var AssayWorkflows = (function () {
'use strict';
const PAGE_SIZE = 20;
let currentOffset = 0;
let currentFilter = '';
let searchTerm = '';
let searchAttrs = '';
let ctx = null;
let container = null;
function render(el, context) {
ctx = context;
container = el;
currentOffset = 0;
currentFilter = '';
searchTerm = '';
searchAttrs = '';
el.innerHTML =
'<h2 class="section-title">Workflows</h2>' +
'<div class="toolbar">' +
'<input type="text" class="search-input" id="wf-search" placeholder="Search by ID or type...">' +
'<select class="filter-select" id="wf-status-filter">' +
'<option value="">All Statuses</option>' +
'<option value="PENDING">Pending</option>' +
'<option value="RUNNING">Running</option>' +
'<option value="COMPLETED">Completed</option>' +
'<option value="FAILED">Failed</option>' +
'<option value="WAITING">Waiting</option>' +
'<option value="CANCELLED">Cancelled</option>' +
'</select>' +
'<input type="text" class="search-input" id="wf-search-attrs" placeholder=\'Search attrs filter, e.g. {"env":"prod"}\' style="flex:1.2;">' +
'<button type="button" class="btn-action btn-action-primary" id="wf-start-toggle">+ Start workflow</button>' +
'</div>' +
'<div id="wf-start-form-wrap"></div>' +
'<div id="wf-table-wrap"></div>' +
'<div id="wf-pagination" class="pagination"></div>';
el.querySelector('#wf-search').addEventListener('input', function (e) {
searchTerm = e.target.value.trim();
currentOffset = 0;
loadWorkflows();
});
el.querySelector('#wf-status-filter').addEventListener('change', function (e) {
currentFilter = e.target.value;
currentOffset = 0;
loadWorkflows();
});
let searchAttrsTimer = null;
el.querySelector('#wf-search-attrs').addEventListener('input', function (e) {
const val = e.target.value.trim();
clearTimeout(searchAttrsTimer);
searchAttrsTimer = setTimeout(function () {
searchAttrs = val;
currentOffset = 0;
loadWorkflows();
}, 300);
});
el.querySelector('#wf-start-toggle').addEventListener('click', toggleStartForm);
el.querySelector('#wf-table-wrap').addEventListener('click', function (e) {
var link = e.target.closest('.wf-link');
if (link) {
e.preventDefault();
if (ctx.showDetail) ctx.showDetail(link.dataset.id, ctx);
return;
}
var signalBtn = e.target.closest('.btn-signal');
if (signalBtn) {
e.preventDefault();
handleSignal(signalBtn.dataset.id);
return;
}
var cancelBtn = e.target.closest('.btn-cancel');
if (cancelBtn) {
e.preventDefault();
handleCancel(cancelBtn.dataset.id);
return;
}
var termBtn = e.target.closest('.btn-terminate');
if (termBtn) {
e.preventDefault();
handleTerminate(termBtn.dataset.id);
}
});
el.querySelector('#wf-pagination').addEventListener('click', function (e) {
var btn = e.target.closest('.btn[data-offset]');
if (!btn) return;
currentOffset = parseInt(btn.dataset.offset, 10);
loadWorkflows();
});
loadWorkflows();
}
async function loadWorkflows() {
var wrap = container.querySelector('#wf-table-wrap');
var params = '?limit=' + PAGE_SIZE + '&offset=' + currentOffset;
if (currentFilter) params += '&status=' + currentFilter;
if (searchTerm) params += '&type=' + encodeURIComponent(searchTerm);
if (searchAttrs) {
try {
JSON.parse(searchAttrs);
params += '&search_attrs=' + encodeURIComponent(searchAttrs);
} catch (_) {
var attrsInput = container.querySelector('#wf-search-attrs');
if (attrsInput) attrsInput.style.borderColor = '#d04040';
renderTable(wrap, []);
return;
}
}
var attrsInput = container.querySelector('#wf-search-attrs');
if (attrsInput) attrsInput.style.borderColor = '';
try {
var workflows = await ctx.apiFetch('/workflows' + params);
renderTable(wrap, workflows);
renderPagination(workflows.length);
} catch (err) {
wrap.innerHTML = '<div class="empty-state"><p>Error loading workflows: ' + ctx.escapeHtml(err.message) + '</p></div>';
}
}
function renderTable(wrap, workflows) {
if (!workflows || workflows.length === 0) {
wrap.innerHTML = '<div class="empty-state"><p>No workflows found</p></div>';
return;
}
var html =
'<table class="data-table">' +
'<thead><tr>' +
'<th>ID</th>' +
'<th>Type</th>' +
'<th>Status</th>' +
'<th>Queue</th>' +
'<th>Created</th>' +
'<th>Actions</th>' +
'</tr></thead><tbody>';
for (var i = 0; i < workflows.length; i++) {
var wf = workflows[i];
var status = (wf.status || 'PENDING').toUpperCase();
var terminal = ctx.isTerminal(status);
html +=
'<tr>' +
'<td><a href="#" class="clickable wf-link mono" data-id="' + ctx.escapeHtml(wf.id) + '">' +
ctx.escapeHtml(ctx.truncate(wf.id, 32)) + '</a></td>' +
'<td>' + ctx.escapeHtml(wf.workflow_type || '-') + '</td>' +
'<td><span class="badge ' + ctx.badgeClass(status) + '">' + status + '</span></td>' +
'<td class="mono">' + ctx.escapeHtml(wf.task_queue || 'main') + '</td>' +
'<td>' + ctx.formatTime(wf.created_at) + '</td>' +
'<td>';
if (!terminal) {
html +=
'<button class="btn btn-sm btn-signal" data-id="' + ctx.escapeHtml(wf.id) + '">Signal</button> ' +
'<button class="btn btn-sm btn-cancel" data-id="' + ctx.escapeHtml(wf.id) + '">Cancel</button> ' +
'<button class="btn btn-sm btn-danger btn-terminate" data-id="' + ctx.escapeHtml(wf.id) + '">Terminate</button>';
} else {
html += '<span style="color: var(--text-muted)">-</span>';
}
html += '</td></tr>';
}
html += '</tbody></table>';
wrap.innerHTML = html;
}
function renderPagination(count) {
var pag = container.querySelector('#wf-pagination');
var html = '';
if (currentOffset > 0) {
html += '<button class="btn btn-sm" data-offset="' + Math.max(0, currentOffset - PAGE_SIZE) + '">Prev</button>';
}
var page = Math.floor(currentOffset / PAGE_SIZE) + 1;
html += '<span style="padding: 0 8px; color: var(--text-muted);">Page ' + page + '</span>';
if (count === PAGE_SIZE) {
html += '<button class="btn btn-sm" data-offset="' + (currentOffset + PAGE_SIZE) + '">Next</button>';
}
pag.innerHTML = html;
}
function toggleStartForm() {
var wrap = container.querySelector('#wf-start-form-wrap');
if (wrap.innerHTML.trim() !== '') {
wrap.innerHTML = '';
return;
}
wrap.innerHTML =
'<form class="inline-form" id="wf-start-form">' +
'<label>Workflow type <span style="color:#d04040">*</span>' +
'<input type="text" name="type" placeholder="e.g. IngestData" required>' +
'</label>' +
'<label>Workflow ID (optional — auto-generated if blank)' +
'<input type="text" name="id" placeholder="e.g. ingest-2026-04-17">' +
'</label>' +
'<label>Task queue' +
'<input type="text" name="task_queue" value="default">' +
'</label>' +
'<label>Input (JSON, optional)' +
'<textarea name="input" placeholder=\'{"key":"value"}\'></textarea>' +
'</label>' +
'<label>Search attributes (JSON, optional)' +
'<textarea name="search_attrs" placeholder=\'{"env":"prod","tenant":"acme"}\'></textarea>' +
'</label>' +
'<div class="form-actions">' +
'<button type="button" class="btn-action" id="wf-start-cancel">Cancel</button>' +
'<button type="submit" class="btn-action btn-action-primary">Start</button>' +
'</div>' +
'</form>';
wrap.querySelector('#wf-start-cancel').addEventListener('click', function () {
wrap.innerHTML = '';
});
wrap.querySelector('#wf-start-form').addEventListener('submit', handleStart);
}
async function handleStart(e) {
e.preventDefault();
var form = e.currentTarget;
var data = new FormData(form);
var body = {
workflow_type: data.get('type').trim(),
task_queue: (data.get('task_queue') || 'default').trim(),
namespace: ctx.getNamespace(),
};
var idVal = (data.get('id') || '').trim();
if (idVal) {
body.workflow_id = idVal;
} else {
body.workflow_id =
'wf-' + body.workflow_type.toLowerCase() + '-' + Date.now();
}
var inputRaw = (data.get('input') || '').trim();
if (inputRaw) {
try {
body.input = JSON.parse(inputRaw);
} catch (err) {
ctx.toast('Input is not valid JSON', 'error');
return;
}
}
var attrsRaw = (data.get('search_attrs') || '').trim();
if (attrsRaw) {
try {
body.search_attributes = JSON.parse(attrsRaw);
} catch (err) {
ctx.toast('Search attributes must be valid JSON', 'error');
return;
}
}
try {
await ctx.apiFetchRaw('/workflows', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
});
ctx.toast('Started ' + body.workflow_id, 'success');
container.querySelector('#wf-start-form-wrap').innerHTML = '';
loadWorkflows();
} catch (err) {
ctx.toast('Start failed: ' + err.message, 'error');
}
}
async function handleSignal(id) {
var name = prompt('Signal name:');
if (!name) return;
var payloadStr = prompt('Signal payload (JSON, or leave empty):', '');
var payload = null;
if (payloadStr) {
try {
payload = JSON.parse(payloadStr);
} catch (_) {
payload = payloadStr;
}
}
try {
await ctx.apiFetch('/workflows/' + encodeURIComponent(id) + '/signal/' + encodeURIComponent(name), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ payload: payload }),
});
ctx.toast("Signal '" + name + "' sent", 'success');
loadWorkflows();
} catch (err) {
ctx.toast('Signal failed: ' + err.message, 'error');
}
}
async function handleCancel(id) {
if (!confirm('Cancel workflow ' + id + '?')) return;
try {
await ctx.apiFetch('/workflows/' + encodeURIComponent(id) + '/cancel', {
method: 'POST',
});
ctx.toast('Cancel requested', 'success');
loadWorkflows();
} catch (err) {
ctx.toast('Cancel failed: ' + err.message, 'error');
}
}
async function handleTerminate(id) {
var reason = prompt(
'Terminate workflow ' + id + '?\n\nReason (optional):',
''
);
if (reason === null) return; var body = reason ? { reason: reason } : {};
try {
await ctx.apiFetch('/workflows/' + encodeURIComponent(id) + '/terminate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
});
ctx.toast('Terminated', 'success');
loadWorkflows();
} catch (err) {
ctx.toast('Terminate failed: ' + err.message, 'error');
}
}
return { render: render };
})();