Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Desktop] Persist troubleshooting terminal when hidden #2398

Merged
merged 4 commits into from
Feb 2, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@
"@tiptap/starter-kit": "^2.10.4",
"@vueuse/core": "^11.0.0",
"@xterm/addon-fit": "^0.10.0",
"@xterm/addon-serialize": "^0.13.0",
"@xterm/xterm": "^5.5.0",
"axios": "^1.7.4",
"dotenv": "^16.4.5",
Expand Down
5 changes: 4 additions & 1 deletion src/components/bottomPanel/tabs/terminal/BaseTerminal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,19 @@
</template>

<script setup lang="ts">
import { Ref, ref } from 'vue'
import { Ref, onUnmounted, ref } from 'vue'

import { useTerminal } from '@/hooks/bottomPanelTabs/useTerminal'

const emit = defineEmits<{
created: [ReturnType<typeof useTerminal>, Ref<HTMLElement>]
unmounted: []
}>()
const terminalEl = ref<HTMLElement>()
const rootEl = ref<HTMLElement>()
emit('created', useTerminal(terminalEl), rootEl)

onUnmounted(() => emit('unmounted'))
</script>

<style scoped>
Expand Down
30 changes: 30 additions & 0 deletions src/hooks/bottomPanelTabs/useTerminalBuffer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { SerializeAddon } from '@xterm/addon-serialize'
import { Terminal } from '@xterm/xterm'
import { markRaw, onMounted, onUnmounted } from 'vue'

export function useTerminalBuffer() {
const serializeAddon = new SerializeAddon()
const terminal = markRaw(new Terminal({ convertEol: true }))

const copyTo = (destinationTerminal: Terminal) => {
destinationTerminal.write(serializeAddon.serialize())
}

const write = (message: string) => terminal.write(message)

const serialize = () => serializeAddon.serialize()

onMounted(() => {
terminal.loadAddon(serializeAddon)
})

onUnmounted(() => {
terminal.dispose()
})

return {
copyTo,
serialize,
write
}
}
7 changes: 6 additions & 1 deletion src/locales/en/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@
"workflow": "Workflow",
"success": "Success",
"ok": "OK",
"feedback": "Feedback"
"feedback": "Feedback",
"continue": "Continue"
},
"issueReport": {
"submitErrorReport": "Submit Error Report (Optional)",
Expand Down Expand Up @@ -702,6 +703,7 @@
"WEBCAM": "WEBCAM"
},
"maintenance": {
"title": "Maintenance",
"allOk": "No issues were detected.",
"status": "Status",
"detected": "Detected",
Expand All @@ -711,9 +713,12 @@
"Skipped": "Skipped",
"showManual": "Show maintenance tasks",
"confirmTitle": "Are you sure?",
"terminalDefaultMessage": "When you run a troubleshooting command, any output will be shown here.",
"consoleLogs": "Console Logs",
"error": {
"toastTitle": "Task error",
"taskFailed": "Task failed to run.",
"cannotContinue": "Unable to continue - errors remain",
"defaultDescription": "An error occurred while running a maintenance task."
}
},
Expand Down
7 changes: 6 additions & 1 deletion src/locales/fr/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
"comingSoon": "Bientôt disponible",
"command": "Commande",
"confirm": "Confirmer",
"continue": "Continuer",
"copyToClipboard": "Copier dans le presse-papiers",
"currentUser": "Utilisateur actuel",
"customize": "Personnaliser",
Expand Down Expand Up @@ -273,15 +274,19 @@
"Skipped": "Ignoré",
"allOk": "Aucun problème détecté.",
"confirmTitle": "Êtes-vous sûr ?",
"consoleLogs": "Journaux de la console",
"detected": "Détecté",
"error": {
"cannotContinue": "Impossible de continuer - des erreurs subsistent",
"defaultDescription": "Une erreur s'est produite lors de l'exécution d'une tâche de maintenance.",
"taskFailed": "La tâche a échoué.",
"toastTitle": "Erreur de tâche"
},
"refreshing": "Actualisation",
"showManual": "Afficher les tâches de maintenance",
"status": "Statut"
"status": "Statut",
"terminalDefaultMessage": "Lorsque vous exécutez une commande de dépannage, toute sortie sera affichée ici.",
"title": "Maintenance"
},
"menu": {
"autoQueue": "File d'attente automatique",
Expand Down
7 changes: 6 additions & 1 deletion src/locales/ja/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
"comingSoon": "近日公開",
"command": "コマンド",
"confirm": "確認",
"continue": "続ける",
"copyToClipboard": "クリップボードにコピー",
"currentUser": "現在のユーザー",
"customize": "カスタマイズ",
Expand Down Expand Up @@ -273,15 +274,19 @@
"Skipped": "スキップされました",
"allOk": "問題は検出されませんでした。",
"confirmTitle": "よろしいですか?",
"consoleLogs": "コンソールログ",
"detected": "検出されました",
"error": {
"cannotContinue": "続行できません - エラーが残っています",
"defaultDescription": "メンテナンスタスクの実行中にエラーが発生しました。",
"taskFailed": "タスクの実行に失敗しました。",
"toastTitle": "タスクエラー"
},
"refreshing": "更新中",
"showManual": "メンテナンスタスクを表示",
"status": "ステータス"
"status": "ステータス",
"terminalDefaultMessage": "トラブルシューティングコマンドを実行すると、出力はここに表示されます。",
"title": "メンテナンス"
},
"menu": {
"autoQueue": "自動キュー",
Expand Down
7 changes: 6 additions & 1 deletion src/locales/ko/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
"comingSoon": "곧 출시 예정",
"command": "명령",
"confirm": "확인",
"continue": "계속",
"copyToClipboard": "클립보드에 복사",
"currentUser": "현재 사용자",
"customize": "사용자 정의",
Expand Down Expand Up @@ -273,15 +274,19 @@
"Skipped": "건너뜀",
"allOk": "문제가 발견되지 않았습니다.",
"confirmTitle": "확실합니까?",
"consoleLogs": "콘솔 로그",
"detected": "감지됨",
"error": {
"cannotContinue": "계속할 수 없습니다 - 오류가 남아 있습니다",
"defaultDescription": "유지 보수 작업을 실행하는 동안 오류가 발생했습니다.",
"taskFailed": "작업 실행에 실패했습니다.",
"toastTitle": "작업 오류"
},
"refreshing": "새로 고침 중",
"showManual": "유지 보수 작업 보기",
"status": "상태"
"status": "상태",
"terminalDefaultMessage": "문제 해결 명령을 실행하면 출력이 여기에 표시됩니다.",
"title": "유지 보수"
},
"menu": {
"autoQueue": "자동 실행 큐",
Expand Down
7 changes: 6 additions & 1 deletion src/locales/ru/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
"comingSoon": "Скоро будет",
"command": "Команда",
"confirm": "Подтвердить",
"continue": "Продолжить",
"copyToClipboard": "Скопировать в буфер обмена",
"currentUser": "Текущий пользователь",
"customize": "Настроить",
Expand Down Expand Up @@ -273,15 +274,19 @@
"Skipped": "Пропущено",
"allOk": "Проблем не обнаружено.",
"confirmTitle": "Вы уверены?",
"consoleLogs": "Консольные журналы",
"detected": "Обнаружено",
"error": {
"cannotContinue": "Невозможно продолжить - остались ошибки",
"defaultDescription": "Произошла ошибка при выполнении задачи по обслуживанию.",
"taskFailed": "Не удалось выполнить задачу.",
"toastTitle": "Ошибка задачи"
},
"refreshing": "Обновление",
"showManual": "Показать задачи по обслуживанию",
"status": "Статус"
"status": "Статус",
"terminalDefaultMessage": "Когда вы запускаете команду для устранения неполадок, любой вывод будет отображаться здесь.",
"title": "Обслуживание"
},
"menu": {
"autoQueue": "Автоочередь",
Expand Down
7 changes: 6 additions & 1 deletion src/locales/zh/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
"comingSoon": "即将推出",
"command": "指令",
"confirm": "确认",
"continue": "继续",
"copyToClipboard": "复制到剪贴板",
"currentUser": "当前用户",
"customize": "自定义",
Expand Down Expand Up @@ -273,15 +274,19 @@
"Skipped": "跳过",
"allOk": "未检测到任何问题。",
"confirmTitle": "你确定吗?",
"consoleLogs": "控制台日志",
"detected": "检测到",
"error": {
"cannotContinue": "无法继续 - 仍有错误",
"defaultDescription": "运行维护任务时发生错误。",
"taskFailed": "任务运行失败。",
"toastTitle": "任务错误"
},
"refreshing": "刷新中",
"showManual": "显示维护任务",
"status": "状态"
"status": "状态",
"terminalDefaultMessage": "当你运行一个故障排除命令时,任何输出都会在这里显示。",
"title": "维护"
},
"menu": {
"autoQueue": "自动执行",
Expand Down
45 changes: 34 additions & 11 deletions src/views/MaintenanceView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@
>
<div class="max-w-screen-sm w-screen m-8 relative">
<!-- Header -->
<h1 class="backspan pi-wrench text-4xl font-bold">Maintenance</h1>
<h1 class="backspan pi-wrench text-4xl font-bold">
{{ t('maintenance.title') }}
</h1>

<!-- Toolbar -->
<div class="w-full flex flex-wrap gap-4 items-center">
<span class="grow">
Status: <StatusTag :refreshing="isRefreshing" :error="anyErrors" />
{{ t('maintenance.status') }}:
<StatusTag :refreshing="isRefreshing" :error="anyErrors" />
</span>
<div class="flex gap-4 items-center">
<SelectButton
Expand Down Expand Up @@ -53,14 +56,14 @@
<!-- Actions -->
<div class="flex justify-between gap-4 flex-row">
<Button
label="Console Logs"
:label="t('maintenance.consoleLogs')"
icon="pi pi-desktop"
icon-pos="left"
severity="secondary"
@click="toggleConsoleDrawer"
/>
<Button
label="Continue"
:label="t('g.continue')"
icon="pi pi-arrow-right"
icon-pos="left"
:severity="anyErrors ? 'secondary' : 'primary'"
Expand All @@ -72,11 +75,14 @@

<Drawer
v-model:visible="terminalVisible"
header="Terminal"
:header="t('g.terminal')"
position="bottom"
style="height: max(50vh, 34rem)"
>
<BaseTerminal @created="terminalCreated" />
<BaseTerminal
@created="terminalCreated"
@unmounted="terminalUnmounted"
/>
</Drawer>
<Toast />
</div>
Expand All @@ -85,6 +91,7 @@

<script setup lang="ts">
import { PrimeIcons } from '@primevue/core/api'
import { Terminal } from '@xterm/xterm'
import Button from 'primevue/button'
import Drawer from 'primevue/drawer'
import SelectButton from 'primevue/selectbutton'
Expand All @@ -98,6 +105,8 @@ import RefreshButton from '@/components/common/RefreshButton.vue'
import StatusTag from '@/components/maintenance/StatusTag.vue'
import TaskListPanel from '@/components/maintenance/TaskListPanel.vue'
import type { useTerminal } from '@/hooks/bottomPanelTabs/useTerminal'
import { useTerminalBuffer } from '@/hooks/bottomPanelTabs/useTerminalBuffer'
import { t } from '@/i18n'
import { useMaintenanceTaskStore } from '@/stores/maintenanceTaskStore'
import { MaintenanceFilter } from '@/types/desktop/maintenanceTypes'
import { electronAPI } from '@/utils/envUtil'
Expand Down Expand Up @@ -138,34 +147,43 @@ const filterOptions = ref([
/** Filter binding; can be set to show all tasks, or only errors. */
const filter = ref<MaintenanceFilter>(filterOptions.value[1])

/** The actual output of all terminal commands - not rendered */
const buffer = useTerminalBuffer()
let xterm: Terminal | null = null

/** If valid, leave the validation window. */
const completeValidation = async (alertOnFail = true) => {
const isValid = await electron.Validation.complete()
if (alertOnFail && !isValid) {
toast.add({
severity: 'error',
summary: 'Error',
detail: 'Unable to continue - errors remain',
summary: t('g.error'),
detail: t('maintenance.error.cannotContinue'),
life: 5_000
})
}
}

// Created and destroyed with the Drawer - contents copied from hidden buffer
const terminalCreated = (
{ terminal, useAutoSize }: ReturnType<typeof useTerminal>,
root: Ref<HTMLElement>
) => {
xterm = terminal
useAutoSize({ root, autoRows: true, autoCols: true })
electron.onLogMessage((message: string) => {
terminal.write(message)
})
terminal.write(t('maintenance.terminalDefaultMessage'))
buffer.copyTo(terminal)

terminal.options.cursorBlink = false
terminal.options.cursorStyle = 'bar'
terminal.options.cursorInactiveStyle = 'bar'
terminal.options.disableStdin = true
}

const terminalUnmounted = () => {
xterm = null
}

const toggleConsoleDrawer = () => {
terminalVisible.value = !terminalVisible.value
}
Expand All @@ -189,6 +207,11 @@ watch(
onMounted(async () => {
electron.Validation.onUpdate(processUpdate)

electron.onLogMessage((message: string) => {
buffer.write(message)
xterm?.write(message)
})

const update = await electron.Validation.getStatus()
processUpdate(update)
})
Expand Down