mirror of
https://github.com/ReCloudStudio/WebHooker.git
synced 2026-09-22 16:11:29 +00:00
feat: migrate to Nuxt 4 (Nitro) and Tailwind CSS v3
This commit is contained in:
parent
f4959eebf8
commit
b139712a91
166 changed files with 19790 additions and 5539 deletions
154
app/components/WebhookPanel.vue
Normal file
154
app/components/WebhookPanel.vue
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
<template>
|
||||
<section class="members-panel webhook-panel">
|
||||
<div class="panel-head">
|
||||
<h3>{{ t("webhook.title") }}</h3>
|
||||
<span class="lbl-note">{{ t("webhook.note") }}</span>
|
||||
</div>
|
||||
|
||||
<p v-if="!info" class="empty-log">{{ t("webhook.empty") }}</p>
|
||||
|
||||
<template v-else>
|
||||
<div class="wh-row">
|
||||
<span class="wh-label">{{ t("webhook.url") }}</span>
|
||||
<code class="wh-value">{{ info.url }}</code>
|
||||
<button class="btn btn-ghost btn-sm" @click="copy(info.url, 'url')">
|
||||
{{ copiedUrl ? t("webhook.copied") : t("webhook.copy") }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="wh-row">
|
||||
<span class="wh-label">{{ t("webhook.secret") }}</span>
|
||||
<code class="wh-value">{{
|
||||
info.secret ? info.secret : info.hasSecret ? maskedSecret : t("webhook.noSecret")
|
||||
}}</code>
|
||||
<button
|
||||
v-if="info.secret"
|
||||
class="btn btn-ghost btn-sm"
|
||||
@click="copy(info.secret!, 'secret')"
|
||||
>
|
||||
{{ copiedSecret ? t("webhook.copied") : t("webhook.copy") }}
|
||||
</button>
|
||||
</div>
|
||||
<p v-if="info.hasSecret && !info.secret" class="hint">{{ t("webhook.secretHidden") }}</p>
|
||||
|
||||
<div class="wh-actions">
|
||||
<button class="btn btn-accent btn-sm" :disabled="busy" @click="onRegenerate">
|
||||
{{ info.hasSecret ? t("webhook.regenerate") : t("webhook.generate") }}
|
||||
</button>
|
||||
<button
|
||||
v-if="info.hasSecret"
|
||||
class="btn btn-ghost btn-sm"
|
||||
:disabled="busy"
|
||||
@click="onDisable"
|
||||
>
|
||||
{{ t("webhook.disable") }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<details class="wh-usage">
|
||||
<summary>{{ t("webhook.usageTitle") }}</summary>
|
||||
<p class="hint">{{ t("webhook.usageGitHub") }}</p>
|
||||
<p class="hint">{{ t("webhook.usageGitea") }}</p>
|
||||
<p class="hint">{{ t("webhook.usageCustom") }}</p>
|
||||
<pre class="wh-code">{{ customExample }}</pre>
|
||||
</details>
|
||||
</template>
|
||||
|
||||
<div class="err">{{ error }}</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from "vue";
|
||||
import { useWebhookApi, type GroupWebhookInfo } from "~/composables/useWebhook";
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const props = defineProps<{ groupId: string; canEdit: boolean }>();
|
||||
|
||||
const api = useWebhookApi();
|
||||
const info = ref<GroupWebhookInfo | null>(null);
|
||||
const copiedUrl = ref(false);
|
||||
const copiedSecret = ref(false);
|
||||
const busy = ref(false);
|
||||
const error = ref("");
|
||||
|
||||
const maskedSecret = "••••••••••••••••";
|
||||
const customExample = [
|
||||
'curl -X POST "$URL" \\',
|
||||
' -H "Content-Type: application/json" \\',
|
||||
' -H "X-WebHooker-Signature: sha256=$(hmac-sha256 "$BODY" "$SECRET")" \\',
|
||||
" -d '{",
|
||||
' "title": "Deploy failed",',
|
||||
' "description": "Prod rollout failed at 12:03 UTC",',
|
||||
' "color": "red",',
|
||||
' "repo": "acme/widget",',
|
||||
' "url": "https://ci.example.com/runs/42",',
|
||||
' "fields": [{ "name": "Env", "value": "prod", "inline": true }]',
|
||||
" }'",
|
||||
].join("\n");
|
||||
|
||||
watch(
|
||||
() => props.groupId,
|
||||
() => {
|
||||
if (!props.canEdit) return;
|
||||
load();
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
async function load(): Promise<void> {
|
||||
error.value = "";
|
||||
try {
|
||||
info.value = await api.info(props.groupId);
|
||||
} catch (err) {
|
||||
error.value = err instanceof Error ? err.message : String(err);
|
||||
info.value = null;
|
||||
}
|
||||
}
|
||||
|
||||
async function onRegenerate(): Promise<void> {
|
||||
busy.value = true;
|
||||
error.value = "";
|
||||
try {
|
||||
info.value = await api.regenerate(props.groupId);
|
||||
copiedSecret.value = false;
|
||||
} catch (err) {
|
||||
error.value = err instanceof Error ? err.message : String(err);
|
||||
} finally {
|
||||
busy.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function onDisable(): Promise<void> {
|
||||
busy.value = true;
|
||||
error.value = "";
|
||||
try {
|
||||
await api.disable(props.groupId);
|
||||
info.value = { url: info.value?.url ?? "", hasSecret: false };
|
||||
} catch (err) {
|
||||
error.value = err instanceof Error ? err.message : String(err);
|
||||
} finally {
|
||||
busy.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function copy(text: string, which: "url" | "secret"): Promise<void> {
|
||||
try {
|
||||
await navigator.clipboard.writeText(text);
|
||||
if (which === "url") {
|
||||
copiedUrl.value = true;
|
||||
window.setTimeout(() => {
|
||||
copiedUrl.value = false;
|
||||
}, 1500);
|
||||
} else {
|
||||
copiedSecret.value = true;
|
||||
window.setTimeout(() => {
|
||||
copiedSecret.value = false;
|
||||
}, 1500);
|
||||
}
|
||||
} catch {
|
||||
// ignore clipboard failures
|
||||
}
|
||||
}
|
||||
</script>
|
||||
Loading…
Add table
Add a link
Reference in a new issue