Initial commit: finish basic WebUI interface

This commit is contained in:
2026-03-09 21:17:22 +08:00
parent d2a2a4de36
commit 2f6cfe7704
23 changed files with 5028 additions and 1 deletions

View File

@@ -0,0 +1,342 @@
<template>
<div class="instance-detail-page">
<div class="page-header">
<button class="back-btn" @click="goBack">
Back
</button>
<h2>{{ instanceName }} - Details</h2>
</div>
<div class="detail-content">
<div class="section">
<h3>Configuration</h3>
<div class="config-list">
<div class="config-item" v-for="(value, key) in instanceConfig" :key="key">
<span class="config-key">{{ key }}:</span>
<span class="config-value">{{ value }}</span>
</div>
</div>
</div>
<div class="section">
<h3>Proxy List</h3>
<div v-if="proxies.length > 0" class="proxy-list">
<div v-for="proxy in proxies" :key="proxy.name" class="proxy-item">
<div class="proxy-header">
<span class="proxy-name">{{ proxy.name }}</span>
<span :class="['proxy-status', proxy.status]">
{{ proxy.status }}
</span>
</div>
<div class="proxy-details">
<div class="proxy-detail" v-for="(value, key) in proxy.details" :key="key">
<span class="detail-key">{{ key }}:</span>
<span class="detail-value">{{ value }}</span>
</div>
</div>
</div>
</div>
<div v-else class="empty-state">
<p>No proxies available</p>
</div>
</div>
<div class="section">
<h3>Logs</h3>
<div class="logs-container">
<div v-if="logs.length > 0" class="log-list">
<div v-for="(log, index) in logs" :key="index" class="log-item">
<span class="log-time">{{ log.time }}</span>
<span :class="['log-level', log.level]">{{ log.level }}</span>
<span class="log-message">{{ log.message }}</span>
</div>
</div>
<div v-else class="empty-state">
<p>No logs available</p>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import { ref, onMounted } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { instanceApi } from '../api/index.js';
import { showNotification, formatDate } from '../utils/functions.js';
export default {
name: 'InstanceDetail',
setup() {
const route = useRoute();
const router = useRouter();
const instanceName = ref(route.params.name);
const instanceConfig = ref({});
const proxies = ref([]);
const logs = ref([]);
const loadInstanceConfig = async () => {
try {
const result = await instanceApi.listInstances();
const instance = result.data.find(i => i.name === instanceName.value);
if (instance) {
instanceConfig.value = {
'Server Address': instance.serverAddr,
'Server Port': instance.serverPort,
'Auth Method': instance.auth_method,
'Boot At Start': instance.bootAtStart ? 'Yes' : 'No',
'Run User': instance.runUser,
'Config Path': instance.configPath,
'Created At': formatDate(instance.createdAt)
};
}
} catch (error) {
showNotification('Load instance configuration failed', 'error');
}
};
const loadProxies = async () => {
try {
const result = await instanceApi.getInstanceProxies(instanceName.value);
proxies.value = result.data.map(proxy => ({
name: proxy.name,
status: proxy.status || 'unknown',
details: proxy
}));
} catch (error) {
showNotification('Load proxy list failed', 'error');
}
};
const loadLogs = async () => {
try {
const result = await instanceApi.getInstanceLogs(instanceName.value);
logs.value = result.data.map(log => ({
time: formatDate(log.timestamp),
level: log.level,
message: log.message
}));
} catch (error) {
showNotification('Load logs failed', 'error');
}
};
const goBack = () => {
router.push('/instances');
};
onMounted(() => {
loadInstanceConfig();
loadProxies();
loadLogs();
});
return {
instanceName,
instanceConfig,
proxies,
logs,
goBack
};
}
};
</script>
<style scoped>
.instance-detail-page {
padding: 24px;
}
.page-header {
display: flex;
align-items: center;
gap: 16px;
margin-bottom: 24px;
}
.back-btn {
padding: 8px 16px;
background: #f0f0f0;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
transition: all 0.3s;
}
.back-btn:hover {
background: #e0e0e0;
}
.page-header h2 {
font-size: 24px;
color: var(--text-color);
margin: 0;
transition: color 0.3s;
}
.detail-content {
display: flex;
flex-direction: column;
gap: 24px;
}
.section {
background: var(--card-bg);
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
transition: background-color 0.3s;
}
.section h3 {
font-size: 18px;
color: var(--text-color);
margin: 0 0 16px 0;
transition: color 0.3s;
}
.config-list {
display: flex;
flex-direction: column;
gap: 12px;
}
.config-item {
display: flex;
justify-content: space-between;
padding: 12px;
background: #f9f9f9;
border-radius: 6px;
}
.config-key {
color: #666;
font-weight: 500;
}
.config-value {
color: var(--text-color);
transition: color 0.3s;
}
.proxy-list {
display: flex;
flex-direction: column;
gap: 12px;
}
.proxy-item {
padding: 16px;
background: #f9f9f9;
border-radius: 6px;
}
.proxy-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
}
.proxy-name {
font-size: 16px;
font-weight: 600;
color: var(--text-color);
transition: color 0.3s;
}
.proxy-status {
padding: 4px 12px;
border-radius: 12px;
font-size: 12px;
font-weight: 500;
}
.proxy-status.active {
background: #f6ffed;
color: #52c41a;
}
.proxy-status.inactive {
background: #fff1f0;
color: #ff4d4f;
}
.proxy-details {
display: flex;
flex-direction: column;
gap: 8px;
}
.proxy-detail {
display: flex;
justify-content: space-between;
font-size: 14px;
}
.detail-key {
color: #666;
}
.detail-value {
color: var(--text-color);
transition: color 0.3s;
}
.logs-container {
max-height: 400px;
overflow-y: auto;
}
.log-list {
display: flex;
flex-direction: column;
gap: 8px;
}
.log-item {
display: flex;
gap: 12px;
padding: 8px;
background: #f9f9f9;
border-radius: 4px;
font-size: 14px;
}
.log-time {
color: #999;
min-width: 140px;
}
.log-level {
min-width: 60px;
font-weight: 500;
}
.log-level.INFO {
color: #1890ff;
}
.log-level.WARN {
color: #faad14;
}
.log-level.ERROR {
color: #ff4d4f;
}
.log-message {
color: var(--text-color);
flex: 1;
transition: color 0.3s;
}
.empty-state {
text-align: center;
padding: 40px 20px;
color: #999;
font-size: 14px;
}
</style>