[codex] add version preview workflow (#1086)
* add version preview workflow * fix sidebar group test * fix legacy usage schema migration
This commit is contained in:
@@ -3,6 +3,7 @@ import router from '@/router'
|
||||
const DEFAULT_BASE_URL = ''
|
||||
|
||||
function getBaseUrl(): string {
|
||||
if (import.meta.env.VITE_HERMES_PREVIEW === '1') return DEFAULT_BASE_URL
|
||||
return localStorage.getItem('hermes_server_url') || DEFAULT_BASE_URL
|
||||
}
|
||||
|
||||
|
||||
@@ -267,8 +267,9 @@ export function buildKanbanEventsWebSocketUrl(opts?: KanbanBoardOptions): string
|
||||
return `${websocketProtocol(base)}//${new URL(base).host}${path}`
|
||||
}
|
||||
|
||||
const host = import.meta.env.DEV
|
||||
? formatHostForPort(location.hostname, 8648)
|
||||
const directDevPort = import.meta.env.VITE_HERMES_DIRECT_WS_PORT
|
||||
const host = import.meta.env.DEV && directDevPort
|
||||
? formatHostForPort(location.hostname, Number(directDevPort))
|
||||
: location.host
|
||||
return `${websocketProtocol()}//${host}${path}`
|
||||
}
|
||||
|
||||
@@ -9,6 +9,34 @@ export interface HealthResponse {
|
||||
node_version?: string
|
||||
}
|
||||
|
||||
export interface PreviewTag {
|
||||
name: string
|
||||
sha: string
|
||||
}
|
||||
|
||||
export interface PreviewStatus {
|
||||
preview_dir: string
|
||||
exists: boolean
|
||||
has_package: boolean
|
||||
installed: boolean
|
||||
running: boolean
|
||||
pid: number | null
|
||||
current_tag: string
|
||||
frontend_url: string
|
||||
agent_bridge_endpoint: string
|
||||
log_path: string
|
||||
webui_home: string
|
||||
action_log_path: string
|
||||
dev_log_path: string
|
||||
action_log: string
|
||||
dev_log: string
|
||||
}
|
||||
|
||||
export interface PreviewActionResponse extends PreviewStatus {
|
||||
success: boolean
|
||||
message?: string
|
||||
}
|
||||
|
||||
// Config-based model types
|
||||
export interface ModelInfo {
|
||||
id: string
|
||||
@@ -84,6 +112,36 @@ export async function triggerUpdate(): Promise<{ success: boolean; message: stri
|
||||
return request<{ success: boolean; message: string }>('/api/hermes/update', { method: 'POST' })
|
||||
}
|
||||
|
||||
export async function fetchPreviewStatus(): Promise<PreviewStatus> {
|
||||
return request<PreviewStatus>('/api/hermes/update/preview')
|
||||
}
|
||||
|
||||
export async function fetchPreviewTags(): Promise<{ tags: PreviewTag[] }> {
|
||||
return request<{ tags: PreviewTag[] }>('/api/hermes/update/preview/tags')
|
||||
}
|
||||
|
||||
export async function preparePreview(tag: string): Promise<PreviewActionResponse> {
|
||||
return request<PreviewActionResponse>('/api/hermes/update/preview/prepare', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ tag }),
|
||||
})
|
||||
}
|
||||
|
||||
export async function installPreview(): Promise<PreviewActionResponse> {
|
||||
return request<PreviewActionResponse>('/api/hermes/update/preview/install', { method: 'POST' })
|
||||
}
|
||||
|
||||
export async function startPreview(tag?: string): Promise<PreviewActionResponse> {
|
||||
return request<PreviewActionResponse>('/api/hermes/update/preview/start', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ tag }),
|
||||
})
|
||||
}
|
||||
|
||||
export async function stopPreview(): Promise<PreviewActionResponse> {
|
||||
return request<PreviewActionResponse>('/api/hermes/update/preview/stop', { method: 'POST' })
|
||||
}
|
||||
|
||||
export async function fetchConfigModels(): Promise<ConfigModelsResponse> {
|
||||
return request<ConfigModelsResponse>('/api/hermes/config/models')
|
||||
}
|
||||
|
||||
@@ -148,8 +148,9 @@ function buildWsUrl(): string {
|
||||
return `${wsProtocol}//${new URL(base).host}/api/hermes/terminal${token ? `?token=${encodeURIComponent(token)}` : ""}`;
|
||||
}
|
||||
|
||||
const host = import.meta.env.DEV
|
||||
? formatHostForPort(location.hostname, 8648)
|
||||
const directDevPort = import.meta.env.VITE_HERMES_DIRECT_WS_PORT;
|
||||
const host = import.meta.env.DEV && directDevPort
|
||||
? formatHostForPort(location.hostname, Number(directDevPort))
|
||||
: location.host;
|
||||
return `${wsProtocol}//${host}/api/hermes/terminal${token ? `?token=${encodeURIComponent(token)}` : ""}`;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,313 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { NAlert, NButton, NDescriptions, NDescriptionsItem, NSelect, NSpace, NTag, useMessage } from 'naive-ui'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import {
|
||||
fetchPreviewStatus,
|
||||
fetchPreviewTags,
|
||||
installPreview,
|
||||
preparePreview,
|
||||
startPreview,
|
||||
stopPreview,
|
||||
type PreviewStatus,
|
||||
type PreviewTag,
|
||||
} from '@/api/hermes/system'
|
||||
|
||||
const { t } = useI18n()
|
||||
const message = useMessage()
|
||||
|
||||
const loading = ref(false)
|
||||
const tagsLoading = ref(false)
|
||||
const actionLoading = ref('')
|
||||
const tags = ref<PreviewTag[]>([])
|
||||
const selectedTag = ref('')
|
||||
const status = ref<PreviewStatus | null>(null)
|
||||
|
||||
const tagOptions = computed(() => tags.value.map(tag => ({
|
||||
label: tag.name,
|
||||
value: tag.name,
|
||||
})))
|
||||
const actionLog = computed(() => status.value?.action_log || '')
|
||||
const devLog = computed(() => status.value?.dev_log || '')
|
||||
|
||||
function applyErrorStatus(err: any) {
|
||||
const messageText = String(err?.message || '')
|
||||
const jsonStart = messageText.indexOf('{')
|
||||
if (jsonStart < 0) return
|
||||
try {
|
||||
const parsed = JSON.parse(messageText.slice(jsonStart))
|
||||
if (parsed && typeof parsed === 'object' && 'preview_dir' in parsed) {
|
||||
status.value = parsed as PreviewStatus
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
|
||||
async function loadStatus() {
|
||||
status.value = await fetchPreviewStatus()
|
||||
if (!selectedTag.value && status.value.current_tag) {
|
||||
selectedTag.value = status.value.current_tag
|
||||
}
|
||||
}
|
||||
|
||||
async function loadTags() {
|
||||
tagsLoading.value = true
|
||||
try {
|
||||
const res = await fetchPreviewTags()
|
||||
tags.value = res.tags
|
||||
if (!selectedTag.value && tags.value[0]) {
|
||||
selectedTag.value = tags.value[0].name
|
||||
}
|
||||
} finally {
|
||||
tagsLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function handleRefresh() {
|
||||
loading.value = true
|
||||
try {
|
||||
await Promise.all([loadStatus(), loadTags()])
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function runAction(action: string, fn: () => Promise<PreviewStatus & { success?: boolean; message?: string }>, successKey: string) {
|
||||
actionLoading.value = action
|
||||
try {
|
||||
const res = await fn()
|
||||
status.value = res
|
||||
if (res.success === false) {
|
||||
message.warning(res.message || t('githubPreview.actionFailed'))
|
||||
return
|
||||
}
|
||||
message.success(t(successKey))
|
||||
} catch (err: any) {
|
||||
applyErrorStatus(err)
|
||||
message.error(err?.message || t('githubPreview.actionFailed'))
|
||||
} finally {
|
||||
actionLoading.value = ''
|
||||
}
|
||||
}
|
||||
|
||||
function requireTag(): string | null {
|
||||
if (!selectedTag.value) {
|
||||
message.warning(t('githubPreview.selectTag'))
|
||||
return null
|
||||
}
|
||||
return selectedTag.value
|
||||
}
|
||||
|
||||
async function handlePrepare() {
|
||||
const tag = requireTag()
|
||||
if (!tag) return
|
||||
await runAction('prepare', () => preparePreview(tag), 'githubPreview.prepareSuccess')
|
||||
}
|
||||
|
||||
async function handleInstall() {
|
||||
await runAction('install', async () => {
|
||||
const res = await installPreview()
|
||||
if (res.success !== false && !res.installed) {
|
||||
return {
|
||||
...res,
|
||||
success: false,
|
||||
message: res.message || t('githubPreview.actionFailed'),
|
||||
}
|
||||
}
|
||||
return res
|
||||
}, 'githubPreview.installSuccess')
|
||||
}
|
||||
|
||||
async function handleStart() {
|
||||
await runAction('start', () => startPreview(selectedTag.value || undefined), 'githubPreview.startSuccess')
|
||||
}
|
||||
|
||||
async function handleStop() {
|
||||
await runAction('stop', stopPreview, 'githubPreview.stopSuccess')
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await handleRefresh()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="github-preview-settings">
|
||||
<div class="settings-section">
|
||||
<div class="control-row">
|
||||
<NSelect
|
||||
v-model:value="selectedTag"
|
||||
class="tag-select"
|
||||
filterable
|
||||
:loading="tagsLoading"
|
||||
:options="tagOptions"
|
||||
:placeholder="t('githubPreview.selectTag')"
|
||||
/>
|
||||
<NSpace>
|
||||
<NButton type="primary" :loading="actionLoading === 'prepare'" :disabled="!selectedTag" @click="handlePrepare">
|
||||
{{ t('githubPreview.prepare') }}
|
||||
</NButton>
|
||||
<NButton :loading="actionLoading === 'install'" :disabled="!status?.has_package" @click="handleInstall">
|
||||
{{ t('githubPreview.install') }}
|
||||
</NButton>
|
||||
<NButton type="success" :loading="actionLoading === 'start'" :disabled="!status?.installed" @click="handleStart">
|
||||
{{ t('githubPreview.start') }}
|
||||
</NButton>
|
||||
<NButton :loading="actionLoading === 'stop'" :disabled="!status?.running" @click="handleStop">
|
||||
{{ t('githubPreview.stop') }}
|
||||
</NButton>
|
||||
<NButton :loading="loading || tagsLoading" @click="handleRefresh">
|
||||
{{ t('githubPreview.refresh') }}
|
||||
</NButton>
|
||||
</NSpace>
|
||||
</div>
|
||||
|
||||
<p class="section-description">{{ t('githubPreview.description') }}</p>
|
||||
|
||||
<NAlert type="info" :bordered="false" class="preview-note">
|
||||
{{ t('githubPreview.note') }}
|
||||
</NAlert>
|
||||
|
||||
<NDescriptions v-if="status" :column="1" bordered size="small" class="status-table">
|
||||
<NDescriptionsItem :label="t('githubPreview.path')">
|
||||
<code>{{ status.preview_dir }}</code>
|
||||
</NDescriptionsItem>
|
||||
<NDescriptionsItem :label="t('githubPreview.webuiHome')">
|
||||
<code>{{ status.webui_home }}</code>
|
||||
</NDescriptionsItem>
|
||||
<NDescriptionsItem :label="t('githubPreview.currentTag')">
|
||||
{{ status.current_tag || '-' }}
|
||||
</NDescriptionsItem>
|
||||
<NDescriptionsItem :label="t('githubPreview.repoReady')">
|
||||
<NTag size="small" :type="status.has_package ? 'success' : 'default'">
|
||||
{{ status.has_package ? t('githubPreview.yes') : t('githubPreview.no') }}
|
||||
</NTag>
|
||||
</NDescriptionsItem>
|
||||
<NDescriptionsItem :label="t('githubPreview.dependencies')">
|
||||
<NTag size="small" :type="status.installed ? 'success' : 'warning'">
|
||||
{{ status.installed ? t('githubPreview.yes') : t('githubPreview.no') }}
|
||||
</NTag>
|
||||
</NDescriptionsItem>
|
||||
<NDescriptionsItem :label="t('githubPreview.running')">
|
||||
<NTag size="small" :type="status.running ? 'success' : 'default'">
|
||||
{{ status.running ? `PID ${status.pid}` : t('githubPreview.notRunning') }}
|
||||
</NTag>
|
||||
</NDescriptionsItem>
|
||||
<NDescriptionsItem :label="t('githubPreview.open')">
|
||||
<a :href="status.frontend_url" target="_blank" rel="noopener noreferrer">{{ status.frontend_url }}</a>
|
||||
</NDescriptionsItem>
|
||||
<NDescriptionsItem :label="t('githubPreview.log')">
|
||||
<code>{{ status.action_log_path }}</code>
|
||||
</NDescriptionsItem>
|
||||
<NDescriptionsItem :label="t('githubPreview.devLog')">
|
||||
<code>{{ status.dev_log_path }}</code>
|
||||
</NDescriptionsItem>
|
||||
</NDescriptions>
|
||||
|
||||
<div class="log-output">
|
||||
<div class="log-output-header">{{ t('githubPreview.logOutput') }}</div>
|
||||
<div class="log-box">
|
||||
<div class="log-title">{{ t('githubPreview.actionLog') }}</div>
|
||||
<pre>{{ actionLog || '-' }}</pre>
|
||||
</div>
|
||||
<div class="log-box">
|
||||
<div class="log-title">{{ t('githubPreview.devLog') }}</div>
|
||||
<pre>{{ devLog || '-' }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@use "@/styles/variables" as *;
|
||||
|
||||
.github-preview-settings {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.settings-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.section-description {
|
||||
margin: 0;
|
||||
color: $text-secondary;
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.control-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.tag-select {
|
||||
width: 260px;
|
||||
}
|
||||
|
||||
.preview-note {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.status-table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.log-output {
|
||||
width: 100%;
|
||||
border: 1px solid $border-color;
|
||||
border-radius: $radius-md;
|
||||
background: $bg-card;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.log-output-header {
|
||||
padding: 12px 14px;
|
||||
border-bottom: 1px solid $border-color;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: $text-primary;
|
||||
}
|
||||
|
||||
.log-box {
|
||||
border-bottom: 1px solid $border-color;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
.log-title {
|
||||
padding: 8px 14px;
|
||||
font-size: 12px;
|
||||
color: $text-secondary;
|
||||
background: $bg-secondary;
|
||||
}
|
||||
|
||||
pre {
|
||||
min-height: 180px;
|
||||
max-height: 320px;
|
||||
margin: 0;
|
||||
padding: 12px 14px;
|
||||
overflow: auto;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
code {
|
||||
font-size: 12px;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
@media (max-width: 1100px) {
|
||||
.control-row {
|
||||
align-items: stretch;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -27,6 +27,7 @@ const selectedKey = computed(() => {
|
||||
return route.name as string;
|
||||
});
|
||||
const isSuperAdmin = computed(() => isStoredSuperAdmin());
|
||||
const isVersionPreview = import.meta.env.VITE_HERMES_PREVIEW === '1';
|
||||
|
||||
function isNavActive(...names: string[]) {
|
||||
return names.includes(selectedKey.value);
|
||||
@@ -35,7 +36,7 @@ const logoPath = '/logo.png';
|
||||
|
||||
const { record: collapsedGroups, persist: persistCollapsedGroups } = usePersistentRecord('hermes.sidebar.collapsedGroups');
|
||||
|
||||
type SidebarGroupKey = "Conversation" | "Agent" | "Monitoring" | "System";
|
||||
type SidebarGroupKey = "Conversation" | "Agent" | "Monitoring" | "Tools" | "System";
|
||||
|
||||
function groupLabel(key: SidebarGroupKey) {
|
||||
return t(`sidebar.group${key}${appStore.sidebarCollapsed ? "Short" : ""}`);
|
||||
@@ -253,6 +254,29 @@ function openChangelog() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tools -->
|
||||
<div class="nav-group">
|
||||
<div class="nav-group-label" @click="toggleGroup('tools')">
|
||||
<span>{{ groupLabel("Tools") }}</span>
|
||||
<svg class="nav-group-arrow" :class="{ collapsed: isGroupCollapsed('tools') }" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<polyline points="6 9 12 15 18 9" />
|
||||
</svg>
|
||||
</div>
|
||||
<div v-show="!isGroupCollapsed('tools')" class="nav-group-items">
|
||||
<RouteLinkItem v-if="isSuperAdmin && !isVersionPreview" class="nav-item" :to="{ name: 'hermes.versionPreview' }" :active="selectedKey === 'hermes.versionPreview'">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z" />
|
||||
<polyline points="7.5 4.21 12 6.81 16.5 4.21" />
|
||||
<polyline points="7.5 19.79 7.5 14.6 3 12" />
|
||||
<polyline points="21 12 16.5 14.6 16.5 19.79" />
|
||||
<polyline points="3.27 6.96 12 12.01 20.73 6.96" />
|
||||
<line x1="12" y1="22.08" x2="12" y2="12" />
|
||||
</svg>
|
||||
<span>{{ t("sidebar.versionPreview") }}</span>
|
||||
</RouteLinkItem>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- System -->
|
||||
<div class="nav-group">
|
||||
<div class="nav-group-label" @click="toggleGroup('system')">
|
||||
|
||||
@@ -148,6 +148,8 @@ export default {
|
||||
noChangelog: 'Kein Anderungsprotokoll verfugbar',
|
||||
kanban: 'Kanban',
|
||||
groupTools: 'Werkzeuge',
|
||||
groupToolsShort: "Tools",
|
||||
versionPreview: "Versionsvorschau",
|
||||
groupPlatform: 'Plattform',
|
||||
gateways: 'Gateways',
|
||||
expand: 'Menü ausklappen',
|
||||
@@ -930,6 +932,36 @@ jobTriggered: 'Job ausgelost',
|
||||
saved: 'Gespeichert',
|
||||
},
|
||||
},
|
||||
githubPreview: {
|
||||
title: "Versionsvorschau",
|
||||
description: "Klont den ausgewählten GitHub-Tag in den Web-UI-Vorschaubereich, installiert Abhängigkeiten und startet ihn mit den Entwicklungsports.",
|
||||
refresh: "Aktualisieren",
|
||||
selectTag: "Tag auswählen",
|
||||
prepare: "Code vorbereiten",
|
||||
install: "Abhängigkeiten installieren",
|
||||
start: "Vorschau starten",
|
||||
stop: "Stoppen",
|
||||
note: "Der Vorschaucode wird im Web-UI-Datenverzeichnis gespeichert. Produktion bleibt auf Port 8648; die Vorschau nutzt Frontend 8651 und Backend 8650.",
|
||||
path: "Vorschaupfad",
|
||||
webuiHome: "Vorschau-Datenverzeichnis",
|
||||
currentTag: "Aktueller Tag",
|
||||
repoReady: "Repository bereit",
|
||||
dependencies: "Abhängigkeiten installiert",
|
||||
running: "Status",
|
||||
notRunning: "Nicht gestartet",
|
||||
open: "Vorschau öffnen",
|
||||
log: "Pfad zum Aktionslog",
|
||||
logOutput: "Logausgabe",
|
||||
actionLog: "Aktionslog",
|
||||
devLog: "Dev-Server-Log",
|
||||
yes: "Ja",
|
||||
no: "Nein",
|
||||
actionFailed: "Aktion fehlgeschlagen",
|
||||
prepareSuccess: "Vorschaucode ist bereit",
|
||||
installSuccess: "Abhängigkeiten installiert",
|
||||
startSuccess: "Vorschau gestartet",
|
||||
stopSuccess: "Vorschau gestoppt",
|
||||
},
|
||||
|
||||
// Platform channel settings
|
||||
platform: {
|
||||
|
||||
@@ -137,6 +137,8 @@ export default {
|
||||
groupMonitoring: 'Monitoring',
|
||||
groupMonitoringShort: 'Mon',
|
||||
groupTools: 'Tools',
|
||||
groupToolsShort: "Tools",
|
||||
versionPreview: "Version Preview",
|
||||
settings: 'Settings',
|
||||
connected: 'Connected',
|
||||
disconnected: 'Disconnected',
|
||||
@@ -1032,6 +1034,36 @@ export default {
|
||||
mimoStylePromptPlaceholder: 'e.g., Bright and bouncy tone, fast pace',
|
||||
},
|
||||
},
|
||||
githubPreview: {
|
||||
title: "Version Preview",
|
||||
description: "Clone a selected GitHub tag into the Web UI preview workspace, install dependencies, and run it with the development ports.",
|
||||
refresh: "Refresh",
|
||||
selectTag: "Select a tag",
|
||||
prepare: "Prepare Code",
|
||||
install: "Install Dependencies",
|
||||
start: "Start Preview",
|
||||
stop: "Stop",
|
||||
note: "Preview code is stored under the Web UI data home. Production remains on port 8648; preview development runs on frontend 8651 and backend 8650.",
|
||||
path: "Preview Path",
|
||||
webuiHome: "Preview Data Home",
|
||||
currentTag: "Current Tag",
|
||||
repoReady: "Repository Ready",
|
||||
dependencies: "Dependencies Installed",
|
||||
running: "Running",
|
||||
notRunning: "Not running",
|
||||
open: "Open Preview",
|
||||
log: "Action Log Path",
|
||||
logOutput: "Log Output",
|
||||
actionLog: "Action Log",
|
||||
devLog: "Dev Server Log",
|
||||
yes: "Yes",
|
||||
no: "No",
|
||||
actionFailed: "Action failed",
|
||||
prepareSuccess: "Preview code is ready",
|
||||
installSuccess: "Dependencies installed",
|
||||
startSuccess: "Preview started",
|
||||
stopSuccess: "Preview stopped",
|
||||
},
|
||||
|
||||
// Platform channel settings
|
||||
platform: {
|
||||
|
||||
@@ -148,6 +148,8 @@ export default {
|
||||
noChangelog: 'No hay registro de cambios',
|
||||
kanban: 'Kanban',
|
||||
groupTools: 'Herramientas',
|
||||
groupToolsShort: "Herr.",
|
||||
versionPreview: "Vista previa de versión",
|
||||
groupPlatform: 'Plataforma',
|
||||
gateways: 'Puertas de enlace',
|
||||
expand: 'Expandir menú',
|
||||
@@ -930,6 +932,36 @@ jobTriggered: 'Job ejecutado',
|
||||
saved: 'Guardado',
|
||||
},
|
||||
},
|
||||
githubPreview: {
|
||||
title: "Vista previa de versión",
|
||||
description: "Clona el tag de GitHub seleccionado en el espacio de vista previa de Web UI, instala dependencias y lo ejecuta con los puertos de desarrollo.",
|
||||
refresh: "Actualizar",
|
||||
selectTag: "Selecciona un tag",
|
||||
prepare: "Preparar código",
|
||||
install: "Instalar dependencias",
|
||||
start: "Iniciar vista previa",
|
||||
stop: "Detener",
|
||||
note: "El código de vista previa se guarda bajo el directorio de datos de Web UI. Producción sigue en el puerto 8648; la vista previa usa frontend 8651 y backend 8650.",
|
||||
path: "Ruta de vista previa",
|
||||
webuiHome: "Datos de vista previa",
|
||||
currentTag: "Tag actual",
|
||||
repoReady: "Repositorio listo",
|
||||
dependencies: "Dependencias instaladas",
|
||||
running: "Estado",
|
||||
notRunning: "No ejecutándose",
|
||||
open: "Abrir vista previa",
|
||||
log: "Ruta del log de acciones",
|
||||
logOutput: "Salida de logs",
|
||||
actionLog: "Log de acciones",
|
||||
devLog: "Log del servidor dev",
|
||||
yes: "Sí",
|
||||
no: "No",
|
||||
actionFailed: "Acción fallida",
|
||||
prepareSuccess: "Código de vista previa listo",
|
||||
installSuccess: "Dependencias instaladas",
|
||||
startSuccess: "Vista previa iniciada",
|
||||
stopSuccess: "Vista previa detenida",
|
||||
},
|
||||
|
||||
// Platform channel settings
|
||||
platform: {
|
||||
|
||||
@@ -148,6 +148,8 @@ export default {
|
||||
noChangelog: 'Aucun journal disponible',
|
||||
kanban: 'Kanban',
|
||||
groupTools: 'Outils',
|
||||
groupToolsShort: "Outils",
|
||||
versionPreview: "Aperçu de version",
|
||||
groupPlatform: 'Plateforme',
|
||||
gateways: 'Passerelles',
|
||||
expand: 'Déplier le menu',
|
||||
@@ -930,6 +932,36 @@ jobTriggered: 'Job declenche',
|
||||
saved: 'Enregistré',
|
||||
},
|
||||
},
|
||||
githubPreview: {
|
||||
title: "Aperçu de version",
|
||||
description: "Clone le tag GitHub sélectionné dans l’espace de prévisualisation Web UI, installe les dépendances, puis lance l’application sur les ports de développement.",
|
||||
refresh: "Actualiser",
|
||||
selectTag: "Sélectionner un tag",
|
||||
prepare: "Préparer le code",
|
||||
install: "Installer les dépendances",
|
||||
start: "Démarrer l’aperçu",
|
||||
stop: "Arrêter",
|
||||
note: "Le code de prévisualisation est stocké dans le dossier de données Web UI. La production reste sur le port 8648 ; la prévisualisation utilise le frontend 8651 et le backend 8650.",
|
||||
path: "Chemin de prévisualisation",
|
||||
webuiHome: "Données de prévisualisation",
|
||||
currentTag: "Tag actuel",
|
||||
repoReady: "Dépôt prêt",
|
||||
dependencies: "Dépendances installées",
|
||||
running: "État",
|
||||
notRunning: "Arrêté",
|
||||
open: "Ouvrir l’aperçu",
|
||||
log: "Chemin du journal d’action",
|
||||
logOutput: "Sortie des journaux",
|
||||
actionLog: "Journal d’action",
|
||||
devLog: "Journal du serveur dev",
|
||||
yes: "Oui",
|
||||
no: "Non",
|
||||
actionFailed: "Échec de l’action",
|
||||
prepareSuccess: "Code de prévisualisation prêt",
|
||||
installSuccess: "Dépendances installées",
|
||||
startSuccess: "Prévisualisation démarrée",
|
||||
stopSuccess: "Prévisualisation arrêtée",
|
||||
},
|
||||
|
||||
// Platform channel settings
|
||||
platform: {
|
||||
|
||||
@@ -148,6 +148,8 @@ export default {
|
||||
noChangelog: '更新履歴はありません',
|
||||
kanban: 'カンバン',
|
||||
groupTools: 'ツール',
|
||||
groupToolsShort: "ツール",
|
||||
versionPreview: "バージョンプレビュー",
|
||||
groupPlatform: 'プラットフォーム',
|
||||
gateways: 'ゲートウェイ',
|
||||
expand: 'メニューを展開',
|
||||
@@ -930,8 +932,37 @@ export default {
|
||||
saved: '保存しました',
|
||||
},
|
||||
},
|
||||
githubPreview: {
|
||||
title: "バージョンプレビュー",
|
||||
description: "選択した GitHub tag を Web UI のプレビュー作業ディレクトリへクローンし、依存関係をインストールして開発ポートで起動します。",
|
||||
refresh: "更新",
|
||||
selectTag: "tag を選択",
|
||||
prepare: "コードを準備",
|
||||
install: "依存関係をインストール",
|
||||
start: "プレビューを開始",
|
||||
stop: "停止",
|
||||
note: "プレビューコードは Web UI データホーム配下に保存されます。本番は 8648 のまま、プレビュー開発環境はフロントエンド 8651、バックエンド 8650 で実行されます。",
|
||||
path: "プレビューパス",
|
||||
webuiHome: "プレビューデータホーム",
|
||||
currentTag: "現在の Tag",
|
||||
repoReady: "リポジトリ準備済み",
|
||||
dependencies: "依存関係インストール済み",
|
||||
running: "実行状態",
|
||||
notRunning: "未実行",
|
||||
open: "プレビューを開く",
|
||||
log: "操作ログパス",
|
||||
logOutput: "ログ出力",
|
||||
actionLog: "操作ログ",
|
||||
devLog: "開発サーバーログ",
|
||||
yes: "はい",
|
||||
no: "いいえ",
|
||||
actionFailed: "操作に失敗しました",
|
||||
prepareSuccess: "プレビューコードの準備が完了しました",
|
||||
installSuccess: "依存関係をインストールしました",
|
||||
startSuccess: "プレビューを起動しました",
|
||||
stopSuccess: "プレビューを停止しました",
|
||||
},
|
||||
|
||||
// プラットフォームチャンネル設定
|
||||
platform: {
|
||||
requireMention: "メンションが必要",
|
||||
requireMentionGroup: "グループで応答するには {'@'}メンションが必要",
|
||||
|
||||
@@ -148,6 +148,8 @@ export default {
|
||||
noChangelog: '변경 이력이 없습니다',
|
||||
kanban: '칸반',
|
||||
groupTools: '도구',
|
||||
groupToolsShort: "도구",
|
||||
versionPreview: "버전 미리보기",
|
||||
groupPlatform: '플랫폼',
|
||||
gateways: '게이트웨이',
|
||||
expand: '메뉴 펼치기',
|
||||
@@ -930,8 +932,37 @@ export default {
|
||||
saved: '저장됨',
|
||||
},
|
||||
},
|
||||
githubPreview: {
|
||||
title: "버전 미리보기",
|
||||
description: "선택한 GitHub tag 를 Web UI 미리보기 작업 디렉터리에 클론하고, 의존성을 설치한 뒤 개발 포트로 실행합니다.",
|
||||
refresh: "새로고침",
|
||||
selectTag: "tag 선택",
|
||||
prepare: "코드 준비",
|
||||
install: "의존성 설치",
|
||||
start: "미리보기 시작",
|
||||
stop: "중지",
|
||||
note: "미리보기 코드는 Web UI 데이터 홈 아래에 저장됩니다. 프로덕션은 8648을 유지하고, 미리보기 개발 환경은 프론트엔드 8651, 백엔드 8650에서 실행됩니다.",
|
||||
path: "미리보기 경로",
|
||||
webuiHome: "미리보기 데이터 홈",
|
||||
currentTag: "현재 Tag",
|
||||
repoReady: "저장소 준비됨",
|
||||
dependencies: "의존성 설치됨",
|
||||
running: "실행 상태",
|
||||
notRunning: "실행 중 아님",
|
||||
open: "미리보기 열기",
|
||||
log: "작업 로그 경로",
|
||||
logOutput: "로그 출력",
|
||||
actionLog: "작업 로그",
|
||||
devLog: "개발 서버 로그",
|
||||
yes: "예",
|
||||
no: "아니요",
|
||||
actionFailed: "작업 실패",
|
||||
prepareSuccess: "미리보기 코드가 준비되었습니다",
|
||||
installSuccess: "의존성이 설치되었습니다",
|
||||
startSuccess: "미리보기가 시작되었습니다",
|
||||
stopSuccess: "미리보기가 중지되었습니다",
|
||||
},
|
||||
|
||||
// 플랫폼 채널 설정
|
||||
platform: {
|
||||
requireMention: "{'@'}멘션 필요",
|
||||
requireMentionGroup: "그룹에서 {'@'}멘션 시에만 응답",
|
||||
|
||||
@@ -148,6 +148,8 @@ export default {
|
||||
noChangelog: 'Nenhum registro disponivel',
|
||||
kanban: 'Kanban',
|
||||
groupTools: 'Ferramentas',
|
||||
groupToolsShort: "Ferr.",
|
||||
versionPreview: "Prévia de versão",
|
||||
groupPlatform: 'Plataforma',
|
||||
gateways: 'Gateways',
|
||||
expand: 'Expandir menu',
|
||||
@@ -930,6 +932,36 @@ jobTriggered: 'Job acionado',
|
||||
saved: 'Salvo',
|
||||
},
|
||||
},
|
||||
githubPreview: {
|
||||
title: "Prévia de versão",
|
||||
description: "Clona a tag do GitHub selecionada para o workspace de prévia do Web UI, instala dependências e executa com as portas de desenvolvimento.",
|
||||
refresh: "Atualizar",
|
||||
selectTag: "Selecione uma tag",
|
||||
prepare: "Preparar código",
|
||||
install: "Instalar dependências",
|
||||
start: "Iniciar prévia",
|
||||
stop: "Parar",
|
||||
note: "O código de prévia é armazenado no diretório de dados do Web UI. Produção permanece na porta 8648; a prévia usa frontend 8651 e backend 8650.",
|
||||
path: "Caminho da prévia",
|
||||
webuiHome: "Dados da prévia",
|
||||
currentTag: "Tag atual",
|
||||
repoReady: "Repositório pronto",
|
||||
dependencies: "Dependências instaladas",
|
||||
running: "Estado",
|
||||
notRunning: "Não em execução",
|
||||
open: "Abrir prévia",
|
||||
log: "Caminho do log de ações",
|
||||
logOutput: "Saída de logs",
|
||||
actionLog: "Log de ações",
|
||||
devLog: "Log do servidor dev",
|
||||
yes: "Sim",
|
||||
no: "Não",
|
||||
actionFailed: "Ação falhou",
|
||||
prepareSuccess: "Código de prévia pronto",
|
||||
installSuccess: "Dependências instaladas",
|
||||
startSuccess: "Prévia iniciada",
|
||||
stopSuccess: "Prévia parada",
|
||||
},
|
||||
|
||||
// Platform channel settings
|
||||
platform: {
|
||||
|
||||
@@ -137,6 +137,8 @@ export default {
|
||||
groupMonitoring: '監控',
|
||||
groupMonitoringShort: '監控',
|
||||
groupTools: '工具',
|
||||
groupToolsShort: "工具",
|
||||
versionPreview: "版本預覽",
|
||||
settings: '設定',
|
||||
connected: '已連線',
|
||||
disconnected: '未連線',
|
||||
@@ -1024,6 +1026,36 @@ export default {
|
||||
mimoStylePromptPlaceholder: '例如:用輕快上揚的語調,語速稍快',
|
||||
},
|
||||
},
|
||||
githubPreview: {
|
||||
title: "版本預覽",
|
||||
description: "將選取的 GitHub tag 複製到 Web UI 預覽工作目錄,安裝依賴並以開發連接埠執行。",
|
||||
refresh: "重新整理",
|
||||
selectTag: "選擇 tag",
|
||||
prepare: "準備程式碼",
|
||||
install: "安裝依賴",
|
||||
start: "開啟預覽",
|
||||
stop: "停止",
|
||||
note: "預覽程式碼存放在 Web UI 資料目錄下。正式環境仍使用 8648,預覽開發環境使用前端 8651、後端 8650。",
|
||||
path: "預覽路徑",
|
||||
webuiHome: "預覽資料目錄",
|
||||
currentTag: "目前 Tag",
|
||||
repoReady: "倉庫就緒",
|
||||
dependencies: "依賴已安裝",
|
||||
running: "執行狀態",
|
||||
notRunning: "未執行",
|
||||
open: "開啟預覽",
|
||||
log: "操作日誌路徑",
|
||||
logOutput: "日誌輸出",
|
||||
actionLog: "操作日誌",
|
||||
devLog: "開發服務日誌",
|
||||
yes: "是",
|
||||
no: "否",
|
||||
actionFailed: "操作失敗",
|
||||
prepareSuccess: "預覽程式碼已準備好",
|
||||
installSuccess: "依賴安裝完成",
|
||||
startSuccess: "預覽已啟動",
|
||||
stopSuccess: "預覽已停止",
|
||||
},
|
||||
|
||||
// 平台頻道設定
|
||||
platform: {
|
||||
|
||||
@@ -137,6 +137,8 @@ export default {
|
||||
groupMonitoring: '监控',
|
||||
groupMonitoringShort: '监控',
|
||||
groupTools: '工具',
|
||||
groupToolsShort: "工具",
|
||||
versionPreview: "版本预览",
|
||||
settings: '设置',
|
||||
connected: '已连接',
|
||||
disconnected: '未连接',
|
||||
@@ -1024,6 +1026,36 @@ export default {
|
||||
mimoStylePromptPlaceholder: '例如:用轻快上扬的语调,语速稍快',
|
||||
},
|
||||
},
|
||||
githubPreview: {
|
||||
title: "版本预览",
|
||||
description: "将选中的 GitHub tag 克隆到 Web UI 预览工作目录,安装依赖并以开发端口运行。",
|
||||
refresh: "刷新",
|
||||
selectTag: "选择 tag",
|
||||
prepare: "准备代码",
|
||||
install: "安装依赖",
|
||||
start: "开启预览",
|
||||
stop: "停止",
|
||||
note: "预览代码存放在 Web UI 数据目录下。正式环境仍使用 8648,预览开发环境使用前端 8651、后端 8650。",
|
||||
path: "预览路径",
|
||||
webuiHome: "预览数据目录",
|
||||
currentTag: "当前 Tag",
|
||||
repoReady: "仓库就绪",
|
||||
dependencies: "依赖已安装",
|
||||
running: "运行状态",
|
||||
notRunning: "未运行",
|
||||
open: "打开预览",
|
||||
log: "操作日志路径",
|
||||
logOutput: "日志输出",
|
||||
actionLog: "操作日志",
|
||||
devLog: "开发服务日志",
|
||||
yes: "是",
|
||||
no: "否",
|
||||
actionFailed: "操作失败",
|
||||
prepareSuccess: "预览代码已准备好",
|
||||
installSuccess: "依赖安装完成",
|
||||
startSuccess: "预览已启动",
|
||||
stopSuccess: "预览已停止",
|
||||
},
|
||||
|
||||
// 平台频道设置
|
||||
platform: {
|
||||
|
||||
@@ -117,6 +117,12 @@ const router = createRouter({
|
||||
name: 'hermes.files',
|
||||
component: () => import('@/views/hermes/FilesView.vue'),
|
||||
},
|
||||
{
|
||||
path: '/hermes/version-preview',
|
||||
name: 'hermes.versionPreview',
|
||||
component: () => import('@/views/hermes/VersionPreviewView.vue'),
|
||||
meta: { requiresSuperAdmin: true },
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
|
||||
@@ -290,9 +290,9 @@ function buildWsUrl(): string {
|
||||
return `${wsProtocol}//${new URL(base).host}/api/hermes/terminal${token ? `?token=${encodeURIComponent(token)}` : ""}`;
|
||||
}
|
||||
|
||||
// Dev mode: connect directly to backend port; Production: same host
|
||||
const host = import.meta.env.DEV
|
||||
? formatHostForPort(location.hostname, 8648)
|
||||
const directDevPort = import.meta.env.VITE_HERMES_DIRECT_WS_PORT;
|
||||
const host = import.meta.env.DEV && directDevPort
|
||||
? formatHostForPort(location.hostname, Number(directDevPort))
|
||||
: location.host;
|
||||
return `${wsProtocol}//${host}/api/hermes/terminal${token ? `?token=${encodeURIComponent(token)}` : ""}`;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
<script setup lang="ts">
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import GithubPreviewSettings from '@/components/hermes/settings/GithubPreviewSettings.vue'
|
||||
|
||||
const { t } = useI18n()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="version-preview-view">
|
||||
<header class="page-header">
|
||||
<h2 class="header-title">{{ t('githubPreview.title') }}</h2>
|
||||
</header>
|
||||
|
||||
<div class="page-content">
|
||||
<GithubPreviewSettings />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@use "@/styles/variables" as *;
|
||||
|
||||
.version-preview-view {
|
||||
height: calc(100 * var(--vh));
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.page-content {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 20px;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user