@access-mcp/system-status 0.2.3 → 0.4.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.
- package/README.md +154 -8
- package/dist/__tests__/server.integration.test.d.ts +1 -0
- package/dist/__tests__/server.integration.test.js +225 -0
- package/dist/__tests__/server.test.d.ts +1 -0
- package/dist/__tests__/server.test.js +380 -0
- package/dist/index.js +2 -2
- package/dist/server.d.ts +29 -1
- package/dist/server.js +349 -133
- package/dist/web-server.js +23 -23
- package/package.json +17 -2
package/dist/server.js
CHANGED
|
@@ -1,65 +1,88 @@
|
|
|
1
|
-
import { BaseAccessServer, handleApiError } from
|
|
1
|
+
import { BaseAccessServer, handleApiError } from "@access-mcp/shared";
|
|
2
2
|
export class SystemStatusServer extends BaseAccessServer {
|
|
3
3
|
constructor() {
|
|
4
|
-
super(
|
|
4
|
+
super("access-mcp-system-status", "0.4.0", "https://operations-api.access-ci.org");
|
|
5
5
|
}
|
|
6
6
|
getTools() {
|
|
7
7
|
return [
|
|
8
8
|
{
|
|
9
|
-
name:
|
|
10
|
-
description:
|
|
9
|
+
name: "get_current_outages",
|
|
10
|
+
description: "Get current system outages and issues affecting ACCESS-CI resources",
|
|
11
11
|
inputSchema: {
|
|
12
|
-
type:
|
|
12
|
+
type: "object",
|
|
13
13
|
properties: {
|
|
14
14
|
resource_filter: {
|
|
15
|
-
type:
|
|
16
|
-
description:
|
|
15
|
+
type: "string",
|
|
16
|
+
description: "Optional: filter by specific resource name or ID",
|
|
17
17
|
},
|
|
18
18
|
},
|
|
19
19
|
required: [],
|
|
20
20
|
},
|
|
21
21
|
},
|
|
22
22
|
{
|
|
23
|
-
name:
|
|
24
|
-
description:
|
|
23
|
+
name: "get_scheduled_maintenance",
|
|
24
|
+
description: "Get scheduled maintenance and future outages for ACCESS-CI resources",
|
|
25
25
|
inputSchema: {
|
|
26
|
-
type:
|
|
26
|
+
type: "object",
|
|
27
27
|
properties: {
|
|
28
28
|
resource_filter: {
|
|
29
|
-
type:
|
|
30
|
-
description:
|
|
29
|
+
type: "string",
|
|
30
|
+
description: "Optional: filter by specific resource name or ID",
|
|
31
31
|
},
|
|
32
32
|
},
|
|
33
33
|
required: [],
|
|
34
34
|
},
|
|
35
35
|
},
|
|
36
36
|
{
|
|
37
|
-
name:
|
|
38
|
-
description:
|
|
37
|
+
name: "get_past_outages",
|
|
38
|
+
description: "Get historical outages and past incidents affecting ACCESS-CI resources",
|
|
39
39
|
inputSchema: {
|
|
40
|
-
type:
|
|
40
|
+
type: "object",
|
|
41
41
|
properties: {
|
|
42
|
+
resource_filter: {
|
|
43
|
+
type: "string",
|
|
44
|
+
description: "Optional: filter by specific resource name or ID",
|
|
45
|
+
},
|
|
42
46
|
limit: {
|
|
43
|
-
type:
|
|
44
|
-
description:
|
|
47
|
+
type: "number",
|
|
48
|
+
description: "Maximum number of past outages to return (default: 100)",
|
|
45
49
|
},
|
|
46
50
|
},
|
|
47
51
|
required: [],
|
|
48
52
|
},
|
|
49
53
|
},
|
|
50
54
|
{
|
|
51
|
-
name:
|
|
52
|
-
description:
|
|
55
|
+
name: "get_system_announcements",
|
|
56
|
+
description: "Get all system announcements (current and scheduled)",
|
|
53
57
|
inputSchema: {
|
|
54
|
-
type:
|
|
58
|
+
type: "object",
|
|
59
|
+
properties: {
|
|
60
|
+
limit: {
|
|
61
|
+
type: "number",
|
|
62
|
+
description: "Maximum number of announcements to return (default: 50)",
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
required: [],
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
name: "check_resource_status",
|
|
70
|
+
description: "Check the operational status of specific ACCESS-CI resources",
|
|
71
|
+
inputSchema: {
|
|
72
|
+
type: "object",
|
|
55
73
|
properties: {
|
|
56
74
|
resource_ids: {
|
|
57
|
-
type:
|
|
58
|
-
items: { type:
|
|
59
|
-
description:
|
|
75
|
+
type: "array",
|
|
76
|
+
items: { type: "string" },
|
|
77
|
+
description: "List of resource IDs or names to check status for",
|
|
78
|
+
},
|
|
79
|
+
use_group_api: {
|
|
80
|
+
type: "boolean",
|
|
81
|
+
description: "Use resource group API for more efficient querying (default: false)",
|
|
82
|
+
default: false,
|
|
60
83
|
},
|
|
61
84
|
},
|
|
62
|
-
required: [
|
|
85
|
+
required: ["resource_ids"],
|
|
63
86
|
},
|
|
64
87
|
},
|
|
65
88
|
];
|
|
@@ -67,22 +90,28 @@ export class SystemStatusServer extends BaseAccessServer {
|
|
|
67
90
|
getResources() {
|
|
68
91
|
return [
|
|
69
92
|
{
|
|
70
|
-
uri:
|
|
71
|
-
name:
|
|
72
|
-
description:
|
|
73
|
-
mimeType:
|
|
93
|
+
uri: "accessci://system-status",
|
|
94
|
+
name: "ACCESS-CI System Status",
|
|
95
|
+
description: "Real-time status of ACCESS-CI infrastructure, outages, and maintenance",
|
|
96
|
+
mimeType: "application/json",
|
|
74
97
|
},
|
|
75
98
|
{
|
|
76
|
-
uri:
|
|
77
|
-
name:
|
|
78
|
-
description:
|
|
79
|
-
mimeType:
|
|
99
|
+
uri: "accessci://outages/current",
|
|
100
|
+
name: "Current Outages",
|
|
101
|
+
description: "Currently active outages and system issues",
|
|
102
|
+
mimeType: "application/json",
|
|
80
103
|
},
|
|
81
104
|
{
|
|
82
|
-
uri:
|
|
83
|
-
name:
|
|
84
|
-
description:
|
|
85
|
-
mimeType:
|
|
105
|
+
uri: "accessci://outages/scheduled",
|
|
106
|
+
name: "Scheduled Maintenance",
|
|
107
|
+
description: "Upcoming scheduled maintenance and planned outages",
|
|
108
|
+
mimeType: "application/json",
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
uri: "accessci://outages/past",
|
|
112
|
+
name: "Past Outages",
|
|
113
|
+
description: "Historical outages and past incidents",
|
|
114
|
+
mimeType: "application/json",
|
|
86
115
|
},
|
|
87
116
|
];
|
|
88
117
|
}
|
|
@@ -90,14 +119,16 @@ export class SystemStatusServer extends BaseAccessServer {
|
|
|
90
119
|
const { name, arguments: args = {} } = request.params;
|
|
91
120
|
try {
|
|
92
121
|
switch (name) {
|
|
93
|
-
case
|
|
122
|
+
case "get_current_outages":
|
|
94
123
|
return await this.getCurrentOutages(args.resource_filter);
|
|
95
|
-
case
|
|
124
|
+
case "get_scheduled_maintenance":
|
|
96
125
|
return await this.getScheduledMaintenance(args.resource_filter);
|
|
97
|
-
case
|
|
126
|
+
case "get_past_outages":
|
|
127
|
+
return await this.getPastOutages(args.resource_filter, args.limit);
|
|
128
|
+
case "get_system_announcements":
|
|
98
129
|
return await this.getSystemAnnouncements(args.limit);
|
|
99
|
-
case
|
|
100
|
-
return await this.checkResourceStatus(args.resource_ids);
|
|
130
|
+
case "check_resource_status":
|
|
131
|
+
return await this.checkResourceStatus(args.resource_ids, args.use_group_api);
|
|
101
132
|
default:
|
|
102
133
|
throw new Error(`Unknown tool: ${name}`);
|
|
103
134
|
}
|
|
@@ -106,7 +137,7 @@ export class SystemStatusServer extends BaseAccessServer {
|
|
|
106
137
|
return {
|
|
107
138
|
content: [
|
|
108
139
|
{
|
|
109
|
-
type:
|
|
140
|
+
type: "text",
|
|
110
141
|
text: `Error: ${handleApiError(error)}`,
|
|
111
142
|
},
|
|
112
143
|
],
|
|
@@ -116,44 +147,55 @@ export class SystemStatusServer extends BaseAccessServer {
|
|
|
116
147
|
async handleResourceRead(request) {
|
|
117
148
|
const { uri } = request.params;
|
|
118
149
|
switch (uri) {
|
|
119
|
-
case
|
|
150
|
+
case "accessci://system-status":
|
|
120
151
|
return {
|
|
121
152
|
contents: [
|
|
122
153
|
{
|
|
123
154
|
uri,
|
|
124
|
-
mimeType:
|
|
125
|
-
text:
|
|
155
|
+
mimeType: "text/plain",
|
|
156
|
+
text: "ACCESS-CI System Status API - Monitor real-time status, outages, and maintenance for ACCESS-CI resources.",
|
|
126
157
|
},
|
|
127
158
|
],
|
|
128
159
|
};
|
|
129
|
-
case
|
|
160
|
+
case "accessci://outages/current":
|
|
130
161
|
const currentOutages = await this.getCurrentOutages();
|
|
131
162
|
return {
|
|
132
163
|
contents: [
|
|
133
164
|
{
|
|
134
165
|
uri,
|
|
135
|
-
mimeType:
|
|
166
|
+
mimeType: "application/json",
|
|
136
167
|
text: currentOutages.content[0].text,
|
|
137
168
|
},
|
|
138
169
|
],
|
|
139
170
|
};
|
|
140
|
-
case
|
|
171
|
+
case "accessci://outages/scheduled":
|
|
141
172
|
const scheduledMaintenance = await this.getScheduledMaintenance();
|
|
142
173
|
return {
|
|
143
174
|
contents: [
|
|
144
175
|
{
|
|
145
176
|
uri,
|
|
146
|
-
mimeType:
|
|
177
|
+
mimeType: "application/json",
|
|
147
178
|
text: scheduledMaintenance.content[0].text,
|
|
148
179
|
},
|
|
149
180
|
],
|
|
150
181
|
};
|
|
182
|
+
case "accessci://outages/past":
|
|
183
|
+
const pastOutages = await this.getPastOutages();
|
|
184
|
+
return {
|
|
185
|
+
contents: [
|
|
186
|
+
{
|
|
187
|
+
uri,
|
|
188
|
+
mimeType: "application/json",
|
|
189
|
+
text: pastOutages.content[0].text,
|
|
190
|
+
},
|
|
191
|
+
],
|
|
192
|
+
};
|
|
151
193
|
default:
|
|
152
194
|
throw new Error(`Unknown resource: ${uri}`);
|
|
153
195
|
}
|
|
154
196
|
}
|
|
155
197
|
async getCurrentOutages(resourceFilter) {
|
|
156
|
-
const response = await this.httpClient.get(
|
|
198
|
+
const response = await this.httpClient.get("/wh2/news/v1/affiliation/access-ci.org/current_outages/");
|
|
157
199
|
let outages = response.data.results || [];
|
|
158
200
|
// Filter by resource if specified
|
|
159
201
|
if (resourceFilter) {
|
|
@@ -172,16 +214,17 @@ export class SystemStatusServer extends BaseAccessServer {
|
|
|
172
214
|
affectedResources.add(resource.ResourceName);
|
|
173
215
|
});
|
|
174
216
|
// Categorize severity (basic heuristic)
|
|
175
|
-
const subject = outage.Subject?.toLowerCase() ||
|
|
176
|
-
let severity =
|
|
177
|
-
if (subject.includes(
|
|
178
|
-
severity =
|
|
217
|
+
const subject = outage.Subject?.toLowerCase() || "";
|
|
218
|
+
let severity = "unknown";
|
|
219
|
+
if (subject.includes("emergency") || subject.includes("critical")) {
|
|
220
|
+
severity = "high";
|
|
179
221
|
}
|
|
180
|
-
else if (subject.includes(
|
|
181
|
-
|
|
222
|
+
else if (subject.includes("maintenance") ||
|
|
223
|
+
subject.includes("scheduled")) {
|
|
224
|
+
severity = "low";
|
|
182
225
|
}
|
|
183
226
|
else {
|
|
184
|
-
severity =
|
|
227
|
+
severity = "medium";
|
|
185
228
|
}
|
|
186
229
|
severityCounts[severity]++;
|
|
187
230
|
return {
|
|
@@ -195,22 +238,19 @@ export class SystemStatusServer extends BaseAccessServer {
|
|
|
195
238
|
total_outages: outages.length,
|
|
196
239
|
affected_resources: Array.from(affectedResources),
|
|
197
240
|
severity_counts: severityCounts,
|
|
198
|
-
outages: enhancedOutages
|
|
241
|
+
outages: enhancedOutages,
|
|
199
242
|
};
|
|
200
243
|
return {
|
|
201
244
|
content: [
|
|
202
245
|
{
|
|
203
|
-
type:
|
|
204
|
-
text: JSON.stringify(
|
|
205
|
-
...summary,
|
|
206
|
-
affected_resources: summary.affected_resources
|
|
207
|
-
}, null, 2),
|
|
246
|
+
type: "text",
|
|
247
|
+
text: JSON.stringify(summary, null, 2),
|
|
208
248
|
},
|
|
209
249
|
],
|
|
210
250
|
};
|
|
211
251
|
}
|
|
212
252
|
async getScheduledMaintenance(resourceFilter) {
|
|
213
|
-
const response = await this.httpClient.get(
|
|
253
|
+
const response = await this.httpClient.get("/wh2/news/v1/affiliation/access-ci.org/future_outages/");
|
|
214
254
|
let maintenance = response.data.results || [];
|
|
215
255
|
// Filter by resource if specified
|
|
216
256
|
if (resourceFilter) {
|
|
@@ -225,97 +265,217 @@ export class SystemStatusServer extends BaseAccessServer {
|
|
|
225
265
|
const dateB = new Date(b.OutageStartDateTime || b.CreationTime);
|
|
226
266
|
return dateA.getTime() - dateB.getTime();
|
|
227
267
|
});
|
|
268
|
+
// Initialize tracking variables
|
|
269
|
+
const affectedResources = new Set();
|
|
270
|
+
let upcoming24h = 0;
|
|
271
|
+
let upcomingWeek = 0;
|
|
272
|
+
const enhancedMaintenance = maintenance.map((item) => {
|
|
273
|
+
// Track affected resources
|
|
274
|
+
item.AffectedResources?.forEach((resource) => {
|
|
275
|
+
affectedResources.add(resource.ResourceName);
|
|
276
|
+
});
|
|
277
|
+
// Check timing - only use OutageStartDateTime for scheduling, fallback shows warning
|
|
278
|
+
const hasScheduledTime = !!item.OutageStartDateTime;
|
|
279
|
+
const startTime = new Date(item.OutageStartDateTime || item.CreationTime);
|
|
280
|
+
const now = new Date();
|
|
281
|
+
const hoursUntil = (startTime.getTime() - now.getTime()) / (1000 * 60 * 60);
|
|
282
|
+
if (hoursUntil <= 24)
|
|
283
|
+
upcoming24h++;
|
|
284
|
+
if (hoursUntil <= 168)
|
|
285
|
+
upcomingWeek++; // 7 days * 24 hours
|
|
286
|
+
return {
|
|
287
|
+
...item,
|
|
288
|
+
scheduled_start: item.OutageStartDateTime,
|
|
289
|
+
scheduled_end: item.OutageEndDateTime,
|
|
290
|
+
hours_until_start: Math.max(0, Math.round(hoursUntil)),
|
|
291
|
+
duration_hours: item.OutageEndDateTime && item.OutageStartDateTime
|
|
292
|
+
? Math.round((new Date(item.OutageEndDateTime).getTime() -
|
|
293
|
+
new Date(item.OutageStartDateTime).getTime()) /
|
|
294
|
+
(1000 * 60 * 60))
|
|
295
|
+
: null,
|
|
296
|
+
has_scheduled_time: hasScheduledTime,
|
|
297
|
+
};
|
|
298
|
+
});
|
|
228
299
|
const summary = {
|
|
229
300
|
total_scheduled: maintenance.length,
|
|
230
|
-
upcoming_24h:
|
|
231
|
-
upcoming_week:
|
|
232
|
-
affected_resources:
|
|
233
|
-
maintenance:
|
|
234
|
-
// Track affected resources
|
|
235
|
-
item.AffectedResources?.forEach((resource) => {
|
|
236
|
-
summary.affected_resources.add(resource.ResourceName);
|
|
237
|
-
});
|
|
238
|
-
// Check timing
|
|
239
|
-
const startTime = new Date(item.OutageStartDateTime || item.CreationTime);
|
|
240
|
-
const now = new Date();
|
|
241
|
-
const hoursUntil = (startTime.getTime() - now.getTime()) / (1000 * 60 * 60);
|
|
242
|
-
if (hoursUntil <= 24)
|
|
243
|
-
summary.upcoming_24h++;
|
|
244
|
-
if (hoursUntil <= 168)
|
|
245
|
-
summary.upcoming_week++; // 7 days * 24 hours
|
|
246
|
-
return {
|
|
247
|
-
...item,
|
|
248
|
-
scheduled_start: item.OutageStartDateTime,
|
|
249
|
-
scheduled_end: item.OutageEndDateTime,
|
|
250
|
-
hours_until_start: Math.max(0, Math.round(hoursUntil)),
|
|
251
|
-
duration_hours: item.OutageEndDateTime && item.OutageStartDateTime
|
|
252
|
-
? Math.round((new Date(item.OutageEndDateTime).getTime() -
|
|
253
|
-
new Date(item.OutageStartDateTime).getTime()) / (1000 * 60 * 60))
|
|
254
|
-
: null,
|
|
255
|
-
};
|
|
256
|
-
})
|
|
301
|
+
upcoming_24h: upcoming24h,
|
|
302
|
+
upcoming_week: upcomingWeek,
|
|
303
|
+
affected_resources: Array.from(affectedResources),
|
|
304
|
+
maintenance: enhancedMaintenance,
|
|
257
305
|
};
|
|
258
306
|
return {
|
|
259
307
|
content: [
|
|
260
308
|
{
|
|
261
|
-
type:
|
|
262
|
-
text: JSON.stringify(
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
309
|
+
type: "text",
|
|
310
|
+
text: JSON.stringify(summary, null, 2),
|
|
311
|
+
},
|
|
312
|
+
],
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
async getPastOutages(resourceFilter, limit = 100) {
|
|
316
|
+
const response = await this.httpClient.get("/wh2/news/v1/affiliation/access-ci.org/past_outages/");
|
|
317
|
+
let pastOutages = response.data.results || [];
|
|
318
|
+
// Filter by resource if specified
|
|
319
|
+
if (resourceFilter) {
|
|
320
|
+
const filter = resourceFilter.toLowerCase();
|
|
321
|
+
pastOutages = pastOutages.filter((outage) => outage.Subject?.toLowerCase().includes(filter) ||
|
|
322
|
+
outage.AffectedResources?.some((resource) => resource.ResourceName?.toLowerCase().includes(filter) ||
|
|
323
|
+
resource.ResourceID?.toString().includes(filter)));
|
|
324
|
+
}
|
|
325
|
+
// Sort by outage end time (most recent first)
|
|
326
|
+
pastOutages.sort((a, b) => {
|
|
327
|
+
const dateA = new Date(a.OutageEndDateTime || a.LastModificationTime);
|
|
328
|
+
const dateB = new Date(b.OutageEndDateTime || b.LastModificationTime);
|
|
329
|
+
return dateB.getTime() - dateA.getTime();
|
|
330
|
+
});
|
|
331
|
+
// Apply limit
|
|
332
|
+
if (limit && pastOutages.length > limit) {
|
|
333
|
+
pastOutages = pastOutages.slice(0, limit);
|
|
334
|
+
}
|
|
335
|
+
// Initialize tracking variables
|
|
336
|
+
const affectedResources = new Set();
|
|
337
|
+
const outageTypes = new Set();
|
|
338
|
+
const recentOutages = pastOutages.filter((outage) => {
|
|
339
|
+
const endTime = new Date(outage.OutageEndDateTime || outage.LastModificationTime);
|
|
340
|
+
const daysAgo = (Date.now() - endTime.getTime()) / (1000 * 60 * 60 * 24);
|
|
341
|
+
return daysAgo <= 30; // Last 30 days
|
|
342
|
+
});
|
|
343
|
+
// Enhance outages with calculated fields
|
|
344
|
+
const enhancedOutages = pastOutages.map((outage) => {
|
|
345
|
+
// Track affected resources
|
|
346
|
+
outage.AffectedResources?.forEach((resource) => {
|
|
347
|
+
affectedResources.add(resource.ResourceName);
|
|
348
|
+
});
|
|
349
|
+
// Track outage types
|
|
350
|
+
if (outage.OutageType) {
|
|
351
|
+
outageTypes.add(outage.OutageType);
|
|
352
|
+
}
|
|
353
|
+
// Calculate duration
|
|
354
|
+
const startTime = new Date(outage.OutageStartDateTime);
|
|
355
|
+
const endTime = new Date(outage.OutageEndDateTime);
|
|
356
|
+
const durationHours = Math.round((endTime.getTime() - startTime.getTime()) / (1000 * 60 * 60));
|
|
357
|
+
// Calculate how long ago it ended
|
|
358
|
+
const daysAgo = Math.round((Date.now() - endTime.getTime()) / (1000 * 60 * 60 * 24));
|
|
359
|
+
return {
|
|
360
|
+
...outage,
|
|
361
|
+
outage_start: outage.OutageStartDateTime,
|
|
362
|
+
outage_end: outage.OutageEndDateTime,
|
|
363
|
+
duration_hours: durationHours,
|
|
364
|
+
days_ago: daysAgo,
|
|
365
|
+
outage_type: outage.OutageType,
|
|
366
|
+
posted_time: outage.CreationTime,
|
|
367
|
+
last_updated: outage.LastModificationTime,
|
|
368
|
+
};
|
|
369
|
+
});
|
|
370
|
+
const summary = {
|
|
371
|
+
total_past_outages: enhancedOutages.length,
|
|
372
|
+
recent_outages_30_days: recentOutages.length,
|
|
373
|
+
affected_resources: Array.from(affectedResources),
|
|
374
|
+
outage_types: Array.from(outageTypes),
|
|
375
|
+
average_duration_hours: enhancedOutages.length > 0
|
|
376
|
+
? Math.round(enhancedOutages
|
|
377
|
+
.filter((o) => o.duration_hours > 0)
|
|
378
|
+
.reduce((sum, o) => sum + o.duration_hours, 0) /
|
|
379
|
+
enhancedOutages.filter((o) => o.duration_hours > 0).length)
|
|
380
|
+
: 0,
|
|
381
|
+
outages: enhancedOutages,
|
|
382
|
+
};
|
|
383
|
+
return {
|
|
384
|
+
content: [
|
|
385
|
+
{
|
|
386
|
+
type: "text",
|
|
387
|
+
text: JSON.stringify(summary, null, 2),
|
|
266
388
|
},
|
|
267
389
|
],
|
|
268
390
|
};
|
|
269
391
|
}
|
|
270
392
|
async getSystemAnnouncements(limit = 50) {
|
|
271
|
-
// Get
|
|
272
|
-
const [currentResponse, futureResponse] = await Promise.all([
|
|
273
|
-
this.httpClient.get(
|
|
274
|
-
this.httpClient.get(
|
|
393
|
+
// Get current, future, and recent past announcements for comprehensive view
|
|
394
|
+
const [currentResponse, futureResponse, pastResponse] = await Promise.all([
|
|
395
|
+
this.httpClient.get("/wh2/news/v1/affiliation/access-ci.org/current_outages/"),
|
|
396
|
+
this.httpClient.get("/wh2/news/v1/affiliation/access-ci.org/future_outages/"),
|
|
397
|
+
this.httpClient.get("/wh2/news/v1/affiliation/access-ci.org/past_outages/"),
|
|
275
398
|
]);
|
|
276
399
|
const currentOutages = currentResponse.data.results || [];
|
|
277
400
|
const futureOutages = futureResponse.data.results || [];
|
|
278
|
-
|
|
279
|
-
|
|
401
|
+
const pastOutages = pastResponse.data.results || [];
|
|
402
|
+
// Filter recent past outages (last 30 days) for announcements
|
|
403
|
+
const recentPastOutages = pastOutages.filter((outage) => {
|
|
404
|
+
const endTime = new Date(outage.OutageEndDateTime || outage.LastModificationTime);
|
|
405
|
+
const daysAgo = (Date.now() - endTime.getTime()) / (1000 * 60 * 60 * 24);
|
|
406
|
+
return daysAgo <= 30;
|
|
407
|
+
});
|
|
408
|
+
// Combine all announcements and sort by most relevant date
|
|
409
|
+
const allAnnouncements = [
|
|
410
|
+
...currentOutages.map((item) => ({ ...item, category: 'current' })),
|
|
411
|
+
...futureOutages.map((item) => ({ ...item, category: 'scheduled' })),
|
|
412
|
+
...recentPastOutages.map((item) => ({ ...item, category: 'recent_past' })),
|
|
413
|
+
]
|
|
280
414
|
.sort((a, b) => {
|
|
281
|
-
|
|
282
|
-
|
|
415
|
+
// Sort by most relevant date: current first, then future by start time, then past by end time
|
|
416
|
+
if (a.category === 'current' && b.category !== 'current')
|
|
417
|
+
return -1;
|
|
418
|
+
if (b.category === 'current' && a.category !== 'current')
|
|
419
|
+
return 1;
|
|
420
|
+
const dateA = new Date(a.OutageStartDateTime || a.CreationTime);
|
|
421
|
+
const dateB = new Date(b.OutageStartDateTime || b.CreationTime);
|
|
283
422
|
return dateB.getTime() - dateA.getTime(); // Most recent first
|
|
284
423
|
})
|
|
285
424
|
.slice(0, limit);
|
|
425
|
+
const summary = {
|
|
426
|
+
total_announcements: allAnnouncements.length,
|
|
427
|
+
current_outages: currentOutages.length,
|
|
428
|
+
scheduled_maintenance: futureOutages.length,
|
|
429
|
+
recent_past_outages: recentPastOutages.length,
|
|
430
|
+
categories: {
|
|
431
|
+
current: allAnnouncements.filter(a => a.category === 'current').length,
|
|
432
|
+
scheduled: allAnnouncements.filter(a => a.category === 'scheduled').length,
|
|
433
|
+
recent_past: allAnnouncements.filter(a => a.category === 'recent_past').length,
|
|
434
|
+
},
|
|
435
|
+
announcements: allAnnouncements,
|
|
436
|
+
};
|
|
286
437
|
return {
|
|
287
438
|
content: [
|
|
288
439
|
{
|
|
289
|
-
type:
|
|
290
|
-
text: JSON.stringify(
|
|
291
|
-
total_announcements: allAnnouncements.length,
|
|
292
|
-
current_outages: currentOutages.length,
|
|
293
|
-
scheduled_maintenance: futureOutages.length,
|
|
294
|
-
announcements: allAnnouncements
|
|
295
|
-
}, null, 2),
|
|
440
|
+
type: "text",
|
|
441
|
+
text: JSON.stringify(summary, null, 2),
|
|
296
442
|
},
|
|
297
443
|
],
|
|
298
444
|
};
|
|
299
445
|
}
|
|
300
|
-
async checkResourceStatus(resourceIds) {
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
446
|
+
async checkResourceStatus(resourceIds, useGroupApi = false) {
|
|
447
|
+
if (useGroupApi) {
|
|
448
|
+
return await this.checkResourceStatusViaGroups(resourceIds);
|
|
449
|
+
}
|
|
450
|
+
// Efficient approach: fetch raw current outages data once
|
|
451
|
+
const response = await this.httpClient.get("/wh2/news/v1/affiliation/access-ci.org/current_outages/");
|
|
452
|
+
const rawOutages = response.data.results || [];
|
|
453
|
+
const resourceStatus = resourceIds.map((resourceId) => {
|
|
454
|
+
const affectedOutages = rawOutages.filter((outage) => outage.AffectedResources?.some((resource) => resource.ResourceID?.toString() === resourceId ||
|
|
306
455
|
resource.ResourceName?.toLowerCase().includes(resourceId.toLowerCase())));
|
|
307
|
-
let status =
|
|
456
|
+
let status = "operational";
|
|
308
457
|
let severity = null;
|
|
309
458
|
if (affectedOutages.length > 0) {
|
|
310
|
-
status =
|
|
311
|
-
// Get highest severity
|
|
312
|
-
const severities = affectedOutages.map((
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
459
|
+
status = "affected";
|
|
460
|
+
// Get highest severity using same logic as getCurrentOutages
|
|
461
|
+
const severities = affectedOutages.map((outage) => {
|
|
462
|
+
const subject = outage.Subject?.toLowerCase() || "";
|
|
463
|
+
if (subject.includes("emergency") || subject.includes("critical")) {
|
|
464
|
+
return "high";
|
|
465
|
+
}
|
|
466
|
+
else if (subject.includes("maintenance") || subject.includes("scheduled")) {
|
|
467
|
+
return "low";
|
|
468
|
+
}
|
|
469
|
+
else {
|
|
470
|
+
return "medium";
|
|
471
|
+
}
|
|
472
|
+
});
|
|
473
|
+
if (severities.includes("high"))
|
|
474
|
+
severity = "high";
|
|
475
|
+
else if (severities.includes("medium"))
|
|
476
|
+
severity = "medium";
|
|
317
477
|
else
|
|
318
|
-
severity =
|
|
478
|
+
severity = "low";
|
|
319
479
|
}
|
|
320
480
|
return {
|
|
321
481
|
resource_id: resourceId,
|
|
@@ -324,21 +484,77 @@ export class SystemStatusServer extends BaseAccessServer {
|
|
|
324
484
|
active_outages: affectedOutages.length,
|
|
325
485
|
outage_details: affectedOutages.map((outage) => ({
|
|
326
486
|
subject: outage.Subject,
|
|
327
|
-
severity
|
|
328
|
-
posted: outage.
|
|
329
|
-
|
|
487
|
+
severity,
|
|
488
|
+
posted: outage.CreationTime,
|
|
489
|
+
last_updated: outage.LastModificationTime,
|
|
490
|
+
})),
|
|
330
491
|
};
|
|
331
492
|
});
|
|
332
493
|
return {
|
|
333
494
|
content: [
|
|
334
495
|
{
|
|
335
|
-
type:
|
|
496
|
+
type: "text",
|
|
497
|
+
text: JSON.stringify({
|
|
498
|
+
checked_at: new Date().toISOString(),
|
|
499
|
+
resources_checked: resourceIds.length,
|
|
500
|
+
operational: resourceStatus.filter((r) => r.status === "operational").length,
|
|
501
|
+
affected: resourceStatus.filter((r) => r.status === "affected")
|
|
502
|
+
.length,
|
|
503
|
+
api_method: "direct_outages_check",
|
|
504
|
+
resource_status: resourceStatus,
|
|
505
|
+
}, null, 2),
|
|
506
|
+
},
|
|
507
|
+
],
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
async checkResourceStatusViaGroups(resourceIds) {
|
|
511
|
+
// Try to use the more efficient group-based API
|
|
512
|
+
const statusPromises = resourceIds.map(async (resourceId) => {
|
|
513
|
+
try {
|
|
514
|
+
const response = await this.httpClient.get(`/wh2/news/v1/info_groupid/${resourceId}/`);
|
|
515
|
+
const groupData = response.data.results || [];
|
|
516
|
+
const hasOutages = groupData.length > 0;
|
|
517
|
+
return {
|
|
518
|
+
resource_id: resourceId,
|
|
519
|
+
status: hasOutages ? "affected" : "operational",
|
|
520
|
+
severity: hasOutages ? "medium" : null,
|
|
521
|
+
active_outages: groupData.length,
|
|
522
|
+
outage_details: groupData.map((outage) => ({
|
|
523
|
+
subject: outage.Subject,
|
|
524
|
+
posted: outage.CreationTime,
|
|
525
|
+
last_updated: outage.LastModificationTime,
|
|
526
|
+
})),
|
|
527
|
+
api_method: "group_specific",
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
catch (error) {
|
|
531
|
+
// Fallback to general check if group API fails
|
|
532
|
+
return {
|
|
533
|
+
resource_id: resourceId,
|
|
534
|
+
status: "unknown",
|
|
535
|
+
severity: null,
|
|
536
|
+
active_outages: 0,
|
|
537
|
+
outage_details: [],
|
|
538
|
+
error: `Group API failed for ${resourceId}`,
|
|
539
|
+
api_method: "group_specific_failed",
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
});
|
|
543
|
+
const resourceStatus = await Promise.all(statusPromises);
|
|
544
|
+
return {
|
|
545
|
+
content: [
|
|
546
|
+
{
|
|
547
|
+
type: "text",
|
|
336
548
|
text: JSON.stringify({
|
|
337
549
|
checked_at: new Date().toISOString(),
|
|
338
550
|
resources_checked: resourceIds.length,
|
|
339
|
-
operational: resourceStatus.filter(r => r.status ===
|
|
340
|
-
affected: resourceStatus.filter(r => r.status ===
|
|
341
|
-
|
|
551
|
+
operational: resourceStatus.filter((r) => r.status === "operational").length,
|
|
552
|
+
affected: resourceStatus.filter((r) => r.status === "affected")
|
|
553
|
+
.length,
|
|
554
|
+
unknown: resourceStatus.filter((r) => r.status === "unknown")
|
|
555
|
+
.length,
|
|
556
|
+
api_method: "resource_group_api",
|
|
557
|
+
resource_status: resourceStatus,
|
|
342
558
|
}, null, 2),
|
|
343
559
|
},
|
|
344
560
|
],
|