rabbit-lsd/query.cfm

220 lines
12 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>Nubes | Rabbit CRUD UI</title>
<link rel="icon" href="https://nubes.ru/themes/custom/nubes/images/nubes-ico.svg" type="image/svg+xml">
<style>
:root { --nubes-blue: #005BFF; --nubes-dark: #1A1A1A; --nubes-grey: #F8F9FA; --nubes-border: #E5E7EB; --nubes-green: #0f9d58; --nubes-red: #d93025; }
body { font-family: 'Segoe UI', Tahoma, sans-serif; margin: 0; padding: 0; background: var(--nubes-grey); color: var(--nubes-dark); }
.header-bg { position: sticky; top: 0; z-index: 1000; background: #fff; border-bottom: 1px solid var(--nubes-border); padding: 15px 0; box-shadow: 0 2px 10px rgba(0,0,0,0.05); }
.container { max-width: 1100px; margin: auto; padding: 0 20px; }
.header-content { display: flex; align-items: center; justify-content: space-between; }
.logo { height: 40px; }
.main-content { padding: 32px 0; }
.card { background: #fff; padding: 24px; border-radius: 16px; box-shadow: 0 4px 20px rgba(0,0,0,0.04); margin-bottom: 20px; }
.btn { display: inline-flex; align-items: center; justify-content: center; cursor: pointer; padding: 12px 24px; border: none; border-radius: 8px; font-weight: 600; font-size: 14px; }
.btn-primary { background: var(--nubes-blue); color: #fff; }
.btn-action { padding: 6px; background: #fff; border: 1px solid var(--nubes-border); border-radius: 8px; font-size: 16px; line-height: 1; cursor: pointer; min-width: 32px; }
.btn-action:hover { background: var(--nubes-grey); border-color: var(--nubes-blue); }
.input-group { display: flex; gap: 12px; margin-bottom: 12px; }
input[type="text"] { flex-grow: 1; padding: 12px 16px; border: 1px solid var(--nubes-border); border-radius: 8px; font-size: 14px; }
table { width: 100%; border-collapse: collapse; }
th { text-align: left; padding: 8px 10px; font-size: 11px; text-transform: uppercase; color: #6B7280; border-bottom: 1px solid var(--nubes-border); }
td { padding: 8px 10px; font-size: 12px; border-bottom: 1px solid var(--nubes-border); vertical-align: top; }
tbody tr:nth-child(even) { background-color: #FAFBFC; }
tbody tr:hover { background-color: #F3F4F6; }
.id-cell { font-family: monospace; color: #9CA3AF; width: 60px; }
.actions-cell { display: flex; gap: 10px; width: 120px; }
.alert { padding: 12px 16px; border-radius: 10px; margin-bottom: 16px; font-size: 14px; }
.alert-info { background: #eef4ff; color: #234; border: 1px solid #d6e3ff; }
.alert-success { background: #eef8f1; color: #1e4620; border: 1px solid #cbe6d3; }
.alert-error { background: #fdeeee; color: var(--nubes-red); border: 1px solid #f4c7c3; }
.badge { display: inline-block; padding: 4px 10px; border-radius: 999px; font-size: 12px; font-weight: 600; }
.badge-queued { background: #eef4ff; color: #1d3b8b; }
.badge-done { background: #eef8f1; color: #1e4620; }
.small { font-size: 12px; color: #6B7280; }
.top-info { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.link-row { display: flex; gap: 10px; align-items: center; }
.link-row a { color: var(--nubes-blue); text-decoration: none; font-weight: 600; }
.split { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; }
.tight { font-size: 12px; }
.table-scroll { overflow-y: auto; display: block; }
.table-scroll-queue { max-height: 220px; }
.table-scroll-db { max-height: 520px; }
.table-scroll table { margin: 0; }
.col-queued { width: 120px; }
.col-processed { width: 120px; }
.col-status { width: 90px; }
.col-id { width: 60px; }
.col-content { width: 100%; }
.col-req { width: 220px; }
.wide { grid-column: 1 / -1; }
@media (max-width: 980px) {
.split { grid-template-columns: 1fr; }
}
</style>
</head>
<body>
<!--- Сводный статус последней операции и базовые ссылки. --->
<cfset status = {}>
<cfset rabbitAdminUrl = request.rabbitAdminUrl>
<cfset rabbitUser = request.rabbitUser>
<cfset rabbitPassword = request.rabbitPassword>
<cfset nodeworkerUrl = request.nodeworkerUrl>
<!--- Лог последних событий из UI-очереди. --->
<cfquery name="qLog" datasource="#request.DS#">
SELECT id, action, text, request_id, target_id, queued_at, processed_at, status, error_reason
FROM #request.logTableName#
ORDER BY id DESC
LIMIT 5
</cfquery>
<!--- Формируем сообщение статуса из лога/ошибок. --->
<cfif structKeyExists(request, "queue_error")>
<cfset status.type = "error">
<cfset status.message = "Ошибка постановки в очередь: " & request.queue_error>
<cfelseif qLog.recordCount GT 0>
<cfset status.type = qLog.status[1] EQ "done" ? "success" : "info">
<cfif qLog.status[1] EQ "done">
<cfset status.message = "Исполнено в " & dateTimeFormat(qLog.processed_at[1], "yyyy-mm-dd HH:nn:ss")>
<cfelse>
<cfset status.message = "Поставлено в очередь: " & uCase(qLog.action[1]) & " в " & dateTimeFormat(qLog.queued_at[1], "yyyy-mm-dd HH:nn:ss")>
</cfif>
</cfif>
<!--- Фиксированный заголовок Nubes. --->
<div class="header-bg">
<div class="container header-content">
<img src="https://nubes.ru/themes/custom/nubes_2025/logo.svg" alt="Nubes" class="logo">
<div style="font-size: 14px; color: var(--nubes-blue); font-weight: 600;">Lucee + Rabbit + Postgres Demo</div>
</div>
</div>
<div class="container main-content">
<!--- Карточка с ссылками и кредами Rabbit/worker. --->
<div class="card">
<div class="top-info">
<div>
<div class="small">RabbitMQ UI</div>
<div class="link-row">
<cfif len(rabbitAdminUrl)>
<a href="<cfoutput>#rabbitAdminUrl#</cfoutput>" target="_blank">Открыть Rabbit UI</a>
<cfelse>
<span class="small">URL не задан</span>
</cfif>
</div>
<div class="small">Логин / Пароль</div>
<div><cfoutput>#rabbitUser#</cfoutput> / <cfoutput>#rabbitPassword#</cfoutput></div>
</div>
<div>
<div class="small">NodeJS Worker</div>
<div class="link-row">
<cfif len(nodeworkerUrl)>
<a href="<cfoutput>#nodeworkerUrl#</cfoutput>" target="_blank">Открыть NodeJS Worker</a>
<cfelse>
<span class="small">URL не задан</span>
</cfif>
</div>
</div>
</div>
</div>
<!--- Карточка действий CRUD. --->
<div class="card">
<cfif structKeyExists(request, "db_error")>
<div class="alert alert-error">Ошибка базы данных: <cfoutput>#request.db_error#</cfoutput></div>
</cfif>
<cfif structKeyExists(status, "message")>
<div class="alert <cfoutput>#status.type EQ 'success' ? 'alert-success' : (status.type EQ 'error' ? 'alert-error' : 'alert-info')#</cfoutput>">
<cfoutput>#status.message#</cfoutput>
</div>
</cfif>
<form method="post" class="input-group">
<input type="hidden" name="crud_action" value="insert">
<input type="text" name="txt_content" placeholder="Новое сообщение..." required>
<button type="submit" class="btn btn-primary">Поставить в очередь</button>
</form>
<div class="small">💾 — сохранить (update), 🗑 — удалить</div>
</div>
<!--- Две таблицы: очередь/лог и фактическая БД. --->
<div class="split">
<div class="card">
<h3>Очередь (лог отправки)</h3>
<div class="table-scroll table-scroll-queue">
<table class="tight">
<thead>
<tr>
<th>ID</th>
<th>Действие</th>
<th>Текст</th>
<th>Target</th>
<th class="col-queued">Queued</th>
<th class="col-processed">Processed</th>
<th class="col-status">Статус</th>
<th>Причина</th>
</tr>
</thead>
<tbody>
<cfoutput query="qLog">
<tr>
<td class="id-cell">#id#</td>
<td>#uCase(action)#</td>
<td>#HTMLEditFormat(text)#</td>
<td><cfif len(target_id)>#target_id#<cfelse>-</cfif></td>
<td>#dateTimeFormat(queued_at, "yyyy-mm-dd HH:nn:ss")#</td>
<td><cfif len(processed_at)>#dateTimeFormat(processed_at, "yyyy-mm-dd HH:nn:ss")#<cfelse>-</cfif></td>
<td>
<span class="badge <cfif status EQ 'done'>badge-done<cfelse>badge-queued</cfif>">#status#</span>
</td>
<td><cfif len(error_reason)>#HTMLEditFormat(error_reason)#<cfelse>-</cfif></td>
</tr>
</cfoutput>
</tbody>
</table>
</div>
</div>
<div class="card wide">
<h3>База (фактические записи)</h3>
<!--- Читаем последние фактические записи для сравнения с логом. --->
<cfquery name="qGet" datasource="#request.DS#">SELECT * FROM #request.tableName# ORDER BY id DESC LIMIT 15</cfquery>
<div class="table-scroll table-scroll-db">
<table class="tight">
<thead><tr><th class="col-id">ID</th><th class="col-content">Содержимое</th><th class="col-req">Request ID</th><th>Действия</th></tr></thead>
<tbody>
<cfoutput query="qGet">
<!--- Отделяем request_id из хвоста строки. --->
<cfset reqMatch = reFindNoCase("\[req:[0-9a-f-]+\]$", test_data, 1, true)>
<cfset reqTag = reqMatch.pos[1] GT 0 ? mid(test_data, reqMatch.pos[1], reqMatch.len[1]) : "">
<cfset cleanText = reReplace(test_data, "(?i)\s*\[req:[0-9a-f-]+\]$", "", "all")>
<cfset reqId = reReplace(reqTag, "(?i)\[req:([0-9a-f-]+)\]", "\1", "all")>
<tr>
<td class="id-cell col-id">#id#</td>
<td>
<form method="post" id="upd_#id#" style="margin:0">
<input type="hidden" name="crud_action" value="update"><input type="hidden" name="id" value="#id#">
<input type="text" name="txt_content" value="#HTMLEditFormat(cleanText)#" style="width:100%; border:none; background:transparent;">
</form>
</td>
<td class="col-req"><cfif len(reqId)>#reqId#<cfelse>-</cfif></td>
<td class="actions-cell">
<button type="submit" form="upd_#id#" class="btn-action">💾</button>
<form method="post" style="margin:0" onsubmit="return confirm('Удалить?')">
<input type="hidden" name="crud_action" value="delete"><input type="hidden" name="id" value="#id#">
<button type="submit" class="btn-action">🗑</button>
</form>
</td>
</tr>
</cfoutput>
</tbody>
</table>
</div>
</div>
</div>
</div>
</body>
</html>