482 lines
26 KiB
HTML
482 lines
26 KiB
HTML
<!doctype html>
|
||
<html lang="ru">
|
||
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<title>NUBES Fission Console</title>
|
||
<link rel="icon" type="image/png"
|
||
href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAA4ZJREFUeJztm8+Lm0UYxz/fySZUoWIrVtbmXfyBP05Fa3e3rogsglJRK0p7E2svnrz6B3gQ8eRJFooXET14KKJ4FUQo3UQQ9CRC6W4WQW0VXZduTd7HQ9c22bzbTCY/ZpO8n0syb56Z+c437zPvhMzIzJhkXGwBsckNiC0gNrkBsQXEZsonSJLj4GNPgb2ACo9j9gDOvrSV6qkB6+sJ6WSJ5MIxUo4h5sDuI9U7tlZ57/+YTAN0zyOPUi+9jOxJxMOUj9wJOBBg117QvqGMwhNpcQ/J+rOkPIc0j+xeytyGsaWXa2+kvc312gxQMleHYgFtrQ9GYJmgmSfup3z15xuDNW/dGXOAjd680NgshlYdvcH2mfEwQIU0tOp4GNADuQGxBcRmPAyQgh/W42FAD+QGxBYQm/EwIF8HhJMbEFtAX3DreQqEkhsQW0BsxsMAlz8Gg8kNiC0gNuNhgJvK54BQcgNiC+gLhUlPgcuTbkAP5AbEFhCbcAOM4LyLitIW3eEGyP7qWUwMHFdaixNO1v4Av5pG8H/yfWfvesk7tq5GczHDAHnmtu727nTQbJYe8o51+qOl2BYgNv1a0iHvTgeNGq94x6buYnOx3YCU3/1asn06ePQZ744HiXTCO3a6/m1zsd0Ax/f+HTc+kU76598AUDL/LpjfjjWxYdXqRvOljDvAfezfO3dQvvCDtLjHu04f0czsaSx9y7uC8eP2S20GWO38Z+A7DwDwIOX135TMvylpKI9VJQv7lcx+hfEhurELsCPOfdDWVtZ5ASWzZ4GXArQ1QJfA/kTW6BhtLgX+7qL9IqTTmKa7GjgAumKry7dsv5q9Vfbfq29QLL1I9wulAtgB4ADmoy9kY4fodugAODuT2dpOJ0aUzH8E6asBXe0+zP5h7bvbzay+/aOdv+Ha8mnE5YEKGxqF17MGDzcxwMzqTBUWgc65vKuxT7cm9kx2TIHrATOzxzHOEpZ5sanYamXuZgEdJzlbqXyOmzrO6N0J56hVj3YK8prl7eK5L6g3DmFc6l3XEDCWbLWyYGYdf9h1TIGWYMmRHDlDaqcY0qKnO/Qr4oStLH/jXSPk4KSShf2ovoTZ80Db4iICv2D2ttWqS91WDDKgpYHy4adR8TWww8BdmN2KrOg5Zwoo+PV0">
|
||
<script>
|
||
if (window.location.protocol !== 'https:' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') {
|
||
window.location.replace('https://' + window.location.host + window.location.pathname + window.location.search + window.location.hash);
|
||
}
|
||
</script>
|
||
<link rel="stylesheet" href="css/styles.css">
|
||
<script src="js/config.js"></script>
|
||
<script src="js/api.js"></script>
|
||
<script src="js/ui.js"></script>
|
||
<script src="js/auth.js"></script>
|
||
<script src="js/triggers.js"></script>
|
||
<script src="js/modals.js"></script>
|
||
<script src="js/init.js"></script>
|
||
<script src="js/functions.js"></script>
|
||
<script src="js/ai.js"></script>
|
||
<script src="js/app.js"></script>
|
||
</head>
|
||
|
||
<body>
|
||
<div id="login-overlay"
|
||
style="display:none; position:fixed; inset:0; background:rgba(0,0,0,.85); z-index:999; align-items:center; justify-content:center; padding:16px;">
|
||
<div class="panel" style="width:min(440px,100%);">
|
||
<div class="brand" style="margin-bottom:24px;">
|
||
<div class="brand-mark">N</div>
|
||
<div class="brand-text">
|
||
<div class="nubes">NUBES</div>
|
||
<div class="product">FISSION CONSOLE</div>
|
||
</div>
|
||
</div>
|
||
<div class="row" style="flex-direction:column; gap:6px; margin-bottom:12px;">
|
||
<label style="font-size:12px; color:#999;">Стенд</label>
|
||
<select id="l-env"
|
||
style="background:var(--bg-base); border:1px solid var(--border); color:var(--fg); padding:8px 10px; border-radius:6px;">
|
||
<option value="dev">Dev</option>
|
||
<option value="test" selected>Test</option>
|
||
<option value="prod">Prod</option>
|
||
</select>
|
||
</div>
|
||
<div class="row" style="flex-direction:column; gap:6px; margin-bottom:12px;">
|
||
<label style="font-size:12px; color:#999;">Токен или логин</label>
|
||
<textarea id="l-token" rows="5"
|
||
style="background:var(--bg-base); border:1px solid var(--border); color:var(--fg); padding:8px 10px; border-radius:6px; font-family:monospace; font-size:12px; resize:vertical; width:100%; box-sizing:border-box;"
|
||
placeholder="Введите токен или любой логин (не менее 6 символов)..."></textarea>
|
||
<div style="font-size:11px; color:var(--text-secondary); margin-top:4px;">Токен из ЛК: <strong
|
||
style="color:#8bc7ff;">Личный кабинет → Профиль пользователя → Токены</strong><br>Или введите любой логин не менее 6 символов для демо-доступа.</div>
|
||
</div>
|
||
<div id="l-error" style="display:none; color:#ff6b6b; font-size:13px; margin-bottom:10px;"></div>
|
||
<button id="l-btn" class="btn" style="width:100%;" onclick="doLogin()">Войти</button>
|
||
</div>
|
||
</div>
|
||
<!-- Overlay: инициализация нового окружения -->
|
||
<div id="init-overlay"
|
||
style="display:none; position:fixed; inset:0; background:rgba(0,0,0,.88); z-index:1000; align-items:center; justify-content:center; padding:16px;">
|
||
<div class="panel" style="width:min(520px,100%);">
|
||
<div style="margin-bottom:16px;">
|
||
<div style="font-size:16px; font-weight:700; margin-bottom:6px;">⚙️ Инициализация окружения</div>
|
||
<div style="font-size:13px; color:var(--text-secondary);">Первый вход. Настраиваем ваше окружение — это займёт
|
||
~5 минут.</div>
|
||
</div>
|
||
<div style="display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:10px; margin-bottom:12px;">
|
||
<div style="background:#0d1827; border:1px solid var(--border); border-radius:6px; padding:10px;">
|
||
<div style="font-size:11px; color:var(--text-secondary); margin-bottom:4px;">Время настройки</div>
|
||
<div id="init-elapsed" style="font-size:18px; font-weight:700; color:#8bc7ff;">0с</div>
|
||
</div>
|
||
<div style="background:#0d1827; border:1px solid var(--border); border-radius:6px; padding:10px;">
|
||
<div style="font-size:11px; color:var(--text-secondary); margin-bottom:4px;">Текущий этап</div>
|
||
<div id="init-current-stage" style="font-size:14px; font-weight:600; color:#f4f7fb;">Ожидание старта</div>
|
||
</div>
|
||
</div>
|
||
<div id="init-summary"
|
||
style="display:none; margin-bottom:10px; background:#101c2b; border:1px solid var(--border); border-radius:6px; padding:10px 12px; font-size:13px; color:#d8e5f2;">
|
||
</div>
|
||
<div id="init-reason"
|
||
style="display:none; margin-bottom:10px; background:#2d2310; border:1px solid #6a5427; border-radius:6px; padding:10px 12px; font-size:13px; color:#ffd98a;">
|
||
</div>
|
||
<div id="init-error"
|
||
style="display:none; margin-bottom:10px; background:#2a1416; border:1px solid #7d3138; border-radius:6px; padding:10px 12px; font-size:13px; color:#ff9da5;">
|
||
</div>
|
||
<div id="init-log"
|
||
style="background:#000d1a; border:1px solid var(--border); border-radius:6px; padding:12px; font-family:monospace; font-size:12px; height:160px; overflow-y:auto; color:#4fc3f7; white-space:pre-wrap; line-height:1.6;">
|
||
</div>
|
||
<div id="init-stages" style="margin-top:14px; display:flex; flex-direction:column; gap:8px;"></div>
|
||
<div style="margin-top:14px; font-size:12px; color:var(--text-secondary);">Страница обновится автоматически когда
|
||
всё будет готово.</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="navbar">
|
||
<div class="brand-mark" id="user-avatar">N</div>
|
||
<div class="brand-text" style="margin-left:auto; text-align:right;">
|
||
<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.32</div>
|
||
</div>
|
||
<div class="row" style="margin:0;">
|
||
<button class="btn ghost" onclick="reloadAll()">Refresh</button>
|
||
<button class="btn" onclick="openCreate()">+ Создать функцию</button>
|
||
<button class="btn ghost" onclick="openHelp()">Help</button>
|
||
<button class="btn ghost" onclick="doLogout()" style="margin-left:8px;">Выход</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="wrap">
|
||
<div class="grid">
|
||
<div class="card">
|
||
<div class="k">Окружения</div>
|
||
<div id="env-count" class="v">-</div>
|
||
</div>
|
||
<div class="card">
|
||
<div class="k">Пакеты</div>
|
||
<div id="pkg-count" class="v">-</div>
|
||
</div>
|
||
<div class="card">
|
||
<div class="k">Функции</div>
|
||
<div id="fn-count" class="v">-</div>
|
||
</div>
|
||
<div class="card">
|
||
<div class="k">HTTP-триггеры</div>
|
||
<div id="http-count" class="v">-</div>
|
||
</div>
|
||
<div class="card">
|
||
<div class="k">Крон-функции</div>
|
||
<div id="cron-count" class="v">-</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="box">
|
||
<div class="toolbar">
|
||
<div style="font-weight:600;">Функции</div>
|
||
<div class="hint">Actions: view, edit code, invoke, delete</div>
|
||
</div>
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>Имя</th>
|
||
<th>Окружение</th>
|
||
<th>Пакет</th>
|
||
<th>Создана</th>
|
||
<th>Изменена</th>
|
||
<th>Маршрут</th>
|
||
<th>Методы</th>
|
||
<th>Cron</th>
|
||
<th class="nowrap">Действия</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="fn-rows"></tbody>
|
||
</table>
|
||
<div id="status" class="status"></div>
|
||
<div class="hint">Изменения применяются напрямую через CRD Fission.</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="create-modal" class="modal">
|
||
<div class="panel">
|
||
<h3>Создать функцию</h3>
|
||
<div class="row">
|
||
<div class="field">
|
||
<label>Name</label>
|
||
<input id="c-name" placeholder="demo-fn">
|
||
</div>
|
||
<div class="field">
|
||
<label>Language</label>
|
||
<select id="c-lang" onchange="onLangChange()">
|
||
<option value="python">Python</option>
|
||
<option value="nodejs">Node.js</option>
|
||
<option value="php">PHP</option>
|
||
<option value="ruby">Ruby</option>
|
||
</select>
|
||
</div>
|
||
<div class="field">
|
||
<label>Entrypoint</label>
|
||
<input id="c-entry" value="main.main">
|
||
</div>
|
||
</div>
|
||
<div class="row">
|
||
<div class="field">
|
||
<label>Route</label>
|
||
<input id="c-route" placeholder="/demo-fn">
|
||
</div>
|
||
<div class="field">
|
||
<label>Методы (через запятую)</label>
|
||
<input id="c-methods" value="GET">
|
||
</div>
|
||
<div class="field">
|
||
<label>Timeout (сек)</label>
|
||
<input id="c-timeout" type="number" min="1" value="60">
|
||
</div>
|
||
</div>
|
||
<div class="row">
|
||
<div class="field" style="min-width:240px; flex:0 0 260px;">
|
||
<label style="display:flex; align-items:center; gap:8px; color:var(--text-primary); margin-top:18px;">
|
||
<input id="c-schedule-enabled" type="checkbox" onchange="toggleScheduleFields('c')" style="width:auto;">
|
||
Выполнять по расписанию
|
||
</label>
|
||
</div>
|
||
<div class="field">
|
||
<label>Cron</label>
|
||
<input id="c-cron" placeholder="*/5 * * * *" disabled>
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<div style="display:flex; gap:4px; margin-bottom:6px;">
|
||
<button class="btn ghost" id="c-mode-code" onclick="setCodeMode('c','code')" style="font-size:.8rem; padding:3px 10px;">✏️ Код</button>
|
||
<button class="btn ghost" id="c-mode-archive" onclick="setCodeMode('c','archive')" style="font-size:.8rem; padding:3px 10px;">📦 Архив (.zip)</button>
|
||
</div>
|
||
<div id="c-code-area">
|
||
<textarea id="c-code">def main():
|
||
return {"ok": True, "msg": "hello from fission console"}
|
||
</textarea>
|
||
<div style="margin-top:6px; display:flex; gap:6px; flex-wrap:wrap;">
|
||
<button class="btn ghost" id="c-ai-btn" onclick="aiCheck('c-code','c-lang','c-ai-result')">🔍 Проверить
|
||
синтаксис линтером</button>
|
||
<button class="btn ghost" id="c-gen-btn" onclick="showGenPrompt()">✨ Сгенерировать код LLM</button>
|
||
<button class="btn ghost" id="c-exp-btn" onclick="aiExplain('c-code','c-lang','c-ai-result')">📖 LLM:
|
||
Что делает?</button>
|
||
</div>
|
||
</div>
|
||
<div id="c-archive-area" style="display:none;">
|
||
<input type="file" id="c-archive-file" accept=".zip" style="display:block; margin-bottom:8px; color:var(--text-primary);">
|
||
<div style="display:flex; gap:6px; flex-wrap:wrap;">
|
||
<button class="btn ghost" onclick="lintArchiveFile('c')">🔍 Проверить архив линтером</button>
|
||
</div>
|
||
</div>
|
||
<div id="c-gen-prompt" style="display:none; margin-top:8px; display:none; gap:6px; align-items:center;">
|
||
<input id="c-gen-desc" type="text"
|
||
placeholder="Что должна делать функция? (напр: принять JSON, вернуть сумму чисел)" style="flex:1; background:#2a2a42; border:1px solid #3a3a5c; border-radius:4px;
|
||
color:#cdd6f4; padding:6px 8px; font-size:.82rem; outline:none; width:100%;"
|
||
onkeydown="if(event.key==='Enter')aiGenerate()" />
|
||
<button class="btn" onclick="aiGenerate()" style="white-space:nowrap;">▶ Генерировать LLM</button>
|
||
</div>
|
||
<div id="c-ai-result"
|
||
style="display:none;margin-top:8px;padding:10px 12px;border-radius:6px;font-size:13px;line-height:1.5;white-space:pre-wrap;font-family:monospace;">
|
||
</div>
|
||
</div>
|
||
<div class="actions">
|
||
<button class="btn ghost" onclick="closeCreate()">Отмена</button>
|
||
<button id="c-submit" class="btn" onclick="submitCreate()">Создать</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="edit-modal" class="modal">
|
||
<div class="panel">
|
||
<h3 id="e-title">Редактирование кода</h3>
|
||
<div id="e-tf-warn"
|
||
style="display:none;background:#553300;color:#ffa;padding:8px 12px;border-radius:6px;margin-bottom:10px;font-size:13px;">
|
||
⚠️ Эта функция управляется Terraform. Изменения могут быть перезаписаны при следующем
|
||
<code>terraform apply</code>.
|
||
</div>
|
||
<input type="hidden" id="e-lang-hidden" value="">
|
||
<div class="row">
|
||
<div class="field">
|
||
<label>Name</label>
|
||
<input id="e-name" disabled>
|
||
</div>
|
||
<div class="field">
|
||
<label>Environment</label>
|
||
<input id="e-env" disabled>
|
||
</div>
|
||
<div class="field">
|
||
<label>Entrypoint</label>
|
||
<input id="e-entry" disabled>
|
||
</div>
|
||
<div class="field">
|
||
<label>Timeout (сек)</label>
|
||
<input id="e-timeout" type="number" min="1" value="60">
|
||
</div>
|
||
</div>
|
||
<div class="row">
|
||
<div class="field" style="min-width:240px; flex:0 0 260px;">
|
||
<label style="display:flex; align-items:center; gap:8px; color:var(--text-primary); margin-top:18px;">
|
||
<input id="e-schedule-enabled" type="checkbox" onchange="toggleScheduleFields('e')" style="width:auto;">
|
||
Выполнять по расписанию
|
||
</label>
|
||
</div>
|
||
<div class="field">
|
||
<label>Cron</label>
|
||
<input id="e-cron" placeholder="*/5 * * * *" disabled>
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<div style="display:flex; gap:4px; margin-bottom:6px;">
|
||
<button class="btn ghost" id="e-mode-code" onclick="setCodeMode('e','code')" style="font-size:.8rem; padding:3px 10px;">✏️ Код</button>
|
||
<button class="btn ghost" id="e-mode-archive" onclick="setCodeMode('e','archive')" style="font-size:.8rem; padding:3px 10px;">📦 Архив (.zip)</button>
|
||
</div>
|
||
<div id="e-code-area">
|
||
<label>Код</label>
|
||
<textarea id="e-code"></textarea>
|
||
<div style="margin-top:6px; display:flex; gap:6px; flex-wrap:wrap;">
|
||
<button class="btn ghost" id="e-ai-btn" onclick="aiCheck('e-code','e-lang-hidden','e-ai-result')">🔍
|
||
Проверить синтаксис линтером</button>
|
||
<button class="btn ghost" id="e-exp-btn" onclick="aiExplain('e-code','e-lang-hidden','e-ai-result')">📖
|
||
LLM: Что делает?</button>
|
||
</div>
|
||
</div>
|
||
<div id="e-archive-area" style="display:none;">
|
||
<input type="file" id="e-archive-file" accept=".zip" style="display:block; margin-bottom:8px; color:var(--text-primary);">
|
||
<div style="display:flex; gap:6px; flex-wrap:wrap;">
|
||
<button class="btn ghost" onclick="lintArchiveFile('e')">🔍 Проверить архив линтером</button>
|
||
</div>
|
||
</div>
|
||
<div id="e-ai-result"
|
||
style="display:none;margin-top:8px;padding:10px 12px;border-radius:6px;font-size:13px;line-height:1.5;white-space:pre-wrap;font-family:monospace;">
|
||
</div>
|
||
</div>
|
||
<div class="actions">
|
||
<button class="btn ghost" onclick="closeEdit()">Отмена</button>
|
||
<button id="e-submit" class="btn" onclick="submitEdit()">Сохранить</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="help-modal" class="modal">
|
||
<div class="panel">
|
||
<h3>Help</h3>
|
||
|
||
<div class="help-section">
|
||
<h4>Поля при создании функции</h4>
|
||
<div class="help-list">
|
||
<div><strong>Name</strong> — имя Function в Fission. Используется в CRD и в связанных объектах.</div>
|
||
<div><strong>Language</strong> — язык runtime. От него зависят environment, шаблон кода и entrypoint по
|
||
умолчанию.</div>
|
||
<div><strong>Entrypoint</strong> — точка входа внутри пакета. Например, <span class="mono">main.main</span>
|
||
для Python или <span class="mono">main.php::handler</span> для PHP.</div>
|
||
<div><strong>Route</strong> — HTTP путь функции. Внешний пользовательский вызов идёт через <span
|
||
class="mono">/fn + ваш Route</span>.</div>
|
||
<div><strong>Методы</strong> — HTTP методы route, например <span class="mono">GET</span> или <span
|
||
class="mono">GET,POST</span>.</div>
|
||
<div><strong>Timeout (сек)</strong> — максимум времени выполнения именно этой функции.</div>
|
||
<div><strong>Код</strong> — исходный код функции.</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="help-section">
|
||
<h4>Поля при редактировании функции</h4>
|
||
<div class="help-list">
|
||
<div><strong>Name</strong> — только для просмотра, не редактируется.</div>
|
||
<div><strong>Environment</strong> — только для просмотра, не редактируется из этой формы.</div>
|
||
<div><strong>Entrypoint</strong> — только для просмотра, не редактируется из этой формы.</div>
|
||
<div><strong>Timeout (сек)</strong> — редактируется и сохраняется в <span
|
||
class="mono">spec.functionTimeout</span>.</div>
|
||
<div><strong>Код</strong> — редактируется и обновляет Package функции.</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="help-section">
|
||
<h4>Как вызвать функцию вне веб-интерфейса</h4>
|
||
<div class="help-list">
|
||
<div>Функции доступны через <span class="mono">/fn/...</span> с вашим токеном или демо-логином в заголовке <span class="mono">Authorization: Bearer</span>.</div>
|
||
<div>Gateway может возвращать редирект — используйте флаг <span class="mono">-L</span> в curl для его автоматического следования.</div>
|
||
<div>Работают обычные HTTP методы: <span class="mono">GET</span>, <span class="mono">POST</span>, <span
|
||
class="mono">PUT</span>, <span class="mono">DELETE</span> и другие, если они разрешены в поле Методы.
|
||
</div>
|
||
</div>
|
||
<div class="help-code">export TOKEN="<токен или демо-логин>"
|
||
curl -skL -X POST https://fission.kube5s.ru/fn/ВАШ_ROUTE \
|
||
-H "Authorization: Bearer $TOKEN" \
|
||
-H "Content-Type: application/json" \
|
||
-d '{"x":1,"y":2}'</div>
|
||
<div class="help-code">export TOKEN="<токен или демо-логин>"
|
||
curl -skL https://fission.kube5s.ru/fn/ВАШ_ROUTE \
|
||
-H "Authorization: Bearer $TOKEN"</div>
|
||
<div class="help-code">import requests
|
||
|
||
token = "ВАШ_ТОКЕН"
|
||
url = "https://fission.kube5s.ru/fn/ВАШ_ROUTE"
|
||
|
||
response = requests.post(
|
||
url,
|
||
headers={"Authorization": f"Bearer {token}"},
|
||
json={"x": 1, "y": 2},
|
||
timeout=60,
|
||
)
|
||
|
||
print(response.status_code)
|
||
print(response.text)</div>
|
||
</div>
|
||
|
||
<div class="help-section">
|
||
<h4>Где взять токен</h4>
|
||
<div class="help-list">
|
||
<div><strong>Токен из ЛК</strong> — Профиль пользователя → Токены. Подходит и для входа в console, и для вызова через <span class="mono">/fn/</span>.</div>
|
||
<div><strong>Демо-логин</strong> — любая строка не менее 6 символов. Тот же логин даёт тот же namespace — ваше окружение сохраняется.</div>
|
||
<div>Токен удобно класть в переменную окружения и потом подставлять в curl как <span
|
||
class="mono">$TOKEN</span>.</div>
|
||
</div>
|
||
<div class="help-code">export TOKEN="<вставьте сюда токен или демо-логин>"</div>
|
||
</div>
|
||
|
||
<div class="actions" style="justify-content:space-between; align-items:center;">
|
||
<span style="font-size:0.75rem; color:var(--text-secondary);">v1.3.32</span>
|
||
<button class="btn ghost" onclick="closeHelp()">Закрыть</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="invoke-modal" class="modal">
|
||
<div class="panel">
|
||
<h3 id="i-title">Вызов</h3>
|
||
<div>
|
||
<label>JSON тело запроса</label>
|
||
<textarea id="i-body">{"name":"world"}</textarea>
|
||
</div>
|
||
<div class="actions">
|
||
<button class="btn ghost" onclick="closeInvoke()">Отмена</button>
|
||
<button id="i-submit" class="btn" onclick="submitInvoke()">Вызвать</button>
|
||
</div>
|
||
<div id="i-status" style="margin-top:8px;font-size:13px;color:var(--text-secondary);min-height:20px;"></div>
|
||
<div id="i-meta"
|
||
style="display:none;margin-top:8px;background:#101c2b;border:1px solid var(--border);border-radius:6px;padding:10px 12px;font-size:13px;color:#d8e5f2;white-space:pre-wrap;">
|
||
</div>
|
||
<div style="margin-top:6px;">
|
||
<label>Response</label>
|
||
<textarea id="i-resp" readonly style="min-height:160px;"></textarea>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
const API_BASE = window.location.pathname.startsWith('/console') ? '/console/api' : '/api';
|
||
|
||
const S = {
|
||
envs: [],
|
||
fns: [],
|
||
httpTriggers: [],
|
||
timeTriggers: [],
|
||
currentEdit: null,
|
||
currentInvoke: null
|
||
};
|
||
|
||
checkAuth();
|
||
</script>
|
||
|
||
<!-- ================================================================
|
||
ai/ask feature — удалить весь блок до "end ai/ask feature"
|
||
================================================================ -->
|
||
<div id="assistant-panel" style="
|
||
position:fixed; bottom:0; right:24px;
|
||
width:720px; background:#1e1e2e; border:1px solid #3a3a5c;
|
||
border-bottom:none; border-radius:8px 8px 0 0;
|
||
font-family:inherit; z-index:900; box-shadow:0 -2px 12px rgba(0,0,0,.4);
|
||
">
|
||
<div id="assistant-header" onclick="toggleAssistant()" style="
|
||
display:flex; align-items:center; justify-content:space-between;
|
||
padding:8px 12px; cursor:pointer; background:#2a2a42; border-radius:8px 8px 0 0;
|
||
user-select:none;
|
||
">
|
||
<span style="font-weight:600; font-size:.85rem;">💬 LLM Ассистент</span>
|
||
<span id="assistant-toggle-icon" style="font-size:.75rem; opacity:.7;">▲</span>
|
||
</div>
|
||
<div id="assistant-body" style="padding:10px; display:flex; flex-direction:column; gap:8px;">
|
||
<div id="assistant-messages" style="
|
||
min-height:80px; max-height:400px; overflow-y:auto;
|
||
font-size:.82rem; line-height:1.5; color:#cdd6f4;
|
||
background:#13131f; border-radius:4px; padding:8px;
|
||
white-space:pre-wrap; word-break:break-word;
|
||
">Чем могу помочь?</div>
|
||
<div style="display:flex; gap:6px;">
|
||
<input id="assistant-input" type="text" placeholder="Введи вопрос..." style="flex:1; background:#2a2a42; border:1px solid #3a3a5c; border-radius:4px;
|
||
color:#cdd6f4; padding:6px 8px; font-size:.82rem; outline:none;"
|
||
onkeydown="if(event.key==='Enter')askAssistant()" />
|
||
<button onclick="askAssistant()" style="
|
||
background:#7c3aed; color:#fff; border:none; border-radius:4px;
|
||
padding:6px 12px; cursor:pointer; font-size:.82rem; white-space:nowrap;
|
||
">→</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
</body>
|
||
|
||
</html> |