mirror of
https://github.com/ReCloudStudio/WebHooker.git
synced 2026-09-22 16:11:29 +00:00
feat(filters): fragment presets and code-block send log detail
This commit is contained in:
parent
c090281cb2
commit
2807c97c5f
5 changed files with 105 additions and 8 deletions
|
|
@ -739,6 +739,10 @@
|
|||
@apply font-mono text-[12px] text-muted;
|
||||
}
|
||||
|
||||
.fragments-presets {
|
||||
@apply mb-2 flex flex-col gap-1.5;
|
||||
}
|
||||
|
||||
.fragments-list {
|
||||
@apply mb-2 flex flex-col gap-1.5;
|
||||
}
|
||||
|
|
@ -759,6 +763,10 @@
|
|||
@apply flex items-center gap-2;
|
||||
}
|
||||
|
||||
.log-block {
|
||||
@apply max-h-72 overflow-auto whitespace-pre-wrap break-all rounded-sm border border-border bg-surface-2 p-2.5 font-mono text-[12px] leading-relaxed text-text;
|
||||
}
|
||||
|
||||
/* ---- Toasts ---- */
|
||||
.toasts {
|
||||
@apply pointer-events-none fixed bottom-7 left-1/2 z-[60] flex -translate-x-1/2 flex-col items-center gap-2;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
import { computed, reactive, ref, watch } from "vue";
|
||||
import type { Filter, FilterNode, NamedFragment, Route, RouteTarget, RouteTemplate } from "~/types";
|
||||
import { ROUTE_TEMPLATES } from "~/types";
|
||||
import { FRAGMENT_PRESETS, ROUTE_TEMPLATES } from "~/types";
|
||||
import type { NodeForm } from "~/composables/useFilterNode";
|
||||
import { blankLeafForm, blankNode, nodeToForm, nodeFormToRouteFilters, formToNode } from "~/composables/useFilterNode";
|
||||
|
||||
|
|
@ -197,8 +197,8 @@ async function runTest(): Promise<void> {
|
|||
}
|
||||
}
|
||||
|
||||
function insertFragment(frag: NamedFragment): void {
|
||||
const child = nodeToForm(frag.node);
|
||||
function insertNode(node: FilterNode): void {
|
||||
const child = nodeToForm(node);
|
||||
if (root.value.kind === "all" || root.value.kind === "any") {
|
||||
root.value.children.push(child);
|
||||
} else {
|
||||
|
|
@ -211,6 +211,10 @@ function insertFragment(frag: NamedFragment): void {
|
|||
}
|
||||
}
|
||||
|
||||
function insertFragment(frag: NamedFragment): void {
|
||||
insertNode(frag.node);
|
||||
}
|
||||
|
||||
async function saveAsFragment(): Promise<void> {
|
||||
fragmentError.value = "";
|
||||
const name = fragmentName.value.trim();
|
||||
|
|
@ -407,6 +411,19 @@ watch(
|
|||
|
||||
<section v-if="groupId" class="editor-section">
|
||||
<h3 class="editor-section-title">{{ t("routeEditor.fragments") }}</h3>
|
||||
<div class="fragments-presets">
|
||||
<span class="hint">{{ t("routeEditor.fragmentsPresets") }}</span>
|
||||
<div v-for="preset in FRAGMENT_PRESETS" :key="preset.id" class="fragment-row">
|
||||
<span class="fragment-name">{{ t(preset.nameKey) }}</span>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-ghost fragment-action"
|
||||
@click="insertNode(preset.node)"
|
||||
>
|
||||
{{ t("routeEditor.fragmentsInsert") }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="fragments-list">
|
||||
<div v-if="!fragments.length" class="hint">{{ t("routeEditor.fragmentsEmpty") }}</div>
|
||||
<div v-for="frag in fragments" :key="frag.id" class="fragment-row">
|
||||
|
|
|
|||
|
|
@ -69,7 +69,8 @@
|
|||
<template v-for="row in detailRows" :key="row.label">
|
||||
<dt>{{ row.label }}</dt>
|
||||
<dd>
|
||||
<code v-if="row.code">{{ row.value }}</code>
|
||||
<pre v-if="row.block" class="log-block">{{ row.value }}</pre>
|
||||
<code v-else-if="row.code">{{ row.value }}</code>
|
||||
<span v-else>{{ row.value }}</span>
|
||||
</dd>
|
||||
</template>
|
||||
|
|
@ -107,8 +108,8 @@ const detailError = ref("");
|
|||
|
||||
const detailRows = computed(() => {
|
||||
const l = detail.value;
|
||||
if (!l) return [] as Array<{ label: string; value: string; code?: boolean }>;
|
||||
const rows: Array<{ label: string; value: string; code?: boolean }> = [
|
||||
if (!l) return [] as Array<{ label: string; value: string; code?: boolean; block?: boolean }>;
|
||||
const rows: Array<{ label: string; value: string; code?: boolean; block?: boolean }> = [
|
||||
{ label: t("logs.id"), value: String(l.id ?? "-"), code: true },
|
||||
{ label: t("logs.time"), value: fmtTime(l.ts) },
|
||||
{ label: t("logs.route"), value: l.routeId, code: true },
|
||||
|
|
@ -125,13 +126,13 @@ const detailRows = computed(() => {
|
|||
{ label: t("logs.duration"), value: l.durationMs != null ? `${l.durationMs} ms` : "-" },
|
||||
{ label: t("logs.attempts"), value: l.attempts != null ? String(l.attempts) : "-" },
|
||||
{ label: t("logs.ok"), value: l.ok ? t("logs.yes") : t("logs.no") },
|
||||
{ label: t("logs.error"), value: l.error || "-" },
|
||||
{ label: t("logs.error"), value: l.error || "-", block: true },
|
||||
];
|
||||
if (l.detail && Object.keys(l.detail).length > 0) {
|
||||
rows.push({
|
||||
label: t("logs.detail"),
|
||||
value: JSON.stringify(l.detail, null, 2),
|
||||
code: true,
|
||||
block: true,
|
||||
});
|
||||
}
|
||||
return rows;
|
||||
|
|
|
|||
|
|
@ -256,6 +256,7 @@ const en: Dict = {
|
|||
"routeEditor.testNotMatched": "Does not match",
|
||||
"routeEditor.testInvalidJson": "Invalid JSON",
|
||||
"routeEditor.fragments": "Fragments",
|
||||
"routeEditor.fragmentsPresets": "Presets",
|
||||
"routeEditor.fragmentsEmpty": "No fragments yet",
|
||||
"routeEditor.fragmentsInsert": "Insert",
|
||||
"routeEditor.fragmentsDelete": "Delete",
|
||||
|
|
@ -265,6 +266,11 @@ const en: Dict = {
|
|||
"routeEditor.errPath": "Field filters need a path",
|
||||
"routeEditor.errValues": "Add at least one value",
|
||||
"routeEditor.errGroupEmpty": "Group needs at least one condition",
|
||||
"fragmentPreset.prBot": "PR by a bot",
|
||||
"fragmentPreset.dependabot": "Dependabot PR",
|
||||
"fragmentPreset.releasePublished": "Release published",
|
||||
"fragmentPreset.pushMain": "Push to main",
|
||||
"fragmentPreset.issueBug": "Issue labeled bug",
|
||||
"groupEditor.editTitle": "Edit group",
|
||||
"groupEditor.newTitle": "New group",
|
||||
"groupEditor.eyebrow": "Group",
|
||||
|
|
@ -627,6 +633,7 @@ const zh: Dict = {
|
|||
"routeEditor.testNotMatched": "不匹配",
|
||||
"routeEditor.testInvalidJson": "无效的 JSON",
|
||||
"routeEditor.fragments": "过滤器片段",
|
||||
"routeEditor.fragmentsPresets": "预设",
|
||||
"routeEditor.fragmentsEmpty": "暂无片段",
|
||||
"routeEditor.fragmentsInsert": "插入",
|
||||
"routeEditor.fragmentsDelete": "删除",
|
||||
|
|
@ -636,6 +643,11 @@ const zh: Dict = {
|
|||
"routeEditor.errPath": "字段过滤需要路径",
|
||||
"routeEditor.errValues": "至少需要一个值",
|
||||
"routeEditor.errGroupEmpty": "分组至少需要一个条件",
|
||||
"fragmentPreset.prBot": "机器人提交的 PR",
|
||||
"fragmentPreset.dependabot": "Dependabot PR",
|
||||
"fragmentPreset.releasePublished": "发布 Release",
|
||||
"fragmentPreset.pushMain": "推送到 main 分支",
|
||||
"fragmentPreset.issueBug": "标记 bug 的 Issue",
|
||||
"groupEditor.editTitle": "编辑分组",
|
||||
"groupEditor.newTitle": "新建分组",
|
||||
"groupEditor.eyebrow": "分组",
|
||||
|
|
|
|||
59
app/types.ts
59
app/types.ts
|
|
@ -208,6 +208,65 @@ export const ROUTE_TEMPLATES: RouteTemplate[] = [
|
|||
},
|
||||
];
|
||||
|
||||
export interface FragmentPreset {
|
||||
id: string;
|
||||
nameKey: string;
|
||||
node: FilterNode;
|
||||
}
|
||||
|
||||
export const FRAGMENT_PRESETS: FragmentPreset[] = [
|
||||
{
|
||||
id: "preset-pr-bot",
|
||||
nameKey: "fragmentPreset.prBot",
|
||||
node: {
|
||||
all: [
|
||||
{ type: "event", match: "pull_request" },
|
||||
{ type: "field", path: "pull_request.user.type", match: "Bot" },
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "preset-dependabot",
|
||||
nameKey: "fragmentPreset.dependabot",
|
||||
node: {
|
||||
all: [
|
||||
{ type: "event", match: "pull_request" },
|
||||
{ type: "field", path: "pull_request.user.login", match: "dependabot[bot]" },
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "preset-release-published",
|
||||
nameKey: "fragmentPreset.releasePublished",
|
||||
node: {
|
||||
all: [
|
||||
{ type: "event", match: "release" },
|
||||
{ type: "action", match: "published" },
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "preset-push-main",
|
||||
nameKey: "fragmentPreset.pushMain",
|
||||
node: {
|
||||
all: [
|
||||
{ type: "event", match: "push" },
|
||||
{ type: "branch", match: "main" },
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "preset-issue-bug",
|
||||
nameKey: "fragmentPreset.issueBug",
|
||||
node: {
|
||||
all: [
|
||||
{ type: "event", match: "issues" },
|
||||
{ type: "field", path: "issue.labels.name", match: "bug" },
|
||||
],
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export const FILTER_TYPES = ["event", "repo", "actor", "action", "branch", "keyword", "field"] as const;
|
||||
|
||||
export const FILTER_OPS: FilterOp[] = [
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue