Skip to content

Commit f29883f

Browse files
Merge pull request #26 from esto-openscan/develop
Prepare final SPA release for stable migration
2 parents 8e0e57e + a6c5aeb commit f29883f

8 files changed

Lines changed: 111 additions & 134 deletions

File tree

.github/workflows/build.yml

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,11 @@ jobs:
3737
npm install -g @quasar/cli
3838
npm ci
3939
40-
- name: Build SPA zip
41-
run: npm run build:zip
40+
- name: Build SPA artifacts
41+
run: |
42+
npm run build
43+
npm run zip:spa
44+
npm run deb:spa
4245
4346
- name: Compute release name
4447
id: version
@@ -67,4 +70,6 @@ jobs:
6770
with:
6871
tag_name: ${{ steps.version.outputs.release_name }}
6972
name: ${{ steps.version.outputs.release_name }}
70-
files: app/dist/spa.zip
73+
files: |
74+
app/dist/spa.zip
75+
app/dist/*.deb

app/README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,12 @@ npm run build:zip
5454
```
5555
This produces `dist/spa.zip`. Requires the `zip` command to be available on the system.
5656

57+
### Build and create Debian package for updater deployment
58+
```bash
59+
npm run build:deb
60+
```
61+
This produces `dist/openscan3-client_<version>_all.deb`. The package installs the built SPA assets into `/usr/share/openscan3-client` and intentionally does not ship nginx configuration or services.
62+
5763
### Regenerate API client and schema helpers
5864
```bash
5965
npm run api:gen

app/package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "openscan3-client",
3-
"version": "0.5.10",
3+
"version": "0.6.0",
44
"apiVersion": "0.9.0",
55
"description": "Frontend client for OpenScan3 firmware devices",
66
"productName": "OpenScan3",
@@ -9,7 +9,9 @@
99
"scripts": {
1010
"api:gen": "node scripts/generate-api.mjs",
1111
"build": "quasar build",
12+
"build:deb": "quasar build && npm run deb:spa",
1213
"build:zip": "quasar build && npm run zip:spa",
14+
"deb:spa": "sh scripts/package-deb.sh",
1315
"dev": "quasar dev",
1416
"lint": "eslint .",
1517
"format": "prettier --write \"**/*.{js,ts,vue,scss,html,md,json}\" --ignore-path .gitignore",

app/scripts/package-deb.sh

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
#!/usr/bin/env sh
2+
set -eu
3+
4+
APP_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
5+
DIST_DIR="$APP_DIR/dist"
6+
SPA_DIR="$DIST_DIR/spa"
7+
PKG_ROOT="$DIST_DIR/deb-root"
8+
INSTALL_DIR="$PKG_ROOT/usr/share/openscan3-client"
9+
DEBIAN_DIR="$PKG_ROOT/DEBIAN"
10+
11+
if ! command -v dpkg-deb >/dev/null 2>&1; then
12+
echo "dpkg-deb command not found (required to create .deb package)" >&2
13+
exit 1
14+
fi
15+
16+
if [ ! -d "$SPA_DIR" ]; then
17+
echo "SPA build not found at $SPA_DIR. Run npm run build first." >&2
18+
exit 1
19+
fi
20+
21+
PACKAGE_NAME=$(node -p "require('$APP_DIR/package.json').name")
22+
VERSION=$(node -p "require('$APP_DIR/package.json').version")
23+
DESCRIPTION=$(node -p "require('$APP_DIR/package.json').description || 'Frontend client for OpenScan3 firmware devices'")
24+
AUTHOR=$(node -p "require('$APP_DIR/package.json').author || 'OpenScan.eu Team <info@openscan.eu>'")
25+
DEB_FILE="$DIST_DIR/${PACKAGE_NAME}_${VERSION}_all.deb"
26+
27+
rm -rf "$PKG_ROOT" "$DEB_FILE"
28+
mkdir -p "$INSTALL_DIR" "$DEBIAN_DIR"
29+
cp -a "$SPA_DIR/." "$INSTALL_DIR/"
30+
find "$PKG_ROOT" -type d -exec chmod 755 {} +
31+
find "$PKG_ROOT" -type f -exec chmod 644 {} +
32+
33+
cat > "$DEBIAN_DIR/control" <<EOF
34+
Package: $PACKAGE_NAME
35+
Version: $VERSION
36+
Section: web
37+
Priority: optional
38+
Architecture: all
39+
Maintainer: $AUTHOR
40+
Description: $DESCRIPTION
41+
Static Quasar SPA assets for the OpenScan3 frontend.
42+
EOF
43+
44+
dpkg-deb -Zxz --root-owner-group --build "$PKG_ROOT" "$DEB_FILE"
45+
echo "$DEB_FILE"

app/src/components/base/BaseVersionInfoCard.vue

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { useVersionStore } from 'src/stores/version'
55
66
const apiConfigStore = useApiConfigStore()
77
const versionStore = useVersionStore()
8+
void versionStore.initialize()
89
</script>
910

1011
<template>
@@ -13,27 +14,23 @@ const versionStore = useVersionStore()
1314
<div class="col">
1415
<div class="text-caption">Frontend</div>
1516
<div>{{ versionStore.frontendVersion }}</div>
16-
<div
17-
v-if="versionStore.frontendUpdateAvailable"
18-
class="text-caption text-positive text-no-wrap q-mt-xs"
19-
>
20-
Update available: {{ versionStore.latestFrontendVersion }}
21-
</div>
2217
</div>
2318
<div class="col">
2419
<div class="text-caption">Firmware</div>
2520
<div>{{ versionStore.firmwareVersion ?? 'Loading...' }}</div>
26-
<div
27-
v-if="versionStore.firmwareUpdateAvailable"
28-
class="text-caption text-positive text-no-wrap q-mt-xs"
29-
>
30-
Update available: {{ versionStore.latestFirmwareVersion }}
31-
</div>
3221
</div>
3322
<div class="col">
3423
<div class="text-caption">API version</div>
3524
<div>{{ apiConfigStore.version }}</div>
3625
</div>
3726
</div>
27+
<q-banner class="bg-blue-1 text-primary q-mt-md" rounded>
28+
<div class="text-weight-medium">Stable release coming soon</div>
29+
<div class="q-mt-xs">
30+
OpenScan is moving from beta to its stable release. Updates are temporarily paused while we prepare the new version.
31+
You can continue using your scanner as usual. Installing the stable release will require flashing a new system image.
32+
We will provide instructions when it is ready.
33+
</div>
34+
</q-banner>
3835
</BaseSection>
3936
</template>

app/src/layouts/MainLayout.vue

Lines changed: 36 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,20 @@
4545
<q-list class="col">
4646
<EssentialLink v-for="link in upperLinks" :key="link.title" v-bind="link" />
4747
<q-separator />
48-
<EssentialLink v-for="link in lowerLinks" :key="link.title" v-bind="link" />
48+
<q-item
49+
clickable
50+
class="update-link--paused text-grey-7"
51+
aria-label="Updates are unavailable"
52+
@click="updateInfoDialog = true"
53+
>
54+
<q-item-section avatar>
55+
<q-icon name="system_update" />
56+
</q-item-section>
57+
<q-item-section>
58+
<q-item-label class="text-strike">Update</q-item-label>
59+
</q-item-section>
60+
<q-tooltip>Updates are unavailable</q-tooltip>
61+
</q-item>
4962
</q-list>
5063

5164
<q-separator />
@@ -89,6 +102,24 @@
89102
<q-page-container>
90103
<router-view />
91104
</q-page-container>
105+
106+
<q-dialog v-model="updateInfoDialog">
107+
<q-card style="max-width: 480px">
108+
<q-card-section class="row items-center q-pb-none">
109+
<div class="text-h6">Stable release coming soon</div>
110+
<q-space />
111+
<q-btn v-close-popup icon="close" flat round dense aria-label="Close" />
112+
</q-card-section>
113+
<q-card-section>
114+
OpenScan is moving from beta to its stable release. Updates are temporarily paused while we prepare the new version.
115+
You can continue using your scanner as usual. Installing the stable release will require flashing a new system image.
116+
We will provide instructions when it is ready.
117+
</q-card-section>
118+
<q-card-actions align="right">
119+
<q-btn v-close-popup flat color="primary" label="Close" />
120+
</q-card-actions>
121+
</q-card>
122+
</q-dialog>
92123
</q-layout>
93124
</template>
94125

@@ -98,7 +129,6 @@ import EssentialLink from 'components/EssentialLink.vue';
98129
import type { EssentialLinkProps } from 'components/models';
99130
import { useDeviceStore } from 'src/stores/device';
100131
import { useTaskStore } from 'src/stores/tasks';
101-
import { useVersionStore } from 'src/stores/version';
102132
import PowerControls from 'components/PowerControls.vue';
103133
import TaskDrawerPanel from 'components/task/TaskDrawerPanel.vue';
104134
@@ -135,23 +165,9 @@ const upperLinks: EssentialLinkProps[] = [
135165
},
136166
];
137167
138-
const versionStore = useVersionStore()
139-
void versionStore.initialize()
140-
141-
const updateBadge = computed(() => versionStore.anyUpdateAvailable ? '!' : '')
142-
143-
const lowerLinks = computed<EssentialLinkProps[]>(() => [
144-
{
145-
title: 'Update',
146-
icon: 'system_update',
147-
target: '_self',
148-
link: '/admin/',
149-
badge: updateBadge.value
150-
}
151-
])
152-
153168
const leftDrawerOpen = ref(true)
154169
const rightDrawerOpen = ref(false)
170+
const updateInfoDialog = ref(false)
155171
156172
const deviceStore = useDeviceStore()
157173
void deviceStore.ensureConnected()
@@ -181,5 +197,8 @@ function toggleRightDrawer() {
181197
height: 40px;
182198
}
183199
200+
.update-link--paused {
201+
opacity: 0.7;
202+
}
184203
185204
</style>

app/src/stores/version.ts

Lines changed: 2 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -7,84 +7,21 @@ interface SoftwareInfo {
77
firmware_version: string
88
}
99

10-
interface GitHubRelease {
11-
tag_name: string
12-
html_url: string
13-
published_at: string
14-
name: string
15-
}
16-
1710
interface VersionState {
1811
frontendVersion: string
1912
firmwareVersion: string | null
2013
firmwareVersionLoading: boolean
2114
firmwareVersionError: string | null
22-
latestFirmwareRelease: GitHubRelease | null
23-
latestFrontendRelease: GitHubRelease | null
24-
releasesLoading: boolean
25-
releasesError: string | null
26-
}
27-
28-
const GITHUB_FIRMWARE_REPO = 'OpenScan-org/OpenScan3'
29-
const GITHUB_FRONTEND_REPO = 'OpenScan-org/OpenScan3-Client'
30-
31-
const stripVersionMetadata = (version: string) => version.replace(/[-+].*$/, '')
32-
33-
function compareVersions(current: string, latest: string): number {
34-
const normalize = (v: string) =>
35-
stripVersionMetadata(v.replace(/^v/, ''))
36-
.split('.')
37-
.map(Number)
38-
const currentParts = normalize(current)
39-
const latestParts = normalize(latest)
40-
41-
for (let i = 0; i < Math.max(currentParts.length, latestParts.length); i++) {
42-
const c = currentParts[i] || 0
43-
const l = latestParts[i] || 0
44-
if (c < l) return -1
45-
if (c > l) return 1
46-
}
47-
return 0
4815
}
4916

5017
export const useVersionStore = defineStore('version', {
5118
state: (): VersionState => ({
5219
frontendVersion: packageJson.version ?? '0.0.0',
5320
firmwareVersion: null,
5421
firmwareVersionLoading: false,
55-
firmwareVersionError: null,
56-
latestFirmwareRelease: null,
57-
latestFrontendRelease: null,
58-
releasesLoading: false,
59-
releasesError: null
22+
firmwareVersionError: null
6023
}),
6124

62-
getters: {
63-
firmwareUpdateAvailable(): boolean {
64-
if (!this.firmwareVersion || !this.latestFirmwareRelease) return false
65-
return compareVersions(this.firmwareVersion, this.latestFirmwareRelease.tag_name) < 0
66-
},
67-
68-
frontendUpdateAvailable(): boolean {
69-
if (!this.latestFrontendRelease) return false
70-
return compareVersions(this.frontendVersion, this.latestFrontendRelease.tag_name) < 0
71-
},
72-
73-
anyUpdateAvailable(): boolean {
74-
return this.firmwareUpdateAvailable || this.frontendUpdateAvailable
75-
},
76-
77-
latestFirmwareVersion(): string | null {
78-
const tag = this.latestFirmwareRelease?.tag_name
79-
return tag ? stripVersionMetadata(tag) : null
80-
},
81-
82-
latestFrontendVersion(): string | null {
83-
const tag = this.latestFrontendRelease?.tag_name
84-
return tag ? stripVersionMetadata(tag) : null
85-
}
86-
},
87-
8825
actions: {
8926
async fetchFirmwareVersion() {
9027
if (this.firmwareVersionLoading) return
@@ -111,42 +48,8 @@ export const useVersionStore = defineStore('version', {
11148
}
11249
},
11350

114-
async fetchLatestReleases() {
115-
if (this.releasesLoading) return
116-
117-
this.releasesLoading = true
118-
this.releasesError = null
119-
120-
try {
121-
const [firmwareResponse, frontendResponse] = await Promise.all([
122-
fetch(`https://api.github.com/repos/${GITHUB_FIRMWARE_REPO}/releases/latest`),
123-
fetch(`https://api.github.com/repos/${GITHUB_FRONTEND_REPO}/releases/latest`)
124-
])
125-
126-
if (firmwareResponse.ok) {
127-
this.latestFirmwareRelease = await firmwareResponse.json()
128-
}
129-
130-
if (frontendResponse.ok) {
131-
this.latestFrontendRelease = await frontendResponse.json()
132-
}
133-
134-
if (!firmwareResponse.ok && !frontendResponse.ok) {
135-
this.releasesError = 'Failed to fetch release information'
136-
}
137-
} catch (error) {
138-
console.error('Failed to fetch latest releases:', error)
139-
this.releasesError = error instanceof Error ? error.message : 'Unknown error'
140-
} finally {
141-
this.releasesLoading = false
142-
}
143-
},
144-
14551
async initialize() {
146-
await Promise.all([
147-
this.fetchFirmwareVersion(),
148-
this.fetchLatestReleases()
149-
])
52+
await this.fetchFirmwareVersion()
15053
}
15154
}
15255
})

0 commit comments

Comments
 (0)