b866e944ab
Apply sync/install/auth/schedule/retry/agent/settings fixes from full code review; document accepted WS and Agent legacy risks for solo ops. Co-authored-by: Cursor <cursoragent@cursor.com>
169 lines
5.2 KiB
Vue
169 lines
5.2 KiB
Vue
<template>
|
|
<v-container fluid class="pa-6">
|
|
<v-card elevation="0" border rounded="lg">
|
|
<v-card-title class="d-flex align-center">
|
|
命令日志
|
|
<v-spacer />
|
|
<v-select
|
|
v-model="viewMode"
|
|
:items="[{ title: '命令视图', value: 'commands' }, { title: '会话视图', value: 'sessions' }]"
|
|
item-title="title"
|
|
item-value="value"
|
|
variant="outlined"
|
|
density="compact"
|
|
hide-details
|
|
style="max-width: 160px"
|
|
@update:model-value="loadData"
|
|
/>
|
|
<v-select
|
|
v-model="serverFilter"
|
|
:items="serverList"
|
|
item-title="name"
|
|
item-value="id"
|
|
label="服务器"
|
|
variant="outlined"
|
|
density="compact"
|
|
hide-details
|
|
style="max-width: 180px"
|
|
class="ml-3"
|
|
clearable
|
|
@update:model-value="loadData"
|
|
/>
|
|
</v-card-title>
|
|
|
|
<v-divider />
|
|
|
|
<!-- Command View -->
|
|
<v-data-table-server
|
|
v-if="viewMode === 'commands'"
|
|
:items="commands"
|
|
:headers="commandHeaders"
|
|
:items-length="total"
|
|
:loading="loading"
|
|
:page="page"
|
|
:items-per-page="30"
|
|
hover
|
|
density="compact"
|
|
@update:page="page = $event; loadData()"
|
|
>
|
|
<template #item.command="{ item }">
|
|
<code class="text-body-2">{{ item.command }}</code>
|
|
</template>
|
|
<template #item.server_name="{ item }">
|
|
<span class="text-medium-emphasis">{{ item.server_name || '—' }}</span>
|
|
</template>
|
|
<template #item.admin_username="{ item }">
|
|
<span class="text-medium-emphasis">{{ item.admin_username || '—' }}</span>
|
|
</template>
|
|
<template #item.created_at="{ item }">
|
|
<span class="text-caption text-medium-emphasis">{{ item.created_at }}</span>
|
|
</template>
|
|
<template #no-data>
|
|
<div class="text-center text-medium-emphasis py-6">暂无数据</div>
|
|
</template>
|
|
</v-data-table-server>
|
|
|
|
<!-- Session View -->
|
|
<v-data-table-server
|
|
v-else
|
|
:items="sessions"
|
|
:headers="sessionHeaders"
|
|
:items-length="total"
|
|
:loading="loading"
|
|
:page="page"
|
|
:items-per-page="30"
|
|
hover
|
|
density="compact"
|
|
@update:page="page = $event; loadData()"
|
|
>
|
|
<template #item.server_name="{ item }">
|
|
<span class="font-weight-medium">{{ item.server_name || '—' }}</span>
|
|
</template>
|
|
<template #item.status="{ item }">
|
|
<v-chip :color="item.status === 'closed' ? 'grey' : 'success'" size="small" variant="tonal" label>
|
|
{{ item.status }}
|
|
</v-chip>
|
|
</template>
|
|
<template #item.started_at="{ item }">
|
|
<span class="text-caption text-medium-emphasis">{{ item.started_at }}</span>
|
|
</template>
|
|
<template #no-data>
|
|
<div class="text-center text-medium-emphasis py-6">暂无数据</div>
|
|
</template>
|
|
</v-data-table-server>
|
|
</v-card>
|
|
</v-container>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, onMounted } from 'vue'
|
|
import { http } from '@/api'
|
|
import { useServerList } from '@/composables/useServerList'
|
|
import { useSnackbar } from '@/composables/useSnackbar'
|
|
import { formatApiError } from '@/utils/apiError'
|
|
import type { CommandLogItem, SshSessionItem } from '@/types/api'
|
|
|
|
const snackbar = useSnackbar()
|
|
const { servers: serverList, loadServers } = useServerList()
|
|
|
|
// ── State ──
|
|
const loading = ref(false)
|
|
const viewMode = ref<'commands' | 'sessions'>('commands')
|
|
const page = ref(1)
|
|
const total = ref(0)
|
|
const itemsPerPage = ref(30)
|
|
const serverFilter = ref<number | null>(null)
|
|
|
|
const commands = ref<CommandLogItem[]>([])
|
|
const sessions = ref<SshSessionItem[]>([])
|
|
|
|
const commandHeaders = [
|
|
{ title: '命令', key: 'command' },
|
|
{ title: '服务器', key: 'server_name' },
|
|
{ title: '用户', key: 'admin_username', width: 100 },
|
|
{ title: '时间', key: 'created_at', width: 160 },
|
|
]
|
|
|
|
const sessionHeaders = [
|
|
{ title: '服务器', key: 'server_name' },
|
|
{ title: '状态', key: 'status', width: 80 },
|
|
{ title: '来源', key: 'remote_addr', width: 120 },
|
|
{ title: '开始时间', key: 'started_at', width: 160 },
|
|
]
|
|
|
|
// ── Data loading ──
|
|
async function loadData() {
|
|
loading.value = true
|
|
try {
|
|
if (viewMode.value === 'commands') {
|
|
const res = await http.getList<CommandLogItem>('/assets/command-logs', {
|
|
server_id: serverFilter.value || undefined,
|
|
page: page.value,
|
|
per_page: itemsPerPage.value,
|
|
})
|
|
commands.value = res.items
|
|
total.value = res.total
|
|
} else {
|
|
const res = await http.getList<SshSessionItem>('/assets/ssh-sessions', {
|
|
server_id: serverFilter.value || undefined,
|
|
page: page.value,
|
|
per_page: itemsPerPage.value,
|
|
})
|
|
sessions.value = res.items
|
|
total.value = res.total
|
|
}
|
|
} catch (e: unknown) {
|
|
commands.value = []
|
|
sessions.value = []
|
|
snackbar(formatApiError(e, '加载命令日志失败'), 'error')
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
onMounted(() => {
|
|
loadServers()
|
|
loadData()
|
|
})
|
|
</script>
|