2025-05-16 15:30:54 +08:00
|
|
|
|
/**
|
|
|
|
|
* @class ConfigurationConfig
|
|
|
|
|
* @classdesc 构型配置开发组件,采用卡片风格,展示机型信息和构型信息。
|
|
|
|
|
* @extends HTMLElement
|
|
|
|
|
*/
|
|
|
|
|
class ConfigurationConfig extends HTMLElement {
|
|
|
|
|
constructor() {
|
|
|
|
|
super();
|
|
|
|
|
this.attachShadow({ mode: 'open' });
|
|
|
|
|
this.currentView = 'planes'; // 'planes' 或 'configurations' 或 'editor'
|
|
|
|
|
this.selectedPlane = null;
|
|
|
|
|
this.selectedConfiguration = null;
|
|
|
|
|
this.userAccessLevel = 0; // 添加用户权限级别属性
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 初始化DOM结构,采用卡片风格,展示机型信息和构型信息。
|
|
|
|
|
*/
|
|
|
|
|
this.shadowRoot.innerHTML = `
|
|
|
|
|
<style>
|
|
|
|
|
:host {
|
|
|
|
|
display: block;
|
|
|
|
|
padding: 20px;
|
|
|
|
|
color: #333;
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
height: calc(100vh - 40px);
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
}
|
|
|
|
|
.page-header {
|
|
|
|
|
margin-bottom: 20px;
|
|
|
|
|
}
|
|
|
|
|
.page-title {
|
|
|
|
|
color: #5c6bc0;
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
font-size: 24px;
|
|
|
|
|
margin-bottom: 8px;
|
|
|
|
|
}
|
|
|
|
|
.back-section {
|
|
|
|
|
margin-bottom: 15px;
|
|
|
|
|
}
|
|
|
|
|
.back-button {
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
color: #5c6bc0;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
text-decoration: none;
|
|
|
|
|
}
|
|
|
|
|
.back-button:hover {
|
|
|
|
|
color: #3949ab;
|
|
|
|
|
}
|
|
|
|
|
.back-icon {
|
|
|
|
|
margin-right: 5px;
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
}
|
|
|
|
|
.config-dev-container {
|
|
|
|
|
display: grid;
|
|
|
|
|
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
|
|
|
|
gap: 20px;
|
|
|
|
|
max-height: calc(100vh - 160px);
|
|
|
|
|
overflow-y: auto;
|
|
|
|
|
padding-right: 10px;
|
|
|
|
|
padding-top: 10px;
|
|
|
|
|
padding-bottom: 30px;
|
|
|
|
|
width: 100%;
|
|
|
|
|
}
|
|
|
|
|
.config-card {
|
|
|
|
|
background: white;
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
|
|
|
|
padding: 20px;
|
|
|
|
|
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
min-height: 280px;
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
align-items: center;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
}
|
|
|
|
|
.config-card:hover {
|
|
|
|
|
transform: translateY(-3px);
|
|
|
|
|
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.15);
|
|
|
|
|
}
|
|
|
|
|
.icon-container {
|
|
|
|
|
width: 160px;
|
|
|
|
|
height: 160px;
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
margin-bottom: 20px;
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
}
|
|
|
|
|
.plane-icon {
|
|
|
|
|
max-width: 100%;
|
|
|
|
|
max-height: 100%;
|
|
|
|
|
object-fit: contain;
|
|
|
|
|
}
|
|
|
|
|
.text-container {
|
|
|
|
|
width: 100%;
|
|
|
|
|
text-align: center;
|
|
|
|
|
flex-grow: 1;
|
|
|
|
|
}
|
|
|
|
|
.config-title {
|
|
|
|
|
font-size: 22px;
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
margin-bottom: 12px;
|
|
|
|
|
color: #333;
|
|
|
|
|
height: 26px;
|
|
|
|
|
line-height: 26px;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
}
|
|
|
|
|
.config-desc {
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
color: #666;
|
|
|
|
|
text-align: center;
|
|
|
|
|
line-height: 1.4;
|
|
|
|
|
padding: 0 10px;
|
|
|
|
|
}
|
|
|
|
|
.loading {
|
|
|
|
|
text-align: center;
|
|
|
|
|
padding: 20px;
|
|
|
|
|
color: #666;
|
|
|
|
|
}
|
|
|
|
|
.error {
|
|
|
|
|
color: #f44336;
|
|
|
|
|
text-align: center;
|
|
|
|
|
padding: 20px;
|
|
|
|
|
}
|
|
|
|
|
.config-info {
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
color: #666;
|
|
|
|
|
margin-top: 8px;
|
|
|
|
|
}
|
|
|
|
|
/* 构型卡片特定样式 */
|
|
|
|
|
.config-dev-container.configurations {
|
|
|
|
|
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
|
|
|
|
gap: 20px;
|
|
|
|
|
}
|
|
|
|
|
.config-card.configuration {
|
|
|
|
|
min-height: auto;
|
|
|
|
|
height: 80px;
|
|
|
|
|
padding: 0 20px;
|
|
|
|
|
flex-direction: row;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
align-items: center;
|
|
|
|
|
position: relative;
|
|
|
|
|
}
|
|
|
|
|
.config-card.configuration .config-title {
|
|
|
|
|
font-size: 18px;
|
|
|
|
|
margin: 0;
|
|
|
|
|
height: auto;
|
|
|
|
|
flex-grow: 1;
|
|
|
|
|
text-align: left;
|
|
|
|
|
}
|
|
|
|
|
.delete-button {
|
|
|
|
|
width: 24px;
|
|
|
|
|
height: 24px;
|
|
|
|
|
border: none;
|
|
|
|
|
background: none;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
padding: 0;
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
color: #999;
|
|
|
|
|
transition: color 0.3s;
|
|
|
|
|
margin-left: 10px;
|
|
|
|
|
}
|
|
|
|
|
.delete-button:hover {
|
|
|
|
|
color: #f44336;
|
|
|
|
|
}
|
|
|
|
|
.delete-button svg {
|
|
|
|
|
width: 20px;
|
|
|
|
|
height: 20px;
|
|
|
|
|
}
|
|
|
|
|
/* 新建构型卡片样式 */
|
|
|
|
|
.config-card.new-config {
|
|
|
|
|
border: 2px dashed #5c6bc0;
|
|
|
|
|
background: #f8f9ff;
|
|
|
|
|
box-shadow: none;
|
|
|
|
|
min-height: auto;
|
|
|
|
|
height: 80px;
|
|
|
|
|
padding: 0 20px;
|
|
|
|
|
flex-direction: row;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
}
|
|
|
|
|
.config-card.new-config:hover {
|
|
|
|
|
background: #f0f2ff;
|
|
|
|
|
box-shadow: none;
|
|
|
|
|
}
|
|
|
|
|
.config-card.new-config .config-title {
|
|
|
|
|
color: #5c6bc0;
|
|
|
|
|
font-size: 18px;
|
|
|
|
|
margin: 0;
|
|
|
|
|
height: auto;
|
|
|
|
|
}
|
|
|
|
|
/* 构型编辑视图样式 */
|
|
|
|
|
.editor-container {
|
|
|
|
|
background: white;
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
|
|
|
|
padding: 30px;
|
|
|
|
|
height: calc(100vh - 160px);
|
|
|
|
|
overflow-y: auto;
|
|
|
|
|
max-width: 1600px;
|
|
|
|
|
margin: 0 auto;
|
|
|
|
|
box-sizing: border-box;
|
2025-05-28 16:20:01 +08:00
|
|
|
|
display: flex;
|
|
|
|
|
gap: 30px;
|
|
|
|
|
}
|
|
|
|
|
.editor-main {
|
|
|
|
|
flex: 1;
|
|
|
|
|
overflow-y: auto;
|
|
|
|
|
min-width: 0; /* 防止内容溢出 */
|
|
|
|
|
padding-right: 30px;
|
|
|
|
|
border-right: 1px solid #e8eaf6;
|
|
|
|
|
}
|
|
|
|
|
.editor-sidebar {
|
|
|
|
|
width: 300px;
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
gap: 20px;
|
|
|
|
|
position: sticky;
|
|
|
|
|
top: 30px;
|
|
|
|
|
height: fit-content;
|
|
|
|
|
}
|
|
|
|
|
.status-card {
|
|
|
|
|
background: #f8f9ff;
|
|
|
|
|
border: 1px solid #e8eaf6;
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
padding: 20px;
|
|
|
|
|
}
|
|
|
|
|
.status-card-title {
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
color: #5c6bc0;
|
|
|
|
|
margin-bottom: 15px;
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 8px;
|
|
|
|
|
}
|
|
|
|
|
.status-card-icon {
|
|
|
|
|
width: 24px;
|
|
|
|
|
height: 24px;
|
|
|
|
|
color: #5c6bc0;
|
|
|
|
|
}
|
|
|
|
|
.status-list {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
gap: 10px;
|
|
|
|
|
}
|
|
|
|
|
.status-item {
|
|
|
|
|
background: white;
|
|
|
|
|
border: 1px solid #e8eaf6;
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
padding: 10px;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
color: #333;
|
|
|
|
|
}
|
|
|
|
|
.status-item.empty {
|
|
|
|
|
color: #999;
|
|
|
|
|
font-style: italic;
|
2025-05-16 15:30:54 +08:00
|
|
|
|
}
|
|
|
|
|
.section {
|
|
|
|
|
margin-bottom: 30px;
|
|
|
|
|
padding-right: 10px;
|
|
|
|
|
}
|
|
|
|
|
.section-title {
|
|
|
|
|
font-size: 18px;
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
color: #5c6bc0;
|
|
|
|
|
margin-bottom: 15px;
|
|
|
|
|
padding-bottom: 8px;
|
|
|
|
|
border-bottom: 2px solid #e8eaf6;
|
|
|
|
|
}
|
|
|
|
|
.form-group {
|
|
|
|
|
margin-bottom: 15px;
|
|
|
|
|
}
|
|
|
|
|
.form-row {
|
|
|
|
|
display: grid;
|
|
|
|
|
grid-template-columns: repeat(4, 1fr);
|
|
|
|
|
gap: 25px;
|
|
|
|
|
margin-bottom: 15px;
|
|
|
|
|
}
|
|
|
|
|
.form-row-2 {
|
|
|
|
|
display: grid;
|
|
|
|
|
grid-template-columns: repeat(2, 1fr);
|
|
|
|
|
gap: 30px;
|
|
|
|
|
margin-bottom: 15px;
|
|
|
|
|
}
|
|
|
|
|
.form-row-3 {
|
|
|
|
|
display: grid;
|
|
|
|
|
grid-template-columns: repeat(3, 1fr);
|
|
|
|
|
gap: 25px;
|
|
|
|
|
margin-bottom: 15px;
|
|
|
|
|
}
|
|
|
|
|
.form-label {
|
|
|
|
|
display: block;
|
|
|
|
|
margin-bottom: 5px;
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
color: #555;
|
|
|
|
|
}
|
|
|
|
|
.form-input {
|
|
|
|
|
width: 100%;
|
|
|
|
|
padding: 8px 12px;
|
|
|
|
|
border: 1px solid #ddd;
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
}
|
|
|
|
|
.form-input:focus {
|
|
|
|
|
border-color: #5c6bc0;
|
|
|
|
|
outline: none;
|
|
|
|
|
box-shadow: 0 0 0 2px rgba(92, 107, 192, 0.2);
|
|
|
|
|
}
|
|
|
|
|
.form-input[readonly] {
|
|
|
|
|
background-color: #f5f5f5;
|
|
|
|
|
border-color: #e0e0e0;
|
|
|
|
|
color: #666;
|
|
|
|
|
cursor: not-allowed;
|
|
|
|
|
}
|
|
|
|
|
.form-input[readonly]:focus {
|
|
|
|
|
border-color: #e0e0e0;
|
|
|
|
|
box-shadow: none;
|
|
|
|
|
}
|
|
|
|
|
.form-textarea {
|
|
|
|
|
width: 100%;
|
|
|
|
|
padding: 8px 12px;
|
|
|
|
|
border: 1px solid #ddd;
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
min-height: 100px;
|
|
|
|
|
resize: vertical;
|
|
|
|
|
}
|
|
|
|
|
.form-textarea:focus {
|
|
|
|
|
border-color: #5c6bc0;
|
|
|
|
|
outline: none;
|
|
|
|
|
box-shadow: 0 0 0 2px rgba(92, 107, 192, 0.2);
|
|
|
|
|
}
|
|
|
|
|
.checkbox-group {
|
|
|
|
|
display: flex;
|
|
|
|
|
gap: 20px;
|
|
|
|
|
margin-top: 10px;
|
|
|
|
|
}
|
|
|
|
|
.checkbox-item {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 5px;
|
|
|
|
|
}
|
|
|
|
|
.checkbox-item input[type="checkbox"] {
|
|
|
|
|
width: 16px;
|
|
|
|
|
height: 16px;
|
|
|
|
|
}
|
|
|
|
|
.checkbox-item label {
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
color: #555;
|
|
|
|
|
}
|
|
|
|
|
.button-group {
|
|
|
|
|
display: flex;
|
|
|
|
|
gap: 10px;
|
|
|
|
|
margin-top: 20px;
|
|
|
|
|
}
|
|
|
|
|
.button {
|
|
|
|
|
padding: 8px 16px;
|
|
|
|
|
border: none;
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
transition: background-color 0.3s;
|
|
|
|
|
}
|
|
|
|
|
.button-primary {
|
|
|
|
|
background-color: #5c6bc0;
|
|
|
|
|
color: white;
|
|
|
|
|
}
|
|
|
|
|
.button-primary:hover {
|
|
|
|
|
background-color: #3949ab;
|
|
|
|
|
}
|
|
|
|
|
.button-secondary {
|
|
|
|
|
background-color: #e8eaf6;
|
|
|
|
|
color: #5c6bc0;
|
|
|
|
|
}
|
|
|
|
|
.button-secondary:hover {
|
|
|
|
|
background-color: #c5cae9;
|
|
|
|
|
}
|
|
|
|
|
.log-section {
|
|
|
|
|
display: grid;
|
|
|
|
|
grid-template-columns: repeat(2, 1fr);
|
|
|
|
|
gap: 30px;
|
|
|
|
|
}
|
|
|
|
|
.log-box {
|
|
|
|
|
background: #f5f5f5;
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
padding: 20px;
|
|
|
|
|
}
|
|
|
|
|
.log-box-title {
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
color: #5c6bc0;
|
|
|
|
|
margin-bottom: 15px;
|
|
|
|
|
}
|
|
|
|
|
</style>
|
|
|
|
|
<div class="config-development">
|
|
|
|
|
<div class="page-header">
|
|
|
|
|
<h2 class="page-title" id="pageTitle">机型</h2>
|
|
|
|
|
<div class="back-section" id="backSection" style="display: none;">
|
|
|
|
|
<a class="back-button" id="backButton">
|
|
|
|
|
<span class="back-icon">←</span>
|
|
|
|
|
<span>返回机型列表</span>
|
|
|
|
|
</a>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="config-dev-container" id="contentContainer">
|
|
|
|
|
<div class="loading">加载中...</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="editor-container" id="editorContainer" style="display: none;">
|
2025-05-28 16:20:01 +08:00
|
|
|
|
<div class="editor-main">
|
|
|
|
|
<div class="section">
|
|
|
|
|
<h3 class="section-title">基本信息</h3>
|
|
|
|
|
<div class="form-row-3">
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
<label class="form-label">操作系统</label>
|
|
|
|
|
<input type="text" class="form-input" id="osName" />
|
|
|
|
|
</div>
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
<label class="form-label">系统版本</label>
|
|
|
|
|
<input type="text" class="form-input" id="osVersion" />
|
|
|
|
|
</div>
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
<label class="form-label">RTX版本</label>
|
|
|
|
|
<input type="text" class="form-input" id="rtxVersion" />
|
|
|
|
|
</div>
|
2025-05-16 15:30:54 +08:00
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2025-05-28 16:20:01 +08:00
|
|
|
|
<div class="section">
|
|
|
|
|
<h3 class="section-title">系统配置</h3>
|
|
|
|
|
<div class="form-row-3">
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
<label class="form-label">CPU亲和性</label>
|
|
|
|
|
<input type="text" class="form-input" id="cpuAffinity" />
|
|
|
|
|
</div>
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
<label class="form-label">域ID</label>
|
|
|
|
|
<input type="text" class="form-input" id="domainId" />
|
|
|
|
|
</div>
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
<label class="form-label">工作路径</label>
|
|
|
|
|
<input type="text" class="form-input" id="workPath" />
|
|
|
|
|
</div>
|
2025-05-16 15:30:54 +08:00
|
|
|
|
</div>
|
2025-05-28 16:20:01 +08:00
|
|
|
|
<div class="form-row-3">
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
<label class="form-label">模型路径</label>
|
|
|
|
|
<input type="text" class="form-input" id="modelsPath" />
|
|
|
|
|
</div>
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
<label class="form-label">服务路径</label>
|
|
|
|
|
<input type="text" class="form-input" id="servicesPath" />
|
|
|
|
|
</div>
|
2025-05-16 15:30:54 +08:00
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2025-05-28 16:20:01 +08:00
|
|
|
|
<div class="section">
|
|
|
|
|
<h3 class="section-title">输出配置</h3>
|
|
|
|
|
<div class="log-section">
|
|
|
|
|
<div class="log-box">
|
|
|
|
|
<div class="log-box-title">控制台输出配置</div>
|
|
|
|
|
<div class="checkbox-group">
|
|
|
|
|
<div class="checkbox-item">
|
|
|
|
|
<input type="checkbox" id="consoleDebug" />
|
|
|
|
|
<label for="consoleDebug">Debug</label>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="checkbox-item">
|
|
|
|
|
<input type="checkbox" id="consoleInfo" />
|
|
|
|
|
<label for="consoleInfo">Info</label>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="checkbox-item">
|
|
|
|
|
<input type="checkbox" id="consoleWarning" />
|
|
|
|
|
<label for="consoleWarning">Warning</label>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="checkbox-item">
|
|
|
|
|
<input type="checkbox" id="consoleError" />
|
|
|
|
|
<label for="consoleError">Error</label>
|
|
|
|
|
</div>
|
2025-05-16 15:30:54 +08:00
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2025-05-28 16:20:01 +08:00
|
|
|
|
<div class="log-box">
|
|
|
|
|
<div class="log-box-title">日志输出配置</div>
|
|
|
|
|
<div class="checkbox-group">
|
|
|
|
|
<div class="checkbox-item">
|
|
|
|
|
<input type="checkbox" id="logDebug" />
|
|
|
|
|
<label for="logDebug">Debug</label>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="checkbox-item">
|
|
|
|
|
<input type="checkbox" id="logInfo" />
|
|
|
|
|
<label for="logInfo">Info</label>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="checkbox-item">
|
|
|
|
|
<input type="checkbox" id="logWarning" />
|
|
|
|
|
<label for="logWarning">Warning</label>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="checkbox-item">
|
|
|
|
|
<input type="checkbox" id="logError" />
|
|
|
|
|
<label for="logError">Error</label>
|
|
|
|
|
</div>
|
2025-05-16 15:30:54 +08:00
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2025-05-28 16:20:01 +08:00
|
|
|
|
<div class="button-group">
|
|
|
|
|
<button class="button button-primary" id="saveButton">保存构型</button>
|
|
|
|
|
<button class="button button-secondary" id="cancelButton">取消</button>
|
|
|
|
|
</div>
|
2025-05-16 15:30:54 +08:00
|
|
|
|
</div>
|
2025-05-28 16:20:01 +08:00
|
|
|
|
<div class="editor-sidebar">
|
|
|
|
|
<div class="status-card">
|
|
|
|
|
<div class="status-card-title" style="position:relative;">
|
|
|
|
|
加载模型
|
|
|
|
|
<img src="/assets/icons/png/plus_b.png" alt="添加模型组" title="添加模型组" id="addModelGroupBtn" style="position:absolute;top:0;right:0;width:20px;height:20px;cursor:pointer;z-index:2;" />
|
|
|
|
|
</div>
|
|
|
|
|
<div class="status-list" id="pendingModelsList">
|
|
|
|
|
<div class="status-item empty">暂无待加载模型</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="status-card">
|
|
|
|
|
<div class="status-card-title">
|
|
|
|
|
加载服务
|
|
|
|
|
</div>
|
|
|
|
|
<div class="status-list" id="pendingServicesList">
|
|
|
|
|
<div class="status-item empty">暂无待加载服务</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2025-05-16 15:30:54 +08:00
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
// 绑定事件
|
|
|
|
|
this.shadowRoot.getElementById('backButton').addEventListener('click', (e) => {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
if (this.currentView === 'editor') {
|
|
|
|
|
this.showConfigurations(this.selectedPlane);
|
|
|
|
|
} else {
|
|
|
|
|
this.showPlanes();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 绑定编辑器按钮事件
|
|
|
|
|
this.shadowRoot.getElementById('saveButton').addEventListener('click', () => this.saveConfiguration());
|
|
|
|
|
this.shadowRoot.getElementById('cancelButton').addEventListener('click', () => {
|
|
|
|
|
this.showConfigurations(this.selectedPlane);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 添加输入验证
|
|
|
|
|
this.shadowRoot.getElementById('cpuAffinity').addEventListener('input', (e) => {
|
|
|
|
|
// 只允许数字和英文逗号
|
|
|
|
|
e.target.value = e.target.value.replace(/[^0-9,]/g, '');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
this.shadowRoot.getElementById('domainId').addEventListener('input', (e) => {
|
|
|
|
|
// 只允许数字
|
|
|
|
|
e.target.value = e.target.value.replace(/[^0-9]/g, '');
|
|
|
|
|
// 限制范围在0-232之间
|
|
|
|
|
const value = parseInt(e.target.value);
|
|
|
|
|
if (value > 232) {
|
|
|
|
|
e.target.value = '232';
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 获取机型数据
|
|
|
|
|
this.loadPlanes();
|
2025-05-28 16:20:01 +08:00
|
|
|
|
|
|
|
|
|
// 渲染sidebar后,绑定添加模型组按钮事件
|
|
|
|
|
const addModelGroupBtn = this.shadowRoot.getElementById('addModelGroupBtn');
|
|
|
|
|
if (addModelGroupBtn) {
|
|
|
|
|
addModelGroupBtn.onclick = () => this.showAddModelGroupDialog();
|
|
|
|
|
}
|
2025-05-16 15:30:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @method loadPlanes
|
|
|
|
|
* @description 从服务器获取机型数据并更新UI
|
|
|
|
|
*/
|
|
|
|
|
async loadPlanes() {
|
|
|
|
|
try {
|
|
|
|
|
const response = await fetch('/api/planes');
|
|
|
|
|
if (!response.ok) {
|
|
|
|
|
throw new Error('获取机型数据失败');
|
|
|
|
|
}
|
|
|
|
|
const planes = await response.json();
|
|
|
|
|
this.renderPlanes(planes);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('加载机型数据失败:', error);
|
|
|
|
|
this.showError('加载机型数据失败,请稍后重试');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @method loadConfigurations
|
|
|
|
|
* @description 从服务器获取构型数据并更新UI
|
|
|
|
|
* @param {string} planeName - 机型名称
|
|
|
|
|
*/
|
|
|
|
|
async loadConfigurations(planeName) {
|
|
|
|
|
try {
|
|
|
|
|
const response = await fetch(`/api/configurations?plane=${encodeURIComponent(planeName)}`);
|
|
|
|
|
if (!response.ok) {
|
|
|
|
|
throw new Error('获取构型数据失败');
|
|
|
|
|
}
|
|
|
|
|
const configs = await response.json();
|
|
|
|
|
this.renderConfigurations(configs, planeName);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('加载构型数据失败:', error);
|
|
|
|
|
this.showError('加载构型数据失败,请稍后重试');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @method renderPlanes
|
|
|
|
|
* @description 渲染机型卡片
|
|
|
|
|
* @param {Array} planes - 机型数据数组
|
|
|
|
|
*/
|
|
|
|
|
renderPlanes(planes) {
|
|
|
|
|
const container = this.shadowRoot.getElementById('contentContainer');
|
|
|
|
|
if (!planes || planes.length === 0) {
|
|
|
|
|
container.innerHTML = '<div class="error">暂无机型数据</div>';
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
container.innerHTML = planes.map(plane => `
|
|
|
|
|
<div class="config-card" data-plane="${plane.PlaneName}">
|
|
|
|
|
<div class="icon-container">
|
|
|
|
|
${plane.Icon ? `<img class="plane-icon" src="${plane.Icon}" alt="${plane.PlaneName}">` : ''}
|
|
|
|
|
</div>
|
|
|
|
|
<div class="text-container">
|
|
|
|
|
<div class="config-title">${plane.PlaneName || '未知机型'}</div>
|
|
|
|
|
<div class="config-desc">${plane.Description || '暂无描述'}</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
`).join('');
|
|
|
|
|
|
|
|
|
|
// 添加点击事件
|
|
|
|
|
container.querySelectorAll('.config-card').forEach(card => {
|
|
|
|
|
card.addEventListener('click', () => {
|
|
|
|
|
const planeName = card.dataset.plane;
|
|
|
|
|
this.showConfigurations(planeName);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @method showBaseConfigDialog
|
|
|
|
|
* @description 显示基准构型选择对话框
|
|
|
|
|
* @param {Array} configs - 构型数据数组
|
|
|
|
|
* @param {string} planeName - 机型名称
|
|
|
|
|
* @returns {Promise<Object|undefined>} 返回选择的基准构型或undefined(表示不使用基准构型)
|
|
|
|
|
*/
|
|
|
|
|
showBaseConfigDialog(configs, planeName) {
|
|
|
|
|
return new Promise((resolve) => {
|
|
|
|
|
const dialog = document.createElement('div');
|
|
|
|
|
dialog.className = 'base-config-dialog';
|
|
|
|
|
dialog.innerHTML = `
|
|
|
|
|
<style>
|
|
|
|
|
.base-config-dialog {
|
|
|
|
|
position: fixed;
|
|
|
|
|
top: 0;
|
|
|
|
|
left: 0;
|
|
|
|
|
right: 0;
|
|
|
|
|
bottom: 0;
|
|
|
|
|
background: rgba(0, 0, 0, 0.5);
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
z-index: 1000;
|
|
|
|
|
}
|
|
|
|
|
.dialog-content {
|
|
|
|
|
background: white;
|
|
|
|
|
padding: 20px;
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
width: 400px;
|
|
|
|
|
max-width: 90%;
|
|
|
|
|
}
|
|
|
|
|
.dialog-title {
|
|
|
|
|
font-size: 18px;
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
color: #333;
|
|
|
|
|
margin-bottom: 15px;
|
|
|
|
|
}
|
|
|
|
|
.select-container {
|
|
|
|
|
margin-bottom: 20px;
|
|
|
|
|
}
|
|
|
|
|
.select-label {
|
|
|
|
|
display: block;
|
|
|
|
|
margin-bottom: 8px;
|
|
|
|
|
color: #555;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
}
|
|
|
|
|
.select-input {
|
|
|
|
|
width: 100%;
|
|
|
|
|
padding: 8px 12px;
|
|
|
|
|
border: 1px solid #ddd;
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
color: #333;
|
|
|
|
|
background-color: white;
|
2025-05-28 16:20:01 +08:00
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
}
|
|
|
|
|
.select-input:focus {
|
|
|
|
|
border-color: #5c6bc0;
|
|
|
|
|
outline: none;
|
|
|
|
|
box-shadow: 0 0 0 2px rgba(92, 107, 192, 0.2);
|
|
|
|
|
}
|
|
|
|
|
.select-input[type="text"] {
|
|
|
|
|
cursor: text;
|
|
|
|
|
}
|
|
|
|
|
.select-input[type="text"]::placeholder {
|
|
|
|
|
color: #999;
|
|
|
|
|
}
|
|
|
|
|
.select-input[type="select"] {
|
2025-05-16 15:30:54 +08:00
|
|
|
|
cursor: pointer;
|
|
|
|
|
appearance: none;
|
|
|
|
|
-webkit-appearance: none;
|
|
|
|
|
-moz-appearance: none;
|
|
|
|
|
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6 9 12 15 18 9'%3e%3c/polyline%3e%3c/svg%3e");
|
|
|
|
|
background-repeat: no-repeat;
|
|
|
|
|
background-position: right 8px center;
|
|
|
|
|
background-size: 16px;
|
|
|
|
|
}
|
|
|
|
|
.dialog-buttons {
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: flex-end;
|
|
|
|
|
gap: 10px;
|
|
|
|
|
}
|
|
|
|
|
.dialog-button {
|
|
|
|
|
padding: 8px 16px;
|
|
|
|
|
border: none;
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
}
|
|
|
|
|
.button-primary {
|
|
|
|
|
background: #5c6bc0;
|
|
|
|
|
color: white;
|
|
|
|
|
}
|
|
|
|
|
.button-secondary {
|
|
|
|
|
background: #e8eaf6;
|
|
|
|
|
color: #5c6bc0;
|
|
|
|
|
}
|
|
|
|
|
</style>
|
|
|
|
|
<div class="dialog-content">
|
2025-05-28 16:20:01 +08:00
|
|
|
|
<div class="dialog-title">新建构型</div>
|
|
|
|
|
<div class="select-container">
|
|
|
|
|
<label class="select-label">构型名称:</label>
|
|
|
|
|
<input type="text" class="select-input" id="newConfName" placeholder="请输入构型名称" />
|
|
|
|
|
</div>
|
2025-05-16 15:30:54 +08:00
|
|
|
|
<div class="select-container">
|
|
|
|
|
<label class="select-label">请选择基准构型:</label>
|
|
|
|
|
<select class="select-input" id="baseConfigSelect">
|
|
|
|
|
<option value="none">不使用基准构型</option>
|
|
|
|
|
${configs.map(config => `
|
|
|
|
|
<option value="${config.ConfID}">${config.ConfName}</option>
|
|
|
|
|
`).join('')}
|
|
|
|
|
</select>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="dialog-buttons">
|
|
|
|
|
<button class="dialog-button button-secondary" id="cancelButton">取消</button>
|
|
|
|
|
<button class="dialog-button button-primary" id="confirmButton">确认</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
const select = dialog.querySelector('#baseConfigSelect');
|
2025-05-28 16:20:01 +08:00
|
|
|
|
const newConfName = dialog.querySelector('#newConfName');
|
2025-05-16 15:30:54 +08:00
|
|
|
|
|
|
|
|
|
dialog.querySelector('#cancelButton').addEventListener('click', () => {
|
|
|
|
|
document.body.removeChild(dialog);
|
|
|
|
|
resolve(null); // 取消时返回null
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
dialog.querySelector('#confirmButton').addEventListener('click', () => {
|
|
|
|
|
const selectedValue = select.value;
|
2025-05-28 16:20:01 +08:00
|
|
|
|
const confName = newConfName.value.trim();
|
|
|
|
|
|
|
|
|
|
if (!confName) {
|
|
|
|
|
alert('请输入构型名称');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-16 15:30:54 +08:00
|
|
|
|
const selectedConfig = selectedValue === 'none' ?
|
|
|
|
|
undefined : // 选择"不使用基准构型"时返回undefined
|
|
|
|
|
configs.find(c => c.ConfID === parseInt(selectedValue));
|
2025-05-28 16:20:01 +08:00
|
|
|
|
|
2025-05-16 15:30:54 +08:00
|
|
|
|
document.body.removeChild(dialog);
|
2025-05-28 16:20:01 +08:00
|
|
|
|
resolve({
|
|
|
|
|
...selectedConfig,
|
|
|
|
|
ConfName: confName
|
|
|
|
|
});
|
2025-05-16 15:30:54 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
document.body.appendChild(dialog);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @method checkUserAccessLevel
|
|
|
|
|
* @description 检查用户访问权限级别
|
|
|
|
|
*/
|
|
|
|
|
async checkUserAccessLevel() {
|
|
|
|
|
try {
|
|
|
|
|
const response = await fetch('/api/check-auth', {
|
|
|
|
|
credentials: 'include'
|
|
|
|
|
});
|
|
|
|
|
const result = await response.json();
|
|
|
|
|
|
|
|
|
|
if (result.success) {
|
|
|
|
|
this.userAccessLevel = parseInt(result.user.access_level);
|
|
|
|
|
// 重新渲染构型列表以更新删除按钮的显示状态
|
|
|
|
|
if (this.currentView === 'configurations' && this.selectedPlane) {
|
|
|
|
|
this.loadConfigurations(this.selectedPlane);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('获取用户权限失败:', error);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @method renderConfigurations
|
|
|
|
|
* @description 渲染构型卡片
|
|
|
|
|
* @param {Array} configs - 构型数据数组
|
|
|
|
|
* @param {string} planeName - 机型名称
|
|
|
|
|
*/
|
|
|
|
|
renderConfigurations(configs, planeName) {
|
|
|
|
|
const container = this.shadowRoot.getElementById('contentContainer');
|
|
|
|
|
if (!configs || configs.length === 0) {
|
|
|
|
|
container.innerHTML = '<div class="error">暂无构型数据</div>';
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 添加新建构型卡片(根据权限显示)
|
|
|
|
|
container.innerHTML = `
|
|
|
|
|
${this.userAccessLevel >= 2 ? `
|
|
|
|
|
<div class="config-card new-config" id="newConfigCard">
|
|
|
|
|
<div class="config-title">+ 新建构型</div>
|
|
|
|
|
</div>
|
|
|
|
|
` : ''}
|
|
|
|
|
${configs.map(config => `
|
|
|
|
|
<div class="config-card configuration" data-config-id="${config.ConfID}">
|
|
|
|
|
<div class="config-title">${config.ConfName || '未知构型'}</div>
|
|
|
|
|
${this.userAccessLevel >= 2 ? `
|
|
|
|
|
<button class="delete-button" data-config-id="${config.ConfID}" title="删除构型">
|
|
|
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
|
|
<path d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
|
|
|
|
|
</svg>
|
|
|
|
|
</button>
|
|
|
|
|
` : ''}
|
|
|
|
|
</div>
|
|
|
|
|
`).join('')}
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
// 添加新建构型卡片点击事件(仅在权限足够时添加)
|
|
|
|
|
if (this.userAccessLevel >= 2) {
|
|
|
|
|
container.querySelector('#newConfigCard')?.addEventListener('click', async () => {
|
|
|
|
|
const baseConfig = await this.showBaseConfigDialog(configs, planeName);
|
|
|
|
|
if (baseConfig !== null) {
|
|
|
|
|
const defaultConfig = {
|
|
|
|
|
ConfName: '',
|
|
|
|
|
OSName: baseConfig?.OSName || '',
|
|
|
|
|
OSVersion: baseConfig?.OSVersion || '',
|
|
|
|
|
RTXVersion: baseConfig?.RTXVersion || '',
|
|
|
|
|
CPUAffinity: baseConfig?.CPUAffinity || '',
|
|
|
|
|
DomainID: baseConfig?.DomainID || '',
|
|
|
|
|
WorkPath: baseConfig?.WorkPath || '',
|
|
|
|
|
ModelsPath: baseConfig?.ModelsPath || '',
|
|
|
|
|
ServicesPath: baseConfig?.ServicesPath || '',
|
|
|
|
|
ConsoleDebug: baseConfig?.ConsoleDebug ?? 1,
|
|
|
|
|
ConsoleInfo: baseConfig?.ConsoleInfo ?? 1,
|
|
|
|
|
ConsoleWarning: baseConfig?.ConsoleWarning ?? 1,
|
|
|
|
|
ConsoleError: baseConfig?.ConsoleError ?? 1,
|
|
|
|
|
LogDebug: baseConfig?.LogDebug ?? 0,
|
|
|
|
|
LogInfo: baseConfig?.LogInfo ?? 1,
|
|
|
|
|
LogWarning: baseConfig?.LogWarning ?? 1,
|
2025-05-16 16:42:31 +08:00
|
|
|
|
LogError: baseConfig?.LogError ?? 1,
|
|
|
|
|
sourceConfId: baseConfig?.ConfID // 添加源构型ID
|
2025-05-16 15:30:54 +08:00
|
|
|
|
};
|
|
|
|
|
this.showEditor(defaultConfig, planeName, true);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 添加构型卡片点击事件
|
|
|
|
|
container.querySelectorAll('.config-card:not(.new-config)').forEach(card => {
|
|
|
|
|
card.addEventListener('click', (e) => {
|
|
|
|
|
// 如果点击的是删除按钮,不触发编辑
|
|
|
|
|
if (e.target.closest('.delete-button')) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const configId = parseInt(card.dataset.configId, 10);
|
|
|
|
|
const config = configs.find(c => c.ConfID === configId);
|
|
|
|
|
if (config) {
|
|
|
|
|
this.showEditor(config, planeName, false);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 添加删除按钮点击事件
|
|
|
|
|
container.querySelectorAll('.delete-button').forEach(button => {
|
|
|
|
|
button.addEventListener('click', async (e) => {
|
|
|
|
|
e.stopPropagation(); // 阻止事件冒泡
|
|
|
|
|
|
|
|
|
|
// 再次检查权限
|
|
|
|
|
if (this.userAccessLevel < 2) {
|
|
|
|
|
alert('您没有删除构型的权限');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const configId = parseInt(button.dataset.configId, 10);
|
|
|
|
|
const config = configs.find(c => c.ConfID === configId);
|
|
|
|
|
|
2025-05-16 16:42:31 +08:00
|
|
|
|
if (config && confirm(`确定要删除构型"${config.ConfName}"吗?\n注意:删除后将无法恢复,且会同时删除该构型下的所有模型组、模型和服务。`)) {
|
2025-05-16 15:30:54 +08:00
|
|
|
|
try {
|
|
|
|
|
const response = await fetch(`/api/configurations/${configId}`, {
|
|
|
|
|
method: 'DELETE',
|
|
|
|
|
credentials: 'include'
|
|
|
|
|
});
|
|
|
|
|
|
2025-05-16 16:42:31 +08:00
|
|
|
|
const responseData = await response.json();
|
|
|
|
|
|
2025-05-16 15:30:54 +08:00
|
|
|
|
if (!response.ok) {
|
2025-05-16 16:42:31 +08:00
|
|
|
|
throw new Error(responseData.error || '删除构型失败');
|
2025-05-16 15:30:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 重新加载构型列表
|
|
|
|
|
this.loadConfigurations(planeName);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('删除构型失败:', error);
|
2025-05-16 16:42:31 +08:00
|
|
|
|
alert(error.message || '删除构型失败,请稍后重试');
|
2025-05-16 15:30:54 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @method showEditor
|
|
|
|
|
* @description 显示构型编辑器
|
|
|
|
|
* @param {Object} configuration - 构型数据
|
|
|
|
|
* @param {string} planeName - 机型名称
|
|
|
|
|
* @param {boolean} isNew - 是否是新建构型
|
|
|
|
|
*/
|
2025-05-28 16:20:01 +08:00
|
|
|
|
async showEditor(configuration, planeName, isNew) {
|
2025-05-16 15:30:54 +08:00
|
|
|
|
this.currentView = 'editor';
|
|
|
|
|
this.selectedConfiguration = configuration;
|
|
|
|
|
this.isNewConfiguration = isNew;
|
|
|
|
|
|
|
|
|
|
// 更新UI
|
|
|
|
|
const pageTitle = this.shadowRoot.getElementById('pageTitle');
|
|
|
|
|
const backSection = this.shadowRoot.getElementById('backSection');
|
|
|
|
|
const backButtonText = this.shadowRoot.querySelector('#backButton span:last-child');
|
|
|
|
|
const contentContainer = this.shadowRoot.getElementById('contentContainer');
|
|
|
|
|
const editorContainer = this.shadowRoot.getElementById('editorContainer');
|
|
|
|
|
|
|
|
|
|
if (!pageTitle || !backSection || !contentContainer || !editorContainer) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-16 16:42:31 +08:00
|
|
|
|
pageTitle.textContent = isNew ? `${planeName} - 新建构型` : `${planeName} - ${configuration.ConfName} 构型`;
|
2025-05-16 15:30:54 +08:00
|
|
|
|
backSection.style.display = 'block';
|
|
|
|
|
backButtonText.textContent = '返回构型列表';
|
|
|
|
|
contentContainer.style.display = 'none';
|
2025-05-28 16:20:01 +08:00
|
|
|
|
editorContainer.style.display = 'flex';
|
2025-05-16 15:30:54 +08:00
|
|
|
|
|
|
|
|
|
// 填充其他表单数据
|
|
|
|
|
this.shadowRoot.getElementById('osName').value = configuration.OSName || '';
|
|
|
|
|
this.shadowRoot.getElementById('osVersion').value = configuration.OSVersion || '';
|
|
|
|
|
this.shadowRoot.getElementById('rtxVersion').value = configuration.RTXVersion || '';
|
|
|
|
|
this.shadowRoot.getElementById('cpuAffinity').value = configuration.CPUAffinity || '';
|
|
|
|
|
this.shadowRoot.getElementById('domainId').value = configuration.DomainID !== undefined ? Math.floor(configuration.DomainID) : '';
|
|
|
|
|
this.shadowRoot.getElementById('modelsPath').value = configuration.ModelsPath || '';
|
|
|
|
|
this.shadowRoot.getElementById('servicesPath').value = configuration.ServicesPath || '';
|
|
|
|
|
this.shadowRoot.getElementById('workPath').value = configuration.WorkPath || '';
|
|
|
|
|
|
|
|
|
|
// 设置复选框状态
|
|
|
|
|
this.shadowRoot.getElementById('consoleDebug').checked = configuration.ConsoleDebug === 1;
|
|
|
|
|
this.shadowRoot.getElementById('consoleInfo').checked = configuration.ConsoleInfo === 1;
|
|
|
|
|
this.shadowRoot.getElementById('consoleWarning').checked = configuration.ConsoleWarning === 1;
|
|
|
|
|
this.shadowRoot.getElementById('consoleError').checked = configuration.ConsoleError === 1;
|
|
|
|
|
this.shadowRoot.getElementById('logDebug').checked = configuration.LogDebug === 1;
|
|
|
|
|
this.shadowRoot.getElementById('logInfo').checked = configuration.LogInfo === 1;
|
|
|
|
|
this.shadowRoot.getElementById('logWarning').checked = configuration.LogWarning === 1;
|
|
|
|
|
this.shadowRoot.getElementById('logError').checked = configuration.LogError === 1;
|
2025-05-28 16:20:01 +08:00
|
|
|
|
|
|
|
|
|
// 在加载模型卡片右上角增加添加模型组按钮
|
|
|
|
|
const modelCard = this.shadowRoot.querySelectorAll('.editor-sidebar .status-card')[0];
|
|
|
|
|
const modelCardTitle = modelCard.querySelector('.status-card-title');
|
|
|
|
|
if (modelCard && modelCardTitle && this.userAccessLevel >= 2) {
|
|
|
|
|
// 移除已存在的按钮(如果有)
|
|
|
|
|
const existingBtn = this.shadowRoot.getElementById('addModelGroupBtn');
|
|
|
|
|
if (existingBtn) {
|
|
|
|
|
existingBtn.remove();
|
|
|
|
|
}
|
|
|
|
|
// 添加新按钮
|
|
|
|
|
const addBtn = document.createElement('img');
|
|
|
|
|
addBtn.src = '/assets/icons/png/plus_b.png';
|
|
|
|
|
addBtn.alt = '添加模型组';
|
|
|
|
|
addBtn.title = '添加模型组';
|
|
|
|
|
addBtn.id = 'addModelGroupBtn';
|
|
|
|
|
addBtn.style.cssText = 'width:20px;height:20px;cursor:pointer;position:absolute;top:18px;right:18px;z-index:2;';
|
|
|
|
|
modelCard.style.position = 'relative';
|
|
|
|
|
modelCard.appendChild(addBtn);
|
|
|
|
|
addBtn.addEventListener('click', () => this.showAddModelGroupDialog());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 在加载服务卡片右上角增加添加按钮
|
|
|
|
|
const serviceCard = this.shadowRoot.querySelectorAll('.editor-sidebar .status-card')[1];
|
|
|
|
|
const serviceCardTitle = serviceCard.querySelector('.status-card-title');
|
|
|
|
|
if (serviceCard && serviceCardTitle && this.userAccessLevel >= 2) {
|
|
|
|
|
// 移除已存在的按钮(如果有)
|
|
|
|
|
const existingBtn = this.shadowRoot.getElementById('addServiceBtn');
|
|
|
|
|
if (existingBtn) {
|
|
|
|
|
existingBtn.remove();
|
|
|
|
|
}
|
|
|
|
|
// 添加新按钮
|
|
|
|
|
const addBtn = document.createElement('img');
|
|
|
|
|
addBtn.src = '/assets/icons/png/plus_b.png';
|
|
|
|
|
addBtn.alt = '添加服务';
|
|
|
|
|
addBtn.title = '添加服务';
|
|
|
|
|
addBtn.id = 'addServiceBtn';
|
|
|
|
|
addBtn.style.cssText = 'width:20px;height:20px;cursor:pointer;position:absolute;top:18px;right:18px;z-index:2;';
|
|
|
|
|
serviceCard.style.position = 'relative';
|
|
|
|
|
serviceCard.appendChild(addBtn);
|
|
|
|
|
addBtn.addEventListener('click', () => this.showAddServiceDialog());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 如果不是新建构型,获取模型组数据和服务列表
|
|
|
|
|
if (!isNew && configuration.ConfID) {
|
|
|
|
|
try {
|
|
|
|
|
// 并行请求模型组数据和服务列表
|
|
|
|
|
const [modelGroupsResponse, servicesResponse] = await Promise.all([
|
|
|
|
|
fetch(`/api/configurations/${configuration.ConfID}/model-groups`),
|
|
|
|
|
fetch(`/api/configurations/${configuration.ConfID}/services`)
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
if (!modelGroupsResponse.ok) {
|
|
|
|
|
throw new Error('获取模型组数据失败');
|
|
|
|
|
}
|
|
|
|
|
const modelGroups = await modelGroupsResponse.json();
|
|
|
|
|
|
|
|
|
|
// 更新加载模型卡片
|
|
|
|
|
const pendingModelsList = this.shadowRoot.getElementById('pendingModelsList');
|
|
|
|
|
if (modelGroups && modelGroups.length > 0) {
|
|
|
|
|
// 获取每个模型组下的模型
|
|
|
|
|
const modelGroupsWithModels = await Promise.all(modelGroups.map(async (group) => {
|
|
|
|
|
try {
|
|
|
|
|
const modelsResponse = await fetch(`/api/model-groups/${group.GroupID}/models`);
|
|
|
|
|
if (!modelsResponse.ok) {
|
|
|
|
|
throw new Error(`获取模型组 ${group.GroupName} 的模型失败`);
|
|
|
|
|
}
|
|
|
|
|
const models = await modelsResponse.json();
|
|
|
|
|
return { ...group, models };
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error(`获取模型组 ${group.GroupName} 的模型失败:`, error);
|
|
|
|
|
return { ...group, models: [] };
|
|
|
|
|
}
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
pendingModelsList.innerHTML = modelGroupsWithModels.map(group => `
|
|
|
|
|
<div class="status-item" data-group-id="${group.GroupID}" style="position:relative;">
|
|
|
|
|
<div style="font-weight: 500; margin-bottom: 4px; display: flex; align-items: center; justify-content: space-between;">
|
|
|
|
|
<span>${group.GroupName}</span>
|
|
|
|
|
${this.userAccessLevel >= 2 ? `
|
|
|
|
|
<span style="display: flex; gap: 8px; align-items: center; position: absolute; right: 12px; top: 10px;">
|
|
|
|
|
<img src="/assets/icons/png/plus_b.png" alt="添加模型" title="添加模型" class="group-add-btn" style="width:18px;height:18px;cursor:pointer;" data-group-id="${group.GroupID}" />
|
|
|
|
|
<img src="/assets/icons/png/sliders_b.png" alt="编辑" title="编辑模型组" class="group-edit-btn" style="width:18px;height:18px;cursor:pointer;" data-group-id="${group.GroupID}" />
|
|
|
|
|
<img src="/assets/icons/png/delete_b.png" alt="删除" title="删除模型组" class="group-delete-btn" style="width:18px;height:18px;cursor:pointer;" data-group-id="${group.GroupID}" />
|
|
|
|
|
</span>
|
|
|
|
|
` : ''}
|
|
|
|
|
</div>
|
|
|
|
|
<div style="font-size: 12px; color: #666; margin-bottom: 8px;">
|
|
|
|
|
频率:${group.Freq || 0} Hz / 优先级:${group.Priority || 0} / 亲和性:${group.CPUAff || '无'}
|
|
|
|
|
</div>
|
|
|
|
|
<div style="margin-top: 8px; border-top: 1px solid #e8eaf6; padding-top: 8px;">
|
|
|
|
|
${group.models && group.models.length > 0 ?
|
|
|
|
|
group.models.map(model => `
|
|
|
|
|
<div style=\"margin-bottom: 6px; padding: 4px 8px; background: #f8f9ff; border-radius: 4px; display: flex; align-items: center; justify-content: space-between;\">
|
|
|
|
|
<div style=\"font-size: 14px;\">${model.ModelName || '未知模型'} ${model.ModelVersion || '未知版本'}</div>
|
|
|
|
|
${this.userAccessLevel >= 2 ? `
|
|
|
|
|
<img src="/assets/icons/png/delete_b.png" alt="删除" title="删除" class="model-delete-btn" style="width:20px;height:20px;cursor:pointer;margin-left:8px;" data-group-id="${group.GroupID}" data-class-name="${model.ClassName}" />
|
|
|
|
|
` : ''}
|
|
|
|
|
</div>
|
|
|
|
|
`).join('')
|
|
|
|
|
: '<div style=\"font-size: 12px; color: #999; font-style: italic;\">暂无模型</div>'
|
|
|
|
|
}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
`).join('');
|
|
|
|
|
|
|
|
|
|
// 绑定模型组按钮事件
|
|
|
|
|
if (this.userAccessLevel >= 2) {
|
|
|
|
|
// 添加模型按钮
|
|
|
|
|
this.shadowRoot.querySelectorAll('.group-add-btn').forEach(btn => {
|
|
|
|
|
btn.addEventListener('click', (e) => {
|
|
|
|
|
const groupId = btn.getAttribute('data-group-id');
|
|
|
|
|
this.onAddModel && this.onAddModel(groupId);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
// 编辑模型组按钮
|
|
|
|
|
this.shadowRoot.querySelectorAll('.group-edit-btn').forEach(btn => {
|
|
|
|
|
btn.addEventListener('click', (e) => {
|
|
|
|
|
const groupId = btn.getAttribute('data-group-id');
|
|
|
|
|
const group = modelGroupsWithModels.find(g => String(g.GroupID) === String(groupId));
|
|
|
|
|
if (group) this.showEditGroupDialog(group);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
// 删除模型组按钮
|
|
|
|
|
this.shadowRoot.querySelectorAll('.group-delete-btn').forEach(btn => {
|
|
|
|
|
btn.addEventListener('click', async (e) => {
|
|
|
|
|
const groupId = btn.getAttribute('data-group-id');
|
|
|
|
|
if (!groupId) return;
|
|
|
|
|
if (!confirm('确定要删除该模型组吗?')) return;
|
|
|
|
|
try {
|
|
|
|
|
const resp = await fetch(`/api/model-groups/${groupId}`, {
|
|
|
|
|
method: 'DELETE',
|
|
|
|
|
credentials: 'include'
|
|
|
|
|
});
|
|
|
|
|
if (!resp.ok) {
|
|
|
|
|
const data = await resp.json();
|
|
|
|
|
throw new Error(data.error || '删除失败');
|
|
|
|
|
}
|
|
|
|
|
this.showEditor(this.selectedConfiguration, this.selectedPlane, false);
|
|
|
|
|
} catch (err) {
|
|
|
|
|
alert('删除失败:' + err.message);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
// 删除模型按钮
|
|
|
|
|
this.shadowRoot.querySelectorAll('.model-delete-btn').forEach(btn => {
|
|
|
|
|
btn.addEventListener('click', async (e) => {
|
|
|
|
|
e.stopPropagation();
|
|
|
|
|
const groupId = btn.getAttribute('data-group-id');
|
|
|
|
|
const className = btn.getAttribute('data-class-name');
|
|
|
|
|
if (!groupId || !className) return;
|
|
|
|
|
if (!confirm('确定要删除该模型吗?')) return;
|
|
|
|
|
try {
|
|
|
|
|
const resp = await fetch(`/api/model-groups/${groupId}/models/${encodeURIComponent(className)}`, {
|
|
|
|
|
method: 'DELETE',
|
|
|
|
|
credentials: 'include'
|
|
|
|
|
});
|
|
|
|
|
if (!resp.ok) {
|
|
|
|
|
const data = await resp.json();
|
|
|
|
|
throw new Error(data.error || '删除失败');
|
|
|
|
|
}
|
|
|
|
|
this.showEditor(this.selectedConfiguration, this.selectedPlane, false);
|
|
|
|
|
} catch (err) {
|
|
|
|
|
alert('删除失败:' + err.message);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
pendingModelsList.innerHTML = '<div class="status-item empty">暂无待加载模型</div>';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 更新服务列表
|
|
|
|
|
const pendingServicesList = this.shadowRoot.getElementById('pendingServicesList');
|
|
|
|
|
if (!servicesResponse.ok) {
|
|
|
|
|
throw new Error('获取服务列表失败');
|
|
|
|
|
}
|
|
|
|
|
const services = await servicesResponse.json();
|
|
|
|
|
if (services && services.length > 0) {
|
|
|
|
|
pendingServicesList.innerHTML = services.map(service => `
|
|
|
|
|
<div class="status-item">
|
|
|
|
|
<div style="margin-bottom: 6px; padding: 4px 8px; background: #f8f9ff; border-radius: 4px; display: flex; align-items: center; justify-content: space-between;">
|
|
|
|
|
<div style="font-size: 14px;">${service.ServiceName || service.ClassName || '未知服务'} ${service.ServiceVersion || '未知'}</div>
|
|
|
|
|
${this.userAccessLevel >= 2 ? `
|
|
|
|
|
<img src="/assets/icons/png/delete_b.png" alt="删除" title="删除" class="service-delete-btn" style="width:20px;height:20px;cursor:pointer;margin-left:8px;" data-service-name="${service.ClassName}" />
|
|
|
|
|
` : ''}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
`).join('');
|
|
|
|
|
|
|
|
|
|
// 绑定服务删除按钮事件
|
|
|
|
|
if (this.userAccessLevel >= 2) {
|
|
|
|
|
this.shadowRoot.querySelectorAll('.service-delete-btn').forEach(btn => {
|
|
|
|
|
btn.addEventListener('click', async (e) => {
|
|
|
|
|
e.stopPropagation();
|
|
|
|
|
const serviceName = btn.getAttribute('data-service-name');
|
|
|
|
|
if (!serviceName) return;
|
|
|
|
|
if (!confirm('确定要删除该服务吗?')) return;
|
|
|
|
|
try {
|
|
|
|
|
const resp = await fetch(`/api/configurations/${configuration.ConfID}/services/${encodeURIComponent(serviceName)}`, {
|
|
|
|
|
method: 'DELETE',
|
|
|
|
|
credentials: 'include'
|
|
|
|
|
});
|
|
|
|
|
if (!resp.ok) {
|
|
|
|
|
const data = await resp.json();
|
|
|
|
|
throw new Error(data.error || '删除失败');
|
|
|
|
|
}
|
|
|
|
|
this.showEditor(this.selectedConfiguration, this.selectedPlane, false);
|
|
|
|
|
} catch (err) {
|
|
|
|
|
alert('删除失败:' + err.message);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
pendingServicesList.innerHTML = '<div class="status-item empty">暂无待加载服务</div>';
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('获取数据失败:', error);
|
|
|
|
|
const pendingModelsList = this.shadowRoot.getElementById('pendingModelsList');
|
|
|
|
|
const pendingServicesList = this.shadowRoot.getElementById('pendingServicesList');
|
|
|
|
|
pendingModelsList.innerHTML = '<div class="status-item empty">获取模型组数据失败</div>';
|
|
|
|
|
pendingServicesList.innerHTML = '<div class="status-item empty">获取服务列表失败</div>';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 控制右侧卡片显示:新建构型时隐藏
|
|
|
|
|
const editorSidebar = this.shadowRoot.querySelector('.editor-sidebar');
|
|
|
|
|
if (isNew) {
|
|
|
|
|
editorSidebar.style.display = 'none';
|
|
|
|
|
} else {
|
|
|
|
|
editorSidebar.style.display = 'flex';
|
|
|
|
|
}
|
2025-05-16 15:30:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @method ensurePathEndsWithSlash
|
|
|
|
|
* @description 确保路径以斜杠结尾
|
|
|
|
|
* @param {string} path - 要处理的路径
|
|
|
|
|
* @returns {string} 处理后的路径
|
|
|
|
|
*/
|
|
|
|
|
ensurePathEndsWithSlash(path) {
|
|
|
|
|
if (!path) return path;
|
|
|
|
|
return path.endsWith('/') ? path : path + '/';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @method saveConfiguration
|
|
|
|
|
* @description 保存构型配置
|
|
|
|
|
*/
|
|
|
|
|
async saveConfiguration() {
|
|
|
|
|
// 检查权限
|
|
|
|
|
if (this.userAccessLevel < 2) {
|
|
|
|
|
alert('您没有保存构型的权限');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 验证域ID
|
|
|
|
|
const domainId = parseInt(this.shadowRoot.getElementById('domainId').value);
|
|
|
|
|
if (isNaN(domainId) || domainId < 0 || domainId > 232) {
|
|
|
|
|
alert('域ID必须在0-232之间');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-28 16:20:01 +08:00
|
|
|
|
// 获取构型名称
|
|
|
|
|
const confName = this.selectedConfiguration.ConfName || '';
|
2025-05-16 15:30:54 +08:00
|
|
|
|
|
|
|
|
|
const updatedConfig = {
|
|
|
|
|
...this.selectedConfiguration,
|
2025-05-28 16:20:01 +08:00
|
|
|
|
ConfName: confName, // 保留构型名称
|
2025-05-16 15:30:54 +08:00
|
|
|
|
OSName: this.shadowRoot.getElementById('osName').value,
|
|
|
|
|
OSVersion: this.shadowRoot.getElementById('osVersion').value,
|
|
|
|
|
RTXVersion: this.shadowRoot.getElementById('rtxVersion').value,
|
|
|
|
|
CPUAffinity: this.shadowRoot.getElementById('cpuAffinity').value,
|
|
|
|
|
DomainID: domainId,
|
|
|
|
|
ModelsPath: this.ensurePathEndsWithSlash(this.shadowRoot.getElementById('modelsPath').value),
|
|
|
|
|
ServicesPath: this.ensurePathEndsWithSlash(this.shadowRoot.getElementById('servicesPath').value),
|
|
|
|
|
WorkPath: this.ensurePathEndsWithSlash(this.shadowRoot.getElementById('workPath').value),
|
|
|
|
|
ConsoleDebug: this.shadowRoot.getElementById('consoleDebug').checked ? 1 : 0,
|
|
|
|
|
ConsoleInfo: this.shadowRoot.getElementById('consoleInfo').checked ? 1 : 0,
|
|
|
|
|
ConsoleWarning: this.shadowRoot.getElementById('consoleWarning').checked ? 1 : 0,
|
|
|
|
|
ConsoleError: this.shadowRoot.getElementById('consoleError').checked ? 1 : 0,
|
|
|
|
|
LogDebug: this.shadowRoot.getElementById('logDebug').checked ? 1 : 0,
|
|
|
|
|
LogInfo: this.shadowRoot.getElementById('logInfo').checked ? 1 : 0,
|
|
|
|
|
LogWarning: this.shadowRoot.getElementById('logWarning').checked ? 1 : 0,
|
|
|
|
|
LogError: this.shadowRoot.getElementById('logError').checked ? 1 : 0
|
|
|
|
|
};
|
|
|
|
|
|
2025-05-16 16:42:31 +08:00
|
|
|
|
// 如果是新建构型,保留源构型ID
|
|
|
|
|
if (this.isNewConfiguration && this.selectedConfiguration.sourceConfId) {
|
|
|
|
|
updatedConfig.sourceConfId = this.selectedConfiguration.sourceConfId;
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-16 15:30:54 +08:00
|
|
|
|
try {
|
|
|
|
|
const url = this.isNewConfiguration ?
|
|
|
|
|
'/api/configurations' :
|
|
|
|
|
`/api/configurations/${this.selectedConfiguration.ConfID}`;
|
|
|
|
|
|
|
|
|
|
const method = this.isNewConfiguration ? 'POST' : 'PUT';
|
|
|
|
|
|
|
|
|
|
const response = await fetch(url, {
|
|
|
|
|
method: method,
|
|
|
|
|
headers: {
|
|
|
|
|
'Content-Type': 'application/json'
|
|
|
|
|
},
|
|
|
|
|
body: JSON.stringify(updatedConfig),
|
|
|
|
|
credentials: 'include'
|
|
|
|
|
});
|
|
|
|
|
|
2025-05-16 16:42:31 +08:00
|
|
|
|
const responseData = await response.json();
|
|
|
|
|
|
2025-05-16 15:30:54 +08:00
|
|
|
|
if (!response.ok) {
|
2025-05-16 16:42:31 +08:00
|
|
|
|
if (responseData.error === '路径验证失败') {
|
|
|
|
|
const errorMessages = responseData.details.map(detail =>
|
2025-05-16 15:30:54 +08:00
|
|
|
|
`${detail.path === 'workPath' ? '工作路径' :
|
|
|
|
|
detail.path === 'modelsPath' ? '模型路径' : '服务路径'}: ${detail.error}`
|
|
|
|
|
).join('\n');
|
|
|
|
|
alert(`路径验证失败:\n${errorMessages}`);
|
|
|
|
|
} else {
|
2025-05-16 16:42:31 +08:00
|
|
|
|
throw new Error(responseData.error || '保存构型失败');
|
2025-05-16 15:30:54 +08:00
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-16 16:42:31 +08:00
|
|
|
|
this.selectedConfiguration = responseData;
|
2025-05-16 15:30:54 +08:00
|
|
|
|
this.showConfigurations(this.selectedPlane);
|
|
|
|
|
} catch (error) {
|
2025-05-16 16:42:31 +08:00
|
|
|
|
console.error('保存构型失败:', error);
|
|
|
|
|
alert(error.message || '保存构型失败,请稍后重试');
|
2025-05-16 15:30:54 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @method showConfigurations
|
|
|
|
|
* @description 显示指定机型的构型列表
|
|
|
|
|
* @param {string} planeName - 机型名称
|
|
|
|
|
*/
|
|
|
|
|
async showConfigurations(planeName) {
|
|
|
|
|
this.currentView = 'configurations';
|
|
|
|
|
this.selectedPlane = planeName;
|
|
|
|
|
|
|
|
|
|
// 更新UI
|
|
|
|
|
this.shadowRoot.getElementById('pageTitle').textContent = `${planeName} - 构型列表`;
|
|
|
|
|
this.shadowRoot.getElementById('backSection').style.display = 'block';
|
|
|
|
|
this.shadowRoot.querySelector('#backButton span:last-child').textContent = '返回机型列表';
|
|
|
|
|
this.shadowRoot.getElementById('contentContainer').classList.add('configurations');
|
|
|
|
|
this.shadowRoot.getElementById('contentContainer').style.display = 'grid';
|
|
|
|
|
this.shadowRoot.getElementById('editorContainer').style.display = 'none';
|
|
|
|
|
|
|
|
|
|
// 检查用户权限
|
|
|
|
|
await this.checkUserAccessLevel();
|
|
|
|
|
|
|
|
|
|
// 加载构型数据
|
|
|
|
|
this.loadConfigurations(planeName);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @method showPlanes
|
|
|
|
|
* @description 显示机型列表
|
|
|
|
|
*/
|
|
|
|
|
showPlanes() {
|
|
|
|
|
this.currentView = 'planes';
|
|
|
|
|
this.selectedPlane = null;
|
|
|
|
|
this.selectedConfiguration = null;
|
|
|
|
|
|
|
|
|
|
// 更新UI
|
|
|
|
|
this.shadowRoot.getElementById('pageTitle').textContent = '机型';
|
|
|
|
|
this.shadowRoot.getElementById('backSection').style.display = 'none';
|
|
|
|
|
this.shadowRoot.getElementById('contentContainer').classList.remove('configurations');
|
|
|
|
|
this.shadowRoot.getElementById('contentContainer').style.display = 'grid';
|
|
|
|
|
this.shadowRoot.getElementById('editorContainer').style.display = 'none';
|
|
|
|
|
|
|
|
|
|
// 重新加载机型数据
|
|
|
|
|
this.loadPlanes();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @method showError
|
|
|
|
|
* @description 显示错误信息
|
|
|
|
|
* @param {string} message - 错误信息
|
|
|
|
|
*/
|
|
|
|
|
showError(message) {
|
|
|
|
|
const container = this.shadowRoot.getElementById('contentContainer');
|
|
|
|
|
container.innerHTML = `<div class="error">${message}</div>`;
|
|
|
|
|
}
|
2025-05-28 16:20:01 +08:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @method onAddModel
|
|
|
|
|
* @description 添加模型功能,弹窗选择ATA章节、模型、版本,确认后添加
|
|
|
|
|
* @param {string} groupId - 模型组ID
|
|
|
|
|
*/
|
|
|
|
|
async onAddModel(groupId) {
|
|
|
|
|
const planeName = this.selectedPlane;
|
|
|
|
|
const confID = this.selectedConfiguration.ConfID;
|
|
|
|
|
if (!planeName || !confID) {
|
|
|
|
|
alert('缺少机型或构型信息');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
// 创建弹窗
|
|
|
|
|
const dialog = document.createElement('div');
|
|
|
|
|
dialog.className = 'add-model-dialog';
|
|
|
|
|
dialog.innerHTML = `
|
|
|
|
|
<style>
|
|
|
|
|
.add-model-dialog { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.4); display: flex; align-items: center; justify-content: center; z-index: 1002; }
|
|
|
|
|
.add-model-content { background: #fff; border-radius: 8px; padding: 24px 32px; min-width: 340px; box-shadow: 0 2px 16px rgba(0,0,0,0.15); }
|
|
|
|
|
.add-model-title { font-size: 18px; font-weight: 500; margin-bottom: 18px; color: #333; }
|
|
|
|
|
.add-model-row { margin-bottom: 16px; }
|
|
|
|
|
.add-model-label { display: block; margin-bottom: 6px; color: #555; font-size: 14px; }
|
|
|
|
|
.add-model-select { width: 100%; padding: 8px 12px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; box-sizing: border-box; }
|
|
|
|
|
.add-model-buttons { display: flex; justify-content: flex-end; gap: 12px; margin-top: 10px; }
|
|
|
|
|
.add-model-btn { padding: 7px 18px; border: none; border-radius: 4px; font-size: 14px; cursor: pointer; }
|
|
|
|
|
.add-model-btn-primary { background: #5c6bc0; color: #fff; }
|
|
|
|
|
.add-model-btn-secondary { background: #e8eaf6; color: #5c6bc0; }
|
|
|
|
|
</style>
|
|
|
|
|
<div class="add-model-content">
|
|
|
|
|
<div class="add-model-title">添加模型</div>
|
|
|
|
|
<div class="add-model-row">
|
|
|
|
|
<label class="add-model-label">ATA章节</label>
|
|
|
|
|
<select class="add-model-select" id="ataSelect"><option value="">加载中...</option></select>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="add-model-row">
|
|
|
|
|
<label class="add-model-label">模型</label>
|
|
|
|
|
<select class="add-model-select" id="modelSelect" disabled><option value="">请先选择章节</option></select>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="add-model-row">
|
|
|
|
|
<label class="add-model-label">版本</label>
|
|
|
|
|
<select class="add-model-select" id="versionSelect" disabled><option value="">请先选择模型</option></select>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="add-model-buttons">
|
|
|
|
|
<button class="add-model-btn add-model-btn-secondary" id="cancelAddModel">取消</button>
|
|
|
|
|
<button class="add-model-btn add-model-btn-primary" id="confirmAddModel">确认添加</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
`;
|
|
|
|
|
document.body.appendChild(dialog);
|
|
|
|
|
// 关闭弹窗
|
|
|
|
|
dialog.querySelector('#cancelAddModel').onclick = () => document.body.removeChild(dialog);
|
|
|
|
|
// 获取ATA章节
|
|
|
|
|
const ataSelect = dialog.querySelector('#ataSelect');
|
|
|
|
|
const modelSelect = dialog.querySelector('#modelSelect');
|
|
|
|
|
const versionSelect = dialog.querySelector('#versionSelect');
|
|
|
|
|
let ataList = [], modelList = [], versionList = [];
|
|
|
|
|
try {
|
|
|
|
|
const ataResp = await fetch('/api/ata-chapters');
|
|
|
|
|
ataList = await ataResp.json();
|
|
|
|
|
ataSelect.innerHTML = '<option value="">请选择章节</option>' + ataList.map(a => `<option value="${a.ID}">${a.ID}</option>`).join('');
|
|
|
|
|
} catch {
|
|
|
|
|
ataSelect.innerHTML = '<option value="">加载失败</option>';
|
|
|
|
|
}
|
|
|
|
|
// 章节选择事件
|
|
|
|
|
ataSelect.onchange = async () => {
|
|
|
|
|
const chapterId = ataSelect.value;
|
|
|
|
|
modelSelect.disabled = true;
|
|
|
|
|
versionSelect.disabled = true;
|
|
|
|
|
modelSelect.innerHTML = '<option value="">加载中...</option>';
|
|
|
|
|
versionSelect.innerHTML = '<option value="">请先选择模型</option>';
|
|
|
|
|
if (!chapterId) {
|
|
|
|
|
modelSelect.innerHTML = '<option value="">请先选择章节</option>';
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
const modelResp = await fetch(`/api/chapter-models/${chapterId}?planeName=${encodeURIComponent(planeName)}`);
|
|
|
|
|
modelList = await modelResp.json();
|
|
|
|
|
modelSelect.innerHTML = '<option value="">请选择模型</option>' + modelList.map(m => `<option value="${m.ClassName}">${m.ModelName_CN}</option>`).join('');
|
|
|
|
|
modelSelect.disabled = false;
|
|
|
|
|
} catch {
|
|
|
|
|
modelSelect.innerHTML = '<option value="">加载失败</option>';
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
// 模型选择事件
|
|
|
|
|
modelSelect.onchange = async () => {
|
|
|
|
|
const className = modelSelect.value;
|
|
|
|
|
versionSelect.disabled = true;
|
|
|
|
|
versionSelect.innerHTML = '<option value="">加载中...</option>';
|
|
|
|
|
if (!className) {
|
|
|
|
|
versionSelect.innerHTML = '<option value="">请先选择模型</option>';
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
const versionResp = await fetch(`/api/model-versions/${className}?planeName=${encodeURIComponent(planeName)}&confID=${encodeURIComponent(confID)}`);
|
|
|
|
|
versionList = await versionResp.json();
|
|
|
|
|
versionSelect.innerHTML = '<option value="">请选择版本</option>' + versionList.map(v => `<option value="${v.Version}">${v.Version}</option>`).join('');
|
|
|
|
|
versionSelect.disabled = false;
|
|
|
|
|
} catch {
|
|
|
|
|
versionSelect.innerHTML = '<option value="">加载失败</option>';
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
// 确认添加
|
|
|
|
|
dialog.querySelector('#confirmAddModel').onclick = async () => {
|
|
|
|
|
const chapterId = ataSelect.value;
|
|
|
|
|
const className = modelSelect.value;
|
|
|
|
|
const version = versionSelect.value;
|
|
|
|
|
if (!chapterId || !className || !version) {
|
|
|
|
|
alert('请选择完整信息');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
// 获取选中模型的详细信息
|
|
|
|
|
const model = modelList.find(m => m.ClassName === className);
|
|
|
|
|
if (!model) {
|
|
|
|
|
alert('模型信息异常');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
// 组装要添加的数据
|
|
|
|
|
const postData = {
|
|
|
|
|
GroupID: groupId,
|
|
|
|
|
ClassName: className,
|
|
|
|
|
ModelName: model.ModelName,
|
|
|
|
|
ModelName_CN: model.ModelName_CN,
|
|
|
|
|
ModelVersion: version
|
|
|
|
|
};
|
|
|
|
|
try {
|
|
|
|
|
const resp = await fetch(`/api/model-groups/${groupId}/models`, {
|
|
|
|
|
method: 'POST',
|
|
|
|
|
headers: { 'Content-Type': 'application/json' },
|
|
|
|
|
credentials: 'include',
|
|
|
|
|
body: JSON.stringify(postData)
|
|
|
|
|
});
|
|
|
|
|
if (!resp.ok) {
|
|
|
|
|
const data = await resp.json();
|
|
|
|
|
throw new Error(data.error || '添加失败');
|
|
|
|
|
}
|
|
|
|
|
document.body.removeChild(dialog);
|
|
|
|
|
this.showEditor(this.selectedConfiguration, this.selectedPlane, false);
|
|
|
|
|
} catch (err) {
|
|
|
|
|
alert('添加失败:' + err.message);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @method showEditGroupDialog
|
|
|
|
|
* @description 显示模型组编辑对话框,编辑频率、优先级、亲和性
|
|
|
|
|
* @param {Object} group - 模型组对象
|
|
|
|
|
*/
|
|
|
|
|
showEditGroupDialog(group) {
|
|
|
|
|
const dialog = document.createElement('div');
|
|
|
|
|
dialog.className = 'edit-group-dialog';
|
|
|
|
|
dialog.innerHTML = `
|
|
|
|
|
<style>
|
|
|
|
|
.edit-group-dialog {
|
|
|
|
|
position: fixed;
|
|
|
|
|
top: 0; left: 0; right: 0; bottom: 0;
|
|
|
|
|
background: rgba(0,0,0,0.4);
|
|
|
|
|
display: flex; align-items: center; justify-content: center;
|
|
|
|
|
z-index: 1001;
|
|
|
|
|
}
|
|
|
|
|
.edit-group-content {
|
|
|
|
|
background: #fff;
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
padding: 24px 32px;
|
|
|
|
|
min-width: 320px;
|
|
|
|
|
box-shadow: 0 2px 16px rgba(0,0,0,0.15);
|
|
|
|
|
}
|
|
|
|
|
.edit-group-title {
|
|
|
|
|
font-size: 18px;
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
margin-bottom: 18px;
|
|
|
|
|
color: #333;
|
|
|
|
|
}
|
|
|
|
|
.edit-group-row {
|
|
|
|
|
margin-bottom: 16px;
|
|
|
|
|
}
|
|
|
|
|
.edit-group-label {
|
|
|
|
|
display: block;
|
|
|
|
|
margin-bottom: 6px;
|
|
|
|
|
color: #555;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
}
|
|
|
|
|
.edit-group-input {
|
|
|
|
|
width: 100%;
|
|
|
|
|
padding: 8px 12px;
|
|
|
|
|
border: 1px solid #ddd;
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
}
|
|
|
|
|
.edit-group-buttons {
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: flex-end;
|
|
|
|
|
gap: 12px;
|
|
|
|
|
margin-top: 10px;
|
|
|
|
|
}
|
|
|
|
|
.edit-group-btn {
|
|
|
|
|
padding: 7px 18px;
|
|
|
|
|
border: none;
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
}
|
|
|
|
|
.edit-group-btn-primary {
|
|
|
|
|
background: #5c6bc0;
|
|
|
|
|
color: #fff;
|
|
|
|
|
}
|
|
|
|
|
.edit-group-btn-secondary {
|
|
|
|
|
background: #e8eaf6;
|
|
|
|
|
color: #5c6bc0;
|
|
|
|
|
}
|
|
|
|
|
</style>
|
|
|
|
|
<div class="edit-group-content">
|
|
|
|
|
<div class="edit-group-title">编辑模型组参数</div>
|
|
|
|
|
<div class="edit-group-row">
|
|
|
|
|
<label class="edit-group-label">频率 (Hz)</label>
|
|
|
|
|
<input type="number" class="edit-group-input" id="editFreq" value="${group.Freq || 0}" min="0" />
|
|
|
|
|
</div>
|
|
|
|
|
<div class="edit-group-row">
|
|
|
|
|
<label class="edit-group-label">优先级</label>
|
|
|
|
|
<input type="number" class="edit-group-input" id="editPriority" value="${group.Priority || 0}" min="0" />
|
|
|
|
|
</div>
|
|
|
|
|
<div class="edit-group-row">
|
|
|
|
|
<label class="edit-group-label">亲和性</label>
|
|
|
|
|
<input type="text" class="edit-group-input" id="editCPUAff" value="${group.CPUAff || ''}" />
|
|
|
|
|
</div>
|
|
|
|
|
<div class="edit-group-buttons">
|
|
|
|
|
<button class="edit-group-btn edit-group-btn-secondary" id="cancelEditGroup">取消</button>
|
|
|
|
|
<button class="edit-group-btn edit-group-btn-primary" id="confirmEditGroup">保存</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
`;
|
|
|
|
|
document.body.appendChild(dialog);
|
|
|
|
|
dialog.querySelector('#cancelEditGroup').onclick = () => document.body.removeChild(dialog);
|
|
|
|
|
dialog.querySelector('#confirmEditGroup').onclick = async () => {
|
|
|
|
|
const freq = parseInt(dialog.querySelector('#editFreq').value) || 0;
|
|
|
|
|
const priority = parseInt(dialog.querySelector('#editPriority').value) || 0;
|
|
|
|
|
const cpuAff = dialog.querySelector('#editCPUAff').value.trim();
|
|
|
|
|
try {
|
|
|
|
|
const resp = await fetch(`/api/model-groups/${group.GroupID}`, {
|
|
|
|
|
method: 'PUT',
|
|
|
|
|
headers: { 'Content-Type': 'application/json' },
|
|
|
|
|
credentials: 'include',
|
|
|
|
|
body: JSON.stringify({ ...group, Freq: freq, Priority: priority, CPUAff: cpuAff })
|
|
|
|
|
});
|
|
|
|
|
if (!resp.ok) {
|
|
|
|
|
const data = await resp.json();
|
|
|
|
|
throw new Error(data.error || '保存失败');
|
|
|
|
|
}
|
|
|
|
|
document.body.removeChild(dialog);
|
|
|
|
|
this.showEditor(this.selectedConfiguration, this.selectedPlane, false);
|
|
|
|
|
} catch (err) {
|
|
|
|
|
alert('保存失败:' + err.message);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @method showAddModelGroupDialog
|
|
|
|
|
* @description 显示添加模型组对话框
|
|
|
|
|
*/
|
|
|
|
|
showAddModelGroupDialog() {
|
|
|
|
|
const cpuAffinityStr = this.shadowRoot.getElementById('cpuAffinity').value || '';
|
|
|
|
|
const cpuOptions = cpuAffinityStr.split(',').map(s => s.trim()).filter(s => s !== '' && !isNaN(Number(s)));
|
|
|
|
|
const dialog = document.createElement('div');
|
|
|
|
|
dialog.className = 'add-model-group-dialog';
|
|
|
|
|
dialog.innerHTML = `
|
|
|
|
|
<style>
|
|
|
|
|
.add-model-group-dialog {
|
|
|
|
|
position: fixed;
|
|
|
|
|
top: 0; left: 0; right: 0; bottom: 0;
|
|
|
|
|
background: rgba(0,0,0,0.4);
|
|
|
|
|
display: flex; align-items: center; justify-content: center;
|
|
|
|
|
z-index: 1001;
|
|
|
|
|
}
|
|
|
|
|
.add-model-group-content {
|
|
|
|
|
background: #fff;
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
padding: 24px 32px;
|
|
|
|
|
min-width: 320px;
|
|
|
|
|
box-shadow: 0 2px 16px rgba(0,0,0,0.15);
|
|
|
|
|
}
|
|
|
|
|
.add-model-group-title {
|
|
|
|
|
font-size: 18px;
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
margin-bottom: 18px;
|
|
|
|
|
color: #333;
|
|
|
|
|
}
|
|
|
|
|
.add-model-group-row {
|
|
|
|
|
margin-bottom: 16px;
|
|
|
|
|
}
|
|
|
|
|
.add-model-group-label {
|
|
|
|
|
display: block;
|
|
|
|
|
margin-bottom: 6px;
|
|
|
|
|
color: #555;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
}
|
|
|
|
|
.add-model-group-input, .add-model-group-multiselect {
|
|
|
|
|
width: 100%;
|
|
|
|
|
padding: 8px 12px;
|
|
|
|
|
border: 1px solid #ddd;
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
}
|
|
|
|
|
.add-model-group-multiselect { height: 80px; }
|
|
|
|
|
.add-model-group-selected {
|
|
|
|
|
margin-top: 6px;
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
color: #5c6bc0;
|
|
|
|
|
min-height: 18px;
|
|
|
|
|
}
|
|
|
|
|
.add-model-group-buttons {
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: flex-end;
|
|
|
|
|
gap: 12px;
|
|
|
|
|
margin-top: 10px;
|
|
|
|
|
}
|
|
|
|
|
.add-model-group-btn {
|
|
|
|
|
padding: 7px 18px;
|
|
|
|
|
border: none;
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
}
|
|
|
|
|
.add-model-group-btn-primary {
|
|
|
|
|
background: #5c6bc0;
|
|
|
|
|
color: #fff;
|
|
|
|
|
}
|
|
|
|
|
.add-model-group-btn-secondary {
|
|
|
|
|
background: #e8eaf6;
|
|
|
|
|
color: #5c6bc0;
|
|
|
|
|
}
|
|
|
|
|
</style>
|
|
|
|
|
<div class="add-model-group-content">
|
|
|
|
|
<div class="add-model-group-title">添加模型组</div>
|
|
|
|
|
<div class="add-model-group-row">
|
|
|
|
|
<label class="add-model-group-label">模型组名称</label>
|
|
|
|
|
<input type="text" class="add-model-group-input" id="newModelGroupName" placeholder="请输入模型组名称" />
|
|
|
|
|
</div>
|
|
|
|
|
<div class="add-model-group-row">
|
|
|
|
|
<label class="add-model-group-label">运行频率 (Hz)</label>
|
|
|
|
|
<input type="number" step="0.01" class="add-model-group-input" id="freqInput" min="0.01" max="1000" placeholder="请输入频率" />
|
|
|
|
|
</div>
|
|
|
|
|
<div class="add-model-group-row">
|
|
|
|
|
<label class="add-model-group-label">优先级 (1-99)</label>
|
|
|
|
|
<input type="number" class="add-model-group-input" id="priorityInput" min="1" max="99" placeholder="请输入优先级" />
|
|
|
|
|
</div>
|
|
|
|
|
<div class="add-model-group-row">
|
|
|
|
|
<label class="add-model-group-label">亲和性</label>
|
|
|
|
|
<div style="position:relative;">
|
|
|
|
|
<input type="text" class="add-model-group-input" id="cpuAffInput" readonly placeholder="请选择" style="background:#fff;cursor:pointer;" />
|
|
|
|
|
<div id="cpuAffDropdown" style="display:none;position:absolute;z-index:10;top:38px;left:0;width:100%;background:#fff;border:1px solid #ddd;border-radius:4px;box-shadow:0 2px 8px rgba(0,0,0,0.08);max-height:150px;overflow:auto;">
|
|
|
|
|
${cpuOptions.map(opt => `
|
|
|
|
|
<label style='display:flex;align-items:center;padding:6px 12px;cursor:pointer;'>
|
|
|
|
|
<input type="checkbox" value="${opt}" style="margin-right:8px;" />${opt}
|
|
|
|
|
</label>
|
|
|
|
|
`).join('')}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="add-model-group-buttons">
|
|
|
|
|
<button class="add-model-group-btn add-model-group-btn-secondary" id="cancelAddModelGroup">取消</button>
|
|
|
|
|
<button class="add-model-group-btn add-model-group-btn-primary" id="confirmAddModelGroup">确认添加</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
`;
|
|
|
|
|
document.body.appendChild(dialog);
|
|
|
|
|
// 亲和性多选下拉逻辑
|
|
|
|
|
const cpuAffInput = dialog.querySelector('#cpuAffInput');
|
|
|
|
|
const cpuAffDropdown = dialog.querySelector('#cpuAffDropdown');
|
|
|
|
|
cpuAffInput.addEventListener('click', () => {
|
|
|
|
|
cpuAffDropdown.style.display = cpuAffDropdown.style.display === 'block' ? 'none' : 'block';
|
|
|
|
|
});
|
|
|
|
|
document.addEventListener('mousedown', function handler(e) {
|
|
|
|
|
if (!dialog.contains(e.target)) {
|
|
|
|
|
cpuAffDropdown.style.display = 'none';
|
|
|
|
|
document.removeEventListener('mousedown', handler);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
cpuAffDropdown.querySelectorAll('input[type="checkbox"]').forEach(checkbox => {
|
|
|
|
|
checkbox.addEventListener('change', () => {
|
|
|
|
|
const selected = Array.from(cpuAffDropdown.querySelectorAll('input[type="checkbox"]:checked')).map(cb => cb.value);
|
|
|
|
|
cpuAffInput.value = selected.join(',');
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
dialog.querySelector('#cancelAddModelGroup').onclick = () => document.body.removeChild(dialog);
|
|
|
|
|
dialog.querySelector('#confirmAddModelGroup').onclick = async () => {
|
|
|
|
|
const name = dialog.querySelector('#newModelGroupName').value.trim();
|
|
|
|
|
const freq = parseFloat(dialog.querySelector('#freqInput').value);
|
|
|
|
|
const priority = parseInt(dialog.querySelector('#priorityInput').value);
|
|
|
|
|
const cpuAffSel = cpuAffInput.value;
|
|
|
|
|
if (!name) { alert('请输入模型组名称'); return; }
|
|
|
|
|
if (isNaN(freq) || freq <= 0 || freq > 1000) { alert('频率必须为0~1000之间的正浮点数'); return; }
|
|
|
|
|
if (isNaN(priority) || priority < 1 || priority > 99) { alert('优先级必须为1-99之间的整数'); return; }
|
|
|
|
|
if (!cpuAffSel) { alert('请选择亲和性'); return; }
|
|
|
|
|
// 组装数据
|
|
|
|
|
const postData = {
|
|
|
|
|
ConfID: this.selectedConfiguration.ConfID,
|
|
|
|
|
GroupName: name,
|
|
|
|
|
Freq: freq,
|
|
|
|
|
Priority: priority,
|
|
|
|
|
CPUAff: cpuAffSel
|
|
|
|
|
};
|
|
|
|
|
try {
|
|
|
|
|
const resp = await fetch(`/api/configurations/${this.selectedConfiguration.ConfID}/model-groups`, {
|
|
|
|
|
method: 'POST',
|
|
|
|
|
headers: { 'Content-Type': 'application/json' },
|
|
|
|
|
credentials: 'include',
|
|
|
|
|
body: JSON.stringify(postData)
|
|
|
|
|
});
|
|
|
|
|
if (!resp.ok) {
|
|
|
|
|
const data = await resp.json();
|
|
|
|
|
throw new Error(data.error || '添加失败');
|
|
|
|
|
}
|
|
|
|
|
document.body.removeChild(dialog);
|
|
|
|
|
this.showEditor(this.selectedConfiguration, this.selectedPlane, false);
|
|
|
|
|
} catch (err) {
|
|
|
|
|
alert('添加失败:' + err.message);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @method showAddServiceDialog
|
|
|
|
|
* @description 显示添加服务对话框
|
|
|
|
|
*/
|
|
|
|
|
async showAddServiceDialog() {
|
|
|
|
|
const dialog = document.createElement('div');
|
|
|
|
|
dialog.className = 'add-service-dialog';
|
|
|
|
|
dialog.innerHTML = `
|
|
|
|
|
<style>
|
|
|
|
|
.add-service-dialog {
|
|
|
|
|
position: fixed;
|
|
|
|
|
top: 0; left: 0; right: 0; bottom: 0;
|
|
|
|
|
background: rgba(0,0,0,0.4);
|
|
|
|
|
display: flex; align-items: center; justify-content: center;
|
|
|
|
|
z-index: 1001;
|
|
|
|
|
}
|
|
|
|
|
.add-service-content {
|
|
|
|
|
background: #fff;
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
padding: 24px 32px;
|
|
|
|
|
min-width: 320px;
|
|
|
|
|
box-shadow: 0 2px 16px rgba(0,0,0,0.15);
|
|
|
|
|
}
|
|
|
|
|
.add-service-title {
|
|
|
|
|
font-size: 18px;
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
margin-bottom: 18px;
|
|
|
|
|
color: #333;
|
|
|
|
|
}
|
|
|
|
|
.add-service-row {
|
|
|
|
|
margin-bottom: 16px;
|
|
|
|
|
}
|
|
|
|
|
.add-service-label {
|
|
|
|
|
display: block;
|
|
|
|
|
margin-bottom: 6px;
|
|
|
|
|
color: #555;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
}
|
|
|
|
|
.add-service-select {
|
|
|
|
|
width: 100%;
|
|
|
|
|
padding: 8px 12px;
|
|
|
|
|
border: 1px solid #ddd;
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
}
|
|
|
|
|
.add-service-buttons {
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: flex-end;
|
|
|
|
|
gap: 12px;
|
|
|
|
|
margin-top: 10px;
|
|
|
|
|
}
|
|
|
|
|
.add-service-btn {
|
|
|
|
|
padding: 7px 18px;
|
|
|
|
|
border: none;
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
}
|
|
|
|
|
.add-service-btn-primary {
|
|
|
|
|
background: #5c6bc0;
|
|
|
|
|
color: #fff;
|
|
|
|
|
}
|
|
|
|
|
.add-service-btn-secondary {
|
|
|
|
|
background: #e8eaf6;
|
|
|
|
|
color: #5c6bc0;
|
|
|
|
|
}
|
|
|
|
|
</style>
|
|
|
|
|
<div class="add-service-content">
|
|
|
|
|
<div class="add-service-title">添加服务</div>
|
|
|
|
|
<div class="add-service-row">
|
|
|
|
|
<label class="add-service-label">服务</label>
|
|
|
|
|
<select class="add-service-select" id="serviceSelect">
|
|
|
|
|
<option value="">加载中...</option>
|
|
|
|
|
</select>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="add-service-row">
|
|
|
|
|
<label class="add-service-label">版本</label>
|
|
|
|
|
<select class="add-service-select" id="versionSelect" disabled>
|
|
|
|
|
<option value="">请先选择服务</option>
|
|
|
|
|
</select>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="add-service-buttons">
|
|
|
|
|
<button class="add-service-btn add-service-btn-secondary" id="cancelAddService">取消</button>
|
|
|
|
|
<button class="add-service-btn add-service-btn-primary" id="confirmAddService">确认添加</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
`;
|
|
|
|
|
document.body.appendChild(dialog);
|
|
|
|
|
|
|
|
|
|
// 获取服务列表
|
|
|
|
|
const serviceSelect = dialog.querySelector('#serviceSelect');
|
|
|
|
|
const versionSelect = dialog.querySelector('#versionSelect');
|
|
|
|
|
let serviceList = [];
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const response = await fetch('/api/services');
|
|
|
|
|
if (!response.ok) throw new Error('获取服务列表失败');
|
|
|
|
|
serviceList = await response.json();
|
|
|
|
|
serviceSelect.innerHTML = '<option value="">请选择服务</option>' +
|
|
|
|
|
serviceList.map(service => `<option value="${service.ClassName}">${service.ServiceName_CN || service.ServiceName}</option>`).join('');
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('获取服务列表失败:', error);
|
|
|
|
|
serviceSelect.innerHTML = '<option value="">获取服务列表失败</option>';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 服务选择事件
|
|
|
|
|
serviceSelect.onchange = async () => {
|
|
|
|
|
const className = serviceSelect.value;
|
|
|
|
|
versionSelect.disabled = true;
|
|
|
|
|
versionSelect.innerHTML = '<option value="">加载中...</option>';
|
|
|
|
|
|
|
|
|
|
if (!className) {
|
|
|
|
|
versionSelect.innerHTML = '<option value="">请先选择服务</option>';
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const response = await fetch(`/api/service-versions/${className}`);
|
|
|
|
|
if (!response.ok) throw new Error('获取版本列表失败');
|
|
|
|
|
const versions = await response.json();
|
|
|
|
|
versionSelect.innerHTML = '<option value="">请选择版本</option>' +
|
|
|
|
|
versions.map(version => `<option value="${version.Version}">${version.Version}</option>`).join('');
|
|
|
|
|
versionSelect.disabled = false;
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('获取版本列表失败:', error);
|
|
|
|
|
versionSelect.innerHTML = '<option value="">获取版本列表失败</option>';
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 取消按钮事件
|
|
|
|
|
dialog.querySelector('#cancelAddService').onclick = () => {
|
|
|
|
|
document.body.removeChild(dialog);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 确认按钮事件
|
|
|
|
|
dialog.querySelector('#confirmAddService').onclick = async () => {
|
|
|
|
|
const className = serviceSelect.value;
|
|
|
|
|
const version = versionSelect.value;
|
|
|
|
|
|
|
|
|
|
if (!className || !version) {
|
|
|
|
|
alert('请选择完整的服务信息');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 查找ServiceName
|
|
|
|
|
const selectedService = serviceList.find(s => s.ClassName === className);
|
|
|
|
|
const serviceName = selectedService ? (selectedService.ServiceName || selectedService.ServiceName_CN || className) : className;
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const response = await fetch(`/api/configurations/${this.selectedConfiguration.ConfID}/services`, {
|
|
|
|
|
method: 'POST',
|
|
|
|
|
headers: { 'Content-Type': 'application/json' },
|
|
|
|
|
credentials: 'include',
|
|
|
|
|
body: JSON.stringify({
|
|
|
|
|
ClassName: className,
|
|
|
|
|
ServiceVersion: version,
|
|
|
|
|
ServiceName: serviceName
|
|
|
|
|
})
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!response.ok) {
|
|
|
|
|
const data = await response.json();
|
|
|
|
|
throw new Error(data.error || '添加服务失败');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
document.body.removeChild(dialog);
|
|
|
|
|
this.showEditor(this.selectedConfiguration, this.selectedPlane, false);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('添加服务失败:', error);
|
|
|
|
|
alert('添加服务失败:' + error.message);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
2025-05-16 15:30:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 注册自定义元素
|
|
|
|
|
customElements.define('configuration-config', ConfigurationConfig);
|