Initial commit: finish basic WebUI interface
This commit is contained in:
342
src/views/InstanceDetail.vue
Normal file
342
src/views/InstanceDetail.vue
Normal 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>
|
||||
Reference in New Issue
Block a user