Some checks are pending
Bidi Control Character Guard / bidi-control-guard (push) Waiting to run
Circular Dependency Check / Check for new circular dependencies (push) Waiting to run
Citus Migration Smoke / Combined migrations on single-node Citus (push) Waiting to run
E2E Fresh Install Tests / fresh-install-e2e (push) Waiting to run
ext-v2 guardrails / Run ext-v2 guard and ESLint (push) Waiting to run
Integration Tests / Check for relevant changes (push) Waiting to run
Integration Tests / ${{ (github.event_name == 'schedule' || github.event.inputs.suite == 'full') && 'Full integration suite' || 'Tier-1 integration subset' }} (push) Blocked by required conditions
Mobile checks / Mobile lint + typecheck (push) Waiting to run
Mobile checks / Mobile unit tests (push) Waiting to run
Mobile checks / Mobile dependency audit (report) (push) Waiting to run
Mobile checks / Mobile reproducibility checks (push) Waiting to run
Secrets guard (env backups) / Ensure no tracked env backup files (push) Waiting to run
Temporal Readiness / fast-readiness (push) Waiting to run
Temporal Readiness / docker-parity (push) Waiting to run
TypeScript Type Check / Nx affected typecheck (push) Waiting to run
Unit Tests / Skipped-test budget (push) Waiting to run
Unit Tests / Nx affected unit tests (push) Waiting to run
Unit Tests / Server unit coverage (informational) (push) Waiting to run
Validate Tenant Management Schema / Check for relevant changes (push) Waiting to run
Validate Tenant Management Schema / Validate Tenant Management Schema (push) Blocked by required conditions
EE Workflows Build Guard / ee-workflows-build-guard (push) Waiting to run
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz Source: /opt/alga-psa on psa.joliet.tech
201 lines
5.0 KiB
JavaScript
201 lines
5.0 KiB
JavaScript
const ENVELOPE_VERSION = '1';
|
|
const app = document.getElementById('app');
|
|
const ticketSection = document.getElementById('ticket-content');
|
|
const loadingEl = document.getElementById('loading');
|
|
const refreshBtn = document.getElementById('refresh-btn');
|
|
|
|
const params = new URLSearchParams(window.location.search);
|
|
const extensionId = params.get('extensionId') || '';
|
|
const tenantId = params.get('tenant') || '';
|
|
const defaultLimit = 10;
|
|
const hostOrigin = resolveHostOrigin();
|
|
|
|
let bootstrapSession = null;
|
|
let lastError = null;
|
|
|
|
function resolveHostOrigin() {
|
|
const referrer = document.referrer;
|
|
if (referrer) {
|
|
try {
|
|
return new URL(referrer).origin;
|
|
} catch {
|
|
// ignore invalid referrer
|
|
}
|
|
}
|
|
try {
|
|
if (window.parent && window.parent !== window && window.parent.location) {
|
|
return window.parent.location.origin;
|
|
}
|
|
} catch {
|
|
// cross-origin access throws; swallow and fallback
|
|
}
|
|
return window.location.origin;
|
|
}
|
|
|
|
function buildProxyUrl(id) {
|
|
const base = hostOrigin || window.location.origin;
|
|
const path = `/api/ext-proxy/${encodeURIComponent(id)}/tickets/list`;
|
|
try {
|
|
return new URL(path, base).toString();
|
|
} catch {
|
|
return path;
|
|
}
|
|
}
|
|
|
|
function postReady(requestId) {
|
|
window.parent?.postMessage(
|
|
{
|
|
alga: true,
|
|
version: ENVELOPE_VERSION,
|
|
type: 'ready',
|
|
request_id: requestId,
|
|
payload: {},
|
|
},
|
|
'*'
|
|
);
|
|
}
|
|
|
|
function renderTickets(tickets) {
|
|
ticketSection.innerHTML = '';
|
|
if (!tickets || tickets.length === 0) {
|
|
const empty = document.createElement('p');
|
|
empty.textContent = 'No open tickets 🎉';
|
|
ticketSection.appendChild(empty);
|
|
return;
|
|
}
|
|
|
|
const table = document.createElement('table');
|
|
const thead = document.createElement('thead');
|
|
thead.innerHTML = `
|
|
<tr>
|
|
<th scope="col">Ticket</th>
|
|
<th scope="col">Title</th>
|
|
<th scope="col">Status</th>
|
|
<th scope="col">Assignee</th>
|
|
</tr>
|
|
`;
|
|
table.appendChild(thead);
|
|
|
|
const tbody = document.createElement('tbody');
|
|
tickets.forEach((ticket) => {
|
|
const tr = document.createElement('tr');
|
|
const status = (ticket.status || '').toLowerCase();
|
|
const assignee = ticket.assignee || 'Unassigned';
|
|
tr.innerHTML = `
|
|
<th scope="row">${ticket.id}</th>
|
|
<td>${ticket.title ?? '—'}</td>
|
|
<td><span class="status-pill" data-status="${status}">${status || 'unknown'}</span></td>
|
|
<td class="pill-muted">${assignee}</td>
|
|
`;
|
|
tbody.appendChild(tr);
|
|
});
|
|
|
|
table.appendChild(tbody);
|
|
ticketSection.appendChild(table);
|
|
}
|
|
|
|
function renderError(message) {
|
|
ticketSection.innerHTML = '';
|
|
const div = document.createElement('div');
|
|
div.className = 'error';
|
|
div.innerHTML = `
|
|
<strong>Unable to load tickets.</strong>
|
|
<div>${message}</div>
|
|
`;
|
|
ticketSection.appendChild(div);
|
|
}
|
|
|
|
function setLoading(isLoading) {
|
|
if (isLoading) {
|
|
loadingEl.dataset.state = 'loading';
|
|
loadingEl.textContent = 'Loading ticket list…';
|
|
if (!ticketSection.contains(loadingEl)) {
|
|
ticketSection.appendChild(loadingEl);
|
|
}
|
|
} else {
|
|
loadingEl.dataset.state = 'idle';
|
|
if (ticketSection.contains(loadingEl)) {
|
|
ticketSection.removeChild(loadingEl);
|
|
}
|
|
}
|
|
}
|
|
|
|
async function fetchTickets(limit = defaultLimit) {
|
|
if (!extensionId) {
|
|
renderError('Missing extension context.');
|
|
return;
|
|
}
|
|
|
|
setLoading(true);
|
|
lastError = null;
|
|
|
|
try {
|
|
const proxyUrl = buildProxyUrl(extensionId);
|
|
const resp = await fetch(proxyUrl, {
|
|
method: 'POST',
|
|
headers: {
|
|
'content-type': 'application/json',
|
|
},
|
|
body: JSON.stringify({ limit }),
|
|
credentials: 'include',
|
|
});
|
|
|
|
const result = await resp.json();
|
|
if (!resp.ok || !result?.ok) {
|
|
const msg =
|
|
result?.error || `Proxy request failed (${resp.status})`;
|
|
lastError = msg;
|
|
renderError(msg);
|
|
} else {
|
|
renderTickets(result.tickets || []);
|
|
}
|
|
} catch (err) {
|
|
lastError = err instanceof Error ? err.message : String(err);
|
|
renderError(lastError);
|
|
} finally {
|
|
setLoading(false);
|
|
reportResize();
|
|
}
|
|
}
|
|
|
|
function reportResize() {
|
|
const height = app.getBoundingClientRect().height;
|
|
window.parent?.postMessage(
|
|
{
|
|
alga: true,
|
|
version: ENVELOPE_VERSION,
|
|
type: 'resize',
|
|
payload: { height },
|
|
},
|
|
'*'
|
|
);
|
|
}
|
|
|
|
window.addEventListener('message', (event) => {
|
|
const data = event.data;
|
|
if (!data || typeof data !== 'object') return;
|
|
if (data.alga !== true || data.version !== ENVELOPE_VERSION) return;
|
|
|
|
if (data.type === 'bootstrap') {
|
|
bootstrapSession = data.payload?.session ?? null;
|
|
// Use session token for future requests if required
|
|
if (bootstrapSession?.token) {
|
|
window.sessionStorage.setItem('alga-ext-session-token', bootstrapSession.token);
|
|
}
|
|
fetchTickets();
|
|
}
|
|
});
|
|
|
|
refreshBtn.addEventListener('click', () => {
|
|
fetchTickets();
|
|
});
|
|
|
|
window.addEventListener('load', () => {
|
|
postReady();
|
|
fetchTickets();
|
|
});
|
|
|
|
// Periodically report size changes (for dynamic content)
|
|
const resizeObserver = new ResizeObserver(() => reportResize());
|
|
resizeObserver.observe(app);
|