Skip to content

Commit e855d78

Browse files
committed
feat(admin-ui): add SNAuth repository switcher
1 parent 591ce5c commit e855d78

10 files changed

Lines changed: 160 additions & 26 deletions

File tree

apps/sensenet/src/components/app-providers.tsx

Lines changed: 41 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
LocalizationProvider,
88
PersonalSettingsContextProvider,
99
RepositoryProvider,
10+
RepositorySwitchContext,
1011
ResponsiveContextProvider,
1112
ThemeProvider,
1213
} from '../context'
@@ -23,6 +24,7 @@ import {
2324
} from '../services'
2425
import {
2526
clearActiveRepositorySelection,
27+
hasSnAuthRepositoryTokens,
2628
normalizeRepositoryUrl,
2729
startSnAuthRepositoryLogin,
2830
} from '../services/repository-session'
@@ -65,6 +67,18 @@ export default function AppProviders({ children }: AppProvidersProps) {
6567
})
6668
}, [])
6769

70+
const switchRepository = useCallback((providedUrl: string) => {
71+
const normalizedUrl = normalizeRepositoryUrl(providedUrl)
72+
73+
if (!hasSnAuthRepositoryTokens(normalizedUrl)) {
74+
startSnAuthRepositoryLogin(normalizedUrl)
75+
}
76+
77+
window.localStorage.setItem('authType', 'SNAuth')
78+
setAuthType('SNAuth')
79+
setUrl(normalizedUrl)
80+
}, [])
81+
6882
useEffect(() => {
6983
const repoUrl = new URL(window.location.href).searchParams.get('repoUrl')
7084
if (repoUrl) {
@@ -96,31 +110,33 @@ export default function AppProviders({ children }: AppProvidersProps) {
96110
<GridLoadingProvider>
97111
<TreeLoadingProvider>
98112
<ThemeProvider>
99-
{authType === 'IdentityServer' ? (
100-
<RepositoryProvider url={url} changeAuthType={changeAuthType}>
101-
<ShareProvider>
102-
<ISAuthProvider>
103-
<ResponsiveContextProvider>
104-
<ExpandedItemsProvider>
105-
<DialogProvider>{children}</DialogProvider>
106-
</ExpandedItemsProvider>
107-
</ResponsiveContextProvider>
108-
</ISAuthProvider>
109-
</ShareProvider>
110-
</RepositoryProvider>
111-
) : (
112-
<SnAuthRepositoryProvider url={url} changeAuthType={changeAuthType}>
113-
<ShareProvider>
114-
<SNAuthProvider>
115-
<ResponsiveContextProvider>
116-
<ExpandedItemsProvider>
117-
<DialogProvider>{children}</DialogProvider>
118-
</ExpandedItemsProvider>
119-
</ResponsiveContextProvider>
120-
</SNAuthProvider>
121-
</ShareProvider>
122-
</SnAuthRepositoryProvider>
123-
)}
113+
<RepositorySwitchContext.Provider value={{ authType, switchRepository }}>
114+
{authType === 'IdentityServer' ? (
115+
<RepositoryProvider url={url} changeAuthType={changeAuthType}>
116+
<ShareProvider>
117+
<ISAuthProvider>
118+
<ResponsiveContextProvider>
119+
<ExpandedItemsProvider>
120+
<DialogProvider>{children}</DialogProvider>
121+
</ExpandedItemsProvider>
122+
</ResponsiveContextProvider>
123+
</ISAuthProvider>
124+
</ShareProvider>
125+
</RepositoryProvider>
126+
) : (
127+
<SnAuthRepositoryProvider url={url} changeAuthType={changeAuthType}>
128+
<ShareProvider>
129+
<SNAuthProvider>
130+
<ResponsiveContextProvider>
131+
<ExpandedItemsProvider>
132+
<DialogProvider>{children}</DialogProvider>
133+
</ExpandedItemsProvider>
134+
</ResponsiveContextProvider>
135+
</SNAuthProvider>
136+
</ShareProvider>
137+
</SnAuthRepositoryProvider>
138+
)}
139+
</RepositorySwitchContext.Provider>
124140
</ThemeProvider>
125141
</TreeLoadingProvider>
126142
</GridLoadingProvider>

apps/sensenet/src/components/drawer/PermanentDrawer.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { useDrawerItems, useLocalization } from '../../hooks'
1515
import { AddButton } from '../AddButton'
1616
import { SearchButton } from '../search-button'
1717
import { PermanentDrawerItem } from './PermanentDrawerItem'
18+
import { RepositorySelector } from './repository-selector'
1819

1920
const useStyles = makeStyles((theme: Theme) => {
2021
return createStyles({
@@ -109,6 +110,7 @@ export const PermanentDrawer = () => {
109110
</ListItemIcon>
110111
</ListItem>
111112
) : null}
113+
{opened ? <RepositorySelector /> : null}
112114
{matchPath(location.pathname, PATHS.savedQueries.appPath) ? <SearchButton isOpened={opened} /> : null}{' '}
113115
{matchPath(location.pathname, [
114116
PATHS.content.appPath,

apps/sensenet/src/components/drawer/TemporaryDrawer.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { ResponsiveContext, ResponsivePersonalSettings } from '../../context'
1818
import { useDrawerItems, useLocalization, useTheme } from '../../hooks'
1919
import { LogoutButton } from '../LogoutButton'
2020
import { UserAvatar } from '../UserAvatar'
21+
import { RepositorySelector } from './repository-selector'
2122

2223
type TemporaryDrawerProps = {
2324
isOpened: boolean
@@ -60,6 +61,7 @@ export const TemporaryDrawer = (props: TemporaryDrawerProps) => {
6061
transition: 'width 100ms ease-in-out',
6162
}}>
6263
<div style={{ paddingTop: '1em' }}>
64+
<RepositorySelector />
6365
{items.map((item, index) => {
6466
const isActive = matchPath(location.pathname, item.url)
6567
return isActive ? (
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import { FormControl, InputLabel, makeStyles, MenuItem, Select, Theme } from '@material-ui/core'
2+
import { useRepository } from '@sensenet/hooks-react'
3+
import React, { useEffect, useState } from 'react'
4+
import { useHistory } from 'react-router-dom'
5+
import { PATHS } from '../../application-paths'
6+
import { useRepositorySwitch } from '../../context'
7+
import { useLocalization } from '../../hooks'
8+
import {
9+
getAuthenticatedSnAuthRepositorySessions,
10+
normalizeRepositoryUrl,
11+
snAuthRepositorySessionsChangedEvent,
12+
} from '../../services/repository-session'
13+
14+
const useStyles = makeStyles((theme: Theme) => ({
15+
root: {
16+
padding: theme.spacing(1, 1, 0),
17+
},
18+
formControl: {
19+
width: '100%',
20+
},
21+
}))
22+
23+
const getRepositoryHost = (repoUrl: string) => {
24+
try {
25+
return new URL(repoUrl).host
26+
} catch {
27+
return repoUrl
28+
}
29+
}
30+
31+
export const RepositorySelector = () => {
32+
const classes = useStyles()
33+
const history = useHistory()
34+
const localization = useLocalization().repositorySelector
35+
const repository = useRepository()
36+
const { authType, switchRepository } = useRepositorySwitch()
37+
const currentRepositoryUrl = normalizeRepositoryUrl(repository.configuration.repositoryUrl)
38+
const [repositorySessions, setRepositorySessions] = useState(getAuthenticatedSnAuthRepositorySessions)
39+
40+
useEffect(() => {
41+
const refreshRepositorySessions = () => setRepositorySessions(getAuthenticatedSnAuthRepositorySessions())
42+
43+
window.addEventListener(snAuthRepositorySessionsChangedEvent, refreshRepositorySessions)
44+
45+
return () => window.removeEventListener(snAuthRepositorySessionsChangedEvent, refreshRepositorySessions)
46+
}, [])
47+
48+
if (authType !== 'SNAuth' || repositorySessions.length < 2) {
49+
return null
50+
}
51+
52+
return (
53+
<div className={classes.root}>
54+
<FormControl className={classes.formControl} variant="outlined" size="small">
55+
<InputLabel id="repository-selector-label">{localization.activeRepository}</InputLabel>
56+
<Select
57+
labelId="repository-selector-label"
58+
label={localization.activeRepository}
59+
value={currentRepositoryUrl}
60+
onChange={(ev) => {
61+
const nextRepositoryUrl = ev.target.value as string
62+
63+
if (nextRepositoryUrl === currentRepositoryUrl) {
64+
return
65+
}
66+
67+
switchRepository(nextRepositoryUrl)
68+
history.push(PATHS.landingPath.appPath)
69+
}}>
70+
{repositorySessions.map((repositorySession) => (
71+
<MenuItem key={repositorySession.repoUrl} value={repositorySession.repoUrl}>
72+
{getRepositoryHost(repositorySession.repoUrl)}
73+
</MenuItem>
74+
))}
75+
</Select>
76+
</FormControl>
77+
</div>
78+
)
79+
}

apps/sensenet/src/context/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ export * from './current-user-provider'
22
export * from './LocalizationContext'
33
export * from './PersonalSettingsContext'
44
export * from './repository-provider'
5+
export * from './repository-switch-context'
56
export * from './ResponsiveContextProvider'
67
export * from './ThemeContext'
78
export * from './ThemeProvider'
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { createContext, useContext } from 'react'
2+
import { AuthServerType } from '../auth-config'
3+
4+
export type RepositorySwitchContextValue = {
5+
authType: AuthServerType
6+
switchRepository: (repoUrl: string) => void
7+
}
8+
9+
export const RepositorySwitchContext = createContext<RepositorySwitchContextValue>({
10+
authType: 'SNAuth',
11+
switchRepository: () => {},
12+
})
13+
14+
export const useRepositorySwitch = () => useContext(RepositorySwitchContext)

apps/sensenet/src/context/sn-auth-repository-provider.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ export function SnAuthRepositoryProvider({
5050
})
5151
const cancelledLogin = useQuery().get('cancelledLogin')
5252
const [configString, setConfigString] = useState<any>()
53-
const [authServerUrl, setAuthServerUrl] = useState()
53+
const [authServerUrl, setAuthServerUrl] = useState<string>()
5454

5555
const clearState = useCallback(() => setAuthState({ repoUrl: '', config: null }), [])
5656

@@ -87,6 +87,7 @@ export function SnAuthRepositoryProvider({
8787
const storedConfig = getSnAuthRepositoryConfig(repoUrl)
8888

8989
setSelectedSnAuthRepository(repoUrl)
90+
setAuthServerUrl(storedConfig?.userManagerSettings.authority)
9091
setAuthState({ repoUrl, config: storedConfig?.userManagerSettings ?? null })
9192
setConfigString(storedConfig ? JSON.stringify(storedConfig) : null)
9293
}

apps/sensenet/src/localization/default.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,7 @@ const values = {
292292
sendLogWithCrashReports: 'Send log data with crash reports by default',
293293
},
294294
repositorySelector: {
295+
activeRepository: 'Repository',
295296
loggedInAs: 'You are currently logged in as {0}',
296297
notLoggedIn: 'You are not logged in.',
297298
anotherRepo: 'Another repository',

apps/sensenet/src/localization/hungarian.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ const values: Localization = {
142142
repositoryDisplayName: 'Egy tetszőleges megjelenítendő név',
143143
},
144144
repositorySelector: {
145+
activeRepository: 'Repository',
145146
anotherRepo: 'Másik repository',
146147
loggedInAs: 'Bejelentkezve mint {0}',
147148
notLoggedIn: 'Nincs bejelentkezve',

apps/sensenet/src/services/repository-session.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ const snAuthPendingLoginRepositoryKey = 'sn-auth-pending-login-repository'
1010
const snAuthScopedConfigKeyPrefix = 'sn-auth-config'
1111
const snAuthStorageKeyPrefix = 'sn-auth'
1212

13+
export const snAuthRepositorySessionsChangedEvent = 'sn-auth-repository-sessions-changed'
14+
1315
export type SnAuthRepositorySession = {
1416
repoUrl: string
1517
authServerUrl?: string
@@ -39,6 +41,15 @@ export const getSnAuthStorageKeyPrefix = (repoUrl: string) =>
3941
export const getSnAuthRepositoryConfigKey = (repoUrl: string) =>
4042
`${snAuthScopedConfigKeyPrefix}:${encodeRepositoryUrl(repoUrl)}`
4143

44+
export const hasSnAuthRepositoryTokens = (repoUrl: string) => {
45+
const storageKeyPrefix = getSnAuthStorageKeyPrefix(repoUrl)
46+
47+
return (
48+
!!window.localStorage.getItem(getStorageKey(ACCESS_TOKEN_KEY, storageKeyPrefix)) &&
49+
!!window.localStorage.getItem(getStorageKey(REFRESH_TOKEN_KEY, storageKeyPrefix))
50+
)
51+
}
52+
4253
export const getSelectedSnAuthRepository = () => window.localStorage.getItem(snAuthSelectedRepositoryKey)
4354

4455
export const setSelectedSnAuthRepository = (repoUrl: string) => {
@@ -83,6 +94,9 @@ export const getSnAuthRepositorySessions = (): SnAuthRepositorySession[] => {
8394
}
8495
}
8596

97+
export const getAuthenticatedSnAuthRepositorySessions = () =>
98+
getSnAuthRepositorySessions().filter((session) => hasSnAuthRepositoryTokens(session.repoUrl))
99+
86100
export const upsertSnAuthRepositorySession = (repoUrl: string, authServerUrl?: string) => {
87101
const normalizedRepoUrl = normalizeRepositoryUrl(repoUrl)
88102
const sessions = getSnAuthRepositorySessions().filter((session) => session.repoUrl !== normalizedRepoUrl)
@@ -97,6 +111,7 @@ export const upsertSnAuthRepositorySession = (repoUrl: string, authServerUrl?: s
97111

98112
window.localStorage.setItem(snAuthRepositorySessionsKey, JSON.stringify(nextSessions))
99113
setSelectedSnAuthRepository(normalizedRepoUrl)
114+
window.dispatchEvent(new Event(snAuthRepositorySessionsChangedEvent))
100115
}
101116

102117
export const removeSnAuthRepositorySession = (repoUrl: string) => {
@@ -112,6 +127,8 @@ export const removeSnAuthRepositorySession = (repoUrl: string) => {
112127
if (getSelectedSnAuthRepository() === normalizedRepoUrl) {
113128
clearSelectedSnAuthRepository()
114129
}
130+
131+
window.dispatchEvent(new Event(snAuthRepositorySessionsChangedEvent))
115132
}
116133

117134
export const getSnAuthRepositoryConfig = (repoUrl: string) => {

0 commit comments

Comments
 (0)