@davidfuchs/mcp-uptime-kuma 0.7.0 → 0.10.0

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.
@@ -1,6 +1,6 @@
1
1
  import { Socket } from 'socket.io-client';
2
2
  import type { LoggingLevel } from '@modelcontextprotocol/sdk/types.js';
3
- import type { MonitorBaseWithExtendedData, MonitorWithExtendedData, ApiResponse, LoginResponse, MonitorList, MonitorSummary, Heartbeat, GetSettingsResponse, Notification, Maintenance, StatusPage } from './types/index.js';
3
+ import type { MonitorBaseWithExtendedData, MonitorWithExtendedData, ApiResponse, LoginResponse, MonitorList, MonitorSummary, Heartbeat, GetSettingsResponse, Notification, Maintenance, StatusPage, DockerHost } from './types/index.js';
4
4
  /**
5
5
  * Uptime Kuma Socket.io API Client
6
6
  */
@@ -15,6 +15,7 @@ export declare class UptimeKumaClient {
15
15
  private tagListCache;
16
16
  private maintenanceListCache;
17
17
  private statusPageListCache;
18
+ private dockerHostListCache;
18
19
  private server?;
19
20
  private shouldLog;
20
21
  private loginCredentials;
@@ -148,8 +149,11 @@ export declare class UptimeKumaClient {
148
149
  private setupTagListListeners;
149
150
  private setupMaintenanceListListeners;
150
151
  private setupStatusPageListListeners;
152
+ private setupDockerHostListListeners;
151
153
  /**
152
- * Create a new monitor
154
+ * Create a new monitor. If `tags` are supplied, they are applied after
155
+ * creation using the separate `addMonitorTag` socket event — the `add`
156
+ * socket handler does not process tags.
153
157
  *
154
158
  * @param monitorData - Monitor configuration (type-specific fields should be included)
155
159
  * @returns Promise resolving to the API response with the new monitorID
@@ -158,7 +162,11 @@ export declare class UptimeKumaClient {
158
162
  monitorID?: number;
159
163
  }>;
160
164
  /**
161
- * Update an existing monitor
165
+ * Update an existing monitor. If `tags` are supplied, they are reconciled
166
+ * against the monitor's current tag set using `addMonitorTag` /
167
+ * `deleteMonitorTag` — the `editMonitor` socket handler does not process
168
+ * tags. Tags whose name is not yet in the catalog are auto-created via
169
+ * `addTag` before binding.
162
170
  *
163
171
  * @param monitorData - Monitor configuration including the id field
164
172
  * @returns Promise resolving to the API response
@@ -166,6 +174,26 @@ export declare class UptimeKumaClient {
166
174
  updateMonitor(monitorData: Record<string, unknown>): Promise<ApiResponse & {
167
175
  monitorID?: number;
168
176
  }>;
177
+ /**
178
+ * Fetch the tag catalog synchronously from the server. Uptime Kuma does
179
+ * not push `tagList` events — it only responds to the `getTags` request —
180
+ * so relying on the push-populated cache returns stale (often empty) data.
181
+ * This method also refreshes `tagListCache` so subsequent cache reads work.
182
+ */
183
+ private fetchTagList;
184
+ /**
185
+ * Bind a tag to a monitor (socket: `addMonitorTag`).
186
+ */
187
+ private addMonitorTag;
188
+ /**
189
+ * Unbind a tag from a monitor (socket: `deleteMonitorTag`).
190
+ */
191
+ private deleteMonitorTag;
192
+ /**
193
+ * Reconcile a monitor's tags against the desired list. Auto-creates any
194
+ * tag name that isn't yet in the catalog. Tag identity is `(name, value)`.
195
+ */
196
+ private reconcileMonitorTags;
169
197
  /**
170
198
  * Delete a monitor
171
199
  *
@@ -195,13 +223,45 @@ export declare class UptimeKumaClient {
195
223
  */
196
224
  deleteNotification(notificationID: number): Promise<ApiResponse>;
197
225
  /**
198
- * Get the cached tag list
226
+ * Get the cached docker host list
227
+ */
228
+ getDockerHostList(): DockerHost[];
229
+ /**
230
+ * Add or update a docker host
231
+ *
232
+ * @param dockerHost - Docker host configuration (name, dockerType, dockerDaemon)
233
+ * @param dockerHostID - If provided, updates existing; otherwise creates new
234
+ * @returns Promise resolving to the API response with the docker host id
235
+ */
236
+ addDockerHost(dockerHost: Record<string, unknown>, dockerHostID?: number): Promise<ApiResponse & {
237
+ id?: number;
238
+ }>;
239
+ /**
240
+ * Delete a docker host. Any monitors referencing it will have their docker_host
241
+ * field cleared by Uptime Kuma.
242
+ *
243
+ * @param dockerHostID - The ID of the docker host to delete
244
+ * @returns Promise resolving to the API response
245
+ */
246
+ deleteDockerHost(dockerHostID: number): Promise<ApiResponse>;
247
+ /**
248
+ * Test connectivity to a docker host without persisting it. Returns a friendly
249
+ * message containing the number of containers when reachable.
250
+ *
251
+ * @param dockerHost - Docker host configuration to test (name, dockerType, dockerDaemon)
252
+ * @returns Promise resolving to the API response
199
253
  */
200
- getTagList(): Array<{
254
+ testDockerHost(dockerHost: Record<string, unknown>): Promise<ApiResponse>;
255
+ /**
256
+ * Get the tag list. Actively fetches from the server since Uptime Kuma
257
+ * does not push `tagList` events on login (issue #46).
258
+ * Falls back to the cache if the socket is not connected.
259
+ */
260
+ getTagList(): Promise<Array<{
201
261
  id: number;
202
262
  name: string;
203
263
  color: string;
204
- }>;
264
+ }>>;
205
265
  /**
206
266
  * Create a new tag
207
267
  *
@@ -240,6 +300,48 @@ export declare class UptimeKumaClient {
240
300
  * Get the cached status page list
241
301
  */
242
302
  getStatusPageList(): StatusPage[];
303
+ /**
304
+ * Get full details of a single status page, including publicGroupList with monitors
305
+ * and any active incidents. Uses the public HTTP API (`/api/status-page/{slug}`),
306
+ * which returns the same data the status page UI renders — richer than the
307
+ * socket `getStatusPage` event, which only returns config.
308
+ *
309
+ * @param slug - The status page slug
310
+ * @returns Promise resolving to the status page config, groups, and incidents
311
+ */
312
+ getStatusPage(slug: string): Promise<ApiResponse & {
313
+ config?: StatusPage;
314
+ publicGroupList?: unknown[];
315
+ incidents?: unknown[];
316
+ }>;
317
+ /**
318
+ * Create a new (empty) status page with the given title and slug
319
+ *
320
+ * Note: This creates a blank status page. Use updateStatusPage afterwards to
321
+ * set description, theme, groups, monitors, etc.
322
+ *
323
+ * @param title - Display title of the status page
324
+ * @param slug - URL slug (lowercase letters, digits, and dashes only)
325
+ * @returns Promise resolving to the API response
326
+ */
327
+ createStatusPage(title: string, slug: string): Promise<ApiResponse>;
328
+ /**
329
+ * Update an existing status page's config and group/monitor list
330
+ *
331
+ * @param slug - The status page slug (immutable identifier)
332
+ * @param config - Status page configuration (title, description, theme, published, etc.)
333
+ * @param publicGroupList - Ordered groups, each with a name, weight, and monitorList `[{id}]`
334
+ * @param imgDataUrl - Optional icon as data URL (pass empty string to keep existing)
335
+ * @returns Promise resolving to the API response
336
+ */
337
+ updateStatusPage(slug: string, config: Record<string, unknown>, publicGroupList?: Array<Record<string, unknown>>, imgDataUrl?: string): Promise<ApiResponse>;
338
+ /**
339
+ * Delete a status page
340
+ *
341
+ * @param slug - The status page slug
342
+ * @returns Promise resolving to the API response
343
+ */
344
+ deleteStatusPage(slug: string): Promise<ApiResponse>;
243
345
  /**
244
346
  * Get the socket instance (for advanced usage)
245
347
  */
@@ -1 +1 @@
1
- {"version":3,"file":"uptime-kuma-client.d.ts","sourceRoot":"","sources":["../src/uptime-kuma-client.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"uptime-kuma-client.d.ts","sourceRoot":"","sources":["../src/uptime-kuma-client.ts"],"names":[],"mappings":"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"}
@@ -30,6 +30,7 @@ export class UptimeKumaClient {
30
30
  tagListCache = [];
31
31
  maintenanceListCache = {};
32
32
  statusPageListCache = {};
33
+ dockerHostListCache = [];
33
34
  server;
34
35
  shouldLog;
35
36
  loginCredentials = null;
@@ -138,6 +139,7 @@ export class UptimeKumaClient {
138
139
  this.socket.off('tagList');
139
140
  this.socket.off('maintenanceList');
140
141
  this.socket.off('statusPageList');
142
+ this.socket.off('dockerHostList');
141
143
  this.socket.disconnect();
142
144
  this.socket = null;
143
145
  }
@@ -150,6 +152,7 @@ export class UptimeKumaClient {
150
152
  this.tagListCache = [];
151
153
  this.maintenanceListCache = {};
152
154
  this.statusPageListCache = {};
155
+ this.dockerHostListCache = [];
153
156
  }
154
157
  /**
155
158
  * Login using username and password, or JWT token
@@ -177,6 +180,7 @@ export class UptimeKumaClient {
177
180
  this.setupTagListListeners();
178
181
  this.setupMaintenanceListListeners();
179
182
  this.setupStatusPageListListeners();
183
+ this.setupDockerHostListListeners();
180
184
  // If JWT token is provided, use token-based authentication
181
185
  if (jwtToken) {
182
186
  this.socket.emit('loginByToken', jwtToken, (response) => {
@@ -611,8 +615,9 @@ export class UptimeKumaClient {
611
615
  if (!this.socket)
612
616
  return;
613
617
  this.socket.on('tagList', (tagList) => {
614
- this.safeLog('debug', `Received tagList with ${tagList.length} tags`);
615
- this.tagListCache = tagList;
618
+ const tags = Array.isArray(tagList) ? tagList : Object.values(tagList);
619
+ this.safeLog('debug', `Received tagList with ${tags.length} tags`);
620
+ this.tagListCache = tags;
616
621
  });
617
622
  }
618
623
  setupMaintenanceListListeners() {
@@ -631,53 +636,177 @@ export class UptimeKumaClient {
631
636
  this.statusPageListCache = statusPageList;
632
637
  });
633
638
  }
639
+ setupDockerHostListListeners() {
640
+ if (!this.socket)
641
+ return;
642
+ this.socket.on('dockerHostList', (dockerHostList) => {
643
+ this.safeLog('debug', `Received dockerHostList with ${dockerHostList.length} docker hosts`);
644
+ this.dockerHostListCache = dockerHostList;
645
+ });
646
+ }
634
647
  // ─── Monitor write operations ───────────────────────────────────────────────
635
648
  /**
636
- * Create a new monitor
649
+ * Create a new monitor. If `tags` are supplied, they are applied after
650
+ * creation using the separate `addMonitorTag` socket event — the `add`
651
+ * socket handler does not process tags.
637
652
  *
638
653
  * @param monitorData - Monitor configuration (type-specific fields should be included)
639
654
  * @returns Promise resolving to the API response with the new monitorID
640
655
  */
641
- createMonitor(monitorData) {
642
- return new Promise((resolve, reject) => {
656
+ async createMonitor(monitorData) {
657
+ const { tags, ...payload } = monitorData;
658
+ const response = await new Promise((resolve, reject) => {
643
659
  if (!this.socket || !this.socket.connected) {
644
660
  reject(new Error('Not connected to server'));
645
661
  return;
646
662
  }
647
- this.socket.emit('add', monitorData, (response) => {
648
- if (response.ok) {
649
- this.safeLog('info', `Successfully created monitor (ID: ${response.monitorID})`);
650
- resolve(response);
663
+ this.socket.emit('add', payload, (res) => {
664
+ if (res.ok) {
665
+ this.safeLog('info', `Successfully created monitor (ID: ${res.monitorID})`);
666
+ resolve(res);
651
667
  }
652
668
  else {
653
- reject(new Error(response.msg || 'Failed to create monitor'));
669
+ reject(new Error(res.msg || 'Failed to create monitor'));
654
670
  }
655
671
  });
656
672
  });
673
+ if (tags && Array.isArray(tags) && response.monitorID != null) {
674
+ await this.reconcileMonitorTags(response.monitorID, tags);
675
+ }
676
+ return response;
657
677
  }
658
678
  /**
659
- * Update an existing monitor
679
+ * Update an existing monitor. If `tags` are supplied, they are reconciled
680
+ * against the monitor's current tag set using `addMonitorTag` /
681
+ * `deleteMonitorTag` — the `editMonitor` socket handler does not process
682
+ * tags. Tags whose name is not yet in the catalog are auto-created via
683
+ * `addTag` before binding.
660
684
  *
661
685
  * @param monitorData - Monitor configuration including the id field
662
686
  * @returns Promise resolving to the API response
663
687
  */
664
- updateMonitor(monitorData) {
665
- return new Promise((resolve, reject) => {
688
+ async updateMonitor(monitorData) {
689
+ const { tags, ...payload } = monitorData;
690
+ const response = await new Promise((resolve, reject) => {
666
691
  if (!this.socket || !this.socket.connected) {
667
692
  reject(new Error('Not connected to server'));
668
693
  return;
669
694
  }
670
- this.socket.emit('editMonitor', monitorData, (response) => {
671
- if (response.ok) {
695
+ this.socket.emit('editMonitor', payload, (res) => {
696
+ if (res.ok) {
672
697
  this.safeLog('info', `Successfully updated monitor (ID: ${monitorData['id']})`);
673
- resolve(response);
698
+ resolve(res);
699
+ }
700
+ else {
701
+ reject(new Error(res.msg || 'Failed to update monitor'));
702
+ }
703
+ });
704
+ });
705
+ if (tags && Array.isArray(tags) && monitorData['id'] != null) {
706
+ await this.reconcileMonitorTags(Number(monitorData['id']), tags);
707
+ }
708
+ return response;
709
+ }
710
+ /**
711
+ * Fetch the tag catalog synchronously from the server. Uptime Kuma does
712
+ * not push `tagList` events — it only responds to the `getTags` request —
713
+ * so relying on the push-populated cache returns stale (often empty) data.
714
+ * This method also refreshes `tagListCache` so subsequent cache reads work.
715
+ */
716
+ fetchTagList() {
717
+ return new Promise((resolve, reject) => {
718
+ if (!this.socket || !this.socket.connected) {
719
+ reject(new Error('Not connected to server'));
720
+ return;
721
+ }
722
+ this.socket.emit('getTags', (res) => {
723
+ if (res.ok && res.tags) {
724
+ this.tagListCache = res.tags;
725
+ resolve(res.tags);
674
726
  }
675
727
  else {
676
- reject(new Error(response.msg || 'Failed to update monitor'));
728
+ reject(new Error(res.msg || 'Failed to fetch tag list'));
677
729
  }
678
730
  });
679
731
  });
680
732
  }
733
+ /**
734
+ * Bind a tag to a monitor (socket: `addMonitorTag`).
735
+ */
736
+ addMonitorTag(tagID, monitorID, value) {
737
+ return new Promise((resolve, reject) => {
738
+ if (!this.socket || !this.socket.connected) {
739
+ reject(new Error('Not connected to server'));
740
+ return;
741
+ }
742
+ this.socket.emit('addMonitorTag', tagID, monitorID, value, (res) => {
743
+ if (res.ok)
744
+ resolve(res);
745
+ else
746
+ reject(new Error(res.msg || `Failed to add tag ${tagID} to monitor ${monitorID}`));
747
+ });
748
+ });
749
+ }
750
+ /**
751
+ * Unbind a tag from a monitor (socket: `deleteMonitorTag`).
752
+ */
753
+ deleteMonitorTag(tagID, monitorID, value) {
754
+ return new Promise((resolve, reject) => {
755
+ if (!this.socket || !this.socket.connected) {
756
+ reject(new Error('Not connected to server'));
757
+ return;
758
+ }
759
+ this.socket.emit('deleteMonitorTag', tagID, monitorID, value, (res) => {
760
+ if (res.ok)
761
+ resolve(res);
762
+ else
763
+ reject(new Error(res.msg || `Failed to remove tag ${tagID} from monitor ${monitorID}`));
764
+ });
765
+ });
766
+ }
767
+ /**
768
+ * Reconcile a monitor's tags against the desired list. Auto-creates any
769
+ * tag name that isn't yet in the catalog. Tag identity is `(name, value)`.
770
+ */
771
+ async reconcileMonitorTags(monitorID, desiredTags) {
772
+ const currentMonitor = this.monitorListCache[String(monitorID)];
773
+ const currentTags = (currentMonitor?.tags ?? []);
774
+ const key = (name, value) => `${name}\u0000${value ?? ''}`;
775
+ const currentKeys = new Set(currentTags.map((t) => key(t.name, t.value)));
776
+ const desiredKeys = new Set(desiredTags.map((t) => key(String(t.name), t.value)));
777
+ // Uptime Kuma never pushes `tagList` events, so the cache is unreliable
778
+ // — fetch the catalog synchronously via the `getTags` socket request.
779
+ const freshTags = await this.fetchTagList();
780
+ const nameToID = new Map();
781
+ for (const t of freshTags)
782
+ nameToID.set(t.name, t.id);
783
+ for (const desired of desiredTags) {
784
+ const name = String(desired.name);
785
+ const value = desired.value ?? '';
786
+ if (currentKeys.has(key(name, value)))
787
+ continue;
788
+ let tagID = nameToID.get(name);
789
+ if (tagID == null) {
790
+ const color = desired.color ?? '#808080';
791
+ const created = await this.addTag(name, color);
792
+ tagID = created.tag?.id;
793
+ if (tagID != null)
794
+ nameToID.set(name, tagID);
795
+ }
796
+ if (tagID == null) {
797
+ throw new Error(`Could not resolve tag ID for "${name}"`);
798
+ }
799
+ await this.addMonitorTag(tagID, monitorID, value);
800
+ }
801
+ for (const existing of currentTags) {
802
+ if (desiredKeys.has(key(existing.name, existing.value)))
803
+ continue;
804
+ const tagID = existing.tag_id ?? nameToID.get(existing.name);
805
+ if (tagID == null)
806
+ continue;
807
+ await this.deleteMonitorTag(tagID, monitorID, existing.value ?? '');
808
+ }
809
+ }
681
810
  /**
682
811
  * Delete a monitor
683
812
  *
@@ -756,12 +885,96 @@ export class UptimeKumaClient {
756
885
  });
757
886
  });
758
887
  }
888
+ // ─── Docker host operations ─────────────────────────────────────────────────
889
+ /**
890
+ * Get the cached docker host list
891
+ */
892
+ getDockerHostList() {
893
+ return this.dockerHostListCache;
894
+ }
895
+ /**
896
+ * Add or update a docker host
897
+ *
898
+ * @param dockerHost - Docker host configuration (name, dockerType, dockerDaemon)
899
+ * @param dockerHostID - If provided, updates existing; otherwise creates new
900
+ * @returns Promise resolving to the API response with the docker host id
901
+ */
902
+ addDockerHost(dockerHost, dockerHostID) {
903
+ return new Promise((resolve, reject) => {
904
+ if (!this.socket || !this.socket.connected) {
905
+ reject(new Error('Not connected to server'));
906
+ return;
907
+ }
908
+ const id = dockerHostID ?? null;
909
+ this.socket.emit('addDockerHost', dockerHost, id, (response) => {
910
+ if (response.ok) {
911
+ this.safeLog('info', `Successfully saved docker host (ID: ${response.id})`);
912
+ resolve(response);
913
+ }
914
+ else {
915
+ reject(new Error(response.msg || 'Failed to save docker host'));
916
+ }
917
+ });
918
+ });
919
+ }
920
+ /**
921
+ * Delete a docker host. Any monitors referencing it will have their docker_host
922
+ * field cleared by Uptime Kuma.
923
+ *
924
+ * @param dockerHostID - The ID of the docker host to delete
925
+ * @returns Promise resolving to the API response
926
+ */
927
+ deleteDockerHost(dockerHostID) {
928
+ return new Promise((resolve, reject) => {
929
+ if (!this.socket || !this.socket.connected) {
930
+ reject(new Error('Not connected to server'));
931
+ return;
932
+ }
933
+ this.socket.emit('deleteDockerHost', dockerHostID, (response) => {
934
+ if (response.ok) {
935
+ this.safeLog('info', `Successfully deleted docker host ${dockerHostID}`);
936
+ resolve(response);
937
+ }
938
+ else {
939
+ reject(new Error(response.msg || 'Failed to delete docker host'));
940
+ }
941
+ });
942
+ });
943
+ }
944
+ /**
945
+ * Test connectivity to a docker host without persisting it. Returns a friendly
946
+ * message containing the number of containers when reachable.
947
+ *
948
+ * @param dockerHost - Docker host configuration to test (name, dockerType, dockerDaemon)
949
+ * @returns Promise resolving to the API response
950
+ */
951
+ testDockerHost(dockerHost) {
952
+ return new Promise((resolve, reject) => {
953
+ if (!this.socket || !this.socket.connected) {
954
+ reject(new Error('Not connected to server'));
955
+ return;
956
+ }
957
+ this.socket.emit('testDockerHost', dockerHost, (response) => {
958
+ // Resolve either way so callers can inspect ok/msg without try/catch
959
+ // (matches the pattern used by UK's UI, which shows both success and
960
+ // failure messages from the same callback).
961
+ resolve(response);
962
+ });
963
+ });
964
+ }
759
965
  // ─── Tag operations ─────────────────────────────────────────────────────────
760
966
  /**
761
- * Get the cached tag list
967
+ * Get the tag list. Actively fetches from the server since Uptime Kuma
968
+ * does not push `tagList` events on login (issue #46).
969
+ * Falls back to the cache if the socket is not connected.
762
970
  */
763
- getTagList() {
764
- return this.tagListCache;
971
+ async getTagList() {
972
+ try {
973
+ return await this.fetchTagList();
974
+ }
975
+ catch {
976
+ return this.tagListCache;
977
+ }
765
978
  }
766
979
  /**
767
980
  * Create a new tag
@@ -847,6 +1060,114 @@ export class UptimeKumaClient {
847
1060
  getStatusPageList() {
848
1061
  return Object.values(this.statusPageListCache);
849
1062
  }
1063
+ /**
1064
+ * Get full details of a single status page, including publicGroupList with monitors
1065
+ * and any active incidents. Uses the public HTTP API (`/api/status-page/{slug}`),
1066
+ * which returns the same data the status page UI renders — richer than the
1067
+ * socket `getStatusPage` event, which only returns config.
1068
+ *
1069
+ * @param slug - The status page slug
1070
+ * @returns Promise resolving to the status page config, groups, and incidents
1071
+ */
1072
+ async getStatusPage(slug) {
1073
+ try {
1074
+ const baseUrl = this.url.replace(/\/$/, '');
1075
+ const res = await fetch(`${baseUrl}/api/status-page/${encodeURIComponent(slug)}`);
1076
+ if (res.status === 404) {
1077
+ return { ok: false, msg: `Status page ${slug} not found` };
1078
+ }
1079
+ if (!res.ok) {
1080
+ throw new Error(`HTTP ${res.status} ${res.statusText}`);
1081
+ }
1082
+ const data = await res.json();
1083
+ return {
1084
+ ok: true,
1085
+ config: data.config,
1086
+ publicGroupList: data.publicGroupList,
1087
+ incidents: data.incidents ?? [],
1088
+ };
1089
+ }
1090
+ catch (error) {
1091
+ const msg = error instanceof Error ? error.message : 'Unknown error';
1092
+ throw new Error(`Failed to get status page ${slug}: ${msg}`);
1093
+ }
1094
+ }
1095
+ /**
1096
+ * Create a new (empty) status page with the given title and slug
1097
+ *
1098
+ * Note: This creates a blank status page. Use updateStatusPage afterwards to
1099
+ * set description, theme, groups, monitors, etc.
1100
+ *
1101
+ * @param title - Display title of the status page
1102
+ * @param slug - URL slug (lowercase letters, digits, and dashes only)
1103
+ * @returns Promise resolving to the API response
1104
+ */
1105
+ createStatusPage(title, slug) {
1106
+ return new Promise((resolve, reject) => {
1107
+ if (!this.socket || !this.socket.connected) {
1108
+ reject(new Error('Not connected to server'));
1109
+ return;
1110
+ }
1111
+ this.socket.emit('addStatusPage', title, slug, (response) => {
1112
+ if (response.ok) {
1113
+ this.safeLog('info', `Successfully created status page ${slug}`);
1114
+ resolve(response);
1115
+ }
1116
+ else {
1117
+ reject(new Error(response.msg || `Failed to create status page ${slug}`));
1118
+ }
1119
+ });
1120
+ });
1121
+ }
1122
+ /**
1123
+ * Update an existing status page's config and group/monitor list
1124
+ *
1125
+ * @param slug - The status page slug (immutable identifier)
1126
+ * @param config - Status page configuration (title, description, theme, published, etc.)
1127
+ * @param publicGroupList - Ordered groups, each with a name, weight, and monitorList `[{id}]`
1128
+ * @param imgDataUrl - Optional icon as data URL (pass empty string to keep existing)
1129
+ * @returns Promise resolving to the API response
1130
+ */
1131
+ updateStatusPage(slug, config, publicGroupList = [], imgDataUrl = '') {
1132
+ return new Promise((resolve, reject) => {
1133
+ if (!this.socket || !this.socket.connected) {
1134
+ reject(new Error('Not connected to server'));
1135
+ return;
1136
+ }
1137
+ this.socket.emit('saveStatusPage', slug, config, imgDataUrl, publicGroupList, (response) => {
1138
+ if (response.ok) {
1139
+ this.safeLog('info', `Successfully updated status page ${slug}`);
1140
+ resolve(response);
1141
+ }
1142
+ else {
1143
+ reject(new Error(response.msg || `Failed to update status page ${slug}`));
1144
+ }
1145
+ });
1146
+ });
1147
+ }
1148
+ /**
1149
+ * Delete a status page
1150
+ *
1151
+ * @param slug - The status page slug
1152
+ * @returns Promise resolving to the API response
1153
+ */
1154
+ deleteStatusPage(slug) {
1155
+ return new Promise((resolve, reject) => {
1156
+ if (!this.socket || !this.socket.connected) {
1157
+ reject(new Error('Not connected to server'));
1158
+ return;
1159
+ }
1160
+ this.socket.emit('deleteStatusPage', slug, (response) => {
1161
+ if (response.ok) {
1162
+ this.safeLog('info', `Successfully deleted status page ${slug}`);
1163
+ resolve(response);
1164
+ }
1165
+ else {
1166
+ reject(new Error(response.msg || `Failed to delete status page ${slug}`));
1167
+ }
1168
+ });
1169
+ });
1170
+ }
850
1171
  // ─── Socket accessor ─────────────────────────────────────────────────────────
851
1172
  /**
852
1173
  * Get the socket instance (for advanced usage)