├── .prettierignore ├── tools └── cruncher-tools │ ├── src │ ├── index.ts │ └── generators │ │ ├── schema.d.ts │ │ ├── files │ │ ├── .gitignore │ │ ├── src │ │ │ ├── controller.test.ts.template │ │ │ ├── controller.ts.template │ │ │ └── index.ts.template │ │ ├── vitest.config.ts │ │ ├── README.md │ │ ├── tsconfig.json.template │ │ ├── tsconfig.spec.json.template │ │ └── package.json.template │ │ ├── schema.json │ │ ├── adapter-generator.spec.ts │ │ └── adapter-generator.ts │ ├── generators.json │ ├── README.md │ ├── package.json │ ├── tsconfig.json │ ├── tsconfig.lib.json │ ├── tsconfig.spec.json │ ├── eslint.config.mjs │ ├── vite.config.ts │ └── project.json ├── docs ├── public │ ├── CNAME │ └── favicon.svg ├── src │ ├── assets │ │ ├── favicon.ico │ │ ├── splash.png │ │ ├── cruncher_logo.webp │ │ ├── cruncher_full_logo.png │ │ └── landing.css │ ├── content │ │ └── docs │ │ │ ├── adapters │ │ │ ├── examples │ │ │ │ ├── docker.cruncher.config.yaml │ │ │ │ └── docker-patterns.cruncher.config.yaml │ │ │ ├── coralogix.mdx │ │ │ └── grafana-loki.mdx │ │ │ ├── getting-started │ │ │ ├── 03-quick-start.cruncher.config.yaml │ │ │ ├── 02-installation.mdx │ │ │ ├── 03-quick-start.mdx │ │ │ ├── 01-introduction.mdx │ │ │ ├── 04-adapters.mdx │ │ │ └── 05-config-file.mdx │ │ │ ├── qql-reference │ │ │ ├── functions │ │ │ │ ├── strings │ │ │ │ │ ├── lower.mdx │ │ │ │ │ ├── upper.mdx │ │ │ │ │ ├── length.mdx │ │ │ │ │ └── trim.mdx │ │ │ │ ├── booleans │ │ │ │ │ ├── isNull.mdx │ │ │ │ │ ├── isNotNull.mdx │ │ │ │ │ ├── endsWith.mdx │ │ │ │ │ ├── startsWith.mdx │ │ │ │ │ ├── contains.mdx │ │ │ │ │ └── match.mdx │ │ │ │ └── numbers │ │ │ │ │ ├── abs.mdx │ │ │ │ │ ├── ceil.mdx │ │ │ │ │ ├── round.mdx │ │ │ │ │ └── floor.mdx │ │ │ ├── 01-qql.mdx │ │ │ ├── commands │ │ │ │ ├── sort.mdx │ │ │ │ ├── regex.mdx │ │ │ │ ├── table.mdx │ │ │ │ ├── timechart.mdx │ │ │ │ ├── stats.mdx │ │ │ │ ├── unpack.mdx │ │ │ │ └── eval.mdx │ │ │ ├── 03-data-types.mdx │ │ │ └── 04-logical-expressions.mdx │ │ │ └── index.mdx │ ├── content.config.ts │ └── components │ │ └── ParamItem.astro ├── .vscode │ ├── extensions.json │ └── launch.json ├── tsconfig.json ├── .gitignore ├── package.json └── astro.config.mjs ├── apps └── cruncher │ ├── .prettierignore │ ├── .gitignore │ ├── src │ ├── processes │ │ ├── frontend │ │ │ ├── vite-env.d.ts │ │ │ ├── global.d.ts │ │ │ ├── core │ │ │ │ ├── client.ts │ │ │ │ ├── utils │ │ │ │ │ ├── highlight.tsx │ │ │ │ │ └── shadowUtils.ts │ │ │ │ ├── notifyError.tsx │ │ │ │ └── store │ │ │ │ │ └── queryState.ts │ │ │ ├── electron.d.ts │ │ │ ├── routes │ │ │ │ ├── index.tsx │ │ │ │ └── settings │ │ │ │ │ ├── index.tsx │ │ │ │ │ ├── general.tsx │ │ │ │ │ └── route.tsx │ │ │ ├── index.css │ │ │ ├── components │ │ │ │ ├── ui │ │ │ │ │ ├── shortcut.tsx │ │ │ │ │ ├── editor │ │ │ │ │ │ └── Editor.test.ts │ │ │ │ │ ├── system.tsx │ │ │ │ │ ├── close-button.tsx │ │ │ │ │ ├── radio.tsx │ │ │ │ │ ├── checkbox.tsx │ │ │ │ │ ├── useOutsideDetector.ts │ │ │ │ │ ├── data-list.tsx │ │ │ │ │ ├── progress.tsx │ │ │ │ │ ├── field.tsx │ │ │ │ │ ├── hover-card.tsx │ │ │ │ │ ├── button.tsx │ │ │ │ │ ├── tooltip.tsx │ │ │ │ │ ├── toaster.tsx │ │ │ │ │ ├── provider.tsx │ │ │ │ │ ├── input-group.tsx │ │ │ │ │ ├── drawer.tsx │ │ │ │ │ ├── popover.tsx │ │ │ │ │ ├── dialog.tsx │ │ │ │ │ ├── color-mode.tsx │ │ │ │ │ └── toggle-tip.tsx │ │ │ │ └── presets │ │ │ │ │ ├── Tooltip.tsx │ │ │ │ │ └── IconButton.tsx │ │ │ ├── features │ │ │ │ └── searcher │ │ │ │ │ ├── data-displays │ │ │ │ │ └── events │ │ │ │ │ │ └── state.ts │ │ │ │ │ ├── header │ │ │ │ │ ├── settings-drawer │ │ │ │ │ │ └── Drawer.tsx │ │ │ │ │ └── Timer.tsx │ │ │ │ │ ├── TabsLineButtons.tsx │ │ │ │ │ └── Highlighter.tsx │ │ │ └── main.tsx │ │ ├── server │ │ │ ├── plugins_engine │ │ │ │ ├── router_messages.d.ts │ │ │ │ ├── trpc.ts │ │ │ │ └── protocolOut.ts │ │ │ ├── types.ts │ │ │ ├── loki │ │ │ │ └── constants.ts │ │ │ ├── lib │ │ │ │ ├── config.ts │ │ │ │ ├── state.ts │ │ │ │ ├── procLock.ts │ │ │ │ ├── displayTypes.ts │ │ │ │ ├── pipelineEngine │ │ │ │ │ ├── stats.ts │ │ │ │ │ ├── where.ts │ │ │ │ │ ├── table.ts │ │ │ │ │ ├── regex.ts │ │ │ │ │ ├── root.ts │ │ │ │ │ └── unpack.ts │ │ │ │ ├── zip.ts │ │ │ │ └── dateUtils.ts │ │ │ ├── config │ │ │ │ └── schema.ts │ │ │ └── engineV2 │ │ │ │ └── utils.ts │ │ └── main │ │ │ ├── utils │ │ │ ├── ipc.ts │ │ │ ├── requestFromServer.ts │ │ │ └── auth.ts │ │ │ ├── preload.ts │ │ │ └── forge.d.ts │ └── icons │ │ ├── mac │ │ └── icon.icns │ │ ├── png │ │ ├── 16x16.png │ │ ├── 24x24.png │ │ ├── 32x32.png │ │ ├── 48x48.png │ │ ├── 64x64.png │ │ ├── 128x128.png │ │ ├── 256x256.png │ │ ├── 512x512.png │ │ └── 1024x1024.png │ │ └── win │ │ └── icon.ico │ ├── forge.env.d.ts │ ├── tsr.config.json │ ├── tsconfig.json │ ├── vite.preload.config.mts │ ├── index.html │ ├── vitest.config.mts │ ├── vite.main.config.mts │ ├── vite.renderer.config.mts │ ├── project.json │ ├── tsconfig.node.json │ ├── vite.server.config.mts │ ├── tsconfig.app.json │ └── eslint.config.mjs ├── .vscode ├── settings.json └── launch.json ├── .prettierrc.yaml ├── vitest.workspace.ts ├── packages ├── adapters │ ├── loki │ │ ├── CHANGELOG.md │ │ ├── .gitignore │ │ ├── src │ │ │ ├── controller.test.ts │ │ │ └── index.ts │ │ ├── vitest.config.ts │ │ ├── README.md │ │ ├── tsconfig.json │ │ ├── project.json │ │ ├── tsconfig.spec.json │ │ └── package.json │ ├── docker │ │ ├── .gitignore │ │ ├── project.json │ │ ├── tsconfig.json │ │ ├── package.json │ │ ├── CHANGELOG.md │ │ └── src │ │ │ └── index.ts │ ├── utils │ │ ├── .gitignore │ │ ├── project.json │ │ ├── tsconfig.json │ │ ├── src │ │ │ ├── formatters.ts │ │ │ └── index.ts │ │ ├── CHANGELOG.md │ │ └── package.json │ ├── coralogix │ │ ├── .gitignore │ │ ├── project.json │ │ ├── tsconfig.json │ │ ├── src │ │ │ └── index.ts │ │ ├── CHANGELOG.md │ │ └── package.json │ ├── mocked-data │ │ ├── .gitignore │ │ ├── src │ │ │ ├── controller.test.ts │ │ │ └── index.ts │ │ ├── vitest.config.ts │ │ ├── README.md │ │ ├── project.json │ │ ├── tsconfig.json │ │ ├── tsconfig.spec.json │ │ ├── CHANGELOG.md │ │ └── package.json │ └── grafana-loki-browser │ │ ├── .gitignore │ │ ├── project.json │ │ ├── tsconfig.json │ │ ├── src │ │ ├── types.ts │ │ ├── index.ts │ │ └── query.ts │ │ ├── package.json │ │ └── CHANGELOG.md ├── utils │ ├── .gitignore │ ├── project.json │ ├── tsconfig.json │ ├── CHANGELOG.md │ └── package.json └── qql │ ├── vitest.config.ts │ ├── project.json │ ├── tsconfig.json │ ├── CHANGELOG.md │ ├── package.json │ └── src │ ├── index.ts │ └── searchTree.ts ├── pnpm-workspace.yaml ├── .npmrc ├── project.json ├── devbox.json ├── tsconfig.modules.json ├── tsconfig.base.json ├── .verdaccio └── config.yml ├── .github └── workflows │ ├── release.yml │ ├── deploy-docs.yml │ └── checks.yml ├── eslint.config.mjs ├── nx.json ├── .gitignore └── package.json /.prettierignore: -------------------------------------------------------------------------------- 1 | **/*.gen.* 2 | -------------------------------------------------------------------------------- /tools/cruncher-tools/src/index.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/public/CNAME: -------------------------------------------------------------------------------- 1 | cruncher.iamshobe.com -------------------------------------------------------------------------------- /apps/cruncher/.prettierignore: -------------------------------------------------------------------------------- 1 | **/*.gen.* 2 | -------------------------------------------------------------------------------- /apps/cruncher/.gitignore: -------------------------------------------------------------------------------- 1 | .tanstack/ 2 | node_modules/ 3 | dist/ -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "eslint.validate": ["json"] 3 | } 4 | -------------------------------------------------------------------------------- /.prettierrc.yaml: -------------------------------------------------------------------------------- 1 | trailingComma: all 2 | tabWidth: 2 3 | semi: true 4 | singleQuote: false 5 | -------------------------------------------------------------------------------- /apps/cruncher/src/processes/frontend/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /apps/cruncher/forge.env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /apps/cruncher/src/processes/frontend/global.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /docs/src/assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IamShobe/cruncher/HEAD/docs/src/assets/favicon.ico -------------------------------------------------------------------------------- /docs/src/assets/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IamShobe/cruncher/HEAD/docs/src/assets/splash.png -------------------------------------------------------------------------------- /docs/src/assets/cruncher_logo.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IamShobe/cruncher/HEAD/docs/src/assets/cruncher_logo.webp -------------------------------------------------------------------------------- /apps/cruncher/src/icons/mac/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IamShobe/cruncher/HEAD/apps/cruncher/src/icons/mac/icon.icns -------------------------------------------------------------------------------- /apps/cruncher/src/icons/png/16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IamShobe/cruncher/HEAD/apps/cruncher/src/icons/png/16x16.png -------------------------------------------------------------------------------- /apps/cruncher/src/icons/png/24x24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IamShobe/cruncher/HEAD/apps/cruncher/src/icons/png/24x24.png -------------------------------------------------------------------------------- /apps/cruncher/src/icons/png/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IamShobe/cruncher/HEAD/apps/cruncher/src/icons/png/32x32.png -------------------------------------------------------------------------------- /apps/cruncher/src/icons/png/48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IamShobe/cruncher/HEAD/apps/cruncher/src/icons/png/48x48.png -------------------------------------------------------------------------------- /apps/cruncher/src/icons/png/64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IamShobe/cruncher/HEAD/apps/cruncher/src/icons/png/64x64.png -------------------------------------------------------------------------------- /apps/cruncher/src/icons/win/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IamShobe/cruncher/HEAD/apps/cruncher/src/icons/win/icon.ico -------------------------------------------------------------------------------- /tools/cruncher-tools/src/generators/schema.d.ts: -------------------------------------------------------------------------------- 1 | export interface AdapterGeneratorGeneratorSchema { 2 | name: string; 3 | } 4 | -------------------------------------------------------------------------------- /apps/cruncher/src/icons/png/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IamShobe/cruncher/HEAD/apps/cruncher/src/icons/png/128x128.png -------------------------------------------------------------------------------- /apps/cruncher/src/icons/png/256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IamShobe/cruncher/HEAD/apps/cruncher/src/icons/png/256x256.png -------------------------------------------------------------------------------- /apps/cruncher/src/icons/png/512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IamShobe/cruncher/HEAD/apps/cruncher/src/icons/png/512x512.png -------------------------------------------------------------------------------- /docs/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["astro-build.astro-vscode"], 3 | "unwantedRecommendations": [] 4 | } 5 | -------------------------------------------------------------------------------- /docs/src/assets/cruncher_full_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IamShobe/cruncher/HEAD/docs/src/assets/cruncher_full_logo.png -------------------------------------------------------------------------------- /vitest.workspace.ts: -------------------------------------------------------------------------------- 1 | export default [ 2 | "**/vite.config.{mjs,js,ts,mts}", 3 | "**/vitest.config.{mjs,js,ts,mts}", 4 | ]; 5 | -------------------------------------------------------------------------------- /apps/cruncher/src/icons/png/1024x1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IamShobe/cruncher/HEAD/apps/cruncher/src/icons/png/1024x1024.png -------------------------------------------------------------------------------- /docs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "astro/tsconfigs/strict", 3 | "include": [".astro/types.d.ts", "**/*"], 4 | "exclude": ["dist"] 5 | } 6 | -------------------------------------------------------------------------------- /apps/cruncher/src/processes/server/plugins_engine/router_messages.d.ts: -------------------------------------------------------------------------------- 1 | import { appRouter } from "./router"; 2 | export type AppRouter = typeof appRouter; 3 | -------------------------------------------------------------------------------- /apps/cruncher/src/processes/frontend/core/client.ts: -------------------------------------------------------------------------------- 1 | import { QueryClient } from "@tanstack/react-query"; 2 | 3 | export const queryClient = new QueryClient(); 4 | -------------------------------------------------------------------------------- /packages/adapters/loki/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.1.0 (2025-06-28) 2 | 3 | ### 🚀 Features 4 | 5 | - Support loki adapter ([#16](https://github.com/IamShobe/cruncher/pull/16)) -------------------------------------------------------------------------------- /apps/cruncher/src/processes/server/types.ts: -------------------------------------------------------------------------------- 1 | // Define a type for IPC messages 2 | export interface IPCMessage { 3 | type: string; 4 | [key: string]: unknown; 5 | } 6 | -------------------------------------------------------------------------------- /packages/utils/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ 3 | coverage/ 4 | .vscode/ 5 | .idea/ 6 | .env 7 | .DS_Store 8 | npm-debug.log 9 | yarn-debug.log 10 | yarn-error.log 11 | -------------------------------------------------------------------------------- /packages/adapters/docker/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ 3 | coverage/ 4 | .vscode/ 5 | .idea/ 6 | .env 7 | .DS_Store 8 | npm-debug.log 9 | yarn-debug.log 10 | yarn-error.log 11 | -------------------------------------------------------------------------------- /packages/adapters/loki/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ 3 | coverage/ 4 | .vscode/ 5 | .idea/ 6 | .env 7 | .DS_Store 8 | npm-debug.log 9 | yarn-debug.log 10 | yarn-error.log 11 | -------------------------------------------------------------------------------- /packages/adapters/utils/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ 3 | coverage/ 4 | .vscode/ 5 | .idea/ 6 | .env 7 | .DS_Store 8 | npm-debug.log 9 | yarn-debug.log 10 | yarn-error.log 11 | -------------------------------------------------------------------------------- /packages/adapters/coralogix/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ 3 | coverage/ 4 | .vscode/ 5 | .idea/ 6 | .env 7 | .DS_Store 8 | npm-debug.log 9 | yarn-debug.log 10 | yarn-error.log 11 | -------------------------------------------------------------------------------- /packages/adapters/loki/src/controller.test.ts: -------------------------------------------------------------------------------- 1 | import { test, expect } from "vitest"; 2 | import adapter from "."; 3 | 4 | test("import", () => { 5 | expect(adapter).toBeDefined(); 6 | }); 7 | -------------------------------------------------------------------------------- /packages/adapters/mocked-data/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ 3 | coverage/ 4 | .vscode/ 5 | .idea/ 6 | .env 7 | .DS_Store 8 | npm-debug.log 9 | yarn-debug.log 10 | yarn-error.log 11 | -------------------------------------------------------------------------------- /packages/qql/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vitest/config"; 2 | 3 | export default defineConfig({ 4 | test: { 5 | exclude: ["dist/**"], 6 | watch: false, 7 | }, 8 | }); 9 | -------------------------------------------------------------------------------- /apps/cruncher/src/processes/frontend/electron.d.ts: -------------------------------------------------------------------------------- 1 | import { electronAPI } from "../main/preload"; 2 | 3 | declare global { 4 | interface Window { 5 | electronAPI: typeof electronAPI; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /docs/src/content/docs/adapters/examples/docker.cruncher.config.yaml: -------------------------------------------------------------------------------- 1 | profiles: 2 | default: 3 | connectors: [docker] 4 | 5 | connectors: 6 | - name: docker 7 | type: docker 8 | params: {} 9 | -------------------------------------------------------------------------------- /packages/adapters/grafana-loki-browser/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ 3 | coverage/ 4 | .vscode/ 5 | .idea/ 6 | .env 7 | .DS_Store 8 | npm-debug.log 9 | yarn-debug.log 10 | yarn-error.log 11 | -------------------------------------------------------------------------------- /packages/adapters/mocked-data/src/controller.test.ts: -------------------------------------------------------------------------------- 1 | import { test, expect } from "vitest"; 2 | import mockedData from "."; 3 | 4 | test("import", () => { 5 | expect(mockedData).toBeDefined(); 6 | }); 7 | -------------------------------------------------------------------------------- /tools/cruncher-tools/src/generators/files/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ 3 | coverage/ 4 | .vscode/ 5 | .idea/ 6 | .env 7 | .DS_Store 8 | npm-debug.log 9 | yarn-debug.log 10 | yarn-error.log 11 | -------------------------------------------------------------------------------- /docs/src/content/docs/getting-started/03-quick-start.cruncher.config.yaml: -------------------------------------------------------------------------------- 1 | profiles: 2 | default: 3 | connectors: [mock] 4 | connectors: 5 | - name: mock 6 | type: mocked_data 7 | params: {} 8 | -------------------------------------------------------------------------------- /tools/cruncher-tools/src/generators/files/src/controller.test.ts.template: -------------------------------------------------------------------------------- 1 | import { test, expect } from "vitest"; 2 | import adapter from "."; 3 | 4 | test("import", () => { 5 | expect(adapter).toBeDefined(); 6 | }); 7 | -------------------------------------------------------------------------------- /packages/adapters/loki/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vitest/config"; 2 | 3 | export default defineConfig({ 4 | test: { 5 | exclude: ["dist/**", "node_modules/**"], 6 | watch: false, 7 | }, 8 | }); 9 | -------------------------------------------------------------------------------- /apps/cruncher/tsr.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "routesDirectory": "./src/processes/frontend/routes", 3 | "generatedRouteTree": "./src/processes/frontend/routeTree.gen.ts", 4 | "routeFileIgnorePrefix": "-", 5 | "quoteStyle": "single" 6 | } -------------------------------------------------------------------------------- /packages/adapters/mocked-data/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vitest/config"; 2 | 3 | export default defineConfig({ 4 | test: { 5 | exclude: ["dist/**", "node_modules/**"], 6 | watch: false, 7 | }, 8 | }); 9 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - packages/utils 3 | - packages/qql 4 | - packages/adapters/utils 5 | - packages/adapters/* 6 | - docs/ 7 | - apps/* 8 | - tools/* 9 | 10 | ignoredBuiltDependencies: 11 | - sharp 12 | -------------------------------------------------------------------------------- /packages/qql/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@cruncher/qql", 3 | "$schema": "../../node_modules/nx/schemas/project-schema.json", 4 | "projectType": "library", 5 | "sourceRoot": "packages/qql/src", 6 | "tags": [], 7 | "targets": {} 8 | } 9 | -------------------------------------------------------------------------------- /tools/cruncher-tools/src/generators/files/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vitest/config"; 2 | 3 | export default defineConfig({ 4 | test: { 5 | exclude: ["dist/**", "node_modules/**"], 6 | watch: false, 7 | }, 8 | }); 9 | -------------------------------------------------------------------------------- /packages/adapters/loki/README.md: -------------------------------------------------------------------------------- 1 | # loki adapter 2 | 3 | ## Building 4 | 5 | Run `pnpm run build` to build the library. 6 | 7 | ## Running unit tests 8 | 9 | Run `pnpm run test` to execute the unit tests via [Vitest](https://vitest.dev/). 10 | -------------------------------------------------------------------------------- /packages/utils/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@cruncher/utils", 3 | "$schema": "../../node_modules/nx/schemas/project-schema.json", 4 | "projectType": "library", 5 | "sourceRoot": "packages/utils/src", 6 | "tags": [], 7 | "targets": {} 8 | } 9 | -------------------------------------------------------------------------------- /apps/cruncher/src/processes/server/loki/constants.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from "node:path"; 2 | import { getStateDirPath } from "~lib/state"; 3 | 4 | export const lokiVersion = "v3.4.4"; 5 | export const lokiLocationDir = resolve(getStateDirPath(), "loki"); 6 | -------------------------------------------------------------------------------- /apps/cruncher/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "references": [ 4 | { "path": "./tsconfig.app.json" }, 5 | { "path": "./tsconfig.node.json" } 6 | ], 7 | "exclude": ["node_modules", "dist", "build", "coverage", "tmp", "e2e", "apps", "libs"] 8 | } -------------------------------------------------------------------------------- /apps/cruncher/src/processes/main/utils/ipc.ts: -------------------------------------------------------------------------------- 1 | // Utility for IPC type guards 2 | export function isIpcMessage( 3 | msg: unknown, 4 | ): msg is { type: string; [key: string]: unknown } { 5 | return typeof msg === "object" && msg !== null && "type" in msg; 6 | } 7 | -------------------------------------------------------------------------------- /packages/adapters/mocked-data/README.md: -------------------------------------------------------------------------------- 1 | # mocked-data adapter 2 | 3 | ## Building 4 | 5 | Run `pnpm run build` to build the library. 6 | 7 | ## Running unit tests 8 | 9 | Run `pnpm run test` to execute the unit tests via [Vitest](https://vitest.dev/). 10 | -------------------------------------------------------------------------------- /tools/cruncher-tools/src/generators/files/README.md: -------------------------------------------------------------------------------- 1 | # <%= name %> adapter 2 | 3 | ## Building 4 | 5 | Run `pnpm run build` to build the library. 6 | 7 | ## Running unit tests 8 | 9 | Run `pnpm run test` to execute the unit tests via [Vitest](https://vitest.dev/). 10 | -------------------------------------------------------------------------------- /apps/cruncher/src/processes/frontend/routes/index.tsx: -------------------------------------------------------------------------------- 1 | import { createFileRoute } from "@tanstack/react-router"; 2 | import { SearcherWrapper } from "~features/searcher/SearcherWrapper"; 3 | 4 | export const Route = createFileRoute("/")({ 5 | component: SearcherWrapper, 6 | }); 7 | -------------------------------------------------------------------------------- /apps/cruncher/src/processes/frontend/routes/settings/index.tsx: -------------------------------------------------------------------------------- 1 | import { createFileRoute, redirect } from "@tanstack/react-router"; 2 | 3 | export const Route = createFileRoute("/settings/")({ 4 | loader: () => { 5 | throw redirect({ to: "/settings/general" }); 6 | }, 7 | }); 8 | -------------------------------------------------------------------------------- /apps/cruncher/vite.preload.config.mts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | 3 | import tsconfigPaths from 'vite-tsconfig-paths'; 4 | 5 | // https://vitejs.dev/config 6 | export default defineConfig({ 7 | plugins: [ 8 | tsconfigPaths(), 9 | ] 10 | }); 11 | -------------------------------------------------------------------------------- /docs/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "command": "./node_modules/.bin/astro dev", 6 | "name": "Development server", 7 | "request": "launch", 8 | "type": "node-terminal" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /packages/adapters/docker/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@cruncher/adapter-docker", 3 | "$schema": "../../../node_modules/nx/schemas/project-schema.json", 4 | "projectType": "library", 5 | "sourceRoot": "packages/adapters/docker/src", 6 | "tags": [], 7 | "targets": {} 8 | } 9 | -------------------------------------------------------------------------------- /packages/adapters/utils/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@cruncher/adapters-utils", 3 | "$schema": "../../../node_modules/nx/schemas/project-schema.json", 4 | "projectType": "library", 5 | "sourceRoot": "packages/adapters/utils/src", 6 | "tags": [], 7 | "targets": {} 8 | } 9 | -------------------------------------------------------------------------------- /apps/cruncher/src/processes/frontend/index.css: -------------------------------------------------------------------------------- 1 | html, 2 | body, 3 | #root { 4 | font-family: 5 | -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, 6 | sans-serif; 7 | height: 100%; 8 | width: 100%; 9 | padding: 0; 10 | margin: 0; 11 | } 12 | -------------------------------------------------------------------------------- /apps/cruncher/src/processes/server/lib/config.ts: -------------------------------------------------------------------------------- 1 | const configDirPath = `${process.env.HOME}/.config/cruncher/`; 2 | 3 | export const getConfigDirPath = () => { 4 | if (!configDirPath) { 5 | throw new Error("Config directory path is not set"); 6 | } 7 | return configDirPath; 8 | }; 9 | -------------------------------------------------------------------------------- /apps/cruncher/src/processes/server/lib/state.ts: -------------------------------------------------------------------------------- 1 | const stateDirPath = `${process.env.HOME}/.local/share/cruncher/`; 2 | 3 | export const getStateDirPath = () => { 4 | if (!stateDirPath) { 5 | throw new Error("State directory path is not set"); 6 | } 7 | return stateDirPath; 8 | }; 9 | -------------------------------------------------------------------------------- /packages/adapters/coralogix/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@cruncher/adapter-coralogix", 3 | "$schema": "../../../node_modules/nx/schemas/project-schema.json", 4 | "projectType": "library", 5 | "sourceRoot": "packages/adapters/coralogix/src", 6 | "tags": [], 7 | "targets": {} 8 | } 9 | -------------------------------------------------------------------------------- /packages/adapters/loki/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../tsconfig.modules.json", 3 | "compilerOptions": { 4 | "baseUrl": ".", 5 | "outDir": "dist", 6 | "rootDir": "src" 7 | }, 8 | "include": ["src/**/*"], 9 | "exclude": ["dist", "node_modules"] 10 | } 11 | -------------------------------------------------------------------------------- /packages/adapters/mocked-data/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@cruncher/adapters-mock", 3 | "$schema": "../../../node_modules/nx/schemas/project-schema.json", 4 | "projectType": "library", 5 | "sourceRoot": "packages/adapters/mocked-data/src", 6 | "tags": [], 7 | "targets": {} 8 | } 9 | -------------------------------------------------------------------------------- /tools/cruncher-tools/generators.json: -------------------------------------------------------------------------------- 1 | { 2 | "generators": { 3 | "adapter-generator": { 4 | "factory": "./src/generators/adapter-generator", 5 | "schema": "./src/generators/schema.json", 6 | "description": "adapter-generator generator" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /apps/cruncher/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Cruncher 6 | 7 | 8 |
9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /docs/src/content.config.ts: -------------------------------------------------------------------------------- 1 | import { defineCollection } from 'astro:content'; 2 | import { docsLoader } from '@astrojs/starlight/loaders'; 3 | import { docsSchema } from '@astrojs/starlight/schema'; 4 | export const collections = { 5 | docs: defineCollection({ loader: docsLoader(), schema: docsSchema() }), 6 | }; 7 | -------------------------------------------------------------------------------- /apps/cruncher/vitest.config.mts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vitest/config"; 2 | import tsconfigPaths from "vite-tsconfig-paths"; 3 | 4 | export default defineConfig({ 5 | test: { 6 | exclude: ["dist/**", "node_modules/**"], 7 | watch: false, 8 | }, 9 | plugins: [tsconfigPaths()], 10 | }); 11 | -------------------------------------------------------------------------------- /packages/adapters/grafana-loki-browser/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@cruncher/adapter-grafana-loki-browser", 3 | "$schema": "../../../node_modules/nx/schemas/project-schema.json", 4 | "projectType": "library", 5 | "sourceRoot": "packages/adapters/grafana-loki-browser/src", 6 | "tags": [], 7 | "targets": {} 8 | } 9 | -------------------------------------------------------------------------------- /packages/adapters/loki/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@cruncher/adapter-loki", 3 | "$schema": "../../../node_modules/nx/schemas/project-schema.json", 4 | "projectType": "library", 5 | "sourceRoot": "packages/adapters/loki/src", 6 | "// targets": "to see all targets run: nx show project loki --web", 7 | "targets": {} 8 | } 9 | -------------------------------------------------------------------------------- /tools/cruncher-tools/README.md: -------------------------------------------------------------------------------- 1 | # cruncher-tools 2 | 3 | This library was generated with [Nx](https://nx.dev). 4 | 5 | ## Building 6 | 7 | Run `nx build cruncher-tools` to build the library. 8 | 9 | ## Running unit tests 10 | 11 | Run `nx test cruncher-tools` to execute the unit tests via [Vitest](https://vitest.dev/). 12 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | # build output 2 | dist/ 3 | # generated types 4 | .astro/ 5 | 6 | # dependencies 7 | node_modules/ 8 | 9 | # logs 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | pnpm-debug.log* 14 | 15 | 16 | # environment variables 17 | .env 18 | .env.production 19 | 20 | # macOS-specific files 21 | .DS_Store 22 | -------------------------------------------------------------------------------- /packages/qql/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.modules.json", 3 | "compilerOptions": { 4 | "baseUrl": ".", 5 | "outDir": "dist", 6 | "rootDir": "src" 7 | }, 8 | "include": [ 9 | "src/**/*" 10 | ], 11 | "exclude": [ 12 | "dist", 13 | "node_modules" 14 | ] 15 | } -------------------------------------------------------------------------------- /packages/utils/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.modules.json", 3 | "compilerOptions": { 4 | "baseUrl": ".", 5 | "outDir": "dist", 6 | "rootDir": "src" 7 | }, 8 | "include": [ 9 | "src/**/*" 10 | ], 11 | "exclude": [ 12 | "dist", 13 | "node_modules" 14 | ] 15 | } -------------------------------------------------------------------------------- /tools/cruncher-tools/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cruncher-tools", 3 | "version": "0.0.1", 4 | "private": true, 5 | "type": "commonjs", 6 | "main": "./src/index.js", 7 | "types": "./src/index.d.ts", 8 | "dependencies": { 9 | "@nx/devkit": "21.2.1", 10 | "tslib": "^2.3.0" 11 | }, 12 | "generators": "./generators.json" 13 | } 14 | -------------------------------------------------------------------------------- /tools/cruncher-tools/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "module": "commonjs" 5 | }, 6 | "files": [], 7 | "include": [], 8 | "references": [ 9 | { 10 | "path": "./tsconfig.lib.json" 11 | }, 12 | { 13 | "path": "./tsconfig.spec.json" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /packages/adapters/docker/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../tsconfig.modules.json", 3 | "compilerOptions": { 4 | "baseUrl": ".", 5 | "outDir": "dist", 6 | "rootDir": "src" 7 | }, 8 | "include": [ 9 | "src/**/*" 10 | ], 11 | "exclude": [ 12 | "dist", 13 | "node_modules" 14 | ] 15 | } -------------------------------------------------------------------------------- /packages/adapters/utils/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../tsconfig.modules.json", 3 | "compilerOptions": { 4 | "baseUrl": ".", 5 | "outDir": "dist", 6 | "rootDir": "src" 7 | }, 8 | "include": [ 9 | "src/**/*" 10 | ], 11 | "exclude": [ 12 | "dist", 13 | "node_modules" 14 | ] 15 | } -------------------------------------------------------------------------------- /packages/adapters/coralogix/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../tsconfig.modules.json", 3 | "compilerOptions": { 4 | "baseUrl": ".", 5 | "outDir": "dist", 6 | "rootDir": "src" 7 | }, 8 | "include": [ 9 | "src/**/*" 10 | ], 11 | "exclude": [ 12 | "dist", 13 | "node_modules" 14 | ] 15 | } -------------------------------------------------------------------------------- /packages/adapters/mocked-data/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../tsconfig.modules.json", 3 | "compilerOptions": { 4 | "baseUrl": ".", 5 | "outDir": "dist", 6 | "rootDir": "src" 7 | }, 8 | "include": [ 9 | "src/**/*" 10 | ], 11 | "exclude": [ 12 | "dist", 13 | "node_modules" 14 | ] 15 | } -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | public-hoist-pattern[] = * 2 | auto-install-peers = true 3 | strict-peer-dependencies = false 4 | prefer-workspace-packages = true 5 | link-workspace-packages = deep 6 | hoist = true 7 | shamefully-hoist = true 8 | hoist-workspace-packages = false 9 | package-manager-strict=false 10 | # https://github.com/pnpm/pnpm/issues/7024 11 | package-import-method=clone-or-copy -------------------------------------------------------------------------------- /packages/adapters/grafana-loki-browser/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../tsconfig.modules.json", 3 | "compilerOptions": { 4 | "baseUrl": ".", 5 | "outDir": "dist", 6 | "rootDir": "src" 7 | }, 8 | "include": [ 9 | "src/**/*" 10 | ], 11 | "exclude": [ 12 | "dist", 13 | "node_modules" 14 | ] 15 | } -------------------------------------------------------------------------------- /tools/cruncher-tools/src/generators/files/tsconfig.json.template: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../tsconfig.modules.json", 3 | "compilerOptions": { 4 | "baseUrl": ".", 5 | "outDir": "dist", 6 | "rootDir": "src" 7 | }, 8 | "include": [ 9 | "src/**/*" 10 | ], 11 | "exclude": [ 12 | "dist", 13 | "node_modules" 14 | ] 15 | } -------------------------------------------------------------------------------- /project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cruncher-monorepo", 3 | "$schema": "node_modules/nx/schemas/project-schema.json", 4 | "targets": { 5 | "local-registry": { 6 | "executor": "@nx/js:verdaccio", 7 | "options": { 8 | "port": 4873, 9 | "config": ".verdaccio/config.yml", 10 | "storage": "tmp/local-registry/storage" 11 | } 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /docs/src/content/docs/qql-reference/functions/strings/lower.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: lower 3 | description: Convert a string to lowercase. 4 | --- 5 | 6 | The `lower` function converts the input string to lowercase. 7 | 8 | ## Usage 9 | 10 | ``` 11 | lower(field) 12 | ``` 13 | 14 | **Example:** 15 | ``` 16 | eval user_lower = lower(user) 17 | ``` 18 | This creates a new field `user_lower` with the lowercase value of `user`. -------------------------------------------------------------------------------- /apps/cruncher/src/processes/frontend/components/ui/shortcut.tsx: -------------------------------------------------------------------------------- 1 | import { css } from "@emotion/react"; 2 | 3 | type ShortcutProps = { 4 | keys: string[]; 5 | }; 6 | 7 | export const Shortcut = ({ keys }: ShortcutProps) => { 8 | return ( 9 | 15 | {keys.join("")} 16 | 17 | ); 18 | }; 19 | -------------------------------------------------------------------------------- /docs/src/content/docs/qql-reference/functions/strings/upper.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: upper 3 | description: Convert a string to uppercase. 4 | --- 5 | 6 | The `upper` function converts the input string to uppercase. 7 | 8 | ## Usage 9 | 10 | ``` 11 | upper(field) 12 | ``` 13 | 14 | **Example:** 15 | ``` 16 | eval user_upper = upper(user) 17 | ``` 18 | This creates a new field `user_upper` with the uppercase value of `user`. 19 | -------------------------------------------------------------------------------- /docs/src/content/docs/qql-reference/functions/booleans/isNull.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: isNull 3 | description: Check if a value is null or undefined. 4 | --- 5 | 6 | The `isNull` function returns true if the given value is null or undefined. 7 | 8 | ## Usage 9 | 10 | ``` 11 | isNull(field) 12 | ``` 13 | 14 | **Example:** 15 | ``` 16 | where isNull(optional_field) 17 | ``` 18 | This matches records where `optional_field` is null or not present. -------------------------------------------------------------------------------- /docs/src/content/docs/qql-reference/functions/numbers/abs.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: abs 3 | description: Get the absolute value of a number. 4 | --- 5 | 6 | The `abs` function returns the absolute value of the input number. 7 | 8 | ## Usage 9 | 10 | ``` 11 | abs(field) 12 | ``` 13 | 14 | **Example:** 15 | ``` 16 | eval abs_duration = abs(duration) 17 | ``` 18 | This creates a new field `abs_duration` with the absolute value of `duration`. 19 | -------------------------------------------------------------------------------- /devbox.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/jetify-com/devbox/0.14.2/.schema/devbox.schema.json", 3 | "packages": [ 4 | "nodejs_22@latest", 5 | "pnpm@latest" 6 | ], 7 | "shell": { 8 | "init_hook": [ 9 | "echo 'Welcome to devbox!' > /dev/null" 10 | ], 11 | "scripts": { 12 | "test": [ 13 | "echo \"Error: no test specified\" && exit 1" 14 | ] 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "docs-cruncher", 3 | "private": true, 4 | "type": "module", 5 | "version": "0.0.1", 6 | "scripts": { 7 | "dev": "astro dev", 8 | "serve": "astro dev", 9 | "build": "astro build", 10 | "preview": "astro preview", 11 | "astro": "astro" 12 | }, 13 | "dependencies": { 14 | "@astrojs/starlight": "^0.30.3", 15 | "astro": "^5.0.2", 16 | "sharp": "^0.32.6" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /docs/src/content/docs/qql-reference/functions/booleans/isNotNull.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: isNotNull 3 | description: Check if a value is not null. 4 | --- 5 | 6 | The `isNotNull` function returns true if the value is not null or undefined. 7 | 8 | ## Usage 9 | 10 | ``` 11 | isNotNull(field) 12 | ``` 13 | 14 | **Example:** 15 | ``` 16 | where isNotNull(user) 17 | ``` 18 | This matches records where the `user` field is present and not null. 19 | -------------------------------------------------------------------------------- /docs/src/content/docs/qql-reference/functions/numbers/ceil.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: ceil 3 | description: Round a number up to the nearest integer. 4 | --- 5 | 6 | The `ceil` function rounds the input number up to the nearest integer. 7 | 8 | ## Usage 9 | 10 | ``` 11 | ceil(field) 12 | ``` 13 | 14 | **Example:** 15 | ``` 16 | eval ceil_duration = ceil(duration) 17 | ``` 18 | This creates a new field `ceil_duration` with the value of `duration` rounded up. 19 | -------------------------------------------------------------------------------- /docs/src/content/docs/qql-reference/functions/numbers/round.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: round 3 | description: Round a number to the nearest integer. 4 | --- 5 | 6 | The `round` function rounds the input number to the nearest integer. 7 | 8 | ## Usage 9 | 10 | ``` 11 | round(field) 12 | ``` 13 | 14 | **Example:** 15 | ``` 16 | eval rounded_duration = round(duration) 17 | ``` 18 | This creates a new field `rounded_duration` with the rounded value of `duration`. 19 | -------------------------------------------------------------------------------- /docs/src/content/docs/qql-reference/functions/booleans/endsWith.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: endsWith 3 | description: Check if a string ends with a suffix. 4 | --- 5 | 6 | The `endsWith` function returns true if the first argument ends with the second argument. 7 | 8 | ## Usage 9 | 10 | ``` 11 | endsWith(field, "suffix") 12 | ``` 13 | 14 | **Example:** 15 | ``` 16 | where endsWith(message, ".log") 17 | ``` 18 | This matches records where the `message` field ends with ".log". 19 | -------------------------------------------------------------------------------- /docs/src/content/docs/qql-reference/functions/numbers/floor.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: floor 3 | description: Round a number down to the nearest integer. 4 | --- 5 | 6 | The `floor` function rounds the input number down to the nearest integer. 7 | 8 | ## Usage 9 | 10 | ``` 11 | floor(field) 12 | ``` 13 | 14 | **Example:** 15 | ``` 16 | eval floor_duration = floor(duration) 17 | ``` 18 | This creates a new field `floor_duration` with the value of `duration` rounded down. 19 | -------------------------------------------------------------------------------- /apps/cruncher/vite.main.config.mts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | 3 | import tsconfigPaths from "vite-tsconfig-paths"; 4 | 5 | // https://vitejs.dev/config 6 | export default defineConfig({ 7 | plugins: [ 8 | tsconfigPaths(), 9 | // { 10 | // name: "restart", 11 | // closeBundle() { 12 | // process.stdin.emit("data", "rs"); 13 | // }, 14 | // }, 15 | ], 16 | build: { 17 | sourcemap: true, 18 | }, 19 | }); 20 | -------------------------------------------------------------------------------- /docs/src/content/docs/qql-reference/functions/strings/length.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: length 3 | description: Get the length of a string or array. 4 | --- 5 | 6 | The `length` function returns the number of characters in a string (or elements in an array, if supported). 7 | 8 | ## Usage 9 | 10 | ``` 11 | length(field) 12 | ``` 13 | 14 | **Example:** 15 | ``` 16 | where length(message) > 100 17 | ``` 18 | This matches records where the `message` field is longer than 100 characters. 19 | -------------------------------------------------------------------------------- /tools/cruncher-tools/src/generators/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/schema", 3 | "$id": "AdapterGenerator", 4 | "title": "", 5 | "type": "object", 6 | "properties": { 7 | "name": { 8 | "type": "string", 9 | "description": "", 10 | "$default": { 11 | "$source": "argv", 12 | "index": 0 13 | }, 14 | "x-prompt": "What name would you like to use?" 15 | } 16 | }, 17 | "required": ["name"] 18 | } 19 | -------------------------------------------------------------------------------- /docs/src/content/docs/qql-reference/functions/booleans/startsWith.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: startsWith 3 | description: Check if a string starts with a prefix. 4 | --- 5 | 6 | The `startsWith` function returns true if the first argument starts with the second argument. 7 | 8 | ## Usage 9 | 10 | ``` 11 | startsWith(field, "prefix") 12 | ``` 13 | 14 | **Example:** 15 | ``` 16 | where startsWith(message, "Error:") 17 | ``` 18 | This matches records where the `message` field starts with "Error:". 19 | -------------------------------------------------------------------------------- /tsconfig.modules.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "compilerOptions": { 4 | "moduleResolution": "bundler", 5 | "target": "esnext", 6 | "module": "esnext", 7 | "esModuleInterop": true, 8 | "noImplicitAny": true, 9 | "sourceMap": true, 10 | "resolveJsonModule": true, 11 | "declaration": true, 12 | "declarationMap": true, 13 | "strict": true, 14 | "skipLibCheck": true 15 | } 16 | } -------------------------------------------------------------------------------- /docs/src/content/docs/qql-reference/functions/strings/trim.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: trim 3 | description: Remove whitespace from both ends of a string. 4 | --- 5 | 6 | The `trim` function removes whitespace from the beginning and end of the input string. 7 | 8 | ## Usage 9 | 10 | ``` 11 | trim(field) 12 | ``` 13 | 14 | **Example:** 15 | ``` 16 | eval clean_message = trim(message) 17 | ``` 18 | This creates a new field `clean_message` with leading and trailing whitespace removed from `message`. 19 | -------------------------------------------------------------------------------- /apps/cruncher/src/processes/frontend/features/searcher/data-displays/events/state.ts: -------------------------------------------------------------------------------- 1 | import { atom, useAtomValue } from "jotai"; 2 | 3 | export const openIndexesAtom = atom([]); 4 | 5 | export const useIsIndexOpen = () => { 6 | const openIndexes = useAtomValue(openIndexesAtom); 7 | return (index: number) => { 8 | return openIndexes.includes(index); 9 | }; 10 | }; 11 | 12 | export const rangeInViewAtom = atom<{ start: number; end: number }>({ 13 | start: 0, 14 | end: 0, 15 | }); 16 | -------------------------------------------------------------------------------- /docs/src/content/docs/qql-reference/functions/booleans/contains.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: contains 3 | description: Check if a string contains a substring. 4 | --- 5 | 6 | The `contains` function returns true if the first argument contains the second argument as a substring. 7 | 8 | ## Usage 9 | 10 | ``` 11 | contains(field, "substring") 12 | ``` 13 | 14 | **Example:** 15 | ``` 16 | where contains(message, "error") 17 | ``` 18 | This matches records where the `message` field contains the substring "error". 19 | -------------------------------------------------------------------------------- /packages/adapters/utils/src/formatters.ts: -------------------------------------------------------------------------------- 1 | import { format } from "date-fns"; 2 | import { utc } from "@date-fns/utc"; 3 | 4 | export const formatDataTime = (date: Date | number): string => { 5 | return format(new Date(date), "yyyy-MM-dd HH:mm:ss.SSS", { 6 | in: utc, 7 | }); 8 | }; 9 | 10 | export const formatDataTimeShort = (date: Date | number): string => { 11 | if (date === undefined) return ""; 12 | 13 | return format(new Date(date), "yyyy-MM-dd HH:mm:ss", { 14 | in: utc, 15 | }); 16 | }; 17 | -------------------------------------------------------------------------------- /apps/cruncher/src/processes/frontend/components/ui/editor/Editor.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from "vitest"; 2 | import { splitTextToChunks } from "./Highlighter"; 3 | 4 | test("split string to highlight chunks", () => { 5 | const result = splitTextToChunks("hello world", [ 6 | { 7 | type: "keyword", 8 | token: { 9 | startOffset: 0, 10 | endOffset: 4, 11 | }, 12 | }, 13 | ]); 14 | 15 | expect(result).toEqual([{ type: "keyword", value: "hello" }, " world"]); 16 | }); 17 | -------------------------------------------------------------------------------- /apps/cruncher/src/processes/frontend/components/ui/system.tsx: -------------------------------------------------------------------------------- 1 | import { createSystem, defaultConfig, defineConfig } from "@chakra-ui/react"; 2 | 3 | const varRoot = ":host"; 4 | 5 | const config = defineConfig({ 6 | cssVarsRoot: varRoot, 7 | conditions: { 8 | light: `, ${varRoot} &, .light &`, 9 | dark: `, ${varRoot} &, .dark &`, 10 | }, 11 | preflight: { scope: varRoot }, 12 | globalCss: { 13 | [varRoot]: defaultConfig.globalCss?.html ?? {}, 14 | }, 15 | }); 16 | 17 | export const system = createSystem(defaultConfig, config); 18 | -------------------------------------------------------------------------------- /tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "rootDir": ".", 5 | "sourceMap": true, 6 | "declaration": false, 7 | "moduleResolution": "node", 8 | "emitDecoratorMetadata": true, 9 | "experimentalDecorators": true, 10 | "importHelpers": true, 11 | "target": "es2015", 12 | "module": "esnext", 13 | "lib": ["es2020", "dom"], 14 | "skipLibCheck": true, 15 | "skipDefaultLibCheck": true, 16 | "baseUrl": ".", 17 | "paths": {} 18 | }, 19 | "exclude": ["node_modules", "tmp"] 20 | } 21 | -------------------------------------------------------------------------------- /packages/adapters/mocked-data/src/index.ts: -------------------------------------------------------------------------------- 1 | import { Adapter, newPluginRef, QueryProvider } from "@cruncher/adapter-utils"; 2 | import { MockController } from "./controller"; 3 | import { z } from "zod/v4"; 4 | 5 | const paramsSchema = z.object({}); 6 | 7 | const adapter: Adapter = { 8 | ref: newPluginRef("mocked_data"), 9 | name: "Mocked Data Adapter", 10 | description: "Adapter for mocked data", 11 | version: "0.1.0", 12 | params: paramsSchema, 13 | factory: (): QueryProvider => { 14 | return MockController; 15 | }, 16 | }; 17 | 18 | export default adapter; 19 | -------------------------------------------------------------------------------- /docs/src/content/docs/qql-reference/01-qql.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: QQL (Quick Query Language) 3 | description: The main query language inside `Cruncher` - it's main goal is to have a quick - easy to learn language - that's powerful enought to allow power users access to enhanced capabilities over that data. 4 | --- 5 | 6 | 7 | QQL is the main query language inside `Cruncher` - it's main goal is to have a quick - easy to learn language - that's powerful enought to allow power users access to enhanced capabilities over that data. 8 | 9 | The query language was heavily inspired from Splunk (SPL2) and Kusto (KQL). 10 | 11 | -------------------------------------------------------------------------------- /docs/src/components/ParamItem.astro: -------------------------------------------------------------------------------- 1 | --- 2 | const { label, type, description } = Astro.props; 3 | --- 4 | 5 | 22 | 23 |
24 | {label} 25 | {type} 26 | - 27 | 28 | 29 | 30 |
31 | -------------------------------------------------------------------------------- /apps/cruncher/src/processes/frontend/core/utils/highlight.tsx: -------------------------------------------------------------------------------- 1 | // Utility to highlight search term in a string 2 | export function highlightText(text: string, searchTerm?: string) { 3 | if (!searchTerm) return text; 4 | const regex = new RegExp( 5 | `(${searchTerm.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")})`, 6 | "gi", 7 | ); 8 | const parts = text.split(regex); 9 | return parts.map((part, i) => 10 | regex.test(part) ? ( 11 | 15 | {part} 16 | 17 | ) : ( 18 | part 19 | ), 20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /apps/cruncher/src/processes/frontend/core/utils/shadowUtils.ts: -------------------------------------------------------------------------------- 1 | export const getCruncherRoot = () => { 2 | const root = document.getElementById("cruncher-root"); 3 | if (!root) { 4 | return undefined; 5 | } 6 | 7 | const shadowRoot = root?.shadowRoot; 8 | if (!shadowRoot) { 9 | return undefined; 10 | } 11 | 12 | const result = shadowRoot.getElementById("cruncher-inner-root"); 13 | return result; 14 | }; 15 | 16 | export const getPopperRoot = () => { 17 | const root = getCruncherRoot(); 18 | if (!root) { 19 | return undefined; 20 | } 21 | 22 | const result = root.querySelector("#cruncher-popovers"); 23 | return result; 24 | }; 25 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "type": "node", 5 | "request": "launch", 6 | "name": "Electron Main", 7 | "runtimeExecutable": "${workspaceFolder}/node_modules/@electron-forge/cli/script/vscode.sh", 8 | "windows": { 9 | "runtimeExecutable": "${workspaceFolder}/node_modules/@electron-forge/cli/script/vscode.cmd" 10 | }, 11 | // runtimeArgs will be passed directly to your Electron application 12 | "runtimeArgs": [ 13 | // "foo", 14 | // "bar" 15 | ], 16 | "cwd": "${workspaceFolder}/apps/cruncher/", 17 | "console": "integratedTerminal" 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /apps/cruncher/src/processes/frontend/components/ui/close-button.tsx: -------------------------------------------------------------------------------- 1 | import type { ButtonProps } from "@chakra-ui/react"; 2 | import { IconButton as ChakraIconButton } from "@chakra-ui/react"; 3 | import * as React from "react"; 4 | import { LuX } from "react-icons/lu"; 5 | 6 | export type CloseButtonProps = ButtonProps; 7 | 8 | export const CloseButton = React.forwardRef< 9 | HTMLButtonElement, 10 | CloseButtonProps 11 | >(function CloseButton(props, ref) { 12 | return ( 13 | // @ts-expect-error - lib component 14 | 15 | {props.children ?? } 16 | 17 | ); 18 | }); 19 | -------------------------------------------------------------------------------- /docs/src/content/docs/qql-reference/functions/booleans/match.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: match 3 | description: Check if a string matches a regular expression. 4 | --- 5 | import { Aside } from '@astrojs/starlight/components'; 6 | 7 | The `match` function returns true if the first argument matches the given regular expression. 8 | 9 | ## Usage 10 | 11 | ``` 12 | match(field, /regex/) 13 | ``` 14 | 15 | **Example:** 16 | ``` 17 | where match(message, `^Error:.*`) 18 | ``` 19 | This matches records where the `message` field matches the regex pattern `^Error:.*`. 20 | 21 | -------------------------------------------------------------------------------- /apps/cruncher/src/processes/main/preload.ts: -------------------------------------------------------------------------------- 1 | // See the Electron documentation for details on how to use preload scripts: 2 | // https://www.electronjs.org/docs/latest/tutorial/process-model#preload-scripts 3 | 4 | // Preload (Isolated World) 5 | import { contextBridge, ipcRenderer } from "electron"; 6 | 7 | export const electronAPI = { 8 | getPort: async () => { 9 | return (await ipcRenderer.invoke("getPort")) as Promise; 10 | }, 11 | getVersion: async () => { 12 | return (await ipcRenderer.invoke("getVersion")) as Promise<{ 13 | tag: string; 14 | isDev: boolean; 15 | }>; 16 | }, 17 | }; 18 | 19 | contextBridge.exposeInMainWorld("electronAPI", electronAPI); 20 | -------------------------------------------------------------------------------- /docs/public/favicon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/adapters/loki/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "types": [ 6 | "vitest/globals", 7 | "vitest/importMeta", 8 | "vite/client", 9 | "node", 10 | "vitest" 11 | ] 12 | }, 13 | "include": [ 14 | "vite.config.ts", 15 | "vite.config.mts", 16 | "vitest.config.ts", 17 | "vitest.config.mts", 18 | "src/**/*.test.ts", 19 | "src/**/*.spec.ts", 20 | "src/**/*.test.tsx", 21 | "src/**/*.spec.tsx", 22 | "src/**/*.test.js", 23 | "src/**/*.spec.js", 24 | "src/**/*.test.jsx", 25 | "src/**/*.spec.jsx", 26 | "src/**/*.d.ts" 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /packages/adapters/mocked-data/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "types": [ 6 | "vitest/globals", 7 | "vitest/importMeta", 8 | "vite/client", 9 | "node", 10 | "vitest" 11 | ] 12 | }, 13 | "include": [ 14 | "vite.config.ts", 15 | "vite.config.mts", 16 | "vitest.config.ts", 17 | "vitest.config.mts", 18 | "src/**/*.test.ts", 19 | "src/**/*.spec.ts", 20 | "src/**/*.test.tsx", 21 | "src/**/*.spec.tsx", 22 | "src/**/*.test.js", 23 | "src/**/*.spec.js", 24 | "src/**/*.test.jsx", 25 | "src/**/*.spec.jsx", 26 | "src/**/*.d.ts" 27 | ] 28 | } -------------------------------------------------------------------------------- /apps/cruncher/src/processes/server/lib/procLock.ts: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | 3 | export function acquireLock(lockFilePath: string): boolean { 4 | if (fs.existsSync(lockFilePath)) { 5 | const pid = parseInt(fs.readFileSync(lockFilePath, "utf8")); 6 | try { 7 | process.kill(pid, 0); // check if process is alive 8 | console.log("Loki is being managed by process", pid); 9 | return false; 10 | } catch (e) { 11 | // Process doesn't exist 12 | console.log("Stale lock file found. Proceeding..."); 13 | } 14 | } 15 | 16 | fs.writeFileSync(lockFilePath, process.pid.toString()); 17 | process.on("exit", () => fs.unlinkSync(lockFilePath)); 18 | return true; 19 | } 20 | -------------------------------------------------------------------------------- /apps/cruncher/vite.renderer.config.mts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import react from "@vitejs/plugin-react"; 3 | import tsconfigPaths from "vite-tsconfig-paths"; 4 | import process from "process"; 5 | import { tanstackRouter } from "@tanstack/router-plugin/vite"; 6 | 7 | // is production 8 | const isProduction = process.env.NODE_ENV === "production"; 9 | 10 | // https://vitejs.dev/config 11 | export default defineConfig({ 12 | plugins: [ 13 | tanstackRouter({ 14 | target: "react", 15 | // autoCodeSplitting: true, 16 | }), 17 | tsconfigPaths(), 18 | react(), 19 | ], 20 | resolve: { 21 | extensions: [".mjs", ".js", ".ts", ".jsx", ".tsx"], 22 | }, 23 | }); 24 | -------------------------------------------------------------------------------- /tools/cruncher-tools/src/generators/files/tsconfig.spec.json.template: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "types": [ 6 | "vitest/globals", 7 | "vitest/importMeta", 8 | "vite/client", 9 | "node", 10 | "vitest" 11 | ] 12 | }, 13 | "include": [ 14 | "vite.config.ts", 15 | "vite.config.mts", 16 | "vitest.config.ts", 17 | "vitest.config.mts", 18 | "src/**/*.test.ts", 19 | "src/**/*.spec.ts", 20 | "src/**/*.test.tsx", 21 | "src/**/*.spec.tsx", 22 | "src/**/*.test.js", 23 | "src/**/*.spec.js", 24 | "src/**/*.test.jsx", 25 | "src/**/*.spec.jsx", 26 | "src/**/*.d.ts" 27 | ] 28 | } -------------------------------------------------------------------------------- /.verdaccio/config.yml: -------------------------------------------------------------------------------- 1 | # path to a directory with all packages 2 | storage: ../tmp/local-registry/storage 3 | 4 | # a list of other known repositories we can talk to 5 | uplinks: 6 | npmjs: 7 | url: https://registry.npmjs.org/ 8 | maxage: 60m 9 | 10 | packages: 11 | "**": 12 | access: public 13 | publish: $authenticated 14 | unpublish: $authenticated 15 | 16 | # if package is not available locally, proxy requests to npm registry 17 | proxy: npmjs 18 | 19 | "@cruncher/app": 20 | access: public 21 | publish: false 22 | unpublish: false 23 | 24 | # log settings 25 | log: 26 | type: stdout 27 | format: pretty 28 | level: warn 29 | 30 | publish: 31 | allow_offline: false 32 | -------------------------------------------------------------------------------- /docs/src/content/docs/adapters/examples/docker-patterns.cruncher.config.yaml: -------------------------------------------------------------------------------- 1 | profiles: 2 | default: 3 | connectors: [docker] 4 | 5 | connectors: 6 | - name: docker 7 | type: docker 8 | params: 9 | containerOverride: 10 | containerA: # lets say containerA has json logs 11 | messageFieldName: raw_message # this is the field that contains the message 12 | 13 | logPatterns: 14 | - name: custom_pattern 15 | pattern: "^(?P[^ ]+) (?P[^ ]+) (?P.*)$" 16 | applyToAll: true # apply this pattern to all containers 17 | exclude: 18 | - containerA # exclude containerA from this pattern 19 | messageFieldName: custom_message 20 | -------------------------------------------------------------------------------- /tools/cruncher-tools/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "declaration": true, 6 | "types": [ 7 | "node" 8 | ] 9 | }, 10 | "include": [ 11 | "src/**/*.ts", 12 | "src/generators/files/src/controller.test.ts.template" 13 | ], 14 | "exclude": [ 15 | "src/generators/files/**", 16 | "vite.config.ts", 17 | "vite.config.mts", 18 | "vitest.config.ts", 19 | "vitest.config.mts", 20 | "src/**/*.test.ts", 21 | "src/**/*.spec.ts", 22 | "src/**/*.test.tsx", 23 | "src/**/*.spec.tsx", 24 | "src/**/*.test.js", 25 | "src/**/*.spec.js", 26 | "src/**/*.test.jsx", 27 | "src/**/*.spec.jsx" 28 | ] 29 | } -------------------------------------------------------------------------------- /apps/cruncher/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@cruncher/app", 3 | "$schema": "../../node_modules/nx/schemas/project-schema.json", 4 | "projectType": "application", 5 | "sourceRoot": "apps/cruncher/src", 6 | "tags": [], 7 | "// targets": "to see all targets run: nx show project cruncher --web", 8 | "targets": { 9 | "package": { 10 | "outputs": ["{projectRoot}/out/", "{projectRoot}/dist/"], 11 | "cache": true 12 | }, 13 | "make": { 14 | "outputs": ["{projectRoot}/out/", "{projectRoot}/dist/"], 15 | "cache": true 16 | }, 17 | "watch-routes": { 18 | "continuous": true 19 | }, 20 | "serve": { 21 | "continuous": true, 22 | "dependsOn": ["^build", "watch-routes"] 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/qql/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.2.4 (2025-06-21) 2 | 3 | ### 🩹 Fixes 4 | 5 | - created new standard for adapters - and new codegen for it ([#15](https://github.com/IamShobe/cruncher/pull/15)) 6 | 7 | ## 0.2.3 (2025-06-21) 8 | 9 | ### 🩹 Fixes 10 | 11 | - Update ci stuff ([#13](https://github.com/IamShobe/cruncher/pull/13)) 12 | 13 | ## 0.2.2 (2025-06-21) 14 | 15 | ### 🩹 Fixes 16 | 17 | - test bump ([5d62e3c](https://github.com/IamShobe/cruncher/commit/5d62e3c)) 18 | 19 | ## 0.2.1 (2025-06-21) 20 | 21 | ### 🩹 Fixes 22 | 23 | - new release flow ([e9ace0d](https://github.com/IamShobe/cruncher/commit/e9ace0d)) 24 | 25 | ## 0.2.0 (2025-06-21) 26 | 27 | ### 🚀 Features 28 | 29 | - nx initial commit ([883376b](https://github.com/IamShobe/cruncher/commit/883376b)) -------------------------------------------------------------------------------- /packages/utils/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.2.4 (2025-06-21) 2 | 3 | ### 🩹 Fixes 4 | 5 | - created new standard for adapters - and new codegen for it ([#15](https://github.com/IamShobe/cruncher/pull/15)) 6 | 7 | ## 0.2.3 (2025-06-21) 8 | 9 | ### 🩹 Fixes 10 | 11 | - Update ci stuff ([#13](https://github.com/IamShobe/cruncher/pull/13)) 12 | 13 | ## 0.2.2 (2025-06-21) 14 | 15 | ### 🩹 Fixes 16 | 17 | - test bump ([5d62e3c](https://github.com/IamShobe/cruncher/commit/5d62e3c)) 18 | 19 | ## 0.2.1 (2025-06-21) 20 | 21 | ### 🩹 Fixes 22 | 23 | - new release flow ([e9ace0d](https://github.com/IamShobe/cruncher/commit/e9ace0d)) 24 | 25 | ## 0.2.0 (2025-06-21) 26 | 27 | ### 🚀 Features 28 | 29 | - nx initial commit ([883376b](https://github.com/IamShobe/cruncher/commit/883376b)) -------------------------------------------------------------------------------- /tools/cruncher-tools/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "types": [ 6 | "vitest/globals", 7 | "vitest/importMeta", 8 | "vite/client", 9 | "node", 10 | "vitest" 11 | ] 12 | }, 13 | "include": [ 14 | "vite.config.ts", 15 | "vite.config.mts", 16 | "vitest.config.ts", 17 | "vitest.config.mts", 18 | "src/**/*.test.ts", 19 | "src/**/*.spec.ts", 20 | "src/**/*.test.tsx", 21 | "src/**/*.spec.tsx", 22 | "src/**/*.test.js", 23 | "src/**/*.spec.js", 24 | "src/**/*.test.jsx", 25 | "src/**/*.spec.jsx", 26 | "src/**/*.d.ts", 27 | "src/generators/files/src/controller.test.ts.template" 28 | ] 29 | } -------------------------------------------------------------------------------- /tools/cruncher-tools/src/generators/files/src/controller.ts.template: -------------------------------------------------------------------------------- 1 | import { QueryOptions, QueryProvider } from "@cruncher/adapter-utils"; 2 | import { ControllerIndexParam, Search } from "@cruncher/qql/grammar"; 3 | import type {<%= titleCase(name) %>Params} from "."; 4 | 5 | 6 | export class <%= titleCaseWithController(name) %> implements QueryProvider { 7 | constructor(params: <%= titleCase(name) %>Params) { 8 | 9 | } 10 | 11 | query( 12 | contollerParams: ControllerIndexParam[], 13 | searchTerm: Search, 14 | options: QueryOptions, 15 | ): Promise { 16 | throw new Error("TODO: implement me") 17 | } 18 | 19 | getControllerParams(): Promise> { 20 | throw new Error("TODO: implement me") 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /apps/cruncher/src/processes/server/lib/displayTypes.ts: -------------------------------------------------------------------------------- 1 | import { ProcessedData } from "@cruncher/adapter-utils/logTypes"; 2 | 3 | export type Events = { 4 | type: "events"; 5 | data: ProcessedData[]; 6 | }; 7 | 8 | export type Table = { 9 | type: "table"; 10 | columns: string[]; 11 | dataPoints: ProcessedData[]; 12 | }; 13 | 14 | export type Bucket = { 15 | name: string; 16 | color: string; 17 | }; 18 | 19 | export type View = { 20 | type: "view"; 21 | data: ProcessedData[]; 22 | XAxis: string; 23 | YAxis: Bucket[]; 24 | allBuckets: (string | number)[]; 25 | }; 26 | 27 | export type DataFormatType = Events | Table; 28 | 29 | export type DisplayResults = { 30 | events: Events; 31 | table: Table | undefined; 32 | view: View | undefined; 33 | }; 34 | -------------------------------------------------------------------------------- /apps/cruncher/src/processes/server/lib/pipelineEngine/stats.ts: -------------------------------------------------------------------------------- 1 | import { DisplayResults } from "~lib/displayTypes"; 2 | import { AggregationFunction } from "@cruncher/qql/grammar"; 3 | import { aggregateData } from "./aggregateData"; 4 | 5 | export const processStats = ( 6 | data: DisplayResults, 7 | functions: AggregationFunction[], 8 | groupBy: string[] | undefined, 9 | ): DisplayResults => { 10 | const { events, table } = data; 11 | const dataPoints = table ? table.dataPoints : events.data; 12 | 13 | const result = aggregateData(dataPoints, functions, groupBy); 14 | 15 | return { 16 | events, 17 | table: { 18 | type: "table", 19 | columns: result.columns, 20 | dataPoints: result.data, 21 | }, 22 | view: undefined, 23 | }; 24 | }; 25 | -------------------------------------------------------------------------------- /tools/cruncher-tools/src/generators/adapter-generator.spec.ts: -------------------------------------------------------------------------------- 1 | import { createTreeWithEmptyWorkspace } from "@nx/devkit/testing"; 2 | import { Tree, readProjectConfiguration } from "@nx/devkit"; 3 | 4 | import { adapterGeneratorGenerator } from "./adapter-generator"; 5 | import { AdapterGeneratorGeneratorSchema } from "./schema"; 6 | 7 | describe("adapter-generator generator", () => { 8 | let tree: Tree; 9 | const options: AdapterGeneratorGeneratorSchema = { name: "test" }; 10 | 11 | beforeEach(() => { 12 | tree = createTreeWithEmptyWorkspace(); 13 | }); 14 | 15 | it("should run successfully", async () => { 16 | await adapterGeneratorGenerator(tree, options); 17 | const config = readProjectConfiguration(tree, "test"); 18 | expect(config).toBeDefined(); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /apps/cruncher/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "module": "nodenext", 5 | "skipLibCheck": true, 6 | "allowJs": true, 7 | "esModuleInterop": true, 8 | "sourceMap": true, 9 | "baseUrl": ".", 10 | "outDir": "dist", 11 | "moduleResolution": "nodenext", 12 | "isolatedModules": true, 13 | "moduleDetection": "force", 14 | "resolveJsonModule": true, 15 | "paths": { 16 | "~lib/*": [ 17 | "./src/processes/server/lib/*" 18 | ], 19 | }, 20 | "strict": true 21 | }, 22 | "include": [ 23 | "./src/**/*", 24 | "../server/src/lib", 25 | "../server/src/config", 26 | "../../packages/adapters/coralogix/src" 27 | ], 28 | "exclude": [ 29 | "./src/processes/frontend/**/*" 30 | ] 31 | } -------------------------------------------------------------------------------- /tools/cruncher-tools/src/generators/files/src/index.ts.template: -------------------------------------------------------------------------------- 1 | import { Adapter, newPluginRef, QueryProvider } from "@cruncher/adapter-utils"; 2 | import { <%= titleCaseWithController(name) %> } from "./controller"; 3 | import { z } from "zod/v4"; 4 | 5 | const paramsSchema = z.object({}); 6 | 7 | export type <%= titleCase(name) %>Params = z.infer; 8 | 9 | const adapter: Adapter = { 10 | ref: newPluginRef("<%= name %>"), 11 | name: "", // TODO: fill me in 12 | description: "", // TODO: fill me in 13 | version: "0.1.0", 14 | params: paramsSchema, 15 | factory: (ctx, {params}): QueryProvider => { 16 | const parsedParams = paramsSchema.parse(params); 17 | 18 | return new <%= titleCaseWithController(name) %>(parsedParams); 19 | }, 20 | }; 21 | 22 | export default adapter; -------------------------------------------------------------------------------- /apps/cruncher/src/processes/server/plugins_engine/trpc.ts: -------------------------------------------------------------------------------- 1 | import { initTRPC } from "@trpc/server"; 2 | import { Engine } from "../engineV2/engine"; 3 | import { EventEmitter } from "stream"; 4 | 5 | /** 6 | * Initialization of tRPC backend 7 | * Should be done only once per backend! 8 | */ 9 | export const createContext = async ( 10 | engineV2: Engine, 11 | eventEmitter: EventEmitter, 12 | ) => { 13 | return { 14 | engine: engineV2, 15 | eventEmitter: eventEmitter, 16 | }; 17 | }; 18 | 19 | export type Context = Awaited>; 20 | const t = initTRPC.context().create(); 21 | 22 | /** 23 | * Export reusable router and procedure helpers 24 | * that can be used throughout the router 25 | */ 26 | export const router = t.router; 27 | export const publicProcedure = t.procedure; 28 | -------------------------------------------------------------------------------- /packages/adapters/grafana-loki-browser/src/types.ts: -------------------------------------------------------------------------------- 1 | export type TimestampValues = number[]; 2 | export type ObjectValues = Record[]; 3 | export type FormatedValues = string[]; 4 | export type NanoSeconds = string[]; // nano seconds in string format 5 | export type IndexInfo = object[]; // index info 6 | export type UniqueId = string[]; // unique id 7 | 8 | export type Frame = { 9 | data: { 10 | values: [ 11 | ObjectValues, 12 | TimestampValues, 13 | FormatedValues, 14 | NanoSeconds, 15 | IndexInfo, 16 | UniqueId, 17 | ]; 18 | }; 19 | }; 20 | 21 | export type Result = { 22 | status: number; 23 | frames: Frame[]; 24 | }; 25 | 26 | export type Results = { 27 | [key: string]: Result; 28 | }; 29 | 30 | export type QueryResponse = { 31 | results: Results; 32 | }; 33 | -------------------------------------------------------------------------------- /docs/src/assets/landing.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --purple-hsl: 40, 60%, 60%; 3 | --overlay-blurple: hsla(var(--purple-hsl), 0.2); 4 | } 5 | 6 | :root[data-theme='light'] { 7 | --purple-hsl: 40, 60%, 60%; 8 | } 9 | 10 | [data-has-hero] .page { 11 | background: 12 | linear-gradient(215deg, var(--overlay-blurple), transparent 40%), 13 | radial-gradient(var(--overlay-blurple), transparent 40%) no-repeat -60vw -40vh / 105vw 200vh, 14 | radial-gradient(var(--overlay-blurple), transparent 65%) no-repeat 50% calc(100% + 20rem) / 15 | 60rem 30rem; 16 | } 17 | 18 | [data-has-hero] header { 19 | border-bottom: 1px solid transparent; 20 | background-color: transparent; 21 | -webkit-backdrop-filter: blur(16px); 22 | backdrop-filter: blur(16px); 23 | } 24 | 25 | [data-has-hero] .hero > img { 26 | filter: drop-shadow(0 0 3rem var(--overlay-blurple)); 27 | } -------------------------------------------------------------------------------- /apps/cruncher/src/processes/frontend/components/presets/Tooltip.tsx: -------------------------------------------------------------------------------- 1 | import { Shortcut } from "~components/ui/shortcut"; 2 | import { 3 | TooltipProps as OTooltipProps, 4 | Tooltip as OTooltip, 5 | } from "~components/ui/tooltip"; 6 | 7 | export type TooltipProps = Omit & { 8 | shortcut?: string[]; 9 | text?: string; 10 | position?: "top" | "bottom" | "left" | "right"; 11 | }; 12 | 13 | export const Tooltip: React.FC = ({ 14 | text, 15 | shortcut, 16 | position, 17 | ...props 18 | }) => { 19 | return ( 20 | 24 | {text} {shortcut && } 25 | 26 | } 27 | positioning={{ placement: position }} 28 | {...props} 29 | /> 30 | ); 31 | }; 32 | -------------------------------------------------------------------------------- /tools/cruncher-tools/eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import baseConfig from "../../eslint.config.mjs"; 2 | 3 | export default [ 4 | ...baseConfig, 5 | { 6 | files: ["**/*.json"], 7 | rules: { 8 | "@nx/dependency-checks": [ 9 | "error", 10 | { 11 | ignoredFiles: [ 12 | "{projectRoot}/eslint.config.{js,cjs,mjs,ts,cts,mts}", 13 | "{projectRoot}/vite.config.{js,ts,mjs,mts}", 14 | ], 15 | }, 16 | ], 17 | }, 18 | languageOptions: { 19 | parser: await import("jsonc-eslint-parser"), 20 | }, 21 | }, 22 | { 23 | files: ["**/package.json", "**/package.json", "**/generators.json"], 24 | rules: { 25 | "@nx/nx-plugin-checks": "error", 26 | }, 27 | languageOptions: { 28 | parser: await import("jsonc-eslint-parser"), 29 | }, 30 | }, 31 | ]; 32 | -------------------------------------------------------------------------------- /docs/src/content/docs/qql-reference/commands/sort.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: sort 3 | description: Sorts records by one or more fields in ascending or descending order. 4 | --- 5 | 6 | import { Badge } from '@astrojs/starlight/components'; 7 | 8 | The `sort` command orders records by one or more fields, in ascending or descending order. 9 | 10 | ## Syntax 11 | 12 | ``` 13 | sort FIELD_NAME [asc|desc], ... 14 | ``` 15 | 16 | - `FIELD_NAME`: The fields to sort by. 17 | - `asc`: Sort in ascending order (default). 18 | - `desc`: Sort in descending order. 19 | 20 | ## Usage 21 | 22 | - Use `sort` to control the order of results in the output table or view. 23 | - Multiple fields can be specified for multi-level sorting. 24 | 25 | ## Example 26 | 27 | ``` 28 | sort -timestamp, user 29 | ``` 30 | 31 | This sorts results by `timestamp` descending, then by `user` ascending`. 32 | -------------------------------------------------------------------------------- /packages/adapters/coralogix/src/index.ts: -------------------------------------------------------------------------------- 1 | import { Adapter, newPluginRef, QueryProvider } from "@cruncher/adapter-utils"; 2 | import { CoralogixController } from "./controller"; 3 | import { z } from "zod/v4"; 4 | 5 | const paramsSchema = z.object({ 6 | api_url: z.url().default("https://api.coralogix.us/api/v1"), 7 | dashboard_url: z.url(), 8 | region: z.string().default("usprod1"), 9 | }); 10 | 11 | export type CoralogixParams = z.infer; 12 | 13 | const adapter: Adapter = { 14 | ref: newPluginRef("coralogix"), 15 | name: "Coralogix Adapter", 16 | description: "Adapter for Coralogix", 17 | version: "0.1.0", 18 | params: paramsSchema, 19 | factory: (ctx, { params }): QueryProvider => { 20 | const parsedParams = paramsSchema.parse(params); 21 | return new CoralogixController(ctx, parsedParams); 22 | }, 23 | }; 24 | 25 | export default adapter; 26 | -------------------------------------------------------------------------------- /tools/cruncher-tools/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import { nxViteTsPaths } from "@nx/vite/plugins/nx-tsconfig-paths.plugin"; 3 | import { nxCopyAssetsPlugin } from "@nx/vite/plugins/nx-copy-assets.plugin"; 4 | 5 | export default defineConfig(() => ({ 6 | root: __dirname, 7 | cacheDir: "../../node_modules/.vite/tools/cruncher-tools", 8 | plugins: [nxViteTsPaths(), nxCopyAssetsPlugin(["*.md"])], 9 | // Uncomment this if you are using workers. 10 | // worker: { 11 | // plugins: [ nxViteTsPaths() ], 12 | // }, 13 | test: { 14 | watch: false, 15 | globals: true, 16 | environment: "jsdom", 17 | include: ["src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"], 18 | reporters: ["default"], 19 | coverage: { 20 | reportsDirectory: "../../coverage/tools/cruncher-tools", 21 | provider: "v8" as const, 22 | }, 23 | }, 24 | })); 25 | -------------------------------------------------------------------------------- /apps/cruncher/src/processes/frontend/components/presets/IconButton.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | IconButtonProps as OIconButtonProps, 3 | IconButton as OIconButton, 4 | } from "@chakra-ui/react"; 5 | import { Tooltip, TooltipProps } from "./Tooltip"; 6 | 7 | export type IconButtonProps = OIconButtonProps & 8 | import("react").RefAttributes & { 9 | tooltip?: TooltipProps["text"]; 10 | tooltipPosition?: TooltipProps["position"]; 11 | tooltipShortcut?: TooltipProps["shortcut"]; 12 | }; 13 | 14 | export const MiniIconButton: React.FC = (props) => { 15 | return ( 16 | 22 | 23 | 24 | ); 25 | }; 26 | -------------------------------------------------------------------------------- /apps/cruncher/vite.server.config.mts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import { dirname, resolve } from "node:path"; 3 | import { fileURLToPath } from "node:url"; 4 | 5 | const __dirname = dirname(fileURLToPath(import.meta.url)); 6 | 7 | import tsconfigPaths from "vite-tsconfig-paths"; 8 | 9 | // https://vitejs.dev/config 10 | export default defineConfig({ 11 | plugins: [ 12 | tsconfigPaths(), 13 | // { 14 | // name: "restart", 15 | // closeBundle() { 16 | // process.stdin.emit("data", "rs"); 17 | // }, 18 | // }, 19 | ], 20 | build: { 21 | // target: "node22", 22 | lib: { 23 | entry: resolve(__dirname, "src/processes/server/main.ts"), 24 | name: "main-server", 25 | formats: ["cjs"], 26 | fileName: () => "server.js", 27 | }, 28 | sourcemap: true, 29 | rollupOptions: { 30 | external: ["node:process", "node:events", "child_process"], 31 | }, 32 | }, 33 | }); 34 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Publish Electron App 2 | on: 3 | workflow_dispatch: 4 | push: 5 | tags: 6 | - "@cruncher/app@*" 7 | 8 | concurrency: 9 | group: ${{ github.workflow }}-${{ github.ref }} 10 | cancel-in-progress: true 11 | 12 | permissions: 13 | contents: write 14 | 15 | jobs: 16 | build-and-publish: 17 | runs-on: ${{ matrix.os }} 18 | strategy: 19 | matrix: 20 | os: [ubuntu-latest, macos-latest] # Linux and macOS 21 | 22 | steps: 23 | - name: Checkout code 24 | uses: actions/checkout@v4 25 | 26 | - uses: pnpm/action-setup@v4 27 | 28 | - name: Install dependencies 29 | run: pnpm install --frozen-lockfile 30 | 31 | - name: Build and publish Electron app 32 | run: pnpm run publish 33 | env: 34 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 35 | # Add other environment variables as needed for your publisher (e.g., S3, DigitalOcean, etc.) 36 | -------------------------------------------------------------------------------- /apps/cruncher/src/processes/frontend/routes/settings/general.tsx: -------------------------------------------------------------------------------- 1 | import { Field, Heading, Input, Stack } from "@chakra-ui/react"; 2 | import { createFileRoute } from "@tanstack/react-router"; 3 | import { useGeneralSettings } from "~core/store/appStore"; 4 | export const Route = createFileRoute("/settings/general")({ 5 | component: GeneralSettings, 6 | }); 7 | 8 | function GeneralSettings() { 9 | const generalSettings = useGeneralSettings(); 10 | return ( 11 | 12 | General Settings 13 | 14 | 15 | 16 | Config File Path 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /apps/cruncher/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "lib": [ 5 | "ES2022", 6 | "DOM", 7 | "DOM.Iterable" 8 | ], 9 | "module": "ESNext", 10 | "skipLibCheck": true, 11 | "paths": { 12 | "~lib/*": [ 13 | "./src/processes/server/lib/*" 14 | ], 15 | "~*": [ 16 | "./src/processes/frontend/*" 17 | ], 18 | }, 19 | "baseUrl": ".", 20 | /* Bundler mode */ 21 | "moduleResolution": "Bundler", 22 | "noEmit": true, 23 | "jsx": "react-jsx", 24 | "jsxImportSource": "@emotion/react", 25 | "typeRoots": [ 26 | "./dist/index.d.ts" 27 | ], 28 | /* Linting */ 29 | "strict": true, 30 | "noUnusedLocals": true, 31 | "noUnusedParameters": true, 32 | "noFallthroughCasesInSwitch": true 33 | }, 34 | "include": [ 35 | "./src/processes/frontend/**/*", 36 | "./src/processes/server/plugins_engine/router_messages.d.ts", 37 | ], 38 | } -------------------------------------------------------------------------------- /packages/utils/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@cruncher/utils", 3 | "version": "0.2.4", 4 | "description": "Utility functions for Cruncher.", 5 | "publishConfig": { 6 | "access": "public" 7 | }, 8 | "main": "dist/index.js", 9 | "types": "dist/index.d.ts", 10 | "exports": { 11 | ".": { 12 | "default": "./dist/index.js", 13 | "types": "./dist/index.d.ts" 14 | } 15 | }, 16 | "files": [ 17 | "dist" 18 | ], 19 | "scripts": { 20 | "build": "tsc --project tsconfig.json", 21 | "format": "prettier --write src/", 22 | "format:check": "prettier --check src/", 23 | "prepublishOnly": "npm run build" 24 | }, 25 | "keywords": [ 26 | "cruncher", 27 | "qql", 28 | "query-language", 29 | "coralogix", 30 | "adapter" 31 | ], 32 | "author": { 33 | "name": "Elran Shefer" 34 | }, 35 | "license": "GPL-3.0-only", 36 | "dependencies": {}, 37 | "devDependencies": { 38 | "typescript": "^5.8.3" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /docs/src/content/docs/getting-started/02-installation.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Installation 3 | description: How to install Cruncher on your system. 4 | --- 5 | 6 | import { Tabs, TabItem } from '@astrojs/starlight/components'; 7 | import { Steps } from '@astrojs/starlight/components'; 8 | 9 | 10 | 11 | 12 | ### Download and Install 13 | 14 | 15 | 1. Go to the [Cruncher releases page](https://github.com/IamShobe/cruncher/releases/latest). 16 | 2. Download the latest `.zip` file for macOS. 17 | 3. Extract the zip file. 18 | 4. Move the `cruncher.app` to your `Applications` folder. 19 | 5. Double-click to run Cruncher. 20 | 21 | 22 | #### If you see a corruption warning: 23 | Open your terminal and run: 24 | ```zsh 25 | xattr -cr /Applications/cruncher.app 26 | ``` 27 | This will clear the quarantine attribute and allow you to open the app. 28 | It's required because Cruncher is not codesigned yet. 29 | 30 | 31 | -------------------------------------------------------------------------------- /docs/src/content/docs/qql-reference/commands/regex.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: regex 3 | description: Extract values from a field using a regular expression and create new fields from the captured groups. 4 | --- 5 | 6 | import { Badge } from '@astrojs/starlight/components'; 7 | 8 | The `regex` command extracts values from a field using a regular expression and creates new fields from the captured groups. 9 | 10 | ## Syntax 11 | 12 | ``` 13 | regex [field=""] `` 14 | ``` 15 | 16 | - `FIELD_NAME`: The field to apply the regex to. If not specified, defaults to `_raw`. 17 | - `PATTERN`: The regular expression pattern, using named capture groups. 18 | 19 | ## Usage 20 | 21 | - Use `regex` to parse and extract structured data from text fields. 22 | - Named capture groups become new fields in the output. 23 | 24 | ## Example 25 | 26 | ``` 27 | regex field="message" `(?\\w+) logged in from (?\\d+\\.\\d+\\.\\d+\\.\\d+)` 28 | ``` 29 | 30 | This extracts `user` and `ip` from the `message` field. -------------------------------------------------------------------------------- /packages/adapters/loki/src/index.ts: -------------------------------------------------------------------------------- 1 | import { Adapter, newPluginRef, QueryProvider } from "@cruncher/adapter-utils"; 2 | import { LokiController } from "./controller"; 3 | import { z } from "zod/v4"; 4 | 5 | const paramsSchema = z.object({ 6 | url: z.url(), 7 | filter: z 8 | .array( 9 | z.object({ 10 | key: z.string(), 11 | value: z.string(), 12 | operator: z.enum(["=", "=~", "!=", "!~"]), 13 | }), 14 | ) 15 | .default([]), 16 | querySuffix: z.array(z.string()).default([]), 17 | }); 18 | 19 | export type LokiParams = z.infer; 20 | 21 | const adapter: Adapter = { 22 | ref: newPluginRef("loki"), 23 | name: "Loki", 24 | description: "Loki log aggregation adapter", 25 | version: "0.1.0", 26 | params: paramsSchema, 27 | factory: (ctx, { params }): QueryProvider => { 28 | const parsedParams = paramsSchema.parse(params); 29 | 30 | return new LokiController(parsedParams); 31 | }, 32 | }; 33 | 34 | export default adapter; 35 | -------------------------------------------------------------------------------- /apps/cruncher/src/processes/frontend/components/ui/radio.tsx: -------------------------------------------------------------------------------- 1 | import { RadioGroup as ChakraRadioGroup } from "@chakra-ui/react"; 2 | import * as React from "react"; 3 | 4 | export interface RadioProps extends ChakraRadioGroup.ItemProps { 5 | rootRef?: React.Ref; 6 | inputProps?: React.InputHTMLAttributes; 7 | } 8 | 9 | export const Radio = React.forwardRef( 10 | function Radio(props, ref) { 11 | const { children, inputProps, rootRef, ...rest } = props; 12 | return ( 13 | // @ts-expect-error - lib component 14 | 15 | 16 | 17 | {children && ( 18 | {children} 19 | )} 20 | 21 | ); 22 | }, 23 | ); 24 | 25 | export const RadioGroup = ChakraRadioGroup.Root; 26 | -------------------------------------------------------------------------------- /apps/cruncher/src/processes/frontend/main.tsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import { 4 | RouterProvider, 5 | createMemoryHistory, 6 | createRouter, 7 | } from "@tanstack/react-router"; 8 | 9 | // Import the generated route tree 10 | import { routeTree } from "./routeTree.gen"; 11 | 12 | const memoryHistory = createMemoryHistory({ 13 | initialEntries: ["/"], // Pass your initial url 14 | }); 15 | 16 | // Create a new router instance 17 | const router = createRouter({ routeTree, history: memoryHistory }); 18 | 19 | // Register the router instance for type safety 20 | declare module "@tanstack/react-router" { 21 | interface Register { 22 | router: typeof router; 23 | } 24 | } 25 | 26 | // Render the app 27 | const rootElement = document.getElementById("root")!; 28 | if (!rootElement.innerHTML) { 29 | const root = ReactDOM.createRoot(rootElement); 30 | root.render( 31 | 32 | 33 | , 34 | ); 35 | } 36 | -------------------------------------------------------------------------------- /apps/cruncher/src/processes/server/lib/pipelineEngine/where.ts: -------------------------------------------------------------------------------- 1 | import { DisplayResults, Events, Table } from "~lib/displayTypes"; 2 | import { LogicalExpression } from "@cruncher/qql/grammar"; 3 | import { processLogicalExpression } from "./logicalExpression"; 4 | 5 | export const processWhere = ( 6 | data: DisplayResults, 7 | logicalExpression: LogicalExpression, 8 | ): DisplayResults => { 9 | const { events, table } = data; 10 | 11 | const newEvents: Events = { 12 | type: "events", 13 | data: events.data.filter((processedData) => 14 | processLogicalExpression(logicalExpression, { data: processedData }), 15 | ), 16 | }; 17 | 18 | const newTable: Table | undefined = table && { 19 | type: "table", 20 | columns: table.columns, 21 | dataPoints: table.dataPoints.filter((processedData) => 22 | processLogicalExpression(logicalExpression, { data: processedData }), 23 | ), 24 | }; 25 | 26 | return { 27 | events: newEvents, 28 | table: newTable, 29 | view: undefined, 30 | }; 31 | }; 32 | -------------------------------------------------------------------------------- /apps/cruncher/src/processes/frontend/components/ui/checkbox.tsx: -------------------------------------------------------------------------------- 1 | import { Checkbox as ChakraCheckbox } from "@chakra-ui/react"; 2 | import * as React from "react"; 3 | 4 | export interface CheckboxProps extends ChakraCheckbox.RootProps { 5 | icon?: React.ReactNode; 6 | inputProps?: React.InputHTMLAttributes; 7 | rootRef?: React.Ref; 8 | } 9 | 10 | export const Checkbox = React.forwardRef( 11 | function Checkbox(props, ref) { 12 | const { icon, children, inputProps, rootRef, ...rest } = props; 13 | return ( 14 | // @ts-expect-error - lib component 15 | 16 | 17 | 18 | {icon || } 19 | 20 | {children != null && ( 21 | {children} 22 | )} 23 | 24 | ); 25 | }, 26 | ); 27 | -------------------------------------------------------------------------------- /tools/cruncher-tools/src/generators/files/package.json.template: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@cruncher/adapter-<%= name %>", 3 | "version": "0.0.1", 4 | "description": "", 5 | "publishConfig": { 6 | "access": "public" 7 | }, 8 | "type": "module", 9 | "main": "dist/index.js", 10 | "types": "dist/index.d.ts", 11 | "exports": { 12 | ".": { 13 | "default": "./dist/index.js", 14 | "types": "./dist/index.d.ts" 15 | } 16 | }, 17 | "files": [ 18 | "dist" 19 | ], 20 | "scripts": { 21 | "build": "tsc --project tsconfig.json", 22 | "test": "vitest", 23 | "format": "prettier --write src/", 24 | "format:check": "prettier --check src/", 25 | "prepublishOnly": "npm run build" 26 | }, 27 | "keywords": [ 28 | "cruncher", 29 | "qql", 30 | "query-language", 31 | "adapter" 32 | ], 33 | "license": "GPL-3.0-only", 34 | "dependencies": { 35 | "@cruncher/adapter-utils": "workspace:*", 36 | "@cruncher/qql": "workspace:*", 37 | "zod": "^3.25.67" 38 | }, 39 | "devDependencies": { 40 | "typescript": "^5.8.3", 41 | "vitest": "^3.2.4" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /apps/cruncher/src/processes/frontend/components/ui/useOutsideDetector.ts: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import { getCruncherRoot } from "~core/utils/shadowUtils"; 3 | 4 | export function useOutsideDetector(onOutsideClick = () => {}) { 5 | const ref = React.useRef(null); 6 | useEffect(() => { 7 | const root = getCruncherRoot(); 8 | if (!root) { 9 | console.warn("Root not found - useOutsideDetector will not work"); 10 | return; 11 | } 12 | 13 | /** 14 | * Alert if clicked on outside of element 15 | */ 16 | function handleClickOutside(event: MouseEvent) { 17 | if ( 18 | ref.current && 19 | event.target && 20 | !ref.current.contains(event.target as Node) 21 | ) { 22 | onOutsideClick(); 23 | } 24 | } 25 | // Bind the event listener 26 | root.addEventListener("mousedown", handleClickOutside); 27 | return () => { 28 | // Unbind the event listener on clean up 29 | root.removeEventListener("mousedown", handleClickOutside); 30 | }; 31 | }, [ref, onOutsideClick]); 32 | 33 | return ref; 34 | } 35 | -------------------------------------------------------------------------------- /packages/adapters/utils/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.2.4 (2025-06-21) 2 | 3 | ### 🩹 Fixes 4 | 5 | - created new standard for adapters - and new codegen for it ([#15](https://github.com/IamShobe/cruncher/pull/15)) 6 | 7 | ### 🧱 Updated Dependencies 8 | 9 | - Updated @cruncher/utils to 0.2.4 10 | - Updated @cruncher/qql to 0.2.4 11 | 12 | ## 0.2.3 (2025-06-21) 13 | 14 | ### 🩹 Fixes 15 | 16 | - fix typing ([9374f27](https://github.com/IamShobe/cruncher/commit/9374f27)) 17 | 18 | ## 0.2.2 (2025-06-21) 19 | 20 | ### 🩹 Fixes 21 | 22 | - Update ci stuff ([#13](https://github.com/IamShobe/cruncher/pull/13)) 23 | 24 | ### 🧱 Updated Dependencies 25 | 26 | - Updated @cruncher/utils to 0.2.3 27 | - Updated @cruncher/qql to 0.2.3 28 | 29 | ## 0.2.1 (2025-06-21) 30 | 31 | ### 🧱 Updated Dependencies 32 | 33 | - Updated @cruncher/utils to 0.2.2 34 | - Updated @cruncher/qql to 0.2.2 35 | 36 | ## 0.2.0 (2025-06-21) 37 | 38 | ### 🚀 Features 39 | 40 | - nx initial commit ([883376b](https://github.com/IamShobe/cruncher/commit/883376b)) 41 | 42 | ### 🧱 Updated Dependencies 43 | 44 | - Updated @cruncher/utils to 0.2.0 45 | - Updated @cruncher/qql to 0.2.0 -------------------------------------------------------------------------------- /apps/cruncher/src/processes/frontend/components/ui/data-list.tsx: -------------------------------------------------------------------------------- 1 | import { DataList as ChakraDataList } from "@chakra-ui/react"; 2 | import { InfoTip } from "./toggle-tip"; 3 | import * as React from "react"; 4 | 5 | export const DataListRoot = ChakraDataList.Root; 6 | 7 | interface ItemProps extends ChakraDataList.ItemProps { 8 | label: React.ReactNode; 9 | value: React.ReactNode; 10 | info?: React.ReactNode; 11 | grow?: boolean; 12 | } 13 | 14 | export const DataListItem = React.forwardRef( 15 | function DataListItem(props, ref) { 16 | const { label, info, value, children, grow, ...rest } = props; 17 | return ( 18 | // @ts-expect-error - lib component 19 | 20 | 21 | {label} 22 | {info && {info}} 23 | 24 | 25 | {value} 26 | 27 | {children} 28 | 29 | ); 30 | }, 31 | ); 32 | -------------------------------------------------------------------------------- /docs/src/content/docs/qql-reference/commands/table.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: table 3 | description: Displays the results of the pipeline as a table, allowing you to select and order specific fields. 4 | --- 5 | 6 | import { Badge } from '@astrojs/starlight/components'; 7 | 8 | 9 | 10 | The `table` command displays the results of the pipeline as a table, allowing you to select and order specific fields. You can also use the `as` keyword to assign an alias to any column. 11 | 12 | ## Syntax 13 | 14 | ``` 15 | table field1 [as alias1], field2 [as alias2], ... 16 | ``` 17 | 18 | - `field [as alias]`: The field to display as a column, optionally renamed with `as`. 19 | 20 | ## Usage 21 | 22 | - Use `table` at the end of a pipeline to control which fields are shown and their order. 23 | - Use `as` to rename columns for clarity or convenience. 24 | - If not specified, all fields may be shown in default order. 25 | 26 | ## Example 27 | 28 | ``` 29 | ... | table timestamp as time, message, level as severity 30 | ``` 31 | 32 | This displays the `timestamp` field as `time`, the `message` field, and the `level` field as `severity` in the output table. -------------------------------------------------------------------------------- /apps/cruncher/src/processes/frontend/core/notifyError.tsx: -------------------------------------------------------------------------------- 1 | // import toast from "react-hot-toast"; 2 | import { toaster } from "~components/ui/toaster"; 3 | import { QQLLexingError, QQLParserError } from "@cruncher/qql"; 4 | 5 | export const notifySuccess = (message: string) => { 6 | console.log(message); 7 | toaster.success({ 8 | title: message, 9 | duration: 5000, 10 | }); 11 | }; 12 | 13 | export const notifyError = (message: string, error: Error) => { 14 | console.error(message, error); 15 | let subMessage = error.message; 16 | if (error instanceof QQLLexingError) { 17 | const errors: string[] = []; 18 | error.errors.map((e) => { 19 | errors.push(`${e.line}:${e.column} - ${e.message}`); 20 | }); 21 | 22 | subMessage = errors.join("\n"); 23 | } else if (error instanceof QQLParserError) { 24 | const errors: string[] = []; 25 | error.errors.map((e) => { 26 | errors.push(`${e.message}`); 27 | }); 28 | 29 | subMessage = errors.join("\n"); 30 | } 31 | toaster.error({ 32 | title: message, 33 | description: subMessage, 34 | duration: 15000, 35 | closable: true, 36 | }); 37 | }; 38 | -------------------------------------------------------------------------------- /packages/adapters/coralogix/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.2.4 (2025-06-21) 2 | 3 | ### 🩹 Fixes 4 | 5 | - created new standard for adapters - and new codegen for it ([#15](https://github.com/IamShobe/cruncher/pull/15)) 6 | 7 | ### 🧱 Updated Dependencies 8 | 9 | - Updated @cruncher/adapters-utils to 0.2.4 10 | - Updated @cruncher/qql to 0.2.4 11 | 12 | ## 0.2.3 (2025-06-21) 13 | 14 | ### 🧱 Updated Dependencies 15 | 16 | - Updated @cruncher/adapters-utils to 0.2.3 17 | 18 | ## 0.2.2 (2025-06-21) 19 | 20 | ### 🩹 Fixes 21 | 22 | - Update ci stuff ([#13](https://github.com/IamShobe/cruncher/pull/13)) 23 | 24 | ### 🧱 Updated Dependencies 25 | 26 | - Updated @cruncher/adapters-utils to 0.2.2 27 | - Updated @cruncher/qql to 0.2.3 28 | 29 | ## 0.2.1 (2025-06-21) 30 | 31 | ### 🧱 Updated Dependencies 32 | 33 | - Updated @cruncher/adapters-utils to 0.2.1 34 | - Updated @cruncher/qql to 0.2.2 35 | 36 | ## 0.2.0 (2025-06-21) 37 | 38 | ### 🚀 Features 39 | 40 | - nx initial commit ([883376b](https://github.com/IamShobe/cruncher/commit/883376b)) 41 | 42 | ### 🧱 Updated Dependencies 43 | 44 | - Updated @cruncher/adapter-utils to 0.2.0 45 | - Updated @cruncher/qql to 0.2.0 -------------------------------------------------------------------------------- /packages/adapters/mocked-data/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.2.4 (2025-06-21) 2 | 3 | ### 🩹 Fixes 4 | 5 | - created new standard for adapters - and new codegen for it ([#15](https://github.com/IamShobe/cruncher/pull/15)) 6 | 7 | ### 🧱 Updated Dependencies 8 | 9 | - Updated @cruncher/adapters-utils to 0.2.4 10 | - Updated @cruncher/qql to 0.2.4 11 | 12 | ## 0.2.3 (2025-06-21) 13 | 14 | ### 🧱 Updated Dependencies 15 | 16 | - Updated @cruncher/adapters-utils to 0.2.3 17 | 18 | ## 0.2.2 (2025-06-21) 19 | 20 | ### 🩹 Fixes 21 | 22 | - Update ci stuff ([#13](https://github.com/IamShobe/cruncher/pull/13)) 23 | 24 | ### 🧱 Updated Dependencies 25 | 26 | - Updated @cruncher/adapters-utils to 0.2.2 27 | - Updated @cruncher/qql to 0.2.3 28 | 29 | ## 0.2.1 (2025-06-21) 30 | 31 | ### 🧱 Updated Dependencies 32 | 33 | - Updated @cruncher/adapters-utils to 0.2.1 34 | - Updated @cruncher/qql to 0.2.2 35 | 36 | ## 0.2.0 (2025-06-21) 37 | 38 | ### 🚀 Features 39 | 40 | - nx initial commit ([883376b](https://github.com/IamShobe/cruncher/commit/883376b)) 41 | 42 | ### 🧱 Updated Dependencies 43 | 44 | - Updated @cruncher/adapter-utils to 0.2.0 45 | - Updated @cruncher/qql to 0.2.0 -------------------------------------------------------------------------------- /apps/cruncher/src/processes/server/config/schema.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod/v4"; 2 | 3 | export const ConnectorConfigSchema = z.object({ 4 | type: z.string(), // e.g., 'grafana_browser' 5 | name: z.string(), // e.g., 'main' 6 | params: z.record(z.string(), z.any()), // Parameters specific to the connector 7 | }); 8 | 9 | export const ProfilesSchema = z.record( 10 | z.string(), // Profile name 11 | z.object({ 12 | connectors: z.array(z.string()), // List of connector names used in this profile 13 | }), 14 | ); 15 | 16 | export const CruncherLokiConfigSchema = z.object({ 17 | listenPort: z.number().optional().default(43100), // Optional port for Loki 18 | enabled: z.boolean().optional().default(false), // Optional flag to enable/disable Loki 19 | }); 20 | 21 | export type CruncherLokiConfig = z.infer; 22 | 23 | export const CruncherConfigSchema = z.object({ 24 | loki: CruncherLokiConfigSchema.optional(), // Optional Loki configuration 25 | profiles: ProfilesSchema.optional(), 26 | connectors: z.array(ConnectorConfigSchema), 27 | }); 28 | 29 | export type CruncherConfig = z.infer; 30 | -------------------------------------------------------------------------------- /packages/adapters/grafana-loki-browser/src/index.ts: -------------------------------------------------------------------------------- 1 | import { Adapter, newPluginRef, QueryProvider } from "@cruncher/adapter-utils"; 2 | import { GrafanaController } from "./controller"; 3 | import { z } from "zod/v4"; 4 | 5 | const paramsSchema = z.object({ 6 | grafanaUrl: z.string(), 7 | uid: z.string(), 8 | filter: z 9 | .array( 10 | z.object({ 11 | key: z.string(), 12 | value: z.string(), 13 | operator: z.enum(["=", "=~", "!=", "!~"]), 14 | }), 15 | ) 16 | .default([]), 17 | querySuffix: z.array(z.string()).default([]), 18 | }); 19 | 20 | const adapter: Adapter = { 21 | ref: newPluginRef("grafana_browser"), 22 | name: "Grafana Browser", 23 | description: "Adapter for Grafana Browser", 24 | version: "0.1.0", 25 | params: paramsSchema, 26 | factory: (context, { params }): QueryProvider => { 27 | const parsedParams = paramsSchema.parse(params); 28 | 29 | return new GrafanaController( 30 | context, 31 | parsedParams.grafanaUrl, 32 | parsedParams.uid, 33 | parsedParams.filter, 34 | parsedParams.querySuffix, 35 | ); 36 | }, 37 | }; 38 | 39 | export default adapter; 40 | -------------------------------------------------------------------------------- /packages/adapters/grafana-loki-browser/src/query.ts: -------------------------------------------------------------------------------- 1 | import { ControllerIndexParam, Search } from "@cruncher/qql/grammar"; 2 | import { buildExpression, LokiLabelFilter } from "@cruncher/adapter-loki/query"; 3 | 4 | export const LIMIT = 5000; 5 | 6 | export const buildQuery = ( 7 | uid: string, 8 | baseFilter: LokiLabelFilter[], 9 | controllerParams: ControllerIndexParam[], 10 | search: Search, 11 | fromTime: Date, 12 | toTime: Date, 13 | filterExtensions?: string[], 14 | ) => { 15 | return { 16 | queries: [ 17 | { 18 | refId: "A", 19 | expr: buildExpression( 20 | baseFilter, 21 | controllerParams, 22 | search, 23 | filterExtensions, 24 | ), 25 | queryType: "range", 26 | datasource: { 27 | type: "loki", 28 | uid: uid, 29 | }, 30 | editorMode: "code", 31 | maxLines: LIMIT, 32 | step: "", 33 | legendFormat: "", 34 | datasourceId: 7, 35 | intervalMs: 60000, 36 | maxDataPoints: 1002, 37 | }, 38 | ], 39 | from: fromTime.getTime().toString(), 40 | to: toTime.getTime().toString(), 41 | }; 42 | }; 43 | -------------------------------------------------------------------------------- /apps/cruncher/src/processes/frontend/components/ui/progress.tsx: -------------------------------------------------------------------------------- 1 | import { Progress as ChakraProgress } from "@chakra-ui/react"; 2 | import { InfoTip } from "./toggle-tip"; 3 | import * as React from "react"; 4 | 5 | export const ProgressBar = React.forwardRef< 6 | HTMLDivElement, 7 | ChakraProgress.TrackProps 8 | >(function ProgressBar(props, ref) { 9 | return ( 10 | // @ts-expect-error - lib component 11 | 12 | 13 | 14 | ); 15 | }); 16 | 17 | export interface ProgressLabelProps extends ChakraProgress.LabelProps { 18 | info?: React.ReactNode; 19 | } 20 | 21 | export const ProgressLabel = React.forwardRef< 22 | HTMLDivElement, 23 | ProgressLabelProps 24 | >(function ProgressLabel(props, ref) { 25 | const { children, info, ...rest } = props; 26 | return ( 27 | // @ts-expect-error - lib component 28 | 29 | {children} 30 | {info && {info}} 31 | 32 | ); 33 | }); 34 | 35 | export const ProgressRoot = ChakraProgress.Root; 36 | export const ProgressValueText = ChakraProgress.ValueText; 37 | -------------------------------------------------------------------------------- /packages/adapters/mocked-data/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@cruncher/adapter-mock", 3 | "version": "0.2.4", 4 | "description": "Mocked data adapter for Cruncher.", 5 | "publishConfig": { 6 | "access": "public" 7 | }, 8 | "type": "module", 9 | "main": "dist/index.js", 10 | "types": "dist/index.d.ts", 11 | "exports": { 12 | ".": { 13 | "default": "./dist/index.js", 14 | "types": "./dist/index.d.ts" 15 | } 16 | }, 17 | "files": [ 18 | "dist" 19 | ], 20 | "scripts": { 21 | "build": "tsc --project tsconfig.json", 22 | "test": "vitest", 23 | "format": "prettier --write src/", 24 | "format:check": "prettier --check src/", 25 | "prepublishOnly": "npm run build" 26 | }, 27 | "keywords": [ 28 | "cruncher", 29 | "qql", 30 | "query-language", 31 | "mocked-data", 32 | "adapter" 33 | ], 34 | "author": { 35 | "name": "Elran Shefer" 36 | }, 37 | "license": "GPL-3.0-only", 38 | "dependencies": { 39 | "@cruncher/adapter-utils": "workspace:*", 40 | "@cruncher/qql": "workspace:*", 41 | "zod": "^3.25.67" 42 | }, 43 | "devDependencies": { 44 | "typescript": "^5.8.3", 45 | "vitest": "^3.2.4" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /apps/cruncher/src/processes/server/plugins_engine/protocolOut.ts: -------------------------------------------------------------------------------- 1 | import z from "zod/v4"; 2 | 3 | export const QueryBatchDoneSchema = z.object({ 4 | type: z.literal("query_batch_done"), 5 | payload: z.object({ 6 | jobId: z.string(), 7 | data: z.any(), // Adjust the type as needed for your data structure 8 | }), 9 | }); 10 | export type QueryBatchDone = z.infer; 11 | 12 | export const QueryJobUpdatedSchema = z.object({ 13 | type: z.literal("query_job_updated"), 14 | payload: z.object({ 15 | jobId: z.string(), 16 | status: z.enum(["running", "completed", "failed", "canceled"]), 17 | error: z.string().nullable().optional(), 18 | }), 19 | }); 20 | export type QueryJobUpdated = z.infer; 21 | 22 | export const ReceivedMessageSchema = z.discriminatedUnion("type", [ 23 | QueryBatchDoneSchema, 24 | QueryJobUpdatedSchema, 25 | ]); 26 | export type ReceivedMessage = z.infer; 27 | 28 | export const UrlNavigationSchema = z.object({ 29 | type: z.literal("url_navigation"), 30 | payload: z.object({ 31 | url: z.string(), 32 | }), 33 | }); 34 | export type UrlNavigation = z.infer; 35 | -------------------------------------------------------------------------------- /packages/adapters/coralogix/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@cruncher/adapter-coralogix", 3 | "type": "module", 4 | "version": "0.2.4", 5 | "description": "Coralogix adapter for Cruncher.", 6 | "publishConfig": { 7 | "access": "public" 8 | }, 9 | "main": "dist/index.js", 10 | "types": "dist/index.d.ts", 11 | "exports": { 12 | ".": { 13 | "default": "./dist/index.js", 14 | "types": "./dist/index.d.ts" 15 | } 16 | }, 17 | "files": [ 18 | "dist" 19 | ], 20 | "scripts": { 21 | "build": "tsc --project tsconfig.json", 22 | "format": "prettier --write src/", 23 | "format:check": "prettier --check src/", 24 | "prepublishOnly": "npm run build" 25 | }, 26 | "keywords": [ 27 | "cruncher", 28 | "qql", 29 | "query-language", 30 | "coralogix", 31 | "adapter" 32 | ], 33 | "author": { 34 | "name": "Elran Shefer" 35 | }, 36 | "license": "GPL-3.0-only", 37 | "dependencies": { 38 | "zod": "^3.25.67", 39 | "async-mutex": "^0.5.0", 40 | "@cruncher/qql": "workspace:*", 41 | "@cruncher/adapter-utils": "workspace:*" 42 | }, 43 | "devDependencies": { 44 | "typescript": "^5.8.3", 45 | "@types/node": "^24.0.3" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /tools/cruncher-tools/src/generators/adapter-generator.ts: -------------------------------------------------------------------------------- 1 | import { 2 | addProjectConfiguration, 3 | formatFiles, 4 | generateFiles, 5 | Tree, 6 | } from "@nx/devkit"; 7 | import * as path from "path"; 8 | import { AdapterGeneratorGeneratorSchema } from "./schema"; 9 | 10 | export async function adapterGeneratorGenerator( 11 | tree: Tree, 12 | options: AdapterGeneratorGeneratorSchema, 13 | ) { 14 | const projectRoot = `packages/adapters/${options.name}`; 15 | addProjectConfiguration(tree, `@cruncher/adapter-${options.name}`, { 16 | root: projectRoot, 17 | projectType: "library", 18 | sourceRoot: `${projectRoot}/src`, 19 | targets: {}, 20 | }); 21 | generateFiles(tree, path.join(__dirname, "files"), projectRoot, { 22 | ...options, 23 | titleCaseWithController, 24 | titleCase, 25 | }); 26 | await formatFiles(tree); 27 | } 28 | 29 | function titleCase(name: string): string { 30 | return name 31 | .split("-") 32 | .map((part) => part.charAt(0).toUpperCase() + part.slice(1)) 33 | .join(""); 34 | } 35 | 36 | function titleCaseWithController(name: string): string { 37 | return titleCase(name) + "Controller"; 38 | } 39 | 40 | export default adapterGeneratorGenerator; 41 | -------------------------------------------------------------------------------- /apps/cruncher/src/processes/frontend/components/ui/field.tsx: -------------------------------------------------------------------------------- 1 | import { Field as ChakraField } from "@chakra-ui/react"; 2 | import * as React from "react"; 3 | 4 | export interface FieldProps extends Omit { 5 | label?: React.ReactNode; 6 | helperText?: React.ReactNode; 7 | errorText?: React.ReactNode; 8 | optionalText?: React.ReactNode; 9 | } 10 | 11 | export const Field = React.forwardRef( 12 | function Field(props, ref) { 13 | const { label, children, helperText, errorText, optionalText, ...rest } = 14 | props; 15 | return ( 16 | // @ts-expect-error - lib component 17 | 18 | {label && ( 19 | 20 | {label} 21 | 22 | 23 | )} 24 | {children} 25 | {helperText && ( 26 | {helperText} 27 | )} 28 | {errorText && ( 29 | {errorText} 30 | )} 31 | 32 | ); 33 | }, 34 | ); 35 | -------------------------------------------------------------------------------- /apps/cruncher/src/processes/main/forge.d.ts: -------------------------------------------------------------------------------- 1 | export {}; // Make this a module 2 | 3 | declare global { 4 | // This allows TypeScript to pick up the magic constants that's auto-generated by Forge's Vite 5 | // plugin that tells the Electron app where to look for the Vite-bundled app code (depending on 6 | // whether you're running in development or production). 7 | const MAIN_WINDOW_VITE_DEV_SERVER_URL: string; 8 | const MAIN_WINDOW_VITE_NAME: string; 9 | 10 | namespace NodeJS { 11 | interface Process { 12 | // Used for hot reload after preload scripts. 13 | viteDevServers: Record; 14 | } 15 | } 16 | 17 | type VitePluginConfig = ConstructorParameters< 18 | typeof import("@electron-forge/plugin-vite").VitePlugin 19 | >[0]; 20 | 21 | interface VitePluginRuntimeKeys { 22 | VITE_DEV_SERVER_URL: `${string}_VITE_DEV_SERVER_URL`; 23 | VITE_NAME: `${string}_VITE_NAME`; 24 | } 25 | } 26 | 27 | declare module "vite" { 28 | interface ConfigEnv< 29 | K extends keyof VitePluginConfig = keyof VitePluginConfig, 30 | > { 31 | root: string; 32 | forgeConfig: VitePluginConfig; 33 | forgeConfigSelf: VitePluginConfig[K][number]; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/adapters/docker/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@cruncher/adapter-docker", 3 | "version": "0.2.5", 4 | "description": "Docker adapter for Cruncher.", 5 | "type": "module", 6 | "publishConfig": { 7 | "access": "public" 8 | }, 9 | "main": "dist/index.js", 10 | "types": "dist/index.d.ts", 11 | "exports": { 12 | ".": { 13 | "default": "./dist/index.js", 14 | "types": "./dist/index.d.ts" 15 | } 16 | }, 17 | "files": [ 18 | "dist" 19 | ], 20 | "scripts": { 21 | "build": "tsc --project tsconfig.json", 22 | "format": "prettier --write src/", 23 | "format:check": "prettier --check src/", 24 | "prepublishOnly": "npm run build" 25 | }, 26 | "keywords": [ 27 | "cruncher", 28 | "qql", 29 | "query-language", 30 | "docker", 31 | "adapter" 32 | ], 33 | "author": { 34 | "name": "Elran Shefer" 35 | }, 36 | "license": "GPL-3.0-only", 37 | "dependencies": { 38 | "zod": "^3.25.67", 39 | "ansicolor": "^2.0.3", 40 | "merge-k-sorted-arrays": "^2.1.0", 41 | "@cruncher/qql": "workspace:*", 42 | "@cruncher/adapter-utils": "workspace:*" 43 | }, 44 | "devDependencies": { 45 | "typescript": "^5.8.3", 46 | "@types/node": "^24.0.3" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /apps/cruncher/src/processes/frontend/components/ui/hover-card.tsx: -------------------------------------------------------------------------------- 1 | import { HoverCard, Portal } from "@chakra-ui/react"; 2 | import * as React from "react"; 3 | 4 | interface HoverCardContentProps extends HoverCard.ContentProps { 5 | portalled?: boolean; 6 | portalRef?: React.RefObject; 7 | } 8 | 9 | export const HoverCardContent = React.forwardRef< 10 | HTMLDivElement, 11 | HoverCardContentProps 12 | >(function HoverCardContent(props, ref) { 13 | const { portalled = true, portalRef, ...rest } = props; 14 | 15 | return ( 16 | 17 | 18 | {/* @ts-expect-error - lib component */} 19 | 20 | 21 | 22 | ); 23 | }); 24 | 25 | export const HoverCardArrow = React.forwardRef< 26 | HTMLDivElement, 27 | HoverCard.ArrowProps 28 | >(function HoverCardArrow(props, ref) { 29 | return ( 30 | // @ts-expect-error - lib component 31 | 32 | 33 | 34 | ); 35 | }); 36 | 37 | export const HoverCardRoot = HoverCard.Root; 38 | export const HoverCardTrigger = HoverCard.Trigger; 39 | -------------------------------------------------------------------------------- /packages/adapters/loki/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@cruncher/adapter-loki", 3 | "version": "0.1.0", 4 | "description": "", 5 | "publishConfig": { 6 | "access": "public" 7 | }, 8 | "type": "module", 9 | "main": "dist/index.js", 10 | "types": "dist/index.d.ts", 11 | "exports": { 12 | ".": { 13 | "default": "./dist/index.js", 14 | "types": "./dist/index.d.ts" 15 | }, 16 | "./query": { 17 | "default": "./dist/query.js", 18 | "types": "./dist/query.d.ts" 19 | } 20 | }, 21 | "files": [ 22 | "dist" 23 | ], 24 | "scripts": { 25 | "build": "tsc --project tsconfig.json", 26 | "test": "vitest", 27 | "format": "prettier --write src/", 28 | "format:check": "prettier --check src/", 29 | "prepublishOnly": "npm run build" 30 | }, 31 | "keywords": [ 32 | "cruncher", 33 | "qql", 34 | "query-language", 35 | "adapter" 36 | ], 37 | "license": "GPL-3.0-only", 38 | "dependencies": { 39 | "@cruncher/adapter-utils": "workspace:*", 40 | "@cruncher/qql": "workspace:*", 41 | "@types/regex-escape": "^3.4.1", 42 | "regex-escape": "^3.4.11", 43 | "zod": "^3.25.67" 44 | }, 45 | "devDependencies": { 46 | "typescript": "^5.8.3", 47 | "vitest": "^3.2.4" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /packages/qql/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@cruncher/qql", 3 | "version": "0.2.4", 4 | "description": "QQL query language for Cruncher platform.", 5 | "publishConfig": { 6 | "access": "public" 7 | }, 8 | "main": "dist/index.js", 9 | "types": "dist/index.d.ts", 10 | "exports": { 11 | ".": { 12 | "default": "./dist/index.js", 13 | "types": "./dist/index.d.ts" 14 | }, 15 | "./grammar": { 16 | "default": "./dist/grammar.js", 17 | "types": "./dist/grammar.d.ts" 18 | }, 19 | "./searchTree": { 20 | "default": "./dist/searchTree.js", 21 | "types": "./dist/searchTree.d.ts" 22 | } 23 | }, 24 | "files": [ 25 | "dist" 26 | ], 27 | "scripts": { 28 | "build": "tsc --project tsconfig.json", 29 | "test": "nx vite:test", 30 | "format": "prettier --write src/", 31 | "format:check": "prettier --check src/", 32 | "prepublishOnly": "npm run build" 33 | }, 34 | "keywords": [ 35 | "cruncher", 36 | "qql", 37 | "query-language" 38 | ], 39 | "author": { 40 | "name": "Elran Shefer" 41 | }, 42 | "license": "GPL-3.0-only", 43 | "dependencies": { 44 | "chevrotain": "^11.0.3" 45 | }, 46 | "devDependencies": { 47 | "typescript": "^5.8.3", 48 | "vitest": "^3.2.4" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /packages/adapters/docker/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.2.5 (2025-06-28) 2 | 3 | ### 🩹 Fixes 4 | 5 | - fixes to allow better reslience ([#16](https://github.com/IamShobe/cruncher/pull/16)) 6 | 7 | ## 0.2.4 (2025-06-21) 8 | 9 | ### 🩹 Fixes 10 | 11 | - created new standard for adapters - and new codegen for it ([#15](https://github.com/IamShobe/cruncher/pull/15)) 12 | 13 | ### 🧱 Updated Dependencies 14 | 15 | - Updated @cruncher/adapters-utils to 0.2.4 16 | - Updated @cruncher/qql to 0.2.4 17 | 18 | ## 0.2.3 (2025-06-21) 19 | 20 | ### 🧱 Updated Dependencies 21 | 22 | - Updated @cruncher/adapters-utils to 0.2.3 23 | 24 | ## 0.2.2 (2025-06-21) 25 | 26 | ### 🩹 Fixes 27 | 28 | - Update ci stuff ([#13](https://github.com/IamShobe/cruncher/pull/13)) 29 | 30 | ### 🧱 Updated Dependencies 31 | 32 | - Updated @cruncher/adapters-utils to 0.2.2 33 | - Updated @cruncher/qql to 0.2.3 34 | 35 | ## 0.2.1 (2025-06-21) 36 | 37 | ### 🧱 Updated Dependencies 38 | 39 | - Updated @cruncher/adapters-utils to 0.2.1 40 | - Updated @cruncher/qql to 0.2.2 41 | 42 | ## 0.2.0 (2025-06-21) 43 | 44 | ### 🚀 Features 45 | 46 | - nx initial commit ([883376b](https://github.com/IamShobe/cruncher/commit/883376b)) 47 | 48 | ### 🧱 Updated Dependencies 49 | 50 | - Updated @cruncher/adapter-utils to 0.2.0 51 | - Updated @cruncher/qql to 0.2.0 -------------------------------------------------------------------------------- /tools/cruncher-tools/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cruncher-tools", 3 | "$schema": "../../node_modules/nx/schemas/project-schema.json", 4 | "sourceRoot": "tools/cruncher-tools/src", 5 | "projectType": "library", 6 | "tags": [], 7 | "targets": { 8 | "build": { 9 | "executor": "@nx/js:tsc", 10 | "outputs": ["{options.outputPath}"], 11 | "options": { 12 | "outputPath": "dist/tools/cruncher-tools", 13 | "main": "tools/cruncher-tools/src/index.ts", 14 | "tsConfig": "tools/cruncher-tools/tsconfig.lib.json", 15 | "assets": [ 16 | "tools/cruncher-tools/*.md", 17 | { 18 | "input": "./tools/cruncher-tools/src", 19 | "glob": "**/!(*.ts)", 20 | "output": "./src" 21 | }, 22 | { 23 | "input": "./tools/cruncher-tools/src", 24 | "glob": "**/*.d.ts", 25 | "output": "./src" 26 | }, 27 | { 28 | "input": "./tools/cruncher-tools", 29 | "glob": "generators.json", 30 | "output": "." 31 | }, 32 | { 33 | "input": "./tools/cruncher-tools", 34 | "glob": "executors.json", 35 | "output": "." 36 | } 37 | ] 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /docs/src/content/docs/getting-started/03-quick-start.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Quick Start 3 | description: A quick guide to get started with Cruncher. 4 | --- 5 | import { FileTree } from '@astrojs/starlight/components'; 6 | import { Steps } from '@astrojs/starlight/components'; 7 | import { Code } from '@astrojs/starlight/components'; 8 | import example from './03-quick-start.cruncher.config.yaml?raw'; 9 | 10 | 11 | 12 | 1. Install Cruncher by following the [installation guide](/docs/getting-started/02-installation). 13 | 2. Create a configuration file named `cruncher.config.yaml` in your home directory under 14 | `~/.config/cruncher/`. 15 | ```bash 16 | mkdir -p ~/.config/cruncher 17 | ``` 18 | 19 | - ~/ user home directory 20 | - .config/ 21 | - cruncher/ 22 | - **cruncher.config.yaml** cruncher main configuration file 23 | 24 | 3. Use the following template for your `cruncher.config.yaml` file: 25 | 26 | 27 | 4. Launch Cruncher and start exploring! 28 | 29 | 5. Configure your data sources and adapters as needed. 30 | You can find more information on how to configure adapters in the [Adapters documentation](/getting-started/04-adapters). 31 | 32 | 33 | -------------------------------------------------------------------------------- /apps/cruncher/src/processes/server/lib/pipelineEngine/table.ts: -------------------------------------------------------------------------------- 1 | import { DisplayResults } from "~lib/displayTypes"; 2 | import { ProcessedData } from "@cruncher/adapter-utils/logTypes"; 3 | import { TableColumn } from "@cruncher/qql/grammar"; 4 | 5 | export const processTable = ( 6 | data: DisplayResults, 7 | columns: TableColumn[], 8 | ): DisplayResults => { 9 | const { events, table } = data; 10 | const dataPoints = table ? table.dataPoints : events.data; 11 | 12 | const newColumns: string[] = []; 13 | for (const column of columns) { 14 | const columnToUse = column.alias ?? column.column; 15 | newColumns.push(columnToUse); 16 | } 17 | 18 | const resultDataPoints: ProcessedData[] = []; 19 | for (const dataPoint of dataPoints) { 20 | const newDataPoint: ProcessedData = { 21 | object: {}, 22 | message: dataPoint.message, 23 | }; 24 | 25 | for (const column of columns) { 26 | const columnToUse = column.alias ?? column.column; 27 | newDataPoint.object[columnToUse] = dataPoint.object[column.column]; 28 | } 29 | 30 | resultDataPoints.push(newDataPoint); 31 | } 32 | 33 | return { 34 | events, 35 | table: { 36 | type: "table", 37 | columns: newColumns, 38 | dataPoints: resultDataPoints, 39 | }, 40 | view: undefined, 41 | }; 42 | }; 43 | -------------------------------------------------------------------------------- /docs/src/content/docs/index.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Cruncher 3 | description: Your logs, Your data - crunch it however you like 4 | template: splash 5 | hero: 6 | tagline: Your logs, Your data - crunch it however you like 7 | image: 8 | file: ../../assets/cruncher_logo.webp 9 | actions: 10 | - text: Get Started 11 | link: /getting-started/01-introduction 12 | icon: right-arrow 13 | - text: View on GitHub 14 | icon: external 15 | variant: minimal 16 | link: https://github.com/IamShobe/cruncher 17 | --- 18 | import { Image } from 'astro:assets'; 19 | import { Card, CardGrid } from '@astrojs/starlight/components'; 20 | import splashImg from '../../assets/splash.png'; 21 | 22 | Cruncher 23 | 24 | ## Features 25 | 26 | 27 | 28 | Post-process your logs and create custom views. 29 | 30 | 31 | Build custom dashboards and visualisations. 32 | 33 | 34 | Connect to multiple data sources and crunch them all together! 35 | 36 | 37 | Cruncher uses QQL (custom language) that is easy to learn and powerful. 38 | 39 | 40 | -------------------------------------------------------------------------------- /apps/cruncher/src/processes/frontend/components/ui/button.tsx: -------------------------------------------------------------------------------- 1 | import type { ButtonProps as ChakraButtonProps } from "@chakra-ui/react"; 2 | import { 3 | AbsoluteCenter, 4 | Button as ChakraButton, 5 | Span, 6 | Spinner, 7 | } from "@chakra-ui/react"; 8 | import * as React from "react"; 9 | 10 | interface ButtonLoadingProps { 11 | loading?: boolean; 12 | loadingText?: React.ReactNode; 13 | } 14 | 15 | export interface ButtonProps extends ChakraButtonProps, ButtonLoadingProps {} 16 | 17 | export const Button = React.forwardRef( 18 | function Button(props, ref) { 19 | const { loading, disabled, loadingText, children, ...rest } = props; 20 | return ( 21 | // @ts-expect-error - lib component 22 | 23 | {loading && !loadingText ? ( 24 | <> 25 | 26 | 27 | 28 | {children} 29 | 30 | ) : loading && loadingText ? ( 31 | <> 32 | 33 | {loadingText} 34 | 35 | ) : ( 36 | children 37 | )} 38 | 39 | ); 40 | }, 41 | ); 42 | -------------------------------------------------------------------------------- /packages/adapters/grafana-loki-browser/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@cruncher/adapter-grafana-loki-browser", 3 | "version": "0.2.5", 4 | "description": "Grafana Loki browser adapter for Cruncher.", 5 | "publishConfig": { 6 | "access": "public" 7 | }, 8 | "type": "module", 9 | "main": "dist/index.js", 10 | "types": "dist/index.d.ts", 11 | "exports": { 12 | ".": { 13 | "default": "./dist/index.js", 14 | "types": "./dist/index.d.ts" 15 | } 16 | }, 17 | "files": [ 18 | "dist" 19 | ], 20 | "scripts": { 21 | "build": "tsc --project tsconfig.json", 22 | "format": "prettier --write src/", 23 | "format:check": "prettier --check src/", 24 | "prepublishOnly": "npm run build" 25 | }, 26 | "keywords": [ 27 | "cruncher", 28 | "qql", 29 | "query-language", 30 | "grafana-loki", 31 | "adapter" 32 | ], 33 | "author": { 34 | "name": "Elran Shefer" 35 | }, 36 | "license": "GPL-3.0-only", 37 | "dependencies": { 38 | "zod": "^3.25.67", 39 | "@cruncher/qql": "workspace:*", 40 | "regex-escape": "^3.4.11", 41 | "async-mutex": "^0.5.0", 42 | "@cruncher/adapter-utils": "workspace:*", 43 | "@cruncher/adapter-loki": "workspace:*" 44 | }, 45 | "devDependencies": { 46 | "typescript": "^5.8.3", 47 | "@types/regex-escape": "^3.4.1", 48 | "@types/node": "^24.0.3" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /apps/cruncher/src/processes/server/engineV2/utils.ts: -------------------------------------------------------------------------------- 1 | import { ScaleLinear, scaleLinear } from "d3-scale"; 2 | import { asDateField, ProcessedData } from "@cruncher/adapter-utils/logTypes"; 3 | 4 | export const getScale = (selectedStartTime: Date, selectedEndTime: Date) => { 5 | if (!selectedStartTime || !selectedEndTime) { 6 | return; 7 | } 8 | 9 | return scaleLinear().domain([ 10 | selectedStartTime.getTime(), 11 | selectedEndTime.getTime(), 12 | ]); 13 | }; 14 | 15 | export const calculateBuckets = ( 16 | scale: ScaleLinear | undefined, 17 | data: ProcessedData[], 18 | ) => { 19 | if (!scale) { 20 | return []; 21 | } 22 | 23 | const buckets: Record = {}; 24 | const ticks = scale.ticks(100); 25 | 26 | data.forEach((object) => { 27 | // round timestamp to the nearest tick 28 | const timestamp = ticks.reduce((prev, curr) => { 29 | const thisTimestamp = asDateField(object.object._time).value; 30 | 31 | return Math.abs(curr - thisTimestamp) < Math.abs(prev - thisTimestamp) 32 | ? curr 33 | : prev; 34 | }); 35 | if (!buckets[timestamp]) { 36 | buckets[timestamp] = 0; 37 | } 38 | 39 | buckets[timestamp] += 1; 40 | }); 41 | 42 | return Object.entries(buckets).map(([timestamp, count]) => ({ 43 | timestamp: parseInt(timestamp), 44 | count, 45 | })); 46 | }; 47 | -------------------------------------------------------------------------------- /apps/cruncher/src/processes/main/utils/requestFromServer.ts: -------------------------------------------------------------------------------- 1 | import { MessagePortMain } from "electron"; 2 | import { isIpcMessage } from "./ipc"; 3 | 4 | /** 5 | * Helper to request the server process for a value and wait for a response. 6 | * @param serverProcess The child process to communicate with 7 | * @param request The request object to send 8 | * @param responseType The expected response type string 9 | */ 10 | export function requestFromServer( 11 | port: MessagePortMain | null, 12 | request: object, 13 | responseType: string, 14 | ): Promise { 15 | if (!port) { 16 | return Promise.reject(new Error("Server port is not initialized")); 17 | } 18 | 19 | return new Promise((resolve, reject) => { 20 | const timeout = setTimeout(() => { 21 | port.off("message", handler); 22 | reject( 23 | new Error( 24 | `Request timed out after 60 seconds for response type: ${responseType}`, 25 | ), 26 | ); 27 | }, 60000); // 60 seconds timeout 28 | 29 | const handler = (payload: Electron.MessageEvent) => { 30 | const msg = payload.data; 31 | if (isIpcMessage(msg) && msg.type === responseType) { 32 | port.off("message", handler); 33 | clearTimeout(timeout); 34 | resolve(msg as T); 35 | } 36 | }; 37 | 38 | port.on("message", handler); 39 | port.postMessage(request); 40 | }); 41 | } 42 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import nx from "@nx/eslint-plugin"; 2 | 3 | export default [ 4 | { 5 | files: ["**/*.json"], 6 | // Override or add rules here 7 | rules: {}, 8 | languageOptions: { 9 | parser: await import("jsonc-eslint-parser"), 10 | }, 11 | }, 12 | ...nx.configs["flat/base"], 13 | ...nx.configs["flat/typescript"], 14 | ...nx.configs["flat/javascript"], 15 | { 16 | ignores: [ 17 | "**/dist", 18 | "**/vite.config.*.timestamp*", 19 | "**/vitest.config.*.timestamp*", 20 | ], 21 | }, 22 | { 23 | files: ["**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx"], 24 | rules: { 25 | "@nx/enforce-module-boundaries": [ 26 | "error", 27 | { 28 | enforceBuildableLibDependency: true, 29 | allow: ["^.*/eslint(\\.base)?\\.config\\.[cm]?[jt]s$"], 30 | depConstraints: [ 31 | { 32 | sourceTag: "*", 33 | onlyDependOnLibsWithTags: ["*"], 34 | }, 35 | ], 36 | }, 37 | ], 38 | "@typescript-eslint/no-inferrable-types": "off", 39 | }, 40 | }, 41 | { 42 | files: [ 43 | "**/*.ts", 44 | "**/*.tsx", 45 | "**/*.cts", 46 | "**/*.mts", 47 | "**/*.js", 48 | "**/*.jsx", 49 | "**/*.cjs", 50 | "**/*.mjs", 51 | ], 52 | // Override or add rules here 53 | rules: {}, 54 | }, 55 | ]; 56 | -------------------------------------------------------------------------------- /apps/cruncher/src/processes/frontend/features/searcher/header/settings-drawer/Drawer.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Button, 3 | CloseButton, 4 | Drawer, 5 | IconButton, 6 | Portal, 7 | } from "@chakra-ui/react"; 8 | import { LuSettings } from "react-icons/lu"; 9 | import { Tooltip } from "~components/presets/Tooltip"; 10 | 11 | export const SettingsDrawer = () => { 12 | return ( 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | Search Settings 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | ); 41 | }; 42 | -------------------------------------------------------------------------------- /docs/src/content/docs/getting-started/01-introduction.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Introduction 3 | description: An introduction to Cruncher, its features, and how to get started. 4 | --- 5 | 6 | 7 | # Cruncher 8 | 9 | ![Splash](../../../assets/splash.png) 10 | 11 | Ever wanted to post process your data? 12 | `Cruncher` is here for the rescue! 13 | 14 | --- 15 | 16 | Heavily inspired from observability tools like `Splunk`, `Grafana` and more - 17 | it's main purpose is to allow post process data from multiple sources. 18 | Goal is to have a generic query language and to implement adapters to different backends - 19 | then, you get all investigation capabilities right from the user's computer. 20 | 21 | In earlier versions, `Cruncher` was built with a mindset to be embedded everywhere - like inside an extension - so everything was built under unique [shadowDOM](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_shadow_DOM). 22 | `Cruncher` is now a standalone application, aiming to be a powerful tool for data analysis and visualization. 23 | 24 | ## Features 25 | - **Crunch Logs**: Post-process your logs and create custom views. 26 | - **Visualise**: Build custom dashboards and visualisations. 27 | - **Multiple Sources**: Connect to multiple data sources and crunch them all together! 28 | - **Quick Query Language**: Cruncher uses [QQL](/qql-reference/01-qql) (custom language) that is easy to learn and powerful. -------------------------------------------------------------------------------- /.github/workflows/deploy-docs.yml: -------------------------------------------------------------------------------- 1 | name: Deploy Docs to GitHub Pages 2 | 3 | on: 4 | # Trigger the workflow every time you push to the `main` branch 5 | # Using a different branch name? Replace `main` with your branch’s name 6 | push: 7 | branches: [main] 8 | # Allows you to run this workflow manually from the Actions tab on GitHub. 9 | workflow_dispatch: 10 | 11 | concurrency: 12 | group: ${{ github.workflow }}-${{ github.ref }} 13 | cancel-in-progress: true 14 | 15 | # Allow this job to clone the repo and create a page deployment 16 | permissions: 17 | contents: read 18 | pages: write 19 | id-token: write 20 | 21 | jobs: 22 | build: 23 | runs-on: ubuntu-latest 24 | steps: 25 | - name: Checkout your repository using git 26 | uses: actions/checkout@v4 27 | 28 | - uses: pnpm/action-setup@v4 29 | 30 | - name: Install, build, and upload your site 31 | uses: withastro/action@v3 32 | with: 33 | path: ./docs/ # The root location of your Astro project inside the repository. (optional) 34 | node-version: 22 35 | package-manager: pnpm@10 36 | 37 | deploy: 38 | needs: build 39 | runs-on: ubuntu-latest 40 | environment: 41 | name: github-pages 42 | url: ${{ steps.deployment.outputs.page_url }} 43 | steps: 44 | - name: Deploy to GitHub Pages 45 | id: deployment 46 | uses: actions/deploy-pages@v4 47 | -------------------------------------------------------------------------------- /packages/adapters/utils/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@cruncher/adapter-utils", 3 | "version": "0.2.4", 4 | "description": "Utility functions for Cruncher adapters.", 5 | "publishConfig": { 6 | "access": "public" 7 | }, 8 | "type": "module", 9 | "main": "dist/index.js", 10 | "types": "dist/index.d.ts", 11 | "exports": { 12 | ".": { 13 | "default": "./dist/index.js", 14 | "types": "./dist/index.d.ts" 15 | }, 16 | "./formatters": { 17 | "default": "./dist/formatters.js", 18 | "types": "./dist/formatters.d.ts" 19 | }, 20 | "./logTypes": { 21 | "default": "./dist/logTypes.js", 22 | "types": "./dist/logTypes.d.ts" 23 | } 24 | }, 25 | "files": [ 26 | "dist" 27 | ], 28 | "scripts": { 29 | "build": "tsc --project tsconfig.json", 30 | "format": "prettier --write src/", 31 | "format:check": "prettier --check src/", 32 | "prepublishOnly": "npm run build" 33 | }, 34 | "keywords": [ 35 | "cruncher", 36 | "qql", 37 | "query-language", 38 | "coralogix", 39 | "adapter" 40 | ], 41 | "author": { 42 | "name": "Elran Shefer" 43 | }, 44 | "license": "GPL-3.0-only", 45 | "dependencies": { 46 | "@cruncher/qql": "workspace:*", 47 | "@cruncher/utils": "workspace:*", 48 | "date-fns": "^4.1.0", 49 | "zod": "^3.25.67", 50 | "@date-fns/utc": "^2.1.0" 51 | }, 52 | "devDependencies": { 53 | "typescript": "^5.8.3" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /docs/src/content/docs/qql-reference/03-data-types.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Data Types 3 | description: Learn about the different data types in Cruncher Query Language (QQL). 4 | --- 5 | 6 | Cruncher QQL allows you to manipulate and analyze fields that are extracted by the underlying data adapters. Each field is assigned a type by the adapter, which can be one of the following: 7 | 8 | - **NumberField**: Numeric values (integers or floats) 9 | - **StringField**: Text values 10 | - **DateField**: Dates as numeric timestamps (milliseconds since epoch) 11 | - **BooleanField**: True/false values 12 | - **ArrayField**: Arrays of fields (can be mixed types) 13 | - **ObjectField**: Objects with named fields 14 | - **NullField**: Represents a missing or undefined value 15 | 16 | When you query data, the adapter extracts fields and assigns them types. Cruncher then allows you to manipulate, transform, and analyze these fields using QQL commands. 17 | 18 | If a value is missing or cannot be typed, it is treated as `null` or `undefined`. 19 | 20 | ## Example: Using Field Types in Queries 21 | 22 | When you use QQL commands, fields are already typed by the adapter. For example: 23 | 24 | ``` 25 | eval duration_sec = duration / 1000 26 | ``` 27 | 28 | If `duration` is a number, `duration_sec` will also be a number field. If a field is missing, it will be `null` or `undefined`. 29 | 30 | --- 31 | 32 | Understanding field types helps you write more precise queries and interpret your results with confidence. 33 | -------------------------------------------------------------------------------- /packages/adapters/grafana-loki-browser/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.2.5 (2025-06-28) 2 | 3 | ### 🩹 Fixes 4 | 5 | - Support loki adapter ([#16](https://github.com/IamShobe/cruncher/pull/16)) 6 | - fixes to allow better reslience ([#16](https://github.com/IamShobe/cruncher/pull/16)) 7 | 8 | ### 🧱 Updated Dependencies 9 | 10 | - Updated @cruncher/adapter-loki to 0.1.0 11 | 12 | ## 0.2.4 (2025-06-21) 13 | 14 | ### 🩹 Fixes 15 | 16 | - created new standard for adapters - and new codegen for it ([#15](https://github.com/IamShobe/cruncher/pull/15)) 17 | 18 | ### 🧱 Updated Dependencies 19 | 20 | - Updated @cruncher/adapters-utils to 0.2.4 21 | - Updated @cruncher/qql to 0.2.4 22 | 23 | ## 0.2.3 (2025-06-21) 24 | 25 | ### 🧱 Updated Dependencies 26 | 27 | - Updated @cruncher/adapters-utils to 0.2.3 28 | 29 | ## 0.2.2 (2025-06-21) 30 | 31 | ### 🩹 Fixes 32 | 33 | - Update ci stuff ([#13](https://github.com/IamShobe/cruncher/pull/13)) 34 | 35 | ### 🧱 Updated Dependencies 36 | 37 | - Updated @cruncher/adapters-utils to 0.2.2 38 | - Updated @cruncher/qql to 0.2.3 39 | 40 | ## 0.2.1 (2025-06-21) 41 | 42 | ### 🧱 Updated Dependencies 43 | 44 | - Updated @cruncher/adapters-utils to 0.2.1 45 | - Updated @cruncher/qql to 0.2.2 46 | 47 | ## 0.2.0 (2025-06-21) 48 | 49 | ### 🚀 Features 50 | 51 | - nx initial commit ([883376b](https://github.com/IamShobe/cruncher/commit/883376b)) 52 | 53 | ### 🧱 Updated Dependencies 54 | 55 | - Updated @cruncher/adapter-utils to 0.2.0 56 | - Updated @cruncher/qql to 0.2.0 -------------------------------------------------------------------------------- /apps/cruncher/src/processes/frontend/components/ui/tooltip.tsx: -------------------------------------------------------------------------------- 1 | import { Tooltip as ChakraTooltip, Portal } from "@chakra-ui/react"; 2 | import * as React from "react"; 3 | 4 | export interface TooltipProps extends ChakraTooltip.RootProps { 5 | showArrow?: boolean; 6 | portalled?: boolean; 7 | portalRef?: React.RefObject; 8 | content: React.ReactNode; 9 | contentProps?: ChakraTooltip.ContentProps; 10 | disabled?: boolean; 11 | } 12 | 13 | export const Tooltip = React.forwardRef( 14 | function Tooltip(props, ref) { 15 | const { 16 | showArrow, 17 | children, 18 | disabled, 19 | portalled, 20 | content, 21 | contentProps, 22 | portalRef, 23 | ...rest 24 | } = props; 25 | 26 | if (disabled) return children; 27 | 28 | return ( 29 | 30 | {children} 31 | 32 | 33 | 34 | {showArrow && ( 35 | 36 | 37 | 38 | )} 39 | {content} 40 | 41 | 42 | 43 | 44 | ); 45 | }, 46 | ); 47 | -------------------------------------------------------------------------------- /apps/cruncher/src/processes/frontend/features/searcher/TabsLineButtons.tsx: -------------------------------------------------------------------------------- 1 | import { Highlighter, HighlighterRef } from "~features/searcher/Highlighter"; 2 | import { MiniIconButton } from "~components/presets/IconButton"; 3 | import { searcherShortcuts, useShortcuts } from "~core/keymaps"; 4 | import { LuPanelTopClose, LuPanelTopOpen } from "react-icons/lu"; 5 | import { Stack } from "@chakra-ui/react"; 6 | import { useAtom } from "jotai/index"; 7 | import { isHeaderOpenAtom } from "~core/search"; 8 | import { useRef } from "react"; 9 | 10 | export const TabsLineButtons = () => { 11 | const [isHeaderOpen, setIsHeaderOpen] = useAtom(isHeaderOpenAtom); 12 | const highlightBoxRef = useRef(null); 13 | const toggleHeader = () => { 14 | setIsHeaderOpen((prev) => !prev); 15 | }; 16 | 17 | useShortcuts(searcherShortcuts, (shortcut) => { 18 | switch (shortcut) { 19 | case "highlight": 20 | highlightBoxRef.current?.focus(); 21 | break; 22 | case "toggle-header": 23 | toggleHeader(); 24 | break; 25 | } 26 | }); 27 | return ( 28 | 29 | 30 | 35 | {isHeaderOpen ? : } 36 | 37 | 38 | ); 39 | }; 40 | -------------------------------------------------------------------------------- /apps/cruncher/src/processes/frontend/components/ui/toaster.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { 4 | Toaster as ChakraToaster, 5 | Portal, 6 | Spinner, 7 | Stack, 8 | Toast, 9 | createToaster, 10 | } from "@chakra-ui/react"; 11 | import { css } from "@emotion/react"; 12 | 13 | export const toaster = createToaster({ 14 | placement: "bottom-end", 15 | pauseOnPageIdle: true, 16 | }); 17 | 18 | export const Toaster = () => { 19 | return ( 20 | 21 | 22 | {(toast) => ( 23 | 24 | {toast.type === "loading" ? ( 25 | 26 | ) : ( 27 | 28 | )} 29 | 30 | {toast.title && {toast.title}} 31 | {toast.description && ( 32 | 38 | {toast.description} 39 | 40 | )} 41 | 42 | {toast.action && ( 43 | {toast.action.label} 44 | )} 45 | {toast.closable && } 46 | 47 | )} 48 | 49 | 50 | ); 51 | }; 52 | -------------------------------------------------------------------------------- /packages/adapters/docker/src/index.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod/v4"; 2 | import { Adapter, newPluginRef, QueryProvider } from "@cruncher/adapter-utils"; 3 | import { DockerController } from "./controller"; 4 | 5 | const paramsSchema = z.object({ 6 | binaryLocation: z.string().default("docker"), 7 | dockerHost: z.string().default("unix:///var/run/docker.sock"), 8 | containerFilter: z.string().optional(), 9 | streams: z.array(z.enum(["stdout", "stderr"])).default(["stdout", "stderr"]), 10 | containerOverride: z 11 | .record( 12 | z.string(), 13 | z.object({ 14 | messageFieldName: z.string().optional(), 15 | }), 16 | ) 17 | .optional(), 18 | logPatterns: z 19 | .array( 20 | z.object({ 21 | name: z.string(), 22 | pattern: z.string(), 23 | applyTo: z.array(z.string()).default([]), 24 | exclude: z.array(z.string()).default([]), 25 | applyToAll: z.boolean().default(false), 26 | messageFieldName: z.string().optional(), 27 | }), 28 | ) 29 | .default([]), 30 | }); 31 | 32 | export type DockerParams = z.infer; 33 | export type DockerLogPatterns = z.infer; 34 | 35 | const adapter: Adapter = { 36 | ref: newPluginRef("docker"), 37 | name: "Docker Logs", 38 | description: "Adapter for Docker container logs", 39 | version: "0.1.0", 40 | params: paramsSchema, 41 | factory: (_context, { params }): QueryProvider => { 42 | const parsedParams = paramsSchema.parse(params); 43 | 44 | return new DockerController(parsedParams); 45 | }, 46 | }; 47 | 48 | export default adapter; 49 | -------------------------------------------------------------------------------- /apps/cruncher/src/processes/server/lib/zip.ts: -------------------------------------------------------------------------------- 1 | import yauzl from "yauzl"; 2 | import fs from "node:fs"; 3 | import path from "node:path"; 4 | 5 | export async function extractZip(zipPath: string, outDir: string) { 6 | return new Promise((resolve, reject) => { 7 | yauzl.open(zipPath, { lazyEntries: true }, (err, zipfile) => { 8 | if (err) { 9 | reject(new Error(`Failed to open zip file: ${err.message}`)); 10 | return; 11 | } 12 | 13 | zipfile.readEntry(); 14 | 15 | zipfile.on("entry", (entry) => { 16 | const dest = path.join(outDir, entry.fileName); 17 | 18 | if (/\/$/.test(entry.fileName)) { 19 | // Directory entry — create it 20 | fs.mkdir(dest, { recursive: true }, (err) => { 21 | if (err) throw err; 22 | zipfile.readEntry(); 23 | }); 24 | } else { 25 | // File entry — ensure parent exists, then pipe 26 | fs.mkdir(path.dirname(dest), { recursive: true }, (err) => { 27 | if (err) throw err; 28 | 29 | zipfile.openReadStream(entry, (err, rs) => { 30 | if (err) throw err; 31 | rs.pipe(fs.createWriteStream(dest)).on("finish", () => 32 | zipfile.readEntry(), 33 | ); 34 | }); 35 | }); 36 | } 37 | }); 38 | 39 | zipfile.on("error", (err) => { 40 | reject(new Error(`Error during zip extraction: ${err.message}`)); 41 | }); 42 | 43 | zipfile.on("end", () => { 44 | console.log("Extraction complete"); 45 | resolve(); 46 | }); 47 | }); 48 | }); 49 | } 50 | -------------------------------------------------------------------------------- /apps/cruncher/src/processes/server/lib/pipelineEngine/regex.ts: -------------------------------------------------------------------------------- 1 | import { DisplayResults } from "~lib/displayTypes"; 2 | import { 3 | asDisplayString, 4 | asStringFieldOrUndefined, 5 | ProcessedData, 6 | } from "@cruncher/adapter-utils/logTypes"; 7 | 8 | export const processRegex = ( 9 | data: DisplayResults, 10 | regex: RegExp, 11 | column: string | undefined, 12 | ): DisplayResults => { 13 | const { events, table } = data; 14 | const dataPoints = table ? table.dataPoints : events.data; 15 | 16 | const resultDataPoints: ProcessedData[] = []; 17 | // if column is not defined, search in json stringified object 18 | const searchInObject = column === undefined; 19 | 20 | const addedColumns = new Set(); 21 | 22 | console.log(regex, column, searchInObject); 23 | 24 | dataPoints.forEach((dataPoint) => { 25 | const term = searchInObject 26 | ? (asStringFieldOrUndefined(dataPoint.object._raw)?.value ?? 27 | dataPoint.message) 28 | : asDisplayString(dataPoint.object[column]); 29 | const match = regex.exec(term); 30 | 31 | if (match) { 32 | // iterate over all groups - and set them in the object 33 | Object.entries(match.groups ?? {}).forEach(([key, value]) => { 34 | addedColumns.add(key); 35 | dataPoint.object[key] = { 36 | type: "string", 37 | value, 38 | }; 39 | }); 40 | } 41 | 42 | resultDataPoints.push(dataPoint); 43 | }); 44 | 45 | return { 46 | events, 47 | table: table && { 48 | type: "table", 49 | columns: [...table.columns, ...addedColumns], 50 | dataPoints: resultDataPoints, 51 | }, 52 | view: undefined, 53 | }; 54 | }; 55 | -------------------------------------------------------------------------------- /.github/workflows/checks.yml: -------------------------------------------------------------------------------- 1 | name: "PR Check" 2 | 3 | on: 4 | pull_request: 5 | 6 | jobs: 7 | test: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout full repo with history and tags 11 | uses: actions/checkout@v4 12 | with: 13 | fetch-depth: 0 14 | 15 | - uses: pnpm/action-setup@v4 16 | - name: Install dependencies 17 | run: pnpm install --frozen-lockfile 18 | 19 | - name: Run tests 20 | run: pnpm test 21 | 22 | format-check: 23 | runs-on: ubuntu-latest 24 | steps: 25 | - name: Checkout full repo with history and tags 26 | uses: actions/checkout@v4 27 | with: 28 | fetch-depth: 0 29 | 30 | - uses: pnpm/action-setup@v4 31 | - name: Install dependencies 32 | run: pnpm install --frozen-lockfile 33 | 34 | - name: Run format check 35 | run: pnpm format:check 36 | 37 | plan_check: 38 | runs-on: ubuntu-latest 39 | steps: 40 | - name: Checkout full repo with history and tags 41 | uses: actions/checkout@v4 42 | with: 43 | fetch-depth: 0 44 | 45 | - uses: pnpm/action-setup@v4 46 | - name: Install dependencies 47 | run: pnpm install --frozen-lockfile 48 | 49 | - name: Configure SHAs for Nx 50 | uses: nrwl/nx-set-shas@v4 51 | 52 | - name: Run `nx release plan:check` 53 | run: | 54 | npx nx release plan:check --base=origin/main --head=HEAD 55 | 56 | - name: Fail if plan check fails 57 | if: failure() 58 | run: | 59 | echo "Plan check failed. Please make sure to run `pnpm nx release plan` before pushing your changes." 60 | exit 1 61 | -------------------------------------------------------------------------------- /docs/src/content/docs/qql-reference/commands/timechart.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: timechart 3 | description: Create time series visualizations with the `timechart` command in QQL. 4 | --- 5 | 6 | import { Badge } from '@astrojs/starlight/components'; 7 | 8 | 9 | 10 | The `timechart` command creates a time series visualization by aggregating data into time buckets. It supports multiple aggregation functions, grouping, and several parameters for customizing the time axis and grouping behavior. 11 | 12 | ## Syntax 13 | 14 | ``` 15 | timechart [span=interval] [timeCol=field] [maxGroups=N] [agg(field) [as alias], ...] [by groupField1, ...] 16 | ``` 17 | 18 | - `span=interval`: (Optional) Bucket size for the time axis (e.g., `1m`, `5m`, `1h`). 19 | - `timeCol=field`: (Optional) The field to use as the time axis (default is usually `timestamp`). 20 | - `maxGroups=N`: (Optional) Maximum number of groups to display (limits cardinality). 21 | - `agg(field) [as alias]`: Aggregation function (e.g., `count()`, `sum(field)`, `avg(field)`) optionally renamed with `as`. 22 | - `by groupField1, ...`: (Optional) Group results by one or more fields. 23 | 24 | ## Usage 25 | 26 | - Use `timechart` to visualize trends over time, with flexible grouping and aggregation. 27 | - Supports multiple aggregations, grouping, and time axis customization. 28 | - All parameters are optional except for at least one aggregation. 29 | 30 | ## Example 31 | 32 | ``` 33 | timechart span=5m timeCol=event_time maxGroups=10 count() as total, avg(duration) as avg_duration by status 34 | ``` 35 | 36 | This creates a time series of event counts and average duration per `status` in 5-minute intervals, using `event_time` as the time axis, and limits to 10 groups. 37 | -------------------------------------------------------------------------------- /apps/cruncher/src/processes/frontend/components/ui/provider.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { ChakraProvider, EnvironmentProvider } from "@chakra-ui/react"; 4 | import createCache from "@emotion/cache"; 5 | import { CacheProvider, css } from "@emotion/react"; 6 | import { ThemeProvider, type ThemeProviderProps } from "next-themes"; 7 | import { useEffect, useState } from "react"; 8 | import root from "react-shadow/emotion"; 9 | import { system } from "./system"; 10 | import datepickerStyle from "react-day-picker/style.css?inline"; 11 | 12 | export function Provider(props: ThemeProviderProps) { 13 | const [shadow, setShadow] = useState(null); 14 | const [cache, setCache] = useState | null>( 15 | null, 16 | ); 17 | 18 | useEffect(() => { 19 | if (!shadow?.shadowRoot || cache) return; 20 | 21 | const emotionCache = createCache({ 22 | key: "root", 23 | container: shadow.shadowRoot, 24 | }); 25 | setCache(emotionCache); 26 | }, [shadow, cache]); 27 | 28 | return ( 29 | 40 | {shadow && cache && ( 41 | shadow.shadowRoot ?? document}> 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | )} 50 | 51 | ); 52 | } 53 | -------------------------------------------------------------------------------- /docs/src/content/docs/adapters/coralogix.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Coralogix 3 | description: How to use Cruncher with Coralogix. 4 | sidebar: 5 | badge: 6 | text: New 7 | variant: tip 8 | --- 9 | import ParamItem from '../../../components/ParamItem.astro'; 10 | 11 | import { Badge } from '@astrojs/starlight/components'; 12 | 13 | 14 | 15 | ## Params 16 | 17 | 18 | The base API URL for Coralogix queries. 19 | 20 | 21 | The URL of your Coralogix dashboard (e.g., https://app.coralogix.com). 22 | 23 | 24 | The Coralogix region identifier (e.g., usprod1). 25 | 26 | 27 | ## Examples 28 | 29 | ### Minimal Configuration 30 | 31 | ```yaml 32 | connectors: 33 | - type: coralogix 34 | name: my_coralogix 35 | params: 36 | dashboard_url: "https://app.coralogix.com" 37 | ``` 38 | 39 | ### Full Configuration 40 | 41 | ```yaml 42 | connectors: 43 | - type: coralogix 44 | name: my_coralogix 45 | params: 46 | api_url: "https://api.coralogix.us/api/v1" 47 | dashboard_url: "https://app.coralogix.com" 48 | region: "usprod1" 49 | ``` 50 | 51 | ## Usage Notes 52 | 53 | - When you run a query using this adapter, Cruncher will automatically open a login prompt in your browser if you are not already authenticated with Coralogix. 54 | - The `api_url` and `region` parameters have sensible defaults for most users; you usually only need to set `dashboard_url`. 55 | 56 | --- 57 | 58 | For more information on how to obtain your Coralogix dashboard URL and region, see the Coralogix documentation or ask your Coralogix administrator. 59 | -------------------------------------------------------------------------------- /docs/src/content/docs/qql-reference/commands/stats.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: stats 3 | description: Perform aggregations over your data using the stats command in QQL. 4 | --- 5 | 6 | import { Badge } from '@astrojs/starlight/components'; 7 | 8 | 9 | 10 | The `stats` command performs aggregations (such as count, sum, avg, min, max, first, last) over your data, optionally grouped by one or more fields. 11 | 12 | ## Syntax 13 | 14 | ``` 15 | stats [agg1(field1) as alias1, ...] [by groupField1, groupField2, ...] 16 | ``` 17 | 18 | - `agg(field) as alias`: Aggregation function (see below) applied to a field, optionally renamed with `as`. 19 | - `by groupField1, ...`: (Optional) Group results by one or more fields. 20 | 21 | ## Available Aggregation Functions 22 | 23 | - `count()`: Counts the number of records in each group (or overall if no grouping). 24 | - `sum(field)`: Sums the values of `field` for each group. 25 | - `avg(field)`: Calculates the average value of `field` for each group. 26 | - `min(field)`: Finds the minimum value of `field` for each group. 27 | - `max(field)`: Finds the maximum value of `field` for each group. 28 | - `first(field)`: Returns the first non-null value of `field` in each group. 29 | - `last(field)`: Returns the last non-null value of `field` in each group. 30 | 31 | > You can use multiple aggregation functions in a single `stats` command, and each can be given an alias using `as`. 32 | 33 | ## Usage 34 | 35 | - Use `stats` to summarize data, such as counting events or calculating averages. 36 | - Multiple aggregations and groupings are supported. 37 | 38 | ## Example 39 | 40 | ``` 41 | stats count() as total, avg(duration) as avg_duration, first(user) as first_user by status 42 | ``` 43 | 44 | This counts all records, calculates the average `duration`, and returns the first `user` for each `status` group. -------------------------------------------------------------------------------- /apps/cruncher/src/processes/frontend/components/ui/input-group.tsx: -------------------------------------------------------------------------------- 1 | import type { BoxProps, InputElementProps } from "@chakra-ui/react"; 2 | import { Group, InputElement } from "@chakra-ui/react"; 3 | import * as React from "react"; 4 | 5 | export interface InputGroupProps extends BoxProps { 6 | startElementProps?: InputElementProps; 7 | endElementProps?: InputElementProps; 8 | startElement?: React.ReactNode; 9 | endElement?: React.ReactNode; 10 | children: React.ReactElement; 11 | startOffset?: InputElementProps["paddingStart"]; 12 | endOffset?: InputElementProps["paddingEnd"]; 13 | } 14 | 15 | export const InputGroup = React.forwardRef( 16 | function InputGroup(props, ref) { 17 | const { 18 | startElement, 19 | startElementProps, 20 | endElement, 21 | endElementProps, 22 | children, 23 | startOffset = "6px", 24 | endOffset = "6px", 25 | ...rest 26 | } = props; 27 | 28 | return ( 29 | // @ts-expect-error - lib component 30 | 31 | {startElement && ( 32 | // @ts-expect-error - lib component 33 | 34 | {startElement} 35 | 36 | )} 37 | {React.cloneElement(children, { 38 | ...(startElement && { 39 | ps: `calc(var(--input-height) - ${startOffset})`, 40 | }), 41 | ...(endElement && { pe: `calc(var(--input-height) - ${endOffset})` }), 42 | ...children.props, 43 | })} 44 | {endElement && ( 45 | // @ts-expect-error - lib component 46 | 47 | {endElement} 48 | 49 | )} 50 | 51 | ); 52 | }, 53 | ); 54 | -------------------------------------------------------------------------------- /apps/cruncher/src/processes/frontend/features/searcher/Highlighter.tsx: -------------------------------------------------------------------------------- 1 | import { Input, InputGroup } from "@chakra-ui/react"; 2 | import { useAtom } from "jotai"; 3 | import { forwardRef, useImperativeHandle, useRef } from "react"; 4 | import { LuHighlighter } from "react-icons/lu"; 5 | import { highlightItemQueryAtom } from "~core/search"; 6 | import { Shortcut } from "~components/ui/shortcut"; 7 | import { searcherShortcuts } from "~core/keymaps"; 8 | 9 | export type HighlighterRef = { 10 | focus: () => void; 11 | blur: () => void; 12 | }; 13 | 14 | export const Highlighter = forwardRef((_props, ref) => { 15 | const [highlightItemQuery, setHighlightItemQuery] = useAtom( 16 | highlightItemQueryAtom, 17 | ); 18 | 19 | const inputRef = useRef(null); 20 | 21 | useImperativeHandle(ref, () => ({ 22 | focus: () => { 23 | if (inputRef.current) { 24 | inputRef.current.focus(); 25 | } 26 | }, 27 | blur: blurInput, 28 | })); 29 | 30 | const blurInput = () => { 31 | if (inputRef.current) { 32 | inputRef.current.blur(); 33 | } 34 | }; 35 | 36 | return ( 37 | } 40 | endElement={} 41 | > 42 | { 49 | if (e.key === "Escape") { 50 | setHighlightItemQuery(""); 51 | blurInput(); 52 | } else if (e.key === "Enter") { 53 | blurInput(); 54 | } 55 | }} 56 | onChange={(e) => setHighlightItemQuery(e.target.value)} 57 | /> 58 | 59 | ); 60 | }); 61 | -------------------------------------------------------------------------------- /apps/cruncher/src/processes/frontend/routes/settings/route.tsx: -------------------------------------------------------------------------------- 1 | import { Box, Heading, Stack } from "@chakra-ui/react"; 2 | import { css } from "@emotion/react"; 3 | import { createFileRoute, Outlet, Link } from "@tanstack/react-router"; 4 | 5 | export const Route = createFileRoute("/settings")({ 6 | component: Settings, 7 | }); 8 | 9 | function Settings() { 10 | return ( 11 | 19 | Settings 20 | 21 | 22 | {({ isActive }) => ( 23 | 24 | )} 25 | 26 | 27 | {({ isActive }) => ( 28 | 29 | )} 30 | 31 | 32 | 33 | 34 | 35 | {/* 36 | */} 37 | 38 | ); 39 | } 40 | 41 | const SettingsTab: React.FC<{ 42 | isActive: boolean; 43 | label: string; 44 | }> = ({ isActive, label }) => { 45 | return ( 46 | 54 | 61 | {label} 62 | 63 | {/* Add your settings tab content here */} 64 | 65 | ); 66 | }; 67 | -------------------------------------------------------------------------------- /packages/qql/src/index.ts: -------------------------------------------------------------------------------- 1 | import type { ILexingError, IRecognitionException } from "chevrotain"; 2 | import { QQLLexer, QQLParser } from "./grammar"; 3 | 4 | export class QQLLexingError extends Error { 5 | constructor( 6 | message: string, 7 | public errors: ILexingError[], 8 | ) { 9 | super(message); 10 | } 11 | } 12 | 13 | export class QQLParserError extends Error { 14 | constructor( 15 | message: string, 16 | public errors: IRecognitionException[], 17 | ) { 18 | super(message); 19 | } 20 | } 21 | 22 | export const allData = (input: string) => { 23 | const lexer = QQLLexer.tokenize(input); 24 | 25 | const parser = new QQLParser(); 26 | parser.input = lexer.tokens; 27 | const response = parser.query(); 28 | 29 | lexer.groups["singleLineComments"].forEach((comment) => { 30 | parser.highlightComment(comment); 31 | }); 32 | 33 | return { 34 | ast: response, 35 | highlight: parser.getHighlightData(), 36 | suggestions: parser.getSuggestionData(), 37 | parserError: parser.errors, 38 | }; 39 | }; 40 | 41 | export const parse = (input: string) => { 42 | const lexer = QQLLexer.tokenize(input); 43 | if (lexer.errors.length > 0) { 44 | throw new QQLLexingError("Failed to tokenize input", lexer.errors); 45 | } 46 | 47 | const parser = new QQLParser(); 48 | parser.input = lexer.tokens; 49 | const response = parser.query(); 50 | if (parser.errors.length > 0) { 51 | throw new QQLParserError("Failed to parse input", parser.errors); 52 | } 53 | 54 | return response; 55 | }; 56 | 57 | export type ParsedQuery = ReturnType; 58 | export type PipelineItem = ParsedQuery["pipeline"][number]; 59 | 60 | export type PipelineItemType = PipelineItem["type"]; 61 | 62 | export type NarrowedPipelineItem = Extract< 63 | PipelineItem, 64 | { type: T } 65 | >; 66 | 67 | export * from "./searchTree"; 68 | -------------------------------------------------------------------------------- /docs/src/content/docs/qql-reference/04-logical-expressions.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Logical Expressions 3 | sidebar_label: logicalExpression 4 | --- 5 | 6 | Logical expressions are used in QQL to combine and evaluate boolean conditions, typically in commands like `where` and `eval`. They support logical operators such as `AND`, `OR`, and `NOT`, as well as grouping with parentheses. 7 | 8 | ## Description 9 | 10 | A logical expression allows you to combine multiple boolean conditions using logical operators. This enables you to build complex filters and computed fields based on your data. 11 | 12 | Supported logical operators: 13 | - `&&` (AND): Both conditions must be true. 14 | - `||` (OR): At least one condition must be true. 15 | - `!` (NOT): Negates a condition. 16 | - Parentheses `()` can be used to group expressions and control precedence. 17 | - Compare operators like `==`, `!=`, `<`, `>`, `<=`, `>=` can be used within logical expressions. 18 | 19 | Logical expressions can be nested and combined with comparison operators and functions. 20 | 21 | ## Syntax 22 | 23 | ``` 24 | where logical_expression 25 | ``` 26 | 27 | A logical expression can be: 28 | - A comparison: `field == value` 29 | - A function: `isNull(field)` 30 | - A combination: `(field1 == "foo" && field2 > 10) || isNull(field3)` 31 | 32 | ## Usage Examples 33 | 34 | **Simple AND:** 35 | ``` 36 | where status == "error" && duration > 1000 37 | ``` 38 | 39 | **Simple OR:** 40 | ``` 41 | where status == "error" || status == "warn" 42 | ``` 43 | 44 | **Negation:** 45 | ``` 46 | where !(level == "info") 47 | ``` 48 | 49 | **Grouping:** 50 | ``` 51 | where (status == "error" || status == "warn") && duration > 1000 52 | ``` 53 | 54 | **With functions:** 55 | ``` 56 | where isNull(optional_field) || contains(message, "timeout") 57 | ``` 58 | 59 | Logical expressions are parsed and evaluated according to standard boolean logic, with parentheses controlling precedence. 60 | -------------------------------------------------------------------------------- /apps/cruncher/src/processes/server/lib/pipelineEngine/root.ts: -------------------------------------------------------------------------------- 1 | import { DisplayResults } from "~lib/displayTypes"; 2 | import { 3 | NarrowedPipelineItem, 4 | PipelineItem, 5 | PipelineItemType, 6 | } from "@cruncher/qql"; 7 | 8 | export type PipelineContext = { 9 | startTime: Date; 10 | endTime: Date; 11 | }; 12 | 13 | export type PipelineItemProcessor = { 14 | [key in PipelineItemType]: ( 15 | context: PipelineContext, 16 | currentData: DisplayResults, 17 | options: NarrowedPipelineItem, 18 | ) => DisplayResults; 19 | }; 20 | 21 | export const processPipelineV2 = ( 22 | processor: PipelineItemProcessor, 23 | currentData: DisplayResults, 24 | pipeline: PipelineItem[], 25 | context: PipelineContext, 26 | ) => { 27 | const innerProcessPipeline = ( 28 | currentData: DisplayResults, 29 | currentIndex: number, 30 | ): DisplayResults => { 31 | if (currentIndex >= pipeline.length) { 32 | return currentData; 33 | } 34 | 35 | const currentPipeline = pipeline[currentIndex]; 36 | if (!currentPipeline) { 37 | throw new Error(`Pipeline item at index ${currentIndex} is undefined`); 38 | } 39 | 40 | const processedData = processPipelineType( 41 | processor, 42 | context, 43 | currentData, 44 | currentPipeline, 45 | ); 46 | 47 | return innerProcessPipeline(processedData, currentIndex + 1); 48 | }; 49 | 50 | return innerProcessPipeline(currentData, 0); 51 | }; 52 | 53 | const processPipelineType = ( 54 | processor: PipelineItemProcessor, 55 | context: PipelineContext, 56 | currentData: DisplayResults, 57 | params: NarrowedPipelineItem, 58 | ) => { 59 | const processorFn = processor[params.type]; 60 | if (!processorFn) { 61 | throw new Error( 62 | `Processor for pipeline item type '${params.type}' not found`, 63 | ); 64 | } 65 | 66 | return processorFn(context, currentData, params); 67 | }; 68 | -------------------------------------------------------------------------------- /apps/cruncher/src/processes/server/lib/pipelineEngine/unpack.ts: -------------------------------------------------------------------------------- 1 | import { processField } from "@cruncher/adapter-utils/logTypes"; 2 | import { DisplayResults } from "~lib/displayTypes"; 3 | 4 | export const processUnpack = ( 5 | data: DisplayResults, 6 | columns: string[], 7 | ): DisplayResults => { 8 | const { events, table, view } = data; 9 | const dataPoints = table ? table.dataPoints : events.data; 10 | // Process each column 11 | columns.forEach((column) => { 12 | // try parse json 13 | dataPoints.forEach((dp) => { 14 | try { 15 | const addObjectFields = (obj: Record) => { 16 | Object.entries(obj).forEach(([key, value]) => { 17 | const newKey = `${column}.${key}`; 18 | try { 19 | dp.object[newKey] = processField(value); 20 | } catch (e) { 21 | // If processing fails, log the error and skip this field 22 | } 23 | }); 24 | }; 25 | const columnField = dp.object[column]; 26 | switch (columnField?.type) { 27 | case "object": 28 | addObjectFields(columnField.raw); 29 | break; 30 | case "string": 31 | try { 32 | const payload = JSON.parse(columnField.value); 33 | addObjectFields(payload); 34 | } catch (e) { 35 | throw new Error( 36 | `Column "${column}" is a string but could not be parsed as JSON.`, 37 | ); 38 | } 39 | break; 40 | default: 41 | throw new Error( 42 | `Column "${column}" has an unsupported type: ${columnField?.type}.`, 43 | ); 44 | } 45 | } catch (e) { 46 | console.error(`Error processing column "${column}":`, e); 47 | return {}; 48 | } 49 | }); 50 | }); 51 | 52 | return { 53 | events, 54 | table, 55 | view, 56 | }; 57 | }; 58 | -------------------------------------------------------------------------------- /docs/src/content/docs/qql-reference/commands/unpack.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: unpack 3 | description: Extracts and flattens fields from nested objects or JSON strings in a column, creating new columns for each key. 4 | --- 5 | 6 | The `unpack` command extracts and flattens fields from nested objects or JSON strings in the specified columns. For each key in the object or parsed JSON, a new column is created with the name `.`. This is useful for working with logs or data where fields are embedded as JSON or objects inside a single column. 7 | 8 | ## Syntax 9 | 10 | ``` 11 | unpack [, , ...] 12 | ``` 13 | 14 | - ``: The name of the column to unpack. You can specify multiple columns, separated by commas. 15 | 16 | ## Usage 17 | 18 | - Use `unpack` to flatten nested objects or JSON strings in your data, making each key accessible as its own column. 19 | - If the column contains a JSON string, it will be parsed and its keys unpacked. 20 | - If the column is already an object, its keys will be unpacked directly. 21 | - If the column is not an object or a valid JSON string, an error will be logged and the row will be skipped for that column. 22 | 23 | ## Examples 24 | 25 | Unpack a JSON string in the `payload` column: 26 | 27 | ``` 28 | ... | unpack payload 29 | ``` 30 | 31 | If `payload` contains: 32 | 33 | ```json 34 | {"user":"alice","ip":"1.2.3.4"} 35 | ``` 36 | 37 | The result will have new columns: `payload.user` and `payload.ip`. 38 | 39 | Unpack multiple columns: 40 | 41 | ``` 42 | ... | unpack payload, details 43 | ``` 44 | 45 | If `details` contains: 46 | 47 | ```json 48 | {"os":"linux","version":"1.0"} 49 | ``` 50 | 51 | The result will have: `payload.user`, `payload.ip`, `details.os`, `details.version`. 52 | 53 | Unpack an object column: 54 | 55 | ``` 56 | ... | unpack metadata 57 | ``` 58 | 59 | If `metadata` is already an object like: 60 | 61 | ```json 62 | {"env":"prod","region":"us-east"} 63 | ``` 64 | 65 | The result will have: `metadata.env`, `metadata.region`. 66 | -------------------------------------------------------------------------------- /docs/astro.config.mjs: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | import { defineConfig } from "astro/config"; 3 | import starlight from "@astrojs/starlight"; 4 | 5 | // https://astro.build/config 6 | export default defineConfig({ 7 | site: "https://cruncher.iamshobe.com", 8 | integrations: [ 9 | starlight({ 10 | title: "Cruncher", 11 | logo: { 12 | light: "/src/assets/cruncher_full_logo.png", 13 | dark: "/src/assets/cruncher_full_logo.png", 14 | replacesTitle: true, 15 | }, 16 | social: { 17 | github: "https://github.com/IamShobe/cruncher", 18 | }, 19 | favicon: "./src/assets/favicon.ico", 20 | customCss: ["./src/assets/landing.css"], 21 | sidebar: [ 22 | { 23 | label: "Getting Started", 24 | autogenerate: { directory: "getting-started" }, 25 | }, 26 | { 27 | label: "QQL Reference", 28 | items: [ 29 | { 30 | label: "QQL (Quick Query Language)", 31 | link: "/qql-reference/01-qql", 32 | }, 33 | { 34 | label: "Query", 35 | link: "/qql-reference/02-query", 36 | }, 37 | { 38 | label: "Data Types", 39 | link: "/qql-reference/03-data-types", 40 | }, 41 | { 42 | label: "Logical Expressions", 43 | link: "/qql-reference/04-logical-expressions", 44 | }, 45 | { 46 | label: "Commands", 47 | autogenerate: { directory: "qql-reference/commands" }, 48 | }, 49 | { 50 | label: "Functions", 51 | autogenerate: { directory: "qql-reference/functions" }, 52 | }, 53 | ], 54 | // autogenerate: { directory: "qql-reference" }, 55 | }, 56 | { 57 | label: "Adapters", 58 | autogenerate: { directory: "adapters" }, 59 | }, 60 | ], 61 | }), 62 | ], 63 | }); 64 | -------------------------------------------------------------------------------- /apps/cruncher/src/processes/frontend/components/ui/drawer.tsx: -------------------------------------------------------------------------------- 1 | import { Drawer as ChakraDrawer, Portal } from "@chakra-ui/react"; 2 | import { CloseButton } from "./close-button"; 3 | import * as React from "react"; 4 | 5 | interface DrawerContentProps extends ChakraDrawer.ContentProps { 6 | portalled?: boolean; 7 | portalRef?: React.RefObject; 8 | offset?: ChakraDrawer.ContentProps["padding"]; 9 | } 10 | 11 | export const DrawerContent = React.forwardRef< 12 | HTMLDivElement, 13 | DrawerContentProps 14 | >(function DrawerContent(props, ref) { 15 | const { children, portalled = true, portalRef, offset, ...rest } = props; 16 | return ( 17 | 18 | 19 | {/* @ts-expect-error - lib component */} 20 | 21 | {children} 22 | 23 | 24 | 25 | ); 26 | }); 27 | 28 | export const DrawerCloseTrigger = React.forwardRef< 29 | HTMLButtonElement, 30 | ChakraDrawer.CloseTriggerProps 31 | >(function DrawerCloseTrigger(props, ref) { 32 | return ( 33 | // @ts-expect-error - lib component 34 | 41 | 42 | 43 | ); 44 | }); 45 | 46 | export const DrawerTrigger = ChakraDrawer.Trigger; 47 | export const DrawerRoot = ChakraDrawer.Root; 48 | export const DrawerFooter = ChakraDrawer.Footer; 49 | export const DrawerHeader = ChakraDrawer.Header; 50 | export const DrawerBody = ChakraDrawer.Body; 51 | export const DrawerBackdrop = ChakraDrawer.Backdrop; 52 | export const DrawerDescription = ChakraDrawer.Description; 53 | export const DrawerTitle = ChakraDrawer.Title; 54 | export const DrawerActionTrigger = ChakraDrawer.ActionTrigger; 55 | -------------------------------------------------------------------------------- /apps/cruncher/src/processes/frontend/core/store/queryState.ts: -------------------------------------------------------------------------------- 1 | import { scaleLinear } from "d3-scale"; 2 | import { atom, createStore } from "jotai"; 3 | import React from "react"; 4 | import { JobBatchFinished } from "src/processes/server/engineV2/types"; 5 | import { allData } from "@cruncher/qql"; 6 | 7 | export const tabNameAtom = atom("New Search"); 8 | export const searchQueryAtom = atom(""); // search query 9 | 10 | export const queryDataAtom = atom((get) => { 11 | const searchQuery = get(searchQueryAtom); 12 | return allData(searchQuery); 13 | }); 14 | 15 | export const QuerySpecificContext = React.createContext | null>(null); 18 | 19 | export const useQuerySpecificStoreInternal = () => { 20 | const store = React.useContext(QuerySpecificContext); 21 | if (!store) { 22 | throw new Error( 23 | "useQuerySpecificStoreInternal must be used within a QuerySpecificProvider", 24 | ); 25 | } 26 | 27 | return store; 28 | }; 29 | 30 | export const lastUpdateAtom = atom(null); 31 | 32 | export const jobMetadataAtom = atom(undefined); 33 | 34 | export const availableColumnsAtom = atom((get) => { 35 | const results = get(jobMetadataAtom); 36 | if (!results) { 37 | return []; 38 | } 39 | 40 | return results.views.events.autoCompleteKeys ?? []; 41 | }); 42 | 43 | export const scaleAtom = atom((get) => { 44 | const results = get(jobMetadataAtom); 45 | const selectedStartTime = results?.scale.from; 46 | const selectedEndTime = results?.scale.to; 47 | 48 | if (!selectedStartTime || !selectedEndTime) { 49 | return; 50 | } 51 | 52 | return scaleLinear().domain([selectedStartTime, selectedEndTime]); 53 | }); 54 | 55 | export const dataBucketsAtom = atom((get) => { 56 | const results = get(jobMetadataAtom); 57 | if (!results) { 58 | return []; 59 | } 60 | 61 | return results.views.events.buckets; 62 | }); 63 | 64 | export const viewSelectedForQueryAtom = atom(false); 65 | -------------------------------------------------------------------------------- /nx.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/nx/schemas/nx-schema.json", 3 | "release": { 4 | "projects": [ 5 | "*", 6 | "!cruncher-tools", 7 | "!docs-cruncher", 8 | "!cruncher-monorepo", 9 | "@cruncher/app" 10 | ], 11 | "versionPlans": true, 12 | "projectsRelationship": "independent", 13 | "changelog": { 14 | "projectChangelogs": { 15 | "renderOptions": { 16 | "authors": false, 17 | "applyUsernameToAuthors": false 18 | } 19 | } 20 | }, 21 | "version": { 22 | "preVersionCommand": "pnpm dlx nx run-many -t build", 23 | "conventionalCommits": true 24 | } 25 | }, 26 | "targetDefaults": { 27 | "build": { 28 | "dependsOn": ["^build"] 29 | }, 30 | "package": { 31 | "outputs": ["{projectRoot}/out"], 32 | "dependsOn": ["^build"], 33 | "cache": true 34 | }, 35 | "publish": { 36 | "dependsOn": ["^build"] 37 | }, 38 | "lint": { 39 | "cache": true 40 | }, 41 | "test": { 42 | "cache": false, 43 | "dependsOn": ["^build"] 44 | }, 45 | "format": { 46 | "cache": true 47 | }, 48 | "serve": { 49 | "cache": false 50 | }, 51 | "@nx/js:tsc": { 52 | "cache": true, 53 | "dependsOn": ["^build"], 54 | "inputs": ["default", "^default"] 55 | } 56 | }, 57 | "plugins": [ 58 | { 59 | "plugin": "@nx/vite/plugin", 60 | "options": { 61 | "buildTargetName": "build", 62 | "testTargetName": "vite:test", 63 | "serveTargetName": "serve", 64 | "devTargetName": "dev", 65 | "previewTargetName": "preview", 66 | "serveStaticTargetName": "serve-static", 67 | "typecheckTargetName": "typecheck", 68 | "buildDepsTargetName": "build-deps", 69 | "watchDepsTargetName": "watch-deps" 70 | } 71 | }, 72 | { 73 | "plugin": "@nx/eslint/plugin", 74 | "options": { 75 | "targetName": "eslint:lint" 76 | } 77 | } 78 | ] 79 | } 80 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | .DS_Store 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # TypeScript cache 43 | *.tsbuildinfo 44 | 45 | # Optional npm cache directory 46 | .npm 47 | 48 | # Optional eslint cache 49 | .eslintcache 50 | 51 | # Optional REPL history 52 | .node_repl_history 53 | 54 | # Output of 'npm pack' 55 | *.tgz 56 | 57 | # Yarn Integrity file 58 | .yarn-integrity 59 | 60 | # dotenv environment variables file 61 | .env 62 | .env.test 63 | 64 | # parcel-bundler cache (https://parceljs.org/) 65 | .cache 66 | 67 | # next.js build output 68 | .next 69 | 70 | # nuxt.js build output 71 | .nuxt 72 | 73 | # vuepress build output 74 | .vuepress/dist 75 | 76 | # Serverless directories 77 | .serverless/ 78 | 79 | # FuseBox cache 80 | .fusebox/ 81 | 82 | # DynamoDB Local files 83 | .dynamodb/ 84 | 85 | # Webpack 86 | .webpack/ 87 | 88 | # Vite 89 | .vite/ 90 | 91 | # Electron-Forge 92 | out/ 93 | build/ 94 | dist/ 95 | 96 | # devbox 97 | .devbox/ 98 | 99 | 100 | cruncher.config.yaml 101 | .tanstack/ 102 | 103 | 104 | .nx/cache 105 | .nx/workspace-data 106 | .cursor/rules/nx-rules.mdc 107 | .github/instructions/nx.instructions.md 108 | 109 | .idea/ 110 | 111 | vite.config.*.timestamp* 112 | vitest.config.*.timestamp* -------------------------------------------------------------------------------- /apps/cruncher/src/processes/frontend/components/ui/popover.tsx: -------------------------------------------------------------------------------- 1 | import { Popover as ChakraPopover, Portal } from "@chakra-ui/react"; 2 | import { CloseButton } from "./close-button"; 3 | import * as React from "react"; 4 | 5 | interface PopoverContentProps extends ChakraPopover.ContentProps { 6 | portalled?: boolean; 7 | portalRef?: React.RefObject; 8 | } 9 | 10 | export const PopoverContent = React.forwardRef< 11 | HTMLDivElement, 12 | PopoverContentProps 13 | >(function PopoverContent(props, ref) { 14 | const { portalled = true, portalRef, ...rest } = props; 15 | return ( 16 | 17 | 18 | {/* @ts-expect-error - lib component */} 19 | 20 | 21 | 22 | ); 23 | }); 24 | 25 | export const PopoverArrow = React.forwardRef< 26 | HTMLDivElement, 27 | ChakraPopover.ArrowProps 28 | >(function PopoverArrow(props, ref) { 29 | return ( 30 | // @ts-expect-error - lib component 31 | 32 | 33 | 34 | ); 35 | }); 36 | 37 | export const PopoverCloseTrigger = React.forwardRef< 38 | HTMLButtonElement, 39 | ChakraPopover.CloseTriggerProps 40 | >(function PopoverCloseTrigger(props, ref) { 41 | return ( 42 | // @ts-expect-error - lib component 43 | 51 | 52 | 53 | ); 54 | }); 55 | 56 | export const PopoverTitle = ChakraPopover.Title; 57 | export const PopoverDescription = ChakraPopover.Description; 58 | export const PopoverFooter = ChakraPopover.Footer; 59 | export const PopoverHeader = ChakraPopover.Header; 60 | export const PopoverRoot = ChakraPopover.Root; 61 | export const PopoverBody = ChakraPopover.Body; 62 | export const PopoverTrigger = ChakraPopover.Trigger; 63 | -------------------------------------------------------------------------------- /apps/cruncher/src/processes/frontend/components/ui/dialog.tsx: -------------------------------------------------------------------------------- 1 | import { Dialog as ChakraDialog, Portal } from "@chakra-ui/react"; 2 | import { CloseButton } from "./close-button"; 3 | import * as React from "react"; 4 | 5 | interface DialogContentProps extends ChakraDialog.ContentProps { 6 | portalled?: boolean; 7 | portalRef?: React.RefObject; 8 | backdrop?: boolean; 9 | } 10 | 11 | export const DialogContent = React.forwardRef< 12 | HTMLDivElement, 13 | DialogContentProps 14 | >(function DialogContent(props, ref) { 15 | const { 16 | children, 17 | portalled = true, 18 | portalRef, 19 | backdrop = true, 20 | ...rest 21 | } = props; 22 | 23 | return ( 24 | 25 | {backdrop && } 26 | 27 | {/* @ts-expect-error - lib component */} 28 | 29 | {children} 30 | 31 | 32 | 33 | ); 34 | }); 35 | 36 | export const DialogCloseTrigger = React.forwardRef< 37 | HTMLButtonElement, 38 | ChakraDialog.CloseTriggerProps 39 | >(function DialogCloseTrigger(props, ref) { 40 | return ( 41 | // @ts-expect-error - lib component 42 | 49 | 50 | {props.children} 51 | 52 | 53 | ); 54 | }); 55 | 56 | export const DialogRoot = ChakraDialog.Root; 57 | export const DialogFooter = ChakraDialog.Footer; 58 | export const DialogHeader = ChakraDialog.Header; 59 | export const DialogBody = ChakraDialog.Body; 60 | export const DialogBackdrop = ChakraDialog.Backdrop; 61 | export const DialogTitle = ChakraDialog.Title; 62 | export const DialogDescription = ChakraDialog.Description; 63 | export const DialogTrigger = ChakraDialog.Trigger; 64 | export const DialogActionTrigger = ChakraDialog.ActionTrigger; 65 | -------------------------------------------------------------------------------- /apps/cruncher/src/processes/frontend/components/ui/color-mode.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import type { IconButtonProps } from "@chakra-ui/react"; 4 | import { ClientOnly, IconButton, Skeleton } from "@chakra-ui/react"; 5 | import { ThemeProvider, useTheme } from "next-themes"; 6 | import type { ThemeProviderProps } from "next-themes"; 7 | import * as React from "react"; 8 | import { LuMoon, LuSun } from "react-icons/lu"; 9 | 10 | export interface ColorModeProviderProps extends ThemeProviderProps {} 11 | 12 | export function ColorModeProvider(props: ColorModeProviderProps) { 13 | return ( 14 | 15 | ); 16 | } 17 | 18 | export function useColorMode() { 19 | const { resolvedTheme, setTheme } = useTheme(); 20 | const toggleColorMode = () => { 21 | setTheme(resolvedTheme === "light" ? "dark" : "light"); 22 | }; 23 | return { 24 | colorMode: resolvedTheme, 25 | setColorMode: setTheme, 26 | toggleColorMode, 27 | }; 28 | } 29 | 30 | export function useColorModeValue(light: T, dark: T) { 31 | const { colorMode } = useColorMode(); 32 | return colorMode === "light" ? light : dark; 33 | } 34 | 35 | export function ColorModeIcon() { 36 | const { colorMode } = useColorMode(); 37 | return colorMode === "light" ? : ; 38 | } 39 | 40 | interface ColorModeButtonProps extends Omit {} 41 | 42 | export const ColorModeButton = React.forwardRef< 43 | HTMLButtonElement, 44 | ColorModeButtonProps 45 | >(function ColorModeButton(props, ref) { 46 | const { toggleColorMode } = useColorMode(); 47 | return ( 48 | }> 49 | 63 | 64 | 65 | 66 | ); 67 | }); 68 | -------------------------------------------------------------------------------- /apps/cruncher/src/processes/frontend/components/ui/toggle-tip.tsx: -------------------------------------------------------------------------------- 1 | import { Popover as ChakraPopover, IconButton, Portal } from "@chakra-ui/react"; 2 | import * as React from "react"; 3 | import { HiOutlineInformationCircle } from "react-icons/hi"; 4 | 5 | export interface ToggleTipProps extends ChakraPopover.RootProps { 6 | showArrow?: boolean; 7 | portalled?: boolean; 8 | portalRef?: React.RefObject; 9 | content?: React.ReactNode; 10 | } 11 | 12 | export const ToggleTip = React.forwardRef( 13 | function ToggleTip(props, ref) { 14 | const { 15 | showArrow, 16 | children, 17 | portalled = true, 18 | content, 19 | portalRef, 20 | ...rest 21 | } = props; 22 | 23 | return ( 24 | 28 | {children} 29 | 30 | 31 | 39 | {showArrow && ( 40 | 41 | 42 | 43 | )} 44 | {content} 45 | 46 | 47 | 48 | 49 | ); 50 | }, 51 | ); 52 | 53 | export const InfoTip = React.forwardRef< 54 | HTMLDivElement, 55 | Partial 56 | >(function InfoTip(props, ref) { 57 | const { children, ...rest } = props; 58 | return ( 59 | 60 | 66 | 67 | 68 | 69 | ); 70 | }); 71 | -------------------------------------------------------------------------------- /packages/adapters/utils/src/index.ts: -------------------------------------------------------------------------------- 1 | import { ControllerIndexParam, Search } from "@cruncher/qql/grammar"; 2 | import { ProcessedData } from "./logTypes"; 3 | import { z } from "zod/v4"; 4 | import { Brand } from "@cruncher/utils"; 5 | 6 | export type QueryOptions = { 7 | fromTime: Date; 8 | toTime: Date; 9 | limit: number; 10 | cancelToken: AbortSignal; 11 | onBatchDone: (data: ProcessedData[]) => void; 12 | }; 13 | 14 | export interface QueryProvider { 15 | getControllerParams(): Promise>; 16 | query( 17 | params: ControllerIndexParam[], 18 | searchTerm: Search, 19 | queryOptions: QueryOptions, 20 | ): Promise; 21 | } 22 | 23 | type ObjectParam = { 24 | type: "object"; 25 | defaultValue?: object; 26 | }; 27 | type ArrayParam = { 28 | type: "array"; 29 | defaultValue?: unknown[]; 30 | }; 31 | type StringParam = { 32 | type: "string"; 33 | defaultValue?: string; 34 | }; 35 | type NumberParam = { 36 | type: "number"; 37 | defaultValue?: number; 38 | }; 39 | type BooleanParam = { 40 | type: "boolean"; 41 | defaultValue?: boolean; 42 | }; 43 | type DateParam = { 44 | type: "date"; 45 | defaultValue?: Date; 46 | }; 47 | 48 | export type Param = { 49 | name: string; 50 | description: string; 51 | } & ( 52 | | ObjectParam 53 | | ArrayParam 54 | | StringParam 55 | | NumberParam 56 | | BooleanParam 57 | | DateParam 58 | ); 59 | 60 | export type FactoryParams = { 61 | params: Record; 62 | }; 63 | 64 | export type PluginRef = Brand; // A unique identifier for a plugin 65 | 66 | export type ExternalAuthProvider = { 67 | getCookies( 68 | requestedUrl: string, 69 | cookies: string[], 70 | validate: (cookies: Record) => Promise, 71 | ): Promise>; 72 | }; 73 | 74 | export type AdapterContext = { 75 | externalAuthProvider: ExternalAuthProvider; 76 | }; 77 | 78 | export interface Adapter { 79 | name: string; 80 | ref: PluginRef; 81 | description: string; 82 | version: string; 83 | 84 | params: z.ZodObject; 85 | 86 | factory: (context: AdapterContext, params: FactoryParams) => QueryProvider; 87 | } 88 | 89 | export function newPluginRef(ref: string): PluginRef { 90 | return ref as PluginRef; 91 | } 92 | -------------------------------------------------------------------------------- /apps/cruncher/eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import { defineConfig, globalIgnores } from "eslint/config"; 2 | import { fixupConfigRules } from "@eslint/compat"; 3 | import globals from "globals"; 4 | import tsParser from "@typescript-eslint/parser"; 5 | import path from "node:path"; 6 | import { fileURLToPath } from "node:url"; 7 | import js from "@eslint/js"; 8 | import { FlatCompat } from "@eslint/eslintrc"; 9 | 10 | const __filename = fileURLToPath(import.meta.url); 11 | const __dirname = path.dirname(__filename); 12 | const compat = new FlatCompat({ 13 | baseDirectory: __dirname, 14 | recommendedConfig: js.configs.recommended, 15 | allConfig: js.configs.all, 16 | }); 17 | 18 | export default defineConfig([ 19 | { 20 | extends: fixupConfigRules( 21 | compat.extends( 22 | "eslint:recommended", 23 | "plugin:@typescript-eslint/eslint-recommended", 24 | "plugin:@typescript-eslint/recommended", 25 | "plugin:import-x/typescript" 26 | ) 27 | ), 28 | // only lint files in the src directory 29 | files: ["src/**/*.ts", "src/**/*.tsx", "src/**/*.js", "src/**/*.jsx"], 30 | 31 | settings: { 32 | "import-x/resolver": { 33 | typescript: true, 34 | node: { 35 | extensions: [".js", ".jsx", ".ts", ".tsx"], 36 | moduleDirectory: ["node_modules", "src/"], 37 | }, 38 | }, 39 | }, 40 | 41 | rules: { 42 | "import-x/default": "off", 43 | "import-x/no-named-as-default": "off", 44 | "import-x/no-named-as-default-member": "off", 45 | "@typescript-eslint/no-empty-object-type": "off", 46 | "@typescript-eslint/no-explicit-any": "warn", 47 | "no-useless-escape": "off", 48 | "no-empty-pattern": "off", 49 | "@typescript-eslint/no-unused-vars": [ 50 | "warn", 51 | { 52 | // allow prefix _ - otherwise unused variables are an error 53 | varsIgnorePattern: "^_", 54 | }, 55 | ], 56 | }, 57 | 58 | languageOptions: { 59 | globals: { 60 | ...globals.browser, 61 | ...globals.node, 62 | }, 63 | 64 | parser: tsParser, 65 | }, 66 | }, 67 | globalIgnores([ 68 | "build/**", 69 | "dist/**", 70 | "coverage/**", 71 | "node_modules/**", 72 | "out/**", 73 | ]), 74 | ]); 75 | -------------------------------------------------------------------------------- /apps/cruncher/src/processes/server/lib/dateUtils.ts: -------------------------------------------------------------------------------- 1 | import { parse, parseISO, isValid } from "date-fns"; 2 | 3 | export enum DateType { 4 | Now = "Now", 5 | } 6 | 7 | export type FullDate = Date | DateType; 8 | 9 | export const isTimeNow = ( 10 | date: Date | DateType | undefined, 11 | ): date is DateType.Now => { 12 | return date === DateType.Now; 13 | }; 14 | 15 | export const compareFullDates = (date1: FullDate, date2: FullDate) => { 16 | const date1Time = isTimeNow(date1) ? new Date() : date1; 17 | const date2Time = isTimeNow(date2) ? new Date() : date2; 18 | 19 | return Math.sign(date1Time.getTime() - date2Time.getTime()); 20 | }; 21 | 22 | /** 23 | * Tries to parse a value into a JavaScript Date using date-fns 24 | * @param {string|number|Date} input 25 | * @returns {Date|null} 26 | */ 27 | export const parseDate = (input: unknown): FullDate | null => { 28 | if (input instanceof Date && isValid(input)) { 29 | return input; 30 | } 31 | 32 | if (typeof input === "string" && input.trim() === "Now") { 33 | return DateType.Now; 34 | } 35 | 36 | // Handle epoch time (number or numeric string) 37 | if ( 38 | typeof input === "number" || 39 | (typeof input === "string" && /^\d+$/.test(input)) 40 | ) { 41 | const date = new Date(Number(input)); 42 | return isValid(date) ? date : null; 43 | } 44 | 45 | // Try ISO parsing 46 | if (typeof input === "string") { 47 | let date = parseISO(input); 48 | if (isValid(date)) return date; 49 | 50 | // Fallback to custom format (e.g. MM/dd/yyyy) 51 | const knownFormats = [ 52 | "MM/dd/yyyy", 53 | "yyyy-MM-dd", 54 | "dd-MM-yyyy", 55 | "MM-dd-yyyy", 56 | "yyyy/MM/dd", 57 | "dd/MM/yyyy", 58 | "MMM dd, yyyy", 59 | "MMMM dd, yyyy", 60 | "EEE MMM dd yyyy HH:mm:ss", 61 | ]; 62 | 63 | for (const format of knownFormats) { 64 | date = parse(input, format, new Date()); 65 | if (isValid(date)) return date; 66 | } 67 | } 68 | 69 | // Could not parse 70 | return null; 71 | }; 72 | 73 | export const dateAsString = (date: FullDate): string => { 74 | if (isTimeNow(date)) { 75 | return "Now"; 76 | } 77 | if (date instanceof Date) { 78 | return date.toISOString(); 79 | } 80 | throw new Error("Invalid date type"); 81 | }; 82 | -------------------------------------------------------------------------------- /docs/src/content/docs/adapters/grafana-loki.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Grafana Loki Browser 3 | description: How to use Cruncher with Grafana Loki. 4 | --- 5 | import ParamItem from '../../../components/ParamItem.astro'; 6 | 7 | import { Badge } from '@astrojs/starlight/components'; 8 | 9 | 10 | 11 | ## Params 12 | 13 | 14 | The base URL of your Grafana instance (e.g., https://my-grafana.example.com). 15 | 16 | 17 | The UID of the Loki data source in Grafana. 18 | 19 | 20 | A list of filter objects to apply to your queries. Each filter object should have: 21 |
22 | - key (string): The label or field to filter on.
23 | - value (string): The value to match.
24 | - operator (enum): One of `=`, `=~`, `!=`, `!~` (exact match, regex match, not equal, not regex match). 25 |
26 | 27 | Additional query suffixes to append to your Loki queries. 28 | 29 | 30 | ## Examples 31 | 32 | ### Minimal Configuration 33 | 34 | ```yaml 35 | connectors: 36 | - type: grafana_browser 37 | name: my_loki 38 | params: 39 | grafanaUrl: "https://my-grafana.example.com" 40 | uid: "loki-uid-123" 41 | ``` 42 | 43 | ### Full Configuration 44 | 45 | ```yaml 46 | connectors: 47 | - type: grafana_browser 48 | name: my_loki 49 | params: 50 | grafanaUrl: "https://my-grafana.example.com" 51 | uid: "loki-uid-123" 52 | filter: 53 | - key: "job" 54 | value: "my-app" 55 | operator: "=" 56 | querySuffix: ["| json"] 57 | ``` 58 | 59 | ## Usage Notes 60 | 61 | - When you run a query using this adapter, Cruncher will automatically open a login prompt in your browser if you are not already authenticated with Grafana. 62 | - You can use the `filter` parameter to restrict logs to specific labels or values. 63 | - The `querySuffix` parameter allows you to append additional Loki query language expressions to your queries. 64 | 65 | --- 66 | 67 | For more information on how to obtain your Grafana Loki data source UID, see the Grafana documentation or ask your Grafana administrator. 68 | -------------------------------------------------------------------------------- /docs/src/content/docs/qql-reference/commands/eval.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: eval 3 | description: The eval command in QQL allows you to create or modify fields by evaluating expressions for each record. 4 | --- 5 | 6 | The `eval` command creates or modifies fields by evaluating expressions for each record. 7 | 8 | ## Syntax 9 | 10 | ``` 11 | eval newField = expression 12 | ``` 13 | 14 | - `newField = expression`: Assigns the result of the expression to a new or existing field. 15 | 16 | ## Supported Functions and Expressions 17 | 18 | You can use arithmetic, logical, and conditional expressions, as well as functions, in `eval` assignments. Common supported functions include: 19 | 20 | - Mathematical: `+`, `-`, `*`, `/` 21 | - String: `lower(str)`, `upper(str)`, `length(str)` 22 | - Number: `abs(x)`, `round(x)`, `ceil(x)`, `floor(x)` 23 | - Boolean: `contains(str, substr)`, `startsWith(str, prefix)`, `endsWith(str, suffix)`, `match(str, regex)`, `isNull(x)`, `isNotNull(x)` 24 | - Conditional: `if(condition, then, else)`, `case((cond1, val1), ..., elseVal)` 25 | 26 | > Only functions supported by your Cruncher deployment are available. See the main documentation for a full list. 27 | 28 | ## Usage 29 | 30 | - Use `eval` to compute new values, transform fields, or perform calculations. 31 | - You can assign multiple fields by chaining multiple `eval` commands in your pipeline. 32 | - Use conditional logic to create new fields based on complex criteria. 33 | 34 | ## Examples 35 | 36 | ``` 37 | eval duration_sec = duration / 1000 38 | ``` 39 | This creates a `duration_sec` field. 40 | 41 | ``` 42 | eval is_error = (status == "error") 43 | ``` 44 | This creates a boolean field `is_error` that is true if `status` is `error`. 45 | 46 | ``` 47 | eval user_lower = lower(user) 48 | ``` 49 | This creates a new field `user_lower` with the lowercase value of `user`. 50 | 51 | ``` 52 | eval error_type = if(status == "error", "critical", "normal") 53 | ``` 54 | This creates a new field `error_type` based on the value of `status`. 55 | 56 | ``` 57 | eval match_found = match(message, "^Error:.*") 58 | ``` 59 | This creates a boolean field `match_found` that is true if `message` matches the regex pattern. 60 | 61 | ``` 62 | eval group = case(status == "error", "A", status == "warn", "B", "C") 63 | ``` 64 | This creates a new field `group` with value "A" if `status` is `error`, "B" if `status` is `warn`, and "C" otherwise. 65 | -------------------------------------------------------------------------------- /apps/cruncher/src/processes/frontend/features/searcher/header/Timer.tsx: -------------------------------------------------------------------------------- 1 | import { Badge } from "@chakra-ui/react"; 2 | import styled from "@emotion/styled"; 3 | import { useEffect, useState } from "react"; 4 | 5 | type TimerProps = { 6 | startTime: Date | undefined; 7 | endTime: Date | undefined; 8 | isLoading: boolean; 9 | }; 10 | 11 | const CustomBadge = styled(Badge)` 12 | font-family: monospace; 13 | justify-content: center; 14 | `; 15 | 16 | const TimerHolder = styled.div` 17 | display: flex; 18 | position: absolute; 19 | right: 0.4rem; 20 | bottom: 0.4rem; 21 | `; 22 | 23 | const formatElapsedTime = (elapsedMilliseconds: number) => { 24 | if (elapsedMilliseconds < 1000) { 25 | // if less than 1 second - show milliseconds 26 | return `${elapsedMilliseconds}ms`; 27 | } 28 | 29 | if (elapsedMilliseconds < 10000) { 30 | // if less than 10 seconds - show not rounded 31 | return `${(elapsedMilliseconds / 1000).toFixed(2)}s`; 32 | } 33 | 34 | if (elapsedMilliseconds < 60000) { 35 | // if less than 1 minute - show seconds 36 | return `${Math.round(elapsedMilliseconds / 1000)}s`; 37 | } 38 | 39 | return `${Math.floor(elapsedMilliseconds / 60000)}m ${Math.floor((elapsedMilliseconds % 60000) / 1000)}s`; 40 | }; 41 | 42 | const formatRange = (start: Date, end: Date) => { 43 | const elapsedMilliseconds = Math.abs(end.getTime() - start.getTime()); 44 | return formatElapsedTime(elapsedMilliseconds); 45 | }; 46 | 47 | export const Timer = ({ startTime, endTime, isLoading }: TimerProps) => { 48 | // if loading render elapsed time from startTime 49 | const [elapsedTime, setElapsedTime] = useState(0); 50 | 51 | useEffect(() => { 52 | setElapsedTime(0); 53 | }, [startTime]); 54 | 55 | useEffect(() => { 56 | if (isLoading) { 57 | const interval = setInterval(() => { 58 | setElapsedTime((prev) => prev + 10); 59 | }, 10); 60 | 61 | return () => clearInterval(interval); 62 | } 63 | }, [isLoading]); 64 | 65 | if (isLoading) { 66 | return ( 67 | 68 | {formatElapsedTime(elapsedTime)} 69 | 70 | ); 71 | } 72 | 73 | if (endTime === undefined || startTime === undefined) { 74 | return ; 75 | } 76 | 77 | return ( 78 | 79 | {formatRange(startTime, endTime)} 80 | 81 | ); 82 | }; 83 | -------------------------------------------------------------------------------- /apps/cruncher/src/processes/main/utils/auth.ts: -------------------------------------------------------------------------------- 1 | import { BrowserWindow } from "electron"; 2 | 3 | export const createAuthWindow = async ( 4 | url: string, 5 | requestedCookies: string[], 6 | checkValidCookies: (cookies: Record) => Promise, 7 | ) => { 8 | const authWindow = new BrowserWindow({ 9 | width: 400, 10 | height: 600, 11 | title: "Auth", 12 | show: false, // Start hidden 13 | webPreferences: { 14 | partition: "persist:auth-fetcher", // Persist cookies 15 | nodeIntegration: false, 16 | contextIsolation: true, 17 | }, 18 | }); 19 | 20 | await authWindow.loadURL(url); 21 | 22 | console.log("Auth Window created, waiting for login..."); 23 | return new Promise((resolve, reject) => { 24 | // add timeout to reject if login takes too long 25 | const timeout = setTimeout(() => { 26 | console.error("Login timeout, closing auth window..."); 27 | authWindow.webContents.off("did-frame-navigate", eventHandler); // Remove the event listener 28 | reject(new Error("Login timeout")); 29 | authWindow.close(); 30 | }, 120000); // 120 seconds timeout 31 | 32 | const eventHandler = async () => { 33 | const session = authWindow.webContents.session; 34 | const cookies = await session.cookies.get({ url: url }); 35 | const values = requestedCookies.reduce( 36 | (acc, cookieName) => { 37 | const cookie = cookies.find((c) => c.name === cookieName); 38 | if (cookie) { 39 | acc[cookieName] = cookie.value; 40 | } 41 | return acc; 42 | }, 43 | {} as Record, 44 | ); 45 | 46 | const validatedCookies = await checkValidCookies(values); 47 | if (validatedCookies) { 48 | console.log("Login successful, capturing cookies..."); 49 | authWindow.webContents.off("did-frame-navigate", eventHandler); // Remove the event listener 50 | clearTimeout(timeout); // Clear the timeout since we have a valid session 51 | authWindow.close(); 52 | resolve(); 53 | } else { 54 | console.info("Cookies not found - prompting user to login again."); 55 | authWindow.show(); // Show the window to prompt user to login again 56 | } 57 | }; 58 | 59 | // OPTIONAL: Detect login completion by checking URL change 60 | authWindow.webContents.on("did-frame-navigate", eventHandler); 61 | }); 62 | }; 63 | -------------------------------------------------------------------------------- /docs/src/content/docs/getting-started/04-adapters.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Adapters 3 | description: How to use Cruncher with various data sources and adapters. 4 | --- 5 | 6 | Cruncher supports a flexible adapter system, allowing you to connect to and query data from a variety of sources. Adapters are responsible for connecting to external systems, extracting fields, and providing data to Cruncher in a unified format. 7 | 8 | ## What is an Adapter? 9 | 10 | An adapter is a module that knows how to communicate with a specific backend or data source (such as Grafana Loki, Docker logs, or mocked/test data). It extracts fields from the source data and exposes them to Cruncher for querying and analysis. 11 | 12 | ## Built-in Adapters 13 | 14 | Cruncher comes with several built-in adapters, including: 15 | 16 | - **Grafana Browser Adapter**: Connects to Grafana Loki via the browser, allowing you to query logs and metrics from your Grafana instance. 17 | - **Docker Adapter**: Reads logs from Docker containers. 18 | - **Mocked Data Adapter**: Provides sample/mock data for testing and demos. 19 | 20 | ## Configuring Adapters 21 | 22 | Adapters are configured in the Cruncher config file (`~/.config/cruncher/cruncher.config.yaml`). Each adapter entry specifies the type, name, and any required parameters. For example: 23 | 24 | ```yaml 25 | connectors: 26 | - type: grafana_browser 27 | name: main 28 | params: 29 | grafanaUrl: 30 | uid: 31 | # ...other params... 32 | - type: docker 33 | name: docker_logs 34 | # ...params... 35 | ``` 36 | 37 | See the [installation guide](/getting-started/02-installation) and [configuration reference](/getting-started/05-config-file) for more details. 38 | 39 | ## How Adapters Work 40 | 41 | - Adapters extract fields and assign types to each field. 42 | - Controller parameters in your QQL queries are passed directly to the adapter, allowing for efficient server-side filtering. 43 | - Once data is loaded, you can use all of Cruncher's QQL features to analyze and transform it. 44 | 45 | ## Writing Your Own Adapter 46 | 47 | Cruncher is designed to be extensible. You can implement your own adapter by following the patterns in the `src/adapters/` directory. Adapters must expose a controller and implement the required interface for data extraction and field typing. 48 | 49 | --- 50 | 51 | Adapters make Cruncher a powerful tool for querying and analyzing data from many different sources, all with a consistent query experience. 52 | -------------------------------------------------------------------------------- /docs/src/content/docs/getting-started/05-config-file.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Cruncher Config File 3 | description: How to create and configure the Cruncher config file. 4 | --- 5 | import { Aside } from '@astrojs/starlight/components'; 6 | 7 | Cruncher uses a YAML configuration file to define which adapters and data sources are available. This file tells Cruncher how to connect to your data and what options to use for each adapter. 8 | 9 | ## Location 10 | 11 | The config file should be placed at: 12 | 13 | ``` 14 | ~/.config/cruncher/cruncher.config.yaml 15 | ``` 16 | 17 | ## Basic Structure 18 | 19 | A typical config file looks like this: 20 | 21 | ```yaml 22 | # Cruncher configuration file 23 | profiles: 24 | default: 25 | connectors: [main] 26 | docker: 27 | connectors: [docker_logs] 28 | all: 29 | connectors: [main, docker_logs] 30 | connectors: 31 | - type: grafana_browser 32 | name: main 33 | params: 34 | grafanaUrl: 35 | uid: 36 | filter: 37 | - key: label1 38 | value: "label1_value" 39 | operator: "=" 40 | querySuffix: [] 41 | - type: docker 42 | name: docker_logs 43 | # ...params for docker adapter... 44 | ``` 45 | 46 | - `connectors`: A list of adapters to use. Each entry defines an adapter type, a name, and any required parameters. 47 | - `type`: The adapter type (e.g., `grafana_browser`, `docker`, `mocked_data`). 48 | - `name`: A unique name for this adapter instance. 49 | - `params`: Adapter-specific parameters (see adapter docs for details). 50 | 51 | ## Profiles 52 | 53 | Cruncher supports the concept of profiles, allowing you to define multiple sets of connectors and settings in the same config file. Each profile can represent a different environment, use case, or set of data sources. 54 | 55 | - The `default` profile is used automatically when starting Cruncher. 56 | - You can switch profiles to quickly inside the Cruncher platform. 57 | 58 | 64 | 65 | --- 66 | 67 | For more details, see the [Adapters documentation](/getting-started/04-adapters). 68 | 69 | -------------------------------------------------------------------------------- /packages/qql/src/searchTree.ts: -------------------------------------------------------------------------------- 1 | import { Search, SearchAND, SearchOR, SearchLiteral } from "./grammar"; 2 | 3 | export type SearchTreeCallback = (item: string) => T; 4 | 5 | export interface SearchTreeBuilder { 6 | buildAnd( 7 | left: SearchTreeCallback, 8 | search: SearchAND, 9 | ): SearchTreeCallback; 10 | buildOr(left: SearchTreeCallback, search: SearchOR): SearchTreeCallback; 11 | buildLiteral(searchLiteral: SearchLiteral): SearchTreeCallback; 12 | } 13 | 14 | export function buildSearchTreeCallback( 15 | searchTerm: Search, 16 | builder: SearchTreeBuilder, 17 | ): SearchTreeCallback { 18 | const left = searchTerm.left; 19 | const right = searchTerm.right; 20 | 21 | let leftCallback: SearchTreeCallback; 22 | switch (left.type) { 23 | case "search": 24 | leftCallback = buildSearchTreeCallback(left, builder); 25 | break; 26 | case "searchLiteral": 27 | leftCallback = builder.buildLiteral(left); 28 | break; 29 | } 30 | 31 | if (!right) { 32 | return leftCallback; 33 | } 34 | 35 | switch (right.type) { 36 | case "and": 37 | return builder.buildAnd(leftCallback, right); 38 | case "or": 39 | return builder.buildOr(leftCallback, right); 40 | } 41 | } 42 | 43 | export const booleanSearchTreeBuilder: SearchTreeBuilder = { 44 | buildAnd: (leftCallback, search) => { 45 | return (item) => { 46 | const leftRes = leftCallback(item); 47 | if (!leftRes) return false; 48 | const rightRes = buildSearchTreeCallback( 49 | search.right, 50 | booleanSearchTreeBuilder, 51 | )(item); 52 | return rightRes; 53 | }; 54 | }, 55 | buildOr: (leftCallback, search) => { 56 | return (item) => { 57 | const leftRes = leftCallback(item); 58 | if (leftRes) return true; 59 | const rightRes = buildSearchTreeCallback( 60 | search.right, 61 | booleanSearchTreeBuilder, 62 | )(item); 63 | return rightRes; 64 | }; 65 | }, 66 | buildLiteral: (searchLiteral) => { 67 | if (searchLiteral.tokens.length === 0) { 68 | return () => true; 69 | } 70 | return (searchTerm) => 71 | searchLiteral.tokens.every((token) => { 72 | return searchTerm.toLowerCase().includes(String(token).toLowerCase()); 73 | }); 74 | }, 75 | }; 76 | 77 | export type BooleanSearchCallback = (item: string) => boolean; 78 | 79 | export const buildDoesLogMatchCallback = ( 80 | searchTerm: Search, 81 | ): BooleanSearchCallback => { 82 | return buildSearchTreeCallback(searchTerm, booleanSearchTreeBuilder); 83 | }; 84 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cruncher-monorepo", 3 | "private": true, 4 | "version": "", 5 | "packageManager": "pnpm@10.12.1", 6 | "description": "Cruncher monorepo.", 7 | "pnpm": { 8 | "onlyBuiltDependencies": [ 9 | "@swc/core", 10 | "bufferutil", 11 | "core-js", 12 | "electron", 13 | "electron-winstaller", 14 | "esbuild", 15 | "msgpackr-extract", 16 | "nx", 17 | "sharp", 18 | "unrs-resolver", 19 | "utf-8-validate" 20 | ] 21 | }, 22 | "repository": { 23 | "type": "git", 24 | "url": "https://github.com/IamShobe/cruncher.git" 25 | }, 26 | "scripts": { 27 | "start": "npm run serve", 28 | "serve": "nx run @cruncher/app:serve", 29 | "serve:docs": "pnpm run -F ./docs serve", 30 | "build": "nx run-many -t build", 31 | "publish": "nx run @cruncher/app:publish", 32 | "package": "nx run @cruncher/app:package", 33 | "format": "nx run-many -t format", 34 | "format:check": "nx run-many -t format:check", 35 | "test": "nx run-many -t test", 36 | "clean": "find . -name 'node_modules' -type d -prune -exec rm -rf '{}' +", 37 | "new-adapter": "pnpx nx generate cruncher-tools:adapter-generator", 38 | "prepare-pr": "nx release plan", 39 | "release": "pnpm dlx nx release", 40 | "push-all": "git push && git push --tags" 41 | }, 42 | "keywords": [], 43 | "author": { 44 | "name": "Elran Shefer" 45 | }, 46 | "license": "GPL-3.0-only", 47 | "devDependencies": { 48 | "@eslint/js": "^9.8.0", 49 | "@nx/eslint": "21.2.1", 50 | "@nx/eslint-plugin": "21.2.1", 51 | "@nx/js": "^21.2.1", 52 | "@nx/plugin": "21.2.1", 53 | "@nx/vite": "21.2.1", 54 | "@nx/web": "21.2.1", 55 | "@release-it/bumper": "^7.0.5", 56 | "@swc-node/register": "~1.9.1", 57 | "@swc/cli": "~0.6.0", 58 | "@swc/core": "~1.5.7", 59 | "@swc/helpers": "~0.5.11", 60 | "@types/node": "18.16.9", 61 | "@vitest/coverage-v8": "^3.0.5", 62 | "@vitest/ui": "^3.0.0", 63 | "eslint": "^9.8.0", 64 | "eslint-config-prettier": "^10.0.0", 65 | "jiti": "2.4.2", 66 | "jsdom": "~22.1.0", 67 | "jsonc-eslint-parser": "^2.1.0", 68 | "nx": "21.2.1", 69 | "prettier": "^3.5.3", 70 | "release-it": "^19.0.3", 71 | "swc-loader": "0.1.15", 72 | "ts-node": "10.9.1", 73 | "tslib": "^2.3.0", 74 | "typescript": "^5.8.3", 75 | "typescript-eslint": "^8.29.0", 76 | "verdaccio": "^6.0.5", 77 | "vite": "^6.0.0", 78 | "vite-plugin-dts": "~4.5.0", 79 | "vitest": "^3.0.0" 80 | }, 81 | "nx": { 82 | "includedScripts": [] 83 | }, 84 | "dependencies": { 85 | "@nx/devkit": "21.2.1" 86 | } 87 | } 88 | --------------------------------------------------------------------------------