Revert "feat: grouped query builder with $upIf/$downIf condition groups"

This reverts commit 99b59070a2.
This commit is contained in:
M1 2026-03-16 13:53:27 +04:00
parent 99b59070a2
commit 5328471229
4 changed files with 3 additions and 168 deletions

View File

@ -50,17 +50,6 @@ pub struct Response {
pub fn evaluate(query: &Value, response: &Response) -> Result<bool> { pub fn evaluate(query: &Value, response: &Response) -> Result<bool> {
match query { match query {
Value::Object(map) => { Value::Object(map) => {
// $upIf / $downIf — named groups, $downIf takes precedence
if map.contains_key("$upIf") || map.contains_key("$downIf") {
if let Some(down) = map.get("$downIf") {
if evaluate(down, response).unwrap_or(false) { return Ok(false); }
}
if let Some(up) = map.get("$upIf") {
return evaluate(up, response);
}
return Ok(true); // only $downIf set and didn't match → up
}
// $and / $or / $not // $and / $or / $not
if let Some(and) = map.get("$and") { if let Some(and) = map.get("$and") {
let Value::Array(clauses) = and else { bail!("$and expects array") }; let Value::Array(clauses) = and else { bail!("$and expects array") };

View File

@ -53,8 +53,8 @@
</div> </div>
<div> <div>
<label class="block text-sm text-gray-400 mb-1.5">Conditions <span class="text-gray-600">(optional)</span></label> <label class="block text-sm text-gray-400 mb-1.5">Query Conditions <span class="text-gray-600">(optional)</span></label>
<p class="text-xs text-gray-600 mb-3">Define up/down conditions. <span class="text-red-400/70">Down if</span> takes priority over <span class="text-green-400/70">Up if</span>. Leave empty to use default (status &lt; 400).</p> <p class="text-xs text-gray-600 mb-3">Define when this monitor should be considered "up". Defaults to status &lt; 400.</p>
<div id="query-builder"></div> <div id="query-builder"></div>
</div> </div>
@ -71,7 +71,7 @@
if (!requireAuth()) throw 'auth'; if (!requireAuth()) throw 'auth';
let currentQuery = null; let currentQuery = null;
const qb = new GroupedQueryBuilder(document.getElementById('query-builder'), (q) => { const qb = new QueryBuilder(document.getElementById('query-builder'), (q) => {
currentQuery = q; currentQuery = q;
}); });

View File

@ -304,150 +304,3 @@ class QueryBuilder {
} }
} }
} }
// ── Grouped Query Builder ─────────────────────────────────────────────────────
// Two named condition groups: "Up if..." and "Down if..."
// $downIf takes precedence over $upIf. Both optional.
class GroupedQueryBuilder {
constructor(container, onChange) {
this.container = container;
this.onChange = onChange;
this.groups = {
upIf: new QueryBuilder(null, () => this._emit()),
downIf: new QueryBuilder(null, () => this._emit()),
};
this.groups.upIf.rules = [];
this.groups.downIf.rules = [];
this._render();
}
_emit() {
this.onChange?.(this.getQuery());
}
getQuery() {
const up = this.groups.upIf.rules.length ? this.groups.upIf.getQuery() : null;
const down = this.groups.downIf.rules.length ? this.groups.downIf.getQuery() : null;
if (!up && !down) return null;
const q = {};
if (up) q.$upIf = up;
if (down) q.$downIf = down;
return q;
}
setQuery(query) {
if (!query || typeof query !== 'object') return;
if (query.$upIf) { this.groups.upIf.setQuery(query.$upIf); }
if (query.$downIf) { this.groups.downIf.setQuery(query.$downIf); }
this._render();
}
_render() {
this.container.innerHTML = `
<div class="space-y-4">
<div id="gqb-upif"></div>
<div id="gqb-downif"></div>
<div class="mt-3 p-3 bg-gray-950 rounded border border-gray-800">
<div class="flex items-center justify-between mb-1">
<span class="text-xs text-gray-500 font-mono">Query JSON</span>
<button id="gqb-copy" class="text-xs text-blue-400 hover:text-blue-300">Copy</button>
</div>
<pre id="gqb-preview" class="text-xs text-gray-300 font-mono whitespace-pre-wrap overflow-x-auto"></pre>
</div>
</div>
`;
this._renderGroup('upIf', this.container.querySelector('#gqb-upif'), '🟢 Up if', 'text-green-400', 'border-green-900/50');
this._renderGroup('downIf', this.container.querySelector('#gqb-downif'), '🔴 Down if', 'text-red-400', 'border-red-900/50');
this._updatePreview();
this.container.querySelector('#gqb-copy').addEventListener('click', () => {
const q = this.getQuery();
navigator.clipboard.writeText(JSON.stringify(q ?? {}, null, 2));
const btn = this.container.querySelector('#gqb-copy');
btn.textContent = 'Copied!';
setTimeout(() => btn.textContent = 'Copy', 1500);
});
}
_renderGroup(key, el, label, labelClass, borderClass) {
const group = this.groups[key];
const hasRules = group.rules.length > 0;
el.innerHTML = `
<div class="rounded border ${borderClass} bg-gray-900/50 p-3">
<div class="flex items-center justify-between mb-3">
<span class="text-sm font-medium ${labelClass}">${label}</span>
<div class="flex items-center gap-3">
${hasRules ? `
<div class="flex items-center gap-1.5">
<span class="text-xs text-gray-500">Match</span>
<select id="gqb-logic-${key}" class="bg-gray-800 border border-gray-700 text-gray-200 text-xs rounded px-2 py-1 focus:border-blue-500 focus:outline-none">
<option value="$and" ${group.logic === '$and' ? 'selected' : ''}>ALL</option>
<option value="$or" ${group.logic === '$or' ? 'selected' : ''}>ANY</option>
</select>
</div>
` : ''}
<button id="gqb-add-${key}" class="text-xs text-blue-400 hover:text-blue-300">+ Add condition</button>
${hasRules ? `<button id="gqb-clear-${key}" class="text-xs text-gray-600 hover:text-gray-400">Clear</button>` : ''}
</div>
</div>
${hasRules ? `
<div id="gqb-rules-${key}" class="space-y-2">
${group.rules.map((rule, i) => group._renderRule(rule, i)).join('')}
</div>
` : `<p class="text-xs text-gray-600 italic">No conditions defaults to status &lt; 400</p>`}
</div>
`;
// Bind logic toggle
const logicSel = el.querySelector(`#gqb-logic-${key}`);
if (logicSel) {
logicSel.addEventListener('change', (e) => {
group.logic = e.target.value;
this._renderGroup(key, el, label, labelClass, borderClass);
this._updatePreview();
this._emit();
});
}
// Bind add
el.querySelector(`#gqb-add-${key}`).addEventListener('click', () => {
group.rules.push(group._emptyRule());
this._renderGroup(key, el, label, labelClass, borderClass);
this._updatePreview();
this._emit();
});
// Bind clear
const clearBtn = el.querySelector(`#gqb-clear-${key}`);
if (clearBtn) {
clearBtn.addEventListener('click', () => {
group.rules = [];
this._renderGroup(key, el, label, labelClass, borderClass);
this._updatePreview();
this._emit();
});
}
// Bind rule events — wrap onChange to re-render preview
if (hasRules) {
group.container = el.querySelector(`#gqb-rules-${key}`);
group.onChange = () => { this._updatePreview(); this._emit(); };
el.querySelectorAll('.qb-rule').forEach((ruleEl, i) => {
group._bindRuleEvents(ruleEl, i);
});
}
}
_updatePreview() {
const q = this.getQuery();
const pre = this.container.querySelector('#gqb-preview');
if (pre) {
pre.innerHTML = q
? escapeHtml(JSON.stringify(q, null, 2))
: '<span class="text-gray-600">No conditions — defaults to status &lt; 400</span>';
}
}
}

View File

@ -86,13 +86,6 @@ export function evaluate(query: unknown, ctx: EvalContext): boolean {
const q = query as Record<string, unknown>; const q = query as Record<string, unknown>;
// $upIf / $downIf — named condition groups, $downIf takes precedence
if ("$upIf" in q || "$downIf" in q) {
if ("$downIf" in q && evaluate(q.$downIf, ctx)) return false;
if ("$upIf" in q) return evaluate(q.$upIf, ctx);
return true; // only $downIf set and it didn't match → up
}
// $and // $and
if ("$and" in q) { if ("$and" in q) {
const clauses = q.$and; const clauses = q.$and;