XNSim/XNSimHtml/components/configuration-config.js

1977 lines
90 KiB
JavaScript
Raw Normal View History

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,
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);
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'
});
const responseData = await response.json();
2025-05-16 15:30:54 +08:00
if (!response.ok) {
throw new Error(responseData.error || '删除构型失败');
2025-05-16 15:30:54 +08:00
}
// 重新加载构型列表
this.loadConfigurations(planeName);
} catch (error) {
console.error('删除构型失败:', error);
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;
}
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
};
// 如果是新建构型保留源构型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'
});
const responseData = await response.json();
2025-05-16 15:30:54 +08:00
if (!response.ok) {
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 {
throw new Error(responseData.error || '保存构型失败');
2025-05-16 15:30:54 +08:00
}
return;
}
this.selectedConfiguration = responseData;
2025-05-16 15:30:54 +08:00
this.showConfigurations(this.selectedPlane);
} catch (error) {
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);