-
Notifications
You must be signed in to change notification settings - Fork 189
Redesign the dashboard entirely #207
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 175 commits
7a88f7d
618082d
32a40b4
06533e3
18f830a
e79d7b9
7c6b9b2
4e0d33b
5838094
6eed36d
6e43eae
8e6ff00
0e37c02
5d013dc
e9794a2
aed273a
51ec006
de032bb
9d820c2
5f8491a
29d2a60
dd57343
d513153
88d5928
40c0d0b
8a366d5
2ee2427
9fdc926
1e18e69
0d31d50
2ddc71d
9d42e68
283c1e4
4223aea
562b95c
f7e7a0e
37245f3
ee24943
caf970a
6cc3781
650796b
39cee7c
78b5be8
306ba9b
a4fce80
1b5556d
367e74a
7d1be41
a9696e3
e7cd7bb
0258dd3
459997e
f82bce0
5622c94
6bfdfa1
926941c
fe8fec3
bcee563
de21750
34970d6
96f5417
5290b41
b234259
d46e5b7
1e1daf6
2c425d1
f63952d
68b3b90
a91f0ce
06deb71
80aac14
a99f55b
645b942
bc2fcc4
3477a4e
f900a3f
84f37f8
7851293
79f034e
0d396ef
0bff9a4
ebb18e1
eec8048
89d47ee
182f622
8029ee2
23b9aa7
f8a2207
a273798
6091a82
adcdf5f
118e728
a4e6488
aad31e4
8c140a6
8f9872b
a305929
a599566
c1ef626
3bb33a1
52384aa
17c2a6e
4031949
d05bbde
d8446b8
290f843
2519376
8efe510
8f1b42e
196e417
4f3187f
4900fd0
27b7b76
aae3553
6bb43d8
fb99d8a
6a59846
933e9ab
9399f29
a75b62f
950ce1a
1817f3d
090e015
a780ea3
a4f9626
b12c431
d070f39
f61466d
6a90847
6a4c8fa
c46e244
6540ae6
7fb55f2
f8bca90
a654a60
d377b23
1af35d8
88f53f0
d619922
4b181bf
55d490d
a53f1d8
9bad343
bb59e9b
7bd8046
83ed542
d524d44
c7daf79
9d7c886
0a32dfd
f232373
98b929c
0d1e1fc
893eee4
aed4662
183ae2e
4ff225d
72dce3d
0625f64
38d264f
7dd54f6
562e059
198906b
7ed9f7f
415ea49
a0fd493
3c7f056
e03aa04
8f90ad2
cb4727f
0ba611c
2ddb802
f83f430
46fe4dd
e1e2541
11d7ffa
3ec6b05
43464fb
f637576
691906b
949003d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -88,18 +88,23 @@ async function getBackupMetadata(backupName: string): Promise<{ | |
| num_files: number; | ||
| } | null> { | ||
| try { | ||
| // Escape LIKE special characters: backslashes first, then % and _ | ||
| const escaped = backupName.replace(/\\/g, "\\\\").replace(/[%_]/g, "\\$&"); | ||
| const result = await clickHouseOG.query({ | ||
| query: ` | ||
| SELECT | ||
| total_size as size_bytes, | ||
| compressed_size as compressed_size_bytes, | ||
| num_files | ||
| FROM system.backups | ||
| WHERE name LIKE '%${backupName}%' | ||
| WHERE name LIKE {pattern:String} | ||
| AND status = 'BACKUP_CREATED' | ||
| ORDER BY start_time DESC | ||
| LIMIT 1 | ||
| `, | ||
| query_params: { | ||
| pattern: `%${escaped}%`, | ||
| }, | ||
| }); | ||
|
|
||
| const data = await result.json<{ | ||
|
|
@@ -160,7 +165,9 @@ export async function performClickHouseBackup(): Promise<BackupResult> { | |
|
|
||
| await ensureBackupTable(); | ||
|
|
||
| const s3Url = `${env.R2_ENDPOINT}/${env.R2_BUCKET}/${backupName}/`; | ||
| // Normalize endpoint (remove trailing slashes) and build path-style URL for Tigris/S3-compatible storage | ||
| const endpoint = env.R2_ENDPOINT.replace(/\/+$/, ''); | ||
| const s3Url = `${endpoint}/${env.R2_BUCKET}/${backupName}`; | ||
| const lastBackupPath = await getLastBackupPath(); | ||
| const isIncremental = lastBackupPath !== null; | ||
|
|
||
|
|
@@ -192,7 +199,8 @@ export async function performClickHouseBackup(): Promise<BackupResult> { | |
| s3_max_put_burst = 50, | ||
| compression_method = 'zstd', | ||
| compression_level = 3, | ||
| max_backup_bandwidth = 104857600${baseBackupSetting} | ||
| max_backup_bandwidth = 104857600, | ||
| s3_use_virtual_addressing = false${baseBackupSetting} | ||
| `; | ||
|
|
||
| await backupClickHouse.command({ query: backupQuery }); | ||
|
|
@@ -404,7 +412,8 @@ export async function restoreBackup( | |
| FROM S3('${backupPath}', '${env.R2_ACCESS_KEY_ID}', '${env.R2_SECRET_ACCESS_KEY}') | ||
| SETTINGS | ||
| async = false, | ||
| s3_max_redirects = 10 | ||
| s3_max_redirects = 10, | ||
| s3_use_virtual_addressing = false | ||
| `; | ||
|
Comment on lines
+405
to
417
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. SQL injection vulnerability: The Add input validation for all parameters: export async function restoreBackup(
backupPath: string,
options: {
tables?: string[];
database?: string;
} = {}
): Promise<BackupResult> {
const timestamp = new Date().toISOString();
+
+ // Validate backupPath format (should be an S3 URL)
+ if (!/^https?:\/\/[^'"\s]+$/.test(backupPath)) {
+ return {
+ success: false,
+ error: "Invalid backup path format",
+ timestamp,
+ };
+ }
+
+ // Validate database and table names (alphanumeric and underscores only)
+ const identifierRegex = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
+ const database = options.database ?? "analytics";
+ if (!identifierRegex.test(database)) {
+ return {
+ success: false,
+ error: "Invalid database name",
+ timestamp,
+ };
+ }
+ if (options.tables?.some((t) => !identifierRegex.test(t))) {
+ return {
+ success: false,
+ error: "Invalid table name",
+ timestamp,
+ };
+ }
|
||
|
|
||
| await backupClickHouse.command({ query: restoreQuery }); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Consider making
max_backup_bandwidthconfigurable.The hardcoded 100 MiB/s (104857600 bytes) bandwidth limit is reasonable, but different environments may have varying network constraints. Consider exposing this as an optional environment variable with this as the default.
🤖 Prompt for AI Agents