From 0ef2a2899afae4c363d1a20cf40c224e91323105 Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 7 Mar 2026 19:38:37 +0100 Subject: [PATCH] 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 --- .gitlab-ci.yml | 81 +++++++++++++++++++++++++++++++++- web/src/App.tsx | 57 +++++++++++++++++++++++- web/src/styles.css | 107 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 242 insertions(+), 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 5893f68..9ce989c 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -67,6 +67,35 @@ docker-build: --insecure-registry=$INTERNAL_REGISTRY \ $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: stage: deploy image: docker:latest @@ -106,6 +135,48 @@ deploy: "$DEPLOY_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: stage: bump-version image: @@ -115,6 +186,7 @@ bump-version: rules: - if: $CI_COMMIT_BRANCH == "main" && $CI_COMMIT_TITLE !~ /\[skip ci\]/ script: + - apk add --no-cache jq - | git config user.name "GitLab CI" git config user.email "ci@adriahub.de" @@ -131,8 +203,13 @@ bump-version: NEXT_VERSION="${MAJOR}.${MINOR}.${NEXT_PATCH}" 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 push origin main diff --git a/web/src/App.tsx b/web/src/App.tsx index d6dab8e..3d74f0c 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -35,6 +35,7 @@ export default function App() { setActiveTabRaw(tab); localStorage.setItem('hub_activeTab', tab); }; + const [showVersionModal, setShowVersionModal] = useState(false); const [pluginData, setPluginData] = useState>({}); const eventSourceRef = useRef(null); @@ -127,6 +128,14 @@ export default function App() { 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 = () => { setUpdateState('checking'); setUpdateError(''); @@ -196,7 +205,13 @@ export default function App() { {'\u{1F504}'} )} - v{version} + setShowVersionModal(true)} + title="Versionsinformationen" + > + v{version} + @@ -276,6 +291,46 @@ export default function App() { )} + {showVersionModal && ( +
setShowVersionModal(false)}> +
e.stopPropagation()}> +
+ Versionsinformationen + +
+
+
+ Hub Version + v{version} +
+
+ Desktop App + + {electronVersion + ? `v${electronVersion}` + : Herunterladen + } + +
+
+ Server + + + {connected ? 'Verbunden' : 'Getrennt'} + +
+ {electronVersion && electronVersion !== version && ( +
+ Desktop App und Hub Version unterschiedlich — Update verfügbar. +
+ )} +
+
+
+ )} +
{plugins.length === 0 ? (
diff --git a/web/src/styles.css b/web/src/styles.css index 062fb67..a6b495c 100644 --- a/web/src/styles.css +++ b/web/src/styles.css @@ -336,6 +336,113 @@ html, body { 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 ── */ .hub-content { flex: 1;