em dashes out of skeleton copy (titles use ' - ', prose reworded); split hardening after review: poll errors name the failing repo, failed release is not retried until a new commit, link-content checks all three dirs, editor write-access note, two-repo unit descriptions and comments
This commit is contained in:
@@ -65,6 +65,10 @@ missing). Everything else runs inside containers.
|
||||
10. **[R]** Verify: `curl -s -o /dev/null -w '%{http_code}' http://localhost:8080/`
|
||||
and `journalctl --user -u ctao-portal-build -n 20` (shows measured build times).
|
||||
|
||||
Editor accounts need WRITE access to `ctao/content` (Gitea → repo →
|
||||
Collaborators, or a team) — public read alone lets them sign in but every
|
||||
save fails.
|
||||
|
||||
## CMS sign-in (Sveltia ↔ Gitea OAuth)
|
||||
|
||||
- Gitea OAuth2 app (PKCE, `confidential_client=false`, no secret) with
|
||||
|
||||
+13
-8
@@ -43,18 +43,22 @@ head_of() {
|
||||
code_sha=$(head_of "$CODE_REPO")
|
||||
content_sha=$(head_of "$CONTENT_REPO")
|
||||
# Gitea down/unreachable is a transient, not a unit failure — exit 0 quietly
|
||||
# instead of painting the journal red every 10 s.
|
||||
for sha in "$code_sha" "$content_sha"; do
|
||||
if [[ ! "$sha" =~ ^[0-9a-f]{40}$ ]]; then
|
||||
echo "poll failed (gitea unreachable?) — skipping this tick"
|
||||
exit 0
|
||||
fi
|
||||
done
|
||||
# instead of painting the journal red every 10 s. Name the repo: a 404 here
|
||||
# also means "repo/branch missing or renamed", not just "Gitea down".
|
||||
[[ "$code_sha" =~ ^[0-9a-f]{40}$ ]] || { echo "poll failed for $CODE_REPO@$BRANCH (gitea down, or repo/branch missing) — skipping"; exit 0; }
|
||||
[[ "$content_sha" =~ ^[0-9a-f]{40}$ ]] || { echo "poll failed for $CONTENT_REPO@$BRANCH (gitea down, or repo/branch missing) — skipping"; exit 0; }
|
||||
release="${code_sha:0:12}-${content_sha:0:12}" # code+content pin the release
|
||||
# Skip only if this pair is both recorded AND still present in releases/
|
||||
# (a deleted release dir must trigger a rebuild, not an eternal skip).
|
||||
[[ "$release" == "$(cat "$BASE/state/last-built" 2>/dev/null)" \
|
||||
&& -d "$BASE/releases/$release" ]] && exit 0
|
||||
# A release that already failed is not retried until either repo moves —
|
||||
# otherwise one bad commit (e.g. broken frontmatter) turns into a full
|
||||
# rebuild every 10 s on a shared VM. The failure is loud once, then quiet.
|
||||
if [[ "$release" == "$(cat "$BASE/state/last-failed" 2>/dev/null)" ]]; then
|
||||
echo "skipping $release — build failed before; push a fix to retry"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "building code=$code_sha content=$content_sha"
|
||||
t0=$(date +%s)
|
||||
@@ -106,7 +110,8 @@ podman run --rm --network="$BUILD_NETNS" --memory=1g \
|
||||
npm run build
|
||||
rm -rf "../releases/$RELEASE"
|
||||
cp -a dist "../releases/$RELEASE"
|
||||
'
|
||||
' || { echo "$release" > "$BASE/state/last-failed"; echo "BUILD FAILED for $release (see above) — will not retry until a new commit"; exit 1; }
|
||||
rm -f "$BASE/state/last-failed"
|
||||
|
||||
# --- 3. Atomic publish: symlink flip via rename(2) — no half-published moment.
|
||||
# mv -T is GNU (the target host is Rocky); when testing on macOS put a
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
# activate a service that is still running.
|
||||
|
||||
[Unit]
|
||||
Description=CTAO portal demo — rebuild if the content repo has new commits
|
||||
Description=CTAO portal demo — rebuild if the code or content repo has new commits
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
# OnBootSec covers reboot. Enable: systemctl --user enable --now ctao-portal-build.timer
|
||||
|
||||
[Unit]
|
||||
Description=CTAO portal demo — poll the content repo every 10 s
|
||||
Description=CTAO portal demo — poll the code and content repos every 10 s
|
||||
|
||||
[Timer]
|
||||
OnActiveSec=5
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Sveltia CMS config — backend: Gitea behind the Cyfronet ingress (HTTPS
|
||||
# vhost). Editors log in via Gitea OAuth (PKCE, no client secret); edits are
|
||||
# committed to the ctao/portal repo. Gitea's ROOT_URL and CORS are pinned to
|
||||
# committed to the ctao/content repo. Gitea's ROOT_URL and CORS are pinned to
|
||||
# these origins in deploy/ctao-demo-gitea.container.
|
||||
backend:
|
||||
name: gitea
|
||||
|
||||
@@ -112,7 +112,7 @@
|
||||
const unit = opts.unit || 'result';
|
||||
status.textContent = hits.length
|
||||
? hits.length + ' ' + unit + (hits.length === 1 ? '' : 's') + ' for “' + input.value.trim() + '”'
|
||||
: 'No results for “' + input.value.trim() + '” — try a shorter or different term.';
|
||||
: 'No results for “' + input.value.trim() + '”. Try a shorter or different term.';
|
||||
// Off-DOM build + atomic swap (no transient page-height collapse)
|
||||
const frag = document.createDocumentFragment();
|
||||
for (const p of hits) {
|
||||
|
||||
@@ -6,7 +6,9 @@
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$0")/.."
|
||||
src="${1:-../ctao-content}"
|
||||
[ -d "$src/news" ] || { echo "content clone not found at $src (git clone <gitea>/ctao/content.git first)"; exit 1; }
|
||||
for d in news pages uploads; do
|
||||
[ -d "$src/$d" ] || { echo "content clone incomplete: $src/$d missing (git clone <gitea>/ctao/content.git first)"; exit 1; }
|
||||
done
|
||||
rm -rf src/content/news src/content/pages public/uploads
|
||||
mkdir -p src/content public
|
||||
cp -a "$src/news" src/content/news
|
||||
|
||||
@@ -14,8 +14,9 @@ const news = defineCollection({
|
||||
date: z.coerce.date(),
|
||||
category: z.string().default('news'),
|
||||
author: z.string().default('CTAO'),
|
||||
// Public-path string, e.g. "/uploads/foo.jpg" — Sveltia uploads media to
|
||||
// public/uploads, which Astro's image() helper can't validate (src/ only).
|
||||
// Public-path string, e.g. "/uploads/foo.jpg" — media lives in the
|
||||
// content repo's uploads/, overlaid to public/uploads at build time,
|
||||
// which Astro's image() helper can't validate (src/ only).
|
||||
cover: z.string().optional(),
|
||||
// BCP-47 tag when an article is not in English (e.g. "pl") — set as lang
|
||||
// on the <article> element so screen readers pick the right voice.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import '../styles/global.css';
|
||||
// `ambient` opts a page into the whole-page drifting brand wash (home only — see DESIGN.md)
|
||||
// `type`/`image` feed the social meta: articles pass type="article" + their cover.
|
||||
const { title = 'CTAO Science Portal', description = 'CTAO Science Portal — demo', ambient = false, type = 'website', image } = Astro.props;
|
||||
const { title = 'CTAO Science Portal', description = 'CTAO Science Portal demo', ambient = false, type = 'website', image } = Astro.props;
|
||||
// Absolute URLs for canonical/OG/RSS (head pattern from the official Astro
|
||||
// blog template). `site` comes from astro.config.mjs and is always set.
|
||||
const site = Astro.site;
|
||||
@@ -43,7 +43,7 @@ const links = [
|
||||
<meta property="og:url" content={canonical} />
|
||||
<meta property="og:image" content={ogImage} />
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<link rel="alternate" type="application/rss+xml" title="CTAO Science Portal — News" href={new URL('/rss.xml', site)} />
|
||||
<link rel="alternate" type="application/rss+xml" title="CTAO Science Portal News" href={new URL('/rss.xml', site)} />
|
||||
<link rel="sitemap" href="/sitemap.xml" />
|
||||
<link rel="icon" href="/brand/CTAO_Logo_positive.svg" type="image/svg+xml" />
|
||||
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
|
||||
@@ -59,7 +59,7 @@ const links = [
|
||||
<a class="skip" href="#main">Skip to content</a>
|
||||
<header class="site-header">
|
||||
<div class="container nav">
|
||||
<a class="brand" href="/" aria-label="CTAO Science Portal — home">
|
||||
<a class="brand" href="/" aria-label="CTAO Science Portal, home">
|
||||
<img src="/brand/AAFF_CTAO_Logo_RGB_Monochrome-negative.svg" alt="CTAO" />
|
||||
<span class="brand-sub">Science Portal</span>
|
||||
</a>
|
||||
@@ -164,7 +164,7 @@ const links = [
|
||||
<div class="container">
|
||||
<div class="foot-top">
|
||||
<img src="/brand/AAFF_CTAO_Logo_RGB_Monochrome-negative.svg" alt="CTAO" />
|
||||
<p>CTAO Science Portal — demo by Cyfronet (SUSS-PORT team).</p>
|
||||
<p>CTAO Science Portal demo by Cyfronet (SUSS-PORT team).</p>
|
||||
</div>
|
||||
{/* Order: action/utility first, legal middle, meta last (GOV.UK/NN-g
|
||||
footer anatomy). Spec §3.3.1 requires the five items, not an order. */}
|
||||
@@ -183,7 +183,7 @@ const links = [
|
||||
{/* The real brand flash — path taken from the official logo SVG (BRAND D.4).
|
||||
Sole decorative accent in this view; static by design. */}
|
||||
<svg class="flash" viewBox="85.3 15 15 15" aria-hidden="true"><path fill="currentColor" d="M94.7,23.8c1-1.4,3.3-2.1,5.2-2.7c-2,0.1-4.4,0.3-5.8-0.6c-1.4-1-2.1-3.3-2.7-5.2c0.1,2,0.3,4.4-0.6,5.8c-1,1.4-3.3,2.1-5.2,2.7c2-0.1,4.4-0.3,5.8,0.6c1.4,1,2.1,3.3,2.7,5.2C93.9,27.6,93.7,25.2,94.7,23.8z"/></svg>
|
||||
<span>© 2026 CTAO — demonstration, not an official service.</span>
|
||||
<span>© 2026 CTAO. Demonstration, not an official service.</span>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
---
|
||||
import Base from '../layouts/Base.astro';
|
||||
---
|
||||
<Base title="CTAO Science Portal — Page not found" description="This page does not exist">
|
||||
<Base title="CTAO Science Portal - Page not found" description="This page does not exist">
|
||||
{/* Document archetype (DESIGN.md): the document IS the page — h1, one line,
|
||||
recovery links, and a real GET search form as the way forward. */}
|
||||
<div class="container article-layout">
|
||||
@@ -10,8 +10,8 @@ import Base from '../layouts/Base.astro';
|
||||
<div class="prose">
|
||||
{/* Keep "the <a>" on one line: the compiler drops the whitespace at a
|
||||
line break before a tag, which glued "the" to the link text. */}
|
||||
<p>The address may be mistyped or the page may have moved —
|
||||
go to the <a href="/">home page</a> or browse <a href="/news">all news</a>.</p>
|
||||
<p>The address may be mistyped or the page may have moved.
|
||||
Go to the <a href="/">home page</a> or browse <a href="/news">all news</a>.</p>
|
||||
</div>
|
||||
<form class="search-form" role="search" action="/search" method="get">
|
||||
<label class="sr-only" for="nf-q">Search news</label>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>CTAO — Content Editor</title>
|
||||
<title>CTAO Content Editor</title>
|
||||
<meta name="theme-color" content="#00004A" />
|
||||
<link rel="icon" href="/brand/CTAO_Logo_positive.svg" type="image/svg+xml" />
|
||||
<style>
|
||||
|
||||
@@ -4,7 +4,7 @@ import Base from '../layouts/Base.astro';
|
||||
// and help-desk tickets. No integrations exist yet, so panels show empty
|
||||
// states instead of fabricated sample rows (source-documents-only rule).
|
||||
---
|
||||
<Base title="CTAO Science Portal — Dashboard" description="User dashboard (mock)">
|
||||
<Base title="CTAO Science Portal - Dashboard" description="User dashboard (mock)">
|
||||
<section class="band--moon">
|
||||
<header class="container page-head">
|
||||
<h1>Dashboard</h1>
|
||||
|
||||
@@ -25,9 +25,9 @@ const services = [
|
||||
icon: '<circle cx="12" cy="12" r="9"/><circle cx="12" cy="12" r="4"/><path d="m5.7 5.7 3.5 3.5m5.6 0 3.5-3.5m0 12.6-3.5-3.5m-5.6 0-3.5 3.5"/>' },
|
||||
// Two subdued planned tiles complete the 2×3 grid (an orphan 4th tile in a
|
||||
// 3-col grid reads broken); the remaining TBD services stay on the quiet line.
|
||||
{ title: 'Software', planned: true, desc: 'Landing page for CTAO software — planned.',
|
||||
{ title: 'Software', planned: true, desc: 'Landing page for CTAO software (planned).',
|
||||
icon: '<rect x="3" y="4.5" width="18" height="15" rx="2"/><path d="m7 9.5 3 2.5-3 2.5M12.5 15H17"/>' },
|
||||
{ title: 'Documentation', planned: true, desc: 'CTAO user documentation — planned.',
|
||||
{ title: 'Documentation', planned: true, desc: 'CTAO user documentation (planned).',
|
||||
icon: '<path d="M12 6.7C10.6 5.2 8.6 4.5 5.7 4.5H4v13.6h1.7c2.9 0 4.9.7 6.3 2.2 1.4-1.5 3.4-2.2 6.3-2.2H20V4.5h-1.7c-2.9 0-4.9.7-6.3 2.2z"/><path d="M12 6.7v13.6"/>' },
|
||||
];
|
||||
const planned = ['Scheduling', 'Science alerts'];
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
---
|
||||
import Base from '../layouts/Base.astro';
|
||||
---
|
||||
<Base title="CTAO Science Portal — Sign in" description="Sign in via CTAO AAI (mock)">
|
||||
<Base title="CTAO Science Portal - Sign in" description="Sign in via CTAO AAI (mock)">
|
||||
<section class="auth-band">
|
||||
<div class="band-bg" aria-hidden="true"></div>
|
||||
<div class="auth-card">
|
||||
<img class="auth-logo" src="/brand/CTAO_Logo_positive.svg" alt="CTAO" />
|
||||
<h1>Sign in</h1>
|
||||
{/* Auth pages are scanned, not read — one line, no reassurance prose */}
|
||||
<p class="muted">Use your CTAO account — single sign-on via CTAO AAI.</p>
|
||||
<p class="muted">Use your CTAO account (single sign-on via CTAO AAI).</p>
|
||||
<button class="btn btn-wide" type="button">Continue with your CTAO account</button>
|
||||
<p class="notice">No account? <a>Register via CTAO AAI</a></p>
|
||||
</div>
|
||||
|
||||
@@ -45,8 +45,8 @@ for (let n = 1; n <= page.lastPage; n++) {
|
||||
const hrefFor = (n) => (n === 1 ? '/news' : `/news/${n}`);
|
||||
---
|
||||
<Base
|
||||
title={first ? 'CTAO Science Portal — News' : `CTAO Science Portal — News, page ${page.currentPage}`}
|
||||
description={first ? 'News and announcements of the CTAO' : `News and announcements of the CTAO — page ${page.currentPage} of ${page.lastPage}`}
|
||||
title={first ? 'CTAO Science Portal - News' : `CTAO Science Portal - News, page ${page.currentPage}`}
|
||||
description={first ? 'News and announcements of the CTAO' : `News and announcements of the CTAO, page ${page.currentPage} of ${page.lastPage}`}
|
||||
>
|
||||
{/* Section-landing archetype: white head, display Galaxy h1 + standfirst
|
||||
(the navy band is reserved for / and /login — DESIGN.md). */}
|
||||
@@ -56,7 +56,7 @@ const hrefFor = (n) => (n === 1 ? '/news' : `/news/${n}`);
|
||||
</header>
|
||||
|
||||
<section class="container page">
|
||||
<h2 class="sr-only">{first ? 'All news' : `All news — page ${page.currentPage}`}</h2>
|
||||
<h2 class="sr-only">{first ? 'All news' : `All news, page ${page.currentPage}`}</h2>
|
||||
<div class="grid">
|
||||
{page.data.map((post, i) => <NewsCard post={post} featured={first && i === 0} />)}
|
||||
</div>
|
||||
|
||||
@@ -20,7 +20,7 @@ const { Content, headings } = await render(post);
|
||||
const toc = headings.filter((h) => h.depth === 2 || h.depth === 3);
|
||||
const readMin = readMinOf(post);
|
||||
---
|
||||
<Base title={`${post.data.title} — CTAO`} description={post.data.description} type="article" image={post.data.cover && encodeURI(post.data.cover)}>
|
||||
<Base title={`${post.data.title} - CTAO`} description={post.data.description} type="article" image={post.data.cover && encodeURI(post.data.cover)}>
|
||||
{/* Reading progress (scroll-driven CSS, no JS) — styled only where
|
||||
animation-timeline is supported; elsewhere it stays an empty div. */}
|
||||
<div class="read-progress" aria-hidden="true"></div>
|
||||
|
||||
@@ -10,7 +10,7 @@ export async function getStaticPaths() {
|
||||
const { page } = Astro.props;
|
||||
const { Content } = await render(page);
|
||||
---
|
||||
<Base title={`${page.data.title} — CTAO Science Portal`} description={page.data.description}>
|
||||
<Base title={`${page.data.title} - CTAO Science Portal`} description={page.data.description}>
|
||||
{/* Same wrapper pattern as news/[slug] (never .container and .article on
|
||||
one element — both set max-width and would depend on rule order). */}
|
||||
<div class="container article-layout">
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
import Base from '../layouts/Base.astro';
|
||||
---
|
||||
<Base title="CTAO Science Portal — Proposals" description="Observation proposals — Proposal Handling System integration (planned)">
|
||||
<Base title="CTAO Science Portal - Proposals" description="Observation proposals: Proposal Handling System integration (planned)">
|
||||
<section class="band--moon">
|
||||
<header class="container page-head">
|
||||
<h1>Observation proposals</h1>
|
||||
@@ -13,15 +13,15 @@ import Base from '../layouts/Base.astro';
|
||||
hand-off instead of imitating their submission UI. */}
|
||||
<div class="panel panel--narrow">
|
||||
<h2 class="panel-title">Proposal Handling System</h2>
|
||||
<p>Submitting observation proposals — including Target of Opportunity (ToO)
|
||||
and multi-wavelength (MWL) requests — happens in the Proposal Handling
|
||||
System, built by the APC team and integrated into this portal.</p>
|
||||
<p>Submitting observation proposals, including Target of Opportunity (ToO)
|
||||
and multi-wavelength (MWL) requests, happens in the Proposal Handling
|
||||
System. That system is built by the APC team and integrated into this portal.</p>
|
||||
<p>What the portal contributes:</p>
|
||||
<ul class="panel-list">
|
||||
<li>This menu entry, with a single sign-on hand-off (CTAO AAI).</li>
|
||||
<li>Proposal status feedback in your <a href="/dashboard">Dashboard</a>.</li>
|
||||
</ul>
|
||||
<button class="btn" type="button" disabled>Open the Proposal Handling System — integration planned</button>
|
||||
<button class="btn" type="button" disabled>Open the Proposal Handling System (integration planned)</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -29,7 +29,7 @@ export async function GET(context) {
|
||||
const xml = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
|
||||
<channel>
|
||||
<title>CTAO Science Portal — News</title>
|
||||
<title>CTAO Science Portal News</title>
|
||||
<link>${new URL('/news', site).href}</link>
|
||||
<description>News and announcements of the Cherenkov Telescope Array Observatory</description>
|
||||
<language>en</language>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
import Base from '../layouts/Base.astro';
|
||||
---
|
||||
<Base title="CTAO Science Portal — Search" description="Search CTAO news and announcements">
|
||||
<Base title="CTAO Science Portal - Search" description="Search CTAO news and announcements">
|
||||
{/* Utility archetype: ONE surface per page — Moon canvas from the landing
|
||||
head down (borderless cards/panels pop against it); editorial pages stay
|
||||
all-white. No mid-page surface seams (DESIGN.md). */}
|
||||
|
||||
@@ -8,7 +8,7 @@ const channels = [
|
||||
{ title: 'Mailing lists', desc: 'Announcements and topical mailing lists.' },
|
||||
];
|
||||
---
|
||||
<Base title="CTAO Science Portal — Support" description="User support: help desk, FAQ, forum, mailing lists (mock)">
|
||||
<Base title="CTAO Science Portal - Support" description="User support: help desk, FAQ, forum, mailing lists (mock)">
|
||||
<section class="band--moon">
|
||||
<header class="container page-head">
|
||||
<h1>User Support</h1>
|
||||
|
||||
Reference in New Issue
Block a user