@cloud-cli/on 1.47.1 → 1.47.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/help.d.ts.map +1 -1
- package/dist/on.js +6 -5
- package/package.json +1 -1
package/dist/help.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"help.d.ts","sourceRoot":"","sources":["../src/help.ts"],"names":[],"mappings":"AAmCA,wBAAgB,cAAc,CAAC,KAAK,UAAQ,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"help.d.ts","sourceRoot":"","sources":["../src/help.ts"],"names":[],"mappings":"AAmCA,wBAAgB,cAAc,CAAC,KAAK,UAAQ,GAAG,MAAM,CAqDpD"}
|
package/dist/on.js
CHANGED
|
@@ -5610,6 +5610,7 @@ function Vi(e = !1) {
|
|
|
5610
5610
|
${e ? "" : "<app-header title=\"Workflow Documentation\" subtitle=\"\" back=\"/runs\" back-label=\"Back to dashboard\"></app-header>"}
|
|
5611
5611
|
<main><div class="help-layout"><article>${o}</article>${e ? "" : c}</div></main>
|
|
5612
5612
|
<script type="module">import '@li3/web';<\/script>
|
|
5613
|
+
<script src="https://cdn.tailwindcss.com"><\/script>
|
|
5613
5614
|
</body>
|
|
5614
5615
|
</html>`;
|
|
5615
5616
|
}
|
|
@@ -6268,7 +6269,7 @@ function Ki(e = "tokens") {
|
|
|
6268
6269
|
}
|
|
6269
6270
|
//#endregion
|
|
6270
6271
|
//#region src/app-header.html?raw
|
|
6271
|
-
var qi = "<template shadow-dom=\"open\" component=\"app-header\">\n <link rel=\"component\" href=\"https://sodium.static.apphor.de/lucide-icon.html\" />\n <style>\n :host { display: block; }\n header { align-items: center; border-bottom: 1px solid #1f2937; display: flex; gap: 1rem; justify-content: space-between; margin: 0 auto; max-width: 78rem; padding-bottom: 1.25rem; width: 100%; }\n .identity { align-items: flex-start; display: flex; gap: .75rem; min-width: 0; }\n .back { align-items: center; border: 1px solid #1f2937; border-radius: .5rem; color: #9ca3af; display: inline-flex; height: 2.25rem; justify-content: center; margin-top: .1rem; text-decoration: none; width: 2.25rem; }\n .back:hover, .back:focus-visible { border-color: #6366f1; color: #e0e7ff; outline: none; }\n .eyebrow { color: #818cf8; font-size: .7rem; font-weight: 700; letter-spacing: .16em; text-transform: uppercase; }\n h1 { color: #fff; font-size: 1.5rem; font-weight: 700; letter-spacing: -.02em; line-height: 1.2; margin: .25rem 0 0; }\n p { color: #9ca3af; font-size: .75rem; margin: .35rem 0 0; }\n .actions { align-items: center; display: flex; gap: .5rem; }\n .action { align-items: center; border: 1px solid #374151; border-radius: 999px; color: #c7d2fe; display: inline-flex; height: 2.25rem; justify-content: center; text-decoration: none; width: 2.25rem; }\n .action:hover, .action:focus-visible { background: #111827; border-color: #6366f1; color: #fff; outline: none; }\n </style>\n <header>\n <div class=\"identity\">\n <template if=\"back\">\n <a class=\"back\" attr-href=\"back\" attr-aria-label=\"backLabel || 'Back'\" attr-title=\"backLabel || 'Back'\">\n <svg aria-hidden=\"true\" viewBox=\"0 0 24 24\" width=\"17\" height=\"17\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"m15 18-6-6 6-6\"></path></svg>\n </a>\n </template>\n <div>\n <div class=\"eyebrow\">Runner engine</div>\n <h1>{{ title }}</h1>\n <p>{{ subtitle }}</p>\n </div>\n </div>\n <div class=\"actions\">\n <a class=\"action\" href=\"/help\" aria-label=\"Help\" title=\"Help\">\n <svg aria-hidden=\"true\" viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"9\"></circle><path d=\"M9.6 9a2.5 2.5 0 1 1 4.2 1.8c-1.2 1-1.8 1.4-1.8 2.7\"></path><path d=\"M12 17h.01\"></path></svg>\n </a>\n <a class=\"action\" href=\"/settings\" aria-label=\"Settings\" title=\"Settings\">\n <lucide-icon icon=\"settings\" size=\"16\"></lucide-icon>\n </a>\n </div>\n </header>\n <script setup>\n import { defineProp } from '@li3/web';\n export default function () {\n return { title: defineProp('title'), subtitle: defineProp('subtitle'), back: defineProp('back'), backLabel: defineProp('back-label') };\n }\n <\/script>\n</template>\n", Ji = "<!doctype html>\n<html lang=\"en\" class=\"dark\">\n <head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <meta name=\"theme-color\" content=\"#030712\" />\n <meta name=\"mobile-web-app-capable\" content=\"yes\" />\n <link rel=\"manifest\" href=\"/manifest.webmanifest\" />\n <link rel=\"icon\" href=\"/app-icon.svg\" type=\"image/svg+xml\" />\n <title>Runner Workflow Management</title>\n <script type=\"importmap\">\n { \"imports\": { \"@li3/\": \"https://cdn.li3.dev/@li3/\" } }\n <\/script>\n <style>\n body[data-page='list'] .editor-panel {\n display: none;\n }\n body[data-page='workflows'] .editor-panel,\n body[data-page='workflows'] .secrets-panel,\n body[data-page='secrets'] .workflow-list,\n body[data-page='secrets'] .editor-panel {\n display: none;\n }\n body[data-page='editor'] .workflow-list,\n body[data-page='editor'] .secrets-panel {\n display: none;\n }\n </style>\n </head>\n <body data-page=\"__WORKFLOW_PAGE__\" class=\"min-h-screen bg-gray-950 p-4 font-sans text-gray-100 md:p-6\">\n <link rel=\"component\" href=\"https://sodium.static.apphor.de/code-editor.html\" />\n <link rel=\"component\" href=\"https://sodium.static.apphor.de/code-block.html\" />\n <link rel=\"component\" href=\"https://sodium.static.apphor.de/lucide-icon.html\" />\n <link rel=\"component\" href=\"/app-header.html\" />\n\n <template app>\n <main class=\"mx-auto max-w-6xl space-y-5\">\n <template if=\"page === 'secrets'\"><app-header title=\"Secrets\" subtitle=\"Write-only credentials available to workflows\" back=\"/runs\" back-label=\"Back to dashboard\"></app-header></template>\n <template if=\"page !== 'secrets'\"><app-header title=\"Workflow control room\" subtitle=\"Draft, validate, and publish revisioned YAML definitions\" back=\"/runs\" back-label=\"Back to dashboard\"></app-header></template>\n <nav class=\"flex flex-wrap gap-1 rounded-xl border border-gray-800 bg-gray-900/70 p-1\" aria-label=\"Settings navigation\">\n <a href=\"/settings/workflows\" class=\"rounded-lg px-3 py-2 text-sm text-gray-300 hover:bg-gray-800\" bind-class=\"page === 'workflows' || page === 'editor' ? 'bg-gray-800.text-white' : ''\">Workflows</a>\n <a href=\"/settings/secrets\" class=\"rounded-lg px-3 py-2 text-sm text-gray-300 hover:bg-gray-800\" bind-class=\"page === 'secrets' ? 'bg-gray-800.text-white' : ''\">Secrets</a>\n <a href=\"/settings/tokens\" class=\"rounded-lg px-3 py-2 text-sm text-gray-300 hover:bg-gray-800\">API tokens</a>\n <a href=\"/settings/notifications\" class=\"rounded-lg px-3 py-2 text-sm text-gray-300 hover:bg-gray-800\">Notifications</a>\n </nav>\n\n <template if=\"notice\">\n <div\n role=\"status\"\n class=\"flex items-center gap-2 rounded-lg border px-3 py-2.5 text-sm\"\n bind-class=\"noticeError ? 'border-rose-500/40 bg-rose-500/10 text-rose-100' : 'border-emerald-500/40 bg-emerald-500/10 text-emerald-100'\"\n >\n <lucide-icon bind-icon=\"noticeError ? 'circle-alert' : 'circle-check'\" size=\"17\"></lucide-icon>\n <span>{{ notice }}</span>\n </div>\n </template>\n\n <section id=\"workflow-layout\" class=\"space-y-5\">\n <nav\n class=\"workflow-list rounded-2xl border border-gray-800 bg-gray-900/80 p-4 shadow-xl shadow-black/10\"\n aria-labelledby=\"workflow-list-title\"\n >\n <div class=\"mb-4 flex items-end justify-between gap-3 border-b border-gray-800 pb-4\">\n <div>\n <p class=\"text-[11px] font-semibold uppercase tracking-[0.18em] text-indigo-400\">Definitions</p>\n <h2 id=\"workflow-list-title\" class=\"mt-1 text-lg font-semibold text-white\">Workflows</h2>\n </div>\n <a\n href=\"/workflows/new\"\n class=\"inline-flex items-center gap-2 rounded-lg bg-indigo-500 px-3 py-2 text-sm font-semibold text-white outline-none transition hover:bg-indigo-400 focus-visible:ring-2 focus-visible:ring-indigo-400/60\"\n >\n <lucide-icon icon=\"plus\" size=\"16\"></lucide-icon>\n New workflow\n </a>\n </div>\n <ul class=\"grid gap-2\" aria-label=\"Workflow definitions\">\n <template for=\"workflow of workflows\">\n <li>\n <a\n attr-href=\"'/workflows/' + workflow.id\"\n class=\"group flex min-h-16 items-center gap-3 rounded-xl border border-gray-800 bg-gray-950/50 px-3.5 py-3 text-left text-sm text-gray-300 outline-none transition hover:border-indigo-500/50 hover:bg-indigo-500/10 focus-visible:border-indigo-400 focus-visible:ring-2 focus-visible:ring-indigo-400/50\"\n >\n <span\n class=\"flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-gray-800 text-gray-400 transition group-hover:bg-indigo-500/15 group-hover:text-indigo-300\"\n >\n <lucide-icon icon=\"workflow\" size=\"17\"></lucide-icon>\n </span>\n <span class=\"min-w-0 flex-1\">\n <span class=\"block truncate text-base font-semibold text-gray-100\">{{ workflow.name }}</span>\n <span class=\"mt-0.5 block truncate font-mono text-[11px] text-gray-500\">{{ workflow.id }}</span>\n </span>\n <span\n class=\"rounded-full border px-2 py-1 text-[10px] font-semibold uppercase tracking-wide\"\n bind-class=\"workflow.status === 'published' ? 'border-emerald-500/30 bg-emerald-500/10 text-emerald-300' : workflow.status === 'archived' ? 'border-gray-700 bg-gray-800 text-gray-500' : 'border-amber-500/30 bg-amber-500/10 text-amber-300'\"\n >{{ workflow.status }}</span\n >\n <lucide-icon\n icon=\"arrow-up-right\"\n size=\"16\"\n class=\"shrink-0 text-gray-600 transition group-hover:text-indigo-300\"\n ></lucide-icon>\n </a>\n </li>\n </template>\n </ul>\n <div\n class=\"rounded-xl border border-dashed border-gray-700 px-4 py-8 text-center\"\n class-hidden=\"workflows.length > 0\"\n >\n <lucide-icon icon=\"workflow\" size=\"22\" class=\"mx-auto text-gray-600\"></lucide-icon>\n <p class=\"mt-2 text-sm font-medium text-gray-300\">No workflows yet</p>\n <p class=\"mt-1 text-xs text-gray-500\">Create a definition to start automating jobs.</p>\n </div>\n </nav>\n\n <section\n class=\"editor-panel overflow-hidden rounded-xl border border-gray-800 bg-gray-900/80 shadow-xl shadow-black/10\"\n >\n <div class=\"border-b border-gray-800 px-4 pt-3 sm:px-5\">\n <a\n href=\"/workflows\"\n class=\"inline-flex items-center gap-1.5 rounded-md px-2 py-1.5 text-xs font-medium text-gray-400 outline-none transition hover:bg-gray-800 hover:text-gray-100 focus-visible:ring-2 focus-visible:ring-indigo-400/60\"\n >\n <lucide-icon icon=\"arrow-left\" size=\"14\"></lucide-icon>\n Back to workflows\n </a>\n </div>\n <div class=\"border-b border-gray-800 bg-gray-900 px-4 py-4 sm:px-5\">\n <div class=\"flex flex-col gap-4 lg:flex-row lg:items-end lg:justify-between\">\n <label class=\"block max-w-xl flex-1 text-xs font-semibold uppercase tracking-wider text-gray-400\"\n >Workflow ID\n <input\n bind-value=\"workflowId\"\n on-input=\"setWorkflowId($event)\"\n bind-disabled=\"selectedId\"\n aria-describedby=\"workflow-id-help\"\n class=\"mt-2 w-full rounded-lg border border-gray-700 bg-gray-950 px-3 py-2.5 font-mono text-sm normal-case tracking-normal text-white outline-none transition focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500/20\"\n placeholder=\"derived-from-workflow-name\"\n />\n <span\n id=\"workflow-id-help\"\n class=\"mt-1 block text-[11px] font-normal normal-case tracking-normal text-gray-500\"\n >The ID becomes permanent after the first save.</span\n >\n </label>\n <label class=\"inline-flex items-center gap-2 text-sm text-gray-300\">\n <input\n type=\"checkbox\"\n bind-checked=\"enabled\"\n on-change=\"setEnabled($event)\"\n class=\"h-4 w-4 rounded border-gray-700 bg-gray-950 text-indigo-500 focus:ring-2 focus:ring-indigo-500/30\"\n />\n Enabled\n </label>\n <div class=\"flex flex-wrap gap-2\" class-hidden=\"readOnly\">\n <button\n type=\"button\"\n aria-label=\"Save workflow draft\"\n title=\"Save workflow draft\"\n on-click=\"save()\"\n bind-disabled=\"busy\"\n class=\"inline-flex items-center gap-2 rounded-lg bg-indigo-500 px-3 py-2.5 text-sm font-semibold text-white transition hover:bg-indigo-400 disabled:cursor-not-allowed disabled:opacity-50\"\n >\n <lucide-icon icon=\"save\" size=\"16\"></lucide-icon>\n <span class=\"hidden sm:inline\">Save draft</span>\n </button>\n <button\n type=\"button\"\n aria-label=\"Publish workflow\"\n title=\"Publish workflow\"\n on-click=\"publish()\"\n bind-disabled=\"busy || !selectedId || source !== savedSource || enabled !== savedEnabled\"\n class=\"inline-flex items-center gap-2 rounded-lg border border-emerald-500/50 px-3 py-2.5 text-sm font-medium text-emerald-300 transition hover:bg-emerald-500/10 disabled:cursor-not-allowed disabled:opacity-50\"\n >\n <lucide-icon icon=\"rocket\" size=\"16\"></lucide-icon>\n <span class=\"hidden sm:inline\">Publish</span>\n </button>\n <button\n type=\"button\"\n aria-label=\"Delete workflow\"\n title=\"Delete workflow\"\n on-click=\"remove()\"\n bind-disabled=\"busy\"\n class=\"inline-flex items-center gap-2 rounded-lg border border-rose-500/50 px-3 py-2.5 text-sm font-medium text-rose-300 transition hover:bg-rose-500/10 disabled:cursor-not-allowed disabled:opacity-50\"\n >\n <lucide-icon icon=\"trash\" size=\"16\"></lucide-icon>\n <span class=\"hidden sm:inline\">Delete</span>\n </button>\n </div>\n </div>\n </div>\n <div class=\"p-3 sm:p-5\">\n <div class=\"mb-2 flex items-center justify-between px-1 text-xs text-gray-500\">\n <div class=\"flex items-center gap-2\">\n <button\n type=\"button\"\n aria-label=\"Validate workflow\"\n title=\"Validate workflow\"\n on-click=\"validate(1)\"\n bind-disabled=\"validation.running\"\n bind-class=\"{ 'hidden': readOnly, 'text-emerald-400': validation.valid, 'text-rose-400': !validation.valid }\"\n class=\"disabled:cursor-not-allowed disabled:opacity-50\"\n >\n <lucide-icon\n class=\"inline-block\"\n bind-icon=\"validation.running ? 'loader-circle' : (validation.valid ? 'circle-check' : 'circle-alert')\"\n bind-class=\"validation.running ? 'animate-spin' : ''\"\n size=\"16\"\n ></lucide-icon>\n </button>\n <span class=\"font-medium\" bind-class=\"validation.error ? 'bg-rose-500/10 text-rose-100' : ''\"\n >{{ validation.error || '' }}</span\n >\n </div>\n <template if=\"revision\">\n <button\n type=\"button\"\n on-click=\"openRevisionDialog()\"\n class=\"font-mono text-indigo-300 outline-none hover:underline focus-visible:rounded focus-visible:ring-2 focus-visible:ring-indigo-400/60\"\n >Revision {{ revision }}</button>\n </template>\n <template if=\"!revision\"><span class=\"font-mono\">Unsaved draft</span></template>\n </div>\n <template if=\"readOnly\">\n <code-block\n language=\"yaml\"\n theme=\"atom-one-dark\"\n class=\"block h-[calc(100vh-23rem)] min-h-[28rem] overflow-auto rounded-lg border border-gray-700 bg-gray-950 p-4\"\n bind-source=\"source\"\n ></code-block>\n </template>\n <template if=\"!readOnly\">\n <code-editor\n id=\"source-yaml\"\n language=\"yaml\"\n theme=\"atom-one-dark\"\n nostatus=\"true\"\n class=\"block h-[calc(100vh-23rem)] min-h-[28rem] overflow-hidden rounded-lg border border-gray-700 bg-gray-950\"\n bind-value=\"source\"\n on-change=\"setSource($event.detail)\"\n ></code-editor>\n </template>\n <details\n class=\"mt-4 overflow-hidden rounded-xl border border-gray-800 bg-gray-950/50\"\n on-toggle=\"loadHelp($event)\"\n >\n <summary\n class=\"flex cursor-pointer list-none items-center gap-2 px-4 py-3 text-sm font-medium text-gray-300 outline-none transition hover:bg-gray-900 focus-visible:ring-2 focus-visible:ring-indigo-400/60\"\n >\n <span class=\"inline-flex items-center gap-2\"\n ><lucide-icon icon=\"circle-question-mark\" size=\"16\"></lucide-icon>Syntax</span\n >\n <span class=\"ml-2 text-xs font-normal text-gray-500\">Reference, tutorials, and examples</span>\n </summary>\n <div\n ref=\"helpContent\"\n class=\"max-h-[38rem] overflow-y-auto border-t border-gray-800 bg-gray-950 px-4 py-5\"\n role=\"region\"\n aria-label=\"Workflow syntax help\"\n aria-live=\"polite\"\n >\n <p class=\"text-sm text-gray-500\">Expand to load the workflow syntax reference.</p>\n </div>\n </details>\n </div>\n </section>\n </section>\n\n <template if=\"revisionDialogOpen\">\n <div class=\"fixed inset-0 z-50 flex items-center justify-center bg-gray-950/80 p-4\" role=\"presentation\">\n <div\n class=\"flex max-h-[90vh] w-full max-w-4xl flex-col overflow-hidden rounded-2xl border border-gray-700 bg-gray-900 shadow-2xl\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"revision-dialog-title\"\n >\n <div class=\"flex items-center justify-between border-b border-gray-800 px-4 py-3\">\n <div>\n <h2 id=\"revision-dialog-title\" class=\"font-semibold text-white\">Workflow revision {{ revisionNumber }}</h2>\n <p class=\"text-xs text-gray-500\">Read-only source snapshot</p>\n </div>\n <button type=\"button\" on-click=\"closeRevisionDialog()\" aria-label=\"Close revision dialog\" class=\"rounded-lg p-2 text-gray-400 outline-none hover:bg-gray-800 hover:text-white focus-visible:ring-2 focus-visible:ring-indigo-400/60\">\n <lucide-icon icon=\"x\" size=\"17\"></lucide-icon>\n </button>\n </div>\n <div class=\"min-h-0 flex-1 overflow-auto p-4\">\n <p class=\"mb-3 text-sm text-rose-300\" class-hidden=\"!revisionError\">{{ revisionError }}</p>\n <code-block language=\"yaml\" theme=\"atom-one-dark\" bind-source=\"revisionSource\" class=\"block min-h-[20rem] overflow-auto rounded-lg border border-gray-700 bg-gray-950 p-4\"></code-block>\n </div>\n <div class=\"flex items-center justify-between border-t border-gray-800 px-4 py-3\">\n <button type=\"button\" on-click=\"navigateRevision(-1)\" bind-disabled=\"revisionLoading || revisionNumber <= 1\" class=\"inline-flex items-center gap-2 rounded-lg border border-gray-700 px-3 py-2 text-sm text-gray-200 outline-none hover:bg-gray-800 focus-visible:ring-2 focus-visible:ring-indigo-400/60 disabled:opacity-40\">\n <lucide-icon icon=\"chevron-left\" size=\"16\"></lucide-icon>Previous\n </button>\n <button type=\"button\" on-click=\"navigateRevision(1)\" bind-disabled=\"revisionLoading\" class=\"inline-flex items-center gap-2 rounded-lg border border-gray-700 px-3 py-2 text-sm text-gray-200 outline-none hover:bg-gray-800 focus-visible:ring-2 focus-visible:ring-indigo-400/60 disabled:opacity-40\">\n Next<lucide-icon icon=\"chevron-right\" size=\"16\"></lucide-icon>\n </button>\n </div>\n </div>\n </div>\n </template>\n\n <section\n class=\"secrets-panel rounded-2xl border border-gray-800 bg-gray-900/80 p-4 shadow-xl shadow-black/10 sm:p-5\"\n aria-labelledby=\"secrets-title\"\n >\n <div class=\"mb-5 flex items-start justify-between gap-3\">\n <div class=\"flex items-start gap-3\">\n <span class=\"rounded-lg bg-indigo-500/10 p-2 text-indigo-300\"\n ><lucide-icon icon=\"key-round\" size=\"18\"></lucide-icon\n ></span>\n <div>\n <h2 id=\"secrets-title\" class=\"text-lg font-semibold text-white\">Secrets</h2>\n <p class=\"mt-0.5 text-sm text-gray-400\">Write-only credentials available to workflows.</p>\n </div>\n </div>\n <span\n class=\"rounded-full border border-gray-700 bg-gray-950 px-2.5 py-1 text-xs font-mono text-gray-400\"\n aria-label=\"Secret count\"\n >{{ secrets.length }}</span\n >\n </div>\n <details ref=\"secretForm\" class=\"mb-5 overflow-hidden rounded-xl border border-gray-800 bg-gray-950/50\">\n <summary class=\"flex cursor-pointer list-none items-center justify-between px-4 py-3 text-sm font-medium text-gray-300 outline-none hover:bg-gray-900 focus-visible:ring-2 focus-visible:ring-indigo-400/60\">\n <span>Add a secret</span>\n <lucide-icon icon=\"plus\" size=\"17\"></lucide-icon>\n </summary>\n <div class=\"space-y-3 border-t border-gray-800 p-4\">\n <p class=\"text-xs text-gray-500\">Saving an existing name replaces its encrypted value.</p>\n <label class=\"sr-only\" for=\"secret-name\">Secret name</label>\n <input\n id=\"secret-name\"\n bind-value=\"secretName\"\n on-input=\"setSecretName($event)\"\n class=\"rounded-lg border border-gray-700 bg-gray-900 px-3 py-2.5 font-mono text-sm uppercase outline-none transition placeholder:text-gray-600 focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500/30\"\n placeholder=\"NPM_TOKEN\"\n />\n <label class=\"inline-flex items-center gap-2 text-sm text-gray-300\">\n <input type=\"checkbox\" bind-checked=\"fileMode\" on-change=\"setFileMode($event)\" class=\"h-4 w-4 rounded border-gray-700 bg-gray-900 text-indigo-500 focus:ring-2 focus:ring-indigo-500/30\" />\n Binary file secret\n </label>\n <template if=\"!fileMode\">\n <label class=\"sr-only\" for=\"secret-value\">Text secret value</label>\n <input\n id=\"secret-value\"\n ref=\"secretValueInput\"\n bind-value=\"secretValue\"\n on-input=\"setSecretValue($event)\"\n type=\"password\"\n class=\"rounded-lg border border-gray-700 bg-gray-900 px-3 py-2.5 font-mono text-sm outline-none transition placeholder:text-gray-600 focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500/30\"\n placeholder=\"New secret value\"\n />\n </template>\n <template if=\"fileMode\">\n <label class=\"sr-only\" for=\"secret-file\">Binary secret file</label>\n <input\n id=\"secret-file\"\n ref=\"secretFileInput\"\n on-change=\"setSecretFile($event)\"\n type=\"file\"\n class=\"rounded-lg border border-gray-700 bg-gray-900 px-3 py-2 text-xs text-gray-400 file:mr-3 file:rounded file:border-0 file:bg-gray-700 file:px-2 file:py-1 file:text-xs file:text-gray-200 focus:border-indigo-500 focus:outline-none focus:ring-2 focus:ring-indigo-500/30\"\n />\n </template>\n <div>\n <button\n type=\"button\"\n aria-label=\"Save secret value\"\n title=\"Save secret value\"\n on-click=\"saveSecret()\"\n bind-disabled=\"busy\"\n class=\"inline-flex items-center justify-center gap-2 rounded-lg border border-indigo-500/60 px-3 py-2.5 text-sm font-medium text-indigo-200 outline-none transition hover:bg-indigo-500/10 focus-visible:ring-2 focus-visible:ring-indigo-400/60 disabled:opacity-50\"\n >\n <lucide-icon icon=\"lock-keyhole\" size=\"16\"></lucide-icon>Save secret\n </button>\n </div>\n </div>\n </div>\n </details>\n <ul class=\"grid gap-2\" aria-label=\"Saved secrets\">\n <template for=\"name of secrets\">\n <li>\n <div class=\"flex min-h-14 items-center gap-2 rounded-xl border border-gray-800 bg-gray-950/50 px-3.5 py-3\">\n <button\n type=\"button\"\n on-click=\"selectSecret(name)\"\n class=\"group flex min-w-0 flex-1 items-center gap-3 text-left outline-none focus-visible:ring-2 focus-visible:ring-indigo-400/50\"\n aria-label=\"Edit secret {{ name }}\"\n >\n <span\n class=\"flex h-8 w-8 shrink-0 items-center justify-center rounded-lg bg-gray-800 text-gray-500 group-hover:text-indigo-300\"\n ><lucide-icon icon=\"key-round\" size=\"15\"></lucide-icon\n ></span>\n <span class=\"min-w-0 flex-1 truncate font-mono text-sm text-gray-200\">{{ name }}</span>\n <span class=\"text-xs text-gray-500\">Select</span>\n <lucide-icon icon=\"chevron-right\" size=\"16\" class=\"text-gray-600 transition group-hover:text-indigo-300\"></lucide-icon>\n </button>\n <button\n type=\"button\"\n on-click=\"removeSecret(name)\"\n aria-label=\"Delete secret {{ name }}\"\n title=\"Delete secret\"\n class=\"inline-flex h-8 w-8 shrink-0 items-center justify-center rounded-lg text-rose-300 outline-none transition hover:bg-rose-500/10 focus-visible:ring-2 focus-visible:ring-rose-400/60\"\n >\n <lucide-icon icon=\"trash\" size=\"15\"></lucide-icon>\n </button>\n </div>\n </li>\n </template>\n </ul>\n <div\n class=\"rounded-xl border border-dashed border-gray-700 px-4 py-8 text-center\"\n class-hidden=\"secrets.length > 0\"\n >\n <lucide-icon icon=\"key-round\" size=\"22\" class=\"mx-auto text-gray-600\"></lucide-icon>\n <p class=\"mt-2 text-sm font-medium text-gray-300\">No secrets configured</p>\n <p class=\"mt-1 text-xs text-gray-500\">Add one above to make it available to a workflow.</p>\n </div>\n </section>\n </main>\n\n <script setup>\n import { onDestroy, onInit, ref, templateRef } from '@li3/web';\n\n export default function () {\n const page = document.body.dataset.page;\n const initialId = __WORKFLOW_ID__;\n const initialRevision = __WORKFLOW_REVISION__;\n const readOnly = Boolean(initialRevision);\n const workflows = ref([]);\n const secrets = ref([]);\n const source = ref('');\n const validation = ref({ valid: true });\n const savedSource = ref('');\n const revision = ref(null);\n const revisionDialogOpen = ref(false);\n const revisionNumber = ref(0);\n const revisionSource = ref('');\n const revisionError = ref('');\n const revisionLoading = ref(false);\n const workflowId = ref('');\n const enabled = ref(true);\n const savedEnabled = ref(true);\n const selectedId = ref('');\n const secretName = ref('');\n const secretValue = ref('');\n const fileMode = ref(false);\n const secretFileName = ref('');\n const secretFileData = ref('');\n const notice = ref('');\n const noticeError = ref(false);\n const busy = ref(false);\n const secretValueInput = templateRef('secretValueInput');\n const secretFileInput = templateRef('secretFileInput');\n const secretForm = templateRef('secretForm');\n const helpContent = templateRef('helpContent');\n const helpLoaded = ref(false);\n const helpLoading = ref(false);\n let validationTimer;\n\n const showNotice = (message, error = false) => {\n notice.value = message;\n noticeError.value = error;\n };\n\n const api = async (url, options = {}) => {\n const response = await fetch(url, {\n headers: { accept: 'application/json', ...(options.body ? { 'content-type': 'application/json' } : {}) },\n ...options,\n });\n const body = response.status === 204 ? null : await response.json().catch(() => ({}));\n if (!response.ok) throw new Error(body.error || `Request failed: ${response.status}`);\n return body;\n };\n\n const derivedId = () =>\n source.value\n .match(/^\\s*(?:id:\\s*([^\\n]+)|name:\\s*([^\\n]+))/m)\n ?.slice(1)\n .find(Boolean)\n ?.trim()\n .toLowerCase()\n .replace(/[^a-z0-9]/g, '-')\n .replace(/(^-|-$)/g, '') || '';\n\n const run = async (action) => {\n if (busy.value) return;\n\n busy.value = true;\n\n try {\n await action();\n } catch (error) {\n showNotice(error.message, true);\n } finally {\n busy.value = false;\n }\n };\n\n const loadWorkflows = async () => {\n workflows.value = (await api('/api/workflows')).workflows;\n };\n\n const loadSecrets = async () => {\n secrets.value = (await api('/api/secrets')).secrets;\n };\n\n const openWorkflow = async (id) => {\n const workflowUrl = initialRevision\n ? `/api/workflows/${id}?revision=${initialRevision}`\n : `/api/workflows/${id}`;\n const workflow = await api(workflowUrl);\n selectedId.value = workflow.id;\n workflowId.value = workflow.id;\n source.value = workflow.sourceYaml;\n savedSource.value = workflow.sourceYaml;\n enabled.value = workflow.enabled;\n savedEnabled.value = workflow.enabled;\n revision.value = workflow.revision || null;\n };\n\n const validateSource = async () => {\n return api('/api/workflows/validate', {\n method: 'POST',\n body: JSON.stringify({ sourceYaml: source.value }),\n });\n };\n\n const validate = (ms = 1000) => {\n if (page !== 'editor') return;\n\n clearTimeout(validationTimer);\n validationTimer = setTimeout(() => {\n validation.value = { ...validation.value, running: true, error: '' };\n void validateSource()\n .then((result) => {\n validation.value = { ...result, running: false };\n })\n .catch((error) => {\n validation.value = { valid: false, running: false, error: error.message };\n \n });\n }, ms);\n };\n\n const save = () =>\n run(async () => {\n const id = workflowId.value.trim() || derivedId();\n if (!id) throw new Error('Set an ID or add a workflow name first.');\n if (\n initialId &&\n id === selectedId.value &&\n source.value === savedSource.value &&\n enabled.value === savedEnabled.value\n ) {\n showNotice('No changes to save.');\n return;\n }\n const workflow = await api(`/api/workflows/${id}`, {\n method: 'PUT',\n body: JSON.stringify({ sourceYaml: source.value, enabled: enabled.value }),\n });\n selectedId.value = workflow.id;\n workflowId.value = workflow.id;\n savedSource.value = source.value;\n savedEnabled.value = enabled.value;\n revision.value = workflow.revision;\n showNotice(`Saved ${workflow.id} as draft revision ${workflow.revision}.`);\n if (!initialId) history.replaceState(null, '', `/workflows/${workflow.id}`);\n });\n const publish = () =>\n run(async () => {\n const id = workflowId.value.trim() || selectedId.value;\n if (!id) throw new Error('Save a workflow before publishing it.');\n const workflow = await api(`/api/workflows/${id}/publish`, { method: 'POST' });\n selectedId.value = workflow.id;\n revision.value = workflow.revision;\n showNotice(`Published ${workflow.id} revision ${workflow.revision}.`);\n });\n const remove = () =>\n run(async () => {\n const id = workflowId.value.trim() || selectedId.value;\n if (!id || !confirm(`Delete ${id} and all of its revisions?`)) return;\n await api(`/api/workflows/${id}`, { method: 'DELETE' });\n window.location.href = '/workflows';\n });\n const saveSecret = () =>\n run(async () => {\n const name = secretName.value.trim();\n if (!name || (fileMode.value ? !secretFileData.value : !secretValue.value)) throw new Error(fileMode.value ? 'Choose a file first.' : 'Set a secret value first.');\n await api(`/api/secrets/${name}`, {\n method: 'PUT',\n body: JSON.stringify(fileMode.value ? { value: secretFileData.value, encoding: 'base64', originalName: secretFileName.value } : { value: secretValue.value, encoding: 'utf8' }),\n });\n secretValue.value = '';\n secretFileName.value = '';\n secretFileData.value = '';\n fileMode.value = false;\n if (secretFileInput.value) secretFileInput.value.value = '';\n if (secretForm.value) secretForm.value.open = false;\n await loadSecrets();\n showNotice(`Saved ${name}.`);\n });\n const removeSecret = (requestedName = '') =>\n run(async () => {\n const name = requestedName || secretName.value.trim();\n if (!name || !confirm(`Delete ${name}?`)) return;\n await api(`/api/secrets/${name}`, { method: 'DELETE' });\n if (secretName.value.trim() === name) {\n secretName.value = '';\n secretValue.value = '';\n secretFileName.value = '';\n secretFileData.value = '';\n fileMode.value = false;\n if (secretFileInput.value) secretFileInput.value.value = '';\n }\n await loadSecrets();\n showNotice(`Deleted ${name}.`);\n });\n const setSource = (value) => {\n source.value = value;\n validate();\n };\n const setWorkflowId = (event) => {\n workflowId.value = event.target.value;\n };\n const setEnabled = (event) => {\n enabled.value = event.target.checked;\n };\n const setSecretName = (event) => {\n secretName.value = event.target.value;\n };\n const setSecretValue = (event) => {\n secretValue.value = event.target.value;\n fileMode.value = false;\n if (secretValue.value) {\n secretFileName.value = '';\n secretFileData.value = '';\n if (secretFileInput.value) secretFileInput.value.value = '';\n }\n };\n const setFileMode = (event) => {\n fileMode.value = event.target.checked;\n if (!fileMode.value) {\n secretFileName.value = '';\n secretFileData.value = '';\n if (secretFileInput.value) secretFileInput.value.value = '';\n } else {\n secretValue.value = '';\n }\n };\n const setSecretFile = async (event) => {\n const file = event.target.files?.[0];\n if (!file) return;\n const bytes = new Uint8Array(await file.arrayBuffer());\n let binary = '';\n for (let index = 0; index < bytes.length; index += 0x8000) {\n binary += String.fromCharCode(...bytes.subarray(index, index + 0x8000));\n }\n secretFileName.value = file.name;\n secretFileData.value = btoa(binary);\n secretValue.value = '';\n };\n const selectSecret = (name) => {\n secretName.value = name;\n secretValueInput.value?.focus();\n };\n const visitWorkflow = (id) => {\n window.location.href = `/workflows/${id}`;\n };\n const loadHelp = async (event) => {\n if (!event.target.open || helpLoaded.value || helpLoading.value || !helpContent.value) return;\n helpLoading.value = true;\n helpContent.value.textContent = 'Loading workflow syntax help...';\n try {\n const response = await fetch('/help?embed=1', { headers: { accept: 'text/html' } });\n if (!response.ok) throw new Error(`Help request failed: ${response.status}`);\n const html = await response.text();\n const parsed = new DOMParser().parseFromString(html, 'text/html');\n const sourceNodes = [\n ...Array.from(parsed.head.querySelectorAll('style')),\n ...Array.from(parsed.body.childNodes),\n ];\n const nodes = sourceNodes.map((node) => document.importNode(node, true));\n helpContent.value.replaceChildren(...nodes);\n helpLoaded.value = true;\n } catch (error) {\n helpContent.value.textContent = 'Unable to load workflow syntax help.';\n console.error(error);\n } finally {\n helpLoading.value = false;\n }\n };\n\n const openRevisionDialog = () => {\n if (!revision.value) return;\n revisionNumber.value = revision.value;\n revisionSource.value = source.value;\n revisionError.value = '';\n revisionDialogOpen.value = true;\n };\n const closeRevisionDialog = () => {\n revisionDialogOpen.value = false;\n };\n const navigateRevision = async (offset) => {\n if (!selectedId.value || revisionLoading.value) return;\n const target = revisionNumber.value + offset;\n if (target < 1) return;\n revisionLoading.value = true;\n revisionError.value = '';\n try {\n const workflow = await api(`/api/workflows/${selectedId.value}?revision=${target}`);\n revisionNumber.value = workflow.revision;\n revisionSource.value = workflow.sourceYaml;\n } catch (error) {\n revisionError.value = error.message;\n } finally {\n revisionLoading.value = false;\n }\n };\n\n onDestroy(() => clearTimeout(validationTimer));\n\n onInit(() => {\n const loadPage = page === 'editor'\n ? initialId ? openWorkflow(initialId) : Promise.resolve()\n : page === 'secrets' ? loadSecrets() : loadWorkflows();\n loadPage.catch((error) => showNotice(error.message, true));\n });\n\n return {\n page,\n workflows,\n secrets,\n source,\n validation,\n revision,\n revisionDialogOpen,\n revisionNumber,\n revisionSource,\n revisionError,\n revisionLoading,\n workflowId,\n enabled,\n readOnly,\n selectedId,\n secretName,\n secretValue,\n fileMode,\n secretFileName,\n secretFileData,\n notice,\n noticeError,\n busy,\n validate,\n save,\n publish,\n remove,\n saveSecret,\n removeSecret,\n setSource,\n setWorkflowId,\n setEnabled,\n setSecretName,\n setSecretValue,\n setFileMode,\n setSecretFile,\n selectSecret,\n visitWorkflow,\n loadHelp,\n openRevisionDialog,\n closeRevisionDialog,\n navigateRevision,\n };\n }\n <\/script>\n </template>\n\n <script type=\"module\">\n import '@li3/web';\n <\/script>\n <script src=\"https://cdn.tailwindcss.com\"><\/script>\n </body>\n</html>\n";
|
|
6272
|
+
var qi = "<template component=\"app-header\">\n <link rel=\"component\" href=\"https://sodium.static.apphor.de/lucide-icon.html\" />\n <header class=\"mx-auto flex w-full max-w-[78rem] items-center justify-between gap-4 border-b border-gray-800 pb-5\">\n <div class=\"flex min-w-0 items-start gap-3\">\n <template if=\"back\">\n <a class=\"mt-0.5 inline-flex h-9 w-9 items-center justify-center rounded-lg border border-gray-800 text-gray-400 no-underline outline-none hover:border-indigo-500 hover:text-indigo-200 focus-visible:ring-2 focus-visible:ring-indigo-400/60\" attr-href=\"back\" attr-aria-label=\"backLabel || 'Back'\" attr-title=\"backLabel || 'Back'\">\n <svg aria-hidden=\"true\" viewBox=\"0 0 24 24\" width=\"17\" height=\"17\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"m15 18-6-6 6-6\"></path></svg>\n </a>\n </template>\n <div>\n <div class=\"text-[.7rem] font-bold uppercase tracking-[.16em] text-indigo-400\">Runner engine</div>\n <h1 class=\"mt-1 text-2xl font-bold tracking-tight text-white\">{{ title }}</h1>\n <p class=\"mt-1 text-xs text-gray-400\">{{ subtitle }}</p>\n </div>\n </div>\n <div class=\"flex items-center gap-2\">\n <a class=\"inline-flex h-9 w-9 items-center justify-center rounded-full border border-gray-700 text-indigo-200 no-underline outline-none hover:border-indigo-500 hover:bg-gray-900 hover:text-white focus-visible:ring-2 focus-visible:ring-indigo-400/60\" href=\"/help\" aria-label=\"Help\" title=\"Help\">\n <svg aria-hidden=\"true\" viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"9\"></circle><path d=\"M9.6 9a2.5 2.5 0 1 1 4.2 1.8c-1.2 1-1.8 1.4-1.8 2.7\"></path><path d=\"M12 17h.01\"></path></svg>\n </a>\n <a class=\"inline-flex h-9 w-9 items-center justify-center rounded-full border border-gray-700 text-indigo-200 no-underline outline-none hover:border-indigo-500 hover:bg-gray-900 hover:text-white focus-visible:ring-2 focus-visible:ring-indigo-400/60\" href=\"/settings\" aria-label=\"Settings\" title=\"Settings\">\n <lucide-icon icon=\"settings\" size=\"16\"></lucide-icon>\n </a>\n </div>\n </header>\n <script setup>\n import { defineProp } from '@li3/web';\n export default function () {\n return { title: defineProp('title'), subtitle: defineProp('subtitle'), back: defineProp('back'), backLabel: defineProp('back-label') };\n }\n <\/script>\n</template>\n", Ji = "<!doctype html>\n<html lang=\"en\" class=\"dark\">\n <head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <meta name=\"theme-color\" content=\"#030712\" />\n <meta name=\"mobile-web-app-capable\" content=\"yes\" />\n <link rel=\"manifest\" href=\"/manifest.webmanifest\" />\n <link rel=\"icon\" href=\"/app-icon.svg\" type=\"image/svg+xml\" />\n <title>Runner Workflow Management</title>\n <script type=\"importmap\">\n { \"imports\": { \"@li3/\": \"https://cdn.li3.dev/@li3/\" } }\n <\/script>\n <style>\n body[data-page='list'] .editor-panel {\n display: none;\n }\n body[data-page='workflows'] .editor-panel,\n body[data-page='workflows'] .secrets-panel,\n body[data-page='secrets'] .workflow-list,\n body[data-page='secrets'] .editor-panel {\n display: none;\n }\n body[data-page='editor'] .workflow-list,\n body[data-page='editor'] .secrets-panel {\n display: none;\n }\n </style>\n </head>\n <body data-page=\"__WORKFLOW_PAGE__\" class=\"min-h-screen bg-gray-950 p-4 font-sans text-gray-100 md:p-6\">\n <link rel=\"component\" href=\"https://sodium.static.apphor.de/code-editor.html\" />\n <link rel=\"component\" href=\"https://sodium.static.apphor.de/code-block.html\" />\n <link rel=\"component\" href=\"https://sodium.static.apphor.de/lucide-icon.html\" />\n <link rel=\"component\" href=\"/app-header.html\" />\n\n <template app>\n <main class=\"mx-auto max-w-6xl space-y-5\">\n <template if=\"page === 'secrets'\"><app-header title=\"Secrets\" subtitle=\"Write-only credentials available to workflows\" back=\"/runs\" back-label=\"Back to dashboard\"></app-header></template>\n <template if=\"page !== 'secrets'\"><app-header title=\"Workflow control room\" subtitle=\"Draft, validate, and publish revisioned YAML definitions\" back=\"/runs\" back-label=\"Back to dashboard\"></app-header></template>\n <nav class=\"flex flex-wrap gap-1 rounded-xl border border-gray-800 bg-gray-900/70 p-1\" aria-label=\"Settings navigation\">\n <a href=\"/settings/workflows\" class=\"rounded-lg px-3 py-2 text-sm text-gray-300 hover:bg-gray-800\" bind-class=\"page === 'workflows' || page === 'editor' ? 'bg-gray-800.text-white' : ''\">Workflows</a>\n <a href=\"/settings/secrets\" class=\"rounded-lg px-3 py-2 text-sm text-gray-300 hover:bg-gray-800\" bind-class=\"page === 'secrets' ? 'bg-gray-800.text-white' : ''\">Secrets</a>\n <a href=\"/settings/tokens\" class=\"rounded-lg px-3 py-2 text-sm text-gray-300 hover:bg-gray-800\">API tokens</a>\n <a href=\"/settings/notifications\" class=\"rounded-lg px-3 py-2 text-sm text-gray-300 hover:bg-gray-800\">Notifications</a>\n </nav>\n\n <template if=\"notice\">\n <div\n role=\"status\"\n class=\"flex items-center gap-2 rounded-lg border px-3 py-2.5 text-sm\"\n bind-class=\"noticeError ? 'border-rose-500/40 bg-rose-500/10 text-rose-100' : 'border-emerald-500/40 bg-emerald-500/10 text-emerald-100'\"\n >\n <lucide-icon bind-icon=\"noticeError ? 'circle-alert' : 'circle-check'\" size=\"17\"></lucide-icon>\n <span>{{ notice }}</span>\n </div>\n </template>\n\n <section id=\"workflow-layout\" class=\"space-y-5\">\n <nav\n class=\"workflow-list rounded-2xl border border-gray-800 bg-gray-900/80 p-4 shadow-xl shadow-black/10\"\n aria-labelledby=\"workflow-list-title\"\n >\n <div class=\"mb-4 flex items-end justify-between gap-3 border-b border-gray-800 pb-4\">\n <div>\n <p class=\"text-[11px] font-semibold uppercase tracking-[0.18em] text-indigo-400\">Definitions</p>\n <h2 id=\"workflow-list-title\" class=\"mt-1 text-lg font-semibold text-white\">Workflows</h2>\n </div>\n <a\n href=\"/workflows/new\"\n class=\"inline-flex items-center gap-2 rounded-lg bg-indigo-500 px-3 py-2 text-sm font-semibold text-white outline-none transition hover:bg-indigo-400 focus-visible:ring-2 focus-visible:ring-indigo-400/60\"\n >\n <lucide-icon icon=\"plus\" size=\"16\"></lucide-icon>\n New workflow\n </a>\n </div>\n <ul class=\"grid gap-2\" aria-label=\"Workflow definitions\">\n <template for=\"workflow of workflows\">\n <li>\n <a\n attr-href=\"'/workflows/' + workflow.id\"\n class=\"group flex min-h-16 items-center gap-3 rounded-xl border border-gray-800 bg-gray-950/50 px-3.5 py-3 text-left text-sm text-gray-300 outline-none transition hover:border-indigo-500/50 hover:bg-indigo-500/10 focus-visible:border-indigo-400 focus-visible:ring-2 focus-visible:ring-indigo-400/50\"\n >\n <span\n class=\"flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-gray-800 text-gray-400 transition group-hover:bg-indigo-500/15 group-hover:text-indigo-300\"\n >\n <lucide-icon icon=\"workflow\" size=\"17\"></lucide-icon>\n </span>\n <span class=\"min-w-0 flex-1\">\n <span class=\"block truncate text-base font-semibold text-gray-100\">{{ workflow.name }}</span>\n <span class=\"mt-0.5 block truncate font-mono text-[11px] text-gray-500\">{{ workflow.id }}</span>\n </span>\n <span\n class=\"rounded-full border px-2 py-1 text-[10px] font-semibold uppercase tracking-wide\"\n bind-class=\"workflow.status === 'published' ? 'border-emerald-500/30 bg-emerald-500/10 text-emerald-300' : workflow.status === 'archived' ? 'border-gray-700 bg-gray-800 text-gray-500' : 'border-amber-500/30 bg-amber-500/10 text-amber-300'\"\n >{{ workflow.status }}</span\n >\n <lucide-icon\n icon=\"arrow-up-right\"\n size=\"16\"\n class=\"shrink-0 text-gray-600 transition group-hover:text-indigo-300\"\n ></lucide-icon>\n </a>\n </li>\n </template>\n </ul>\n <div\n class=\"rounded-xl border border-dashed border-gray-700 px-4 py-8 text-center\"\n class-hidden=\"workflows.length > 0\"\n >\n <lucide-icon icon=\"workflow\" size=\"22\" class=\"mx-auto text-gray-600\"></lucide-icon>\n <p class=\"mt-2 text-sm font-medium text-gray-300\">No workflows yet</p>\n <p class=\"mt-1 text-xs text-gray-500\">Create a definition to start automating jobs.</p>\n </div>\n </nav>\n\n <section\n class=\"editor-panel overflow-hidden rounded-xl border border-gray-800 bg-gray-900/80 shadow-xl shadow-black/10\"\n >\n <div class=\"border-b border-gray-800 px-4 pt-3 sm:px-5\">\n <a\n href=\"/workflows\"\n class=\"inline-flex items-center gap-1.5 rounded-md px-2 py-1.5 text-xs font-medium text-gray-400 outline-none transition hover:bg-gray-800 hover:text-gray-100 focus-visible:ring-2 focus-visible:ring-indigo-400/60\"\n >\n <lucide-icon icon=\"arrow-left\" size=\"14\"></lucide-icon>\n Back to workflows\n </a>\n </div>\n <div class=\"border-b border-gray-800 bg-gray-900 px-4 py-4 sm:px-5\">\n <div class=\"flex flex-col gap-4 lg:flex-row lg:items-end lg:justify-between\">\n <label class=\"block max-w-xl flex-1 text-xs font-semibold uppercase tracking-wider text-gray-400\"\n >Workflow ID\n <input\n bind-value=\"workflowId\"\n on-input=\"setWorkflowId($event)\"\n bind-disabled=\"selectedId\"\n aria-describedby=\"workflow-id-help\"\n class=\"mt-2 w-full rounded-lg border border-gray-700 bg-gray-950 px-3 py-2.5 font-mono text-sm normal-case tracking-normal text-white outline-none transition focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500/20\"\n placeholder=\"derived-from-workflow-name\"\n />\n <span\n id=\"workflow-id-help\"\n class=\"mt-1 block text-[11px] font-normal normal-case tracking-normal text-gray-500\"\n >The ID becomes permanent after the first save.</span\n >\n </label>\n <label class=\"inline-flex items-center gap-2 text-sm text-gray-300\">\n <input\n type=\"checkbox\"\n bind-checked=\"enabled\"\n on-change=\"setEnabled($event)\"\n class=\"h-4 w-4 rounded border-gray-700 bg-gray-950 text-indigo-500 focus:ring-2 focus:ring-indigo-500/30\"\n />\n Enabled\n </label>\n <div class=\"flex flex-wrap gap-2\" class-hidden=\"readOnly\">\n <button\n type=\"button\"\n aria-label=\"Save workflow draft\"\n title=\"Save workflow draft\"\n on-click=\"save()\"\n bind-disabled=\"busy\"\n class=\"inline-flex items-center gap-2 rounded-lg bg-indigo-500 px-3 py-2.5 text-sm font-semibold text-white transition hover:bg-indigo-400 disabled:cursor-not-allowed disabled:opacity-50\"\n >\n <lucide-icon icon=\"save\" size=\"16\"></lucide-icon>\n <span class=\"hidden sm:inline\">Save draft</span>\n </button>\n <button\n type=\"button\"\n aria-label=\"Publish workflow\"\n title=\"Publish workflow\"\n on-click=\"publish()\"\n bind-disabled=\"busy || !selectedId || source !== savedSource || enabled !== savedEnabled\"\n class=\"inline-flex items-center gap-2 rounded-lg border border-emerald-500/50 px-3 py-2.5 text-sm font-medium text-emerald-300 transition hover:bg-emerald-500/10 disabled:cursor-not-allowed disabled:opacity-50\"\n >\n <lucide-icon icon=\"rocket\" size=\"16\"></lucide-icon>\n <span class=\"hidden sm:inline\">Publish</span>\n </button>\n <button\n type=\"button\"\n aria-label=\"Delete workflow\"\n title=\"Delete workflow\"\n on-click=\"remove()\"\n bind-disabled=\"busy\"\n class=\"inline-flex items-center gap-2 rounded-lg border border-rose-500/50 px-3 py-2.5 text-sm font-medium text-rose-300 transition hover:bg-rose-500/10 disabled:cursor-not-allowed disabled:opacity-50\"\n >\n <lucide-icon icon=\"trash\" size=\"16\"></lucide-icon>\n <span class=\"hidden sm:inline\">Delete</span>\n </button>\n </div>\n </div>\n </div>\n <div class=\"p-3 sm:p-5\">\n <div class=\"mb-2 flex items-center justify-between px-1 text-xs text-gray-500\">\n <div class=\"flex items-center gap-2\">\n <button\n type=\"button\"\n aria-label=\"Validate workflow\"\n title=\"Validate workflow\"\n on-click=\"validate(1)\"\n bind-disabled=\"validation.running\"\n bind-class=\"{ 'hidden': readOnly, 'text-emerald-400': validation.valid, 'text-rose-400': !validation.valid }\"\n class=\"disabled:cursor-not-allowed disabled:opacity-50\"\n >\n <lucide-icon\n class=\"inline-block\"\n bind-icon=\"validation.running ? 'loader-circle' : (validation.valid ? 'circle-check' : 'circle-alert')\"\n bind-class=\"validation.running ? 'animate-spin' : ''\"\n size=\"16\"\n ></lucide-icon>\n </button>\n <span class=\"font-medium\" bind-class=\"validation.error ? 'bg-rose-500/10 text-rose-100' : ''\"\n >{{ validation.error || '' }}</span\n >\n </div>\n <template if=\"revision\">\n <button\n type=\"button\"\n on-click=\"openRevisionDialog()\"\n class=\"font-mono text-indigo-300 outline-none hover:underline focus-visible:rounded focus-visible:ring-2 focus-visible:ring-indigo-400/60\"\n >Revision {{ revision }}</button>\n </template>\n <template if=\"!revision\"><span class=\"font-mono\">Unsaved draft</span></template>\n </div>\n <template if=\"readOnly\">\n <code-block\n language=\"yaml\"\n theme=\"atom-one-dark\"\n class=\"block h-[calc(100vh-23rem)] min-h-[28rem] overflow-auto rounded-lg border border-gray-700 bg-gray-950 p-4\"\n bind-source=\"source\"\n ></code-block>\n </template>\n <template if=\"!readOnly\">\n <code-editor\n id=\"source-yaml\"\n language=\"yaml\"\n theme=\"atom-one-dark\"\n nostatus=\"true\"\n class=\"block h-[calc(100vh-23rem)] min-h-[28rem] overflow-hidden rounded-lg border border-gray-700 bg-gray-950\"\n bind-value=\"source\"\n on-change=\"setSource($event.detail)\"\n ></code-editor>\n </template>\n <details\n class=\"mt-4 overflow-hidden rounded-xl border border-gray-800 bg-gray-950/50\"\n on-toggle=\"loadHelp($event)\"\n >\n <summary\n class=\"flex cursor-pointer list-none items-center gap-2 px-4 py-3 text-sm font-medium text-gray-300 outline-none transition hover:bg-gray-900 focus-visible:ring-2 focus-visible:ring-indigo-400/60\"\n >\n <span class=\"inline-flex items-center gap-2\"\n ><lucide-icon icon=\"circle-question-mark\" size=\"16\"></lucide-icon>Syntax</span\n >\n <span class=\"ml-2 text-xs font-normal text-gray-500\">Reference, tutorials, and examples</span>\n </summary>\n <div\n ref=\"helpContent\"\n class=\"max-h-[38rem] overflow-y-auto border-t border-gray-800 bg-gray-950 px-4 py-5\"\n role=\"region\"\n aria-label=\"Workflow syntax help\"\n aria-live=\"polite\"\n >\n <p class=\"text-sm text-gray-500\">Expand to load the workflow syntax reference.</p>\n </div>\n </details>\n </div>\n </section>\n </section>\n\n <template if=\"revisionDialogOpen\">\n <div class=\"fixed inset-0 z-50 flex items-center justify-center bg-gray-950/80 p-4\" role=\"presentation\">\n <div\n class=\"flex max-h-[90vh] w-full max-w-4xl flex-col overflow-hidden rounded-2xl border border-gray-700 bg-gray-900 shadow-2xl\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"revision-dialog-title\"\n >\n <div class=\"flex items-center justify-between border-b border-gray-800 px-4 py-3\">\n <div>\n <h2 id=\"revision-dialog-title\" class=\"font-semibold text-white\">Workflow revision {{ revisionNumber }}</h2>\n <p class=\"text-xs text-gray-500\">Read-only source snapshot</p>\n </div>\n <button type=\"button\" on-click=\"closeRevisionDialog()\" aria-label=\"Close revision dialog\" class=\"rounded-lg p-2 text-gray-400 outline-none hover:bg-gray-800 hover:text-white focus-visible:ring-2 focus-visible:ring-indigo-400/60\">\n <lucide-icon icon=\"x\" size=\"17\"></lucide-icon>\n </button>\n </div>\n <div class=\"min-h-0 flex-1 overflow-auto p-4\">\n <p class=\"mb-3 text-sm text-rose-300\" class-hidden=\"!revisionError\">{{ revisionError }}</p>\n <code-block language=\"yaml\" theme=\"atom-one-dark\" bind-source=\"revisionSource\" class=\"block min-h-[20rem] overflow-auto rounded-lg border border-gray-700 bg-gray-950 p-4\"></code-block>\n </div>\n <div class=\"flex items-center justify-between border-t border-gray-800 px-4 py-3\">\n <button type=\"button\" on-click=\"navigateRevision(-1)\" bind-disabled=\"revisionLoading || revisionNumber <= 1\" class=\"inline-flex items-center gap-2 rounded-lg border border-gray-700 px-3 py-2 text-sm text-gray-200 outline-none hover:bg-gray-800 focus-visible:ring-2 focus-visible:ring-indigo-400/60 disabled:opacity-40\">\n <lucide-icon icon=\"chevron-left\" size=\"16\"></lucide-icon>Previous\n </button>\n <button type=\"button\" on-click=\"navigateRevision(1)\" bind-disabled=\"revisionLoading\" class=\"inline-flex items-center gap-2 rounded-lg border border-gray-700 px-3 py-2 text-sm text-gray-200 outline-none hover:bg-gray-800 focus-visible:ring-2 focus-visible:ring-indigo-400/60 disabled:opacity-40\">\n Next<lucide-icon icon=\"chevron-right\" size=\"16\"></lucide-icon>\n </button>\n </div>\n </div>\n </div>\n </template>\n\n <section\n class=\"secrets-panel rounded-2xl border border-gray-800 bg-gray-900/80 p-4 shadow-xl shadow-black/10 sm:p-5\"\n aria-labelledby=\"secrets-title\"\n >\n <div class=\"mb-5 flex items-start justify-between gap-3\">\n <div class=\"flex items-start gap-3\">\n <span class=\"rounded-lg bg-indigo-500/10 p-2 text-indigo-300\"\n ><lucide-icon icon=\"key-round\" size=\"18\"></lucide-icon\n ></span>\n <div>\n <h2 id=\"secrets-title\" class=\"text-lg font-semibold text-white\">Secrets</h2>\n <p class=\"mt-0.5 text-sm text-gray-400\">Write-only credentials available to workflows.</p>\n </div>\n </div>\n <span\n class=\"rounded-full border border-gray-700 bg-gray-950 px-2.5 py-1 text-xs font-mono text-gray-400\"\n aria-label=\"Secret count\"\n >{{ secrets.length }}</span\n >\n </div>\n <details ref=\"secretForm\" class=\"mb-5 overflow-hidden rounded-xl border border-gray-800 bg-gray-950/50\">\n <summary class=\"flex cursor-pointer list-none items-center justify-between px-4 py-3 text-sm font-medium text-gray-300 outline-none hover:bg-gray-900 focus-visible:ring-2 focus-visible:ring-indigo-400/60\">\n <span>Add a secret</span>\n <lucide-icon icon=\"plus\" size=\"17\"></lucide-icon>\n </summary>\n <div class=\"space-y-3 border-t border-gray-800 p-4\">\n <p class=\"text-xs text-gray-500\">Saving an existing name replaces its encrypted value.</p>\n <label class=\"sr-only\" for=\"secret-name\">Secret name</label>\n <input\n id=\"secret-name\"\n bind-value=\"secretName\"\n on-input=\"setSecretName($event)\"\n class=\"rounded-lg border border-gray-700 bg-gray-900 px-3 py-2.5 font-mono text-sm uppercase outline-none transition placeholder:text-gray-600 focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500/30\"\n placeholder=\"NPM_TOKEN\"\n />\n <label class=\"inline-flex items-center gap-2 text-sm text-gray-300\">\n <input type=\"checkbox\" bind-checked=\"fileMode\" on-change=\"setFileMode($event)\" class=\"h-4 w-4 rounded border-gray-700 bg-gray-900 text-indigo-500 focus:ring-2 focus:ring-indigo-500/30\" />\n Binary file secret\n </label>\n <template if=\"!fileMode\">\n <label class=\"sr-only\" for=\"secret-value\">Text secret value</label>\n <input\n id=\"secret-value\"\n ref=\"secretValueInput\"\n bind-value=\"secretValue\"\n on-input=\"setSecretValue($event)\"\n type=\"password\"\n class=\"rounded-lg border border-gray-700 bg-gray-900 px-3 py-2.5 font-mono text-sm outline-none transition placeholder:text-gray-600 focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500/30\"\n placeholder=\"New secret value\"\n />\n </template>\n <template if=\"fileMode\">\n <label class=\"sr-only\" for=\"secret-file\">Binary secret file</label>\n <input\n id=\"secret-file\"\n ref=\"secretFileInput\"\n on-change=\"setSecretFile($event)\"\n type=\"file\"\n class=\"rounded-lg border border-gray-700 bg-gray-900 px-3 py-2 text-xs text-gray-400 file:mr-3 file:rounded file:border-0 file:bg-gray-700 file:px-2 file:py-1 file:text-xs file:text-gray-200 focus:border-indigo-500 focus:outline-none focus:ring-2 focus:ring-indigo-500/30\"\n />\n </template>\n <div>\n <button\n type=\"button\"\n aria-label=\"Save secret value\"\n title=\"Save secret value\"\n on-click=\"saveSecret()\"\n bind-disabled=\"busy\"\n class=\"inline-flex items-center justify-center gap-2 rounded-lg border border-indigo-500/60 px-3 py-2.5 text-sm font-medium text-indigo-200 outline-none transition hover:bg-indigo-500/10 focus-visible:ring-2 focus-visible:ring-indigo-400/60 disabled:opacity-50\"\n >\n <lucide-icon icon=\"lock-keyhole\" size=\"16\"></lucide-icon>Save secret\n </button>\n </div>\n </div>\n </div>\n </details>\n <ul class=\"grid gap-2\" aria-label=\"Saved secrets\">\n <template for=\"name of secrets\">\n <li>\n <div class=\"flex min-h-14 items-center gap-2 rounded-xl border border-gray-800 bg-gray-950/50 px-3.5 py-3\">\n <button\n type=\"button\"\n on-click=\"selectSecret(name)\"\n class=\"group flex min-w-0 flex-1 items-center gap-3 text-left outline-none focus-visible:ring-2 focus-visible:ring-indigo-400/50\"\n aria-label=\"Edit secret {{ name }}\"\n >\n <span\n class=\"flex h-8 w-8 shrink-0 items-center justify-center rounded-lg bg-gray-800 text-gray-500 group-hover:text-indigo-300\"\n ><lucide-icon icon=\"key-round\" size=\"15\"></lucide-icon\n ></span>\n <span class=\"min-w-0 flex-1 truncate font-mono text-sm text-gray-200\">{{ name }}</span>\n <span class=\"text-xs text-gray-500\">Select</span>\n <lucide-icon icon=\"chevron-right\" size=\"16\" class=\"text-gray-600 transition group-hover:text-indigo-300\"></lucide-icon>\n </button>\n <button\n type=\"button\"\n on-click=\"removeSecret(name)\"\n aria-label=\"Delete secret {{ name }}\"\n title=\"Delete secret\"\n class=\"inline-flex h-8 w-8 shrink-0 items-center justify-center rounded-lg text-rose-300 outline-none transition hover:bg-rose-500/10 focus-visible:ring-2 focus-visible:ring-rose-400/60\"\n >\n <lucide-icon icon=\"trash\" size=\"15\"></lucide-icon>\n </button>\n </div>\n </li>\n </template>\n </ul>\n <div\n class=\"rounded-xl border border-dashed border-gray-700 px-4 py-8 text-center\"\n class-hidden=\"secrets.length > 0\"\n >\n <lucide-icon icon=\"key-round\" size=\"22\" class=\"mx-auto text-gray-600\"></lucide-icon>\n <p class=\"mt-2 text-sm font-medium text-gray-300\">No secrets configured</p>\n <p class=\"mt-1 text-xs text-gray-500\">Add one above to make it available to a workflow.</p>\n </div>\n </section>\n </main>\n\n <script setup>\n import { onDestroy, onInit, ref, templateRef } from '@li3/web';\n\n export default function () {\n const page = document.body.dataset.page;\n const initialId = __WORKFLOW_ID__;\n const initialRevision = __WORKFLOW_REVISION__;\n const readOnly = Boolean(initialRevision);\n const workflows = ref([]);\n const secrets = ref([]);\n const source = ref('');\n const validation = ref({ valid: true });\n const savedSource = ref('');\n const revision = ref(null);\n const revisionDialogOpen = ref(false);\n const revisionNumber = ref(0);\n const revisionSource = ref('');\n const revisionError = ref('');\n const revisionLoading = ref(false);\n const workflowId = ref('');\n const enabled = ref(true);\n const savedEnabled = ref(true);\n const selectedId = ref('');\n const secretName = ref('');\n const secretValue = ref('');\n const fileMode = ref(false);\n const secretFileName = ref('');\n const secretFileData = ref('');\n const notice = ref('');\n const noticeError = ref(false);\n const busy = ref(false);\n const secretValueInput = templateRef('secretValueInput');\n const secretFileInput = templateRef('secretFileInput');\n const secretForm = templateRef('secretForm');\n const helpContent = templateRef('helpContent');\n const helpLoaded = ref(false);\n const helpLoading = ref(false);\n let validationTimer;\n\n const showNotice = (message, error = false) => {\n notice.value = message;\n noticeError.value = error;\n };\n\n const api = async (url, options = {}) => {\n const response = await fetch(url, {\n headers: { accept: 'application/json', ...(options.body ? { 'content-type': 'application/json' } : {}) },\n ...options,\n });\n const body = response.status === 204 ? null : await response.json().catch(() => ({}));\n if (!response.ok) throw new Error(body.error || `Request failed: ${response.status}`);\n return body;\n };\n\n const derivedId = () =>\n source.value\n .match(/^\\s*(?:id:\\s*([^\\n]+)|name:\\s*([^\\n]+))/m)\n ?.slice(1)\n .find(Boolean)\n ?.trim()\n .toLowerCase()\n .replace(/[^a-z0-9]/g, '-')\n .replace(/(^-|-$)/g, '') || '';\n\n const run = async (action) => {\n if (busy.value) return;\n\n busy.value = true;\n\n try {\n await action();\n } catch (error) {\n showNotice(error.message, true);\n } finally {\n busy.value = false;\n }\n };\n\n const loadWorkflows = async () => {\n workflows.value = (await api('/api/workflows')).workflows;\n };\n\n const loadSecrets = async () => {\n secrets.value = (await api('/api/secrets')).secrets;\n };\n\n const openWorkflow = async (id) => {\n const workflowUrl = initialRevision\n ? `/api/workflows/${id}?revision=${initialRevision}`\n : `/api/workflows/${id}`;\n const workflow = await api(workflowUrl);\n selectedId.value = workflow.id;\n workflowId.value = workflow.id;\n source.value = workflow.sourceYaml;\n savedSource.value = workflow.sourceYaml;\n enabled.value = workflow.enabled;\n savedEnabled.value = workflow.enabled;\n revision.value = workflow.revision || null;\n };\n\n const validateSource = async () => {\n return api('/api/workflows/validate', {\n method: 'POST',\n body: JSON.stringify({ sourceYaml: source.value }),\n });\n };\n\n const validate = (ms = 1000) => {\n if (page !== 'editor') return;\n\n clearTimeout(validationTimer);\n validationTimer = setTimeout(() => {\n validation.value = { ...validation.value, running: true, error: '' };\n void validateSource()\n .then((result) => {\n validation.value = { ...result, running: false };\n })\n .catch((error) => {\n validation.value = { valid: false, running: false, error: error.message };\n \n });\n }, ms);\n };\n\n const save = () =>\n run(async () => {\n const id = workflowId.value.trim() || derivedId();\n if (!id) throw new Error('Set an ID or add a workflow name first.');\n if (\n initialId &&\n id === selectedId.value &&\n source.value === savedSource.value &&\n enabled.value === savedEnabled.value\n ) {\n showNotice('No changes to save.');\n return;\n }\n const workflow = await api(`/api/workflows/${id}`, {\n method: 'PUT',\n body: JSON.stringify({ sourceYaml: source.value, enabled: enabled.value }),\n });\n selectedId.value = workflow.id;\n workflowId.value = workflow.id;\n savedSource.value = source.value;\n savedEnabled.value = enabled.value;\n revision.value = workflow.revision;\n showNotice(`Saved ${workflow.id} as draft revision ${workflow.revision}.`);\n if (!initialId) history.replaceState(null, '', `/workflows/${workflow.id}`);\n });\n const publish = () =>\n run(async () => {\n const id = workflowId.value.trim() || selectedId.value;\n if (!id) throw new Error('Save a workflow before publishing it.');\n const workflow = await api(`/api/workflows/${id}/publish`, { method: 'POST' });\n selectedId.value = workflow.id;\n revision.value = workflow.revision;\n showNotice(`Published ${workflow.id} revision ${workflow.revision}.`);\n });\n const remove = () =>\n run(async () => {\n const id = workflowId.value.trim() || selectedId.value;\n if (!id || !confirm(`Delete ${id} and all of its revisions?`)) return;\n await api(`/api/workflows/${id}`, { method: 'DELETE' });\n window.location.href = '/workflows';\n });\n const saveSecret = () =>\n run(async () => {\n const name = secretName.value.trim();\n if (!name || (fileMode.value ? !secretFileData.value : !secretValue.value)) throw new Error(fileMode.value ? 'Choose a file first.' : 'Set a secret value first.');\n await api(`/api/secrets/${name}`, {\n method: 'PUT',\n body: JSON.stringify(fileMode.value ? { value: secretFileData.value, encoding: 'base64', originalName: secretFileName.value } : { value: secretValue.value, encoding: 'utf8' }),\n });\n secretValue.value = '';\n secretFileName.value = '';\n secretFileData.value = '';\n fileMode.value = false;\n if (secretFileInput.value) secretFileInput.value.value = '';\n if (secretForm.value) secretForm.value.open = false;\n await loadSecrets();\n showNotice(`Saved ${name}.`);\n });\n const removeSecret = (requestedName = '') =>\n run(async () => {\n const name = requestedName || secretName.value.trim();\n if (!name || !confirm(`Delete ${name}?`)) return;\n await api(`/api/secrets/${name}`, { method: 'DELETE' });\n if (secretName.value.trim() === name) {\n secretName.value = '';\n secretValue.value = '';\n secretFileName.value = '';\n secretFileData.value = '';\n fileMode.value = false;\n if (secretFileInput.value) secretFileInput.value.value = '';\n }\n await loadSecrets();\n showNotice(`Deleted ${name}.`);\n });\n const setSource = (value) => {\n source.value = value;\n validate();\n };\n const setWorkflowId = (event) => {\n workflowId.value = event.target.value;\n };\n const setEnabled = (event) => {\n enabled.value = event.target.checked;\n };\n const setSecretName = (event) => {\n secretName.value = event.target.value;\n };\n const setSecretValue = (event) => {\n secretValue.value = event.target.value;\n fileMode.value = false;\n if (secretValue.value) {\n secretFileName.value = '';\n secretFileData.value = '';\n if (secretFileInput.value) secretFileInput.value.value = '';\n }\n };\n const setFileMode = (event) => {\n fileMode.value = event.target.checked;\n if (!fileMode.value) {\n secretFileName.value = '';\n secretFileData.value = '';\n if (secretFileInput.value) secretFileInput.value.value = '';\n } else {\n secretValue.value = '';\n }\n };\n const setSecretFile = async (event) => {\n const file = event.target.files?.[0];\n if (!file) return;\n const bytes = new Uint8Array(await file.arrayBuffer());\n let binary = '';\n for (let index = 0; index < bytes.length; index += 0x8000) {\n binary += String.fromCharCode(...bytes.subarray(index, index + 0x8000));\n }\n secretFileName.value = file.name;\n secretFileData.value = btoa(binary);\n secretValue.value = '';\n };\n const selectSecret = (name) => {\n secretName.value = name;\n secretValueInput.value?.focus();\n };\n const visitWorkflow = (id) => {\n window.location.href = `/workflows/${id}`;\n };\n const loadHelp = async (event) => {\n if (!event.target.open || helpLoaded.value || helpLoading.value || !helpContent.value) return;\n helpLoading.value = true;\n helpContent.value.textContent = 'Loading workflow syntax help...';\n try {\n const response = await fetch('/help?embed=1', { headers: { accept: 'text/html' } });\n if (!response.ok) throw new Error(`Help request failed: ${response.status}`);\n const html = await response.text();\n const parsed = new DOMParser().parseFromString(html, 'text/html');\n const sourceNodes = [\n ...Array.from(parsed.head.querySelectorAll('style')),\n ...Array.from(parsed.body.childNodes),\n ];\n const nodes = sourceNodes.map((node) => document.importNode(node, true));\n helpContent.value.replaceChildren(...nodes);\n helpLoaded.value = true;\n } catch (error) {\n helpContent.value.textContent = 'Unable to load workflow syntax help.';\n console.error(error);\n } finally {\n helpLoading.value = false;\n }\n };\n\n const openRevisionDialog = () => {\n if (!revision.value) return;\n revisionNumber.value = revision.value;\n revisionSource.value = source.value;\n revisionError.value = '';\n revisionDialogOpen.value = true;\n };\n const closeRevisionDialog = () => {\n revisionDialogOpen.value = false;\n };\n const navigateRevision = async (offset) => {\n if (!selectedId.value || revisionLoading.value) return;\n const target = revisionNumber.value + offset;\n if (target < 1) return;\n revisionLoading.value = true;\n revisionError.value = '';\n try {\n const workflow = await api(`/api/workflows/${selectedId.value}?revision=${target}`);\n revisionNumber.value = workflow.revision;\n revisionSource.value = workflow.sourceYaml;\n } catch (error) {\n revisionError.value = error.message;\n } finally {\n revisionLoading.value = false;\n }\n };\n\n onDestroy(() => clearTimeout(validationTimer));\n\n onInit(() => {\n const loadPage = page === 'editor'\n ? initialId ? openWorkflow(initialId) : Promise.resolve()\n : page === 'secrets' ? loadSecrets() : loadWorkflows();\n loadPage.catch((error) => showNotice(error.message, true));\n });\n\n return {\n page,\n workflows,\n secrets,\n source,\n validation,\n revision,\n revisionDialogOpen,\n revisionNumber,\n revisionSource,\n revisionError,\n revisionLoading,\n workflowId,\n enabled,\n readOnly,\n selectedId,\n secretName,\n secretValue,\n fileMode,\n secretFileName,\n secretFileData,\n notice,\n noticeError,\n busy,\n validate,\n save,\n publish,\n remove,\n saveSecret,\n removeSecret,\n setSource,\n setWorkflowId,\n setEnabled,\n setSecretName,\n setSecretValue,\n setFileMode,\n setSecretFile,\n selectSecret,\n visitWorkflow,\n loadHelp,\n openRevisionDialog,\n closeRevisionDialog,\n navigateRevision,\n };\n }\n <\/script>\n </template>\n\n <script type=\"module\">\n import '@li3/web';\n <\/script>\n <script src=\"https://cdn.tailwindcss.com\"><\/script>\n </body>\n</html>\n";
|
|
6272
6273
|
//#endregion
|
|
6273
6274
|
//#region src/workflows-ui.ts
|
|
6274
6275
|
function Yi(e, t = "", n) {
|
|
@@ -11286,9 +11287,9 @@ var el = class {
|
|
|
11286
11287
|
`${t.workingDir}:/workspace`,
|
|
11287
11288
|
"-w",
|
|
11288
11289
|
"/workspace",
|
|
11289
|
-
...Object.keys(l).flatMap((e) => ["-e", e]),
|
|
11290
|
+
...Object.keys(l).filter((e) => e !== "PATH").flatMap((e) => ["-e", e]),
|
|
11290
11291
|
"--entrypoint",
|
|
11291
|
-
"sh",
|
|
11292
|
+
"/bin/sh",
|
|
11292
11293
|
...t.dockerArgs || [],
|
|
11293
11294
|
t.image,
|
|
11294
11295
|
"-e",
|
|
@@ -11397,10 +11398,10 @@ var el = class {
|
|
|
11397
11398
|
"-w",
|
|
11398
11399
|
"/workspace",
|
|
11399
11400
|
"--entrypoint",
|
|
11400
|
-
"sh",
|
|
11401
|
+
"/bin/sh",
|
|
11401
11402
|
...t.volumes?.flatMap((e) => ["-v", e]) || [],
|
|
11402
11403
|
...t.dockerArgs || [],
|
|
11403
|
-
...Object.keys(l).flatMap((e) => ["-e", e]),
|
|
11404
|
+
...Object.keys(l).filter((e) => e !== "PATH").flatMap((e) => ["-e", e]),
|
|
11404
11405
|
t.image,
|
|
11405
11406
|
"-c",
|
|
11406
11407
|
t.command
|