@samitouri / QOSami-HFS / commits / a14ee01c

admin/home: "install other version"

Massimo Melina committed Jan 22, 2025 at 00:08 UTC a14ee01cc1bd7bee55081516bae20056ef6e3f8f
3 files changed +44 -28
admin/src/HomePage.ts
+11 -4
@@ -32,7 +32,8 @@ export default function HomePage() {
32 const { list: plugins } = useApiList('get_plugins')
33 const [checkPlugins, setCheckPlugins] = useState(false)
34 const { list: pluginUpdates} = useApiList(checkPlugins && 'get_plugin_updates')
35 - const [updates, setUpdates] = useState<undefined | any[]>()
35 + const [updates, setUpdates] = useState<undefined | Release[]>()
36 + const [otherVersions, setOtherVersions] = useState<undefined | Release[]>()
37 if (statusEl || !status)
38 return statusEl
39 const { http, https } = status
@@ -122,13 +123,19 @@ export default function HomePage() {
123 ]
124 }
125 }),
125 - updates && with_(_.find(updates, 'isNewer'), newer => h(Fragment, {},
126 + updates && with_(_.find(updates, 'isNewer'), newer =>
127 !updates.length || !status.updatePossible && !newer ? entry('', "No update available")
128 : newer && !status.updatePossible ? entry('success', `Version ${newer.name} available`)
129 : h(Flex, { vert: true },
130 updates.map((x: any) => h(Update, { info: x, key: x.name })) ),
130 - entry('', h(Link, { href: REPO_URL + 'releases/', target: 'repo' }, "All releases"))
131 - )),
131 + ),
132 + !status.updatePossible ? entry('', h(Link, { href: REPO_URL + 'releases/', target: 'repo' }, "All releases"))
133 + : otherVersions ? h(Flex, { vert: true }, otherVersions.map((x: any) => h(Update, { info: x, key: x.name, bodyCollapsed: true })) )
134 + : h(Btn, {
135 + variant: 'outlined',
136 + onClick: () => apiCall<typeof adminApis.get_other_versions>('get_other_versions')
137 + .then(x => setOtherVersions(x.options), alertDialog)
138 + }, "Install other version"),
139 h(SwitchThemeBtn, { variant: 'outlined' }),
140 Date.now() - Number(new Date(status.started)) > HOUR && h(Link, {
141 title: "Donate",
src/adminApis.ts
+6 -1
@@ -24,7 +24,7 @@ import { execFile } from 'child_process'
24 import { promisify } from 'util'
25 import { customHtmlSections, customHtml, saveCustomHtml } from './customHtml'
26 import _ from 'lodash'
27 -import { autoCheckUpdateResult, getUpdates, localUpdateAvailable, update, updateSupported } from './update'
27 +import { autoCheckUpdateResult, getUpdates, getVersions, localUpdateAvailable, update, updateSupported } from './update'
28 import { resolve } from 'path'
29 import { getErrorSections } from './errorPages'
30 import { ip2country } from './geo'
@@ -78,6 +78,11 @@ export const adminApis = {
78 async check_update() {
79 return { options: await getUpdates() }
80 },
81 + async get_other_versions() {
82 + let left = 50
83 + const res = await getVersions(r => !r.prerelease && !left--)
84 + return { options: res.filter(x => !x.prerelease) }
85 + },
86 async wait_project_info() { // used by admin/home/check-for-updates
87 await getProjectInfo()
88 return {}
src/update.ts
+27 -23
@@ -56,33 +56,37 @@ export type Release = { // not using interface, as it will not work with kvstora
56 const ReleaseKeys = ['prerelease', 'tag_name', 'name', 'body', 'assets', 'isNewer', 'versionScalar'] satisfies (keyof Release)[]
57 const ReleaseAssetKeys = ['name', 'browser_download_url'] satisfies (keyof Release['assets'][0])[]
58
59 +const curV = currentVersion.getScalar()
60 +function prepareRelease(r: Release) {
61 + const v = versionToScalar(r.name)
62 + return Object.assign(_.pick(r, ReleaseKeys), { // prune a bit, as it will be serialized and it has a lot of unused data
63 + versionScalar: v,
64 + isNewer: v > curV, // make easy to know what's newer
65 + assets: r.assets.map((a: any) => _.pick(a, ReleaseAssetKeys))
66 + })
67 +}
68 +
69 +export async function getVersions(interrupt?: (r: Release) => boolean) {
70 + if (!updateToBeta.get() && !RUNNING_BETA) return []
71 + const ret: Release[] = []
72 + for await (const x of apiGithubPaginated(`repos/${HFS_REPO}/releases`)) {
73 + if (x.name.endsWith('-ignore')) continue
74 + const rel = prepareRelease(x)
75 + if (rel.versionScalar === curV) continue
76 + if (interrupt?.(rel)) break // avoid fetching too much data
77 + ret.push(rel)
78 + }
79 + return _.sortBy(ret, x => -x.versionScalar)
80 +}
81 +
82 export async function getUpdates(strict=false) {
83 getProjectInfo() // check for alerts
61 - const stable: Release = await getRepoInfo(HFS_REPO + '/releases/latest')
62 - stable.isNewer = currentVersion.olderThan(stable.tag_name)
63 - stable.versionScalar = versionToScalar(stable.name)
64 - const ret = await getBetas()
84 + const stable: Release = prepareRelease(await getRepoInfo(HFS_REPO + '/releases/latest'))
85 + const res = await getVersions(r => r.versionScalar < stable.versionScalar) // we don't consider betas before stable
86 + const ret = res.filter(x => x.prerelease && (strict ? x.isNewer : (x.versionScalar !== currentVersion.getScalar())) )
87 if (stable.isNewer || RUNNING_BETA)
88 ret.push(stable)
67 - // prune a bit, as it will be serialized, but it has a lot of unused data
68 - return _.sortBy(ret, x => -x.versionScalar).filter(x => !strict || x.isNewer).map(x =>
69 - Object.assign(_.pick(x, ReleaseKeys), { assets: x.assets.map(a => _.pick(a, ReleaseAssetKeys)) }))
70 -
71 - async function getBetas() {
72 - if (!updateToBeta.get() && !RUNNING_BETA) return []
73 - const ret: Release[] = []
74 - const curV = currentVersion.getScalar()
75 - for await (const x of apiGithubPaginated(`repos/${HFS_REPO}/releases`)) {
76 - if (!x.prerelease || x.name.endsWith('-ignore')) continue
77 - const v = x.versionScalar = versionToScalar(x.name)
78 - if (v < stable.versionScalar) // we don't consider betas before stable
79 - return ret
80 - if (v === curV) continue // skip current
81 - x.isNewer = v > curV // make easy to know what's newer
82 - ret.push(x)
83 - }
84 - return ret
85 - }
89 + return ret
90 }
91
92 const LOCAL_UPDATE = 'hfs-update.zip' // update from file takes precedence over net