XNSim/XNSimHtml/components/configuration-config.js

1055 lines
44 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* @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;
}
.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;">
<div class="section">
<h3 class="section-title">基本信息</h3>
<div class="form-row">
<div class="form-group">
<label class="form-label">构型名称</label>
<input type="text" class="form-input" id="confName" readonly />
</div>
<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>
</div>
</div>
<div class="section">
<h3 class="section-title">系统配置</h3>
<div class="form-row">
<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>
<div class="form-row-3">
<div class="form-group">
<label class="form-label">工作路径</label>
<input type="text" class="form-input" id="workPath" />
</div>
<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>
</div>
</div>
<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>
</div>
</div>
<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>
</div>
</div>
</div>
</div>
<div class="button-group">
<button class="button button-primary" id="saveButton">保存构型</button>
<button class="button button-secondary" id="cancelButton">取消</button>
</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();
}
/**
* @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;
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;
}
.select-input:focus {
border-color: #5c6bc0;
outline: none;
box-shadow: 0 0 0 2px rgba(92, 107, 192, 0.2);
}
.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">
<div class="dialog-title">选择基准构型</div>
<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');
dialog.querySelector('#cancelButton').addEventListener('click', () => {
document.body.removeChild(dialog);
resolve(null); // 取消时返回null
});
dialog.querySelector('#confirmButton').addEventListener('click', () => {
const selectedValue = select.value;
const selectedConfig = selectedValue === 'none' ?
undefined : // 选择"不使用基准构型"时返回undefined
configs.find(c => c.ConfID === parseInt(selectedValue));
document.body.removeChild(dialog);
resolve(selectedConfig);
});
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
};
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注意:删除后将无法恢复,且会同时删除该构型下的所有模型组、模型和服务。`)) {
try {
const response = await fetch(`/api/configurations/${configId}`, {
method: 'DELETE',
credentials: 'include'
});
const responseData = await response.json();
if (!response.ok) {
throw new Error(responseData.error || '删除构型失败');
}
// 重新加载构型列表
this.loadConfigurations(planeName);
} catch (error) {
console.error('删除构型失败:', error);
alert(error.message || '删除构型失败,请稍后重试');
}
}
});
});
}
/**
* @method showEditor
* @description 显示构型编辑器
* @param {Object} configuration - 构型数据
* @param {string} planeName - 机型名称
* @param {boolean} isNew - 是否是新建构型
*/
showEditor(configuration, planeName, isNew) {
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');
const confNameInput = this.shadowRoot.getElementById('confName');
if (!pageTitle || !backSection || !contentContainer || !editorContainer) {
return;
}
pageTitle.textContent = isNew ? `${planeName} - 新建构型` : `${planeName} - ${configuration.ConfName} 构型`;
backSection.style.display = 'block';
backButtonText.textContent = '返回构型列表';
contentContainer.style.display = 'none';
editorContainer.style.display = 'block';
// 设置构型名称输入框状态
confNameInput.readOnly = !isNew;
if (isNew) {
confNameInput.value = '';
} else {
confNameInput.value = configuration.ConfName || '';
}
// 填充其他表单数据
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;
}
/**
* @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;
}
// 验证构型名称
const confName = this.shadowRoot.getElementById('confName').value.trim();
if (!confName) {
alert('构型名称不能为空');
return;
}
const updatedConfig = {
...this.selectedConfiguration,
ConfName: confName,
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;
}
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();
if (!response.ok) {
if (responseData.error === '路径验证失败') {
const errorMessages = responseData.details.map(detail =>
`${detail.path === 'workPath' ? '工作路径' :
detail.path === 'modelsPath' ? '模型路径' : '服务路径'}: ${detail.error}`
).join('\n');
alert(`路径验证失败:\n${errorMessages}`);
} else {
throw new Error(responseData.error || '保存构型失败');
}
return;
}
this.selectedConfiguration = responseData;
this.showConfigurations(this.selectedPlane);
} catch (error) {
console.error('保存构型失败:', error);
alert(error.message || '保存构型失败,请稍后重试');
}
}
/**
* @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>`;
}
}
// 注册自定义元素
customElements.define('configuration-config', ConfigurationConfig);