feat: initialize managed portal

This commit is contained in:
Yoilun
2026-04-27 10:04:36 +08:00
commit d4e351df71
145 changed files with 13425 additions and 0 deletions

View 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>