Skip to content

Commit 4bc7864

Browse files
fix(admin-ui): service status shows accurate values on dashboard instead of marking Not present as Down (#2505)
* fix(admin-ui): service status shows accurate values on dashboard instead of marking Not present as Down (#2500) * coderabbit suggestions * coderabbit suggestions * coderabbit suggestions
1 parent e3f3b89 commit 4bc7864

File tree

4 files changed

+160
-68
lines changed

4 files changed

+160
-68
lines changed

admin-ui/app/redux/features/healthSlice.ts

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import { createSlice } from '@reduxjs/toolkit'
1+
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
22
import reducerRegistry from 'Redux/reducers/ReducerRegistry'
33

4-
type HealthStatus = 'Running' | 'Not present'
4+
type HealthStatus = 'Running' | 'Not present' | 'Down'
55

66
export type HealthServiceKey =
77
| 'jans-lock'
@@ -33,6 +33,17 @@ const initialState: HealthState = {
3333
loading: false,
3434
}
3535

36+
interface HealthStatusResponsePayload {
37+
data?: {
38+
status?: HealthStatus
39+
db_status?: HealthStatus
40+
}
41+
}
42+
43+
interface HealthServerStatusResponsePayload {
44+
data?: HealthStatusResponse
45+
}
46+
3647
const healthSlice = createSlice({
3748
name: 'health',
3849
initialState,
@@ -43,14 +54,21 @@ const healthSlice = createSlice({
4354
getHealthServerStatus: (state) => {
4455
state.loading = true
4556
},
46-
getHealthStatusResponse: (state, action) => {
57+
getHealthStatusResponse: (state, action: PayloadAction<HealthStatusResponsePayload | null>) => {
4758
state.loading = false
4859
if (action.payload?.data) {
49-
state.serverStatus = action.payload.data.status
50-
state.dbStatus = action.payload.data.db_status
60+
if (action.payload.data.status) {
61+
state.serverStatus = action.payload.data.status
62+
}
63+
if (action.payload.data.db_status) {
64+
state.dbStatus = action.payload.data.db_status
65+
}
5166
}
5267
},
53-
getHealthServerStatusResponse: (state, action) => {
68+
getHealthServerStatusResponse: (
69+
state,
70+
action: PayloadAction<HealthServerStatusResponsePayload | null>,
71+
) => {
5472
state.loading = false
5573
if (action.payload?.data) {
5674
state.health = action.payload.data

admin-ui/app/redux/features/initSlice.ts

Lines changed: 50 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,49 @@
11
import reducerRegistry from 'Redux/reducers/ReducerRegistry'
22
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
33

4-
interface InitState {
5-
scripts: any[]
6-
clients: any[]
7-
scopes: any[]
8-
attributes: any[]
4+
interface GenericItem {
5+
[key: string]: string | number | boolean | string[] | number[] | boolean[] | null
6+
}
7+
8+
export interface InitState {
9+
scripts: GenericItem[]
10+
clients: GenericItem[]
11+
scopes: GenericItem[]
12+
attributes: GenericItem[]
913
totalClientsEntries: number
1014
isTimeout: boolean
1115
loadingScripts: boolean
12-
isLoading?: boolean
16+
}
17+
18+
interface ActionDataPayload {
19+
[key: string]: string | number | boolean | string[] | number[] | boolean[] | null
20+
}
21+
22+
interface ScriptsResponsePayload {
23+
data?: {
24+
entries?: GenericItem[]
25+
}
26+
}
27+
28+
interface ClientsResponsePayload {
29+
data?: {
30+
entries?: GenericItem[]
31+
totalEntriesCount?: number
32+
}
33+
}
34+
35+
interface ScopesResponsePayload {
36+
data?: GenericItem[]
37+
}
38+
39+
interface AttributesResponsePayload {
40+
data?: {
41+
entries?: GenericItem[]
42+
}
43+
}
44+
45+
interface ApiTimeoutPayload {
46+
isTimeout: boolean
1347
}
1448

1549
const initialState: InitState = {
@@ -26,40 +60,36 @@ const initSlice = createSlice({
2660
name: 'init',
2761
initialState,
2862
reducers: {
29-
getScripts: (state, _action: PayloadAction<{ action?: Record<string, unknown> }>) => {
63+
getScripts: (state, _action: PayloadAction<{ action?: ActionDataPayload }>) => {
3064
state.loadingScripts = true
3165
},
32-
getScriptsResponse: (state, action: PayloadAction<{ data?: { entries?: any[] } }>) => {
66+
getScriptsResponse: (state, action: PayloadAction<ScriptsResponsePayload>) => {
3367
state.loadingScripts = false
3468
if (action.payload?.data) {
35-
state.scripts = action.payload.data?.entries || []
69+
state.scripts = action.payload.data.entries || []
3670
}
3771
},
3872
getClients: () => {},
39-
getClientsResponse: (
40-
state,
41-
action: PayloadAction<{ data?: { entries?: any[]; totalEntriesCount?: number } }>,
42-
) => {
73+
getClientsResponse: (state, action: PayloadAction<ClientsResponsePayload>) => {
4374
if (action.payload?.data) {
44-
state.clients = action.payload.data?.entries || []
75+
state.clients = action.payload.data.entries || []
4576
state.totalClientsEntries = action.payload.data.totalEntriesCount || 0
4677
}
4778
},
4879
getScopes: () => {},
49-
getScopesResponse: (state, action: PayloadAction<{ data?: any[] }>) => {
80+
getScopesResponse: (state, action: PayloadAction<ScopesResponsePayload>) => {
5081
if (action.payload?.data) {
5182
state.scopes = action.payload.data
5283
}
5384
},
5485
getAttributes: () => {},
55-
getAttributesResponse: (state, action: PayloadAction<{ data?: { entries?: any[] } }>) => {
86+
getAttributesResponse: (state, action: PayloadAction<AttributesResponsePayload>) => {
5687
if (action.payload?.data) {
57-
state.attributes = action.payload.data?.entries || []
88+
state.attributes = action.payload.data.entries || []
5889
}
5990
},
60-
handleApiTimeout: (state, action: PayloadAction<{ isTimeout?: boolean }>) => {
61-
state.isLoading = false
62-
state.isTimeout = action.payload.isTimeout || false
91+
handleApiTimeout: (state, action: PayloadAction<ApiTimeoutPayload>) => {
92+
state.isTimeout = action.payload.isTimeout
6393
},
6494
},
6595
})

admin-ui/app/routes/Dashboards/DashboardPage.tsx

Lines changed: 77 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ import CrossIcon from 'Images/svg/cross.svg'
1818
import SetTitle from 'Utils/SetTitle'
1919
import styles from './styles'
2020
import type { HealthState } from 'Redux/features/healthSlice'
21+
import type { AuthState } from 'Redux/features/types/authTypes'
22+
import type { InitState } from 'Redux/features/initSlice'
23+
import type { LicenseDetailsState } from 'Redux/features/licenseDetailsSlice'
24+
import type { CedarPermissionsState } from '@/cedarling/types'
25+
import type { UserAction, ActionData } from 'Utils/PermChecker'
2126

2227
import { formatDate } from 'Utils/Util'
2328
import UsersIcon from '@/components/SVG/menu/Users'
@@ -34,43 +39,76 @@ import { useAppNavigation, ROUTES } from '@/helpers/navigation'
3439
interface DashboardHealthRootState {
3540
healthReducer: HealthState
3641
}
42+
43+
interface StatDataItem {
44+
month: number | string
45+
mau?: number
46+
authz_code_access_token_count?: number
47+
client_credentials_access_token_count?: number
48+
}
49+
50+
interface LockDetailItem {
51+
monthly_active_users?: number
52+
monthly_active_clients?: number
53+
}
54+
55+
interface RootState {
56+
mauReducer: {
57+
stat: StatDataItem[]
58+
loading: boolean
59+
}
60+
initReducer: InitState
61+
lockReducer: {
62+
lockDetail: LockDetailItem[] | LockDetailItem
63+
loading: boolean
64+
}
65+
authReducer: AuthState
66+
licenseDetailsReducer: LicenseDetailsState
67+
healthReducer: HealthState
68+
cedarPermissions: CedarPermissionsState
69+
}
70+
3771
// Constants moved outside component for better performance
3872
const FETCHING_LICENSE_DETAILS = 'Fetch license details'
3973

4074
function DashboardPage() {
4175
const { t } = useTranslation()
4276
const dispatch = useDispatch()
43-
const userAction = useMemo(() => ({}), [])
77+
const userAction = useMemo(() => ({ action_message: '', action_data: null }) as UserAction, [])
4478
const options = useMemo(() => ({}), [])
4579
const isTabletOrMobile = useMediaQuery({ query: '(max-width: 1224px)' })
4680
const isMobile = useMediaQuery({ maxWidth: 767 })
4781
const { classes } = styles()
48-
const [mauCount, setMauCount] = useState(null)
49-
const [tokenCount, setTokenCount] = useState(null)
82+
const [mauCount, setMauCount] = useState<number | null>(null)
83+
const [tokenCount, setTokenCount] = useState<number | null>(null)
5084

5185
const [requestStates, setRequestStates] = useState({
5286
licenseRequested: false,
5387
clientsRequested: false,
5488
})
55-
const statData = useSelector((state: any) => state.mauReducer.stat)
56-
const loading = useSelector((state: any) => state.mauReducer.loading)
57-
const clients = useSelector((state: any) => state.initReducer.clients)
58-
const lock = useSelector((state: any) => state.lockReducer.lockDetail)
59-
const { isUserInfoFetched } = useSelector((state: any) => state.authReducer)
60-
const totalClientsEntries = useSelector((state: any) => state.initReducer.totalClientsEntries)
61-
const license = useSelector((state: any) => state.licenseDetailsReducer.item)
89+
const statData = useSelector((state: RootState) => state.mauReducer.stat)
90+
const loading = useSelector((state: RootState) => state.mauReducer.loading)
91+
const clients = useSelector((state: RootState) => state.initReducer.clients)
92+
const lock = useSelector((state: RootState) => state.lockReducer.lockDetail)
93+
const { isUserInfoFetched } = useSelector((state: RootState) => state.authReducer)
94+
const totalClientsEntries = useSelector(
95+
(state: RootState) => state.initReducer.totalClientsEntries,
96+
)
97+
const license = useSelector((state: RootState) => state.licenseDetailsReducer.item)
6298
const serverStatus = useSelector(
6399
(state: DashboardHealthRootState) => state.healthReducer.serverStatus,
64100
)
65101
const serverHealth = useSelector((state: DashboardHealthRootState) => state.healthReducer.health)
66102
const dbStatus = useSelector((state: DashboardHealthRootState) => state.healthReducer.dbStatus)
67-
const access_token = useSelector((state: any) => state.authReducer.token?.access_token)
68-
const permissions = useSelector((state: any) => state.authReducer.permissions)
103+
const access_token = useSelector((state: RootState) => state.authReducer.token?.access_token)
104+
const permissions = useSelector((state: RootState) => state.authReducer.permissions)
69105

70106
const { hasCedarReadPermission, authorizeHelper } = useCedarling()
71107
const { navigateToRoute } = useAppNavigation()
72-
const cedarInitialized = useSelector((state: any) => state.cedarPermissions?.initialized)
73-
const cedarIsInitializing = useSelector((state: any) => state.cedarPermissions?.isInitializing)
108+
const cedarInitialized = useSelector((state: RootState) => state.cedarPermissions?.initialized)
109+
const cedarIsInitializing = useSelector(
110+
(state: RootState) => state.cedarPermissions?.isInitializing,
111+
)
74112

75113
const dashboardResourceId = useMemo(() => ADMIN_UI_RESOURCES.Dashboard, [])
76114
const dashboardScopes = useMemo(
@@ -108,7 +146,9 @@ function DashboardPage() {
108146
const formattedMonth =
109147
currentMonth > 9 ? currentMonth.toString() : '0' + currentMonth.toString()
110148
const yearMonth = currentYear.toString() + formattedMonth
111-
const currentMonthData = statData.find(({ month }: any) => month.toString() === yearMonth)
149+
const currentMonthData = statData.find(
150+
(item: StatDataItem) => item.month.toString() === yearMonth,
151+
)
112152

113153
const mau = currentMonthData?.mau
114154
const token =
@@ -132,8 +172,8 @@ function DashboardPage() {
132172
!requestStates.licenseRequested
133173
) {
134174
setRequestStates((prev) => ({ ...prev, licenseRequested: true }))
135-
buildPayload(userAction as any, FETCHING_LICENSE_DETAILS, options as any)
136-
dispatch(getLicenseDetails({} as any))
175+
buildPayload(userAction, FETCHING_LICENSE_DETAILS, options as ActionData)
176+
dispatch(getLicenseDetails())
137177
}
138178
}, [
139179
access_token,
@@ -155,8 +195,8 @@ function DashboardPage() {
155195
!requestStates.clientsRequested
156196
) {
157197
setRequestStates((prev) => ({ ...prev, clientsRequested: true }))
158-
buildPayload(userAction as any, 'Fetch openid connect clients', {} as any)
159-
dispatch(getClients({ action: userAction } as any))
198+
buildPayload(userAction, 'Fetch openid connect clients', {} as ActionData)
199+
dispatch(getClients())
160200
}
161201
}, [
162202
access_token,
@@ -167,11 +207,6 @@ function DashboardPage() {
167207
userAction,
168208
])
169209

170-
const isUp = useCallback((status: any) => {
171-
if (!status) return false
172-
return status.toUpperCase() === 'ONLINE' || status.toUpperCase() === 'RUNNING'
173-
}, [])
174-
175210
const summaryData = useMemo(() => {
176211
const baseData = [
177212
{
@@ -191,16 +226,17 @@ function DashboardPage() {
191226
},
192227
]
193228

194-
if (lock && lock.length > 0) {
229+
if (lock && Array.isArray(lock) && lock.length > 0) {
230+
const lockItem = lock[0] as LockDetailItem
195231
baseData.push(
196232
{
197233
text: t('dashboard.mau_users'),
198-
value: lock[0]?.monthly_active_users ?? 0,
234+
value: lockItem?.monthly_active_users ?? 0,
199235
icon: <JansLockUsers className={classes.summaryIcon} style={{ top: '8px' }} />,
200236
},
201237
{
202238
text: t('dashboard.mau_clients'),
203-
value: lock[0]?.monthly_active_clients ?? 0,
239+
value: lockItem?.monthly_active_clients ?? 0,
204240
icon: <JansLockClients className={classes.summaryIcon} style={{ top: '8px' }} />,
205241
},
206242
)
@@ -276,25 +312,32 @@ function DashboardPage() {
276312
const getClassName = useCallback(
277313
(key: string) => {
278314
const value = getStatusValue(key)
279-
return isUp(value) ? classes.checkText : classes.crossText
315+
if (!value) return classes.crossText
316+
const statusUpper = String(value).toUpperCase()
317+
return statusUpper === 'RUNNING' || statusUpper === 'ONLINE'
318+
? classes.checkText
319+
: classes.crossText
280320
},
281-
[getStatusValue, isUp, classes.checkText, classes.crossText],
321+
[getStatusValue, classes.checkText, classes.crossText],
282322
)
283323

284324
const getStatusText = useCallback(
285325
(key: string) => {
286326
const value = getStatusValue(key)
287-
return isUp(value) ? 'Running' : 'Down'
327+
if (!value) return 'Unknown'
328+
return value
288329
},
289-
[getStatusValue, isUp],
330+
[getStatusValue],
290331
)
291332

292333
const getStatusIcon = useCallback(
293334
(key: string) => {
294335
const value = getStatusValue(key)
295-
return isUp(value) ? CheckIcon : CrossIcon
336+
if (!value) return CrossIcon
337+
const statusUpper = String(value).toUpperCase()
338+
return statusUpper === 'RUNNING' || statusUpper === 'ONLINE' ? CheckIcon : CrossIcon
296339
},
297-
[getStatusValue, isUp],
340+
[getStatusValue],
298341
)
299342

300343
const StatusCard = useMemo(
@@ -351,7 +394,7 @@ function DashboardPage() {
351394
dispatch(
352395
auditLogoutLogs({
353396
message: 'Logging out due to insufficient permissions for Admin UI access.',
354-
} as any),
397+
}),
355398
)
356399
} else {
357400
navigateToRoute(ROUTES.LOGOUT)

0 commit comments

Comments
 (0)