Electron CI Build + Version Info Modal

CI Pipeline:
- New electron-build stage (electronuserland/builder:wine) builds Windows installer
- New electron-deploy stage copies artifacts to container /data/downloads/
- Triggers on electron/ or VERSION changes + manual trigger
- bump-version now syncs electron/package.json via jq

Version Modal:
- Click version badge to open info modal
- Shows Hub Version, Desktop App Version, Server status
- Hint when versions differ (update available)
- Download link when not using Electron app
- Escape + overlay click to close

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Daniel 2026-03-07 19:38:37 +01:00
parent 76a2487d70
commit 0ef2a2899a
3 changed files with 242 additions and 3 deletions

View file

@ -67,6 +67,35 @@ docker-build:
--insecure-registry=$INTERNAL_REGISTRY \ --insecure-registry=$INTERNAL_REGISTRY \
$DESTINATIONS $DESTINATIONS
electron-build:
stage: build
image: electronuserland/builder:wine
rules:
- if: $CI_COMMIT_BRANCH == "main"
changes:
- electron/**/*
- VERSION
- if: $CI_COMMIT_BRANCH == "main"
when: manual
allow_failure: true
script:
- |
VERSION=$(cat VERSION 2>/dev/null || echo "0.0.0")
echo "[Electron] Building v${VERSION} for Windows..."
cd electron
node -e "
const pkg = require('./package.json');
pkg.version = '${VERSION}';
require('fs').writeFileSync('package.json', JSON.stringify(pkg, null, 2) + '\n');
"
npm ci --no-audit --no-fund
npx electron-forge make --platform win32 --arch x64
artifacts:
paths:
- electron/out/make/squirrel.windows/x64/
expire_in: 1 hour
deploy: deploy:
stage: deploy stage: deploy
image: docker:latest image: docker:latest
@ -106,6 +135,48 @@ deploy:
"$DEPLOY_IMAGE" "$DEPLOY_IMAGE"
- docker ps --filter name="$CONTAINER_NAME" --format "ID={{.ID}} Status={{.Status}} Image={{.Image}}" - docker ps --filter name="$CONTAINER_NAME" --format "ID={{.ID}} Status={{.Status}} Image={{.Image}}"
electron-deploy:
stage: deploy
image: docker:latest
needs: [electron-build, deploy]
rules:
- if: $CI_COMMIT_BRANCH == "main"
changes:
- electron/**/*
- VERSION
- if: $CI_COMMIT_BRANCH == "main"
when: manual
allow_failure: true
variables:
CONTAINER_NAME: "gaming-hub"
script:
- |
VERSION=$(cat VERSION 2>/dev/null || echo "0.0.0")
SRC_DIR="electron/out/make/squirrel.windows/x64"
echo "[Electron Deploy] Deploying v${VERSION} artifacts..."
# Ensure downloads directory exists in container
docker exec "$CONTAINER_NAME" mkdir -p /data/downloads
# Copy RELEASES metadata
docker cp "${SRC_DIR}/RELEASES" "${CONTAINER_NAME}:/data/downloads/RELEASES"
# Copy nupkg (for Squirrel auto-update)
for f in ${SRC_DIR}/*.nupkg; do
docker cp "$f" "${CONTAINER_NAME}:/data/downloads/$(basename "$f")"
done
# Copy Setup.exe with stable name for download button
SETUP_EXE=$(ls ${SRC_DIR}/*Setup*.exe 2>/dev/null | head -1)
if [ -n "$SETUP_EXE" ]; then
docker cp "$SETUP_EXE" "${CONTAINER_NAME}:/data/downloads/GamingHub-Setup.exe"
echo "[Electron Deploy] Setup.exe deployed: $(basename "$SETUP_EXE")"
fi
echo "[Electron Deploy] Files in /data/downloads/:"
docker exec "$CONTAINER_NAME" ls -lh /data/downloads/
bump-version: bump-version:
stage: bump-version stage: bump-version
image: image:
@ -115,6 +186,7 @@ bump-version:
rules: rules:
- if: $CI_COMMIT_BRANCH == "main" && $CI_COMMIT_TITLE !~ /\[skip ci\]/ - if: $CI_COMMIT_BRANCH == "main" && $CI_COMMIT_TITLE !~ /\[skip ci\]/
script: script:
- apk add --no-cache jq
- | - |
git config user.name "GitLab CI" git config user.name "GitLab CI"
git config user.email "ci@adriahub.de" git config user.email "ci@adriahub.de"
@ -131,8 +203,13 @@ bump-version:
NEXT_VERSION="${MAJOR}.${MINOR}.${NEXT_PATCH}" NEXT_VERSION="${MAJOR}.${MINOR}.${NEXT_PATCH}"
echo "$NEXT_VERSION" > VERSION echo "$NEXT_VERSION" > VERSION
echo "Bumped version: $VERSION -> $NEXT_VERSION"
git add VERSION # Sync electron/package.json version
jq --arg v "$NEXT_VERSION" '.version = $v' electron/package.json > electron/package.json.tmp
mv electron/package.json.tmp electron/package.json
echo "Bumped version: $VERSION -> $NEXT_VERSION (VERSION + electron/package.json)"
git add VERSION electron/package.json
git commit -m "v${NEXT_VERSION} [skip ci]" git commit -m "v${NEXT_VERSION} [skip ci]"
git push origin main git push origin main

View file

@ -35,6 +35,7 @@ export default function App() {
setActiveTabRaw(tab); setActiveTabRaw(tab);
localStorage.setItem('hub_activeTab', tab); localStorage.setItem('hub_activeTab', tab);
}; };
const [showVersionModal, setShowVersionModal] = useState(false);
const [pluginData, setPluginData] = useState<Record<string, any>>({}); const [pluginData, setPluginData] = useState<Record<string, any>>({});
const eventSourceRef = useRef<EventSource | null>(null); const eventSourceRef = useRef<EventSource | null>(null);
@ -127,6 +128,14 @@ export default function App() {
const electronVersion = (window as any).electronAPI?.version ?? null; const electronVersion = (window as any).electronAPI?.version ?? null;
// Close version modal on Escape
useEffect(() => {
if (!showVersionModal) return;
const handler = (e: KeyboardEvent) => { if (e.key === 'Escape') setShowVersionModal(false); };
window.addEventListener('keydown', handler);
return () => window.removeEventListener('keydown', handler);
}, [showVersionModal]);
const handleCheckForUpdates = () => { const handleCheckForUpdates = () => {
setUpdateState('checking'); setUpdateState('checking');
setUpdateError(''); setUpdateError('');
@ -196,7 +205,13 @@ export default function App() {
{'\u{1F504}'} {'\u{1F504}'}
</button> </button>
)} )}
<span className="hub-version">v{version}</span> <span
className="hub-version hub-version-clickable"
onClick={() => setShowVersionModal(true)}
title="Versionsinformationen"
>
v{version}
</span>
</div> </div>
</header> </header>
@ -276,6 +291,46 @@ export default function App() {
</div> </div>
)} )}
{showVersionModal && (
<div className="hub-version-overlay" onClick={() => setShowVersionModal(false)}>
<div className="hub-version-modal" onClick={e => e.stopPropagation()}>
<div className="hub-version-modal-header">
<span>Versionsinformationen</span>
<button className="hub-version-modal-close" onClick={() => setShowVersionModal(false)}>
{'\u2715'}
</button>
</div>
<div className="hub-version-modal-body">
<div className="hub-version-modal-row">
<span className="hub-version-modal-label">Hub Version</span>
<span className="hub-version-modal-value">v{version}</span>
</div>
<div className="hub-version-modal-row">
<span className="hub-version-modal-label">Desktop App</span>
<span className="hub-version-modal-value">
{electronVersion
? `v${electronVersion}`
: <a href="/downloads/GamingHub-Setup.exe" download className="hub-version-modal-link">Herunterladen</a>
}
</span>
</div>
<div className="hub-version-modal-row">
<span className="hub-version-modal-label">Server</span>
<span className="hub-version-modal-value">
<span className={`hub-version-modal-dot ${connected ? 'online' : ''}`} />
{connected ? 'Verbunden' : 'Getrennt'}
</span>
</div>
{electronVersion && electronVersion !== version && (
<div className="hub-version-modal-hint">
Desktop App und Hub Version unterschiedlich Update verfügbar.
</div>
)}
</div>
</div>
</div>
)}
<main className="hub-content"> <main className="hub-content">
{plugins.length === 0 ? ( {plugins.length === 0 ? (
<div className="hub-empty"> <div className="hub-empty">

View file

@ -336,6 +336,113 @@ html, body {
max-width: 300px; max-width: 300px;
} }
/* ── Version Info Modal ── */
.hub-version-clickable {
cursor: pointer;
transition: all var(--transition);
padding: 2px 8px;
border-radius: var(--radius);
}
.hub-version-clickable:hover {
color: var(--accent);
background: rgba(230, 126, 34, 0.1);
}
.hub-version-overlay {
position: fixed;
inset: 0;
z-index: 9999;
display: flex;
align-items: center;
justify-content: center;
background: rgba(0, 0, 0, 0.7);
backdrop-filter: blur(4px);
}
.hub-version-modal {
background: var(--bg-primary);
border: 1px solid var(--border);
border-radius: 16px;
width: 340px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.4);
overflow: hidden;
animation: hub-modal-in 200ms ease;
}
@keyframes hub-modal-in {
from { opacity: 0; transform: scale(0.95) translateY(8px); }
to { opacity: 1; transform: scale(1) translateY(0); }
}
.hub-version-modal-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 14px 16px;
border-bottom: 1px solid var(--border);
font-weight: 700;
font-size: 14px;
}
.hub-version-modal-close {
background: none;
border: none;
color: var(--text-muted);
cursor: pointer;
padding: 4px 8px;
border-radius: 6px;
font-size: 14px;
transition: all var(--transition);
}
.hub-version-modal-close:hover {
background: rgba(255, 255, 255, 0.08);
color: var(--text-normal);
}
.hub-version-modal-body {
padding: 16px;
display: flex;
flex-direction: column;
gap: 12px;
}
.hub-version-modal-row {
display: flex;
justify-content: space-between;
align-items: center;
}
.hub-version-modal-label {
color: var(--text-muted);
font-size: 13px;
}
.hub-version-modal-value {
font-weight: 600;
font-size: 13px;
display: flex;
align-items: center;
gap: 6px;
}
.hub-version-modal-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--danger);
flex-shrink: 0;
}
.hub-version-modal-dot.online {
background: var(--success);
}
.hub-version-modal-link {
color: var(--accent);
text-decoration: none;
font-weight: 500;
font-size: 13px;
}
.hub-version-modal-link:hover {
text-decoration: underline;
}
.hub-version-modal-hint {
font-size: 11px;
color: var(--accent);
padding: 6px 10px;
background: rgba(230, 126, 34, 0.1);
border-radius: var(--radius);
text-align: center;
}
/* ── Main Content Area ── */ /* ── Main Content Area ── */
.hub-content { .hub-content {
flex: 1; flex: 1;