@cloud-cli/on 1.18.1 → 1.19.0
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/on.js +26 -21
- package/dist/workflows.d.ts +2 -1
- package/dist/workflows.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/on.js
CHANGED
|
@@ -3857,7 +3857,7 @@ var qn = class {
|
|
|
3857
3857
|
let t = Buffer.from(e, "base64"), n = c.createDecipheriv("aes-256-gcm", this.encryptionKey(), t.subarray(0, 12));
|
|
3858
3858
|
return n.setAuthTag(t.subarray(12, 28)), Buffer.concat([n.update(t.subarray(28)), n.final()]).toString("utf8");
|
|
3859
3859
|
}
|
|
3860
|
-
}, Jn = "<!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='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/lucide-icon.html\" />\n\n <template app>\n <main class=\"mx-auto max-w-6xl space-y-5\">\n <header class=\"flex flex-col gap-4 border-b border-gray-800 pb-5 sm:flex-row sm:items-end sm:justify-between\">\n <div class=\"flex items-start gap-3\">\n <a\n href=\"/runs\"\n aria-label=\"Back to dashboard\"\n class=\"mt-1 rounded-lg border border-gray-800 bg-gray-900 p-2 text-gray-400 transition hover:border-indigo-500/60 hover:text-indigo-300\"\n >\n <lucide-icon icon=\"arrow-left\" size=\"17\"></lucide-icon>\n </a>\n <div>\n <p class=\"text-xs font-semibold uppercase tracking-[0.18em] text-indigo-400\">Runner engine</p>\n <h1 class=\"mt-1 text-2xl font-bold tracking-tight text-white\">Workflow control room</h1>\n <p class=\"mt-1 text-sm text-gray-400\">Draft, validate, and publish revisioned YAML definitions.</p>\n </div>\n </div>\n <a\n href=\"/workflows/new\"\n class=\"inline-flex items-center justify-center gap-2 rounded-lg bg-indigo-500 px-4 py-2.5 text-sm font-semibold text-white shadow-lg shadow-indigo-950/40 transition hover:bg-indigo-400\"\n >\n <lucide-icon icon=\"plus\" size=\"16\"></lucide-icon>\n New workflow\n </a>\n </header>\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 <aside class=\"workflow-list rounded-xl border border-gray-800 bg-gray-900/80 p-3 shadow-xl shadow-black/10\">\n <div class=\"mb-3 flex items-center justify-between px-2\">\n <h2 class=\"text-xs font-semibold uppercase tracking-wider text-gray-400\">Workflows</h2>\n <span class=\"rounded-full bg-gray-800 px-2 py-0.5 text-xs font-mono text-gray-400\"\n >{{ workflows.length }}</span\n >\n </div>\n <div class=\"space-y-1 flex flex-col w-full\">\n <template for=\"workflow of workflows\">\n <button\n on-click=\"visitWorkflow(workflow.id)\"\n class=\"flex w-full items-center justify-between rounded-lg px-3 py-2.5 text-left text-sm transition hover:bg-gray-800\"\n bind-class=\"workflow.id === selectedId ? 'bg-indigo-500/15.text-indigo-100' : 'text-gray-300'\"\n >\n <span class=\"truncate font-medium\">{{ workflow.name }}</span>\n <span\n class=\"ml-3 rounded-full border border-gray-700 px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-gray-400\"\n >{{ workflow.status }}</span\n >\n </button>\n </template>\n <p class=\"px-3 py-5 text-center text-sm text-gray-500\" class-hidden=\"workflows.length > 0\">\n No workflows yet.\n </p>\n </div>\n </aside>\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 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 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 </label>\n <div class=\"flex flex-wrap gap-2\">\n <button\n on-click=\"validate()\"\n bind-disabled=\"busy\"\n class=\"inline-flex items-center gap-2 rounded-lg border border-gray-700 px-3 py-2.5 text-sm font-medium text-gray-200 transition hover:border-gray-500 hover:bg-gray-800 disabled:cursor-not-allowed disabled:opacity-50\"\n >\n <lucide-icon icon=\"badge-check\" size=\"16\"></lucide-icon>\n Validate\n </button>\n <button\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 Save draft\n </button>\n <button\n on-click=\"publish()\"\n bind-disabled=\"busy\"\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 Publish\n </button>\n <button\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-2\" size=\"16\"></lucide-icon>\n Delete\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 <span class=\"font-medium\">YAML definition</span>\n <span class=\"font-mono\">{{ source.length }} characters</span>\n </div>\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 </div>\n </section>\n </section>\n\n <section\n class=\"secrets-panel rounded-xl border border-gray-800 bg-gray-900/80 p-4 shadow-xl shadow-black/10 sm:p-5\"\n >\n <div class=\"mb-4 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 class=\"text-base font-semibold text-white\">Secrets</h2>\n <p class=\"mt-0.5 text-sm text-gray-400\">\n Values are write-only and encrypted. Re-saving a name replaces its value.\n </p>\n </div>\n </div>\n <div class=\"grid gap-3 md:grid-cols-[14rem_1fr_auto_auto]\">\n <input\n bind-value=\"secretName\"\n on-input=\"setSecretName($event)\"\n class=\"rounded-lg border border-gray-700 bg-gray-950 px-3 py-2.5 font-mono text-sm uppercase outline-none transition focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500/20\"\n placeholder=\"NPM_TOKEN\"\n />\n <input\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-950 px-3 py-2.5 font-mono text-sm outline-none transition focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500/20\"\n placeholder=\"New secret value\"\n />\n <button\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 transition hover:bg-indigo-500/10 disabled:opacity-50\"\n >\n <lucide-icon icon=\"lock-keyhole\" size=\"16\"></lucide-icon>Save value\n </button>\n <button\n on-click=\"removeSecret()\"\n bind-disabled=\"busy\"\n class=\"inline-flex items-center justify-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:opacity-50\"\n >\n <lucide-icon icon=\"trash-2\" size=\"16\"></lucide-icon>Delete\n </button>\n </div>\n <div class=\"mt-4 flex flex-col space-y-1\">\n <template for=\"name of secrets\"\n ><button\n on-click=\"selectSecret(name)\"\n class=\"rounded-md border border-gray-700 px-2.5 py-1.5 font-mono text-xs text-gray-300 transition hover:border-indigo-500 hover:text-indigo-200 w-full\"\n >\n {{ name }}\n </button></template\n >\n </div>\n </section>\n </main>\n\n <script setup>\n import { 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 workflows = ref([]);\n const secrets = ref([]);\n const source = ref('');\n const workflowId = ref('');\n const selectedId = ref('');\n const secretName = ref('');\n const secretValue = ref('');\n const notice = ref('');\n const noticeError = ref(false);\n const busy = ref(false);\n const secretValueInput = templateRef('secretValueInput');\n\n const showNotice = (message, error = false) => {\n notice.value = message;\n noticeError.value = error;\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 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 const run = async (action) => {\n if (busy.value) return;\n busy.value = true;\n try {\n await action();\n } catch (error) {\n showNotice(error.message, true);\n } finally {\n busy.value = false;\n }\n };\n const loadWorkflows = async () => {\n workflows.value = (await api('/api/workflows')).workflows;\n };\n const loadSecrets = async () => {\n secrets.value = (await api('/api/secrets')).secrets;\n };\n const openWorkflow = async (id) => {\n const workflow = await api(`/api/workflows/${id}`);\n selectedId.value = workflow.id;\n workflowId.value = workflow.id;\n source.value = workflow.sourceYaml;\n };\n const validate = () =>\n run(async () => {\n const result = await api('/api/workflows/validate', {\n method: 'POST',\n body: JSON.stringify({ sourceYaml: source.value }),\n });\n showNotice(`Valid: ${result.workflows.length} workflow definition(s).`);\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 const workflow = await api(`/api/workflows/${id}`, {\n method: 'PUT',\n body: JSON.stringify({ sourceYaml: source.value }),\n });\n selectedId.value = workflow.id;\n workflowId.value = workflow.id;\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 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 || !secretValue.value) throw new Error('Set a secret name and value first.');\n await api(`/api/secrets/${name}`, { method: 'PUT', body: JSON.stringify({ value: secretValue.value }) });\n secretValue.value = '';\n await loadSecrets();\n showNotice(`Saved ${name}.`);\n });\n const removeSecret = () =>\n run(async () => {\n const name = secretName.value.trim();\n if (!name || !confirm(`Delete ${name}?`)) return;\n await api(`/api/secrets/${name}`, { method: 'DELETE' });\n secretName.value = '';\n secretValue.value = '';\n await loadSecrets();\n showNotice(`Deleted ${name}.`);\n });\n const setSource = (value) => {\n source.value = value;\n };\n const setWorkflowId = (event) => {\n workflowId.value = event.target.value;\n };\n const setSecretName = (event) => {\n secretName.value = event.target.value;\n };\n const setSecretValue = (event) => {\n secretValue.value = event.target.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\n onInit(() => {\n const loadPage =\n page === 'editor'\n ? initialId\n ? openWorkflow(initialId)\n : Promise.resolve()\n : Promise.all([loadWorkflows(), loadSecrets()]);\n loadPage.catch((error) => showNotice(error.message, true));\n });\n return {\n workflows,\n secrets,\n source,\n workflowId,\n selectedId,\n secretName,\n secretValue,\n notice,\n noticeError,\n busy,\n validate,\n save,\n publish,\n remove,\n saveSecret,\n removeSecret,\n setSource,\n setWorkflowId,\n setSecretName,\n setSecretValue,\n selectSecret,\n visitWorkflow,\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";
|
|
3860
|
+
}, Jn = "<!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='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/lucide-icon.html\" />\n\n <template app>\n <main class=\"mx-auto max-w-6xl space-y-5\">\n <header class=\"flex flex-col gap-4 border-b border-gray-800 pb-5 sm:flex-row sm:items-end sm:justify-between\">\n <div class=\"flex items-start gap-3\">\n <a\n href=\"/runs\"\n aria-label=\"Back to dashboard\"\n class=\"mt-1 rounded-lg border border-gray-800 bg-gray-900 p-2 text-gray-400 transition hover:border-indigo-500/60 hover:text-indigo-300\"\n >\n <lucide-icon icon=\"arrow-left\" size=\"17\"></lucide-icon>\n </a>\n <div>\n <p class=\"text-xs font-semibold uppercase tracking-[0.18em] text-indigo-400\">Runner engine</p>\n <h1 class=\"mt-1 text-2xl font-bold tracking-tight text-white\">Workflow control room</h1>\n <p class=\"mt-1 text-sm text-gray-400\">Draft, validate, and publish revisioned YAML definitions.</p>\n </div>\n </div>\n <a\n href=\"/workflows/new\"\n class=\"inline-flex items-center justify-center gap-2 rounded-lg bg-indigo-500 px-4 py-2.5 text-sm font-semibold text-white shadow-lg shadow-indigo-950/40 transition hover:bg-indigo-400\"\n >\n <lucide-icon icon=\"plus\" size=\"16\"></lucide-icon>\n New workflow\n </a>\n </header>\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 <aside class=\"workflow-list rounded-xl border border-gray-800 bg-gray-900/80 p-3 shadow-xl shadow-black/10\">\n <div class=\"mb-3 flex items-center justify-between px-2\">\n <h2 class=\"text-xs font-semibold uppercase tracking-wider text-gray-400\">Workflows</h2>\n <span class=\"rounded-full bg-gray-800 px-2 py-0.5 text-xs font-mono text-gray-400\"\n >{{ workflows.length }}</span\n >\n </div>\n <div class=\"space-y-1 flex flex-col w-full\">\n <template for=\"workflow of workflows\">\n <button\n on-click=\"visitWorkflow(workflow.id)\"\n class=\"flex w-full items-center justify-between rounded-lg px-3 py-2.5 text-left text-sm transition hover:bg-gray-800\"\n bind-class=\"workflow.id === selectedId ? 'bg-indigo-500/15.text-indigo-100' : 'text-gray-300'\"\n >\n <span class=\"truncate font-medium\">{{ workflow.name }}</span>\n <span\n class=\"ml-3 rounded-full border border-gray-700 px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-gray-400\"\n >{{ workflow.status }}</span\n >\n </button>\n </template>\n <p class=\"px-3 py-5 text-center text-sm text-gray-500\" class-hidden=\"workflows.length > 0\">\n No workflows yet.\n </p>\n </div>\n </aside>\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 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 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 </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\">\n <button\n on-click=\"validate()\"\n bind-disabled=\"busy\"\n class=\"inline-flex items-center gap-2 rounded-lg border border-gray-700 px-3 py-2.5 text-sm font-medium text-gray-200 transition hover:border-gray-500 hover:bg-gray-800 disabled:cursor-not-allowed disabled:opacity-50\"\n >\n <lucide-icon icon=\"badge-check\" size=\"16\"></lucide-icon>\n Validate\n </button>\n <button\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 Save draft\n </button>\n <button\n on-click=\"publish()\"\n bind-disabled=\"busy\"\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 Publish\n </button>\n <button\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-2\" size=\"16\"></lucide-icon>\n Delete\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 <span class=\"font-medium\">YAML definition</span>\n <span class=\"font-mono\">{{ source.length }} characters</span>\n </div>\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 </div>\n </section>\n </section>\n\n <section\n class=\"secrets-panel rounded-xl border border-gray-800 bg-gray-900/80 p-4 shadow-xl shadow-black/10 sm:p-5\"\n >\n <div class=\"mb-4 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 class=\"text-base font-semibold text-white\">Secrets</h2>\n <p class=\"mt-0.5 text-sm text-gray-400\">\n Values are write-only and encrypted. Re-saving a name replaces its value.\n </p>\n </div>\n </div>\n <div class=\"grid gap-3 md:grid-cols-[14rem_1fr_auto_auto]\">\n <input\n bind-value=\"secretName\"\n on-input=\"setSecretName($event)\"\n class=\"rounded-lg border border-gray-700 bg-gray-950 px-3 py-2.5 font-mono text-sm uppercase outline-none transition focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500/20\"\n placeholder=\"NPM_TOKEN\"\n />\n <input\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-950 px-3 py-2.5 font-mono text-sm outline-none transition focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500/20\"\n placeholder=\"New secret value\"\n />\n <button\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 transition hover:bg-indigo-500/10 disabled:opacity-50\"\n >\n <lucide-icon icon=\"lock-keyhole\" size=\"16\"></lucide-icon>Save value\n </button>\n <button\n on-click=\"removeSecret()\"\n bind-disabled=\"busy\"\n class=\"inline-flex items-center justify-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:opacity-50\"\n >\n <lucide-icon icon=\"trash-2\" size=\"16\"></lucide-icon>Delete\n </button>\n </div>\n <div class=\"mt-4 flex flex-col space-y-1\">\n <template for=\"name of secrets\"\n ><button\n on-click=\"selectSecret(name)\"\n class=\"rounded-md border border-gray-700 px-2.5 py-1.5 font-mono text-xs text-gray-300 transition hover:border-indigo-500 hover:text-indigo-200 w-full\"\n >\n {{ name }}\n </button></template\n >\n </div>\n </section>\n </main>\n\n <script setup>\n import { 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 workflows = ref([]);\n const secrets = ref([]);\n const source = ref('');\n const workflowId = ref('');\n const enabled = ref(true);\n const selectedId = ref('');\n const secretName = ref('');\n const secretValue = ref('');\n const notice = ref('');\n const noticeError = ref(false);\n const busy = ref(false);\n const secretValueInput = templateRef('secretValueInput');\n\n const showNotice = (message, error = false) => {\n notice.value = message;\n noticeError.value = error;\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 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 const run = async (action) => {\n if (busy.value) return;\n busy.value = true;\n try {\n await action();\n } catch (error) {\n showNotice(error.message, true);\n } finally {\n busy.value = false;\n }\n };\n const loadWorkflows = async () => {\n workflows.value = (await api('/api/workflows')).workflows;\n };\n const loadSecrets = async () => {\n secrets.value = (await api('/api/secrets')).secrets;\n };\n const openWorkflow = async (id) => {\n const workflow = await api(`/api/workflows/${id}`);\n selectedId.value = workflow.id;\n workflowId.value = workflow.id;\n source.value = workflow.sourceYaml;\n enabled.value = workflow.enabled;\n };\n const validate = () =>\n run(async () => {\n const result = await api('/api/workflows/validate', {\n method: 'POST',\n body: JSON.stringify({ sourceYaml: source.value }),\n });\n showNotice(`Valid: ${result.workflows.length} workflow definition(s).`);\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 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 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 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 || !secretValue.value) throw new Error('Set a secret name and value first.');\n await api(`/api/secrets/${name}`, { method: 'PUT', body: JSON.stringify({ value: secretValue.value }) });\n secretValue.value = '';\n await loadSecrets();\n showNotice(`Saved ${name}.`);\n });\n const removeSecret = () =>\n run(async () => {\n const name = secretName.value.trim();\n if (!name || !confirm(`Delete ${name}?`)) return;\n await api(`/api/secrets/${name}`, { method: 'DELETE' });\n secretName.value = '';\n secretValue.value = '';\n await loadSecrets();\n showNotice(`Deleted ${name}.`);\n });\n const setSource = (value) => {\n source.value = value;\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 };\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\n onInit(() => {\n const loadPage =\n page === 'editor'\n ? initialId\n ? openWorkflow(initialId)\n : Promise.resolve()\n : Promise.all([loadWorkflows(), loadSecrets()]);\n loadPage.catch((error) => showNotice(error.message, true));\n });\n return {\n workflows,\n secrets,\n source,\n workflowId,\n enabled,\n selectedId,\n secretName,\n secretValue,\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 selectSecret,\n visitWorkflow,\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";
|
|
3861
3861
|
//#endregion
|
|
3862
3862
|
//#region src/workflows-ui.ts
|
|
3863
3863
|
function Yn(e, t = "") {
|
|
@@ -8200,7 +8200,7 @@ function Xo(e) {
|
|
|
8200
8200
|
}
|
|
8201
8201
|
var Zo = class {
|
|
8202
8202
|
async init() {
|
|
8203
|
-
await S.exec("\n CREATE TABLE IF NOT EXISTS workflows (\n id TEXT PRIMARY KEY, name TEXT NOT NULL, source_yaml TEXT NOT NULL,\n status TEXT NOT NULL DEFAULT 'draft', active_revision INTEGER,\n created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP\n );\n CREATE TABLE IF NOT EXISTS workflow_revisions (\n workflow_id TEXT NOT NULL, revision INTEGER NOT NULL, source_yaml TEXT NOT NULL,\n normalized_json TEXT NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP,\n PRIMARY KEY (workflow_id, revision)\n );\n CREATE TABLE IF NOT EXISTS scheduled_runs (\n workflow_id TEXT NOT NULL, trigger_id TEXT NOT NULL, scheduled_for TEXT NOT NULL,\n created_at DATETIME DEFAULT CURRENT_TIMESTAMP,\n PRIMARY KEY (workflow_id, trigger_id, scheduled_for)\n );\n ");
|
|
8203
|
+
await S.exec("\n CREATE TABLE IF NOT EXISTS workflows (\n id TEXT PRIMARY KEY, name TEXT NOT NULL, source_yaml TEXT NOT NULL,\n status TEXT NOT NULL DEFAULT 'draft', active_revision INTEGER, enabled INTEGER NOT NULL DEFAULT 1,\n created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP\n );\n CREATE TABLE IF NOT EXISTS workflow_revisions (\n workflow_id TEXT NOT NULL, revision INTEGER NOT NULL, source_yaml TEXT NOT NULL,\n normalized_json TEXT NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP,\n PRIMARY KEY (workflow_id, revision)\n );\n CREATE TABLE IF NOT EXISTS scheduled_runs (\n workflow_id TEXT NOT NULL, trigger_id TEXT NOT NULL, scheduled_for TEXT NOT NULL,\n created_at DATETIME DEFAULT CURRENT_TIMESTAMP,\n PRIMARY KEY (workflow_id, trigger_id, scheduled_for)\n );\n "), (await S.all("PRAGMA table_info(workflows)")).some((e) => e.name === "enabled") || await S.run("ALTER TABLE workflows ADD COLUMN enabled INTEGER NOT NULL DEFAULT 1");
|
|
8204
8204
|
}
|
|
8205
8205
|
validate(e) {
|
|
8206
8206
|
let t = Xo(e);
|
|
@@ -8210,25 +8210,27 @@ var Zo = class {
|
|
|
8210
8210
|
}
|
|
8211
8211
|
return t;
|
|
8212
8212
|
}
|
|
8213
|
-
async saveDraft(e, t) {
|
|
8214
|
-
let
|
|
8215
|
-
if (
|
|
8216
|
-
let
|
|
8217
|
-
return await S.run("INSERT INTO workflows (id, name, source_yaml, status) VALUES (?, ?, ?, 'draft')\n ON CONFLICT(id) DO UPDATE SET name = excluded.name, source_yaml = excluded.source_yaml, status = 'draft', updated_at = CURRENT_TIMESTAMP", [
|
|
8213
|
+
async saveDraft(e, t, n = !0) {
|
|
8214
|
+
let r = this.validate(t);
|
|
8215
|
+
if (r.length !== 1 || r[0].id !== e) throw Error("Workflow id must match the YAML name or id");
|
|
8216
|
+
let i = await S.get("SELECT COALESCE(MAX(revision), 0) AS revision FROM workflow_revisions WHERE workflow_id = ?", [e]), a = Number(i?.revision || 0) + 1;
|
|
8217
|
+
return await S.run("INSERT INTO workflows (id, name, source_yaml, status, enabled) VALUES (?, ?, ?, 'draft', ?)\n ON CONFLICT(id) DO UPDATE SET name = excluded.name, source_yaml = excluded.source_yaml, status = 'draft', enabled = excluded.enabled, updated_at = CURRENT_TIMESTAMP", [
|
|
8218
8218
|
e,
|
|
8219
|
-
|
|
8220
|
-
t
|
|
8219
|
+
r[0].name,
|
|
8220
|
+
t,
|
|
8221
|
+
+!!n
|
|
8221
8222
|
]), await S.run("INSERT INTO workflow_revisions (workflow_id, revision, source_yaml, normalized_json) VALUES (?, ?, ?, ?)", [
|
|
8222
8223
|
e,
|
|
8223
|
-
|
|
8224
|
+
a,
|
|
8224
8225
|
t,
|
|
8225
|
-
JSON.stringify(
|
|
8226
|
+
JSON.stringify(r[0])
|
|
8226
8227
|
]), {
|
|
8227
8228
|
id: e,
|
|
8228
|
-
name:
|
|
8229
|
+
name: r[0].name,
|
|
8229
8230
|
sourceYaml: t,
|
|
8230
|
-
revision:
|
|
8231
|
-
status: "draft"
|
|
8231
|
+
revision: a,
|
|
8232
|
+
status: "draft",
|
|
8233
|
+
enabled: n
|
|
8232
8234
|
};
|
|
8233
8235
|
}
|
|
8234
8236
|
async publish(e) {
|
|
@@ -8241,33 +8243,36 @@ var Zo = class {
|
|
|
8241
8243
|
name: t.name,
|
|
8242
8244
|
sourceYaml: t.source_yaml,
|
|
8243
8245
|
revision: Number(n.revision),
|
|
8244
|
-
status: "published"
|
|
8246
|
+
status: "published",
|
|
8247
|
+
enabled: !!t.enabled
|
|
8245
8248
|
};
|
|
8246
8249
|
}
|
|
8247
8250
|
async list() {
|
|
8248
|
-
return (await S.all("SELECT id, name, source_yaml, COALESCE(active_revision, 0) AS revision, status FROM workflows ORDER BY name")).map((e) => ({
|
|
8251
|
+
return (await S.all("SELECT id, name, source_yaml, COALESCE(active_revision, 0) AS revision, status, enabled FROM workflows ORDER BY name")).map((e) => ({
|
|
8249
8252
|
id: e.id,
|
|
8250
8253
|
name: e.name,
|
|
8251
8254
|
sourceYaml: e.source_yaml,
|
|
8252
8255
|
revision: Number(e.revision),
|
|
8253
|
-
status: e.status
|
|
8256
|
+
status: e.status,
|
|
8257
|
+
enabled: !!e.enabled
|
|
8254
8258
|
}));
|
|
8255
8259
|
}
|
|
8256
8260
|
async get(e) {
|
|
8257
|
-
let t = await S.get("SELECT id, name, source_yaml, COALESCE(active_revision, 0) AS revision, status FROM workflows WHERE id = ?", [e]);
|
|
8261
|
+
let t = await S.get("SELECT id, name, source_yaml, COALESCE(active_revision, 0) AS revision, status, enabled FROM workflows WHERE id = ?", [e]);
|
|
8258
8262
|
return t ? {
|
|
8259
8263
|
id: t.id,
|
|
8260
8264
|
name: t.name,
|
|
8261
8265
|
sourceYaml: t.source_yaml,
|
|
8262
8266
|
revision: Number(t.revision),
|
|
8263
|
-
status: t.status
|
|
8267
|
+
status: t.status,
|
|
8268
|
+
enabled: !!t.enabled
|
|
8264
8269
|
} : null;
|
|
8265
8270
|
}
|
|
8266
8271
|
async delete(e) {
|
|
8267
8272
|
return await this.get(e) ? (await S.run("DELETE FROM scheduled_runs WHERE workflow_id = ?", [e]), await S.run("DELETE FROM workflow_revisions WHERE workflow_id = ?", [e]), await S.run("DELETE FROM workflows WHERE id = ?", [e]), !0) : !1;
|
|
8268
8273
|
}
|
|
8269
8274
|
async published() {
|
|
8270
|
-
return (await S.all("SELECT w.id AS workflow_id, r.revision, r.normalized_json FROM workflows w JOIN workflow_revisions r\n ON r.workflow_id = w.id AND r.revision = w.active_revision WHERE w.status = 'published'")).map((e) => ({
|
|
8275
|
+
return (await S.all("SELECT w.id AS workflow_id, r.revision, r.normalized_json FROM workflows w JOIN workflow_revisions r\n ON r.workflow_id = w.id AND r.revision = w.active_revision WHERE w.status = 'published' AND w.enabled = 1")).map((e) => ({
|
|
8271
8276
|
workflowId: e.workflow_id,
|
|
8272
8277
|
revision: Number(e.revision),
|
|
8273
8278
|
definition: JSON.parse(e.normalized_json)
|
|
@@ -8526,7 +8531,7 @@ var Zo = class {
|
|
|
8526
8531
|
return;
|
|
8527
8532
|
}
|
|
8528
8533
|
try {
|
|
8529
|
-
let e = await this.workflows.saveDraft(n, r.sourceYaml);
|
|
8534
|
+
let e = await this.workflows.saveDraft(n, r.sourceYaml, r.enabled !== !1);
|
|
8530
8535
|
t.writeHead(200, { "Content-Type": "application/json; charset=utf-8" }), t.end(JSON.stringify(e));
|
|
8531
8536
|
} catch (e) {
|
|
8532
8537
|
t.writeHead(422, { "Content-Type": "application/json; charset=utf-8" }), t.end(JSON.stringify({ error: e.message }));
|
package/dist/workflows.d.ts
CHANGED
|
@@ -5,13 +5,14 @@ export interface StoredWorkflow {
|
|
|
5
5
|
sourceYaml: string;
|
|
6
6
|
revision: number;
|
|
7
7
|
status: 'draft' | 'published' | 'archived';
|
|
8
|
+
enabled: boolean;
|
|
8
9
|
}
|
|
9
10
|
/** Parses the portable, DB-stored YAML format. Includes are deliberately unsupported. */
|
|
10
11
|
export declare function parseWorkflow(sourceYaml: string): WorkflowDefinition[];
|
|
11
12
|
export declare class WorkflowRepository {
|
|
12
13
|
init(): Promise<void>;
|
|
13
14
|
validate(sourceYaml: string): WorkflowDefinition[];
|
|
14
|
-
saveDraft(id: string, sourceYaml: string): Promise<StoredWorkflow>;
|
|
15
|
+
saveDraft(id: string, sourceYaml: string, enabled?: boolean): Promise<StoredWorkflow>;
|
|
15
16
|
publish(id: string): Promise<StoredWorkflow | null>;
|
|
16
17
|
list(): Promise<StoredWorkflow[]>;
|
|
17
18
|
get(id: string): Promise<StoredWorkflow | null>;
|
package/dist/workflows.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workflows.d.ts","sourceRoot":"","sources":["../src/workflows.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEvE,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,OAAO,GAAG,WAAW,GAAG,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"workflows.d.ts","sourceRoot":"","sources":["../src/workflows.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEvE,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,OAAO,GAAG,WAAW,GAAG,UAAU,CAAC;IAC3C,OAAO,EAAE,OAAO,CAAC;CAClB;AAQD,yFAAyF;AACzF,wBAAgB,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,kBAAkB,EAAE,CAuBtE;AAED,qBAAa,kBAAkB;IACvB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAwB3B,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,kBAAkB,EAAE;IAa5C,SAAS,CAAC,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,UAAO,GAAG,OAAO,CAAC,cAAc,CAAC;IAWlF,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC;IASnD,IAAI,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC;IAKjC,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC;IAK/C,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IASpC,SAAS,IAAI,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAMxC,WAAW,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC;IAKrF,iBAAiB,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CAKvG"}
|