Add services dropdown with operations panel
This commit is contained in:
@@ -0,0 +1,11 @@
|
|||||||
|
from api.http_client import HttpClient
|
||||||
|
|
||||||
|
|
||||||
|
def get_services(client):
|
||||||
|
data = client.get("/services")
|
||||||
|
return data.get("results", [])
|
||||||
|
|
||||||
|
|
||||||
|
def get_service_detail(client, svc_id):
|
||||||
|
data = client.get(f"/services/{svc_id}")
|
||||||
|
return data.get("svc", {})
|
||||||
+21
-2
@@ -1,7 +1,8 @@
|
|||||||
from flask import Blueprint, current_app, render_template, request, make_response
|
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
|
||||||
|
from operations.get_services import get_services, get_service_detail
|
||||||
|
|
||||||
bp = Blueprint("main", __name__)
|
bp = Blueprint("main", __name__)
|
||||||
|
|
||||||
@@ -40,6 +41,7 @@ def index():
|
|||||||
resp.status_code = 302
|
resp.status_code = 302
|
||||||
return resp
|
return resp
|
||||||
|
|
||||||
|
services = []
|
||||||
if active_token:
|
if active_token:
|
||||||
try:
|
try:
|
||||||
client = HttpClient(
|
client = HttpClient(
|
||||||
@@ -47,10 +49,27 @@ def index():
|
|||||||
active_token,
|
active_token,
|
||||||
)
|
)
|
||||||
org = get_organization(client)
|
org = get_organization(client)
|
||||||
|
raw = get_services(client)
|
||||||
|
services = sorted(raw, key=lambda s: (s.get("svcId", 0), s.get("svc", "")))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
error = str(e)
|
error = str(e)
|
||||||
|
|
||||||
return render_template("index.html",
|
return render_template("index.html",
|
||||||
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)
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/api/operations/<int:svc_id>")
|
||||||
|
def api_operations(svc_id):
|
||||||
|
env_token = current_app.config["NUBES_API_TOKEN"]
|
||||||
|
user_token = request.cookies.get("token") or ""
|
||||||
|
active_token = user_token or env_token
|
||||||
|
try:
|
||||||
|
client = HttpClient(current_app.config["NUBES_API_ENDPOINT"], active_token)
|
||||||
|
detail = get_service_detail(client, svc_id)
|
||||||
|
ops = detail.get("operations", [])
|
||||||
|
return jsonify({"svc": detail.get("svc", ""), "operations": ops})
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({"error": str(e)}), 500
|
||||||
|
|||||||
+79
-20
@@ -20,30 +20,89 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
{% if organization %}
|
{% if error %}
|
||||||
<table>
|
<p style="color: var(--destructive);">{{ error }}</p>
|
||||||
<thead>
|
{% elif organization %}
|
||||||
<tr>
|
<p>{{ organization.displayName }} — {{ organization.explainedStatus or "OK" }}</p>
|
||||||
<th>Имя</th>
|
|
||||||
<th>instanceUid</th>
|
|
||||||
<th>Сервис</th>
|
|
||||||
<th>Статус</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td>{{ organization.displayName }}</td>
|
|
||||||
<td style="font-family: monospace; font-size: 12px;">{{ organization.instanceUid }}</td>
|
|
||||||
<td>{{ organization.svc }}</td>
|
|
||||||
<td><span class="badge badge-success">{{ organization.explainedStatus or "OK" }}</span></td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
{% else %}
|
{% else %}
|
||||||
<p style="color: var(--muted);">{% if error %}Ошибка: {{ error }}{% else %}Введите токен{% endif %}</p>
|
<p style="color: var(--muted);">Введите токен</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{% if services %}
|
||||||
|
<div class="card" style="max-width: 780px; margin: 16px auto;">
|
||||||
|
<div class="card-header">Сервисы ({{ services|length }})</div>
|
||||||
|
<div class="card-body" style="padding: 0;">
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style="width:50px;">#</th>
|
||||||
|
<th>Сервис</th>
|
||||||
|
<th style="width:80px;">Операции</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for s in services %}
|
||||||
|
<tr class="svc-row" data-svc-id="{{ s.svcId }}" style="cursor:pointer;">
|
||||||
|
<td>{{ s.svcId }}</td>
|
||||||
|
<td>{{ s.svc }}{% if s.svcExtendedName %} — {{ s.svcExtendedName }}{% endif %}</td>
|
||||||
|
<td style="text-align:center;">
|
||||||
|
<span class="ops-count" id="ops-{{ s.svcId }}">?</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card" id="ops-panel" style="max-width: 780px; margin: 16px auto; display: none;">
|
||||||
|
<div class="card-header" id="ops-title">Операции</div>
|
||||||
|
<div class="card-body" id="ops-body" style="padding: 8px 14px;"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const opsCache = {};
|
||||||
|
document.querySelectorAll('.svc-row').forEach(row => {
|
||||||
|
row.addEventListener('click', async () => {
|
||||||
|
const svcId = row.dataset.svcId;
|
||||||
|
document.querySelectorAll('.svc-row').forEach(r => r.style.background = '');
|
||||||
|
row.style.background = 'var(--brand-grey-light)';
|
||||||
|
if (opsCache[svcId]) {
|
||||||
|
showOps(svcId, opsCache[svcId]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const r = await fetch('/api/operations/' + svcId);
|
||||||
|
const d = await r.json();
|
||||||
|
opsCache[svcId] = d;
|
||||||
|
showOps(svcId, d);
|
||||||
|
} catch(e) {
|
||||||
|
document.getElementById('ops-body').innerHTML = '<p style=\"color:var(--destructive);\">Ошибка</p>';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function showOps(svcId, data) {
|
||||||
|
const panel = document.getElementById('ops-panel');
|
||||||
|
const title = document.getElementById('ops-title');
|
||||||
|
const body = document.getElementById('ops-body');
|
||||||
|
panel.style.display = 'block';
|
||||||
|
title.textContent = 'Операции: ' + data.svc + ' (svcId=' + svcId + ')';
|
||||||
|
if (data.error) {
|
||||||
|
body.innerHTML = '<p style=\"color:var(--destructive);\">' + data.error + '</p>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const ops = data.operations || [];
|
||||||
|
document.getElementById('ops-' + svcId).textContent = ops.length;
|
||||||
|
body.innerHTML = ops.map(o =>
|
||||||
|
'<span class=\"badge\" style=\"margin:2px;\">' + o.operation + '</span>'
|
||||||
|
).join('') || '<p style=\"color:var(--muted);\">Нет операций</p>';
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<script>lucide.createIcons();</script>
|
<script>lucide.createIcons();</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Reference in New Issue
Block a user