987 lines
43 KiB
HTML
987 lines
43 KiB
HTML
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<title>创建AI检测任务</title>
|
|
<meta charset="UTF-8">
|
|
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
|
|
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
|
|
<style>
|
|
#app{
|
|
padding-bottom: 300px;
|
|
}
|
|
body {
|
|
font-family: Arial, sans-serif;
|
|
max-width: 1200px;
|
|
margin: 0 auto;
|
|
padding: 20px;
|
|
background-color: #f5f5f5;
|
|
}
|
|
.container {
|
|
display: flex;
|
|
gap: 20px;
|
|
}
|
|
.left-panel {
|
|
flex: 3;
|
|
}
|
|
.right-panel {
|
|
flex: 2;
|
|
background: white;
|
|
padding: 20px;
|
|
border-radius: 8px;
|
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
}
|
|
.card {
|
|
background: white;
|
|
padding: 20px;
|
|
margin-bottom: 20px;
|
|
border-radius: 8px;
|
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
}
|
|
.form-group {
|
|
margin-bottom: 15px;
|
|
}
|
|
label {
|
|
display: block;
|
|
margin-bottom: 5px;
|
|
font-weight: bold;
|
|
color: #333;
|
|
}
|
|
input[type="text"], input[type="password"], select, textarea {
|
|
width: 100%;
|
|
padding: 8px;
|
|
border: 1px solid #ddd;
|
|
border-radius: 4px;
|
|
box-sizing: border-box;
|
|
}
|
|
textarea {
|
|
min-height: 100px;
|
|
resize: vertical;
|
|
}
|
|
.btn {
|
|
padding: 10px 20px;
|
|
background-color: #007bff;
|
|
color: white;
|
|
border: none;
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
font-size: 16px;
|
|
}
|
|
.btn:hover {
|
|
background-color: #0056b3;
|
|
}
|
|
.btn:disabled {
|
|
background-color: #ccc;
|
|
cursor: not-allowed;
|
|
}
|
|
.btn-success {
|
|
background-color: #28a745;
|
|
}
|
|
.btn-success:hover {
|
|
background-color: #218838;
|
|
}
|
|
.btn-danger {
|
|
background-color: #dc3545;
|
|
}
|
|
.btn-danger:hover {
|
|
background-color: #c82333;
|
|
}
|
|
.btn-sm {
|
|
padding: 5px 10px;
|
|
font-size: 14px;
|
|
}
|
|
.error {
|
|
color: #dc3545;
|
|
font-size: 14px;
|
|
margin-top: 5px;
|
|
}
|
|
.success {
|
|
color: #28a745;
|
|
font-size: 14px;
|
|
margin-top: 5px;
|
|
}
|
|
.warning {
|
|
color: #ffc107;
|
|
font-size: 14px;
|
|
margin-top: 5px;
|
|
}
|
|
.model-item {
|
|
border: 1px solid #ddd;
|
|
padding: 15px;
|
|
margin-bottom: 10px;
|
|
border-radius: 4px;
|
|
background: #f8f9fa;
|
|
}
|
|
.model-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 10px;
|
|
}
|
|
.model-status {
|
|
display: inline-block;
|
|
padding: 2px 8px;
|
|
border-radius: 12px;
|
|
font-size: 12px;
|
|
}
|
|
.status-valid {
|
|
background-color: #d4edda;
|
|
color: #155724;
|
|
}
|
|
.status-invalid {
|
|
background-color: #f8d7da;
|
|
color: #721c24;
|
|
}
|
|
.status-pending {
|
|
background-color: #fff3cd;
|
|
color: #856404;
|
|
}
|
|
.step {
|
|
display: flex;
|
|
align-items: center;
|
|
margin-bottom: 20px;
|
|
}
|
|
.step-number {
|
|
width: 30px;
|
|
height: 30px;
|
|
background-color: #6c757d;
|
|
color: white;
|
|
border-radius: 50%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
margin-right: 10px;
|
|
}
|
|
.step-number.active {
|
|
background-color: #007bff;
|
|
}
|
|
.step-number.completed {
|
|
background-color: #28a745;
|
|
}
|
|
.step-content {
|
|
flex: 1;
|
|
}
|
|
.progress-bar {
|
|
height: 4px;
|
|
background-color: #e9ecef;
|
|
border-radius: 2px;
|
|
margin: 20px 0;
|
|
overflow: hidden;
|
|
}
|
|
.progress-fill {
|
|
height: 100%;
|
|
background-color: #007bff;
|
|
width: 0%;
|
|
transition: width 0.3s ease;
|
|
}
|
|
.task-preview {
|
|
background: #f8f9fa;
|
|
padding: 15px;
|
|
border-radius: 4px;
|
|
margin-top: 20px;
|
|
}
|
|
.task-info-item {
|
|
margin-bottom: 8px;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
}
|
|
.task-info-label {
|
|
font-weight: bold;
|
|
color: #666;
|
|
}
|
|
.model-selector {
|
|
max-height: 300px;
|
|
overflow-y: auto;
|
|
border: 1px solid #ddd;
|
|
border-radius: 4px;
|
|
padding: 10px;
|
|
}
|
|
.model-option {
|
|
padding: 10px;
|
|
border-bottom: 1px solid #eee;
|
|
cursor: pointer;
|
|
transition: background-color 0.2s;
|
|
}
|
|
.model-option:hover {
|
|
background-color: #f0f8ff;
|
|
}
|
|
.model-option.selected {
|
|
background-color: #e7f3ff;
|
|
border-left: 3px solid #007bff;
|
|
}
|
|
.model-details {
|
|
font-size: 12px;
|
|
color: #666;
|
|
margin-top: 5px;
|
|
}
|
|
.validation-result {
|
|
padding: 10px;
|
|
margin-top: 10px;
|
|
border-radius: 4px;
|
|
}
|
|
.validation-success {
|
|
background-color: #d4edda;
|
|
border: 1px solid #c3e6cb;
|
|
}
|
|
.validation-error {
|
|
background-color: #f8d7da;
|
|
border: 1px solid #f5c6cb;
|
|
}
|
|
table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
margin-top: 10px;
|
|
}
|
|
th, td {
|
|
padding: 8px;
|
|
text-align: left;
|
|
border-bottom: 1px solid #ddd;
|
|
}
|
|
th {
|
|
background-color: #f8f9fa;
|
|
font-weight: bold;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div id="app">
|
|
<h1>创建AI检测任务</h1>
|
|
|
|
<!-- 步骤指示器 -->
|
|
<div class="progress-bar">
|
|
<div class="progress-fill" :style="{width: (currentStep / 4 * 100) + '%'}"></div>
|
|
</div>
|
|
|
|
<div class="step">
|
|
<div class="step-number" :class="{'active': currentStep === 1, 'completed': currentStep > 1}">1</div>
|
|
<div class="step-content">
|
|
<h3>选择模型</h3>
|
|
<p v-if="currentStep === 1">选择要使用的加密模型文件</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="step">
|
|
<div class="step-number" :class="{'active': currentStep === 2, 'completed': currentStep > 2}">2</div>
|
|
<div class="step-content">
|
|
<h3>配置密钥</h3>
|
|
<p v-if="currentStep === 2">为每个模型配置加密密钥</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="step">
|
|
<div class="step-number" :class="{'active': currentStep === 3, 'completed': currentStep > 3}">3</div>
|
|
<div class="step-content">
|
|
<h3>任务设置</h3>
|
|
<p v-if="currentStep === 3">配置RTMP流和任务参数</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="step">
|
|
<div class="step-number" :class="{'active': currentStep === 4, 'completed': currentStep > 4}">4</div>
|
|
<div class="step-content">
|
|
<h3>验证和创建</h3>
|
|
<p v-if="currentStep === 4">验证所有配置并创建任务</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="container">
|
|
<!-- 左侧主内容 -->
|
|
<div class="left-panel">
|
|
<!-- 步骤1: 选择模型 -->
|
|
<div class="card" v-if="currentStep === 1">
|
|
<h3>选择加密模型</h3>
|
|
<div class="form-group">
|
|
<label>可用加密模型:</label>
|
|
<div class="model-selector">
|
|
<div v-if="loadingModels">加载模型中...</div>
|
|
<div v-else-if="availableModels.length === 0">没有找到加密模型文件</div>
|
|
<div v-for="model in availableModels"
|
|
:key="model.filename"
|
|
class="model-option"
|
|
:class="{'selected': selectedModels.some(m => m.filename === model.filename)}"
|
|
@click="toggleModelSelection(model)">
|
|
<div><strong>{{ model.filename }}</strong></div>
|
|
<div class="model-details">
|
|
大小: {{ formatFileSize(model.size) }} |
|
|
哈希: {{ model.model_hash || '未知' }} |
|
|
修改: {{ formatTimestamp(model.modified) }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="selectedModels.length > 0">
|
|
<h4>已选模型 ({{ selectedModels.length }})</h4>
|
|
<div v-for="(model, index) in selectedModels" :key="index" class="model-item">
|
|
<div class="model-header">
|
|
<div><strong>{{ model.filename }}</strong></div>
|
|
<button class="btn btn-sm btn-danger" @click="removeModel(index)">移除</button>
|
|
</div>
|
|
<div class="model-details">
|
|
路径: {{ model.path }}<br>
|
|
模型哈希: {{ model.model_hash || '未知' }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<button class="btn" @click="loadAvailableModels" :disabled="loadingModels">
|
|
{{ loadingModels ? '加载中...' : '刷新列表' }}
|
|
</button>
|
|
<button class="btn btn-success" @click="nextStep" :disabled="selectedModels.length === 0">
|
|
下一步: 配置密钥
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 步骤2: 配置密钥 -->
|
|
<div class="card" v-if="currentStep === 2">
|
|
<h3>配置模型加密密钥</h3>
|
|
<p>为每个选中的模型配置加密密钥。密钥将在创建任务前进行验证。</p>
|
|
|
|
<div v-for="(model, index) in selectedModels" :key="index" class="model-item">
|
|
<div class="model-header">
|
|
<div>
|
|
<strong>{{ model.filename }}</strong>
|
|
<span class="model-status" :class="{
|
|
'status-valid': model.validation.valid,
|
|
'status-invalid': model.validation.checked && !model.validation.valid,
|
|
'status-pending': !model.validation.checked
|
|
}">
|
|
{{ model.validation.checked ? (model.validation.valid ? '有效' : '无效') : '待验证' }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label>加密密钥:</label>
|
|
<input type="password"
|
|
v-model="model.encryption_key"
|
|
placeholder="输入加密密钥"
|
|
@input="validateSingleModel(index)">
|
|
|
|
<div v-if="model.validation.checked" class="validation-result"
|
|
:class="{'validation-success': model.validation.valid, 'validation-error': !model.validation.valid}">
|
|
<div v-if="model.validation.valid">
|
|
✓ 密钥验证成功<br>
|
|
<small>模型哈希: {{ model.validation.model_hash }}</small>
|
|
</div>
|
|
<div v-else>
|
|
✗ 密钥验证失败: {{ model.validation.error }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<button class="btn btn-sm" @click="testModelDecryption(index)"
|
|
:disabled="!model.encryption_key">
|
|
测试解密
|
|
</button>
|
|
<button class="btn btn-sm" @click="generateKeyForModel(index)">
|
|
生成新密钥
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<button class="btn" @click="prevStep">上一步</button>
|
|
<button class="btn btn-success" @click="validateAllModels"
|
|
:disabled="!allModelsHaveKeys || validatingAll">
|
|
{{ validatingAll ? '验证中...' : '验证所有模型' }}
|
|
</button>
|
|
<button class="btn" @click="nextStep" :disabled="!allModelsValid">
|
|
下一步: 任务设置
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 步骤3: 任务设置 -->
|
|
<div class="card" v-if="currentStep === 3">
|
|
<h3>任务配置</h3>
|
|
|
|
<div class="form-group">
|
|
<label>任务名称:</label>
|
|
<input type="text" v-model="taskConfig.taskname" placeholder="输入任务名称">
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label>RTMP视频流地址:</label>
|
|
<input type="text" v-model="taskConfig.rtmp_url"
|
|
placeholder="rtmp://server:port/stream/key">
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label>推流地址 (可选):</label>
|
|
<input type="text" v-model="taskConfig.push_url"
|
|
placeholder="rtmp://server:port/stream/key">
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label>算法ID (可选):</label>
|
|
<input type="text" v-model="taskConfig.AlgoId" placeholder="输入算法ID">
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label>模型配置:</label>
|
|
<div class="task-preview">
|
|
<div class="task-info-item">
|
|
<span class="task-info-label">模型数量:</span>
|
|
<span>{{ selectedModels.length }}</span>
|
|
</div>
|
|
<div class="task-info-item">
|
|
<span class="task-info-label">已验证模型:</span>
|
|
<span>{{ validModelsCount }} / {{ selectedModels.length }}</span>
|
|
</div>
|
|
<div class="task-info-item">
|
|
<span class="task-info-label">加密要求:</span>
|
|
<span>强制加密 ✓</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<button class="btn" @click="prevStep">上一步</button>
|
|
<button class="btn btn-success" @click="nextStep">
|
|
下一步: 验证和创建
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 步骤4: 验证和创建 -->
|
|
<div class="card" v-if="currentStep === 4">
|
|
<h3>验证并创建任务</h3>
|
|
|
|
<div v-if="creatingTask" class="validation-result validation-success">
|
|
<h4>正在创建任务...</h4>
|
|
<p>{{ createTaskMessage }}</p>
|
|
</div>
|
|
|
|
<div v-else>
|
|
<h4>配置概览</h4>
|
|
<div class="task-preview">
|
|
<div class="task-info-item">
|
|
<span class="task-info-label">任务名称:</span>
|
|
<span>{{ taskConfig.taskname }}</span>
|
|
</div>
|
|
<div class="task-info-item">
|
|
<span class="task-info-label">视频流:</span>
|
|
<span>{{ taskConfig.rtmp_url }}</span>
|
|
</div>
|
|
<div class="task-info-item">
|
|
<span class="task-info-label">推流地址:</span>
|
|
<span>{{ taskConfig.push_url || '未设置' }}</span>
|
|
</div>
|
|
<div class="task-info-item">
|
|
<span class="task-info-label">模型数量:</span>
|
|
<span>{{ selectedModels.length }} 个</span>
|
|
</div>
|
|
<div class="task-info-item">
|
|
<span class="task-info-label">密钥验证:</span>
|
|
<span>{{ validModelsCount === selectedModels.length ? '全部通过 ✓' : '未通过 ✗' }}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<h4>最终验证</h4>
|
|
<div class="form-group">
|
|
<button class="btn" @click="performFinalValidation"
|
|
:disabled="validatingFinal || creatingTask">
|
|
{{ validatingFinal ? '验证中...' : '执行最终验证' }}
|
|
</button>
|
|
</div>
|
|
|
|
<div v-if="finalValidationResult" class="validation-result"
|
|
:class="{'validation-success': finalValidationResult.success, 'validation-error': !finalValidationResult.success}">
|
|
<h4 v-if="finalValidationResult.success">✓ 验证通过</h4>
|
|
<h4 v-else>✗ 验证失败</h4>
|
|
|
|
<div v-if="finalValidationResult.success">
|
|
<p>所有模型验证成功,可以创建任务</p>
|
|
<p>有效模型: {{ finalValidationResult.valid_models }} / {{ finalValidationResult.total_models }}</p>
|
|
</div>
|
|
<div v-else>
|
|
<p>验证失败: {{ finalValidationResult.error }}</p>
|
|
<div v-if="finalValidationResult.validation_results">
|
|
<p>详细错误:</p>
|
|
<ul>
|
|
<li v-for="(result, idx) in finalValidationResult.validation_results"
|
|
v-if="!result.key_valid">
|
|
模型 {{ result.model_index }}: {{ result.error }}
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<button class="btn" @click="prevStep">上一步</button>
|
|
<button class="btn btn-success" @click="createTask"
|
|
:disabled="!finalValidationResult || !finalValidationResult.success || creatingTask">
|
|
{{ creatingTask ? '创建中...' : '创建任务' }}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 右侧信息面板 -->
|
|
<div class="right-panel">
|
|
<h3>任务状态</h3>
|
|
|
|
<div v-if="currentStep === 1">
|
|
<p><strong>步骤1/4:</strong> 选择模型</p>
|
|
<p>请从已上传的加密模型中选择要使用的模型。可以多选。</p>
|
|
<p><em>提示:</em> 模型文件必须已经通过上传接口加密上传。</p>
|
|
</div>
|
|
|
|
<div v-if="currentStep === 2">
|
|
<p><strong>步骤2/4:</strong> 配置密钥</p>
|
|
<p>为每个模型输入加密密钥。密钥强度要求:</p>
|
|
<ul>
|
|
<li>长度至少16位</li>
|
|
<li>包含大小写字母</li>
|
|
<li>包含数字</li>
|
|
<li>包含特殊字符</li>
|
|
</ul>
|
|
<p>点击"测试解密"可以验证密钥是否正确。</p>
|
|
</div>
|
|
|
|
<div v-if="currentStep === 3">
|
|
<p><strong>步骤3/4:</strong> 任务设置</p>
|
|
<p>配置任务的基本参数:</p>
|
|
<ul>
|
|
<li><strong>RTMP流:</strong> 输入视频流地址</li>
|
|
<li><strong>推流地址:</strong> 处理后的视频推流地址(可选)</li>
|
|
<li><strong>任务名称:</strong> 用于标识任务的名称</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<div v-if="currentStep === 4">
|
|
<p><strong>步骤4/4:</strong> 验证和创建</p>
|
|
<p>系统将执行最终验证:</p>
|
|
<ul>
|
|
<li>验证所有模型密钥</li>
|
|
<li>检查系统资源</li>
|
|
<li>确认任务参数</li>
|
|
</ul>
|
|
<p>验证通过后,点击"创建任务"启动检测。</p>
|
|
</div>
|
|
|
|
<div class="task-preview" v-if="createdTaskId">
|
|
<h4>任务创建成功</h4>
|
|
<p><strong>任务ID:</strong> {{ createdTaskId }}</p>
|
|
<p><strong>状态:</strong> {{ taskStatus }}</p>
|
|
<p><strong>创建时间:</strong> {{ new Date().toLocaleString() }}</p>
|
|
|
|
<div class="form-group">
|
|
<button class="btn btn-sm" @click="goToTaskManagement">查看任务管理</button>
|
|
<button class="btn btn-sm" @click="copyTaskId">复制任务ID</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="errorMessage" class="error">
|
|
<h4>错误信息</h4>
|
|
<p>{{ errorMessage }}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// 等待DOM加载完成后初始化Vue
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
new Vue({
|
|
el: '#app',
|
|
data: function() {
|
|
return {
|
|
currentStep: 1,
|
|
availableModels: [],
|
|
selectedModels: [],
|
|
loadingModels: false,
|
|
validatingAll: false,
|
|
validatingFinal: false,
|
|
creatingTask: false,
|
|
|
|
taskConfig: {
|
|
taskname: '检测任务_' + new Date().toLocaleDateString('zh-CN').replace(/\//g, '-'),
|
|
rtmp_url: 'rtmp://localhost:1935/live/stream',
|
|
push_url: '',
|
|
AlgoId: ''
|
|
},
|
|
|
|
finalValidationResult: null,
|
|
createTaskMessage: '',
|
|
createdTaskId: null,
|
|
taskStatus: '',
|
|
errorMessage: '',
|
|
base_url:'http://192.168.10.131:9309'
|
|
};
|
|
},
|
|
computed: {
|
|
allModelsHaveKeys: function() {
|
|
return this.selectedModels.every(function(model) {
|
|
return model.encryption_key && model.encryption_key.trim().length > 0;
|
|
});
|
|
},
|
|
allModelsValid: function() {
|
|
return this.selectedModels.every(function(model) {
|
|
return model.validation.valid;
|
|
});
|
|
},
|
|
validModelsCount: function() {
|
|
return this.selectedModels.filter(function(model) {
|
|
return model.validation.valid;
|
|
}).length;
|
|
}
|
|
},
|
|
methods: {
|
|
// 步骤导航
|
|
nextStep: function() {
|
|
if (this.currentStep < 4) {
|
|
this.currentStep++;
|
|
}
|
|
},
|
|
prevStep: function() {
|
|
if (this.currentStep > 1) {
|
|
this.currentStep--;
|
|
}
|
|
},
|
|
|
|
// 加载可用模型
|
|
loadAvailableModels: function() {
|
|
var self = this;
|
|
self.loadingModels = true;
|
|
self.errorMessage = '';
|
|
|
|
axios.get(this.base_url + '/api/models/encrypted/list_available')
|
|
.then(function(response) {
|
|
if (response.data.status === 'success') {
|
|
self.availableModels = response.data.data.models.map(function(model) {
|
|
return {
|
|
filename: model.filename,
|
|
path: model.path,
|
|
size: model.size,
|
|
modified: model.modified,
|
|
model_hash: model.model_hash || '',
|
|
encryption_key: '',
|
|
validation: {
|
|
checked: false,
|
|
valid: false,
|
|
error: '',
|
|
model_hash: ''
|
|
}
|
|
};
|
|
});
|
|
} else {
|
|
self.errorMessage = response.data.message;
|
|
}
|
|
})
|
|
.catch(function(error) {
|
|
self.errorMessage = error.response && error.response.data ? error.response.data.message : error.message;
|
|
})
|
|
.finally(function() {
|
|
self.loadingModels = false;
|
|
});
|
|
},
|
|
|
|
// 模型选择
|
|
toggleModelSelection: function(model) {
|
|
var index = this.selectedModels.findIndex(function(m) {
|
|
return m.filename === model.filename;
|
|
});
|
|
if (index === -1) {
|
|
// 深拷贝模型对象
|
|
var newModel = JSON.parse(JSON.stringify(model));
|
|
this.selectedModels.push(newModel);
|
|
} else {
|
|
this.selectedModels.splice(index, 1);
|
|
}
|
|
},
|
|
|
|
removeModel: function(index) {
|
|
this.selectedModels.splice(index, 1);
|
|
},
|
|
|
|
// 单个模型验证
|
|
validateSingleModel: function(index) {
|
|
var self = this;
|
|
var model = self.selectedModels[index];
|
|
|
|
if (!model.encryption_key || model.encryption_key.trim().length < 16) {
|
|
model.validation = {
|
|
checked: true,
|
|
valid: false,
|
|
error: '密钥长度不足或为空',
|
|
model_hash: ''
|
|
};
|
|
return;
|
|
}
|
|
|
|
// 验证密钥强度(前端简单验证)
|
|
if (model.encryption_key.length < 16) {
|
|
model.validation = {
|
|
checked: true,
|
|
valid: false,
|
|
error: '密钥长度至少16位',
|
|
model_hash: ''
|
|
};
|
|
return;
|
|
}
|
|
|
|
// 调用API验证
|
|
axios.post(this.base_url + '/api/models/process/verify_key', {
|
|
model_path: model.path,
|
|
encryption_key: model.encryption_key
|
|
})
|
|
.then(function(response) {
|
|
if (response.data.status === 'success') {
|
|
var verification = response.data.data.model_verification;
|
|
if (verification && verification.success) {
|
|
model.validation = {
|
|
checked: true,
|
|
valid: true,
|
|
error: '',
|
|
model_hash: verification.model_hash || ''
|
|
};
|
|
} else {
|
|
model.validation = {
|
|
checked: true,
|
|
valid: false,
|
|
error: verification ? verification.error : '验证失败',
|
|
model_hash: ''
|
|
};
|
|
}
|
|
} else {
|
|
model.validation = {
|
|
checked: true,
|
|
valid: false,
|
|
error: response.data.message,
|
|
model_hash: ''
|
|
};
|
|
}
|
|
})
|
|
.catch(function(error) {
|
|
model.validation = {
|
|
checked: true,
|
|
valid: false,
|
|
error: error.response && error.response.data ? error.response.data.message : error.message,
|
|
model_hash: ''
|
|
};
|
|
});
|
|
},
|
|
|
|
// 测试解密
|
|
testModelDecryption: function(index) {
|
|
var self = this;
|
|
var model = self.selectedModels[index];
|
|
self.errorMessage = '';
|
|
|
|
if (!model.encryption_key) {
|
|
self.errorMessage = '请输入加密密钥';
|
|
return;
|
|
}
|
|
|
|
axios.post(this.base_url + '/api/models/process/test_decrypt', {
|
|
model_path: model.path,
|
|
encryption_key: model.encryption_key
|
|
})
|
|
.then(function(response) {
|
|
if (response.data.status === 'success') {
|
|
alert('解密测试成功!\n模型哈希: ' + response.data.data.model_hash +
|
|
'\n解密时间: ' + response.data.data.decryption_time.toFixed(3) + '秒');
|
|
} else {
|
|
self.errorMessage = response.data.message;
|
|
}
|
|
})
|
|
.catch(function(error) {
|
|
self.errorMessage = error.response && error.response.data ? error.response.data.message : error.message;
|
|
});
|
|
},
|
|
|
|
// 生成新密钥
|
|
generateKeyForModel: function(index) {
|
|
var self = this;
|
|
axios.post(this.base_url + '/api/models/generate_key')
|
|
.then(function(response) {
|
|
if (response.data.status === 'success') {
|
|
self.selectedModels[index].encryption_key = response.data.data.key;
|
|
self.validateSingleModel(index);
|
|
}
|
|
})
|
|
.catch(function(error) {
|
|
self.errorMessage = error.response && error.response.data ? error.response.data.message : error.message;
|
|
});
|
|
},
|
|
|
|
// 验证所有模型
|
|
validateAllModels: function() {
|
|
var self = this;
|
|
self.validatingAll = true;
|
|
self.errorMessage = '';
|
|
|
|
// 依次验证每个模型
|
|
var validateNext = function(index) {
|
|
if (index >= self.selectedModels.length) {
|
|
self.validatingAll = false;
|
|
if (self.allModelsValid) {
|
|
self.errorMessage = '所有模型验证通过 (' + self.validModelsCount + '/' + self.selectedModels.length + ')';
|
|
} else {
|
|
self.errorMessage = '部分模型验证失败,请检查密钥';
|
|
}
|
|
return;
|
|
}
|
|
|
|
self.validateSingleModel(index);
|
|
setTimeout(function() {
|
|
validateNext(index + 1);
|
|
}, 500);
|
|
};
|
|
|
|
validateNext(0);
|
|
},
|
|
|
|
// 最终验证
|
|
performFinalValidation: function() {
|
|
var self = this;
|
|
self.validatingFinal = true;
|
|
self.errorMessage = '';
|
|
self.finalValidationResult = null;
|
|
|
|
// 准备验证数据
|
|
var validationData = {
|
|
models: self.selectedModels.map(function(model) {
|
|
return {
|
|
path: model.path,
|
|
encryption_key: model.encryption_key
|
|
};
|
|
})
|
|
};
|
|
|
|
axios.post(this.base_url + '/api/models/process/validate_task', validationData)
|
|
.then(function(response) {
|
|
if (response.data.status === 'success') {
|
|
self.finalValidationResult = response.data.data;
|
|
self.errorMessage = '验证通过: ' + self.finalValidationResult.valid_models + '/' + self.finalValidationResult.total_models + ' 个模型有效';
|
|
} else {
|
|
self.finalValidationResult = response.data.data;
|
|
self.errorMessage = response.data.message;
|
|
}
|
|
})
|
|
.catch(function(error) {
|
|
self.errorMessage = error.response && error.response.data ? error.response.data.message : error.message;
|
|
})
|
|
.finally(function() {
|
|
self.validatingFinal = false;
|
|
});
|
|
},
|
|
|
|
// 创建任务
|
|
createTask: function() {
|
|
var self = this;
|
|
self.creatingTask = true;
|
|
self.createTaskMessage = '正在创建任务...';
|
|
self.errorMessage = '';
|
|
|
|
// 准备任务数据
|
|
var taskData = {
|
|
taskname: self.taskConfig.taskname,
|
|
rtmp_url: self.taskConfig.rtmp_url,
|
|
push_url: self.taskConfig.push_url || undefined,
|
|
AlgoId: self.taskConfig.AlgoId || undefined,
|
|
models: self.selectedModels.map(function(model) {
|
|
return {
|
|
path: model.path,
|
|
encryption_key: model.encryption_key,
|
|
tags: model.tags || {},
|
|
conf_thres: model.conf_thres || 0.25,
|
|
iou_thres: model.iou_thres || 0.45,
|
|
imgsz: model.imgsz || 640,
|
|
device: model.device || 'cuda:0',
|
|
half: model.half !== undefined ? model.half : true,
|
|
enabled: true
|
|
};
|
|
})
|
|
};
|
|
|
|
// 先执行最终验证
|
|
self.performFinalValidation();
|
|
|
|
// 等待验证完成
|
|
var checkValidation = function() {
|
|
if (self.finalValidationResult && self.finalValidationResult.success) {
|
|
// 验证通过,提交任务创建请求
|
|
self.createTaskMessage = '正在提交任务创建请求...';
|
|
axios.post(self.base_url + '/api/tasks/create', taskData)
|
|
.then(function(response) {
|
|
if (response.data.status === 'success') {
|
|
self.createdTaskId = response.data.data.task_id;
|
|
self.taskStatus = '已创建';
|
|
self.createTaskMessage = '任务创建成功!';
|
|
} else {
|
|
throw new Error(response.data.message);
|
|
}
|
|
})
|
|
.catch(function(error) {
|
|
self.errorMessage = error.response && error.response.data ? error.response.data.message : error.message;
|
|
self.createTaskMessage = '任务创建失败';
|
|
})
|
|
.finally(function() {
|
|
self.creatingTask = false;
|
|
});
|
|
} else if (self.finalValidationResult && !self.finalValidationResult.success) {
|
|
// 验证失败
|
|
self.errorMessage = '模型验证失败,无法创建任务: ' + (self.finalValidationResult.error || '未知错误');
|
|
self.createTaskMessage = '任务创建失败';
|
|
self.creatingTask = false;
|
|
} else {
|
|
// 等待验证结果
|
|
setTimeout(checkValidation, 500);
|
|
}
|
|
};
|
|
|
|
checkValidation();
|
|
},
|
|
|
|
// 辅助函数
|
|
formatFileSize: function(bytes) {
|
|
if (bytes === 0) return '0 Bytes';
|
|
var k = 1024;
|
|
var sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
|
var i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
|
},
|
|
|
|
formatTimestamp: function(timestamp) {
|
|
var date = new Date(timestamp * 1000);
|
|
return date.toLocaleDateString();
|
|
},
|
|
|
|
copyTaskId: function() {
|
|
if (this.createdTaskId) {
|
|
navigator.clipboard.writeText(this.createdTaskId)
|
|
.then(function() {
|
|
alert('任务ID已复制到剪贴板');
|
|
})
|
|
.catch(function() {
|
|
// 备用方案
|
|
var textArea = document.createElement("textarea");
|
|
textArea.value = this.createdTaskId;
|
|
document.body.appendChild(textArea);
|
|
textArea.select();
|
|
document.execCommand("copy");
|
|
document.body.removeChild(textArea);
|
|
alert('任务ID已复制到剪贴板');
|
|
});
|
|
}
|
|
},
|
|
|
|
goToTaskManagement: function() {
|
|
window.location.href = '/';
|
|
}
|
|
},
|
|
mounted: function() {
|
|
this.loadAvailableModels();
|
|
}
|
|
});
|
|
});
|
|
</script>
|
|
</body>
|
|
</html> |