Skip to content

Commit 36356ee

Browse files
committed
Full configuration management via Admin API
1 parent 429f03a commit 36356ee

File tree

6 files changed

+191
-13
lines changed

6 files changed

+191
-13
lines changed

docs/admin-api.md

Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,14 +71,75 @@ All endpoints may return these errors:
7171

7272
### GET /v1/admin/config
7373

74-
Returns the entire current configuration object.
74+
Returns the entire current configuration as read from `config.toml`.
75+
76+
**Query Parameters:**
77+
78+
| Parameter | Type | Default | Description |
79+
| --------- | ------ | ------- | --------------------------------- |
80+
| `format` | string | `json` | Response format: `json` or `toml` |
81+
82+
**JSON example:**
7583

7684
```bash
7785
curl -H "Authorization: Bearer <token>" \
7886
http://localhost:3000/v1/admin/config
7987
```
8088

81-
**Response:** The full parsed `config.toml` as JSON, including all monitors, groups, status pages, notifications, pulse monitors, and server settings.
89+
**TOML example:**
90+
91+
```bash
92+
curl -H "Authorization: Bearer <token>" \
93+
"http://localhost:3000/v1/admin/config?format=toml"
94+
```
95+
96+
**Response:** The full parsed `config.toml`, including all monitors, groups, status pages, notifications, pulse monitors and server settings.
97+
98+
### POST /v1/admin/config
99+
100+
Replace the entire configuration. The body is validated, written to `config.toml`, and hot-reloaded. On reload failure, the previous configuration is automatically restored.
101+
102+
**Query Parameters:**
103+
104+
| Parameter | Type | Default | Description |
105+
| --------- | ------ | ------- | ------------------------------------- |
106+
| `format` | string | `json` | Request body format: `json` or `toml` |
107+
108+
**JSON example:**
109+
110+
```bash
111+
curl -X POST \
112+
-H "Authorization: Bearer <token>" \
113+
-H "Content-Type: application/json" \
114+
-d @config.json \
115+
http://localhost:3000/v1/admin/config
116+
```
117+
118+
**TOML example:**
119+
120+
```bash
121+
curl -X POST \
122+
-H "Authorization: Bearer <token>" \
123+
-H "Content-Type: application/toml" \
124+
-d @config.toml \
125+
"http://localhost:3000/v1/admin/config?format=toml"
126+
```
127+
128+
**Success Response:**
129+
130+
```json
131+
{ "success": true }
132+
```
133+
134+
**Error Responses:**
135+
136+
| Status | Description |
137+
| ------ | ------------------------------------------------------- |
138+
| `400` | Invalid request body or configuration validation failed |
139+
| `401` | Unauthorized |
140+
| `500` | Config write or reload failed |
141+
142+
> **Tip:** You can GET the config, modify it, and POST it back to make bulk changes safely. Both endpoints use the same key format, so round-tripping works without any transformation.
82143
83144
---
84145

@@ -1139,6 +1200,7 @@ All incident operations broadcast real-time events to WebSocket subscribers of t
11391200
| Method | Endpoint | Description |
11401201
| -------- | ------------------------------------------- | ------------------------------ |
11411202
| `GET` | `/v1/admin/config` | Get full configuration |
1203+
| `POST` | `/v1/admin/config` | Replace full configuration |
11421204
| `GET` | `/v1/admin/monitors` | List all monitors |
11431205
| `GET` | `/v1/admin/monitors/:id` | Get a monitor |
11441206
| `POST` | `/v1/admin/monitors` | Create a monitor |

docs/api.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -534,7 +534,7 @@ See the [Admin API Reference](admin-api.md) for complete documentation of all en
534534

535535
| Resource | Endpoints |
536536
| --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
537-
| Configuration | `GET /v1/admin/config` |
537+
| Configuration | `GET/POST /v1/admin/config` |
538538
| Monitors | `GET/POST /v1/admin/monitors`, `GET/PUT/DELETE /v1/admin/monitors/:id` |
539539
| Groups | `GET/POST /v1/admin/groups`, `GET/PUT/DELETE /v1/admin/groups/:id` |
540540
| Status Pages | `GET/POST /v1/admin/status-pages`, `GET/PUT/DELETE /v1/admin/status-pages/:id` |

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "uptimemonitor-server",
33
"module": "src/index.ts",
4-
"version": "0.4.4",
4+
"version": "0.4.5",
55
"type": "module",
66
"private": true,
77
"scripts": {

src/admin/config.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import type { Server, Web } from "@rabbit-company/web";
2+
import { adminBearerAuth, readRawConfig, validateConfig, writeAndReload } from "./helpers";
3+
import { Logger } from "../logger";
4+
import TOML from "smol-toml";
5+
6+
export function registerConfigRoutes(app: Web, getServer: () => Server): void {
7+
app.get("/v1/admin/config", adminBearerAuth(), async (ctx) => {
8+
const query = ctx.query();
9+
const raw = await readRawConfig();
10+
11+
if (query.get("format") === "toml") {
12+
return ctx.text(TOML.stringify(raw), 200, { "Content-Type": "application/toml; charset=utf-8" });
13+
}
14+
15+
return ctx.json(raw);
16+
});
17+
18+
app.post("/v1/admin/config", adminBearerAuth(), async (ctx) => {
19+
const format = ctx.query().get("format")?.toLowerCase();
20+
21+
let raw: any;
22+
23+
try {
24+
if (format === "toml") {
25+
const text = await ctx.req.text();
26+
raw = Bun.TOML.parse(text);
27+
} else {
28+
raw = await ctx.req.json();
29+
}
30+
} catch (e: any) {
31+
return ctx.json({ error: "Invalid request body", details: e?.message }, 400);
32+
}
33+
34+
const validationErrors = validateConfig(raw);
35+
if (validationErrors) {
36+
return ctx.json({ error: "Configuration validation failed", details: validationErrors }, 400);
37+
}
38+
39+
try {
40+
await writeAndReload(raw, getServer);
41+
Logger.audit("Admin API: Config updated");
42+
43+
return ctx.json({ success: true });
44+
} catch (e: any) {
45+
Logger.error("Admin API: Failed to update config", { error: e?.message });
46+
return ctx.json({ error: e?.message }, 500);
47+
}
48+
});
49+
}

src/admin/index.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
import type { Web, Server } from "@rabbit-company/web";
22
import { Logger } from "../logger";
3-
import { config } from "../config";
43
import { registerMonitorRoutes } from "./monitors";
5-
import { adminBearerAuth } from "./helpers";
64
import { registerGroupRoutes } from "./groups";
75
import { registerStatusPageRoutes } from "./status-pages";
86
import { registerPulseMonitorRoutes } from "./pulse-monitors";
97
import { registerNotificationRoutes } from "./notifications";
108
import { registerAdminReportRoutes } from "./reports";
119
import { registerIncidentRoutes } from "./incidents";
10+
import { registerConfigRoutes } from "./config";
1211

1312
export function registerAdminAPI(app: Web, getServer: () => Server): void {
13+
registerConfigRoutes(app, getServer);
1414
registerMonitorRoutes(app, getServer);
1515
registerGroupRoutes(app, getServer);
1616
registerStatusPageRoutes(app, getServer);
@@ -19,9 +19,5 @@ export function registerAdminAPI(app: Web, getServer: () => Server): void {
1919
registerAdminReportRoutes(app);
2020
registerIncidentRoutes(app, getServer);
2121

22-
app.get("/v1/admin/config", adminBearerAuth(), (ctx) => {
23-
return ctx.json(config);
24-
});
25-
2622
Logger.info("Admin API registered", { prefix: "/v1/admin" });
2723
}

src/openapi.ts

Lines changed: 74 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1001,18 +1001,32 @@ export const openapi = {
10011001
get: {
10021002
tags: ["Admin: Configuration"],
10031003
summary: "Get full configuration",
1004-
description: "Returns the entire current server configuration. Requires Admin API to be enabled.",
1004+
description: "Returns the entire current configuration as read from config.toml. Use ?format=toml to get TOML instead of JSON.",
10051005
operationId: "adminGetConfig",
10061006
security: [{ adminBearerAuth: [] }],
1007+
parameters: [
1008+
{
1009+
name: "format",
1010+
in: "query",
1011+
required: false,
1012+
description: "Response format: json (default) or toml",
1013+
schema: { type: "string", enum: ["json", "toml"], default: "json" },
1014+
},
1015+
],
10071016
responses: {
10081017
"200": {
10091018
description: "Full server configuration",
10101019
content: {
10111020
"application/json": {
10121021
schema: {
10131022
type: "object",
1014-
description:
1015-
"The complete parsed config.toml as JSON, including all monitors, groups, status pages, notifications, pulse monitors, and server settings.",
1023+
description: "The complete parsed config.toml as JSON.",
1024+
},
1025+
},
1026+
"application/toml": {
1027+
schema: {
1028+
type: "string",
1029+
description: "The complete config as TOML.",
10161030
},
10171031
},
10181032
},
@@ -1023,6 +1037,63 @@ export const openapi = {
10231037
},
10241038
},
10251039
},
1040+
post: {
1041+
tags: ["Admin: Configuration"],
1042+
summary: "Replace full configuration",
1043+
description:
1044+
"Replace the entire configuration. The body is validated, written to config.toml, and hot-reloaded. On failure, the previous configuration is automatically restored. Use ?format=toml to send TOML instead of JSON.",
1045+
operationId: "adminUpdateConfig",
1046+
security: [{ adminBearerAuth: [] }],
1047+
parameters: [
1048+
{
1049+
name: "format",
1050+
in: "query",
1051+
required: false,
1052+
description: "Request body format: json (default) or toml",
1053+
schema: { type: "string", enum: ["json", "toml"], default: "json" },
1054+
},
1055+
],
1056+
requestBody: {
1057+
required: true,
1058+
content: {
1059+
"application/json": {
1060+
schema: {
1061+
type: "object",
1062+
description: "The full configuration object with the same structure as config.toml.",
1063+
},
1064+
},
1065+
"application/toml": {
1066+
schema: {
1067+
type: "string",
1068+
description: "The full configuration as TOML.",
1069+
},
1070+
},
1071+
},
1072+
},
1073+
responses: {
1074+
"200": {
1075+
description: "Configuration updated successfully",
1076+
content: {
1077+
"application/json": {
1078+
schema: { $ref: "#/components/schemas/AdminSuccessSimple" },
1079+
example: { success: true },
1080+
},
1081+
},
1082+
},
1083+
"400": {
1084+
description: "Invalid request body or configuration validation failed",
1085+
content: { "application/json": { schema: { $ref: "#/components/schemas/AdminValidationError" } } },
1086+
},
1087+
"401": {
1088+
description: "Unauthorized",
1089+
content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } },
1090+
},
1091+
"500": {
1092+
description: "Config write or reload failed",
1093+
content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } },
1094+
},
1095+
},
1096+
},
10261097
},
10271098
"/v1/admin/monitors": {
10281099
get: {

0 commit comments

Comments
 (0)