feat(hololake): add human-confirmed signed updates
This commit is contained in:
parent
e70c9201c2
commit
70556ed45f
8 changed files with 721 additions and 5 deletions
|
|
@ -5,7 +5,7 @@ import './design-tokens.css'
|
|||
import './styles.css'
|
||||
|
||||
type ThemeId = 'night' | 'dawn' | 'nebula' | 'candle' | 'clear'
|
||||
type Panel = 'channel' | 'receipts' | 'settings' | null
|
||||
type Panel = 'channel' | 'receipts' | 'updates' | 'settings' | null
|
||||
|
||||
interface HomeStatus {
|
||||
schema: string
|
||||
|
|
@ -63,6 +63,29 @@ interface RepositoryCandidate {
|
|||
inspection: RepositoryInspection
|
||||
}
|
||||
|
||||
interface ReleaseCandidate {
|
||||
candidateId: string
|
||||
confirmationToken: string
|
||||
releaseId: string
|
||||
currentVersion: string
|
||||
version: string
|
||||
notes: string
|
||||
features: string[]
|
||||
fixes: string[]
|
||||
minimumVersion: string
|
||||
dataMigrationRequired: boolean
|
||||
compatibilityNotes?: string
|
||||
restartMessage?: string
|
||||
rollbackSupported: boolean
|
||||
previousVersion?: string
|
||||
expiresAtUnixMs: number
|
||||
}
|
||||
|
||||
interface ReleaseCheckReceipt {
|
||||
state: string
|
||||
candidate?: ReleaseCandidate
|
||||
}
|
||||
|
||||
const themes: Array<{ id: ThemeId; name: string }> = [
|
||||
{ id: 'night', name: '夜湖星光' },
|
||||
{ id: 'dawn', name: '晨湖曦光' },
|
||||
|
|
@ -111,6 +134,9 @@ function HoloLakeApp() {
|
|||
const [repositoryCandidate, setRepositoryCandidate] = useState<RepositoryCandidate | null>(null)
|
||||
const [repositoryBusy, setRepositoryBusy] = useState(false)
|
||||
const [repositoryMessage, setRepositoryMessage] = useState('')
|
||||
const [releaseCandidate, setReleaseCandidate] = useState<ReleaseCandidate | null>(null)
|
||||
const [releaseBusy, setReleaseBusy] = useState(false)
|
||||
const [releaseMessage, setReleaseMessage] = useState('')
|
||||
const [message, setMessage] = useState('')
|
||||
|
||||
const refreshStatus = useCallback(async () => {
|
||||
|
|
@ -188,6 +214,44 @@ function HoloLakeApp() {
|
|||
}
|
||||
}
|
||||
|
||||
const checkUpdate = async () => {
|
||||
setReleaseBusy(true)
|
||||
setReleaseMessage('')
|
||||
try {
|
||||
const receipt = await invoke<ReleaseCheckReceipt>('check_hololake_update')
|
||||
setReleaseCandidate(receipt.candidate || null)
|
||||
if (!receipt.candidate) setReleaseMessage('当前已经是最新版本。')
|
||||
} catch (error) {
|
||||
const reason = String(error)
|
||||
setReleaseCandidate(null)
|
||||
setReleaseMessage(reason.includes('UNPROVISIONED_NO_NETWORK_REQUEST')
|
||||
? '京东主控的正式发布地址和更新公钥还没有登记。HoloLake 保持离线,不会向任何上游检查更新。'
|
||||
: `更新检查没有完成:${reason}`)
|
||||
} finally {
|
||||
setReleaseBusy(false)
|
||||
}
|
||||
}
|
||||
|
||||
const confirmUpdateInstall = async () => {
|
||||
if (!releaseCandidate) return
|
||||
setReleaseBusy(true)
|
||||
setReleaseMessage('正在重新核对广播并验证安装包;完成后由你手动重启 HoloLake。')
|
||||
try {
|
||||
await invoke('confirm_hololake_update_install', {
|
||||
input: {
|
||||
candidateId: releaseCandidate.candidateId,
|
||||
confirmationToken: releaseCandidate.confirmationToken,
|
||||
},
|
||||
})
|
||||
setReleaseCandidate(null)
|
||||
setReleaseMessage('签名安装包已验证并安装。请在准备好后手动重启 HoloLake。')
|
||||
} catch (error) {
|
||||
setReleaseMessage(`更新没有安装:${String(error)}`)
|
||||
} finally {
|
||||
setReleaseBusy(false)
|
||||
}
|
||||
}
|
||||
|
||||
const issueInvitation = async () => {
|
||||
setMessage('')
|
||||
try {
|
||||
|
|
@ -270,10 +334,10 @@ function HoloLakeApp() {
|
|||
<span className={`status-light ${status.codeRepositoryMountCount > 0 ? 'ready' : 'quiet'}`} aria-hidden="true" />
|
||||
<div><h2>代码通道仓库</h2><p>{status.codeRepositoryMountCount > 0 ? `已验证 · ${status.codeRepositoryMountCount} 个仓库` : '等待人类确认绑定'}</p></div>
|
||||
</article>
|
||||
<article className="status-item">
|
||||
<button className="status-item status-button" type="button" onClick={() => setPanel('updates')}>
|
||||
<span className={`status-light ${status.updateState.startsWith('READY') ? 'ready' : 'quiet'}`} aria-hidden="true" />
|
||||
<div><h2>更新广播</h2><p>{status.updateState.startsWith('READY') ? '仅接收光湖签名广播' : '信任根未配置 · 已关闭联网'}</p></div>
|
||||
</article>
|
||||
</button>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
|
|
@ -316,6 +380,27 @@ function HoloLakeApp() {
|
|||
{repositoryMessage && <p className="panel-message" aria-live="polite">{repositoryMessage}</p>}
|
||||
{receipts.length ? <ol className="receipt-list">{receipts.map((receipt) => <li key={receipt.sequence}><span>{receipt.sequence.toString().padStart(2, '0')}</span><div><b>{receipt.kind === 'REPOSITORY_BINDING_REVALIDATED' ? '仓库绑定已复核' : '远端对象读取已验证'}</b><small>{new Date(Number(receipt.observedAtUnixMs)).toLocaleString('zh-CN')} · {receipt.eventHash.slice(0, 12)}</small></div></li>)}</ol> : <div className="empty-state compact"><i /><b>湖面还没有新回执</b><span>完成代码仓库绑定或读取后,这里会出现可核验记录。</span></div>}
|
||||
</>}
|
||||
{panel === 'updates' && <>
|
||||
<p className="panel-kicker">光湖签名广播</p>
|
||||
<h2 id="panel-title">更新由你决定何时进入</h2>
|
||||
<p className="panel-intro">HoloLake 不接收开源上游自动推送。只有登记到本机的光湖主控地址和公钥可以参与更新,并且检查、安装、重启是三件分开的事。</p>
|
||||
{releaseCandidate ? <div className="release-card">
|
||||
<div className="release-version"><span>新版本</span><b>{releaseCandidate.version}</b><small>当前 {releaseCandidate.currentVersion}</small></div>
|
||||
<p>{releaseCandidate.notes}</p>
|
||||
<ul>{releaseCandidate.features.map((feature) => <li key={feature}>{feature}</li>)}{releaseCandidate.fixes.map((fix) => <li key={fix}>{fix}</li>)}</ul>
|
||||
<dl className="candidate-facts">
|
||||
<div><dt>数据迁移</dt><dd>{releaseCandidate.dataMigrationRequired ? '需要' : '不需要'}</dd></div>
|
||||
<div><dt>最低版本</dt><dd>{releaseCandidate.minimumVersion}</dd></div>
|
||||
<div><dt>重启</dt><dd>安装后由你手动完成</dd></div>
|
||||
<div><dt>回滚声明</dt><dd>{releaseCandidate.rollbackSupported ? `支持 · ${releaseCandidate.previousVersion || '上一版本'}` : '不支持'}</dd></div>
|
||||
</dl>
|
||||
<p className="release-warning">安装前会再次读取同一广播,验证下载域名、Tauri 签名、文件大小和 SHA-256;内容变化就停止并要求重新确认。</p>
|
||||
<button className="primary-action panel-action" type="button" disabled={releaseBusy} onClick={() => void confirmUpdateInstall()}>确认下载并安装<Icon name="arrow" /></button>
|
||||
</div> : <div className="update-state-card"><span className={`status-light ${status.updateState.startsWith('READY') ? 'ready' : 'quiet'}`} /><div><b>{status.updateState.startsWith('READY') ? '主控信任根已登记' : '更新网络保持关闭'}</b><span>{status.updateState.startsWith('READY') ? '可以由你手动检查签名广播' : '没有正式地址和公钥时,不发起网络请求'}</span></div></div>}
|
||||
{!releaseCandidate && <button className="primary-action panel-action" type="button" disabled={releaseBusy} onClick={() => void checkUpdate()}>{releaseBusy ? '正在核对…' : '检查光湖更新'}<Icon name="arrow" /></button>}
|
||||
{releaseMessage && <p className="panel-message" aria-live="polite">{releaseMessage}</p>}
|
||||
<p className="boundary-note">当前工程仍把持久回滚执行器列为生产签名发布前硬门;在它完成前不会宣称公共自动更新链路可以正式启用。</p>
|
||||
</>}
|
||||
{panel === 'settings' && <>
|
||||
<p className="panel-kicker">湖面设置</p>
|
||||
<h2 id="panel-title">选择你看见光湖的方式</h2>
|
||||
|
|
|
|||
Loading…
Reference in a new issue