@backstage/plugin-user-settings 0.7.11-next.2 → 0.7.11

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.
@@ -0,0 +1,756 @@
1
+ import { useOutlet } from 'react-router-dom';
2
+ import React, { useState, useEffect, useCallback, cloneElement } from 'react';
3
+ import { Typography, Button, makeStyles, Avatar, ListItem, ListItemIcon, ListItemText, Tooltip, Grid, ListItemSecondaryAction, List, Switch, TextField, IconButton, Menu, MenuItem } from '@material-ui/core';
4
+ import { EmptyState, CodeSnippet, sidebarConfig, InfoCard, useSidebarPinState, Page, Header, RoutedTabs } from '@backstage/core-components';
5
+ import Star from '@material-ui/icons/Star';
6
+ import { useApi, errorApiRef, SessionState, googleAuthApiRef, microsoftAuthApiRef, githubAuthApiRef, gitlabAuthApiRef, oktaAuthApiRef, bitbucketAuthApiRef, oneloginAuthApiRef, atlassianAuthApiRef, bitbucketServerAuthApiRef, configApiRef, featureFlagsApiRef, FeatureFlagState, identityApiRef, alertApiRef, appThemeApiRef, attachComponentData, useElementFilter } from '@backstage/core-plugin-api';
7
+ import ClearIcon from '@material-ui/icons/Clear';
8
+ import useAsync from 'react-use/lib/useAsync';
9
+ import SignOutIcon from '@material-ui/icons/MeetingRoom';
10
+ import MoreVertIcon from '@material-ui/icons/MoreVert';
11
+ import useObservable from 'react-use/lib/useObservable';
12
+ import AutoIcon from '@material-ui/icons/BrightnessAuto';
13
+ import ToggleButton from '@material-ui/lab/ToggleButton';
14
+ import ToggleButtonGroup from '@material-ui/lab/ToggleButtonGroup';
15
+ import { useTranslationRef, appLanguageApiRef } from '@backstage/core-plugin-api/alpha';
16
+ import { u as userSettingsTranslationRef } from './translation-d20c444b.esm.js';
17
+ import { EntityRefLinks } from '@backstage/plugin-catalog-react';
18
+ import Grid$1 from '@material-ui/core/Grid';
19
+ import Typography$1 from '@material-ui/core/Typography';
20
+
21
+ const EXAMPLE$1 = `auth:
22
+ providers:
23
+ google:
24
+ development:
25
+ clientId: \${AUTH_GOOGLE_CLIENT_ID}
26
+ clientSecret: \${AUTH_GOOGLE_CLIENT_SECRET}
27
+ `;
28
+ const EmptyProviders = () => /* @__PURE__ */ React.createElement(
29
+ EmptyState,
30
+ {
31
+ missing: "content",
32
+ title: "No Authentication Providers",
33
+ description: "You can add Authentication Providers to Backstage which allows you to use these providers to authenticate yourself.",
34
+ action: /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Typography, { variant: "body1" }, "Open ", /* @__PURE__ */ React.createElement("code", null, "app-config.yaml"), " and make the changes as highlighted below:"), /* @__PURE__ */ React.createElement(
35
+ CodeSnippet,
36
+ {
37
+ text: EXAMPLE$1,
38
+ language: "yaml",
39
+ showLineNumbers: true,
40
+ highlightedNumbers: [3, 4, 5, 6, 7, 8],
41
+ customStyle: { background: "inherit", fontSize: "115%" }
42
+ }
43
+ ), /* @__PURE__ */ React.createElement(
44
+ Button,
45
+ {
46
+ variant: "contained",
47
+ color: "primary",
48
+ href: "https://backstage.io/docs/auth/add-auth-provider"
49
+ },
50
+ "Read More"
51
+ ))
52
+ }
53
+ );
54
+
55
+ const useStyles$3 = makeStyles((theme) => ({
56
+ avatar: {
57
+ width: ({ size }) => size,
58
+ height: ({ size }) => size,
59
+ fontSize: ({ size }) => size * 0.7,
60
+ border: `1px solid ${theme.palette.textSubtle}`
61
+ }
62
+ }));
63
+ const ProviderSettingsAvatar = ({ size, picture }) => {
64
+ const { iconSize } = sidebarConfig;
65
+ const classes = useStyles$3(size ? { size } : { size: iconSize });
66
+ return /* @__PURE__ */ React.createElement(Avatar, { src: picture, className: classes.avatar });
67
+ };
68
+
69
+ const emptyProfile = {};
70
+ const ProviderSettingsItem = (props) => {
71
+ const { title, description, icon: Icon, apiRef } = props;
72
+ const api = useApi(apiRef);
73
+ const errorApi = useApi(errorApiRef);
74
+ const [signedIn, setSignedIn] = useState(false);
75
+ const [profile, setProfile] = useState(emptyProfile);
76
+ useEffect(() => {
77
+ let didCancel = false;
78
+ const subscription = api.sessionState$().subscribe((sessionState) => {
79
+ if (sessionState !== SessionState.SignedIn) {
80
+ setProfile(emptyProfile);
81
+ setSignedIn(false);
82
+ }
83
+ if (!didCancel) {
84
+ api.getProfile({ optional: true }).then((profileResponse) => {
85
+ if (!didCancel) {
86
+ if (sessionState === SessionState.SignedIn) {
87
+ setSignedIn(true);
88
+ }
89
+ if (profileResponse) {
90
+ setProfile(profileResponse);
91
+ }
92
+ }
93
+ });
94
+ }
95
+ });
96
+ return () => {
97
+ didCancel = true;
98
+ subscription.unsubscribe();
99
+ };
100
+ }, [api]);
101
+ return /* @__PURE__ */ React.createElement(ListItem, null, /* @__PURE__ */ React.createElement(ListItemIcon, null, /* @__PURE__ */ React.createElement(Icon, null)), /* @__PURE__ */ React.createElement(
102
+ ListItemText,
103
+ {
104
+ primary: title,
105
+ secondary: /* @__PURE__ */ React.createElement(Tooltip, { placement: "top", arrow: true, title: description }, /* @__PURE__ */ React.createElement(Grid, { container: true, spacing: 6 }, /* @__PURE__ */ React.createElement(Grid, { item: true }, /* @__PURE__ */ React.createElement(ProviderSettingsAvatar, { size: 48, picture: profile.picture })), /* @__PURE__ */ React.createElement(Grid, { item: true, xs: 12, sm: true, container: true }, /* @__PURE__ */ React.createElement(Grid, { item: true, xs: true, container: true, direction: "column", spacing: 2 }, /* @__PURE__ */ React.createElement(Grid, { item: true, xs: true }, /* @__PURE__ */ React.createElement(
106
+ Typography,
107
+ {
108
+ variant: "subtitle1",
109
+ color: "textPrimary",
110
+ gutterBottom: true
111
+ },
112
+ profile.displayName
113
+ ), /* @__PURE__ */ React.createElement(Typography, { variant: "body2", color: "textSecondary" }, profile.email), /* @__PURE__ */ React.createElement(Typography, { variant: "body2", color: "textSecondary" }, description)))))),
114
+ secondaryTypographyProps: { noWrap: true, style: { width: "80%" } }
115
+ }
116
+ ), /* @__PURE__ */ React.createElement(ListItemSecondaryAction, null, /* @__PURE__ */ React.createElement(
117
+ Tooltip,
118
+ {
119
+ placement: "top",
120
+ arrow: true,
121
+ title: signedIn ? `Sign out from ${title}` : `Sign in to ${title}`
122
+ },
123
+ /* @__PURE__ */ React.createElement(
124
+ Button,
125
+ {
126
+ variant: "outlined",
127
+ color: "primary",
128
+ onClick: () => {
129
+ const action = signedIn ? api.signOut() : api.signIn();
130
+ action.catch((error) => errorApi.post(error));
131
+ }
132
+ },
133
+ signedIn ? `Sign out` : `Sign in`
134
+ )
135
+ )));
136
+ };
137
+
138
+ const DefaultProviderSettings = (props) => {
139
+ const { configuredProviders } = props;
140
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, configuredProviders.includes("google") && /* @__PURE__ */ React.createElement(
141
+ ProviderSettingsItem,
142
+ {
143
+ title: "Google",
144
+ description: "Provides authentication towards Google APIs and identities",
145
+ apiRef: googleAuthApiRef,
146
+ icon: Star
147
+ }
148
+ ), configuredProviders.includes("microsoft") && /* @__PURE__ */ React.createElement(
149
+ ProviderSettingsItem,
150
+ {
151
+ title: "Microsoft",
152
+ description: "Provides authentication towards Microsoft APIs and identities",
153
+ apiRef: microsoftAuthApiRef,
154
+ icon: Star
155
+ }
156
+ ), configuredProviders.includes("github") && /* @__PURE__ */ React.createElement(
157
+ ProviderSettingsItem,
158
+ {
159
+ title: "GitHub",
160
+ description: "Provides authentication towards GitHub APIs",
161
+ apiRef: githubAuthApiRef,
162
+ icon: Star
163
+ }
164
+ ), configuredProviders.includes("gitlab") && /* @__PURE__ */ React.createElement(
165
+ ProviderSettingsItem,
166
+ {
167
+ title: "GitLab",
168
+ description: "Provides authentication towards GitLab APIs",
169
+ apiRef: gitlabAuthApiRef,
170
+ icon: Star
171
+ }
172
+ ), configuredProviders.includes("okta") && /* @__PURE__ */ React.createElement(
173
+ ProviderSettingsItem,
174
+ {
175
+ title: "Okta",
176
+ description: "Provides authentication towards Okta APIs",
177
+ apiRef: oktaAuthApiRef,
178
+ icon: Star
179
+ }
180
+ ), configuredProviders.includes("bitbucket") && /* @__PURE__ */ React.createElement(
181
+ ProviderSettingsItem,
182
+ {
183
+ title: "Bitbucket",
184
+ description: "Provides authentication towards Bitbucket APIs",
185
+ apiRef: bitbucketAuthApiRef,
186
+ icon: Star
187
+ }
188
+ ), configuredProviders.includes("onelogin") && /* @__PURE__ */ React.createElement(
189
+ ProviderSettingsItem,
190
+ {
191
+ title: "OneLogin",
192
+ description: "Provides authentication towards OneLogin APIs",
193
+ apiRef: oneloginAuthApiRef,
194
+ icon: Star
195
+ }
196
+ ), configuredProviders.includes("atlassian") && /* @__PURE__ */ React.createElement(
197
+ ProviderSettingsItem,
198
+ {
199
+ title: "Atlassian",
200
+ description: "Provides authentication towards Atlassian APIs",
201
+ apiRef: atlassianAuthApiRef,
202
+ icon: Star
203
+ }
204
+ ), configuredProviders.includes("bitbucketServer") && /* @__PURE__ */ React.createElement(
205
+ ProviderSettingsItem,
206
+ {
207
+ title: "Bitbucket Server",
208
+ description: "Provides authentication towards Bitbucket Server APIs",
209
+ apiRef: bitbucketServerAuthApiRef,
210
+ icon: Star
211
+ }
212
+ ));
213
+ };
214
+
215
+ const UserSettingsAuthProviders = (props) => {
216
+ const { providerSettings } = props;
217
+ const configApi = useApi(configApiRef);
218
+ const providersConfig = configApi.getOptionalConfig("auth.providers");
219
+ const configuredProviders = (providersConfig == null ? void 0 : providersConfig.keys()) || [];
220
+ const providers = providerSettings != null ? providerSettings : /* @__PURE__ */ React.createElement(DefaultProviderSettings, { configuredProviders });
221
+ if (!providerSettings && !(configuredProviders == null ? void 0 : configuredProviders.length)) {
222
+ return /* @__PURE__ */ React.createElement(EmptyProviders, null);
223
+ }
224
+ return /* @__PURE__ */ React.createElement(InfoCard, { title: "Available Providers" }, /* @__PURE__ */ React.createElement(List, { dense: true }, providers));
225
+ };
226
+
227
+ const EXAMPLE = `import { createPlugin } from '@backstage/core-plugin-api';
228
+
229
+ export default createPlugin({
230
+ id: 'plugin-name',
231
+ featureFlags: [{ name: 'enable-example-feature' }],
232
+ });
233
+ `;
234
+ const EmptyFlags = () => /* @__PURE__ */ React.createElement(
235
+ EmptyState,
236
+ {
237
+ missing: "content",
238
+ title: "No Feature Flags",
239
+ description: "Feature Flags make it possible for plugins to register features in Backstage for users to opt into. You can use this to split out logic in your code for manual A/B testing, etc.",
240
+ action: /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Typography, { variant: "body1" }, "An example for how to add a feature flag is highlighted below:"), /* @__PURE__ */ React.createElement(
241
+ CodeSnippet,
242
+ {
243
+ text: EXAMPLE,
244
+ language: "typescript",
245
+ showLineNumbers: true,
246
+ highlightedNumbers: [6],
247
+ customStyle: { background: "inherit", fontSize: "115%" }
248
+ }
249
+ ), /* @__PURE__ */ React.createElement(
250
+ Button,
251
+ {
252
+ variant: "contained",
253
+ color: "primary",
254
+ href: "https://backstage.io/docs/api/utility-apis"
255
+ },
256
+ "Read More"
257
+ ))
258
+ }
259
+ );
260
+
261
+ const getSecondaryText = (flag) => {
262
+ if (flag.description) {
263
+ return flag.description;
264
+ }
265
+ return flag.pluginId ? `Registered in ${flag.pluginId} plugin` : "Registered in the application";
266
+ };
267
+ const FlagItem = ({ flag, enabled, toggleHandler }) => /* @__PURE__ */ React.createElement(ListItem, { divider: true, button: true, onClick: () => toggleHandler(flag.name) }, /* @__PURE__ */ React.createElement(ListItemIcon, null, /* @__PURE__ */ React.createElement(Tooltip, { placement: "top", arrow: true, title: enabled ? "Disable" : "Enable" }, /* @__PURE__ */ React.createElement(Switch, { color: "primary", checked: enabled, name: flag.name }))), /* @__PURE__ */ React.createElement(ListItemText, { primary: flag.name, secondary: getSecondaryText(flag) }));
268
+
269
+ const sortFlags = (flags, featureFlagsApi) => {
270
+ const activeFlags = flags.filter((flag) => featureFlagsApi.isActive(flag.name));
271
+ const idleFlags = flags.filter((flag) => !featureFlagsApi.isActive(flag.name));
272
+ return [...activeFlags, ...idleFlags];
273
+ };
274
+ const UserSettingsFeatureFlags = () => {
275
+ const featureFlagsApi = useApi(featureFlagsApiRef);
276
+ const inputRef = React.useRef();
277
+ const initialFeatureFlags = featureFlagsApi.getRegisteredFlags();
278
+ const initialFeatureFlagsSorted = sortFlags(
279
+ initialFeatureFlags,
280
+ featureFlagsApi
281
+ );
282
+ const [featureFlags] = useState(initialFeatureFlagsSorted);
283
+ const initialFlagState = Object.fromEntries(
284
+ featureFlags.map(({ name }) => [name, featureFlagsApi.isActive(name)])
285
+ );
286
+ const [state, setState] = useState(initialFlagState);
287
+ const [filterInput, setFilterInput] = useState("");
288
+ const toggleFlag = useCallback(
289
+ (flagName) => {
290
+ const newState = featureFlagsApi.isActive(flagName) ? FeatureFlagState.None : FeatureFlagState.Active;
291
+ featureFlagsApi.save({
292
+ states: { [flagName]: newState },
293
+ merge: true
294
+ });
295
+ setState((prevState) => ({
296
+ ...prevState,
297
+ [flagName]: newState === FeatureFlagState.Active
298
+ }));
299
+ },
300
+ [featureFlagsApi]
301
+ );
302
+ if (!featureFlags.length) {
303
+ return /* @__PURE__ */ React.createElement(EmptyFlags, null);
304
+ }
305
+ const clearFilterInput = () => {
306
+ var _a;
307
+ setFilterInput("");
308
+ (_a = inputRef == null ? void 0 : inputRef.current) == null ? void 0 : _a.focus();
309
+ };
310
+ const filteredFeatureFlags = featureFlags.filter((featureFlag) => {
311
+ const featureFlagName = featureFlag.name.toLocaleLowerCase("en-US");
312
+ return featureFlagName.includes(filterInput.toLocaleLowerCase("en-US"));
313
+ });
314
+ const Header = () => /* @__PURE__ */ React.createElement(Grid, { container: true, style: { justifyContent: "space-between" } }, /* @__PURE__ */ React.createElement(Grid, { item: true, xs: 6, md: 8 }, /* @__PURE__ */ React.createElement(Typography, { variant: "h5" }, "Feature Flags"), /* @__PURE__ */ React.createElement(Typography, { variant: "subtitle1" }, "Please refresh the page when toggling feature flags")), featureFlags.length >= 10 && /* @__PURE__ */ React.createElement(Grid, { item: true, xs: 6, md: 4 }, /* @__PURE__ */ React.createElement(
315
+ TextField,
316
+ {
317
+ label: "Filter",
318
+ style: { display: "flex", justifyContent: "flex-end" },
319
+ inputRef: (ref) => ref && ref.focus(),
320
+ InputProps: {
321
+ ...filterInput.length && {
322
+ endAdornment: /* @__PURE__ */ React.createElement(
323
+ IconButton,
324
+ {
325
+ "aria-label": "Clear filter",
326
+ onClick: clearFilterInput,
327
+ edge: "end"
328
+ },
329
+ /* @__PURE__ */ React.createElement(ClearIcon, null)
330
+ )
331
+ }
332
+ },
333
+ onChange: (e) => setFilterInput(e.target.value),
334
+ value: filterInput
335
+ }
336
+ )));
337
+ return /* @__PURE__ */ React.createElement(InfoCard, { title: /* @__PURE__ */ React.createElement(Header, null) }, /* @__PURE__ */ React.createElement(List, { dense: true }, filteredFeatureFlags.map((featureFlag) => {
338
+ const enabled = Boolean(state[featureFlag.name]);
339
+ return /* @__PURE__ */ React.createElement(
340
+ FlagItem,
341
+ {
342
+ key: featureFlag.name,
343
+ flag: featureFlag,
344
+ enabled,
345
+ toggleHandler: toggleFlag
346
+ }
347
+ );
348
+ })));
349
+ };
350
+
351
+ const useUserProfile = () => {
352
+ var _a;
353
+ const identityApi = useApi(identityApiRef);
354
+ const alertApi = useApi(alertApiRef);
355
+ const { value, loading, error } = useAsync(async () => {
356
+ return {
357
+ profile: await identityApi.getProfileInfo(),
358
+ identity: await identityApi.getBackstageIdentity()
359
+ };
360
+ }, []);
361
+ useEffect(() => {
362
+ if (error) {
363
+ alertApi.post({
364
+ message: `Failed to load user identity: ${error}`,
365
+ severity: "error"
366
+ });
367
+ }
368
+ }, [error, alertApi]);
369
+ if (loading || error) {
370
+ return {
371
+ profile: {},
372
+ displayName: "",
373
+ loading
374
+ };
375
+ }
376
+ return {
377
+ profile: value.profile,
378
+ backstageIdentity: value.identity,
379
+ displayName: (_a = value.profile.displayName) != null ? _a : value.identity.userEntityRef,
380
+ loading
381
+ };
382
+ };
383
+
384
+ const useStyles$2 = makeStyles((theme) => ({
385
+ avatar: {
386
+ width: ({ size }) => size,
387
+ height: ({ size }) => size,
388
+ fontSize: ({ size }) => size * 0.7,
389
+ border: `1px solid ${theme.palette.textSubtle}`
390
+ }
391
+ }));
392
+ const UserSettingsSignInAvatar = (props) => {
393
+ const { size } = props;
394
+ const { iconSize } = sidebarConfig;
395
+ const classes = useStyles$2(size ? { size } : { size: iconSize });
396
+ const { profile } = useUserProfile();
397
+ return /* @__PURE__ */ React.createElement(
398
+ Avatar,
399
+ {
400
+ src: profile.picture,
401
+ className: classes.avatar,
402
+ alt: "Profile picture"
403
+ }
404
+ );
405
+ };
406
+
407
+ const UserSettingsMenu = () => {
408
+ const errorApi = useApi(errorApiRef);
409
+ const identityApi = useApi(identityApiRef);
410
+ const [open, setOpen] = React.useState(false);
411
+ const [anchorEl, setAnchorEl] = React.useState(
412
+ void 0
413
+ );
414
+ const handleOpen = (event) => {
415
+ setAnchorEl(event.currentTarget);
416
+ setOpen(true);
417
+ };
418
+ const handleClose = () => {
419
+ setAnchorEl(void 0);
420
+ setOpen(false);
421
+ };
422
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(
423
+ IconButton,
424
+ {
425
+ "data-testid": "user-settings-menu",
426
+ "aria-label": "more",
427
+ onClick: handleOpen
428
+ },
429
+ /* @__PURE__ */ React.createElement(MoreVertIcon, null)
430
+ ), /* @__PURE__ */ React.createElement(Menu, { anchorEl, open, onClose: handleClose }, /* @__PURE__ */ React.createElement(
431
+ MenuItem,
432
+ {
433
+ "data-testid": "sign-out",
434
+ onClick: () => identityApi.signOut().catch((error) => errorApi.post(error))
435
+ },
436
+ /* @__PURE__ */ React.createElement(ListItemIcon, null, /* @__PURE__ */ React.createElement(SignOutIcon, null)),
437
+ "Sign Out"
438
+ )));
439
+ };
440
+
441
+ const UserSettingsProfileCard = () => {
442
+ const { profile, displayName } = useUserProfile();
443
+ return /* @__PURE__ */ React.createElement(InfoCard, { title: "Profile", variant: "gridItem" }, /* @__PURE__ */ React.createElement(Grid, { container: true, spacing: 6 }, /* @__PURE__ */ React.createElement(Grid, { item: true }, /* @__PURE__ */ React.createElement(UserSettingsSignInAvatar, { size: 96 })), /* @__PURE__ */ React.createElement(Grid, { item: true, xs: 12, sm: true, container: true }, /* @__PURE__ */ React.createElement(Grid, { item: true, xs: true, container: true, direction: "column", spacing: 2 }, /* @__PURE__ */ React.createElement(Grid, { item: true, xs: true }, /* @__PURE__ */ React.createElement(Typography, { variant: "subtitle1", gutterBottom: true }, displayName), profile.email && /* @__PURE__ */ React.createElement(Typography, { variant: "body2", color: "textSecondary" }, profile.email))), /* @__PURE__ */ React.createElement(Grid, { item: true }, /* @__PURE__ */ React.createElement(UserSettingsMenu, null)))));
444
+ };
445
+
446
+ const UserSettingsPinToggle = () => {
447
+ const { isPinned, toggleSidebarPinState } = useSidebarPinState();
448
+ return /* @__PURE__ */ React.createElement(ListItem, null, /* @__PURE__ */ React.createElement(
449
+ ListItemText,
450
+ {
451
+ primary: "Pin Sidebar",
452
+ secondary: "Prevent the sidebar from collapsing"
453
+ }
454
+ ), /* @__PURE__ */ React.createElement(ListItemSecondaryAction, null, /* @__PURE__ */ React.createElement(
455
+ Tooltip,
456
+ {
457
+ placement: "top",
458
+ arrow: true,
459
+ title: `${isPinned ? "Unpin" : "Pin"} Sidebar`
460
+ },
461
+ /* @__PURE__ */ React.createElement(
462
+ Switch,
463
+ {
464
+ color: "primary",
465
+ checked: isPinned,
466
+ onChange: () => toggleSidebarPinState(),
467
+ name: "pin",
468
+ inputProps: { "aria-label": "Pin Sidebar Switch" }
469
+ }
470
+ )
471
+ )));
472
+ };
473
+
474
+ const ThemeIcon = ({ id, activeId, icon }) => icon ? cloneElement(icon, {
475
+ color: activeId === id ? "primary" : void 0
476
+ }) : /* @__PURE__ */ React.createElement(AutoIcon, { color: activeId === id ? "primary" : void 0 });
477
+ const useStyles$1 = makeStyles((theme) => ({
478
+ container: {
479
+ display: "flex",
480
+ flexWrap: "wrap",
481
+ width: "100%",
482
+ justifyContent: "space-between",
483
+ alignItems: "center",
484
+ paddingBottom: 8,
485
+ paddingRight: 16
486
+ },
487
+ list: {
488
+ width: "initial",
489
+ [theme.breakpoints.down("xs")]: {
490
+ width: "100%",
491
+ padding: `0 0 12px`
492
+ }
493
+ },
494
+ listItemText: {
495
+ paddingRight: 0,
496
+ paddingLeft: 0
497
+ },
498
+ listItemSecondaryAction: {
499
+ position: "relative",
500
+ transform: "unset",
501
+ top: "auto",
502
+ right: "auto",
503
+ paddingLeft: 16,
504
+ [theme.breakpoints.down("xs")]: {
505
+ paddingLeft: 0
506
+ }
507
+ }
508
+ }));
509
+ const TooltipToggleButton$1 = ({
510
+ children,
511
+ title,
512
+ value,
513
+ ...props
514
+ }) => /* @__PURE__ */ React.createElement(Tooltip, { placement: "top", arrow: true, title }, /* @__PURE__ */ React.createElement(ToggleButton, { value, ...props }, children));
515
+ const UserSettingsThemeToggle = () => {
516
+ const classes = useStyles$1();
517
+ const appThemeApi = useApi(appThemeApiRef);
518
+ const activeThemeId = useObservable(
519
+ appThemeApi.activeThemeId$(),
520
+ appThemeApi.getActiveThemeId()
521
+ );
522
+ const themeIds = appThemeApi.getInstalledThemes();
523
+ const { t } = useTranslationRef(userSettingsTranslationRef);
524
+ const handleSetTheme = (_event, newThemeId) => {
525
+ if (themeIds.some((it) => it.id === newThemeId)) {
526
+ appThemeApi.setActiveThemeId(newThemeId);
527
+ } else {
528
+ appThemeApi.setActiveThemeId(void 0);
529
+ }
530
+ };
531
+ return /* @__PURE__ */ React.createElement(
532
+ ListItem,
533
+ {
534
+ className: classes.list,
535
+ classes: { container: classes.container }
536
+ },
537
+ /* @__PURE__ */ React.createElement(
538
+ ListItemText,
539
+ {
540
+ className: classes.listItemText,
541
+ primary: t("theme"),
542
+ secondary: t("change_the_theme_mode")
543
+ }
544
+ ),
545
+ /* @__PURE__ */ React.createElement(ListItemSecondaryAction, { className: classes.listItemSecondaryAction }, /* @__PURE__ */ React.createElement(
546
+ ToggleButtonGroup,
547
+ {
548
+ exclusive: true,
549
+ size: "small",
550
+ value: activeThemeId != null ? activeThemeId : "auto",
551
+ onChange: handleSetTheme
552
+ },
553
+ themeIds.map((theme) => {
554
+ const themeId = theme.id;
555
+ const themeIcon = theme.icon;
556
+ const themeTitle = theme.title || (themeId === "light" || themeId === "dark" ? t(`theme_${themeId}`) : themeId);
557
+ return /* @__PURE__ */ React.createElement(
558
+ TooltipToggleButton$1,
559
+ {
560
+ key: themeId,
561
+ title: t("select_theme", { theme: themeTitle }),
562
+ value: themeId
563
+ },
564
+ /* @__PURE__ */ React.createElement(React.Fragment, null, themeTitle, "\xA0", /* @__PURE__ */ React.createElement(
565
+ ThemeIcon,
566
+ {
567
+ id: themeId,
568
+ icon: themeIcon,
569
+ activeId: activeThemeId
570
+ }
571
+ ))
572
+ );
573
+ }),
574
+ /* @__PURE__ */ React.createElement(Tooltip, { placement: "top", arrow: true, title: t("select_theme_auto") }, /* @__PURE__ */ React.createElement(ToggleButton, { value: "auto", selected: activeThemeId === void 0 }, t("theme_auto"), "\xA0", /* @__PURE__ */ React.createElement(
575
+ AutoIcon,
576
+ {
577
+ color: activeThemeId === void 0 ? "primary" : void 0
578
+ }
579
+ )))
580
+ ))
581
+ );
582
+ };
583
+
584
+ const useStyles = makeStyles((theme) => ({
585
+ container: {
586
+ display: "flex",
587
+ flexWrap: "wrap",
588
+ width: "100%",
589
+ justifyContent: "space-between",
590
+ alignItems: "center",
591
+ paddingBottom: 8,
592
+ paddingRight: 16
593
+ },
594
+ list: {
595
+ width: "initial",
596
+ [theme.breakpoints.down("xs")]: {
597
+ width: "100%",
598
+ padding: `0 0 12px`
599
+ }
600
+ },
601
+ listItemText: {
602
+ paddingRight: 0,
603
+ paddingLeft: 0
604
+ },
605
+ listItemSecondaryAction: {
606
+ position: "relative",
607
+ transform: "unset",
608
+ top: "auto",
609
+ right: "auto",
610
+ paddingLeft: 16,
611
+ [theme.breakpoints.down("xs")]: {
612
+ paddingLeft: 0
613
+ }
614
+ }
615
+ }));
616
+ const TooltipToggleButton = ({
617
+ children,
618
+ title,
619
+ value,
620
+ ...props
621
+ }) => /* @__PURE__ */ React.createElement(Tooltip, { placement: "top", arrow: true, title }, /* @__PURE__ */ React.createElement(ToggleButton, { value, ...props }, children));
622
+ const UserSettingsLanguageToggle = () => {
623
+ const classes = useStyles();
624
+ const languageApi = useApi(appLanguageApiRef);
625
+ const { t } = useTranslationRef(userSettingsTranslationRef);
626
+ const [languageObservable] = useState(() => languageApi.language$());
627
+ const { language: currentLanguage } = useObservable(
628
+ languageObservable,
629
+ languageApi.getLanguage()
630
+ );
631
+ const { languages } = languageApi.getAvailableLanguages();
632
+ if (languages.length <= 1) {
633
+ return null;
634
+ }
635
+ const handleSetLanguage = (_event, newLanguage) => {
636
+ languageApi.setLanguage(newLanguage);
637
+ };
638
+ return /* @__PURE__ */ React.createElement(
639
+ ListItem,
640
+ {
641
+ className: classes.list,
642
+ classes: { container: classes.container }
643
+ },
644
+ /* @__PURE__ */ React.createElement(
645
+ ListItemText,
646
+ {
647
+ className: classes.listItemText,
648
+ primary: t("language"),
649
+ secondary: t("change_the_language")
650
+ }
651
+ ),
652
+ /* @__PURE__ */ React.createElement(ListItemSecondaryAction, { className: classes.listItemSecondaryAction }, /* @__PURE__ */ React.createElement(
653
+ ToggleButtonGroup,
654
+ {
655
+ exclusive: true,
656
+ size: "small",
657
+ value: currentLanguage,
658
+ onChange: handleSetLanguage
659
+ },
660
+ languages.map((language) => {
661
+ return /* @__PURE__ */ React.createElement(
662
+ TooltipToggleButton,
663
+ {
664
+ key: language,
665
+ title: t("select_lng", { language }),
666
+ value: language
667
+ },
668
+ /* @__PURE__ */ React.createElement(React.Fragment, null, language)
669
+ );
670
+ })
671
+ ))
672
+ );
673
+ };
674
+
675
+ const UserSettingsAppearanceCard = () => {
676
+ const { isMobile } = useSidebarPinState();
677
+ return /* @__PURE__ */ React.createElement(InfoCard, { title: "Appearance", variant: "gridItem" }, /* @__PURE__ */ React.createElement(List, { dense: true }, /* @__PURE__ */ React.createElement(UserSettingsThemeToggle, null), /* @__PURE__ */ React.createElement(UserSettingsLanguageToggle, null), !isMobile && /* @__PURE__ */ React.createElement(UserSettingsPinToggle, null)));
678
+ };
679
+
680
+ const Contents = () => {
681
+ const { backstageIdentity } = useUserProfile();
682
+ if (!backstageIdentity) {
683
+ return /* @__PURE__ */ React.createElement(Typography$1, null, "No Backstage Identity");
684
+ }
685
+ return /* @__PURE__ */ React.createElement(Grid$1, { container: true, spacing: 1 }, /* @__PURE__ */ React.createElement(Grid$1, { item: true, xs: 12 }, /* @__PURE__ */ React.createElement(Typography$1, { variant: "subtitle1", gutterBottom: true }, "User Entity:", " ", /* @__PURE__ */ React.createElement(
686
+ EntityRefLinks,
687
+ {
688
+ entityRefs: [backstageIdentity.userEntityRef],
689
+ getTitle: (ref) => ref
690
+ }
691
+ ))), /* @__PURE__ */ React.createElement(Grid$1, { item: true, xs: 12 }, /* @__PURE__ */ React.createElement(Typography$1, { variant: "subtitle1" }, "Ownership Entities:", " ", /* @__PURE__ */ React.createElement(
692
+ EntityRefLinks,
693
+ {
694
+ entityRefs: backstageIdentity.ownershipEntityRefs,
695
+ getTitle: (ref) => ref
696
+ }
697
+ ))));
698
+ };
699
+ const UserSettingsIdentityCard = () => /* @__PURE__ */ React.createElement(InfoCard, { title: "Backstage Identity" }, /* @__PURE__ */ React.createElement(Contents, null));
700
+
701
+ const UserSettingsGeneral = () => {
702
+ return /* @__PURE__ */ React.createElement(Grid, { container: true, direction: "row", spacing: 3 }, /* @__PURE__ */ React.createElement(Grid, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ React.createElement(UserSettingsProfileCard, null)), /* @__PURE__ */ React.createElement(Grid, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ React.createElement(UserSettingsAppearanceCard, null)), /* @__PURE__ */ React.createElement(Grid, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ React.createElement(UserSettingsIdentityCard, null)));
703
+ };
704
+
705
+ const LAYOUT_DATA_KEY = "plugin.user-settings.settingsLayout";
706
+ const LAYOUT_ROUTE_DATA_KEY = "plugin.user-settings.settingsLayoutRoute";
707
+ const Route = () => null;
708
+ attachComponentData(Route, LAYOUT_ROUTE_DATA_KEY, true);
709
+ attachComponentData(Route, "core.gatherMountPoints", true);
710
+ const SettingsLayout = (props) => {
711
+ const { title, children } = props;
712
+ const { isMobile } = useSidebarPinState();
713
+ const routes = useElementFilter(
714
+ children,
715
+ (elements) => elements.selectByComponentData({
716
+ key: LAYOUT_ROUTE_DATA_KEY,
717
+ withStrictError: "Child of SettingsLayout must be an SettingsLayout.Route"
718
+ }).getElements().map((child) => child.props)
719
+ );
720
+ return /* @__PURE__ */ React.createElement(Page, { themeId: "home" }, !isMobile && /* @__PURE__ */ React.createElement(Header, { title: title != null ? title : "Settings" }), /* @__PURE__ */ React.createElement(RoutedTabs, { routes }));
721
+ };
722
+ attachComponentData(SettingsLayout, LAYOUT_DATA_KEY, true);
723
+ SettingsLayout.Route = Route;
724
+
725
+ const DefaultSettingsPage = (props) => {
726
+ const { providerSettings, tabs } = props;
727
+ return /* @__PURE__ */ React.createElement(SettingsLayout, null, /* @__PURE__ */ React.createElement(SettingsLayout.Route, { path: "general", title: "General" }, /* @__PURE__ */ React.createElement(UserSettingsGeneral, null)), /* @__PURE__ */ React.createElement(
728
+ SettingsLayout.Route,
729
+ {
730
+ path: "auth-providers",
731
+ title: "Authentication Providers"
732
+ },
733
+ /* @__PURE__ */ React.createElement(UserSettingsAuthProviders, { providerSettings })
734
+ ), /* @__PURE__ */ React.createElement(SettingsLayout.Route, { path: "feature-flags", title: "Feature Flags" }, /* @__PURE__ */ React.createElement(UserSettingsFeatureFlags, null)), tabs);
735
+ };
736
+
737
+ const SettingsPage = (props) => {
738
+ const { providerSettings } = props;
739
+ const outlet = useOutlet();
740
+ const layout = useElementFilter(
741
+ outlet,
742
+ (elements) => elements.selectByComponentData({
743
+ key: LAYOUT_DATA_KEY
744
+ }).getElements()
745
+ );
746
+ const tabs = useElementFilter(
747
+ outlet,
748
+ (elements) => elements.selectByComponentData({
749
+ key: LAYOUT_ROUTE_DATA_KEY
750
+ }).getElements()
751
+ );
752
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, layout.length !== 0 && layout || /* @__PURE__ */ React.createElement(DefaultSettingsPage, { tabs, providerSettings }));
753
+ };
754
+
755
+ export { DefaultProviderSettings as D, LAYOUT_ROUTE_DATA_KEY as L, ProviderSettingsItem as P, SettingsLayout as S, UserSettingsAuthProviders as U, SettingsPage as a, UserSettingsGeneral as b, UserSettingsProfileCard as c, UserSettingsMenu as d, UserSettingsSignInAvatar as e, UserSettingsAppearanceCard as f, UserSettingsThemeToggle as g, UserSettingsPinToggle as h, UserSettingsIdentityCard as i, UserSettingsLanguageToggle as j, UserSettingsFeatureFlags as k, useUserProfile as u };
756
+ //# sourceMappingURL=SettingsPage-f863b932.esm.js.map