1046 lines
43 KiB
JavaScript
1046 lines
43 KiB
JavaScript
|
/**
|
|||
|
* @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
|
|||
|
};
|
|||
|
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}"吗?`)) {
|
|||
|
try {
|
|||
|
const response = await fetch(`/api/configurations/${configId}`, {
|
|||
|
method: 'DELETE',
|
|||
|
credentials: 'include'
|
|||
|
});
|
|||
|
|
|||
|
if (!response.ok) {
|
|||
|
throw new Error('删除构型失败');
|
|||
|
}
|
|||
|
|
|||
|
// 重新加载构型列表
|
|||
|
this.loadConfigurations(planeName);
|
|||
|
} catch (error) {
|
|||
|
console.error('删除构型失败:', error);
|
|||
|
alert('删除构型失败,请稍后重试');
|
|||
|
}
|
|||
|
}
|
|||
|
});
|
|||
|
});
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* @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
|
|||
|
};
|
|||
|
|
|||
|
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'
|
|||
|
});
|
|||
|
|
|||
|
if (!response.ok) {
|
|||
|
const errorData = await response.json();
|
|||
|
if (errorData.error === '路径验证失败') {
|
|||
|
const errorMessages = errorData.details.map(detail =>
|
|||
|
`${detail.path === 'workPath' ? '工作路径' :
|
|||
|
detail.path === 'modelsPath' ? '模型路径' : '服务路径'}: ${detail.error}`
|
|||
|
).join('\n');
|
|||
|
alert(`路径验证失败:\n${errorMessages}`);
|
|||
|
} else {
|
|||
|
throw new Error(errorData.error || '保存配置失败');
|
|||
|
}
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
this.selectedConfiguration = updatedConfig;
|
|||
|
this.showConfigurations(this.selectedPlane);
|
|||
|
} catch (error) {
|
|||
|
console.error('保存配置失败:', error);
|
|||
|
alert('保存配置失败,请稍后重试');
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* @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);
|