// Additional pages — Repositories dashboard, Updates inbox, Templates gallery, Deployments.
const { useState: useStateP, useMemo: useMemoP } = React;
const _userLogin = window.DEVEX_INIT?.user?.login || 'org';
// ---------- Mini sparkline ----------
function Spark({ data, color = 'var(--accent)', w = 90, h = 24 }) {
const max = Math.max(...data);
const min = Math.min(...data);
const pts = data.map((v, i) => {
const x = (i / (data.length - 1)) * w;
const y = h - ((v - min) / (max - min || 1)) * (h - 2) - 1;
return [x, y];
});
const d = pts.map((p, i) => `${i ? 'L' : 'M'}${p[0].toFixed(1)} ${p[1].toFixed(1)}`).join(' ');
return (
);
}
// ---------- Status pill ----------
function StatusPill({ status }) {
const map = {
live: { bg: 'var(--green-tint)', col: 'var(--green)', label: '● live' },
deploying: { bg: 'var(--accent-tint)', col: 'var(--accent)', label: '◐ deploying' },
archived: { bg: 'var(--surface-2)', col: 'var(--muted)', label: '○ archived' },
failed: { bg: 'var(--accent-tint)', col: 'var(--accent)', label: '✕ failed' },
};
const m = map[status] || map.archived;
return {m.label};
}
function CIBadge({ ci }) {
const map = {
success: { col: 'var(--green)', sym: '✓' },
running: { col: 'var(--accent)', sym: '◐' },
failed: { col: 'var(--accent)', sym: '✕' },
skipped: { col: 'var(--muted)', sym: '—' },
};
const m = map[ci] || map.skipped;
return {m.sym};
}
// ---------- Repositories Dashboard ----------
function ReposPage() {
const I = window.I;
const { REPOS, DEPLOY_SPARK } = window.DEVEX_DATA;
const [filter, setFilter] = useStateP('all');
const [query, setQuery] = useStateP('');
const filtered = useMemoP(() => REPOS.filter(r => {
if (filter !== 'all' && r.status !== filter) return false;
if (query && !r.name.includes(query.toLowerCase())) return false;
return true;
}), [filter, query]);
const featured = REPOS.slice(0, 3);
return (
<>
Repository
registry.
// every repo, ownership, deploy state.
{/* Filters */}
{['Repo','Template','Owner','Endpoint','CI','Status','Version',''].map(h => (
| {h} |
))}
{filtered.map(r => (
|
{_userLogin}/{r.name}
|
{r.template} |
{r.owner} |
{r.endpoint !== '—' ? {r.endpoint} : —}
|
|
|
{r.version}
{r.updateAvail && ● update}
|
|
))}
>
);
}
// ---------- Updates Inbox ----------
function UpdatesPage() {
const I = window.I;
const { UPDATES } = window.DEVEX_DATA;
const [active, setActive] = useStateP(UPDATES[0].id);
const current = UPDATES.find(u => u.id === active);
const kindColor = { security: 'var(--accent)', breaking: 'var(--accent)', patch: 'var(--green)', feature: 'var(--text)' };
return (
<>
Stack
drift.
// pending update PRs. review, apply, dismiss.
{/* Inbox list */}
▍ Inbox
{UPDATES.length} open
{UPDATES.map(u => (
setActive(u.id)} style={{
padding: '14px 18px',
borderBottom: '1px solid var(--border)',
cursor: 'pointer',
background: active === u.id ? 'var(--accent-tint)' : 'var(--bg)',
borderLeft: active === u.id ? '3px solid var(--accent)' : '3px solid transparent',
display: 'flex', flexDirection: 'column', gap: 6,
}}>
{u.kind}
{u.id}
{u.opened}
{u.title}
{_userLogin}/{u.repo} · {u.from} → {u.to}
{u.files} files
+{u.additions}
−{u.deletions}
))}
{/* Diff preview */}
▍ {current.id} · {current.title}
repo · {_userLogin}/{current.repo}
from · {current.from}
to · {current.to}
by · devex-bot
{current.diff.map((d, i) => (
{d.f}
{d.d.split('\n').map((line, j) => (
{line || '\u00a0'}
))}
))}
>
);
}
// ---------- Templates Gallery ----------
function TemplatesPage() {
const I = window.I;
const { TEMPLATES_EXPANDED } = window.DEVEX_DATA;
const [cat, setCat] = useStateP('all');
const cats = ['all', ...new Set(TEMPLATES_EXPANDED.map(t => t.category || 'Stack'))];
const list = TEMPLATES_EXPANDED.filter(t => cat === 'all' || (t.category || 'Stack') === cat);
return (
<>
Template
library.
// curated, versioned scaffolds.
{cats.map(c => (
))}
{list.map(t => (
{t.name}
{t.author === 'devex-core' && core}
{t.desc}
{(t.stack || []).map(s => {s})}
{t.usage ? `${t.usage} uses` : 'new'}
{t.author || 'devex-core'}
))}
>
);
}
// ---------- Deployments (lightweight) ----------
function DeploymentsPage() {
const I = window.I;
const { REPOS } = window.DEVEX_DATA;
const live = REPOS.filter(r => r.status === 'live' || r.status === 'deploying');
return (
<>
Deploy
timeline.
// every workflow run.
{live.map((r, i) => (
{r.created}
{_userLogin}/{r.name}
{r.template} · {r.endpoint}
))}
>
);
}
window.ReposPage = ReposPage;
window.UpdatesPage = UpdatesPage;
window.TemplatesPage = TemplatesPage;
window.DeploymentsPage = DeploymentsPage;