@page
|
@model IndexModel
|
@{
|
ViewData["Title"] = "实例列表";
|
}
|
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
<div>
|
<h2 class="mb-0">
|
<i class="bi bi-cpu-fill me-2"></i>S7 PLC 仿真器实例
|
</h2>
|
<p class="text-muted mb-0 mt-1">管理和监控 S7 PLC 仿真器实例</p>
|
</div>
|
<a asp-page="/Create" class="btn btn-primary">
|
<i class="bi bi-plus-lg me-1"></i>创建实例
|
</a>
|
</div>
|
|
<div id="instancesContainer">
|
<!-- Loading state -->
|
<div id="loadingState" class="text-center py-5">
|
<div class="spinner-border text-primary" role="status">
|
<span class="visually-hidden">加载中...</span>
|
</div>
|
<p class="mt-3 text-muted">正在加载实例列表...</p>
|
</div>
|
|
<!-- Empty state -->
|
<div id="emptyState" class="empty-state d-none">
|
<i class="bi bi-inbox"></i>
|
<h3>暂无实例</h3>
|
<p>点击上方"创建实例"按钮来创建您的第一个仿真器实例</p>
|
</div>
|
|
<!-- Instances grid -->
|
<div id="instancesGrid" class="row row-cols-1 row-cols-md-2 row-cols-xl-3 g-4 d-none"></div>
|
</div>
|
|
@section Scripts {
|
<script>
|
let autoRefreshInterval = null;
|
|
document.addEventListener('DOMContentLoaded', function () {
|
loadInstances();
|
startAutoRefresh(loadInstances, 5000);
|
});
|
|
async function loadInstances() {
|
try {
|
const instances = await apiCall(`${API_BASE_URL}/SimulatorInstances`);
|
renderInstances(instances);
|
} catch (error) {
|
console.error('Failed to load instances:', error);
|
document.getElementById('loadingState').classList.add('d-none');
|
document.getElementById('emptyState').classList.remove('d-none');
|
}
|
}
|
|
function renderInstances(instances) {
|
const loadingState = document.getElementById('loadingState');
|
const emptyState = document.getElementById('emptyState');
|
const grid = document.getElementById('instancesGrid');
|
|
loadingState.classList.add('d-none');
|
grid.classList.add('d-none');
|
emptyState.classList.add('d-none');
|
|
if (!instances || instances.length === 0) {
|
emptyState.classList.remove('d-none');
|
return;
|
}
|
|
grid.classList.remove('d-none');
|
grid.innerHTML = instances.map(instance => createInstanceCard(instance)).join('');
|
}
|
|
function createInstanceCard(instance) {
|
const statusClass = getStatusClass(instance.status);
|
const statusText = getStatusText(instance.status);
|
const plcTypeText = getPlcTypeText(instance.plcType);
|
const isRunning = instance.status === 'Running';
|
const isStopped = instance.status === 'Stopped';
|
|
return `
|
<div class="col">
|
<div class="card instance-card h-100 ${statusClass}">
|
<div class="card-header d-flex justify-content-between align-items-center">
|
<h5 class="card-title mb-0">${escapeHtml(instance.instanceId)}</h5>
|
<span class="badge ${statusClass}">${statusText}</span>
|
</div>
|
<div class="card-body">
|
<div class="instance-info mb-3">
|
<div class="row mb-2">
|
<div class="col-6">
|
<small class="instance-info-label">名称</small>
|
<div class="instance-info-value">${escapeHtml(instance.name || '-')}</div>
|
</div>
|
<div class="col-6">
|
<small class="instance-info-label">PLC型号</small>
|
<div class="instance-info-value">${plcTypeText}</div>
|
</div>
|
</div>
|
<div class="row mb-2">
|
<div class="col-6">
|
<small class="instance-info-label">端口</small>
|
<div class="instance-info-value">${instance.port || '-'}</div>
|
</div>
|
<div class="col-6">
|
<small class="instance-info-label">客户端</small>
|
<div class="instance-info-value">
|
<i class="bi bi-people-fill me-1"></i>${instance.clientCount || 0}
|
</div>
|
</div>
|
</div>
|
${instance.startTime ? `
|
<div class="row">
|
<div class="col-12">
|
<small class="instance-info-label">启动时间</small>
|
<div class="instance-info-value small">${formatDate(instance.startTime)}</div>
|
</div>
|
</div>
|
` : ''}
|
${instance.errorMessage ? `
|
<div class="alert alert-danger alert-sm mt-2 mb-0 py-2 small">
|
<i class="bi bi-exclamation-triangle-fill me-1"></i>${escapeHtml(instance.errorMessage)}
|
</div>
|
` : ''}
|
</div>
|
</div>
|
<div class="card-footer bg-white">
|
<div class="action-buttons d-flex gap-2">
|
${isRunning ? `
|
<button class="btn btn-warning btn-sm flex-fill" onclick="stopInstance('${instance.instanceId}')" ${instance.status === 'Stopping' ? 'disabled' : ''}>
|
<i class="bi bi-stop-fill me-1"></i>停止
|
</button>
|
` : ''}
|
${isStopped ? `
|
<button class="btn btn-success btn-sm flex-fill" onclick="startInstance('${instance.instanceId}')" ${instance.status === 'Starting' ? 'disabled' : ''}>
|
<i class="bi bi-play-fill me-1"></i>启动
|
</button>
|
` : ''}
|
<a href="/Details?id=${encodeURIComponent(instance.instanceId)}" class="btn btn-info btn-sm text-white flex-fill">
|
<i class="bi bi-info-circle-fill me-1"></i>详情
|
</a>
|
<a href="/Edit?id=${encodeURIComponent(instance.instanceId)}" class="btn btn-primary btn-sm flex-fill">
|
<i class="bi bi-pencil-fill me-1"></i>编辑
|
</a>
|
<button class="btn btn-danger btn-sm" onclick="deleteInstance('${instance.instanceId}')">
|
<i class="bi bi-trash-fill"></i>
|
</button>
|
</div>
|
</div>
|
</div>
|
</div>
|
`;
|
}
|
|
function getStatusClass(status) {
|
const map = {
|
'Stopped': 'status-stopped',
|
'Starting': 'status-starting',
|
'Running': 'status-running',
|
'Stopping': 'status-stopping',
|
'Error': 'status-error'
|
};
|
return map[status] || '';
|
}
|
|
async function startInstance(id) {
|
confirmAction(`确定要启动实例 "${id}" 吗?`, async () => {
|
try {
|
await apiCall(`${API_BASE_URL}/SimulatorInstances/${encodeURIComponent(id)}/start`, {
|
method: 'POST'
|
});
|
showToast(`实例 "${id}" 启动命令已发送`, 'success');
|
setTimeout(() => loadInstances(), 1000);
|
} catch (error) {
|
console.error('Failed to start instance:', error);
|
}
|
});
|
}
|
|
async function stopInstance(id) {
|
confirmAction(`确定要停止实例 "${id}" 吗?`, async () => {
|
try {
|
await apiCall(`${API_BASE_URL}/SimulatorInstances/${encodeURIComponent(id)}/stop`, {
|
method: 'POST'
|
});
|
showToast(`实例 "${id}" 停止命令已发送`, 'success');
|
setTimeout(() => loadInstances(), 1000);
|
} catch (error) {
|
console.error('Failed to stop instance:', error);
|
}
|
});
|
}
|
|
async function deleteInstance(id) {
|
confirmAction(`确定要删除实例 "${id}" 吗?此操作不可撤销!`, async () => {
|
try {
|
await apiCall(`${API_BASE_URL}/SimulatorInstances/${encodeURIComponent(id)}?deleteConfig=true`, {
|
method: 'DELETE'
|
});
|
showToast(`实例 "${id}" 已删除`, 'success');
|
loadInstances();
|
} catch (error) {
|
console.error('Failed to delete instance:', error);
|
}
|
});
|
}
|
|
function escapeHtml(text) {
|
const div = document.createElement('div');
|
div.textContent = text;
|
return div.innerHTML;
|
}
|
|
// Stop auto-refresh when page is hidden
|
document.addEventListener('visibilitychange', function () {
|
if (document.hidden) {
|
stopAutoRefresh();
|
} else {
|
startAutoRefresh(loadInstances, 5000);
|
}
|
});
|
</script>
|
}
|