├── .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 |
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 | 
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 |
--------------------------------------------------------------------------------