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