4 Commits

Author SHA1 Message Date
aa62528862 chore: bump version to 1.6.3
All checks were successful
CI / lint-and-test (push) Successful in 35s
Release / build-and-push (push) Successful in 1m47s
2026-04-22 12:02:28 +02:00
ac155d8843 feat: +/- buttons on service pills for additive/subtractive filtering
- Replace single-click service pill filter with explicit +/− buttons
- '+' adds the service to the current filter (keeps other selections)
- '−' removes the service from the current filter
- Result pills keep toggle click behavior
- Add .pill__action styles for small inline buttons
2026-04-22 12:02:11 +02:00
ed7465f5cd chore: bump version to 1.6.2
All checks were successful
Release / build-and-push (push) Successful in 1m33s
CI / lint-and-test (push) Successful in 33s
2026-04-22 11:53:21 +02:00
0eebcd0765 feat: clickable pills, configurable page size, CQRE.NET branding
- Service/category pills are now clickable: click to filter by that service
- Result pills (Success, Failure, etc.) are now clickable: click to filter by that result
- Click again to clear the filter (toggle behavior)
- Change default page size from 100 to 25
- Add DEFAULT_PAGE_SIZE config (env var, default 25), exposed via /api/config/features
- Change footer brand from CQRE to CQRE.NET
- Add pill--clickable hover styles
- Bump CSS cache-buster to v=10
2026-04-22 11:53:01 +02:00
6 changed files with 97 additions and 9 deletions

View File

@@ -55,6 +55,9 @@ LLM_API_VERSION=
# For local dev, start Valkey with: docker run -d -p 6379:6379 valkey/valkey:8-alpine
REDIS_URL=redis://localhost:6379/0
# UI default page size (number of events shown per page)
DEFAULT_PAGE_SIZE=25
# Optional: privacy / access control
# Hide entire services from users without PRIVACY_SERVICE_ROLES
# PRIVACY_SERVICES=Exchange,Teams

View File

@@ -1 +1 @@
1.6.1
1.6.3

View File

@@ -60,6 +60,9 @@ class Settings(BaseSettings):
# Redis (caching + async job queue)
REDIS_URL: str = "redis://localhost:6379/0"
# UI defaults
DEFAULT_PAGE_SIZE: int = 25
_settings = Settings()
@@ -100,3 +103,4 @@ PRIVACY_SENSITIVE_OPERATIONS = {o.strip() for o in _settings.PRIVACY_SENSITIVE_O
PRIVACY_SERVICE_ROLES = {r.strip() for r in _settings.PRIVACY_SERVICE_ROLES.split(",") if r.strip()}
REDIS_URL = _settings.REDIS_URL
DEFAULT_PAGE_SIZE = _settings.DEFAULT_PAGE_SIZE

View File

@@ -4,7 +4,7 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Admin Operations Center</title>
<link rel="stylesheet" href="/style.css?v=9" />
<link rel="stylesheet" href="/style.css?v=11" />
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
<script src="https://alcdn.msauth.net/browser/2.37.0/js/msal-browser.min.js" crossorigin="anonymous"></script>
</head>
@@ -184,8 +184,12 @@
<template x-for="(evt, idx) in askEvents" :key="evt.id || idx">
<article class="event event--compact">
<div class="event__meta">
<span class="pill" x-text="evt.display_category || evt.service || '—'"></span>
<span class="pill" :class="['success','succeeded','ok','passed','true'].includes((evt.result || '').toLowerCase()) ? 'pill--ok' : 'pill--warn'" x-text="evt.result || '—'"></span>
<span class="pill">
<span x-text="evt.display_category || evt.service || '—'"></span>
<span class="pill__action" @click.stop="addServiceFilter(evt.service || evt.display_category)" title="Include this service">+</span>
<span class="pill__action" @click.stop="removeServiceFilter(evt.service || evt.display_category)" title="Exclude this service"></span>
</span>
<span class="pill pill--clickable" :class="['success','succeeded','ok','passed','true'].includes((evt.result || '').toLowerCase()) ? 'pill--ok' : 'pill--warn'" x-text="evt.result || '—'" @click="filterByResult(evt.result)" title="Filter by this result"></span>
</div>
<h3 x-text="evt.operation || '—'"></h3>
<p class="event__detail" x-show="evt.display_summary"><strong>Summary:</strong> <span x-text="evt.display_summary"></span></p>
@@ -211,8 +215,8 @@
<template x-for="(evt, idx) in events" :key="evt._id || evt.id || idx">
<article class="event">
<div class="event__meta">
<span class="pill" x-text="evt.display_category || evt.service || '—'"></span>
<span class="pill" :class="['success','succeeded','ok','passed','true'].includes((evt.result || '').toLowerCase()) ? 'pill--ok' : 'pill--warn'" x-text="evt.result || '—'"></span>
<span class="pill pill--clickable" x-text="evt.display_category || evt.service || '—'" @click="filterByService(evt.service || evt.display_category)" title="Filter by this service"></span>
<span class="pill pill--clickable" :class="['success','succeeded','ok','passed','true'].includes((evt.result || '').toLowerCase()) ? 'pill--ok' : 'pill--warn'" x-text="evt.result || '—'" @click="filterByResult(evt.result)" title="Filter by this result"></span>
</div>
<h3 x-text="evt.operation || '—'"></h3>
<p class="event__detail" x-show="evt.display_summary"><strong>Summary:</strong> <span x-text="evt.display_summary"></span></p>
@@ -277,7 +281,7 @@
<a :href="docsUrl" target="_blank" rel="noopener">📖 Documentation</a>
</div>
<div class="footer__right">
<span>Built with ❤️ by CQRE</span>
<span>Built with ❤️ by CQRE.NET</span>
</div>
</footer>
</div>
@@ -305,7 +309,7 @@
accessToken: null,
authScopes: [],
filters: {
actor: '', selectedServices: [], search: '', operation: '', result: '', start: '', end: '', limit: 100, includeTags: '', excludeTags: '',
actor: '', selectedServices: [], search: '', operation: '', result: '', start: '', end: '', limit: 25, includeTags: '', excludeTags: '',
},
options: { actors: [], services: [], operations: [], results: [] },
savedSearches: [],
@@ -396,6 +400,9 @@
if (featRes.ok) {
const featBody = await featRes.json();
this.aiFeaturesEnabled = featBody.ai_features_enabled !== false;
if (featBody.default_page_size) {
this.filters.limit = featBody.default_page_size;
}
} else {
this.aiFeaturesEnabled = true;
}
@@ -661,7 +668,33 @@
clearFilters() {
const noisy = ['Exchange', 'SharePoint', 'Teams'];
this.filters = { actor: '', selectedServices: this.options.services.filter((s) => !noisy.includes(s)), search: '', operation: '', result: '', start: '', end: '', limit: 100, includeTags: '', excludeTags: '' };
this.filters = { actor: '', selectedServices: this.options.services.filter((s) => !noisy.includes(s)), search: '', operation: '', result: '', start: '', end: '', limit: 25, includeTags: '', excludeTags: '' };
this.saveFilters();
this.resetPagination();
this.loadEvents();
},
addServiceFilter(service) {
if (!service) return;
if (!this.filters.selectedServices.includes(service)) {
this.filters.selectedServices.push(service);
this.saveFilters();
this.resetPagination();
this.loadEvents();
}
},
removeServiceFilter(service) {
if (!service) return;
this.filters.selectedServices = this.filters.selectedServices.filter((s) => s !== service);
this.saveFilters();
this.resetPagination();
this.loadEvents();
},
filterByResult(result) {
if (!result) return;
this.filters.result = this.filters.result === result ? '' : result;
this.saveFilters();
this.resetPagination();
this.loadEvents();

View File

@@ -355,6 +355,52 @@ input {
border-color: rgba(239, 68, 68, 0.5);
}
.pill--clickable {
cursor: pointer;
transition: transform 0.1s ease, box-shadow 0.15s ease, background 0.15s ease;
}
.pill--clickable:hover {
transform: translateY(-1px);
box-shadow: 0 2px 8px rgba(125, 211, 252, 0.2);
background: rgba(125, 211, 252, 0.2);
}
.pill--clickable.pill--ok:hover {
box-shadow: 0 2px 8px rgba(34, 197, 94, 0.2);
background: rgba(34, 197, 94, 0.25);
}
.pill--clickable.pill--warn:hover {
box-shadow: 0 2px 8px rgba(249, 115, 22, 0.2);
background: rgba(249, 115, 22, 0.25);
}
.pill__action {
display: inline-flex;
align-items: center;
justify-content: center;
width: 16px;
height: 16px;
border-radius: 4px;
margin-left: 4px;
font-size: 11px;
font-weight: 700;
line-height: 1;
cursor: pointer;
color: var(--muted);
background: rgba(255, 255, 255, 0.06);
border: 1px solid transparent;
transition: all 0.12s ease;
vertical-align: middle;
}
.pill__action:hover {
color: var(--text);
background: rgba(125, 211, 252, 0.2);
border-color: rgba(125, 211, 252, 0.3);
}
.event h3 {
margin: 0 0 6px;
font-size: 17px;

View File

@@ -4,6 +4,7 @@ from config import (
AUTH_ENABLED,
AUTH_SCOPE,
AUTH_TENANT_ID,
DEFAULT_PAGE_SIZE,
)
from fastapi import APIRouter
@@ -25,4 +26,5 @@ def auth_config():
def features_config():
return {
"ai_features_enabled": AI_FEATURES_ENABLED,
"default_page_size": DEFAULT_PAGE_SIZE,
}