Files
jormun-admin/templates/table/query.html.twig

209 lines
9.7 KiB
Twig
Raw Normal View History

2026-03-08 04:16:14 -04:00
{% extends 'base.html.twig' %}
{% import '_sidebar_tables.html.twig' as macros %}
{% block title %}{{ tableName }} — Query · Jormun Admin{% endblock %}
{% block breadcrumbs %}
<a href="{{ path('dashboard') }}"><i class="bi bi-house-fill"></i> Home</a>
<span class="sep">/</span>
<a href="{{ path('table_browse', {name: tableName}) }}">{{ tableName }}</a>
<span class="sep">/</span>
<span class="current">Query</span>
{% endblock %}
{% block sidebar_tables %}
{{ macros.sidebar_table_links(tables, tableName) }}
{% endblock %}
{% block content %}
<nav class="op-tabs">
<a href="{{ path('table_browse', {name: tableName}) }}" class="op-tab">
<i class="bi bi-grid-3x3-gap"></i> Browse
</a>
<a href="{{ path('table_structure', {name: tableName}) }}" class="op-tab">
<i class="bi bi-diagram-3"></i> Structure
</a>
<a href="{{ path('table_query', {name: tableName}) }}" class="op-tab active">
<i class="bi bi-search"></i> Query
</a>
<a href="{{ path('table_item_new', {name: tableName}) }}" class="op-tab">
<i class="bi bi-plus-circle"></i> Insert
</a>
</nav>
<div x-data="queryBuilder()" class="row g-3">
{# Builder form #}
<div class="col-12 col-lg-4">
<div class="content-card">
<div class="content-card-header">
<h6><i class="bi bi-funnel me-1" style="color:var(--jormun-teal);"></i> Query Builder</h6>
</div>
<div style="padding:1rem;">
<form method="POST" action="{{ path('table_query', {name: tableName}) }}">
{# Mode toggle #}
<div class="mb-3">
<label class="form-label" style="font-size:0.72rem;font-weight:500;text-transform:uppercase;letter-spacing:0.04em;color:#374151;">Mode</label>
<div class="d-flex gap-2">
<label style="display:flex;align-items:center;gap:0.3rem;font-size:0.78rem;cursor:pointer;">
<input type="radio" name="mode" value="query" x-model="queryMode" {% if mode != 'scan' %}checked{% endif %}> Query
</label>
<label style="display:flex;align-items:center;gap:0.3rem;font-size:0.78rem;cursor:pointer;">
<input type="radio" name="mode" value="scan" x-model="queryMode" {% if mode == 'scan' %}checked{% endif %}> Scan (full table)
</label>
</div>
</div>
{# Conditions (only for Query mode) #}
<div x-show="queryMode === 'query'">
<div class="mb-2" style="font-size:0.72rem;font-weight:500;text-transform:uppercase;letter-spacing:0.04em;color:#374151;">
Key Conditions
</div>
<div class="mb-2" style="font-size:0.72rem;color:#64748b;">
PK: <span class="type-badge">{{ keySchema.HASH.name ?? '?' }}</span>
{% if keySchema.RANGE is defined %}
SK: <span class="type-badge">{{ keySchema.RANGE.name }}</span>
{% endif %}
</div>
<template x-for="(cond, i) in conditions" :key="i">
<div class="d-flex gap-1 mb-2 align-items-start">
<input
type="text"
:name="'cond_key[]'"
x-model="cond.key"
class="jormun-input"
placeholder="attribute"
style="width:35%;"
>
<select :name="'cond_op[]'" x-model="cond.op" class="jormun-select" style="width:28%;">
<option value="=">=</option>
<option value="<">&lt;</option>
<option value="<=">&lt;=</option>
<option value=">">&gt;</option>
<option value=">=">&gt;=</option>
<option value="begins_with">begins</option>
</select>
<input
type="text"
:name="'cond_val[]'"
x-model="cond.val"
class="jormun-input"
placeholder="value"
style="flex:1;"
>
<button type="button" @click="removeCond(i)" class="btn-danger-sm" style="flex-shrink:0;padding:0.35rem 0.5rem;">
<i class="bi bi-x"></i>
</button>
</div>
</template>
<button type="button" @click="addCond()" class="btn-jormun-outline w-100 mb-3" style="font-size:0.72rem;padding:0.3rem;">
<i class="bi bi-plus"></i> Add Condition
</button>
</div>
<button type="submit" class="btn-jormun w-100">
<i :class="queryMode === 'scan' ? 'bi bi-lightning' : 'bi bi-search'"></i>
<span x-text="queryMode === 'scan' ? 'Run Scan' : 'Run Query'"></span>
</button>
</form>
</div>
</div>
</div>
{# Results #}
<div class="col-12 col-lg-8">
{% if error %}
<div style="background:#fef2f2;border:1px solid #fca5a5;color:#991b1b;border-radius:6px;padding:0.75rem 1rem;margin-bottom:1rem;font-size:0.8rem;">
<i class="bi bi-exclamation-triangle-fill me-2"></i>{{ error }}
</div>
{% endif %}
{% if results is not null %}
<div class="content-card">
<div class="content-card-header">
<h6><i class="bi bi-table me-1" style="color:var(--jormun-teal);"></i> Results</h6>
<span class="stat-pill">{{ results|length }} item{{ results|length != 1 ? 's' : '' }}</span>
</div>
{% if results is empty %}
<div class="empty-state">
<i class="bi bi-search"></i>
<div>No items matched your query.</div>
</div>
{% else %}
{# Collect columns #}
{% set cols = {} %}
{% for item in results %}{% for k in item|keys %}{% set cols = cols|merge({(k): true}) %}{% endfor %}{% endfor %}
<div style="overflow-x:auto;">
<table class="data-table">
<thead>
<tr>
{% for col in cols|keys %}
<th>{{ col }}
{% if keySchema.HASH is defined and keySchema.HASH.name == col %}
<span class="type-badge">PK</span>
{% endif %}
{% if keySchema.RANGE is defined and keySchema.RANGE.name == col %}
<span class="type-badge">SK</span>
{% endif %}
</th>
{% endfor %}
</tr>
</thead>
<tbody>
{% for item in results %}
<tr>
{% for col in cols|keys %}
<td class="cell-value {% if keySchema.HASH is defined and keySchema.HASH.name == col %}pk-cell{% endif %}">
{% if item[col] is defined %}
{% set val = item[col] %}
{% if val is iterable %}
<span style="color:#6366f1;">{{ val|json_encode|slice(0, 50) }}{% if val|json_encode|length > 50 %}{% endif %}</span>
{% else %}
{{ val|e|slice(0, 60) }}{% if (val|e|length) > 60 %}{% endif %}
{% endif %}
{% else %}
<span class="null-val">NULL</span>
{% endif %}
</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
</div>
{% else %}
<div class="content-card">
<div class="empty-state" style="padding:4rem 1rem;">
<i class="bi bi-search" style="color:#cbd5e1;"></i>
<div style="font-family:'Syne',sans-serif;font-weight:700;font-size:0.9rem;color:#374151;margin-bottom:0.3rem;">Build a query</div>
<div style="font-size:0.75rem;color:#94a3b8;">
Set conditions on the left and click Run Query, or use Scan for all items.
</div>
</div>
</div>
{% endif %}
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
function queryBuilder() {
return {
queryMode: '{{ mode }}',
conditions: [{ key: '{{ keySchema.HASH.name ?? '' }}', op: '=', val: '' }],
addCond() { this.conditions.push({ key: '', op: '=', val: '' }); },
removeCond(i) { if (this.conditions.length > 1) this.conditions.splice(i, 1); }
};
}
</script>
{% endblock %}