@checkstack/queue-frontend 0.1.0 → 0.2.1
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/CHANGELOG.md +90 -0
- package/package.json +7 -1
- package/src/api.ts +2 -9
- package/src/components/QueueLagAlert.tsx +95 -0
- package/src/index.tsx +1 -13
- package/src/pages/QueueConfigPage.tsx +134 -64
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,95 @@
|
|
|
1
1
|
# @checkstack/queue-frontend
|
|
2
2
|
|
|
3
|
+
## 0.2.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies [4eed42d]
|
|
8
|
+
- @checkstack/frontend-api@0.3.0
|
|
9
|
+
- @checkstack/ui@0.2.2
|
|
10
|
+
|
|
11
|
+
## 0.2.0
|
|
12
|
+
|
|
13
|
+
### Minor Changes
|
|
14
|
+
|
|
15
|
+
- 180be38: # Queue Lag Warning
|
|
16
|
+
|
|
17
|
+
Added a queue lag warning system that displays alerts when pending jobs exceed configurable thresholds.
|
|
18
|
+
|
|
19
|
+
## Features
|
|
20
|
+
|
|
21
|
+
- **Backend Stats API**: New `getStats`, `getLagStatus`, and `updateLagThresholds` RPC endpoints
|
|
22
|
+
- **Signal-based Updates**: `QUEUE_LAG_CHANGED` signal for real-time frontend updates
|
|
23
|
+
- **Aggregated Stats**: `QueueManager.getAggregatedStats()` sums stats across all queues
|
|
24
|
+
- **Configurable Thresholds**: Warning (default 100) and Critical (default 500) thresholds stored in config
|
|
25
|
+
- **Dashboard Integration**: Queue lag alert displayed on main Dashboard (access-gated)
|
|
26
|
+
- **Queue Settings Page**: Lag alert and Performance Tuning guidance card with concurrency tips
|
|
27
|
+
|
|
28
|
+
## UI Changes
|
|
29
|
+
|
|
30
|
+
- Queue lag alert banner appears on Dashboard and Queue Settings when pending jobs exceed thresholds
|
|
31
|
+
- New "Performance Tuning" card with concurrency settings guidance and bottleneck indicators
|
|
32
|
+
|
|
33
|
+
- 7a23261: ## TanStack Query Integration
|
|
34
|
+
|
|
35
|
+
Migrated all frontend components to use `usePluginClient` hook with TanStack Query integration, replacing the legacy `forPlugin()` pattern.
|
|
36
|
+
|
|
37
|
+
### New Features
|
|
38
|
+
|
|
39
|
+
- **`usePluginClient` hook**: Provides type-safe access to plugin APIs with `.useQuery()` and `.useMutation()` methods
|
|
40
|
+
- **Automatic request deduplication**: Multiple components requesting the same data share a single network request
|
|
41
|
+
- **Built-in caching**: Configurable stale time and cache duration per query
|
|
42
|
+
- **Loading/error states**: TanStack Query provides `isLoading`, `error`, `isRefetching` states automatically
|
|
43
|
+
- **Background refetching**: Stale data is automatically refreshed when components mount
|
|
44
|
+
|
|
45
|
+
### Contract Changes
|
|
46
|
+
|
|
47
|
+
All RPC contracts now require `operationType: "query"` or `operationType: "mutation"` metadata:
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
const getItems = proc()
|
|
51
|
+
.meta({ operationType: "query", access: [access.read] })
|
|
52
|
+
.output(z.array(itemSchema))
|
|
53
|
+
.query();
|
|
54
|
+
|
|
55
|
+
const createItem = proc()
|
|
56
|
+
.meta({ operationType: "mutation", access: [access.manage] })
|
|
57
|
+
.input(createItemSchema)
|
|
58
|
+
.output(itemSchema)
|
|
59
|
+
.mutation();
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Migration
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
// Before (forPlugin pattern)
|
|
66
|
+
const api = useApi(myPluginApiRef);
|
|
67
|
+
const [items, setItems] = useState<Item[]>([]);
|
|
68
|
+
useEffect(() => {
|
|
69
|
+
api.getItems().then(setItems);
|
|
70
|
+
}, [api]);
|
|
71
|
+
|
|
72
|
+
// After (usePluginClient pattern)
|
|
73
|
+
const client = usePluginClient(MyPluginApi);
|
|
74
|
+
const { data: items, isLoading } = client.getItems.useQuery({});
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Bug Fixes
|
|
78
|
+
|
|
79
|
+
- Fixed `rpc.test.ts` test setup for middleware type inference
|
|
80
|
+
- Fixed `SearchDialog` to use `setQuery` instead of deprecated `search` method
|
|
81
|
+
- Fixed null→undefined warnings in notification and queue frontends
|
|
82
|
+
|
|
83
|
+
### Patch Changes
|
|
84
|
+
|
|
85
|
+
- Updated dependencies [180be38]
|
|
86
|
+
- Updated dependencies [7a23261]
|
|
87
|
+
- @checkstack/queue-common@0.2.0
|
|
88
|
+
- @checkstack/frontend-api@0.2.0
|
|
89
|
+
- @checkstack/common@0.3.0
|
|
90
|
+
- @checkstack/ui@0.2.1
|
|
91
|
+
- @checkstack/signal-frontend@0.0.7
|
|
92
|
+
|
|
3
93
|
## 0.1.0
|
|
4
94
|
|
|
5
95
|
### Minor Changes
|
package/package.json
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@checkstack/queue-frontend",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"import": "./src/index.tsx"
|
|
10
|
+
}
|
|
11
|
+
},
|
|
7
12
|
"scripts": {
|
|
8
13
|
"build": "tsc",
|
|
9
14
|
"dev": "tsc --watch",
|
|
@@ -15,6 +20,7 @@
|
|
|
15
20
|
"@checkstack/common": "workspace:*",
|
|
16
21
|
"@checkstack/frontend-api": "workspace:*",
|
|
17
22
|
"@checkstack/queue-common": "workspace:*",
|
|
23
|
+
"@checkstack/signal-frontend": "workspace:*",
|
|
18
24
|
"@checkstack/ui": "workspace:*",
|
|
19
25
|
"ajv": "^8.17.1",
|
|
20
26
|
"ajv-formats": "^3.0.1",
|
package/src/api.ts
CHANGED
|
@@ -1,15 +1,8 @@
|
|
|
1
|
-
import { createApiRef } from "@checkstack/frontend-api";
|
|
2
|
-
import { QueueApi } from "@checkstack/queue-common";
|
|
3
|
-
import type { InferClient } from "@checkstack/common";
|
|
4
|
-
|
|
5
1
|
// Re-export types for convenience
|
|
6
2
|
export type {
|
|
7
3
|
QueuePluginDto,
|
|
8
4
|
QueueConfigurationDto,
|
|
9
5
|
UpdateQueueConfiguration,
|
|
10
6
|
} from "@checkstack/queue-common";
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
export type QueueApiClient = InferClient<typeof QueueApi>;
|
|
14
|
-
|
|
15
|
-
export const queueApiRef = createApiRef<QueueApiClient>("queue-api");
|
|
7
|
+
// Client definition is in @checkstack/queue-common - use with usePluginClient
|
|
8
|
+
export { QueueApi } from "@checkstack/queue-common";
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import {
|
|
3
|
+
useApi,
|
|
4
|
+
accessApiRef,
|
|
5
|
+
usePluginClient,
|
|
6
|
+
} from "@checkstack/frontend-api";
|
|
7
|
+
import { useSignal } from "@checkstack/signal-frontend";
|
|
8
|
+
import {
|
|
9
|
+
QueueApi,
|
|
10
|
+
QUEUE_LAG_CHANGED,
|
|
11
|
+
queueAccess,
|
|
12
|
+
type LagSeverity,
|
|
13
|
+
} from "@checkstack/queue-common";
|
|
14
|
+
import { Alert, AlertTitle, AlertDescription } from "@checkstack/ui";
|
|
15
|
+
import { AlertTriangle, AlertCircle } from "lucide-react";
|
|
16
|
+
|
|
17
|
+
interface QueueLagAlertProps {
|
|
18
|
+
/** Only show if user has queue settings access */
|
|
19
|
+
requireAccess?: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Displays a warning alert when queue is lagging (high pending jobs).
|
|
24
|
+
* Uses signal for real-time updates + initial fetch via useQuery.
|
|
25
|
+
*/
|
|
26
|
+
export const QueueLagAlert: React.FC<QueueLagAlertProps> = ({
|
|
27
|
+
requireAccess = true,
|
|
28
|
+
}) => {
|
|
29
|
+
const accessApi = useApi(accessApiRef);
|
|
30
|
+
const queueClient = usePluginClient(QueueApi);
|
|
31
|
+
|
|
32
|
+
// Check access if required
|
|
33
|
+
const { allowed, loading: accessLoading } = accessApi.useAccess(
|
|
34
|
+
queueAccess.settings.read
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
// Fetch lag status via useQuery
|
|
38
|
+
const { data: lagStatus, isLoading } = queueClient.getLagStatus.useQuery(
|
|
39
|
+
undefined,
|
|
40
|
+
{
|
|
41
|
+
enabled: !requireAccess || allowed,
|
|
42
|
+
staleTime: 30_000, // Cache for 30 seconds
|
|
43
|
+
}
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
// State for real-time updates via signal
|
|
47
|
+
const [signalData, setSignalData] = React.useState<
|
|
48
|
+
| {
|
|
49
|
+
pending: number;
|
|
50
|
+
severity: LagSeverity;
|
|
51
|
+
}
|
|
52
|
+
| undefined
|
|
53
|
+
>();
|
|
54
|
+
|
|
55
|
+
// Listen for real-time lag updates
|
|
56
|
+
useSignal(QUEUE_LAG_CHANGED, (payload) => {
|
|
57
|
+
setSignalData({ pending: payload.pending, severity: payload.severity });
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// Use signal data if available, otherwise use query data
|
|
61
|
+
const pending = signalData?.pending ?? lagStatus?.pending ?? 0;
|
|
62
|
+
const severity = signalData?.severity ?? lagStatus?.severity ?? "none";
|
|
63
|
+
|
|
64
|
+
// Don't render if loading, no access, or no lag
|
|
65
|
+
if (isLoading || accessLoading) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (requireAccess && !allowed) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (severity === "none") {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const variant = severity === "critical" ? "error" : "warning";
|
|
78
|
+
const Icon = severity === "critical" ? AlertCircle : AlertTriangle;
|
|
79
|
+
const title =
|
|
80
|
+
severity === "critical" ? "Queue backlog critical" : "Queue building up";
|
|
81
|
+
const description =
|
|
82
|
+
severity === "critical"
|
|
83
|
+
? `${pending} jobs pending. Consider scaling or reducing load.`
|
|
84
|
+
: `${pending} jobs pending. Some jobs may be delayed.`;
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<Alert variant={variant} className="mb-4">
|
|
88
|
+
<Icon className="h-5 w-5" />
|
|
89
|
+
<div>
|
|
90
|
+
<AlertTitle>{title}</AlertTitle>
|
|
91
|
+
<AlertDescription>{description}</AlertDescription>
|
|
92
|
+
</div>
|
|
93
|
+
</Alert>
|
|
94
|
+
);
|
|
95
|
+
};
|
package/src/index.tsx
CHANGED
|
@@ -1,31 +1,18 @@
|
|
|
1
1
|
import {
|
|
2
|
-
rpcApiRef,
|
|
3
|
-
ApiRef,
|
|
4
2
|
UserMenuItemsSlot,
|
|
5
3
|
createSlotExtension,
|
|
6
4
|
createFrontendPlugin,
|
|
7
5
|
} from "@checkstack/frontend-api";
|
|
8
|
-
import { queueApiRef, type QueueApiClient } from "./api";
|
|
9
6
|
import { QueueConfigPage } from "./pages/QueueConfigPage";
|
|
10
7
|
import { QueueUserMenuItems } from "./components/UserMenuItems";
|
|
11
8
|
import {
|
|
12
9
|
queueRoutes,
|
|
13
|
-
QueueApi,
|
|
14
10
|
pluginMetadata,
|
|
15
11
|
queueAccess,
|
|
16
12
|
} from "@checkstack/queue-common";
|
|
17
13
|
|
|
18
14
|
export const queuePlugin = createFrontendPlugin({
|
|
19
15
|
metadata: pluginMetadata,
|
|
20
|
-
apis: [
|
|
21
|
-
{
|
|
22
|
-
ref: queueApiRef,
|
|
23
|
-
factory: (deps: { get: <T>(ref: ApiRef<T>) => T }): QueueApiClient => {
|
|
24
|
-
const rpcApi = deps.get(rpcApiRef);
|
|
25
|
-
return rpcApi.forPlugin(QueueApi);
|
|
26
|
-
},
|
|
27
|
-
},
|
|
28
|
-
],
|
|
29
16
|
routes: [
|
|
30
17
|
{
|
|
31
18
|
route: queueRoutes.routes.config,
|
|
@@ -42,3 +29,4 @@ export const queuePlugin = createFrontendPlugin({
|
|
|
42
29
|
});
|
|
43
30
|
|
|
44
31
|
export * from "./api";
|
|
32
|
+
export { QueueLagAlert } from "./components/QueueLagAlert";
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import { useEffect, useState } from "react";
|
|
2
2
|
import {
|
|
3
|
-
useApi,
|
|
4
3
|
wrapInSuspense,
|
|
5
4
|
accessApiRef,
|
|
5
|
+
useApi,
|
|
6
|
+
usePluginClient,
|
|
6
7
|
} from "@checkstack/frontend-api";
|
|
7
|
-
import {
|
|
8
|
-
|
|
8
|
+
import {
|
|
9
|
+
QueuePluginDto,
|
|
10
|
+
queueAccess,
|
|
11
|
+
QueueApi,
|
|
12
|
+
} from "@checkstack/queue-common";
|
|
9
13
|
import {
|
|
10
14
|
Button,
|
|
11
15
|
Alert,
|
|
@@ -20,54 +24,55 @@ import {
|
|
|
20
24
|
CardTitle,
|
|
21
25
|
useToast,
|
|
22
26
|
} from "@checkstack/ui";
|
|
23
|
-
import { AlertTriangle, Save } from "lucide-react";
|
|
27
|
+
import { AlertTriangle, Save, Info, Gauge, Activity } from "lucide-react";
|
|
28
|
+
import { QueueLagAlert } from "../components/QueueLagAlert";
|
|
24
29
|
|
|
25
30
|
const QueueConfigPageContent = () => {
|
|
26
|
-
const
|
|
31
|
+
const queueClient = usePluginClient(QueueApi);
|
|
27
32
|
const accessApi = useApi(accessApiRef);
|
|
28
33
|
const toast = useToast();
|
|
29
|
-
const { allowed: canRead, loading: accessLoading } =
|
|
30
|
-
|
|
34
|
+
const { allowed: canRead, loading: accessLoading } = accessApi.useAccess(
|
|
35
|
+
queueAccess.settings.read
|
|
36
|
+
);
|
|
31
37
|
const { allowed: canUpdate } = accessApi.useAccess(
|
|
32
38
|
queueAccess.settings.manage
|
|
33
39
|
);
|
|
34
40
|
|
|
35
|
-
|
|
41
|
+
// Fetch plugins and configuration
|
|
42
|
+
const { data: pluginsList } = queueClient.getPlugins.useQuery();
|
|
43
|
+
const { data: configuration, refetch: refetchConfig } =
|
|
44
|
+
queueClient.getConfiguration.useQuery();
|
|
45
|
+
const updateConfigMutation = queueClient.updateConfiguration.useMutation();
|
|
46
|
+
|
|
36
47
|
const [selectedPluginId, setSelectedPluginId] = useState<string>("");
|
|
37
48
|
const [config, setConfig] = useState<Record<string, unknown>>({});
|
|
38
|
-
const [isSaving, setIsSaving] = useState(false);
|
|
39
49
|
|
|
50
|
+
// Sync state with fetched data
|
|
40
51
|
useEffect(() => {
|
|
41
|
-
|
|
42
|
-
const [pluginsList, configuration] = await Promise.all([
|
|
43
|
-
api.getPlugins(),
|
|
44
|
-
api.getConfiguration(),
|
|
45
|
-
]);
|
|
46
|
-
setPlugins(pluginsList);
|
|
52
|
+
if (configuration) {
|
|
47
53
|
setSelectedPluginId(configuration.pluginId);
|
|
48
54
|
setConfig(configuration.config);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
}, [api]);
|
|
55
|
+
}
|
|
56
|
+
}, [configuration]);
|
|
52
57
|
|
|
53
58
|
const handleSave = async () => {
|
|
54
59
|
if (!selectedPluginId) return;
|
|
55
|
-
setIsSaving(true);
|
|
56
60
|
try {
|
|
57
|
-
await
|
|
61
|
+
await updateConfigMutation.mutateAsync({
|
|
58
62
|
pluginId: selectedPluginId,
|
|
59
63
|
config,
|
|
60
64
|
});
|
|
61
65
|
toast.success("Configuration saved successfully!");
|
|
66
|
+
refetchConfig();
|
|
62
67
|
} catch (error) {
|
|
63
68
|
const message = error instanceof Error ? error.message : String(error);
|
|
64
69
|
toast.error(`Failed to save configuration: ${message}`);
|
|
65
|
-
} finally {
|
|
66
|
-
setIsSaving(false);
|
|
67
70
|
}
|
|
68
71
|
};
|
|
69
72
|
|
|
70
73
|
const isMemoryQueue = selectedPluginId === "memory";
|
|
74
|
+
const plugins: QueuePluginDto[] = pluginsList ?? [];
|
|
75
|
+
const isSaving = updateConfigMutation.isPending;
|
|
71
76
|
|
|
72
77
|
return (
|
|
73
78
|
<PageLayout
|
|
@@ -77,50 +82,115 @@ const QueueConfigPageContent = () => {
|
|
|
77
82
|
allowed={canRead}
|
|
78
83
|
maxWidth="3xl"
|
|
79
84
|
>
|
|
80
|
-
<
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
<
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
<
|
|
92
|
-
<
|
|
93
|
-
<
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
85
|
+
<QueueLagAlert requireAccess={false} />
|
|
86
|
+
<div className="space-y-6">
|
|
87
|
+
<Card>
|
|
88
|
+
<CardHeader>
|
|
89
|
+
<CardTitle>Queue Configuration</CardTitle>
|
|
90
|
+
<p className="text-sm text-muted-foreground">
|
|
91
|
+
Select and configure the queue plugin
|
|
92
|
+
</p>
|
|
93
|
+
</CardHeader>
|
|
94
|
+
<CardContent className="space-y-6">
|
|
95
|
+
{isMemoryQueue && (
|
|
96
|
+
<Alert variant="warning">
|
|
97
|
+
<AlertTriangle className="h-5 w-5" />
|
|
98
|
+
<div>
|
|
99
|
+
<AlertTitle>In-Memory Queue Warning</AlertTitle>
|
|
100
|
+
<AlertDescription>
|
|
101
|
+
The in-memory queue is suitable for development and
|
|
102
|
+
single-instance deployments only. It will not scale across
|
|
103
|
+
multiple instances and jobs will be lost on restart. For
|
|
104
|
+
production environments with multiple instances, consider
|
|
105
|
+
using a persistent queue implementation.
|
|
106
|
+
</AlertDescription>
|
|
107
|
+
</div>
|
|
108
|
+
</Alert>
|
|
109
|
+
)}
|
|
110
|
+
|
|
111
|
+
<PluginConfigForm
|
|
112
|
+
label="Queue Plugin"
|
|
113
|
+
plugins={plugins}
|
|
114
|
+
selectedPluginId={selectedPluginId}
|
|
115
|
+
onPluginChange={(value) => {
|
|
116
|
+
setSelectedPluginId(value);
|
|
117
|
+
setConfig({});
|
|
118
|
+
}}
|
|
119
|
+
config={config}
|
|
120
|
+
onConfigChange={setConfig}
|
|
121
|
+
disabled={!canUpdate}
|
|
122
|
+
/>
|
|
123
|
+
</CardContent>
|
|
124
|
+
<CardFooter className="flex justify-end gap-2">
|
|
125
|
+
<Button onClick={handleSave} disabled={!canUpdate || isSaving}>
|
|
126
|
+
<Save className="mr-2 h-4 w-4" />
|
|
127
|
+
{isSaving ? "Saving..." : "Save Configuration"}
|
|
128
|
+
</Button>
|
|
129
|
+
</CardFooter>
|
|
130
|
+
</Card>
|
|
131
|
+
|
|
132
|
+
{/* Performance Guidance */}
|
|
133
|
+
<Card>
|
|
134
|
+
<CardHeader>
|
|
135
|
+
<CardTitle className="flex items-center gap-2">
|
|
136
|
+
<Info className="h-5 w-5" />
|
|
137
|
+
Performance Tuning
|
|
138
|
+
</CardTitle>
|
|
139
|
+
</CardHeader>
|
|
140
|
+
<CardContent className="space-y-4">
|
|
141
|
+
<div className="grid gap-4 md:grid-cols-2">
|
|
142
|
+
<div className="space-y-2">
|
|
143
|
+
<h4 className="flex items-center gap-2 font-medium">
|
|
144
|
+
<Gauge className="h-4 w-4" />
|
|
145
|
+
Concurrency Settings
|
|
146
|
+
</h4>
|
|
147
|
+
<ul className="text-sm text-muted-foreground space-y-1 list-disc list-inside">
|
|
148
|
+
<li>
|
|
149
|
+
<strong>Default (10)</strong>: Conservative, safe for most
|
|
150
|
+
workloads
|
|
151
|
+
</li>
|
|
152
|
+
<li>
|
|
153
|
+
<strong>Moderate (25-50)</strong>: Good for I/O-bound health
|
|
154
|
+
checks
|
|
155
|
+
</li>
|
|
156
|
+
<li>
|
|
157
|
+
<strong>Aggressive (100)</strong>: Maximum, monitor resource
|
|
158
|
+
usage
|
|
159
|
+
</li>
|
|
160
|
+
</ul>
|
|
161
|
+
<p className="text-xs text-muted-foreground mt-2">
|
|
162
|
+
Formula: throughput ≈ concurrency / avg_job_duration
|
|
163
|
+
</p>
|
|
100
164
|
</div>
|
|
101
|
-
</Alert>
|
|
102
|
-
)}
|
|
103
165
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
166
|
+
<div className="space-y-2">
|
|
167
|
+
<h4 className="flex items-center gap-2 font-medium">
|
|
168
|
+
<Activity className="h-4 w-4" />
|
|
169
|
+
Bottleneck Indicators
|
|
170
|
+
</h4>
|
|
171
|
+
<ul className="text-sm text-muted-foreground space-y-1 list-disc list-inside">
|
|
172
|
+
<li>
|
|
173
|
+
<strong>Jobs queueing</strong>: Increase concurrency or
|
|
174
|
+
scale horizontally
|
|
175
|
+
</li>
|
|
176
|
+
<li>
|
|
177
|
+
<strong>High CPU (>70%)</strong>: Scale horizontally,
|
|
178
|
+
don't increase concurrency
|
|
179
|
+
</li>
|
|
180
|
+
<li>
|
|
181
|
+
<strong>DB connection errors</strong>: Reduce concurrency or
|
|
182
|
+
increase pool
|
|
183
|
+
</li>
|
|
184
|
+
<li>
|
|
185
|
+
<strong>Rate limit errors</strong>: Reduce concurrency for
|
|
186
|
+
external checks
|
|
187
|
+
</li>
|
|
188
|
+
</ul>
|
|
189
|
+
</div>
|
|
190
|
+
</div>
|
|
191
|
+
</CardContent>
|
|
192
|
+
</Card>
|
|
193
|
+
</div>
|
|
124
194
|
</PageLayout>
|
|
125
195
|
);
|
|
126
196
|
};
|