Hermes 284313f908
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
Initial import of AlgaPSA codebase from PSA server
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz

Source: /opt/alga-psa on psa.joliet.tech
2026-06-22 16:12:17 -05:00

291 lines
8.2 KiB
HTML

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta
http-equiv="Content-Security-Policy"
content="default-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self'; img-src 'self' data:; connect-src 'self' https:;"
/>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Dual Portal Demo Extension</title>
<style>
:root {
--bg: #f8fafc;
--card-bg: #ffffff;
--fg: #1e293b;
--fg-muted: #64748b;
--accent: #6366f1;
--accent-light: #eef2ff;
--border: #e2e8f0;
--success: #059669;
--success-bg: #ecfdf5;
--success-border: #a7f3d0;
--code-bg: #f1f5f9;
/* Portal-specific colors */
--msp-accent: #8b5cf6;
--msp-accent-light: #f5f3ff;
--client-accent: #0891b2;
--client-accent-light: #ecfeff;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Ubuntu, Cantarell, "Noto Sans", "Helvetica Neue", Arial, sans-serif;
background: var(--bg);
color: var(--fg);
min-height: 100vh;
display: flex;
align-items: flex-start;
justify-content: center;
padding: 24px;
}
body.msp-portal {
background-image:
radial-gradient(at 20% 30%, rgba(139, 92, 246, 0.08) 0%, transparent 50%),
radial-gradient(at 80% 70%, rgba(139, 92, 246, 0.04) 0%, transparent 50%);
}
body.client-portal {
background-image:
radial-gradient(at 20% 30%, rgba(8, 145, 178, 0.08) 0%, transparent 50%),
radial-gradient(at 80% 70%, rgba(8, 145, 178, 0.04) 0%, transparent 50%);
}
.card {
background: var(--card-bg);
border: 1px solid var(--border);
border-radius: 16px;
padding: 32px 40px;
max-width: 640px;
width: 100%;
box-shadow:
0 1px 3px rgba(0, 0, 0, 0.04),
0 4px 12px rgba(0, 0, 0, 0.04);
}
.header {
text-align: center;
margin-bottom: 24px;
}
.badge {
display: inline-block;
color: white;
padding: 5px 14px;
border-radius: 20px;
font-size: 11px;
font-weight: 600;
letter-spacing: 0.5px;
text-transform: uppercase;
margin-bottom: 16px;
}
.msp-portal .badge { background: var(--msp-accent); }
.client-portal .badge { background: var(--client-accent); }
h1 {
margin: 0 0 8px;
font-size: 26px;
font-weight: 700;
color: var(--fg);
}
.subtitle {
margin: 0;
font-size: 15px;
color: var(--fg-muted);
line-height: 1.5;
}
.portal-indicator {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
margin: 20px 0;
padding: 12px;
border-radius: 10px;
font-size: 14px;
font-weight: 500;
}
.msp-portal .portal-indicator {
background: var(--msp-accent-light);
color: var(--msp-accent);
border: 1px solid rgba(139, 92, 246, 0.2);
}
.client-portal .portal-indicator {
background: var(--client-accent-light);
color: var(--client-accent);
border: 1px solid rgba(8, 145, 178, 0.2);
}
.portal-indicator svg {
width: 20px;
height: 20px;
}
.info-box {
background: var(--accent-light);
border: 1px solid rgba(99, 102, 241, 0.2);
padding: 16px;
border-radius: 10px;
margin: 20px 0;
}
.msp-portal .info-box {
background: var(--msp-accent-light);
border-color: rgba(139, 92, 246, 0.2);
}
.client-portal .info-box {
background: var(--client-accent-light);
border-color: rgba(8, 145, 178, 0.2);
}
.info-row {
display: flex;
align-items: baseline;
margin-bottom: 8px;
font-size: 14px;
}
.info-row:last-child {
margin-bottom: 0;
}
.info-label {
color: var(--fg-muted);
font-weight: 500;
min-width: 100px;
}
.info-value {
color: var(--fg);
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
font-size: 13px;
background: var(--code-bg);
padding: 2px 8px;
border-radius: 4px;
word-break: break-all;
}
.section-title {
font-size: 14px;
font-weight: 600;
color: var(--fg);
margin: 24px 0 12px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.feature-list {
list-style: none;
padding: 0;
margin: 16px 0;
}
.feature-list li {
display: flex;
align-items: flex-start;
gap: 10px;
padding: 10px 0;
border-bottom: 1px solid var(--border);
font-size: 14px;
}
.feature-list li:last-child {
border-bottom: none;
}
.feature-list svg {
width: 18px;
height: 18px;
flex-shrink: 0;
margin-top: 2px;
}
.msp-portal .feature-list svg { color: var(--msp-accent); }
.client-portal .feature-list svg { color: var(--client-accent); }
.handler-result {
min-height: 60px;
}
.result-box {
background: var(--success-bg);
border: 1px solid var(--success-border);
padding: 16px;
border-radius: 10px;
}
.result-header {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 12px;
font-weight: 600;
color: var(--success);
}
.result-header svg {
width: 18px;
height: 18px;
}
.result-row {
display: flex;
align-items: baseline;
margin-bottom: 6px;
font-size: 13px;
}
.result-row:last-child {
margin-bottom: 0;
}
.result-label {
color: var(--fg-muted);
min-width: 100px;
}
.result-value {
color: var(--fg);
}
.result-value code {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
font-size: 12px;
background: rgba(5, 150, 105, 0.1);
padding: 2px 6px;
border-radius: 4px;
}
.loading {
color: var(--fg-muted);
font-style: italic;
}
.error {
color: #dc2626;
background: #fef2f2;
border: 1px solid #fecaca;
padding: 12px 16px;
border-radius: 10px;
font-size: 14px;
}
code {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
font-size: 13px;
background: var(--code-bg);
padding: 2px 6px;
border-radius: 4px;
}
.msp-portal code { color: var(--msp-accent); }
.client-portal code { color: var(--client-accent); }
</style>
</head>
<body>
<div class="card">
<div class="header">
<div class="badge" id="badge">Loading...</div>
<h1>Dual Portal Demo</h1>
<p class="subtitle">
This extension works in both the <strong>MSP Portal</strong> and <strong>Client Portal</strong>.
It detects the context and renders a different UI for each.
</p>
</div>
<div class="portal-indicator" id="portal-indicator">
<!-- Filled by JS based on detected portal -->
</div>
<div class="info-box" id="ctx">
<div class="info-row">
<span class="info-label">Extension ID</span>
<span class="info-value">Loading...</span>
</div>
</div>
<div class="section-title" id="features-title">Features Available</div>
<ul class="feature-list" id="features">
<!-- Filled by JS based on detected portal -->
</ul>
<div class="section-title">WASM Handler Response (via Proxy)</div>
<div class="handler-result" id="handler-result">
<span class="loading">Calling handler...</span>
</div>
</div>
<script src="./main.js" type="module"></script>
</body>
</html>