grafana-react 0.0.0 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +88 -2
- package/build/cli/cli.test.d.ts +8 -0
- package/build/cli/cli.test.js +78 -0
- package/build/cli/index.d.ts +13 -0
- package/build/cli/index.js +266 -0
- package/build/components/annotation/annotation.d.ts +27 -0
- package/build/components/annotation/annotation.js +12 -0
- package/build/components/base.d.ts +23 -0
- package/build/components/base.js +28 -0
- package/build/components/dashboard/dashboard.d.ts +33 -0
- package/build/components/dashboard/dashboard.js +11 -0
- package/build/components/index.d.ts +44 -0
- package/build/components/index.js +54 -0
- package/build/components/link/link.d.ts +25 -0
- package/build/components/link/link.js +10 -0
- package/build/components/override/override.d.ts +22 -0
- package/build/components/override/override.js +11 -0
- package/build/components/panels/core/alert-list/alert-list.d.ts +33 -0
- package/build/components/panels/core/alert-list/alert-list.js +15 -0
- package/build/components/panels/core/annotations-list/annotations-list.d.ts +31 -0
- package/build/components/panels/core/annotations-list/annotations-list.js +15 -0
- package/build/components/panels/core/bar-chart/bar-chart.d.ts +47 -0
- package/build/components/panels/core/bar-chart/bar-chart.js +16 -0
- package/build/components/panels/core/bar-gauge/bar-gauge.d.ts +68 -0
- package/build/components/panels/core/bar-gauge/bar-gauge.js +30 -0
- package/build/components/panels/core/candlestick/candlestick.d.ts +42 -0
- package/build/components/panels/core/candlestick/candlestick.js +17 -0
- package/build/components/panels/core/canvas/canvas.d.ts +18 -0
- package/build/components/panels/core/canvas/canvas.js +12 -0
- package/build/components/panels/core/dashboard-list/dashboard-list.d.ts +35 -0
- package/build/components/panels/core/dashboard-list/dashboard-list.js +15 -0
- package/build/components/panels/core/datagrid/datagrid.d.ts +16 -0
- package/build/components/panels/core/datagrid/datagrid.js +12 -0
- package/build/components/panels/core/flame-graph/flame-graph.d.ts +16 -0
- package/build/components/panels/core/flame-graph/flame-graph.js +12 -0
- package/build/components/panels/core/gauge/gauge.d.ts +64 -0
- package/build/components/panels/core/gauge/gauge.js +30 -0
- package/build/components/panels/core/geomap/geomap.d.ts +20 -0
- package/build/components/panels/core/geomap/geomap.js +12 -0
- package/build/components/panels/core/heatmap/heatmap.d.ts +25 -0
- package/build/components/panels/core/heatmap/heatmap.js +16 -0
- package/build/components/panels/core/histogram/histogram.d.ts +39 -0
- package/build/components/panels/core/histogram/histogram.js +16 -0
- package/build/components/panels/core/logs/logs.d.ts +33 -0
- package/build/components/panels/core/logs/logs.js +17 -0
- package/build/components/panels/core/news/news.d.ts +20 -0
- package/build/components/panels/core/news/news.js +14 -0
- package/build/components/panels/core/node-graph/node-graph.d.ts +18 -0
- package/build/components/panels/core/node-graph/node-graph.js +12 -0
- package/build/components/panels/core/pie-chart/pie-chart.d.ts +33 -0
- package/build/components/panels/core/pie-chart/pie-chart.js +16 -0
- package/build/components/panels/core/stat/stat.d.ts +54 -0
- package/build/components/panels/core/stat/stat.js +18 -0
- package/build/components/panels/core/state-timeline/state-timeline.d.ts +37 -0
- package/build/components/panels/core/state-timeline/state-timeline.js +16 -0
- package/build/components/panels/core/status-history/status-history.d.ts +35 -0
- package/build/components/panels/core/status-history/status-history.js +16 -0
- package/build/components/panels/core/table/table.d.ts +63 -0
- package/build/components/panels/core/table/table.js +25 -0
- package/build/components/panels/core/text/text.d.ts +16 -0
- package/build/components/panels/core/text/text.js +10 -0
- package/build/components/panels/core/timeseries/timeseries.d.ts +103 -0
- package/build/components/panels/core/timeseries/timeseries.js +31 -0
- package/build/components/panels/core/traces/traces.d.ts +18 -0
- package/build/components/panels/core/traces/traces.js +12 -0
- package/build/components/panels/core/trend/trend.d.ts +39 -0
- package/build/components/panels/core/trend/trend.js +16 -0
- package/build/components/panels/core/xy-chart/xy-chart.d.ts +44 -0
- package/build/components/panels/core/xy-chart/xy-chart.js +17 -0
- package/build/components/panels/index.d.ts +32 -0
- package/build/components/panels/index.js +38 -0
- package/build/components/panels/plugins/business-variable-panel/business-variable-panel.d.ts +85 -0
- package/build/components/panels/plugins/business-variable-panel/business-variable-panel.js +15 -0
- package/build/components/plugin-panel/plugin-panel.d.ts +25 -0
- package/build/components/plugin-panel/plugin-panel.js +17 -0
- package/build/components/query/query.d.ts +33 -0
- package/build/components/query/query.js +19 -0
- package/build/components/row/row.d.ts +27 -0
- package/build/components/row/row.js +13 -0
- package/build/components/variable/variable.d.ts +32 -0
- package/build/components/variable/variable.js +12 -0
- package/build/index.d.ts +33 -0
- package/build/index.js +52 -0
- package/build/lib/index.d.ts +5 -0
- package/build/lib/index.js +5 -0
- package/build/lib/renderer.d.ts +24 -0
- package/build/lib/renderer.js +1531 -0
- package/build/lib/renderer.test.d.ts +4 -0
- package/build/lib/renderer.test.js +480 -0
- package/build/lib/utils.d.ts +70 -0
- package/build/lib/utils.js +229 -0
- package/build/lib/utils.test.d.ts +4 -0
- package/build/lib/utils.test.js +252 -0
- package/build/types/common/axis.d.ts +76 -0
- package/build/types/common/axis.js +8 -0
- package/build/types/common/enums.d.ts +50 -0
- package/build/types/common/enums.js +9 -0
- package/build/types/common/field-config.d.ts +123 -0
- package/build/types/common/field-config.js +8 -0
- package/build/types/common/index.d.ts +10 -0
- package/build/types/common/index.js +7 -0
- package/build/types/common/viz-options.d.ts +94 -0
- package/build/types/common/viz-options.js +8 -0
- package/build/types/display.d.ts +29 -0
- package/build/types/display.js +4 -0
- package/build/types/grafana-json.d.ts +147 -0
- package/build/types/grafana-json.js +6 -0
- package/build/types/index.d.ts +10 -0
- package/build/types/index.js +7 -0
- package/build/types/panel-base.d.ts +72 -0
- package/build/types/panel-base.js +4 -0
- package/package.json +67 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Kiwi Research
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,5 +1,91 @@
|
|
|
1
1
|
# grafana-react
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
React-based DSL for creating Grafana dashboards. Write dashboards as JSX components and compile them to Grafana JSON.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
<p align="center">
|
|
6
|
+
<img src="docs/public/img/hero-code.png" alt="JSX code for Stat panel" height="180" />
|
|
7
|
+
|
|
8
|
+
<img src="docs/public/img/hero-grafana-stat-panel.png" alt="Rendered Grafana Stat panel" height="140" />
|
|
9
|
+
</p>
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- **Declarative JSX Syntax** - Write dashboards using familiar React patterns
|
|
14
|
+
- **Component Composition** - Create reusable dashboard components
|
|
15
|
+
- **Type Safety** - Full TypeScript support with comprehensive types
|
|
16
|
+
- **CLI Tool** - Build, validate, and watch dashboard files
|
|
17
|
+
- **Zero Runtime** - Compiles to static JSON, no React in production
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install grafana-react react@19 tsx@4
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Quick Start
|
|
26
|
+
|
|
27
|
+
```tsx
|
|
28
|
+
import { Dashboard, Row, Stat, Timeseries, Variable } from 'grafana-react';
|
|
29
|
+
|
|
30
|
+
export default function MyDashboard() {
|
|
31
|
+
return (
|
|
32
|
+
<Dashboard uid="my-dashboard" title="My Dashboard" datasource="prometheus">
|
|
33
|
+
<Variable name="instance">label_values(up, instance)</Variable>
|
|
34
|
+
|
|
35
|
+
<Row title="Summary">
|
|
36
|
+
<Stat
|
|
37
|
+
title="CPU %"
|
|
38
|
+
unit="percent"
|
|
39
|
+
thresholds={{ 70: 'yellow', 90: 'red' }}
|
|
40
|
+
>
|
|
41
|
+
100 - avg(rate(cpu_idle[$__rate_interval])) * 100
|
|
42
|
+
</Stat>
|
|
43
|
+
</Row>
|
|
44
|
+
|
|
45
|
+
<Row title="Details">
|
|
46
|
+
<Timeseries title="CPU Over Time" unit="percent" stack legend="right">
|
|
47
|
+
sum(rate(node_cpu_seconds_total[5m])) by (mode) * 100
|
|
48
|
+
</Timeseries>
|
|
49
|
+
</Row>
|
|
50
|
+
</Dashboard>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Build to JSON using the CLI:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
npx grafana-react build my-dashboard.tsx output/my-dashboard.json
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Or programmatically with `renderToString`:
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
import React from 'react';
|
|
65
|
+
import { renderToString } from 'grafana-react';
|
|
66
|
+
|
|
67
|
+
// Use the JSON string directly, e.g. add as a ConfigMap with Pulumi
|
|
68
|
+
const json = renderToString(React.createElement(MyDashboard));
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Documentation
|
|
72
|
+
|
|
73
|
+
Full documentation is available at: **[kiwi-research.github.io/grafana-react](https://kiwi-research.github.io/grafana-react)**
|
|
74
|
+
|
|
75
|
+
- [Getting Started](https://kiwi-research.github.io/grafana-react/getting-started/installation/)
|
|
76
|
+
- [Components Reference](https://kiwi-research.github.io/grafana-react/components/structure/)
|
|
77
|
+
- [CLI Usage](https://kiwi-research.github.io/grafana-react/guides/cli-usage/)
|
|
78
|
+
- [Reusable Components](https://kiwi-research.github.io/grafana-react/guides/reusable-components/)
|
|
79
|
+
|
|
80
|
+
## CLI Commands
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
npx grafana-react build <input.tsx> [output.json] # Build single dashboard
|
|
84
|
+
npx grafana-react build-all <dir> [output-dir] # Build all dashboards
|
|
85
|
+
npx grafana-react validate <input.tsx> # Validate without output
|
|
86
|
+
npx grafana-react watch <dir> [output-dir] # Watch and rebuild
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## License
|
|
90
|
+
|
|
91
|
+
MIT
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI integration tests
|
|
3
|
+
*
|
|
4
|
+
* These tests verify the CLI's basic functionality.
|
|
5
|
+
* Note: Full dashboard compilation tests are limited because Node.js
|
|
6
|
+
* cannot dynamically import .tsx files without a TypeScript loader.
|
|
7
|
+
*/
|
|
8
|
+
import { describe, it } from 'node:test';
|
|
9
|
+
import assert from 'node:assert';
|
|
10
|
+
import { execSync, spawnSync } from 'node:child_process';
|
|
11
|
+
import * as path from 'node:path';
|
|
12
|
+
// Path to the built CLI
|
|
13
|
+
const CLI_PATH = path.resolve(import.meta.dirname, '../../build/cli/index.js');
|
|
14
|
+
describe('CLI integration', () => {
|
|
15
|
+
it('shows help with --help flag', () => {
|
|
16
|
+
const result = execSync(`node ${CLI_PATH} --help`, {
|
|
17
|
+
encoding: 'utf8',
|
|
18
|
+
});
|
|
19
|
+
assert.ok(result.includes('grafana-react'));
|
|
20
|
+
assert.ok(result.includes('build'));
|
|
21
|
+
assert.ok(result.includes('validate'));
|
|
22
|
+
assert.ok(result.includes('watch'));
|
|
23
|
+
assert.ok(result.includes('build-all'));
|
|
24
|
+
});
|
|
25
|
+
it('shows help with no arguments', () => {
|
|
26
|
+
const result = execSync(`node ${CLI_PATH}`, {
|
|
27
|
+
encoding: 'utf8',
|
|
28
|
+
});
|
|
29
|
+
assert.ok(result.includes('Usage:'));
|
|
30
|
+
});
|
|
31
|
+
it('shows version with --version flag', () => {
|
|
32
|
+
const result = execSync(`node ${CLI_PATH} --version`, {
|
|
33
|
+
encoding: 'utf8',
|
|
34
|
+
});
|
|
35
|
+
assert.ok(result.includes('grafana-react v'));
|
|
36
|
+
});
|
|
37
|
+
it('shows version with -v flag', () => {
|
|
38
|
+
const result = execSync(`node ${CLI_PATH} -v`, {
|
|
39
|
+
encoding: 'utf8',
|
|
40
|
+
});
|
|
41
|
+
assert.ok(result.includes('grafana-react v'));
|
|
42
|
+
});
|
|
43
|
+
it('exits with error for missing input file on build', () => {
|
|
44
|
+
const result = spawnSync('node', [CLI_PATH, 'build'], {
|
|
45
|
+
encoding: 'utf8',
|
|
46
|
+
});
|
|
47
|
+
assert.strictEqual(result.status, 1);
|
|
48
|
+
assert.ok(result.stderr.includes('Missing input file'));
|
|
49
|
+
});
|
|
50
|
+
it('exits with error for missing input directory on build-all', () => {
|
|
51
|
+
const result = spawnSync('node', [CLI_PATH, 'build-all'], {
|
|
52
|
+
encoding: 'utf8',
|
|
53
|
+
});
|
|
54
|
+
assert.strictEqual(result.status, 1);
|
|
55
|
+
assert.ok(result.stderr.includes('Missing input directory'));
|
|
56
|
+
});
|
|
57
|
+
it('exits with error for missing input file on validate', () => {
|
|
58
|
+
const result = spawnSync('node', [CLI_PATH, 'validate'], {
|
|
59
|
+
encoding: 'utf8',
|
|
60
|
+
});
|
|
61
|
+
assert.strictEqual(result.status, 1);
|
|
62
|
+
assert.ok(result.stderr.includes('Missing input file'));
|
|
63
|
+
});
|
|
64
|
+
it('exits with error for non-existent file', () => {
|
|
65
|
+
const result = spawnSync('node', [CLI_PATH, 'build', '/nonexistent/file.tsx'], {
|
|
66
|
+
encoding: 'utf8',
|
|
67
|
+
});
|
|
68
|
+
assert.strictEqual(result.status, 1);
|
|
69
|
+
assert.ok(result.stderr.includes('File not found'));
|
|
70
|
+
});
|
|
71
|
+
it('exits with error for unknown command', () => {
|
|
72
|
+
const result = spawnSync('node', [CLI_PATH, 'unknown-command'], {
|
|
73
|
+
encoding: 'utf8',
|
|
74
|
+
});
|
|
75
|
+
assert.strictEqual(result.status, 1);
|
|
76
|
+
assert.ok(result.stderr.includes('Unknown command'));
|
|
77
|
+
});
|
|
78
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Grafana React CLI
|
|
4
|
+
*
|
|
5
|
+
* Build Grafana dashboards from React/TSX files.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* grafana-react build <input> [output] Build a single dashboard
|
|
9
|
+
* grafana-react build-all <dir> [outdir] Build all dashboards in a directory
|
|
10
|
+
* grafana-react validate <input> Validate a dashboard without output
|
|
11
|
+
* grafana-react watch <dir> [outdir] Watch and rebuild on changes
|
|
12
|
+
*/
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Grafana React CLI
|
|
4
|
+
*
|
|
5
|
+
* Build Grafana dashboards from React/TSX files.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* grafana-react build <input> [output] Build a single dashboard
|
|
9
|
+
* grafana-react build-all <dir> [outdir] Build all dashboards in a directory
|
|
10
|
+
* grafana-react validate <input> Validate a dashboard without output
|
|
11
|
+
* grafana-react watch <dir> [outdir] Watch and rebuild on changes
|
|
12
|
+
*/
|
|
13
|
+
// Register tsx loader for TypeScript/TSX support
|
|
14
|
+
try {
|
|
15
|
+
const { register } = await import('tsx/esm/api');
|
|
16
|
+
register();
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
console.error(`Error: The grafana-react CLI requires 'tsx' to be installed.
|
|
20
|
+
|
|
21
|
+
Install it with:
|
|
22
|
+
npm install tsx@4
|
|
23
|
+
|
|
24
|
+
Or install all peer dependencies:
|
|
25
|
+
npm install react@19 tsx@4
|
|
26
|
+
`);
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
import * as fs from 'node:fs';
|
|
30
|
+
import * as path from 'node:path';
|
|
31
|
+
import React from 'react';
|
|
32
|
+
import { renderToString } from '../lib/renderer.js';
|
|
33
|
+
// ============================================================================
|
|
34
|
+
// CLI Helpers
|
|
35
|
+
// ============================================================================
|
|
36
|
+
function printUsage() {
|
|
37
|
+
console.log(`
|
|
38
|
+
grafana-react - Build Grafana dashboards from React/TSX files
|
|
39
|
+
|
|
40
|
+
Usage:
|
|
41
|
+
grafana-react build <input.tsx> [output.json]
|
|
42
|
+
Build a single dashboard from a TSX file.
|
|
43
|
+
If output is not specified, prints to stdout.
|
|
44
|
+
|
|
45
|
+
grafana-react build-all <input-dir> [output-dir]
|
|
46
|
+
Build all *.dashboard.tsx files in a directory.
|
|
47
|
+
Output files are named *.gen.json in the output directory.
|
|
48
|
+
|
|
49
|
+
grafana-react validate <input.tsx>
|
|
50
|
+
Validate a dashboard file without generating output.
|
|
51
|
+
|
|
52
|
+
grafana-react watch <input-dir> [output-dir]
|
|
53
|
+
Watch for changes and rebuild automatically.
|
|
54
|
+
|
|
55
|
+
Options:
|
|
56
|
+
--help, -h Show this help message
|
|
57
|
+
--version, -v Show version
|
|
58
|
+
|
|
59
|
+
Examples:
|
|
60
|
+
grafana-react build dashboards/node.dashboard.tsx
|
|
61
|
+
grafana-react build dashboards/node.dashboard.tsx output/node.json
|
|
62
|
+
grafana-react build-all dashboards/ output/
|
|
63
|
+
grafana-react validate dashboards/node.dashboard.tsx
|
|
64
|
+
`);
|
|
65
|
+
}
|
|
66
|
+
function printVersion() {
|
|
67
|
+
const pkgPath = new URL('../../package.json', import.meta.url);
|
|
68
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
69
|
+
console.log(`grafana-react v${pkg.version}`);
|
|
70
|
+
}
|
|
71
|
+
function error(message) {
|
|
72
|
+
console.error(`Error: ${message}`);
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
function success(message) {
|
|
76
|
+
console.log(`\u2713 ${message}`);
|
|
77
|
+
}
|
|
78
|
+
// ============================================================================
|
|
79
|
+
// Build Functions
|
|
80
|
+
// ============================================================================
|
|
81
|
+
async function loadDashboard(inputFile) {
|
|
82
|
+
const absolutePath = path.resolve(inputFile);
|
|
83
|
+
if (!fs.existsSync(absolutePath)) {
|
|
84
|
+
error(`File not found: ${absolutePath}`);
|
|
85
|
+
}
|
|
86
|
+
const ext = path.extname(absolutePath);
|
|
87
|
+
if (ext !== '.tsx' && ext !== '.ts') {
|
|
88
|
+
error(`Expected .tsx or .ts file, got ${ext}`);
|
|
89
|
+
}
|
|
90
|
+
// Import the module (tsx loader handles TSX transformation)
|
|
91
|
+
const module = (await import(absolutePath));
|
|
92
|
+
// Find the dashboard component
|
|
93
|
+
const Component = module.default ??
|
|
94
|
+
module.Dashboard ??
|
|
95
|
+
Object.values(module).find((exp) => typeof exp === 'function' &&
|
|
96
|
+
/dashboard/i.test(exp.name ?? ''));
|
|
97
|
+
if (!Component) {
|
|
98
|
+
error('No dashboard component found. Export as default or named "Dashboard"');
|
|
99
|
+
}
|
|
100
|
+
return React.createElement(Component);
|
|
101
|
+
}
|
|
102
|
+
async function buildOne(inputFile, outputFile) {
|
|
103
|
+
const element = await loadDashboard(inputFile);
|
|
104
|
+
const json = renderToString(element);
|
|
105
|
+
if (outputFile) {
|
|
106
|
+
const absoluteOutput = path.resolve(outputFile);
|
|
107
|
+
const outputDir = path.dirname(absoluteOutput);
|
|
108
|
+
if (!fs.existsSync(outputDir)) {
|
|
109
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
110
|
+
}
|
|
111
|
+
fs.writeFileSync(absoluteOutput, json + '\n');
|
|
112
|
+
success(`Built ${path.relative(process.cwd(), absoluteOutput)}`);
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
console.log(json);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
async function buildAll(inputDir, outputDir) {
|
|
119
|
+
const absoluteInput = path.resolve(inputDir);
|
|
120
|
+
if (!fs.existsSync(absoluteInput)) {
|
|
121
|
+
error(`Directory not found: ${absoluteInput}`);
|
|
122
|
+
}
|
|
123
|
+
const files = findDashboardFiles(absoluteInput);
|
|
124
|
+
if (files.length === 0) {
|
|
125
|
+
console.log('No dashboard files found (*.dashboard.tsx)');
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
console.log(`Found ${files.length} dashboard file(s)`);
|
|
129
|
+
const resolvedOutputDir = outputDir ? path.resolve(outputDir) : absoluteInput;
|
|
130
|
+
for (const file of files) {
|
|
131
|
+
const relativePath = path.relative(absoluteInput, file);
|
|
132
|
+
const outputName = relativePath.replace(/\.dashboard\.tsx$/, '.gen.json');
|
|
133
|
+
const outputPath = path.join(resolvedOutputDir, outputName);
|
|
134
|
+
try {
|
|
135
|
+
const element = await loadDashboard(file);
|
|
136
|
+
const json = renderToString(element);
|
|
137
|
+
const outputFileDir = path.dirname(outputPath);
|
|
138
|
+
if (!fs.existsSync(outputFileDir)) {
|
|
139
|
+
fs.mkdirSync(outputFileDir, { recursive: true });
|
|
140
|
+
}
|
|
141
|
+
fs.writeFileSync(outputPath, json + '\n');
|
|
142
|
+
success(`${relativePath} -> ${path.relative(process.cwd(), outputPath)}`);
|
|
143
|
+
}
|
|
144
|
+
catch (err) {
|
|
145
|
+
console.error(`\u2717 ${relativePath}: ${err.message}`);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
async function validate(inputFile) {
|
|
150
|
+
const element = await loadDashboard(inputFile);
|
|
151
|
+
// Just render to validate - don't output
|
|
152
|
+
renderToString(element);
|
|
153
|
+
success(`Valid: ${path.relative(process.cwd(), inputFile)}`);
|
|
154
|
+
}
|
|
155
|
+
async function watch(inputDir, outputDir) {
|
|
156
|
+
const absoluteInput = path.resolve(inputDir);
|
|
157
|
+
const resolvedOutputDir = outputDir ? path.resolve(outputDir) : absoluteInput;
|
|
158
|
+
console.log(`Watching ${absoluteInput} for changes...`);
|
|
159
|
+
// Initial build
|
|
160
|
+
await buildAll(inputDir, outputDir);
|
|
161
|
+
// Watch for changes
|
|
162
|
+
const { watch: fsWatch } = await import('node:fs');
|
|
163
|
+
fsWatch(absoluteInput, { recursive: true }, async (_eventType, filename) => {
|
|
164
|
+
if (!filename?.endsWith('.dashboard.tsx'))
|
|
165
|
+
return;
|
|
166
|
+
const filePath = path.join(absoluteInput, filename);
|
|
167
|
+
const relativePath = path.relative(absoluteInput, filePath);
|
|
168
|
+
const outputName = relativePath.replace(/\.dashboard\.tsx$/, '.gen.json');
|
|
169
|
+
const outputPath = path.join(resolvedOutputDir, outputName);
|
|
170
|
+
console.log(`\nRebuilding ${filename}...`);
|
|
171
|
+
try {
|
|
172
|
+
// Clear module cache for hot reload
|
|
173
|
+
const absolutePath = path.resolve(filePath);
|
|
174
|
+
delete require.cache?.[absolutePath];
|
|
175
|
+
const element = await loadDashboard(filePath);
|
|
176
|
+
const json = renderToString(element);
|
|
177
|
+
const outputFileDir = path.dirname(outputPath);
|
|
178
|
+
if (!fs.existsSync(outputFileDir)) {
|
|
179
|
+
fs.mkdirSync(outputFileDir, { recursive: true });
|
|
180
|
+
}
|
|
181
|
+
fs.writeFileSync(outputPath, json + '\n');
|
|
182
|
+
success(`${relativePath} -> ${path.relative(process.cwd(), outputPath)}`);
|
|
183
|
+
}
|
|
184
|
+
catch (err) {
|
|
185
|
+
console.error(`\u2717 ${relativePath}: ${err.message}`);
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
// Keep process alive
|
|
189
|
+
process.on('SIGINT', () => {
|
|
190
|
+
console.log('\nStopping watch...');
|
|
191
|
+
process.exit(0);
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
// ============================================================================
|
|
195
|
+
// File Discovery
|
|
196
|
+
// ============================================================================
|
|
197
|
+
function findDashboardFiles(dir) {
|
|
198
|
+
const files = [];
|
|
199
|
+
function walk(currentDir) {
|
|
200
|
+
const entries = fs.readdirSync(currentDir, { withFileTypes: true });
|
|
201
|
+
for (const entry of entries) {
|
|
202
|
+
const fullPath = path.join(currentDir, entry.name);
|
|
203
|
+
if (entry.isDirectory() && entry.name !== 'node_modules') {
|
|
204
|
+
walk(fullPath);
|
|
205
|
+
}
|
|
206
|
+
else if (entry.isFile() && entry.name.endsWith('.dashboard.tsx')) {
|
|
207
|
+
files.push(fullPath);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
walk(dir);
|
|
212
|
+
return files.sort();
|
|
213
|
+
}
|
|
214
|
+
// ============================================================================
|
|
215
|
+
// Main
|
|
216
|
+
// ============================================================================
|
|
217
|
+
async function main() {
|
|
218
|
+
const args = process.argv.slice(2);
|
|
219
|
+
if (args.length === 0 || args.includes('--help') || args.includes('-h')) {
|
|
220
|
+
printUsage();
|
|
221
|
+
process.exit(0);
|
|
222
|
+
}
|
|
223
|
+
if (args.includes('--version') || args.includes('-v')) {
|
|
224
|
+
printVersion();
|
|
225
|
+
process.exit(0);
|
|
226
|
+
}
|
|
227
|
+
const command = args[0];
|
|
228
|
+
switch (command) {
|
|
229
|
+
case 'build':
|
|
230
|
+
if (!args[1]) {
|
|
231
|
+
error('Missing input file. Usage: grafana-react build <input.tsx> [output.json]');
|
|
232
|
+
}
|
|
233
|
+
await buildOne(args[1], args[2]);
|
|
234
|
+
break;
|
|
235
|
+
case 'build-all':
|
|
236
|
+
if (!args[1]) {
|
|
237
|
+
error('Missing input directory. Usage: grafana-react build-all <input-dir> [output-dir]');
|
|
238
|
+
}
|
|
239
|
+
await buildAll(args[1], args[2]);
|
|
240
|
+
break;
|
|
241
|
+
case 'validate':
|
|
242
|
+
if (!args[1]) {
|
|
243
|
+
error('Missing input file. Usage: grafana-react validate <input.tsx>');
|
|
244
|
+
}
|
|
245
|
+
await validate(args[1]);
|
|
246
|
+
break;
|
|
247
|
+
case 'watch':
|
|
248
|
+
if (!args[1]) {
|
|
249
|
+
error('Missing input directory. Usage: grafana-react watch <input-dir> [output-dir]');
|
|
250
|
+
}
|
|
251
|
+
await watch(args[1], args[2]);
|
|
252
|
+
break;
|
|
253
|
+
default:
|
|
254
|
+
// Treat as build command if it's a file path
|
|
255
|
+
if (args[0].endsWith('.tsx') || args[0].endsWith('.ts')) {
|
|
256
|
+
await buildOne(args[0], args[1]);
|
|
257
|
+
}
|
|
258
|
+
else {
|
|
259
|
+
error(`Unknown command: ${command}. Use --help for usage.`);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
main().catch((err) => {
|
|
264
|
+
console.error('Error:', err.message);
|
|
265
|
+
process.exit(1);
|
|
266
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Annotation - Alert annotation markers
|
|
3
|
+
*
|
|
4
|
+
* Annotations display markers on panels when alerts fire.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* <Annotation name="Alerts" color="light-red" title="{{alertname}}">
|
|
8
|
+
* ALERTS{alertstate="firing"}
|
|
9
|
+
* </Annotation>
|
|
10
|
+
*/
|
|
11
|
+
export interface AnnotationProps {
|
|
12
|
+
/** Annotation name */
|
|
13
|
+
name: string;
|
|
14
|
+
/** Marker color */
|
|
15
|
+
color?: string;
|
|
16
|
+
/** Title format using {{label}} syntax */
|
|
17
|
+
title?: string;
|
|
18
|
+
/** Tag keys to include */
|
|
19
|
+
tags?: string;
|
|
20
|
+
/** Hide annotation from view */
|
|
21
|
+
hide?: boolean;
|
|
22
|
+
/** Enable annotation */
|
|
23
|
+
enabled?: boolean;
|
|
24
|
+
/** PromQL expression as child */
|
|
25
|
+
children?: string;
|
|
26
|
+
}
|
|
27
|
+
export declare const Annotation: import("react").FC<AnnotationProps>;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Annotation - Alert annotation markers
|
|
3
|
+
*
|
|
4
|
+
* Annotations display markers on panels when alerts fire.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* <Annotation name="Alerts" color="light-red" title="{{alertname}}">
|
|
8
|
+
* ALERTS{alertstate="firing"}
|
|
9
|
+
* </Annotation>
|
|
10
|
+
*/
|
|
11
|
+
import { createComponent } from '../base.js';
|
|
12
|
+
export const Annotation = createComponent('annotation');
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base component utilities
|
|
3
|
+
*
|
|
4
|
+
* Provides the foundation for creating grafana-react components.
|
|
5
|
+
* Components don't render to DOM - they create a virtual tree
|
|
6
|
+
* that gets converted to Grafana JSON by the renderer.
|
|
7
|
+
*/
|
|
8
|
+
import type React from 'react';
|
|
9
|
+
/** Symbol used to identify component types */
|
|
10
|
+
export declare const COMPONENT_TYPE: unique symbol;
|
|
11
|
+
/** All component types */
|
|
12
|
+
export type ComponentType = 'dashboard' | 'row' | 'variable' | 'annotation' | 'link' | 'query' | 'override' | 'stat' | 'timeseries' | 'table' | 'bargauge' | 'heatmap' | 'gauge' | 'text' | 'barchart' | 'piechart' | 'histogram' | 'state-timeline' | 'status-history' | 'candlestick' | 'trend' | 'xychart' | 'logs' | 'datagrid' | 'nodeGraph' | 'traces' | 'flamegraph' | 'canvas' | 'geomap' | 'dashlist' | 'alertlist' | 'annolist' | 'news' | 'plugin' | 'business-variable';
|
|
13
|
+
/**
|
|
14
|
+
* Create a grafana-react component with type marker
|
|
15
|
+
*
|
|
16
|
+
* Components created this way don't render anything - they just
|
|
17
|
+
* carry their props and children for the renderer to process.
|
|
18
|
+
*/
|
|
19
|
+
export declare function createComponent<P extends object>(type: ComponentType): React.FC<P>;
|
|
20
|
+
/**
|
|
21
|
+
* Get the component type from a React element
|
|
22
|
+
*/
|
|
23
|
+
export declare function getComponentType(element: React.ReactElement): ComponentType | null;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base component utilities
|
|
3
|
+
*
|
|
4
|
+
* Provides the foundation for creating grafana-react components.
|
|
5
|
+
* Components don't render to DOM - they create a virtual tree
|
|
6
|
+
* that gets converted to Grafana JSON by the renderer.
|
|
7
|
+
*/
|
|
8
|
+
/** Symbol used to identify component types */
|
|
9
|
+
export const COMPONENT_TYPE = Symbol('grafana-component-type');
|
|
10
|
+
/**
|
|
11
|
+
* Create a grafana-react component with type marker
|
|
12
|
+
*
|
|
13
|
+
* Components created this way don't render anything - they just
|
|
14
|
+
* carry their props and children for the renderer to process.
|
|
15
|
+
*/
|
|
16
|
+
export function createComponent(type) {
|
|
17
|
+
const Component = () => null;
|
|
18
|
+
Component[COMPONENT_TYPE] = type;
|
|
19
|
+
Component.displayName = type.charAt(0).toUpperCase() + type.slice(1);
|
|
20
|
+
return Component;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Get the component type from a React element
|
|
24
|
+
*/
|
|
25
|
+
export function getComponentType(element) {
|
|
26
|
+
const type = element.type;
|
|
27
|
+
return type?.[COMPONENT_TYPE] ?? null;
|
|
28
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dashboard - Root container for Grafana dashboards
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* <Dashboard uid="my-dashboard" title="My Dashboard" tags={['tag1']}>
|
|
6
|
+
* <Variable name="instance" />
|
|
7
|
+
* <Row title="Summary">...</Row>
|
|
8
|
+
* </Dashboard>
|
|
9
|
+
*/
|
|
10
|
+
import type { ReactNode } from 'react';
|
|
11
|
+
export interface DashboardProps {
|
|
12
|
+
/** Unique identifier for the dashboard */
|
|
13
|
+
uid: string;
|
|
14
|
+
/** Dashboard title */
|
|
15
|
+
title: string;
|
|
16
|
+
/** Dashboard tags for filtering */
|
|
17
|
+
tags?: string[];
|
|
18
|
+
/** Default datasource UID for all panels */
|
|
19
|
+
datasource?: string;
|
|
20
|
+
/** Datasource type (e.g., 'prometheus', 'victoriametrics-metrics-datasource'). Defaults to 'prometheus' */
|
|
21
|
+
datasourceType?: string;
|
|
22
|
+
/** Auto-refresh interval (e.g., '5s', '1m', 'auto') */
|
|
23
|
+
refresh?: string | 'auto' | false;
|
|
24
|
+
/** Default time range (e.g., '1h', '6h', '24h') */
|
|
25
|
+
time?: string;
|
|
26
|
+
/** Timezone setting */
|
|
27
|
+
timezone?: 'browser' | 'utc';
|
|
28
|
+
/** Tooltip sharing mode */
|
|
29
|
+
tooltip?: 'shared' | 'single' | 'hidden';
|
|
30
|
+
/** Dashboard children */
|
|
31
|
+
children?: ReactNode;
|
|
32
|
+
}
|
|
33
|
+
export declare const Dashboard: import("react").FC<DashboardProps>;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dashboard - Root container for Grafana dashboards
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* <Dashboard uid="my-dashboard" title="My Dashboard" tags={['tag1']}>
|
|
6
|
+
* <Variable name="instance" />
|
|
7
|
+
* <Row title="Summary">...</Row>
|
|
8
|
+
* </Dashboard>
|
|
9
|
+
*/
|
|
10
|
+
import { createComponent } from '../base.js';
|
|
11
|
+
export const Dashboard = createComponent('dashboard');
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Component exports
|
|
3
|
+
*
|
|
4
|
+
* All components and their prop types are exported from here.
|
|
5
|
+
*/
|
|
6
|
+
export { COMPONENT_TYPE, getComponentType, createComponent } from './base.js';
|
|
7
|
+
export type { ComponentType } from './base.js';
|
|
8
|
+
export { Dashboard, type DashboardProps } from './dashboard/dashboard.js';
|
|
9
|
+
export { Row, type RowProps } from './row/row.js';
|
|
10
|
+
export { Variable, type VariableProps } from './variable/variable.js';
|
|
11
|
+
export { Annotation, type AnnotationProps } from './annotation/annotation.js';
|
|
12
|
+
export { Link, type LinkProps } from './link/link.js';
|
|
13
|
+
export { Query, type QueryProps } from './query/query.js';
|
|
14
|
+
export { Override, type OverrideProps } from './override/override.js';
|
|
15
|
+
export { PluginPanel, type PluginPanelProps, } from './plugin-panel/plugin-panel.js';
|
|
16
|
+
export { Stat, type StatProps } from './panels/core/stat/stat.js';
|
|
17
|
+
export { Timeseries, type TimeseriesProps, } from './panels/core/timeseries/timeseries.js';
|
|
18
|
+
export { Table, type TableProps } from './panels/core/table/table.js';
|
|
19
|
+
export { BarGauge, type BarGaugeProps, } from './panels/core/bar-gauge/bar-gauge.js';
|
|
20
|
+
export { Heatmap, type HeatmapProps } from './panels/core/heatmap/heatmap.js';
|
|
21
|
+
export { Gauge, type GaugeProps } from './panels/core/gauge/gauge.js';
|
|
22
|
+
export { Text, type TextProps } from './panels/core/text/text.js';
|
|
23
|
+
export { BarChart, type BarChartProps, } from './panels/core/bar-chart/bar-chart.js';
|
|
24
|
+
export { PieChart, type PieChartProps, } from './panels/core/pie-chart/pie-chart.js';
|
|
25
|
+
export { Histogram, type HistogramProps, } from './panels/core/histogram/histogram.js';
|
|
26
|
+
export { StateTimeline, type StateTimelineProps, } from './panels/core/state-timeline/state-timeline.js';
|
|
27
|
+
export { StatusHistory, type StatusHistoryProps, } from './panels/core/status-history/status-history.js';
|
|
28
|
+
export { Candlestick, type CandlestickProps, } from './panels/core/candlestick/candlestick.js';
|
|
29
|
+
export { Trend, type TrendProps } from './panels/core/trend/trend.js';
|
|
30
|
+
export { XYChart, type XYChartProps } from './panels/core/xy-chart/xy-chart.js';
|
|
31
|
+
export { Logs, type LogsProps } from './panels/core/logs/logs.js';
|
|
32
|
+
export { Datagrid, type DatagridProps, } from './panels/core/datagrid/datagrid.js';
|
|
33
|
+
export { NodeGraph, type NodeGraphProps, } from './panels/core/node-graph/node-graph.js';
|
|
34
|
+
export { Traces, type TracesProps } from './panels/core/traces/traces.js';
|
|
35
|
+
export { FlameGraph, type FlameGraphProps, } from './panels/core/flame-graph/flame-graph.js';
|
|
36
|
+
export { Canvas, type CanvasProps } from './panels/core/canvas/canvas.js';
|
|
37
|
+
export { Geomap, type GeomapProps } from './panels/core/geomap/geomap.js';
|
|
38
|
+
export { DashboardList, type DashboardListProps, } from './panels/core/dashboard-list/dashboard-list.js';
|
|
39
|
+
export { AlertList, type AlertListProps, } from './panels/core/alert-list/alert-list.js';
|
|
40
|
+
export { AnnotationsList, type AnnotationsListProps, } from './panels/core/annotations-list/annotations-list.js';
|
|
41
|
+
export { News, type NewsProps } from './panels/core/news/news.js';
|
|
42
|
+
export { BusinessVariablePanel, type BusinessVariablePanelProps, type TreeViewLevel, type VariableGroup, } from './panels/plugins/business-variable-panel/business-variable-panel.js';
|
|
43
|
+
import { Fragment } from 'react';
|
|
44
|
+
export { Fragment };
|