Skip to content

Commit a2e7b65

Browse files
Show targets and status icons in the dashboard
1 parent be5a9a8 commit a2e7b65

File tree

9 files changed

+893
-109
lines changed

9 files changed

+893
-109
lines changed

messages/en-US.json

Lines changed: 72 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1450,5 +1450,75 @@
14501450
"autoLoginRedirecting": "Redirecting to login...",
14511451
"autoLoginError": "Auto Login Error",
14521452
"autoLoginErrorNoRedirectUrl": "No redirect URL received from the identity provider.",
1453-
"autoLoginErrorGeneratingUrl": "Failed to generate authentication URL."
1454-
}
1453+
"autoLoginErrorGeneratingUrl": "Failed to generate authentication URL.",
1454+
"target": "Target",
1455+
"checking": "Checking",
1456+
"resourceDisabled": "This resource is currently disabled",
1457+
"checkingConnection": "Checking connection status...",
1458+
"connectionSuccessful": "Connection successful",
1459+
"connectionFailed": "Connection failed - target may be unreachable",
1460+
"statusUnknown": "Status unknown",
1461+
"targetNotConfigured": "Target not configured",
1462+
"tcpCheck": "TCP Check",
1463+
"connectionStatus": "Connection Status",
1464+
"responseTime": "Response Time",
1465+
"lastChecked": "Last Checked",
1466+
"refreshStatus": "Refresh Status",
1467+
"showColumns": "Show Columns",
1468+
"hideColumns": "Hide Columns",
1469+
"columnVisibility": "Column Visibility",
1470+
"toggleColumn": "Toggle {columnName} column",
1471+
"allColumns": "All Columns",
1472+
"defaultColumns": "Default Columns",
1473+
"customizeView": "Customize View",
1474+
"viewOptions": "View Options",
1475+
"statusIndicators": "Status Indicators",
1476+
"quickHealthCheck": "Quick Health Check",
1477+
"networkConnectivity": "Network Connectivity",
1478+
"targetReachable": "Target is reachable",
1479+
"targetUnreachable": "Target is unreachable",
1480+
"connectivityError": "Connectivity error: {error}",
1481+
"checkingAllResources": "Checking all resources...",
1482+
"healthCheckComplete": "Health check complete",
1483+
"resourcesOnline": "{count} resources online",
1484+
"resourcesOffline": "{count} resources offline",
1485+
"batchStatusCheck": "Batch Status Check",
1486+
"runHealthCheck": "Run Health Check",
1487+
"autoRefresh": "Auto Refresh",
1488+
"manualRefresh": "Manual Refresh",
1489+
"refreshInterval": "Refresh Interval",
1490+
"statusRefreshed": "Status refreshed successfully",
1491+
"statusRefreshFailed": "Failed to refresh status",
1492+
"tcpConnectionTest": "TCP Connection Test",
1493+
"portConnectivity": "Port Connectivity",
1494+
"networkLatency": "Network Latency",
1495+
"ms": "ms",
1496+
"seconds": "seconds",
1497+
"timeout": "Timeout",
1498+
"connectionTimeout": "Connection timeout after {timeout}ms",
1499+
"dnsBased": "DNS-based",
1500+
"ipBased": "IP-based",
1501+
"localhost": "Localhost",
1502+
"remoteHost": "Remote Host",
1503+
"showStatusColumn": "Show status indicators for quick health visibility",
1504+
"showTargetColumn": "Show target destinations to see where resources point",
1505+
"customizeColumns": "Customize which columns are visible in the table",
1506+
"statusTooltip": "Click to refresh status for this resource",
1507+
"targetTooltip": "The destination that this resource proxies to",
1508+
"bulkActions": "Bulk Actions",
1509+
"selectAll": "Select All",
1510+
"selectNone": "Select None",
1511+
"selectedResources": "Selected Resources",
1512+
"enableSelected": "Enable Selected",
1513+
"disableSelected": "Disable Selected",
1514+
"checkSelectedStatus": "Check Status of Selected",
1515+
"exportSelected": "Export Selected",
1516+
"healthDashboard": "Health Dashboard",
1517+
"systemHealth": "System Health",
1518+
"resourcesHealthSummary": "Resources Health Summary",
1519+
"overallHealth": "Overall Health",
1520+
"healthy": "Healthy",
1521+
"degraded": "Degraded",
1522+
"critical": "Critical",
1523+
"monitoringNote": "Status indicators provide a quick TCP connectivity check refreshed when the page loads. This is not full monitoring but helps identify unreachable targets at a glance."
1524+
}

server/auth/actions.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ export enum ActionsEnum {
2323
deleteResource = "deleteResource",
2424
getResource = "getResource",
2525
listResources = "listResources",
26+
tcpCheck = "tcpCheck",
27+
batchTcpCheck = "batchTcpCheck",
2628
updateResource = "updateResource",
2729
createTarget = "createTarget",
2830
deleteTarget = "deleteTarget",

server/routers/external.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,20 @@ authenticated.get(
286286
resource.listResources
287287
);
288288

289+
authenticated.post(
290+
"/org/:orgId/resources/tcp-check",
291+
verifyOrgAccess,
292+
verifyUserHasAction(ActionsEnum.tcpCheck),
293+
resource.tcpCheck
294+
);
295+
296+
authenticated.post(
297+
"/org/:orgId/resources/tcp-check-batch",
298+
verifyOrgAccess,
299+
verifyUserHasAction(ActionsEnum.batchTcpCheck),
300+
resource.batchTcpCheck
301+
);
302+
289303
authenticated.get(
290304
"/org/:orgId/user-resources",
291305
verifyOrgAccess,

server/routers/resource/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,4 @@ export * from "./deleteResourceRule";
2222
export * from "./listResourceRules";
2323
export * from "./updateResourceRule";
2424
export * from "./getUserResources";
25+
export * from "./tcpCheck";

server/routers/resource/listResources.ts

Lines changed: 97 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ import {
66
userResources,
77
roleResources,
88
resourcePassword,
9-
resourcePincode
9+
resourcePincode,
10+
targets,
1011
} from "@server/db";
1112
import response from "@server/lib/response";
1213
import HttpCode from "@server/types/HttpCode";
@@ -39,6 +40,51 @@ const listResourcesSchema = z.object({
3940
.pipe(z.number().int().nonnegative())
4041
});
4142

43+
// (resource fields + a single joined target)
44+
type JoinedRow = {
45+
resourceId: number;
46+
name: string;
47+
ssl: boolean;
48+
fullDomain: string | null;
49+
passwordId: number | null;
50+
sso: boolean;
51+
pincodeId: number | null;
52+
whitelist: boolean;
53+
http: boolean;
54+
protocol: string;
55+
proxyPort: number | null;
56+
enabled: boolean;
57+
domainId: string | null;
58+
59+
targetId: number | null;
60+
targetIp: string | null;
61+
targetPort: number | null;
62+
targetEnabled: boolean | null;
63+
};
64+
65+
// grouped by resource with targets[])
66+
export type ResourceWithTargets = {
67+
resourceId: number;
68+
name: string;
69+
ssl: boolean;
70+
fullDomain: string | null;
71+
passwordId: number | null;
72+
sso: boolean;
73+
pincodeId: number | null;
74+
whitelist: boolean;
75+
http: boolean;
76+
protocol: string;
77+
proxyPort: number | null;
78+
enabled: boolean;
79+
domainId: string | null;
80+
targets: Array<{
81+
targetId: number;
82+
ip: string;
83+
port: number;
84+
enabled: boolean;
85+
}>;
86+
};
87+
4288
function queryResources(accessibleResourceIds: number[], orgId: string) {
4389
return db
4490
.select({
@@ -54,7 +100,12 @@ function queryResources(accessibleResourceIds: number[], orgId: string) {
54100
protocol: resources.protocol,
55101
proxyPort: resources.proxyPort,
56102
enabled: resources.enabled,
57-
domainId: resources.domainId
103+
domainId: resources.domainId,
104+
105+
targetId: targets.targetId,
106+
targetIp: targets.ip,
107+
targetPort: targets.port,
108+
targetEnabled: targets.enabled,
58109
})
59110
.from(resources)
60111
.leftJoin(
@@ -65,6 +116,7 @@ function queryResources(accessibleResourceIds: number[], orgId: string) {
65116
resourcePincode,
66117
eq(resourcePincode.resourceId, resources.resourceId)
67118
)
119+
.leftJoin(targets, eq(targets.resourceId, resources.resourceId))
68120
.where(
69121
and(
70122
inArray(resources.resourceId, accessibleResourceIds),
@@ -74,7 +126,7 @@ function queryResources(accessibleResourceIds: number[], orgId: string) {
74126
}
75127

76128
export type ListResourcesResponse = {
77-
resources: NonNullable<Awaited<ReturnType<typeof queryResources>>>;
129+
resources: ResourceWithTargets[];
78130
pagination: { total: number; limit: number; offset: number };
79131
};
80132

@@ -139,7 +191,7 @@ export async function listResources(
139191
);
140192
}
141193

142-
let accessibleResources;
194+
let accessibleResources: Array<{ resourceId: number }>;
143195
if (req.user) {
144196
accessibleResources = await db
145197
.select({
@@ -176,9 +228,48 @@ export async function listResources(
176228

177229
const baseQuery = queryResources(accessibleResourceIds, orgId);
178230

179-
const resourcesList = await baseQuery!.limit(limit).offset(offset);
231+
const rows: JoinedRow[] = await baseQuery.limit(limit).offset(offset);
232+
233+
// avoids TS issues with reduce/never[]
234+
const map = new Map<number, ResourceWithTargets>();
235+
236+
for (const row of rows) {
237+
let entry = map.get(row.resourceId);
238+
if (!entry) {
239+
entry = {
240+
resourceId: row.resourceId,
241+
name: row.name,
242+
ssl: row.ssl,
243+
fullDomain: row.fullDomain,
244+
passwordId: row.passwordId,
245+
sso: row.sso,
246+
pincodeId: row.pincodeId,
247+
whitelist: row.whitelist,
248+
http: row.http,
249+
protocol: row.protocol,
250+
proxyPort: row.proxyPort,
251+
enabled: row.enabled,
252+
domainId: row.domainId,
253+
targets: [],
254+
};
255+
map.set(row.resourceId, entry);
256+
}
257+
258+
// Push target if present (left join can be null)
259+
if (row.targetId != null && row.targetIp && row.targetPort != null && row.targetEnabled != null) {
260+
entry.targets.push({
261+
targetId: row.targetId,
262+
ip: row.targetIp,
263+
port: row.targetPort,
264+
enabled: row.targetEnabled,
265+
});
266+
}
267+
}
268+
269+
const resourcesList: ResourceWithTargets[] = Array.from(map.values());
270+
180271
const totalCountResult = await countQuery;
181-
const totalCount = totalCountResult[0].count;
272+
const totalCount = totalCountResult[0]?.count ?? 0;
182273

183274
return response<ListResourcesResponse>(res, {
184275
data: {

0 commit comments

Comments
 (0)