@page
|
@model DetailsModel
|
@{
|
ViewData["Title"] = $"实例详情 - {Model.Instance?.InstanceId}";
|
}
|
|
@if (Model.Instance == null)
|
{
|
<div class="alert alert-warning" role="alert">
|
<i class="bi bi-exclamation-triangle-fill me-2"></i>
|
实例不存在或加载失败
|
</div>
|
<a asp-page="/Index" class="btn btn-primary">
|
<i class="bi bi-arrow-left me-1"></i>返回列表
|
</a>
|
}
|
else
|
{
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
<div>
|
<h2 class="mb-0">
|
<i class="bi bi-info-circle me-2"></i>实例详情: @Model.Instance.InstanceId
|
</h2>
|
<p class="text-muted mb-0 mt-1">查看和管理仿真器实例的详细信息</p>
|
</div>
|
<div>
|
<a asp-page="/Edit" asp-route-id="@Model.Instance.InstanceId" class="btn btn-primary me-2">
|
<i class="bi bi-pencil-fill me-1"></i>编辑
|
</a>
|
<a asp-page="/Index" class="btn btn-secondary">
|
<i class="bi bi-arrow-left me-1"></i>返回列表
|
</a>
|
</div>
|
</div>
|
|
<!-- 状态卡片 -->
|
<div class="row mb-4">
|
<div class="col-md-3">
|
<div class="card text-center">
|
<div class="card-body">
|
<h6 class="card-subtitle mb-2 text-muted">状态</h6>
|
<h4 class="card-title">
|
<span class="badge @GetStatusBadgeClass(Model.Instance.Status) fs-6">
|
@GetStatusText(Model.Instance.Status)
|
</span>
|
</h4>
|
</div>
|
</div>
|
</div>
|
<div class="col-md-3">
|
<div class="card text-center">
|
<div class="card-body">
|
<h6 class="card-subtitle mb-2 text-muted">连接客户端</h6>
|
<h4 class="card-title">
|
<i class="bi bi-people-fill text-primary me-1"></i>@Model.Instance.ClientCount
|
</h4>
|
</div>
|
</div>
|
</div>
|
<div class="col-md-3">
|
<div class="card text-center">
|
<div class="card-body">
|
<h6 class="card-subtitle mb-2 text-muted">总请求数</h6>
|
<h4 class="card-title">
|
<i class="bi bi-graph-up-arrow text-success me-1"></i>@Model.Instance.TotalRequests
|
</h4>
|
</div>
|
</div>
|
</div>
|
<div class="col-md-3">
|
<div class="card text-center">
|
<div class="card-body">
|
<h6 class="card-subtitle mb-2 text-muted">运行时间</h6>
|
<h4 class="card-title" id="runningTime">
|
@if (Model.Instance.StartTime.HasValue)
|
{
|
<span>@GetRunningTime(Model.Instance.StartTime.Value)</span>
|
}
|
else
|
{
|
<span class="text-muted">-</span>
|
}
|
</h4>
|
</div>
|
</div>
|
</div>
|
</div>
|
|
<!-- 实例信息 -->
|
<div class="card mb-4">
|
<div class="card-header">
|
<h5 class="mb-0">
|
<i class="bi bi-cpu me-2"></i>实例信息
|
</h5>
|
</div>
|
<div class="card-body">
|
<div class="row">
|
<div class="col-md-6 mb-3">
|
<strong>实例ID:</strong>
|
<div class="text-muted">@Model.Instance.InstanceId</div>
|
</div>
|
<div class="col-md-6 mb-3">
|
<strong>启动时间:</strong>
|
<div class="text-muted">@(Model.Instance.StartTime?.ToString("yyyy-MM-dd HH:mm:ss") ?? "-")</div>
|
</div>
|
<div class="col-md-6 mb-3">
|
<strong>最后活动:</strong>
|
<div class="text-muted">@(Model.Instance.LastActivityTime?.ToString("yyyy-MM-dd HH:mm:ss") ?? "-")</div>
|
</div>
|
<div class="col-md-6 mb-3">
|
<strong>错误信息:</strong>
|
<div class="text-muted">@(Model.Instance.ErrorMessage ?? "无")</div>
|
</div>
|
</div>
|
</div>
|
<div class="card-footer">
|
<div class="d-flex gap-2">
|
@if (Model.Instance.Status == WIDESEAWCS_S7Simulator.Core.Enums.InstanceStatus.Running)
|
{
|
<button class="btn btn-warning" onclick="stopInstance('@Model.Instance.InstanceId')">
|
<i class="bi bi-stop-fill me-1"></i>停止
|
</button>
|
<button class="btn btn-info" onclick="restartInstance('@Model.Instance.InstanceId')">
|
<i class="bi bi-arrow-clockwise me-1"></i>重启
|
</button>
|
}
|
else if (Model.Instance.Status == WIDESEAWCS_S7Simulator.Core.Enums.InstanceStatus.Stopped)
|
{
|
<button class="btn btn-success" onclick="startInstance('@Model.Instance.InstanceId')">
|
<i class="bi bi-play-fill me-1"></i>启动
|
</button>
|
}
|
</div>
|
</div>
|
</div>
|
|
<!-- 连接的客户端 -->
|
<div class="card">
|
<div class="card-header d-flex justify-content-between align-items-center">
|
<h5 class="mb-0">
|
<i class="bi bi-people me-2"></i>连接的客户端
|
</h5>
|
<span class="badge bg-primary">@Model.Clients.Count</span>
|
</div>
|
<div class="card-body">
|
@if (Model.Clients.Any())
|
{
|
<div class="table-responsive">
|
<table class="table table-striped table-hover client-table">
|
<thead>
|
<tr>
|
<th>客户端ID</th>
|
<th>远程地址</th>
|
<th>连接时间</th>
|
<th>最后活动</th>
|
<th>操作</th>
|
</tr>
|
</thead>
|
<tbody>
|
@foreach (var client in Model.Clients)
|
{
|
<tr>
|
<td><code>@client.ClientId</code></td>
|
<td>@client.RemoteEndPoint</td>
|
<td>@client.ConnectedTime.ToString("yyyy-MM-dd HH:mm:ss")</td>
|
<td>@client.LastActivityTime.ToString("yyyy-MM-dd HH:mm:ss")</td>
|
<td>
|
<button class="btn btn-sm btn-danger" onclick="disconnectClient('@Model.Instance.InstanceId', '@client.ClientId')">
|
<i class="bi bi-x-circle-fill"></i>断开
|
</button>
|
</td>
|
</tr>
|
}
|
</tbody>
|
</table>
|
</div>
|
}
|
else
|
{
|
<div class="text-center text-muted py-4">
|
<i class="bi bi-inbox" style="font-size: 2rem;"></i>
|
<p class="mt-2">暂无连接的客户端</p>
|
</div>
|
}
|
</div>
|
</div>
|
}
|
|
@{
|
string GetStatusBadgeClass(WIDESEAWCS_S7Simulator.Core.Enums.InstanceStatus status)
|
{
|
return status switch
|
{
|
WIDESEAWCS_S7Simulator.Core.Enums.InstanceStatus.Running => "bg-success",
|
WIDESEAWCS_S7Simulator.Core.Enums.InstanceStatus.Stopped => "bg-secondary",
|
WIDESEAWCS_S7Simulator.Core.Enums.InstanceStatus.Starting => "bg-info",
|
WIDESEAWCS_S7Simulator.Core.Enums.InstanceStatus.Stopping => "bg-warning text-dark",
|
WIDESEAWCS_S7Simulator.Core.Enums.InstanceStatus.Error => "bg-danger",
|
_ => "bg-secondary"
|
};
|
}
|
|
string GetStatusText(WIDESEAWCS_S7Simulator.Core.Enums.InstanceStatus status)
|
{
|
return status switch
|
{
|
WIDESEAWCS_S7Simulator.Core.Enums.InstanceStatus.Running => "运行中",
|
WIDESEAWCS_S7Simulator.Core.Enums.InstanceStatus.Stopped => "已停止",
|
WIDESEAWCS_S7Simulator.Core.Enums.InstanceStatus.Starting => "启动中",
|
WIDESEAWCS_S7Simulator.Core.Enums.InstanceStatus.Stopping => "停止中",
|
WIDESEAWCS_S7Simulator.Core.Enums.InstanceStatus.Error => "错误",
|
_ => status.ToString()
|
};
|
}
|
|
string GetRunningTime(DateTime startTime)
|
{
|
var span = DateTime.Now - startTime;
|
if (span.TotalDays >= 1)
|
return $"{(int)span.TotalDays}天 {span.Hours}小时";
|
else if (span.TotalHours >= 1)
|
return $"{(int)span.TotalHours}小时 {span.Minutes}分钟";
|
else if (span.TotalMinutes >= 1)
|
return $"{(int)span.TotalMinutes}分钟 {span.Seconds}秒";
|
else
|
return $"{span.Seconds}秒";
|
}
|
}
|
|
@section Scripts {
|
<script>
|
// Auto refresh
|
const instanceId = '@Model.Instance?.InstanceId';
|
let refreshInterval = null;
|
|
document.addEventListener('DOMContentLoaded', function () {
|
if (instanceId) {
|
startAutoRefresh();
|
// Update running time every second
|
setInterval(updateRunningTime, 1000);
|
}
|
});
|
|
function startAutoRefresh() {
|
refreshInterval = setInterval(async () => {
|
try {
|
const [instance, clients] = await Promise.all([
|
fetch(`${API_BASE_URL}/SimulatorInstances/${encodeURIComponent(instanceId)}`).then(r => r.json()),
|
fetch(`${API_BASE_URL}/instances/${encodeURIComponent(instanceId)}/Clients`).then(r => r.json())
|
]);
|
|
// Update status badges
|
updateInstanceStatus(instance);
|
} catch (error) {
|
console.error('Auto refresh failed:', error);
|
}
|
}, 5000);
|
}
|
|
function updateInstanceStatus(instance) {
|
// You can add logic here to update UI elements without full page reload
|
// For now, we rely on page refresh for major changes
|
}
|
|
function updateRunningTime() {
|
const startTimeElement = document.querySelector('#runningTime span');
|
if (startTimeElement && startTimeElement.textContent !== '-') {
|
// This would require storing the start time and calculating
|
// For simplicity, we'll skip the real-time update
|
}
|
}
|
|
async function startInstance(id) {
|
confirmAction(`确定要启动实例 "${id}" 吗?`, async () => {
|
try {
|
await apiCall(`${API_BASE_URL}/SimulatorInstances/${encodeURIComponent(id)}/start`, {
|
method: 'POST'
|
});
|
showToast('启动命令已发送', 'success');
|
setTimeout(() => location.reload(), 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('停止命令已发送', 'success');
|
setTimeout(() => location.reload(), 1000);
|
} catch (error) {
|
console.error('Failed to stop instance:', error);
|
}
|
});
|
}
|
|
async function restartInstance(id) {
|
confirmAction(`确定要重启实例 "${id}" 吗?`, async () => {
|
try {
|
await apiCall(`${API_BASE_URL}/SimulatorInstances/${encodeURIComponent(id)}/restart`, {
|
method: 'POST'
|
});
|
showToast('重启命令已发送', 'success');
|
setTimeout(() => location.reload(), 1000);
|
} catch (error) {
|
console.error('Failed to restart instance:', error);
|
}
|
});
|
}
|
|
async function disconnectClient(instanceId, clientId) {
|
confirmAction(`确定要断开客户端 "${clientId}" 吗?`, async () => {
|
try {
|
await apiCall(`${API_BASE_URL}/instances/${encodeURIComponent(instanceId)}/Clients/${encodeURIComponent(clientId)}`, {
|
method: 'DELETE'
|
});
|
showToast('客户端已断开', 'success');
|
setTimeout(() => location.reload(), 500);
|
} catch (error) {
|
console.error('Failed to disconnect client:', error);
|
}
|
});
|
}
|
|
// Stop auto-refresh when page is hidden
|
document.addEventListener('visibilitychange', function () {
|
if (document.hidden) {
|
if (refreshInterval) clearInterval(refreshInterval);
|
} else {
|
startAutoRefresh();
|
}
|
});
|
</script>
|
}
|