feat: weather-demo MQ pipeline + sqs-consumer v1.2 with JWT auto-refresh
- sqs-consumer: new Go binary v1.2 with tokenManager (auto-login /auth/login, 5min cache, retry on 401) - console: add MQ/KW triggers UI (mqtriggers.go, kwtriggers.go, mq.js) - console: update python-env image to v1.1 (boto3+psycopg2+requests) - weather-demo: fix consumer/main.py (Flask Request.get_json instead of dict access) - weather-demo: fix fetcher/main.py (boto3 SQS publish, 5 cities) - weather-demo: update main.tf (python-env v1.1, deploy_type=literal) - python-env: add psycopg2-binary to Dockerfile (v1.1) - terraform provider: client auth fix
This commit is contained in:
+73
-2
@@ -25,6 +25,7 @@
|
||||
<script src="js/fn-archive.js"></script>
|
||||
<script src="js/ai.js"></script>
|
||||
<script src="js/app.js"></script>
|
||||
<script src="js/mq.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
@@ -102,7 +103,7 @@
|
||||
<div class="nubes">NUBES</div>
|
||||
<div class="product">FISSION CONSOLE</div>
|
||||
</div>
|
||||
<div style="font-size:0.65rem; color:var(--text-secondary); margin-left:10px; align-self:center; opacity:0.7;">v1.3.87</div>
|
||||
<div style="font-size:0.65rem; color:var(--text-secondary); margin-left:10px; align-self:center; opacity:0.7;">v1.3.88</div>
|
||||
</div>
|
||||
<div class="row" style="margin:0;">
|
||||
<button class="btn ghost" onclick="reloadAll()">Refresh</button>
|
||||
@@ -136,6 +137,10 @@
|
||||
<div class="k">Крон-функции</div>
|
||||
<div id="cron-count" class="v">-</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="k">MQ-триггеры</div>
|
||||
<div id="mq-count" class="v">-</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="box">
|
||||
@@ -174,6 +179,72 @@
|
||||
<div id="status" class="status"></div>
|
||||
<div class="hint">Изменения применяются напрямую через CRD Fission.</div>
|
||||
</div>
|
||||
|
||||
<!-- MQ-триггеры -->
|
||||
<div class="box">
|
||||
<div class="toolbar">
|
||||
<div style="font-weight:600;">MQ-триггеры</div>
|
||||
<button class="btn" onclick="openCreateMQ()">+ Добавить</button>
|
||||
</div>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Имя</th>
|
||||
<th>Очередь</th>
|
||||
<th>Функция</th>
|
||||
<th>Endpoint SQS</th>
|
||||
<th>Статус</th>
|
||||
<th>Действия</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="mq-rows">
|
||||
<tr><td colspan="6" style="color:var(--fg-muted,#888);text-align:center;">Загрузка...</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="hint">MQ-триггер поллит очередь SQS и вызывает Fission-функцию при появлении сообщений.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Модалка: создать MQ-триггер -->
|
||||
<div id="mq-create-modal" class="modal">
|
||||
<div class="panel" style="width:520px;max-width:96vw;">
|
||||
<h3>📨 Создать MQ-триггер</h3>
|
||||
<div class="row">
|
||||
<div class="field">
|
||||
<label>Имя триггера</label>
|
||||
<input id="mq-name" placeholder="weather-trigger">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Функция</label>
|
||||
<input id="mq-fn" placeholder="weather-store">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="field">
|
||||
<label>Имя очереди (SQS)</label>
|
||||
<input id="mq-queue" placeholder="weather-raw">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>SQS Endpoint</label>
|
||||
<input id="mq-endpoint" placeholder="http://shared-sqs.shared-sqs.svc.cluster.local:4100">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="field">
|
||||
<label>Access Key</label>
|
||||
<input id="mq-access-key" placeholder="SSAK-...">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Secret Key</label>
|
||||
<input id="mq-secret-key" type="password" placeholder="...">
|
||||
</div>
|
||||
</div>
|
||||
<div id="mq-create-error" class="modal-error"></div>
|
||||
<div class="actions">
|
||||
<button class="btn ghost" onclick="closeMQCreate()">Отмена</button>
|
||||
<button class="btn" onclick="submitCreateMQ()">Создать</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Модалка: создать функцию из кода (prefix cc-) -->
|
||||
@@ -543,7 +614,7 @@
|
||||
</div>
|
||||
|
||||
<div class="actions" style="justify-content:space-between; align-items:center;">
|
||||
<span style="font-size:0.75rem; color:var(--text-secondary);">v1.3.87</span>
|
||||
<span style="font-size:0.75rem; color:var(--text-secondary);">v1.3.88</span>
|
||||
<button class="btn ghost" onclick="closeHelp()">Закрыть</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -16,6 +16,9 @@ async function reloadAll() {
|
||||
getJSON(API_BASE + '/timetriggers')
|
||||
]);
|
||||
|
||||
// MQ-триггеры загружаем параллельно, не блокируем основную таблицу
|
||||
if (typeof loadMQTriggers === 'function') loadMQTriggers();
|
||||
|
||||
S.envs = envs || [];
|
||||
S.fns = fns || [];
|
||||
S.httpTriggers = http || [];
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
// mq.js — MQ-триггеры (SQS → Fission function)
|
||||
// Архитектура: Console создаёт K8s Deployment + Secret (sqs-consumer) в namespace пользователя.
|
||||
// sqs-consumer поллит SQS очередь → HTTP POST в Fission-функцию → DeleteMessage.
|
||||
// Backend: POST /console/api/mqtriggers, DELETE /console/api/mqtriggers/{name}
|
||||
// v1.3.88
|
||||
|
||||
// ── Загрузка и отрисовка ────────────────────────────────────────────
|
||||
|
||||
async function loadMQTriggers() {
|
||||
try {
|
||||
const data = await apiFetch('/console/api/mqtriggers');
|
||||
renderMQTable(data.items || []);
|
||||
const cnt = document.getElementById('mq-count');
|
||||
if (cnt) cnt.textContent = (data.items || []).length;
|
||||
} catch (e) {
|
||||
renderMQTable([]);
|
||||
const cnt = document.getElementById('mq-count');
|
||||
if (cnt) cnt.textContent = '0';
|
||||
}
|
||||
}
|
||||
|
||||
function renderMQTable(items) {
|
||||
const tbody = document.getElementById('mq-rows');
|
||||
if (!tbody) return;
|
||||
if (!items.length) {
|
||||
tbody.innerHTML = '<tr><td colspan="6" style="color:var(--fg-muted,#888);text-align:center;">Нет MQ-триггеров</td></tr>';
|
||||
return;
|
||||
}
|
||||
tbody.innerHTML = items.map(t => `
|
||||
<tr>
|
||||
<td>${escHtml(t.name)}</td>
|
||||
<td>${escHtml(t.queue)}</td>
|
||||
<td>${escHtml(t.functionName)}</td>
|
||||
<td style="font-size:0.8em;color:var(--fg-muted,#aaa);">${escHtml(t.sqsEndpoint || '')}</td>
|
||||
<td><span style="color:${t.ready ? '#4a4' : '#a44'}">${t.ready ? '▶ Running' : '◼ Pending'}</span></td>
|
||||
<td>
|
||||
<button class="btn ghost small" onclick="deleteMQTrigger('${escHtml(t.name)}')">Удалить</button>
|
||||
</td>
|
||||
</tr>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
// ── Создание ─────────────────────────────────────────────────────────
|
||||
|
||||
function openCreateMQ() {
|
||||
document.getElementById('mq-name').value = '';
|
||||
document.getElementById('mq-fn').value = '';
|
||||
document.getElementById('mq-queue').value = '';
|
||||
document.getElementById('mq-endpoint').value = 'http://shared-sqs.shared-sqs.svc.cluster.local:4100';
|
||||
document.getElementById('mq-access-key').value = '';
|
||||
document.getElementById('mq-secret-key').value = '';
|
||||
document.getElementById('mq-create-error').textContent = '';
|
||||
document.getElementById('mq-create-modal').style.display = 'flex';
|
||||
}
|
||||
|
||||
function closeMQCreate() {
|
||||
document.getElementById('mq-create-modal').style.display = 'none';
|
||||
}
|
||||
|
||||
async function submitCreateMQ() {
|
||||
const name = document.getElementById('mq-name').value.trim();
|
||||
const fnName = document.getElementById('mq-fn').value.trim();
|
||||
const queue = document.getElementById('mq-queue').value.trim();
|
||||
const endpoint = document.getElementById('mq-endpoint').value.trim();
|
||||
const accessKey = document.getElementById('mq-access-key').value.trim();
|
||||
const secretKey = document.getElementById('mq-secret-key').value.trim();
|
||||
const errEl = document.getElementById('mq-create-error');
|
||||
|
||||
if (!name || !fnName || !queue || !accessKey || !secretKey) {
|
||||
errEl.textContent = 'Заполните все поля';
|
||||
return;
|
||||
}
|
||||
|
||||
errEl.textContent = '';
|
||||
try {
|
||||
await apiFetch('/console/api/mqtriggers', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ name, functionName: fnName, queue, sqsEndpoint: endpoint, accessKey, secretKey })
|
||||
});
|
||||
closeMQCreate();
|
||||
await loadMQTriggers();
|
||||
} catch (e) {
|
||||
errEl.textContent = e.message || 'Ошибка создания';
|
||||
}
|
||||
}
|
||||
|
||||
// ── Удаление ─────────────────────────────────────────────────────────
|
||||
|
||||
async function deleteMQTrigger(name) {
|
||||
if (!confirm(`Удалить MQ-триггер "${name}"?`)) return;
|
||||
try {
|
||||
await apiFetch(`/console/api/mqtriggers/${encodeURIComponent(name)}`, { method: 'DELETE' });
|
||||
await loadMQTriggers();
|
||||
} catch (e) {
|
||||
alert('Ошибка удаления: ' + (e.message || e));
|
||||
}
|
||||
}
|
||||
|
||||
// ── Утилита ──────────────────────────────────────────────────────────
|
||||
|
||||
function escHtml(s) {
|
||||
return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
|
||||
}
|
||||
Reference in New Issue
Block a user