Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions client/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { KanbanBoard } from "./pages/KanbanBoard";
import { Sessions } from "./pages/Sessions";
import { SessionDetail } from "./pages/SessionDetail";
import { ActivityFeed } from "./pages/ActivityFeed";
import { QueryExplorer } from "./pages/QueryExplorer";
import { Analytics } from "./pages/Analytics";
import { Workflows } from "./pages/Workflows";
import { Settings } from "./pages/Settings";
Expand Down Expand Up @@ -40,6 +41,7 @@ export default function App() {
<Route path="sessions" element={<Sessions />} />
<Route path="sessions/:id" element={<SessionDetail />} />
<Route path="activity" element={<ActivityFeed />} />
<Route path="query" element={<QueryExplorer />} />
<Route path="analytics" element={<Analytics />} />
<Route path="workflows" element={<Workflows />} />
<Route path="cc-config" element={<CcConfig />} />
Expand Down
2 changes: 2 additions & 0 deletions client/src/components/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
Columns3,
FolderOpen,
Activity,
Search,
BarChart3,
Workflow,
Boxes,
Expand Down Expand Up @@ -47,6 +48,7 @@ const NAV_KEYS = [
{ to: "/kanban", icon: Columns3, key: "nav:agentBoard" },
{ to: "/sessions", icon: FolderOpen, key: "nav:sessions" },
{ to: "/activity", icon: Activity, key: "nav:activityFeed" },
{ to: "/query", icon: Search, key: "nav:queryExplorer" },
{ to: "/analytics", icon: BarChart3, key: "nav:analytics" },
{ to: "/workflows", icon: Workflow, key: "nav:workflows" },
{ to: "/cc-config", icon: Boxes, key: "nav:ccConfig" },
Expand Down
7 changes: 7 additions & 0 deletions client/src/i18n/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ import run_vi from "./locales/vi/run.json";
import alerts_en from "./locales/en/alerts.json";
import alerts_zh from "./locales/zh/alerts.json";
import alerts_vi from "./locales/vi/alerts.json";
import query_en from "./locales/en/query.json";
import query_zh from "./locales/zh/query.json";
import query_vi from "./locales/vi/query.json";

i18n
.use(LanguageDetector)
Expand All @@ -71,6 +74,7 @@ i18n
ccConfig: ccConfig_en,
run: run_en,
alerts: alerts_en,
query: query_en,
},
zh: {
common: common_zh,
Expand All @@ -87,6 +91,7 @@ i18n
ccConfig: ccConfig_zh,
run: run_zh,
alerts: alerts_zh,
query: query_zh,
},
vi: {
common: common_vi,
Expand All @@ -103,6 +108,7 @@ i18n
ccConfig: ccConfig_vi,
run: run_vi,
alerts: alerts_vi,
query: query_vi,
},
},
supportedLngs: ["en", "zh", "vi"],
Expand All @@ -123,6 +129,7 @@ i18n
"ccConfig",
"run",
"alerts",
"query",
],
defaultNS: "common",
interpolation: { escapeValue: false },
Expand Down
1 change: 1 addition & 0 deletions client/src/i18n/locales/en/nav.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"agentBoard": "Kanban Board",
"sessions": "Sessions",
"activityFeed": "Activity Feed",
"queryExplorer": "Query Explorer",
"analytics": "Analytics",
"workflows": "Workflows",
"alerts": "Alerts",
Expand Down
61 changes: 61 additions & 0 deletions client/src/i18n/locales/en/query.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
{
"title": "Query Explorer",
"subtitle": "Build ad-hoc queries against your dashboard data and export the results.",
"entity": "Entity",
"match": "Match",
"matchAnd": "AND",
"matchOr": "OR",
"limit": "Limit",
"sort": "Sort",
"sortNone": "No sort",
"asc": "Ascending",
"desc": "Descending",
"filters": "Filters",
"addFilter": "Add filter",
"removeFilter": "Remove filter",
"noFilters": "No filters — the query returns all rows up to the limit.",
"valuePlaceholder": "Value",
"valuePlaceholderIn": "Comma-separated values",
"noValueNeeded": "No value needed",
"run": "Run query",
"running": "Running...",
"save": "Save",
"confirmSave": "Save",
"cancel": "Cancel",
"saveNamePlaceholder": "Query name",
"exportCsv": "Export CSV",
"exportJson": "Export JSON",
"savedQueries": "Saved queries",
"noSaved": "No saved queries yet.",
"loadQuery": "Load query",
"deleteQuery": "Delete query",
"readyTitle": "Build and run a query",
"readyDesc": "Pick an entity, add filters, and hit Run query to see results here.",
"noResults": "No matching rows",
"noResultsDesc": "No rows matched the current filters. Try loosening them or switching the match mode.",
"rowsTotal": "Showing {{shown}} of {{count}} rows",
"tookMs": "{{ms}} ms",
"truncated": "Results were truncated to the limit of {{limit}} rows. Increase the limit or add filters to narrow the result set.",
"errorSchema": "Failed to load the query schema.",
"errorRun": "Failed to run the query.",
"errorSaved": "Failed to load saved queries.",
"errorSave": "Failed to save the query.",
"errorDelete": "Failed to delete the query.",
"entities": {
"events": "Events",
"agents": "Agents",
"sessions": "Sessions"
},
"ops": {
"eq": "equals",
"ne": "not equal",
"gt": "greater than",
"gte": "greater or equal",
"lt": "less than",
"lte": "less or equal",
"like": "contains",
"in": "in list",
"is_null": "is empty",
"is_not_null": "is not empty"
}
}
1 change: 1 addition & 0 deletions client/src/i18n/locales/vi/nav.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"agentBoard": "Bảng Kanban",
"sessions": "Phiên",
"activityFeed": "Luồng hoạt động",
"queryExplorer": "Trình khám phá truy vấn",
"analytics": "Phân tích",
"workflows": "Quy trình",
"alerts": "Cảnh báo",
Expand Down
61 changes: 61 additions & 0 deletions client/src/i18n/locales/vi/query.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
{
"title": "Trình khám phá truy vấn",
"subtitle": "Xây dựng truy vấn tùy biến trên dữ liệu bảng điều khiển và xuất kết quả.",
"entity": "Thực thể",
"match": "Khớp",
"matchAnd": "VÀ",
"matchOr": "HOẶC",
"limit": "Giới hạn",
"sort": "Sắp xếp",
"sortNone": "Không sắp xếp",
"asc": "Tăng dần",
"desc": "Giảm dần",
"filters": "Bộ lọc",
"addFilter": "Thêm bộ lọc",
"removeFilter": "Xóa bộ lọc",
"noFilters": "Chưa có bộ lọc — truy vấn trả về tất cả các hàng trong giới hạn.",
"valuePlaceholder": "Giá trị",
"valuePlaceholderIn": "Các giá trị cách nhau bởi dấu phẩy",
"noValueNeeded": "Không cần giá trị",
"run": "Chạy truy vấn",
"running": "Đang chạy...",
"save": "Lưu",
"confirmSave": "Lưu",
"cancel": "Hủy",
"saveNamePlaceholder": "Tên truy vấn",
"exportCsv": "Xuất CSV",
"exportJson": "Xuất JSON",
"savedQueries": "Truy vấn đã lưu",
"noSaved": "Chưa có truy vấn nào được lưu.",
"loadQuery": "Tải truy vấn",
"deleteQuery": "Xóa truy vấn",
"readyTitle": "Xây dựng và chạy truy vấn",
"readyDesc": "Chọn một thực thể, thêm bộ lọc và nhấn Chạy truy vấn để xem kết quả tại đây.",
"noResults": "Không có hàng nào khớp",
"noResultsDesc": "Không có hàng nào khớp với bộ lọc hiện tại. Hãy thử nới lỏng bộ lọc hoặc đổi chế độ khớp.",
"rowsTotal": "Hiển thị {{shown}} trên {{count}} hàng",
"tookMs": "{{ms}} ms",
"truncated": "Kết quả đã bị cắt bớt theo giới hạn {{limit}} hàng. Hãy tăng giới hạn hoặc thêm bộ lọc để thu hẹp tập kết quả.",
"errorSchema": "Không tải được lược đồ truy vấn.",
"errorRun": "Không chạy được truy vấn.",
"errorSaved": "Không tải được các truy vấn đã lưu.",
"errorSave": "Không lưu được truy vấn.",
"errorDelete": "Không xóa được truy vấn.",
"entities": {
"events": "Sự kiện",
"agents": "Agent",
"sessions": "Phiên"
},
"ops": {
"eq": "bằng",
"ne": "khác",
"gt": "lớn hơn",
"gte": "lớn hơn hoặc bằng",
"lt": "nhỏ hơn",
"lte": "nhỏ hơn hoặc bằng",
"like": "chứa",
"in": "trong danh sách",
"is_null": "trống",
"is_not_null": "không trống"
}
}
1 change: 1 addition & 0 deletions client/src/i18n/locales/zh/nav.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"agentBoard": "Kanban 看板",
"sessions": "会话",
"activityFeed": "活动流",
"queryExplorer": "查询浏览器",
"analytics": "分析",
"workflows": "工作流",
"alerts": "警报",
Expand Down
61 changes: 61 additions & 0 deletions client/src/i18n/locales/zh/query.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
{
"title": "查询浏览器",
"subtitle": "针对仪表盘数据构建临时查询并导出结果。",
"entity": "实体",
"match": "匹配",
"matchAnd": "并且",
"matchOr": "或者",
"limit": "条数上限",
"sort": "排序",
"sortNone": "不排序",
"asc": "升序",
"desc": "降序",
"filters": "筛选条件",
"addFilter": "添加条件",
"removeFilter": "移除条件",
"noFilters": "暂无筛选条件 —— 查询将返回上限以内的所有行。",
"valuePlaceholder": "值",
"valuePlaceholderIn": "用逗号分隔多个值",
"noValueNeeded": "无需填写值",
"run": "运行查询",
"running": "运行中...",
"save": "保存",
"confirmSave": "保存",
"cancel": "取消",
"saveNamePlaceholder": "查询名称",
"exportCsv": "导出 CSV",
"exportJson": "导出 JSON",
"savedQueries": "已保存的查询",
"noSaved": "暂无已保存的查询。",
"loadQuery": "加载查询",
"deleteQuery": "删除查询",
"readyTitle": "构建并运行查询",
"readyDesc": "选择一个实体,添加筛选条件,然后点击“运行查询”即可在此处查看结果。",
"noResults": "没有匹配的行",
"noResultsDesc": "当前筛选条件没有匹配的行。请尝试放宽条件或切换匹配模式。",
"rowsTotal": "显示 {{count}} 行中的 {{shown}} 行",
"tookMs": "{{ms}} 毫秒",
"truncated": "结果已被截断至上限 {{limit}} 行。请提高上限或添加筛选条件以缩小结果集。",
"errorSchema": "加载查询架构失败。",
"errorRun": "运行查询失败。",
"errorSaved": "加载已保存的查询失败。",
"errorSave": "保存查询失败。",
"errorDelete": "删除查询失败。",
"entities": {
"events": "事件",
"agents": "Agent",
"sessions": "会话"
},
"ops": {
"eq": "等于",
"ne": "不等于",
"gt": "大于",
"gte": "大于等于",
"lt": "小于",
"lte": "小于等于",
"like": "包含",
"in": "在列表中",
"is_null": "为空",
"is_not_null": "不为空"
}
}
55 changes: 55 additions & 0 deletions client/src/lib/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ import type {
CostResult,
DashboardEvent,
ModelPricing,
QueryBody,
QueryRunResult,
QuerySchema,
SavedQuery,
Session,
SessionDrillIn,
SessionStats,
Expand Down Expand Up @@ -165,6 +169,39 @@ export const api = {
facets: () => request<{ event_types: string[]; tool_names: string[] }>("/events/facets"),
},

query: {
/** Fetch the entity/field/operator schema that drives the builder. */
schema: () => request<QuerySchema>("/query/schema"),
/** Run a query. JSON by default; pass `format: "csv"` to get the raw
* Response back (a text/csv download) without forcing a JSON parse. */
run: ((body: QueryBody, format?: "csv") => {
if (format === "csv") {
return runCsv(body);
}
return request<QueryRunResult>("/query/run", {
method: "POST",
body: JSON.stringify(body),
});
}) as {
(body: QueryBody): Promise<QueryRunResult>;
(body: QueryBody, format: "csv"): Promise<Response>;
},
saved: {
// The server wraps these as { saved: ... } (repo convention); unwrap so
// callers get the bare row(s).
list: () => request<{ saved: SavedQuery[] }>("/query/saved").then((r) => r.saved),
create: (body: { name: string; query: QueryBody; tags?: string[] }) =>
request<{ saved: SavedQuery }>("/query/saved", {
method: "POST",
body: JSON.stringify(body),
}).then((r) => r.saved),
remove: (id: string | number) =>
request<{ ok: true }>(`/query/saved/${encodeURIComponent(String(id))}`, {
method: "DELETE",
}),
},
},

analytics: {
get: () => request<Analytics>(`/analytics?tz_offset=${new Date().getTimezoneOffset()}`),
},
Expand Down Expand Up @@ -466,6 +503,24 @@ export const api = {
},
};

/**
* POST /api/query/run?format=csv — returns the raw Response so the caller can
* stream it into a Blob download. We don't parse the body here (it's text/csv,
* not JSON), but we still surface server errors via the shared error shape.
*/
async function runCsv(body: QueryBody): Promise<Response> {
const res = await fetch(`${BASE}/query/run?format=csv`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body),
});
if (!res.ok) {
const errBody = await res.json().catch(() => ({}));
throw new Error(errBody?.error?.message || `HTTP ${res.status}`);
}
return res;
}

function requestBackupsHelper(params?: { scope?: "user" | "project"; type?: CcArtifactType }) {
const qs = new URLSearchParams();
if (params?.scope) qs.set("scope", params.scope);
Expand Down
Loading
Loading