Two-column layout: org+instances left, services right (collapsible)
This commit is contained in:
+9
-4
@@ -1,7 +1,7 @@
|
|||||||
from flask import Blueprint, current_app, render_template, request, make_response, jsonify
|
from flask import Blueprint, current_app, render_template, request, make_response, jsonify
|
||||||
|
|
||||||
from api.http_client import HttpClient
|
from api.http_client import HttpClient
|
||||||
from operations.get_instances import get_organization
|
from operations.get_instances import get_organization, get_instances
|
||||||
from operations.get_services import get_services, get_service_detail
|
from operations.get_services import get_services, get_service_detail
|
||||||
|
|
||||||
bp = Blueprint("main", __name__)
|
bp = Blueprint("main", __name__)
|
||||||
@@ -42,6 +42,7 @@ def index():
|
|||||||
return resp
|
return resp
|
||||||
|
|
||||||
services = []
|
services = []
|
||||||
|
instances = []
|
||||||
if active_token:
|
if active_token:
|
||||||
try:
|
try:
|
||||||
client = HttpClient(
|
client = HttpClient(
|
||||||
@@ -49,8 +50,11 @@ def index():
|
|||||||
active_token,
|
active_token,
|
||||||
)
|
)
|
||||||
org = get_organization(client)
|
org = get_organization(client)
|
||||||
raw = get_services(client)
|
raw_svc = get_services(client)
|
||||||
services = sorted(raw, key=lambda s: (s.get("svcId", 0), s.get("svc", "")))
|
services = sorted(raw_svc, key=lambda s: (s.get("svcId", 0), s.get("svc", "")))
|
||||||
|
instances = get_instances(client)
|
||||||
|
# org first, then rest
|
||||||
|
instances.sort(key=lambda i: (0 if i.get("serviceId") == 19 else 1, i.get("displayName", "")))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
error = str(e)
|
error = str(e)
|
||||||
|
|
||||||
@@ -58,7 +62,8 @@ def index():
|
|||||||
organization=org, error=error,
|
organization=org, error=error,
|
||||||
env_token_masked=_mask(env_token),
|
env_token_masked=_mask(env_token),
|
||||||
has_user_token=bool(user_token),
|
has_user_token=bool(user_token),
|
||||||
services=services)
|
services=services,
|
||||||
|
instances=instances)
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/api/operations/<int:svc_id>")
|
@bp.route("/api/operations/<int:svc_id>")
|
||||||
|
|||||||
+65
-28
@@ -6,61 +6,99 @@
|
|||||||
<link rel="icon" type="image/svg+xml" href="{{ url_for('static', filename='favicon.svg') }}" />
|
<link rel="icon" type="image/svg+xml" href="{{ url_for('static', filename='favicon.svg') }}" />
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}" />
|
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}" />
|
||||||
<script src="https://unpkg.com/lucide@latest"></script>
|
<script src="https://unpkg.com/lucide@latest"></script>
|
||||||
|
<style>
|
||||||
|
.layout { display:flex; gap:16px; max-width:1200px; margin:16px auto; padding:0 16px; }
|
||||||
|
.left { width:380px; flex-shrink:0; }
|
||||||
|
.right { flex:1; min-width:0; }
|
||||||
|
.svc-row { cursor:pointer; }
|
||||||
|
.inst-row { cursor:default; }
|
||||||
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="card" style="max-width: 780px; margin: 32px auto;">
|
|
||||||
<div class="card-header" style="justify-content: space-between;">
|
<form method="post" style="max-width:1200px;margin:16px auto 0;padding:0 16px;display:flex;align-items:center;gap:8px;">
|
||||||
<span>Организация</span>
|
|
||||||
<form method="post" style="display:flex;align-items:center;gap:6px;">
|
|
||||||
<input type="password" name="token" placeholder="env: {{ env_token_masked }}" value="{{ '' if not has_user_token else '••••••••' }}" style="height:28px;width:200px;font-size:12px;border:1px solid var(--brand-gray);border-radius:4px;padding:0 6px;" />
|
<input type="password" name="token" placeholder="env: {{ env_token_masked }}" value="{{ '' if not has_user_token else '••••••••' }}" style="height:28px;width:200px;font-size:12px;border:1px solid var(--brand-gray);border-radius:4px;padding:0 6px;" />
|
||||||
<button type="submit" name="action" value="save" class="btn" style="height:28px;font-size:12px;padding:0 8px;">OK</button>
|
<button type="submit" name="action" value="save" class="btn" style="height:28px;font-size:12px;padding:0 8px;">OK</button>
|
||||||
{% if has_user_token %}
|
{% if has_user_token %}
|
||||||
<button type="submit" name="action" value="clear" class="btn btn-danger" style="height:28px;font-size:12px;padding:0 8px;">Выйти</button>
|
<button type="submit" name="action" value="clear" class="btn btn-danger" style="height:28px;font-size:12px;padding:0 8px;">Выйти</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if error %}<span style="color:var(--destructive);font-size:12px;">{{ error }}</span>{% endif %}
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
<div class="layout">
|
||||||
|
|
||||||
|
<!-- ЛЕВАЯ КОЛОНКА -->
|
||||||
|
<div class="left">
|
||||||
|
{% if organization %}
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">Организация</div>
|
||||||
|
<div class="card-body" style="font-size:12px;">
|
||||||
|
<table>
|
||||||
|
<tr><td style="color:var(--muted);">Имя</td><td>{{ organization.displayName }}</td></tr>
|
||||||
|
<tr><td style="color:var(--muted);">UID</td><td style="font-family:monospace;font-size:11px;">{{ organization.instanceUid }}</td></tr>
|
||||||
|
<tr><td style="color:var(--muted);">Статус</td><td><span class="badge badge-success">{{ organization.explainedStatus or "OK" }}</span></td></tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if instances %}
|
||||||
|
<div class="card" style="margin-top:8px;">
|
||||||
|
<div class="card-header" style="cursor:pointer;" onclick="this.nextElementSibling.style.display=this.nextElementSibling.style.display==='none'?'block':'none'">
|
||||||
|
Инстансы ({{ instances|length }})
|
||||||
|
</div>
|
||||||
|
<div class="card-body" style="padding:0;max-height:300px;overflow-y:auto;">
|
||||||
|
<table>
|
||||||
|
{% for i in instances %}
|
||||||
|
<tr class="inst-row">
|
||||||
|
<td style="font-size:12px;">{{ i.displayName }}</td>
|
||||||
|
<td style="font-size:11px;color:var(--muted);">{{ i.svc }}</td>
|
||||||
|
<td><span class="badge badge-success" style="font-size:10px;">{{ i.explainedStatus or "?" }}</span></td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
|
||||||
{% if error %}
|
|
||||||
<p style="color: var(--destructive);">{{ error }}</p>
|
|
||||||
{% elif organization %}
|
|
||||||
<p>{{ organization.displayName }} — {{ organization.explainedStatus or "OK" }}</p>
|
|
||||||
{% else %}
|
|
||||||
<p style="color: var(--muted);">Введите токен</p>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
|
<!-- ПРАВАЯ КОЛОНКА -->
|
||||||
|
<div class="right">
|
||||||
{% if services %}
|
{% if services %}
|
||||||
<div class="card" style="max-width: 780px; margin: 16px auto;">
|
<div class="card">
|
||||||
<div class="card-header">Сервисы ({{ services|length }})</div>
|
<div class="card-header" style="cursor:pointer;" onclick="document.getElementById('svc-table').style.display=document.getElementById('svc-table').style.display==='none'?'':'none'">
|
||||||
<div class="card-body" style="padding: 0;">
|
Сервисы ({{ services|length }})
|
||||||
|
</div>
|
||||||
|
<div id="svc-table" style="display:none;">
|
||||||
|
<div class="card-body" style="padding:0;max-height:60vh;overflow-y:auto;">
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th style="width:50px;">#</th>
|
<th style="width:50px;">#</th>
|
||||||
<th>Сервис</th>
|
<th>Сервис</th>
|
||||||
<th style="width:80px;">Операции</th>
|
<th style="width:70px;">Опер.</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for s in services %}
|
{% for s in services %}
|
||||||
<tr class="svc-row" data-svc-id="{{ s.svcId }}" style="cursor:pointer;">
|
<tr class="svc-row" data-svc-id="{{ s.svcId }}">
|
||||||
<td>{{ s.svcId }}</td>
|
<td>{{ s.svcId }}</td>
|
||||||
<td>{{ s.svc }}{% if s.svcExtendedName %} — {{ s.svcExtendedName }}{% endif %}</td>
|
<td style="font-size:12px;">{{ s.svc }}{% if s.svcExtendedName %} <span style="color:var(--muted);">— {{ s.svcExtendedName }}</span>{% endif %}</td>
|
||||||
<td style="text-align:center;">
|
<td style="text-align:center;"><span class="ops-count" id="ops-{{ s.svcId }}">?</span></td>
|
||||||
<span class="ops-count" id="ops-{{ s.svcId }}">?</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr class="ops-detail" id="detail-{{ s.svcId }}" style="display:none;">
|
<tr class="ops-detail" id="detail-{{ s.svcId }}" style="display:none;">
|
||||||
<td colspan="3" style="padding:4px 10px;background:var(--brand-grey-light);">
|
<td colspan="3" style="padding:4px 10px;background:var(--brand-grey-light);"></td>
|
||||||
<span style="color:var(--muted);font-size:12px;">Загрузка...</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const opsCache = {};
|
const opsCache = {};
|
||||||
@@ -81,13 +119,14 @@
|
|||||||
fillDetail(svcId, opsCache[svcId]);
|
fillDetail(svcId, opsCache[svcId]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
detail.cells[0].innerHTML = '<span style=\"color:var(--muted);font-size:12px;\">Загрузка...</span>';
|
||||||
try {
|
try {
|
||||||
const r = await fetch('/api/operations/' + svcId);
|
const r = await fetch('/api/operations/' + svcId);
|
||||||
const d = await r.json();
|
const d = await r.json();
|
||||||
opsCache[svcId] = d;
|
opsCache[svcId] = d;
|
||||||
fillDetail(svcId, d);
|
fillDetail(svcId, d);
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
detail.cells[0].innerHTML = '<span style=\"color:var(--destructive);font-size:12px;\">Ошибка загрузки</span>';
|
detail.cells[0].innerHTML = '<span style=\"color:var(--destructive);font-size:12px;\">Ошибка</span>';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -101,12 +140,10 @@
|
|||||||
const ops = data.operations || [];
|
const ops = data.operations || [];
|
||||||
document.getElementById('ops-' + svcId).textContent = ops.length;
|
document.getElementById('ops-' + svcId).textContent = ops.length;
|
||||||
detail.cells[0].innerHTML = ops.map(o =>
|
detail.cells[0].innerHTML = ops.map(o =>
|
||||||
'<span class=\"badge\" style=\"margin:2px;\">' + o.operation + '</span>'
|
'<span class=\"badge\" style=\"margin:2px;font-size:11px;\">' + o.operation + '</span>'
|
||||||
).join('') || '<span style=\"color:var(--muted);font-size:12px;\">Нет операций</span>';
|
).join('') || '<span style=\"color:var(--muted);font-size:12px;\">Нет операций</span>';
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<script>lucide.createIcons();</script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Reference in New Issue
Block a user