All checks were successful
Build & Sign Wraith / Build Windows + Sign (push) Successful in 1m4s
DownloadUpdate expects *UpdateInfo (with downloadUrl), not a version string. Frontend was passing latestVersion which caused a silent deserialization failure. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
138 lines
4.6 KiB
Vue
138 lines
4.6 KiB
Vue
<template>
|
|
<div class="h-6 flex items-center justify-between px-4 bg-[var(--wraith-bg-secondary)] border-t border-[var(--wraith-border)] text-[10px] text-[var(--wraith-text-muted)] shrink-0">
|
|
<!-- Left: connection info -->
|
|
<div class="flex items-center gap-3">
|
|
<template v-if="sessionStore.activeSession">
|
|
<span class="flex items-center gap-1">
|
|
<span
|
|
class="w-1.5 h-1.5 rounded-full"
|
|
:class="sessionStore.activeSession.protocol === 'ssh' ? 'bg-[#3fb950]' : 'bg-[#1f6feb]'"
|
|
/>
|
|
{{ sessionStore.activeSession.protocol.toUpperCase() }}
|
|
</span>
|
|
<span class="text-[var(--wraith-text-secondary)]">·</span>
|
|
<span>{{ connectionInfo }}</span>
|
|
</template>
|
|
<template v-else>
|
|
<span>Ready</span>
|
|
</template>
|
|
</div>
|
|
|
|
<!-- Right: terminal info + update notification -->
|
|
<div class="flex items-center gap-3">
|
|
<!-- Update notification pill -->
|
|
<button
|
|
v-if="updateAvailable && updateInfo"
|
|
class="flex items-center gap-1.5 px-2 py-0.5 rounded-full text-[10px] font-medium transition-colors cursor-pointer"
|
|
:class="updateState === 'downloading'
|
|
? 'bg-[#1f6feb]/30 text-[#58a6ff]'
|
|
: 'bg-[#1f6feb]/20 text-[#58a6ff] hover:bg-[#1f6feb]/30'"
|
|
:disabled="updateState === 'downloading'"
|
|
@click="handleUpdate"
|
|
>
|
|
<template v-if="updateState === 'idle'">
|
|
<svg class="w-3 h-3" viewBox="0 0 16 16" fill="currentColor">
|
|
<path d="M8 2a6 6 0 1 0 0 12A6 6 0 0 0 8 2Zm.75 3.5v3.69l1.72-1.72a.75.75 0 1 1 1.06 1.06l-3 3a.75.75 0 0 1-1.06 0l-3-3a.75.75 0 1 1 1.06-1.06l1.72 1.72V5.5a.75.75 0 0 1 1.5 0Z" />
|
|
</svg>
|
|
v{{ updateInfo.latestVersion }} available — Update
|
|
</template>
|
|
<template v-else-if="updateState === 'downloading'">
|
|
<svg class="w-3 h-3 animate-spin" viewBox="0 0 16 16" fill="currentColor">
|
|
<path d="M8 0a8 8 0 1 0 0 16A8 8 0 0 0 8 0ZM1.5 8a6.5 6.5 0 1 1 13 0 6.5 6.5 0 0 1-13 0Z" opacity=".25" />
|
|
<path d="M8 0a8 8 0 0 1 8 8h-1.5A6.5 6.5 0 0 0 8 1.5V0Z" />
|
|
</svg>
|
|
Downloading...
|
|
</template>
|
|
</button>
|
|
|
|
<button
|
|
class="hover:text-[var(--wraith-text-primary)] transition-colors cursor-pointer"
|
|
title="Change terminal theme"
|
|
@click="emit('open-theme-picker')"
|
|
>
|
|
Theme: {{ activeThemeName }}
|
|
</button>
|
|
<span>UTF-8</span>
|
|
<span>120×40</span>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { computed, ref, onMounted } from "vue";
|
|
import { Call } from "@wailsio/runtime";
|
|
import { useSessionStore } from "@/stores/session.store";
|
|
import { useConnectionStore } from "@/stores/connection.store";
|
|
|
|
const sessionStore = useSessionStore();
|
|
const connectionStore = useConnectionStore();
|
|
|
|
const activeThemeName = ref("Dracula");
|
|
|
|
const emit = defineEmits<{
|
|
(e: "open-theme-picker"): void;
|
|
}>();
|
|
|
|
interface UpdateInfoData {
|
|
available: boolean;
|
|
currentVersion: string;
|
|
latestVersion: string;
|
|
downloadUrl: string;
|
|
sha256: string;
|
|
}
|
|
|
|
const updateAvailable = ref(false);
|
|
const updateInfo = ref<UpdateInfoData | null>(null);
|
|
const updateState = ref<"idle" | "downloading">("idle");
|
|
|
|
const connectionInfo = computed(() => {
|
|
const session = sessionStore.activeSession;
|
|
if (!session) return "";
|
|
|
|
const conn = connectionStore.connections.find((c) => c.id === session.connectionId);
|
|
if (!conn) return session.name;
|
|
|
|
return `root@${conn.hostname}:${conn.port}`;
|
|
});
|
|
|
|
/** Check for updates on mount. */
|
|
onMounted(async () => {
|
|
try {
|
|
const info = await Call.ByName(
|
|
"github.com/vstockwell/wraith/internal/updater.UpdateService.CheckForUpdate"
|
|
) as UpdateInfoData | null;
|
|
if (info && info.available) {
|
|
updateAvailable.value = true;
|
|
updateInfo.value = info;
|
|
}
|
|
} catch {
|
|
// Silent fail — update check is non-critical.
|
|
}
|
|
});
|
|
|
|
/** Download and apply an update. */
|
|
async function handleUpdate(): Promise<void> {
|
|
if (!updateInfo.value || updateState.value === "downloading") return;
|
|
updateState.value = "downloading";
|
|
try {
|
|
const path = await Call.ByName(
|
|
"github.com/vstockwell/wraith/internal/updater.UpdateService.DownloadUpdate",
|
|
updateInfo.value
|
|
) as string;
|
|
await Call.ByName(
|
|
"github.com/vstockwell/wraith/internal/updater.UpdateService.ApplyUpdate",
|
|
path
|
|
);
|
|
} catch (e) {
|
|
console.error("Update failed:", e);
|
|
updateState.value = "idle";
|
|
}
|
|
}
|
|
|
|
function setThemeName(name: string): void {
|
|
activeThemeName.value = name;
|
|
}
|
|
|
|
defineExpose({ setThemeName, activeThemeName, updateAvailable, updateInfo });
|
|
</script>
|