mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-09-18 05:23:57 +08:00
astrobot
This commit is contained in:
@@ -1275,6 +1275,8 @@ button.gx-chip:hover {
|
||||
transition: transform var(--motion-slow);
|
||||
width: 84vw;
|
||||
z-index: calc(var(--z-drawer) + 1);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
[data-theme="light"] .gx-drawer {
|
||||
@@ -1724,6 +1726,46 @@ button.gx-chip:hover {
|
||||
|
||||
.gx-nav-item i { font-size: 1.3rem; width: 24px; }
|
||||
|
||||
.gx-device-picker {
|
||||
border-top: 1px solid var(--glass-border);
|
||||
margin-top: auto;
|
||||
padding-top: var(--sp-3);
|
||||
}
|
||||
|
||||
.gx-device-picker__heading {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 0 var(--sp-2) var(--sp-1);
|
||||
}
|
||||
|
||||
.gx-device-picker__heading .gx-nav-section__title {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.gx-device-picker__heading-icon {
|
||||
color: var(--primary);
|
||||
font-size: var(--fs-sm);
|
||||
padding-right: var(--sp-2);
|
||||
}
|
||||
|
||||
.gx-device-picker__hint {
|
||||
color: var(--text-muted);
|
||||
font-size: var(--fs-xs);
|
||||
padding: 0 var(--sp-2);
|
||||
}
|
||||
|
||||
.gx-device-picker__row {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: var(--sp-1);
|
||||
}
|
||||
|
||||
.gx-device-picker__item {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.gx-device-picker__name {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
@@ -1742,6 +1784,67 @@ button.gx-chip:hover {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.gx-device-picker__rename {
|
||||
color: var(--text-muted);
|
||||
flex: 0 0 auto;
|
||||
height: 36px;
|
||||
width: 36px;
|
||||
}
|
||||
|
||||
.gx-device-picker__rename:hover {
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.gx-device-picker__editor {
|
||||
background: var(--surface-container);
|
||||
border: 1px solid var(--glass-border);
|
||||
border-radius: var(--radius-md);
|
||||
margin: var(--sp-2) var(--sp-2) 0;
|
||||
padding: var(--sp-3);
|
||||
}
|
||||
|
||||
.gx-device-picker__editor-label {
|
||||
color: var(--text-muted);
|
||||
display: block;
|
||||
font-size: var(--fs-xs);
|
||||
margin-bottom: var(--sp-2);
|
||||
}
|
||||
|
||||
.gx-device-picker__editor-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--sp-2);
|
||||
}
|
||||
|
||||
.gx-device-picker__editor input {
|
||||
background: var(--surface-container-high);
|
||||
border: 1px solid var(--glass-border);
|
||||
border-radius: var(--radius-sm);
|
||||
color: var(--on-surface);
|
||||
flex: 1 1 100%;
|
||||
font: inherit;
|
||||
min-width: 0;
|
||||
padding: var(--sp-2);
|
||||
}
|
||||
|
||||
.gx-device-picker__editor button {
|
||||
border: 1px solid var(--glass-border);
|
||||
border-radius: var(--radius-sm);
|
||||
cursor: pointer;
|
||||
font: inherit;
|
||||
padding: var(--sp-2) var(--sp-3);
|
||||
}
|
||||
|
||||
.gx-device-picker__save {
|
||||
background: var(--primary);
|
||||
color: var(--on-primary);
|
||||
}
|
||||
|
||||
.gx-device-picker__cancel {
|
||||
background: transparent;
|
||||
color: var(--on-surface);
|
||||
}
|
||||
|
||||
.gx-scrim {
|
||||
align-items: center;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
|
||||
@@ -157,7 +157,6 @@ export const AppShell = {
|
||||
<i class="bi" :class="navPinned ? 'bi-pin-angle-fill' : 'bi-pin-angle'"></i>
|
||||
</button>
|
||||
</div>
|
||||
<DevicePicker />
|
||||
<div class="gx-nav-section">
|
||||
<div class="gx-nav-section__title">{{ tr("Main") }}</div>
|
||||
<a class="gx-nav-item" :class="{ active: isActive('/') }" @click.prevent="navTo('/')">
|
||||
@@ -176,6 +175,7 @@ export const AppShell = {
|
||||
<i class="bi" :class="link.icon"></i><span>{{ tr(link.name, link.name) }}</span>
|
||||
</a>
|
||||
</div>
|
||||
<DevicePicker />
|
||||
</aside>
|
||||
|
||||
<main class="gx-content">
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
const SLUG_RE = /^[A-Za-z0-9]{16}$/
|
||||
const DEVICE_NAMES_KEY = "galaxy-device-names"
|
||||
const MAX_NAME_LENGTH = 40
|
||||
|
||||
export const DevicePicker = {
|
||||
name: "DevicePicker",
|
||||
@@ -6,14 +8,40 @@ export const DevicePicker = {
|
||||
return {
|
||||
devices: [],
|
||||
activeSlug: "",
|
||||
customNames: {},
|
||||
draftName: "",
|
||||
editingSlug: "",
|
||||
loading: true,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
hasMultipleDevices() { return this.devices.length > 1 },
|
||||
hasDevices() { return this.devices.length > 0 },
|
||||
},
|
||||
methods: {
|
||||
loadCustomNames() {
|
||||
try {
|
||||
const saved = JSON.parse(localStorage.getItem(DEVICE_NAMES_KEY) || "{}")
|
||||
if (saved && typeof saved === "object" && !Array.isArray(saved)) {
|
||||
this.customNames = Object.fromEntries(Object.entries(saved).filter(([slug, name]) => (
|
||||
SLUG_RE.test(slug) && typeof name === "string" && name.trim()
|
||||
)))
|
||||
}
|
||||
} catch (error) {
|
||||
this.customNames = {}
|
||||
}
|
||||
},
|
||||
saveCustomNames() {
|
||||
try {
|
||||
localStorage.setItem(DEVICE_NAMES_KEY, JSON.stringify(this.customNames))
|
||||
} catch (error) {
|
||||
// Private browsing can disable localStorage; the current name still works.
|
||||
}
|
||||
},
|
||||
displayName(device, index) {
|
||||
return this.customNames[device.slug] || device.name || `Comma ${index + 1}`
|
||||
},
|
||||
async loadDevices() {
|
||||
this.loadCustomNames()
|
||||
try {
|
||||
const response = await fetch("/_gateway/devices", { cache: "no-store" })
|
||||
if (!response.ok) return
|
||||
@@ -28,6 +56,30 @@ export const DevicePicker = {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
startRename(device, index) {
|
||||
this.editingSlug = device.slug
|
||||
this.draftName = this.displayName(device, index)
|
||||
this.$nextTick(() => {
|
||||
const input = this.$refs.deviceNameInput
|
||||
;(Array.isArray(input) ? input[0] : input)?.focus()
|
||||
})
|
||||
},
|
||||
cancelRename() {
|
||||
this.editingSlug = ""
|
||||
this.draftName = ""
|
||||
},
|
||||
saveRename(device) {
|
||||
if (!device) return
|
||||
const name = this.draftName.trim().slice(0, MAX_NAME_LENGTH)
|
||||
if (name) this.customNames = { ...this.customNames, [device.slug]: name }
|
||||
else {
|
||||
const nextNames = { ...this.customNames }
|
||||
delete nextNames[device.slug]
|
||||
this.customNames = nextNames
|
||||
}
|
||||
this.saveCustomNames()
|
||||
this.cancelRename()
|
||||
},
|
||||
selectDevice(device) {
|
||||
if (!device?.path || device.slug === this.activeSlug) return
|
||||
window.location.assign(device.path)
|
||||
@@ -37,16 +89,35 @@ export const DevicePicker = {
|
||||
this.loadDevices()
|
||||
},
|
||||
template: `
|
||||
<div v-if="!loading && hasMultipleDevices" class="gx-nav-section gx-device-picker">
|
||||
<div class="gx-nav-section__title">Commas</div>
|
||||
<a v-for="device in devices" :key="device.slug" class="gx-nav-item gx-device-picker__item"
|
||||
:class="{ active: device.slug === activeSlug }" :href="device.path"
|
||||
:aria-current="device.slug === activeSlug ? 'page' : undefined"
|
||||
@click.prevent="selectDevice(device)">
|
||||
<i class="bi bi-cpu"></i>
|
||||
<span class="gx-device-picker__name">{{ device.name }}</span>
|
||||
<span v-if="device.slug === activeSlug" class="gx-device-picker__current">Current</span>
|
||||
</a>
|
||||
<div v-if="!loading && hasDevices" class="gx-device-picker">
|
||||
<div class="gx-device-picker__heading">
|
||||
<div>
|
||||
<div class="gx-nav-section__title">Commas</div>
|
||||
<div class="gx-device-picker__hint">Switch device</div>
|
||||
</div>
|
||||
<i class="bi bi-arrow-left-right gx-device-picker__heading-icon" aria-hidden="true"></i>
|
||||
</div>
|
||||
<div v-for="(device, index) in devices" :key="device.slug" class="gx-device-picker__row">
|
||||
<a class="gx-nav-item gx-device-picker__item" :class="{ active: device.slug === activeSlug }" :href="device.path"
|
||||
:aria-current="device.slug === activeSlug ? 'page' : undefined"
|
||||
@click.prevent="selectDevice(device)">
|
||||
<i class="bi bi-cpu"></i>
|
||||
<span class="gx-device-picker__name">{{ displayName(device, index) }}</span>
|
||||
<span v-if="device.slug === activeSlug" class="gx-device-picker__current">Current</span>
|
||||
</a>
|
||||
<button type="button" class="gx-icon-btn gx-device-picker__rename" :aria-label="'Rename ' + displayName(device, index)"
|
||||
:title="'Rename ' + displayName(device, index)" @click="startRename(device, index)">
|
||||
<i class="bi bi-pencil" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
<form v-if="editingSlug" class="gx-device-picker__editor" @submit.prevent="saveRename(devices.find((device) => device.slug === editingSlug))">
|
||||
<label class="gx-device-picker__editor-label" for="gx-device-name">Rename comma</label>
|
||||
<div class="gx-device-picker__editor-row">
|
||||
<input id="gx-device-name" ref="deviceNameInput" v-model="draftName" maxlength="40" autocomplete="off" autofocus />
|
||||
<button type="submit" class="gx-device-picker__save">Save</button>
|
||||
<button type="button" class="gx-device-picker__cancel" @click="cancelRename">Cancel</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
`,
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import { GalaxyToggleCard } from "../components/GalaxyToggleCard.js"
|
||||
import { GalaxySection } from "../components/GalaxySection.js"
|
||||
import { DevModeBanner } from "../components/DevModeBanner.js"
|
||||
import { LanguageSelector } from "../components/LanguageSelector.js"
|
||||
import { setLanguage, t } from "../i18n.js"
|
||||
import { languageState, setLanguage, t } from "../i18n.js"
|
||||
|
||||
const LEGACY_PERSONALITY_KEYS = new Set([
|
||||
"AccelerationProfile", "AggressiveFollow", "AggressiveFollowHigh", "CustomAccelProfile",
|
||||
@@ -22,6 +22,8 @@ const LEGACY_PERSONALITY_KEYS = new Set([
|
||||
"StandardFollowHigh", "TrafficFollow", "TruckTuning",
|
||||
])
|
||||
|
||||
const LANGUAGE_SECTION_SLUG = "language"
|
||||
|
||||
export const Settings = {
|
||||
name: "Settings",
|
||||
components: { SettingTree, PersonalityProfiles, GalaxyToggleCard, GalaxySection, DevModeBanner, LongitudinalMode, LanguageSelector },
|
||||
@@ -53,6 +55,7 @@ export const Settings = {
|
||||
},
|
||||
hiddenAdvancedCount() { return countAdvancedHiddenByDeveloperMode(this.layout, this.values) },
|
||||
searchActive() { return !!this.searchTerm },
|
||||
currentLanguage() { return languageState.code },
|
||||
searchTerm: {
|
||||
get() { return store.search },
|
||||
set(v) { store.search = v },
|
||||
@@ -114,9 +117,16 @@ export const Settings = {
|
||||
},
|
||||
applyRouteSection() {
|
||||
const route = store.route
|
||||
if (route === "/settings") {
|
||||
if (this.activeSectionSlug === LANGUAGE_SECTION_SLUG) {
|
||||
const preferred = this.sections.find((s) => s.slug === this.defaultSectionSlug)
|
||||
this.activeSectionSlug = (preferred || this.sections[0])?.slug || ""
|
||||
}
|
||||
return
|
||||
}
|
||||
if (!route.startsWith("/settings/")) return
|
||||
const slug = route.replace(/^\/settings\/?/, "").split("?")[0].split("/")[0]
|
||||
if (slug && this.sections.some((s) => s.slug === slug)) this.activeSectionSlug = slug
|
||||
if (slug === LANGUAGE_SECTION_SLUG || this.sections.some((s) => s.slug === slug)) this.activeSectionSlug = slug
|
||||
if (store.params.open) this.expanded = { ...this.expanded, [store.params.open]: true }
|
||||
},
|
||||
lockReason(param) {
|
||||
@@ -169,13 +179,20 @@ export const Settings = {
|
||||
<div v-else>
|
||||
<div class="gx-tabs" style="display:flex; flex-wrap:wrap; gap:8px; margin-bottom:16px;">
|
||||
<button v-for="s in sections" :key="s.slug" type="button"
|
||||
class="gx-chip" :style="s.slug === activeSection.slug ? 'background: var(--primary); color: var(--on-primary);' : 'background: var(--surface-variant); color: var(--on-surface-variant); cursor:pointer;'"
|
||||
class="gx-chip" :style="s.slug === activeSectionSlug ? 'background: var(--primary); color: var(--on-primary);' : 'background: var(--surface-variant); color: var(--on-surface-variant); cursor:pointer;'"
|
||||
@click="selectSection(s.slug)">
|
||||
{{ tr(s.name, s.name) }}
|
||||
</button>
|
||||
<button type="button" class="gx-chip"
|
||||
:style="activeSectionSlug === 'language' ? 'background: var(--primary); color: var(--on-primary);' : 'background: var(--surface-variant); color: var(--on-surface-variant); cursor:pointer;'"
|
||||
@click="selectSection('language')">
|
||||
{{ tr("Language") }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="gx-card">
|
||||
<LanguageSelector v-if="activeSectionSlug === 'language'" :device-value="currentLanguage" />
|
||||
|
||||
<div v-else class="gx-card">
|
||||
<div class="gx-section__header">
|
||||
<i class="bi" :class="activeSection.icon"></i>
|
||||
<span class="gx-section__title">{{ tr(activeSection.name, activeSection.name) }}</span>
|
||||
@@ -189,8 +206,6 @@ export const Settings = {
|
||||
</template>
|
||||
|
||||
<div v-else class="gx-empty">{{ tr("No settings available.") }}</div>
|
||||
|
||||
<LanguageSelector v-if="route === '/settings' && !loading" :device-value="String(values.LanguageSetting || '')" />
|
||||
</div>
|
||||
`,
|
||||
}
|
||||
|
||||
@@ -17,6 +17,27 @@ function toPercent(value) {
|
||||
}
|
||||
|
||||
const CORE_UPDATE_BRANCHES = ["StarPilot", "Dom"]
|
||||
const REBOOT_PENDING_STORAGE_KEY = "galaxy-update-reboot-pending"
|
||||
|
||||
function readRebootMarker() {
|
||||
try {
|
||||
const raw = localStorage.getItem(REBOOT_PENDING_STORAGE_KEY)
|
||||
if (!raw) return null
|
||||
const parsed = JSON.parse(raw)
|
||||
const startedAt = Number(parsed?.startedAt)
|
||||
return Number.isFinite(startedAt) && startedAt > 0 ? startedAt : null
|
||||
} catch (e) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
function writeRebootMarker(startedAt) {
|
||||
try { localStorage.setItem(REBOOT_PENDING_STORAGE_KEY, JSON.stringify({ startedAt })) } catch (e) {}
|
||||
}
|
||||
|
||||
function clearRebootMarker() {
|
||||
try { localStorage.removeItem(REBOOT_PENDING_STORAGE_KEY) } catch (e) {}
|
||||
}
|
||||
|
||||
export const SystemTools = {
|
||||
name: "SystemTools",
|
||||
@@ -45,6 +66,11 @@ export const SystemTools = {
|
||||
|
||||
isOnroad: false,
|
||||
fastStatus: null,
|
||||
statusUnavailable: false,
|
||||
rebootPending: !!readRebootMarker(),
|
||||
rebootStartedAt: readRebootMarker() || 0,
|
||||
rebootOfflineSeen: false,
|
||||
reconnectedNotice: false,
|
||||
checkedForUpdates: false,
|
||||
busy: "",
|
||||
autoUpdateBusy: false,
|
||||
@@ -80,8 +106,8 @@ export const SystemTools = {
|
||||
return this.branchLoading || this.isOnroad || !!this.fastStatus?.isOnroad || this.updateInProgress || !!this.busy
|
||||
},
|
||||
statusRebooting() { return String(this.fastStatus?.stage || "").trim().toLowerCase() === "rebooting" },
|
||||
updateInProgress() { return !!this.fastStatus?.running || this.statusRebooting },
|
||||
statusPollingNeeded() { return !this.fastStatus || this.updateInProgress },
|
||||
updateInProgress() { return !!this.fastStatus?.running || this.statusRebooting || this.rebootPending },
|
||||
statusPollingNeeded() { return !this.fastStatus || this.updateInProgress || this.rebootPending },
|
||||
versionChoices() { return this.targetBranch === "StarPilot" ? releaseVersions(this.versionCommits) : this.versionCommits },
|
||||
installVersionBlocked() {
|
||||
return this.branchSwitchBlocked || this.branchBusy || !this.branches.includes(this.targetBranch) ||
|
||||
@@ -145,13 +171,42 @@ export const SystemTools = {
|
||||
try {
|
||||
const status = await api.getUpdateFastStatus()
|
||||
if (!status) throw new Error("Update status unavailable")
|
||||
const stage = String(status.stage || "").trim().toLowerCase()
|
||||
if (stage === "rebooting" && !this.rebootPending) {
|
||||
this.rebootPending = true
|
||||
this.rebootStartedAt = Date.now()
|
||||
writeRebootMarker(this.rebootStartedAt)
|
||||
}
|
||||
const pendingAge = this.rebootStartedAt ? Date.now() - this.rebootStartedAt : 0
|
||||
const deviceReturned = this.rebootPending && !status.running && stage !== "rebooting" &&
|
||||
(this.statusUnavailable || pendingAge >= 30_000)
|
||||
const updateFailed = this.rebootPending && stage === "error"
|
||||
this.fastStatus = status
|
||||
this.statusUnavailable = false
|
||||
this.isOnroad = !!status.isOnroad
|
||||
if (deviceReturned) this.clearRebootPending()
|
||||
else if (updateFailed) this.clearRebootPending(false)
|
||||
} catch (e) {
|
||||
this.fastStatus = null
|
||||
this.statusUnavailable = true
|
||||
if (this.rebootPending) this.rebootOfflineSeen = true
|
||||
else this.fastStatus = null
|
||||
if (throwOnError) throw e
|
||||
}
|
||||
},
|
||||
markRebootPending() {
|
||||
this.rebootPending = true
|
||||
this.rebootStartedAt = Date.now()
|
||||
this.rebootOfflineSeen = false
|
||||
this.reconnectedNotice = false
|
||||
writeRebootMarker(this.rebootStartedAt)
|
||||
},
|
||||
clearRebootPending(showNotice = true) {
|
||||
this.rebootPending = false
|
||||
this.rebootStartedAt = 0
|
||||
this.rebootOfflineSeen = false
|
||||
clearRebootMarker()
|
||||
if (showNotice) this.reconnectedNotice = true
|
||||
},
|
||||
async backupToggles() {
|
||||
try {
|
||||
const blob = await api.backupToggles()
|
||||
@@ -228,6 +283,7 @@ export const SystemTools = {
|
||||
if (!(await GalaxyConfirm({ title: "Reset toggles to default?", message: "This resets all toggles to their default values and reboots.", confirmLabel: "Reset", danger: true }))) return
|
||||
try {
|
||||
await api.resetTogglesDefault()
|
||||
this.markRebootPending()
|
||||
showSnackbar("Resetting toggles to default... rebooting.")
|
||||
} catch (e) {
|
||||
showSnackbar("Reset failed.", "error")
|
||||
@@ -347,6 +403,7 @@ export const SystemTools = {
|
||||
return
|
||||
}
|
||||
const result = await api.installUpdateVersion(branch, commit)
|
||||
this.markRebootPending()
|
||||
showSnackbar(result?.message || `Installing ${version} on ${branch}...`)
|
||||
await this.loadFastStatus()
|
||||
} catch (e) {
|
||||
@@ -420,6 +477,7 @@ export const SystemTools = {
|
||||
}
|
||||
const fn = action === "fast" ? api.updateFast : action === "recover" ? api.updateRecover : api.updateRollback
|
||||
const payload = await fn()
|
||||
this.markRebootPending()
|
||||
showSnackbar(payload?.message || "Update started.")
|
||||
await this.loadFastStatus()
|
||||
} catch (e) {
|
||||
@@ -432,6 +490,7 @@ export const SystemTools = {
|
||||
if (!(await GalaxyConfirm({ title: "Factory reset (SAVE ME)?", message: "This wipes params, backups, themes, models, maps, and route data, then reboots. This cannot be undone.", confirmLabel: "Factory Reset", danger: true }))) return
|
||||
try {
|
||||
await api.factoryReset()
|
||||
this.markRebootPending()
|
||||
showSnackbar("SAVE ME initiated — factory resetting...")
|
||||
await this.loadFastStatus()
|
||||
} catch (e) {
|
||||
@@ -502,22 +561,26 @@ export const SystemTools = {
|
||||
<div v-if="branchLoading" class="gx-loading">Loading update info...</div>
|
||||
<template v-else>
|
||||
<GxNotice v-if="isOnroad" text="Updates and branch switching are only available while offroad." style="margin-bottom:12px;" />
|
||||
<GxNotice v-if="rebootPending" tone="info" icon="bi-arrow-repeat gx-spin" title="Device rebooting"
|
||||
:text="statusUnavailable ? 'The device is temporarily offline. Galaxy will keep checking until it reconnects.' : 'The update is complete. Waiting for the device to reconnect…'" />
|
||||
<GxNotice v-else-if="reconnectedNotice" tone="info" icon="bi-check-circle-fill" title="Device reconnected"
|
||||
text="Galaxy is connected again and the update status is current." />
|
||||
|
||||
<div v-if="fastStatus" class="gx-card" style="margin-bottom:12px;">
|
||||
<div class="gx-section__header">
|
||||
<i class="bi bi-arrow-repeat"></i>
|
||||
<span class="gx-section__title">Update Status</span>
|
||||
<span v-if="updateInProgress" class="gx-chip" style="background:var(--primary);color:var(--on-primary);">{{ statusRebooting ? 'Reconnecting…' : fastStatus.progressPercent + '%' }}</span>
|
||||
<span v-if="updateInProgress" class="gx-chip" style="background:var(--primary);color:var(--on-primary);">{{ rebootPending || statusRebooting ? 'Reconnecting…' : fastStatus.progressPercent + '%' }}</span>
|
||||
<span v-else-if="updateAvailable" class="gx-chip" style="background:var(--warning);color:var(--black);">Update available</span>
|
||||
<span v-else-if="checkedForUpdates" class="gx-chip">Up to date</span>
|
||||
<span v-else class="gx-chip">Not checked</span>
|
||||
</div>
|
||||
<div style="padding: var(--sp-3); display:grid; gap:6px;">
|
||||
<div class="gx-row" style="border-top:none; min-height:0; padding:4px 0;"><span class="gx-row__label">Installed branch</span><span class="gx-row__value">{{ fastStatus.branch || currentBranch || '—' }}</span></div>
|
||||
<div v-if="updateInProgress" class="gx-row" style="border-top:none; min-height:0; padding:4px 0;"><span class="gx-row__label">Stage</span><span class="gx-row__value">{{ fastStatus.stage }} · {{ fastStatus.progressLabel }}</span></div>
|
||||
<div v-if="updateInProgress && !rebootPending" class="gx-row" style="border-top:none; min-height:0; padding:4px 0;"><span class="gx-row__label">Stage</span><span class="gx-row__value">{{ fastStatus.stage }} · {{ fastStatus.progressLabel }}</span></div>
|
||||
<div class="gx-row" style="border-top:none; min-height:0; padding:4px 0;"><span class="gx-row__label">Local</span><span class="gx-row__value" style="font-family:monospace;">{{ shortCommit(fastStatus.localCommit) }}</span></div>
|
||||
<div class="gx-row" style="border-top:none; min-height:0; padding:4px 0;"><span class="gx-row__label">Remote</span><span class="gx-row__value" style="font-family:monospace;">{{ shortCommit(fastStatus.remoteCommit) }}</span></div>
|
||||
<div v-if="updateInProgress" class="gx-update-progress" role="progressbar" aria-label="Update progress"
|
||||
<div v-if="updateInProgress && !rebootPending" class="gx-update-progress" role="progressbar" aria-label="Update progress"
|
||||
:aria-valuenow="Math.round(fastStatus.progressPercent || 0)" aria-valuemin="0" aria-valuemax="100">
|
||||
<div class="gx-update-progress__track">
|
||||
<div class="gx-update-progress__fill" :class="{ 'gx-update-progress__fill--error': fastStatus.stage === 'error' }"
|
||||
@@ -530,6 +593,7 @@ export const SystemTools = {
|
||||
<small v-if="fastStatus.progressDetail">{{ fastStatus.progressDetail }}</small>
|
||||
</div>
|
||||
<div v-if="fastStatus.message" class="gx-note">{{ fastStatus.message }}</div>
|
||||
<div v-if="statusUnavailable" class="gx-note">Waiting for the device to reconnect. The last update status is being kept on screen.</div>
|
||||
<div v-if="fastStatus.warning && (updateInProgress || fastStatus.updateAvailable)" class="gx-note gx-note--danger">{{ fastStatus.warning }}</div>
|
||||
<div v-if="fastStatus.agnosUpdate?.available && fastStatus.agnosUpdate?.warnings?.length" style="margin-top:4px;">
|
||||
<div v-for="w in fastStatus.agnosUpdate.warnings" :key="w" class="gx-note gx-note--danger"><i class="bi bi-exclamation-triangle-fill"></i> {{ w }}</div>
|
||||
|
||||
@@ -68,9 +68,11 @@ def test_ui_device_picker_uses_gateway_directory_and_preserves_local_galaxy():
|
||||
shell = _read("js/components/AppShell.js")
|
||||
|
||||
assert 'fetch("/_gateway/devices"' in picker
|
||||
assert "hasMultipleDevices" in picker
|
||||
assert "localStorage" in picker
|
||||
assert "Rename comma" in picker
|
||||
assert "hasDevices" in picker
|
||||
assert "window.location.assign(device.path)" in picker
|
||||
assert '<DevicePicker />' in shell
|
||||
assert shell.index('<DevicePicker />') > shell.index('v-for="(links, section) in NAV"')
|
||||
assert "Local Galaxy instances do not have the gateway directory endpoint." in picker
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user