refactor(instance): migrate from name-based to ID-based instance management
feat(instanceDetail): add instance status display and restart && stop/start control - Change instance identification from name to ID in API endpoints and routes - Add instance status management functionality with start/stop/restart - Implement user type checks for instance operations - Update UI to reflect instance status changes - Remove completed TODO notes for implemented APIs
This commit is contained in:
@@ -4,10 +4,53 @@
|
||||
<button class="common-btn" @click="goBack">
|
||||
<i class="fas fa-arrow-left"></i> Back
|
||||
</button>
|
||||
<h2>{{ instanceName }} - Details</h2>
|
||||
<h2>{{ instanceID }} - Details</h2>
|
||||
</div>
|
||||
|
||||
<div class="detail-content">
|
||||
<div class="section">
|
||||
<h3>Instance Status
|
||||
<div class="action-buttons">
|
||||
<button
|
||||
v-if="userType !== 'visitor' && instanceStatus === 'running'"
|
||||
class="common-btn restart-btn"
|
||||
@click="restartInstance"
|
||||
>
|
||||
<i class="fas fa-redo"></i> Restart
|
||||
</button>
|
||||
<button
|
||||
v-if="userType !== 'visitor'"
|
||||
class="common-btn status-btn"
|
||||
:class="instanceStatus === 'running' ? 'stop-btn' : 'start-btn'"
|
||||
@click="handleStatusClick"
|
||||
>
|
||||
<i :class="['fas', instanceStatus === 'running' ? 'fa-stop' : 'fa-play']"></i>
|
||||
{{ instanceStatus === 'running' ? 'Stop' : 'Start' }}
|
||||
</button>
|
||||
</div>
|
||||
</h3>
|
||||
<div class="status-list">
|
||||
<div class="status-item">
|
||||
<span class="status-key">Status:</span>
|
||||
<span :class="['status-value', instanceStatus]">
|
||||
{{ instanceStatus === 'running' ? 'Running' : 'Stopped' }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="status-item" v-if="instanceStatus === 'running' && statusInfo.pid">
|
||||
<span class="status-key">PID:</span>
|
||||
<span class="status-value">{{ statusInfo.pid }}</span>
|
||||
</div>
|
||||
<div class="status-item" v-if="statusInfo.initSystem">
|
||||
<span class="status-key">Init System:</span>
|
||||
<span class="status-value">{{ statusInfo.initSystem }}</span>
|
||||
</div>
|
||||
<div class="status-item" v-if="statusInfo.serviceName">
|
||||
<span class="status-key">Service Name:</span>
|
||||
<span class="status-value">{{ statusInfo.serviceName }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h3>Configuration
|
||||
<button class="common-btn" @click="editConfig">
|
||||
@@ -233,21 +276,23 @@
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { instanceApi } from '../api/index.js';
|
||||
import { showNotification, formatDate } from '../utils/functions.js';
|
||||
import { showNotification, formatDate, getCookie } from '../utils/functions.js';
|
||||
|
||||
export default {
|
||||
name: 'InstanceDetail',
|
||||
setup() {
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const instanceName = ref(route.params.name);
|
||||
const instanceID = ref('');
|
||||
const instanceID = ref(route.params.id);
|
||||
const instanceConfig = ref({});
|
||||
const proxies = ref([]);
|
||||
const logs = ref([]);
|
||||
const instanceStatus = ref('stopped');
|
||||
const statusInfo = ref({});
|
||||
const showEditConfigModal = ref(false);
|
||||
const showAddProxyModal = ref(false);
|
||||
const loading = ref(false);
|
||||
const userType = ref(getCookie('user-type') || 'visitor');
|
||||
const formData = ref({
|
||||
name: '',
|
||||
auth_method: 'token',
|
||||
@@ -265,9 +310,8 @@ export default {
|
||||
const loadInstanceConfig = async () => {
|
||||
try {
|
||||
const result = await instanceApi.listInstances();
|
||||
const instance = result.data.find(i => i.name === instanceName.value);
|
||||
const instance = result.data.find(i => String(i.instanceID) === instanceID.value);
|
||||
if (instance) {
|
||||
instanceID.value = String(instance.instanceID);
|
||||
instanceConfig.value = {
|
||||
'Server Address': instance.serverAddr,
|
||||
'Server Port': instance.serverPort,
|
||||
@@ -283,6 +327,54 @@ export default {
|
||||
}
|
||||
};
|
||||
|
||||
const loadInstanceStatus = async () => {
|
||||
try {
|
||||
const result = await instanceApi.getInstanceStatus(instanceID.value);
|
||||
instanceStatus.value = result.data.status;
|
||||
statusInfo.value = result.data;
|
||||
} catch (error) {
|
||||
showNotification('Load instance status failed', 'error');
|
||||
}
|
||||
};
|
||||
|
||||
const startInstance = async () => {
|
||||
try {
|
||||
await instanceApi.startInstance(instanceID.value);
|
||||
showNotification('Instance started successfully', 'success');
|
||||
await loadInstanceStatus();
|
||||
} catch (error) {
|
||||
showNotification(error.message || 'Start instance failed', 'error');
|
||||
}
|
||||
};
|
||||
|
||||
const stopInstance = async () => {
|
||||
try {
|
||||
await instanceApi.stopInstance(instanceID.value);
|
||||
showNotification('Instance stopped successfully', 'success');
|
||||
await loadInstanceStatus();
|
||||
} catch (error) {
|
||||
showNotification(error.message || 'Stop instance failed', 'error');
|
||||
}
|
||||
};
|
||||
|
||||
const restartInstance = async () => {
|
||||
try {
|
||||
await instanceApi.restartInstance(instanceID.value);
|
||||
showNotification('Instance restarted successfully', 'success');
|
||||
await loadInstanceStatus();
|
||||
} catch (error) {
|
||||
showNotification(error.message || 'Restart instance failed', 'error');
|
||||
}
|
||||
};
|
||||
|
||||
const handleStatusClick = async () => {
|
||||
if (instanceStatus.value === 'running') {
|
||||
await stopInstance();
|
||||
} else {
|
||||
await startInstance();
|
||||
}
|
||||
};
|
||||
|
||||
const loadProxies = async () => {
|
||||
try {
|
||||
const result = await instanceApi.getInstanceProxies(instanceID.value);
|
||||
@@ -301,7 +393,7 @@ export default {
|
||||
|
||||
const loadLogs = async () => {
|
||||
try {
|
||||
const result = await instanceApi.getInstanceLogs(instanceName.value);
|
||||
const result = await instanceApi.getInstanceLogs(instanceID.value);
|
||||
logs.value = result.data.map(log => ({
|
||||
time: formatDate(log.timestamp),
|
||||
level: log.level,
|
||||
@@ -318,10 +410,8 @@ export default {
|
||||
|
||||
const editConfig = async () => {
|
||||
try {
|
||||
const result = await instanceApi.listInstances();
|
||||
const instance = result.data.find(i => i.name === instanceName.value);
|
||||
const instance = instanceID.value;
|
||||
if (instance) {
|
||||
instanceID.value = String(instance.instanceID);
|
||||
formData.value = {
|
||||
name: instance.name,
|
||||
auth_method: instance.auth_method || 'token',
|
||||
@@ -365,7 +455,6 @@ export default {
|
||||
};
|
||||
|
||||
await instanceApi.modifyInstance(
|
||||
instanceName.value,
|
||||
instanceID.value,
|
||||
'systemConfig',
|
||||
systemConfigData
|
||||
@@ -387,7 +476,6 @@ export default {
|
||||
}
|
||||
|
||||
await instanceApi.modifyInstance(
|
||||
instanceName.value,
|
||||
instanceID.value,
|
||||
'configFile',
|
||||
configFileData
|
||||
@@ -397,7 +485,6 @@ export default {
|
||||
closeEditConfigModal();
|
||||
closeAddProxyModal();
|
||||
// 重新加载配置
|
||||
instanceName.value = formData.value.name;
|
||||
await loadInstanceConfig();
|
||||
} catch (error) {
|
||||
showNotification(error.message || 'Save configuration failed', 'error');
|
||||
@@ -431,15 +518,19 @@ export default {
|
||||
|
||||
onMounted(async () => {
|
||||
await loadInstanceConfig();
|
||||
await loadInstanceStatus();
|
||||
await loadProxies();
|
||||
await loadLogs();
|
||||
});
|
||||
|
||||
return {
|
||||
instanceName,
|
||||
instanceID,
|
||||
instanceConfig,
|
||||
proxies,
|
||||
logs,
|
||||
instanceStatus,
|
||||
statusInfo,
|
||||
userType,
|
||||
showEditConfigModal,
|
||||
showAddProxyModal,
|
||||
loading,
|
||||
@@ -450,7 +541,9 @@ export default {
|
||||
addProxy,
|
||||
closeAddProxyModal,
|
||||
handleEditConfiguration,
|
||||
handleAddProxySubmit
|
||||
handleAddProxySubmit,
|
||||
handleStatusClick,
|
||||
restartInstance
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -643,4 +736,94 @@ export default {
|
||||
justify-content: space-between;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.section h3 .action-buttons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.status-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.status-item {
|
||||
padding: 16px;
|
||||
border-radius: 8px;
|
||||
background: var(--card-bg);
|
||||
border: 1px solid var(--border-color);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.status-key {
|
||||
color: var(--text-secondary);
|
||||
font-weight: 500;
|
||||
min-width: 90px;
|
||||
}
|
||||
|
||||
.status-value {
|
||||
color: var(--text-color);
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.status-value.running {
|
||||
color: #52c41a;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.status-value.stopped {
|
||||
color: #ff4d4f;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.common-btn.status-btn {
|
||||
padding: 8px 16px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
background: #52c41a;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.common-btn.status-btn:hover {
|
||||
background: #40a412;
|
||||
}
|
||||
|
||||
.common-btn.stop-btn {
|
||||
background: #ff4d4f;
|
||||
}
|
||||
|
||||
.common-btn.stop-btn:hover {
|
||||
background: #cf1616;
|
||||
}
|
||||
|
||||
.common-btn.restart-btn {
|
||||
padding: 8px 16px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
background: #1890ff;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.common-btn.restart-btn:hover {
|
||||
background: #096dd9;
|
||||
}
|
||||
|
||||
.status-btn i,
|
||||
.restart-btn i {
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user