feat: initialize managed portal
This commit is contained in:
312
web/src/views/ManagedServices.vue
Normal file
312
web/src/views/ManagedServices.vue
Normal file
@@ -0,0 +1,312 @@
|
||||
<template>
|
||||
<div class="managed-services-page">
|
||||
<div class="page-toolbar">
|
||||
<p class="page-desc">
|
||||
统一管理两个 Docker 子服务:RTSP、容器状态与子项目结构化结果摘要。
|
||||
</p>
|
||||
<el-button type="primary" @click="loadServices" :loading="loading">
|
||||
刷新
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<el-row v-loading="loading" :gutter="20">
|
||||
<el-col
|
||||
v-for="service in services"
|
||||
:key="service.id"
|
||||
:xs="24"
|
||||
:md="12"
|
||||
:xl="8"
|
||||
>
|
||||
<el-card class="service-card" shadow="never">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<div>
|
||||
<div class="service-name">{{ service.display_name }}</div>
|
||||
<div class="service-type">{{ service.project_type }}</div>
|
||||
</div>
|
||||
<el-tag :type="statusType(service.status)">
|
||||
{{ statusText(service.status) }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="service-section">
|
||||
<span class="section-label">RTSP</span>
|
||||
<div class="rtsp-value">
|
||||
{{ service.rtsp || service.config_error || "-" }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="service-section">
|
||||
<span class="section-label">最近结果</span>
|
||||
<div>{{ formatTime(service.summary?.last_result_time) }}</div>
|
||||
</div>
|
||||
|
||||
<div class="service-section">
|
||||
<span class="section-label">摘要</span>
|
||||
<div class="summary-text">
|
||||
{{
|
||||
service.summary?.headline ||
|
||||
service.result_error ||
|
||||
"暂无摘要"
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="
|
||||
service.config_error ||
|
||||
service.service_error ||
|
||||
service.result_error
|
||||
"
|
||||
class="service-errors"
|
||||
>
|
||||
<el-tag v-if="service.config_error" type="warning">
|
||||
配置异常
|
||||
</el-tag>
|
||||
<el-tag v-if="service.service_error" type="danger">
|
||||
服务状态异常
|
||||
</el-tag>
|
||||
<el-tag v-if="service.result_error" type="info">
|
||||
结果读取异常
|
||||
</el-tag>
|
||||
</div>
|
||||
|
||||
<div class="service-actions">
|
||||
<el-button @click="openEdit(service)">编辑 RTSP</el-button>
|
||||
<el-button
|
||||
type="warning"
|
||||
@click="handleRestart(service)"
|
||||
:loading="restartTarget === service.id"
|
||||
>
|
||||
重启容器
|
||||
</el-button>
|
||||
<el-button type="primary" @click="goDetail(service)">
|
||||
查看详情
|
||||
</el-button>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-empty
|
||||
v-if="!loading && services.length === 0"
|
||||
description="暂无被管理服务"
|
||||
/>
|
||||
|
||||
<el-dialog v-model="editVisible" width="680px" title="编辑 RTSP">
|
||||
<el-form label-width="100px">
|
||||
<el-form-item label="服务">
|
||||
<span>{{ editForm.display_name }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="RTSP">
|
||||
<el-input
|
||||
v-model="editForm.rtsp"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="rtsp://user:password@host:554/stream"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="editVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="saveConfig(false)" :loading="saving">
|
||||
保存
|
||||
</el-button>
|
||||
<el-button type="warning" @click="saveConfig(true)" :loading="saving">
|
||||
保存并重启
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted, ref } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import { ElMessage } from "element-plus";
|
||||
import {
|
||||
getManagedServices,
|
||||
restartManagedService,
|
||||
updateManagedServiceConfig,
|
||||
} from "@/api";
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const services = ref([]);
|
||||
const loading = ref(false);
|
||||
const saving = ref(false);
|
||||
const restartTarget = ref("");
|
||||
const editVisible = ref(false);
|
||||
const editForm = ref({
|
||||
id: "",
|
||||
display_name: "",
|
||||
rtsp: "",
|
||||
});
|
||||
|
||||
const loadServices = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
const res = await getManagedServices();
|
||||
services.value = res.services || [];
|
||||
} catch (error) {
|
||||
ElMessage.error(error.message || "加载被管理服务失败");
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const openEdit = (service) => {
|
||||
editForm.value = {
|
||||
id: service.id,
|
||||
display_name: service.display_name,
|
||||
rtsp: service.rtsp || "",
|
||||
};
|
||||
editVisible.value = true;
|
||||
};
|
||||
|
||||
const saveConfig = async (restartAfter) => {
|
||||
if (!editForm.value.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
saving.value = true;
|
||||
try {
|
||||
await updateManagedServiceConfig(editForm.value.id, {
|
||||
rtsp_url: editForm.value.rtsp,
|
||||
});
|
||||
if (restartAfter) {
|
||||
await restartManagedService(editForm.value.id);
|
||||
}
|
||||
ElMessage.success(restartAfter ? "配置已保存并重启容器" : "配置已保存");
|
||||
editVisible.value = false;
|
||||
await loadServices();
|
||||
} catch (error) {
|
||||
ElMessage.error(error.message || "保存配置失败");
|
||||
} finally {
|
||||
saving.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const handleRestart = async (service) => {
|
||||
restartTarget.value = service.id;
|
||||
try {
|
||||
await restartManagedService(service.id);
|
||||
ElMessage.success("容器已重启");
|
||||
await loadServices();
|
||||
} catch (error) {
|
||||
ElMessage.error(error.message || "重启服务失败");
|
||||
} finally {
|
||||
restartTarget.value = "";
|
||||
}
|
||||
};
|
||||
|
||||
const goDetail = (service) => {
|
||||
router.push(`/managed-services/${service.id}`);
|
||||
};
|
||||
|
||||
const statusType = (status) => {
|
||||
const map = {
|
||||
running: "success",
|
||||
stopped: "info",
|
||||
failed: "danger",
|
||||
unknown: "warning",
|
||||
};
|
||||
return map[status] || "warning";
|
||||
};
|
||||
|
||||
const statusText = (status) => {
|
||||
const map = {
|
||||
running: "运行中",
|
||||
stopped: "已停止",
|
||||
failed: "失败",
|
||||
unknown: "未知",
|
||||
};
|
||||
return map[status] || status || "未知";
|
||||
};
|
||||
|
||||
const formatTime = (value) => {
|
||||
if (!value) {
|
||||
return "-";
|
||||
}
|
||||
const date = new Date(value);
|
||||
if (Number.isNaN(date.getTime())) {
|
||||
return value;
|
||||
}
|
||||
return date.toLocaleString();
|
||||
};
|
||||
|
||||
onMounted(loadServices);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.managed-services-page {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.page-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 20px;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.page-desc {
|
||||
margin: 0;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.service-card {
|
||||
margin-bottom: 20px;
|
||||
min-height: 320px;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.service-name {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.service-type {
|
||||
margin-top: 4px;
|
||||
color: #909399;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.service-section {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.section-label {
|
||||
display: block;
|
||||
margin-bottom: 6px;
|
||||
color: #909399;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.rtsp-value,
|
||||
.summary-text {
|
||||
line-height: 1.6;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.service-errors {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.service-actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user