@fluxo-dev/shell 0.0.8

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.
Files changed (66) hide show
  1. package/.babelrc +12 -0
  2. package/.eslintrc.json +18 -0
  3. package/README.md +7 -0
  4. package/jest.config.ts +11 -0
  5. package/package.json +4 -0
  6. package/project.json +43 -0
  7. package/src/index.ts +1 -0
  8. package/src/lib/app.module.css +1 -0
  9. package/src/lib/app.spec.tsx +17 -0
  10. package/src/lib/app.tsx +374 -0
  11. package/src/lib/components/error-page/404.tsx +16 -0
  12. package/src/lib/components/routes/main-routes.tsx +26 -0
  13. package/src/lib/layouts/blank/blank-layout.tsx +10 -0
  14. package/src/lib/layouts/main/app-bar.tsx +192 -0
  15. package/src/lib/layouts/main/app-layout.tsx +52 -0
  16. package/src/lib/layouts/main/content.tsx +10 -0
  17. package/src/lib/layouts/main/footer-bar.tsx +76 -0
  18. package/src/lib/layouts/main/navbar-item.module.css +49 -0
  19. package/src/lib/layouts/main/navbar-item.tsx +129 -0
  20. package/src/lib/layouts/main/navbar.tsx +38 -0
  21. package/src/lib/layouts/main/search-bar.tsx +44 -0
  22. package/src/lib/layouts/main/shell-spotlight.tsx +58 -0
  23. package/src/lib/models/app-jit.ts +6 -0
  24. package/src/lib/pages/general/fluxo-plugin-loader.tsx +22 -0
  25. package/src/lib/pages/login/account-confirmation.tsx +86 -0
  26. package/src/lib/pages/login/change-password.tsx +58 -0
  27. package/src/lib/pages/login/forgot-password.tsx +53 -0
  28. package/src/lib/pages/login/login-page.tsx +70 -0
  29. package/src/lib/pages/login/reset-password.tsx +91 -0
  30. package/src/lib/pages/settings/apps/add-jit-app.tsx +79 -0
  31. package/src/lib/pages/settings/apps/app-item.tsx +157 -0
  32. package/src/lib/pages/settings/apps/app-jit-item.tsx +105 -0
  33. package/src/lib/pages/settings/apps/apps-list.tsx +171 -0
  34. package/src/lib/pages/settings/apps/embedded-module-item.tsx +66 -0
  35. package/src/lib/pages/settings/apps/install-or-upgrade-app.tsx +105 -0
  36. package/src/lib/pages/settings/menu-designer/custom-node.module.css +34 -0
  37. package/src/lib/pages/settings/menu-designer/custom-node.tsx +160 -0
  38. package/src/lib/pages/settings/menu-designer/menu-create-form.tsx +109 -0
  39. package/src/lib/pages/settings/menu-designer/menu-designer.module.css +19 -0
  40. package/src/lib/pages/settings/menu-designer/menu-designer.tsx +463 -0
  41. package/src/lib/pages/settings/menu-designer/menu-item-edit.tsx +115 -0
  42. package/src/lib/pages/settings/menu-designer/placeholder.module.css +8 -0
  43. package/src/lib/pages/settings/menu-designer/placeholder.tsx +13 -0
  44. package/src/lib/pages/settings/security/components/domani-role/domain-role-form.tsx +67 -0
  45. package/src/lib/pages/settings/security/components/domani-role/domain-role-grid.tsx +139 -0
  46. package/src/lib/pages/settings/security/components/permission/create-custom-permission-form.tsx +64 -0
  47. package/src/lib/pages/settings/security/components/permission/create-scope-form.tsx +158 -0
  48. package/src/lib/pages/settings/security/components/permission/permission-grid.tsx +304 -0
  49. package/src/lib/pages/settings/security/components/permission/permission-navigator.tsx +320 -0
  50. package/src/lib/pages/settings/security/components/permission/update-permission.tsx +54 -0
  51. package/src/lib/pages/settings/security/components/route-rule/route-rule-create-form.tsx +59 -0
  52. package/src/lib/pages/settings/security/components/route-rule/route-rule-grid.tsx +229 -0
  53. package/src/lib/pages/settings/security/components/user/user-form.tsx +123 -0
  54. package/src/lib/pages/settings/security/components/user/users-grid.tsx +127 -0
  55. package/src/lib/pages/settings/settings.tsx +298 -0
  56. package/src/lib/pages/settings/token/token-row.tsx +69 -0
  57. package/src/lib/pages/settings/token/tokens.tsx +73 -0
  58. package/src/lib/pages/settings/ui/ui.tsx +149 -0
  59. package/src/lib/services/app-jit-manager.ts +92 -0
  60. package/src/lib/services/app-menu.service.ts +35 -0
  61. package/src/lib/utils/icon-factory.tsx +25 -0
  62. package/src/lib/utils/security/only-not-logged-route.tsx +17 -0
  63. package/src/lib/utils/security/protected-route.tsx +47 -0
  64. package/tsconfig.json +20 -0
  65. package/tsconfig.lib.json +24 -0
  66. package/tsconfig.spec.json +20 -0
@@ -0,0 +1,298 @@
1
+ import {
2
+ DataCard,
3
+ ShellPage,
4
+ SideMenuGroup,
5
+ WaitData,
6
+ } from "@fluxo-dev/ui-components";
7
+ import { Text } from '@mantine/core';
8
+ import {
9
+ IconApi, IconApps, IconCode,
10
+ IconDashboard,
11
+ IconKey,
12
+ IconLockAccess,
13
+ IconMenu, IconPrinter, IconRoute, IconUsers, IconUsersGroup,
14
+ IconVariable,
15
+ } from '@tabler/icons-react';
16
+ import React, { useEffect, useState } from 'react';
17
+ import { Ui } from './ui/ui';
18
+ import { MenuDesigner } from './menu-designer/menu-designer';
19
+ import { RestApiDocumentation } from '@fluxo-fe/shell/korebi';
20
+ import {Documentation, EmbeddedModule, FluxoClientSys} from "@fluxo-dev/api-client-base";
21
+ import { ErrorPage } from "@fluxo-dev/ui-components";
22
+ import Tokens from './token/tokens';
23
+ import {useNavigate} from "react-router-dom";
24
+ import {AppsList} from "./apps/apps-list";
25
+ import {PermissionGrid} from "./security/components/permission/permission-grid";
26
+ import {DomainRoleGrid} from "./security/components/domani-role/domain-role-grid";
27
+ import {UsersGrid} from "./security/components/user/users-grid";
28
+ import {RouteRuleGrid} from "./security/components/route-rule/route-rule-grid";
29
+ import {PrintServerList} from "@fluxo-fe/shell/printing";
30
+
31
+ export interface SettingsProps {
32
+ pluginEndpoint: string;
33
+ modules: EmbeddedModule[];
34
+ }
35
+
36
+ export function Settings(props: SettingsProps) {
37
+ const [currentTab, setCurrentTab] = useState('ui');
38
+ const [apiRestDoc, setApiRestDoc] = useState<Documentation>();
39
+ const navigate = useNavigate();
40
+
41
+
42
+ useEffect(() => {
43
+ const hash = window.location.hash.slice(1);
44
+ if (hash) {
45
+ setCurrentTab(hash);
46
+ }
47
+ }, []);
48
+
49
+ useEffect(() => {
50
+ FluxoClientSys.Instance.korebi.documentation.getRestApiDocumentation().then((res) => {
51
+ setApiRestDoc(res);
52
+ });
53
+ }, []);
54
+
55
+ const getSideMenuGroups = (): SideMenuGroup[] => {
56
+ return [
57
+ {
58
+ name: "Interfaccia",
59
+ items: [
60
+ {
61
+ key: 'ui',
62
+ title: (
63
+ <Text>
64
+ Brand
65
+ </Text>
66
+ ),
67
+ onClick: () => {
68
+ window.location.hash = 'ui';
69
+ setCurrentTab('ui')
70
+ },
71
+ disabled: false,
72
+ icon: <IconDashboard size={18} />,
73
+ description:
74
+ 'Gestisci colori logo e altri impostazioni di interfaccia',
75
+ },
76
+ {
77
+ key: 'menu',
78
+ title: (
79
+ <Text>
80
+ Menu e ricerca
81
+ </Text>
82
+ ),
83
+ onClick: () => {
84
+ window.location.hash = 'menu';
85
+ setCurrentTab('menu')
86
+ },
87
+ disabled: false,
88
+ icon: <IconMenu size={18} />,
89
+ description: 'Gestione del menu laterale e delle azioni veloci',
90
+ },
91
+ ],
92
+ },
93
+ {
94
+ name: 'Sicurezza',
95
+ items: [
96
+ {
97
+ key: 'users',
98
+ title: (
99
+ <Text>
100
+ Utenti
101
+ </Text>
102
+ ),
103
+ onClick: () => {
104
+ window.location.hash = 'users';
105
+ setCurrentTab('users');
106
+ },
107
+ disabled: false,
108
+ icon: <IconUsers size={18} />,
109
+ description: 'Crea, modifica, elimina utenti',
110
+ },
111
+ {
112
+ key: 'roles',
113
+ title: (
114
+ <Text>
115
+ Ruoli
116
+ </Text>
117
+ ),
118
+ onClick: () => {
119
+ window.location.hash = 'roles';
120
+ setCurrentTab('roles')
121
+ },
122
+ disabled: false,
123
+ icon: <IconUsersGroup size={18} />,
124
+ description: 'Crea, modifica, elimina ruoli',
125
+ },
126
+ {
127
+ key: 'permissions',
128
+ title: (
129
+ <Text>
130
+ Permessi
131
+ </Text>
132
+ ),
133
+ onClick: () => {
134
+ window.location.hash = 'permissions';
135
+ setCurrentTab('permissions')
136
+ },
137
+ disabled: false,
138
+ icon: <IconKey size={18} />,
139
+ description: 'Gestisci i permessi utente',
140
+ },
141
+ {
142
+ key: 'routes',
143
+ title: (
144
+ <Text>
145
+ Regole sulle rotte <small><b>(Avanzato)</b></small>
146
+ </Text>
147
+ ),
148
+ onClick: () => {
149
+ window.location.hash = 'routes';
150
+ setCurrentTab('routes')
151
+ },
152
+ disabled: false,
153
+ icon: <IconRoute size={18} />,
154
+ description: 'Gestisci le regole sulle rotte',
155
+ },
156
+ ]
157
+ },
158
+ {
159
+ name: 'Stampe',
160
+ items: [
161
+ {
162
+ key: 'print-server',
163
+ title: (
164
+ <Text>
165
+ Print server
166
+ </Text>
167
+ ),
168
+ onClick: () => {
169
+ window.location.hash = 'print-server';
170
+ setCurrentTab('print-server')
171
+ },
172
+ disabled: false,
173
+ icon: <IconPrinter size={18} />,
174
+ description: 'Gestisci i print server',
175
+ }
176
+ ]
177
+ },
178
+ {
179
+ name: 'Sviluppatori',
180
+ items: [
181
+ {
182
+ key: 'tokens',
183
+ title: (
184
+ <Text>
185
+ Access tokens <small>(account)</small>
186
+ </Text>
187
+ ),
188
+ onClick: () => {
189
+ window.location.hash = 'tokens';
190
+ setCurrentTab('tokens')
191
+ },
192
+ disabled: false,
193
+ icon: <IconLockAccess size={18} />,
194
+ description: 'Gestisci i tuoi access tokens',
195
+ },
196
+ {
197
+ key: 'environment',
198
+ title: (
199
+ <Text>
200
+ Variabili d'ambiente <small>(Globale)</small>
201
+ </Text>
202
+ ),
203
+ onClick: () => {
204
+ window.location.hash = 'environment';
205
+ setCurrentTab('environment')
206
+ },
207
+ disabled: false,
208
+ icon: <IconVariable size={18} />,
209
+ description: "Crea, modifica o elimina le variabili d'ambiente",
210
+ },
211
+ {
212
+ key: 'plugin',
213
+ title: (
214
+ <Text>
215
+ Apps <small>(Globale)</small>
216
+ </Text>
217
+ ),
218
+ onClick: () => {
219
+ window.location.hash = 'plugin';
220
+ setCurrentTab('plugin')
221
+ },
222
+ disabled: false,
223
+ icon: <IconApps size={18} />,
224
+ description: 'Installa o gestisci le apps',
225
+ },
226
+ {
227
+ key: 'apidoc',
228
+ title: (
229
+ <Text>
230
+ API <small>REST</small>
231
+ </Text>
232
+ ),
233
+ onClick: () => {
234
+ window.location.hash = 'apidoc';
235
+ setCurrentTab('apidoc')
236
+ },
237
+ disabled: false,
238
+ icon: <IconApi size={18} />,
239
+ description: 'Documentazione delle API REST',
240
+ },
241
+ {
242
+ key: 'ide',
243
+ title: (
244
+ <Text>
245
+ Ambiente di sviluppo <small>KOREBI</small>
246
+ </Text>
247
+ ),
248
+ onClick: () => navigate('/korebi-ide'),
249
+ disabled: false,
250
+ icon: <IconCode size={18} />,
251
+ description: 'Accedi all\'ambiente di sviluppo',
252
+ },
253
+ ],
254
+ },
255
+ ];
256
+ };
257
+
258
+ const renderCurrentSection = (tab: string) => {
259
+ switch (tab) {
260
+ case 'ui':
261
+ return <Ui />;
262
+ case 'menu':
263
+ return <MenuDesigner />;
264
+ case 'tokens':
265
+ return <Tokens />;
266
+ case 'users':
267
+ return <UsersGrid />;
268
+ case 'roles':
269
+ return <DomainRoleGrid />;
270
+ case 'permissions':
271
+ return <PermissionGrid />;
272
+ case 'routes':
273
+ return <RouteRuleGrid />;
274
+ case 'print-server':
275
+ return <PrintServerList />;
276
+ case 'apidoc':
277
+ return apiRestDoc ? (
278
+ <DataCard title={'Documentazione API REST'}>
279
+ <RestApiDocumentation documentation={apiRestDoc} />
280
+ </DataCard>
281
+ ) : (
282
+ <WaitData />
283
+ );
284
+ case 'plugin':
285
+ return <AppsList pluginEndpoint={props.pluginEndpoint} modules={props.modules} />;
286
+ default:
287
+ return <ErrorPage />;
288
+ }
289
+ };
290
+
291
+ return (
292
+ <ShellPage
293
+ headerProps={{ title: 'Impostazioni', subtitle: '' }}
294
+ contextSidebarProps={{ groups: getSideMenuGroups(), activeKey: currentTab }}>
295
+ {renderCurrentSection(currentTab)}
296
+ </ShellPage>
297
+ );
298
+ }
@@ -0,0 +1,69 @@
1
+ import { ApiKey, ApiKeyService } from "@fluxo-dev/api-client-base";
2
+ import { Badge, Button, Group, Stack } from "@mantine/core";
3
+ import { showNotification } from "@mantine/notifications";
4
+ import dayjs from 'dayjs';
5
+
6
+ interface TokenRowProps {
7
+ token: ApiKey;
8
+ onChange: () => void
9
+ }
10
+
11
+ function TokenRow(props: TokenRowProps) {
12
+ const apiKeyService = new ApiKeyService();
13
+
14
+ const enable = () => {
15
+ apiKeyService.enable(props.token.id)
16
+ .then((res) => {
17
+ showNotification({
18
+ title: '',
19
+ message: `Token abilitato`,
20
+ color: 'green',
21
+ });
22
+ props.onChange();
23
+ })
24
+ }
25
+
26
+ const disable = () => {
27
+ apiKeyService.disable(props.token.id)
28
+ .then((res) => {
29
+ showNotification({
30
+ title: '',
31
+ message: `Token disabilitato`,
32
+ color: 'green',
33
+ });
34
+ props.onChange();
35
+ })
36
+ }
37
+
38
+ const deletion = () => {
39
+ apiKeyService.delete(props.token.id)
40
+ .then((res) => {
41
+ showNotification({
42
+ title: 'Cancellazione completata',
43
+ message: `Il token è stato cancellato con successo`,
44
+ color: 'green',
45
+ });
46
+ props.onChange();
47
+ })
48
+ }
49
+
50
+ return (
51
+ <Group justify='space-between' style={{ border: '1px solid #dedede', padding: 15 }}>
52
+ <Stack gap={0}>
53
+ <span>{props.token.description}</span>
54
+ <small>{dayjs(props.token.expirationDate).format('DD/MM/YYYY')}</small>
55
+ </Stack>
56
+ <Group>
57
+ {props.token.isActive ? <Badge color="green">Attivo</Badge> : <Badge color="red">Disattivo</Badge>}
58
+ <Button variant="light" size="xs" onClick={props.token.isActive ? disable : enable}>
59
+ {props.token.isActive ? 'Disabilita' : 'Abilita'}
60
+ </Button>
61
+ <Button variant="light" color="red" size="xs" onClick={deletion}>
62
+ Cancella
63
+ </Button>
64
+ </Group>
65
+ </Group>
66
+ );
67
+ }
68
+
69
+ export default TokenRow;
@@ -0,0 +1,73 @@
1
+ import { DataCard } from "@fluxo-dev/ui-components";
2
+ import { ApiKey, ApiKeyService } from "@fluxo-dev/api-client-base";
3
+ import { Button, Modal, Stack, TextInput } from "@mantine/core";
4
+ import {DatePickerInput} from '@mantine/dates';
5
+ import { showNotification } from "@mantine/notifications";
6
+ import { IconFilePlus, IconPlus } from '@tabler/icons-react';
7
+ import { useEffect, useState } from "react";
8
+ import TokenRow from "./token-row";
9
+
10
+ function Tokens() {
11
+ const [tokens, setTokens] = useState<ApiKey[]>([]);
12
+ const [newTokenDescr, setNewTokenDescr] = useState<string>('');
13
+ const [newTokenDate, setNewTokenDate] = useState<Date | null>();
14
+ const [newTokenModalOpen, setNewTokenModalOpen] = useState<boolean>(false);
15
+ const apiKeyService = new ApiKeyService();
16
+
17
+ useEffect(() => {
18
+ getApiKeys();
19
+ }, [])
20
+
21
+ const getApiKeys = () => {
22
+ apiKeyService.getApiKeys().then(res => {
23
+ setTokens(res);
24
+ })
25
+ }
26
+
27
+ const create = () => {
28
+ apiKeyService.create({ description: newTokenDescr, expirationDate: newTokenDate! })
29
+ .then((res) => {
30
+ showNotification({
31
+ title: 'Salvataggio completato',
32
+ message: `Il menu è stato salvato con successo`,
33
+ color: 'green',
34
+ });
35
+ getApiKeys();
36
+ setNewTokenModalOpen(false);
37
+ setNewTokenDescr('');
38
+ setNewTokenDate(undefined);
39
+ }).catch((err) => {
40
+ showNotification({
41
+ title: 'Errore',
42
+ message: `Errore durante il salvataggio del token. ${err.message}`,
43
+ color: 'red',
44
+ })
45
+ })
46
+ }
47
+
48
+ return (
49
+ <>
50
+ <DataCard
51
+ title={'Access token'}
52
+ actions={[
53
+ { icon: <IconPlus size={18} />, label: 'Genera un nuovo token', onClick: () => setNewTokenModalOpen(true) },
54
+ ]}>
55
+ <Stack gap={16}>
56
+ {
57
+ tokens.map((v, i) => <TokenRow key={i} token={v} onChange={getApiKeys} />)
58
+ }
59
+ </Stack>
60
+ </DataCard>
61
+
62
+ <Modal centered opened={newTokenModalOpen} onClose={() => setNewTokenModalOpen(false)} title="Nuovo token">
63
+ <Stack align="flex-start" justify="flex-start">
64
+ <DatePickerInput w='100%' label="Data scadenza" onChange={(v) => setNewTokenDate(v)} />
65
+ <TextInput w='100%' label='Descrizione' onChange={(v) => setNewTokenDescr(v.currentTarget.value)} />
66
+ <Button onClick={create} leftSection={<IconFilePlus />} ml='auto'>Salva</Button>
67
+ </Stack>
68
+ </Modal>
69
+ </>
70
+ );
71
+ }
72
+
73
+ export default Tokens;
@@ -0,0 +1,149 @@
1
+ import {DataCard} from "@fluxo-dev/ui-components";
2
+ import {
3
+ Alert,
4
+ Box,
5
+ Button,
6
+ ColorInput,
7
+ ColorSwatch,
8
+ Divider,
9
+ FileButton,
10
+ Group,
11
+ NumberInput,
12
+ Stack,
13
+ Text, useMantineTheme
14
+ } from "@mantine/core";
15
+ import AppBar from "../../../layouts/main/app-bar";
16
+ import {IconDeviceFloppy, IconInfoSquare, IconUpload} from '@tabler/icons-react';
17
+ import {useEffect, useState} from "react";
18
+ import {IFluxoRootState, useAppSelector} from "@fluxo-dev/state";
19
+ import {showNotification} from "@mantine/notifications";
20
+ import {FluxoClientSys} from "@fluxo-dev/api-client-base";
21
+
22
+ export function Ui() {
23
+
24
+ const {configuration} = useAppSelector((state: IFluxoRootState) => state.layout);
25
+ const theme = useMantineTheme();
26
+
27
+ const [newLogo, setNewLogo] = useState<File | null>(null);
28
+ const [newLogoBase64, setNewLogoBase64] = useState<string | undefined>(configuration.logo);
29
+ const [newLogoHeight, setNewLogoHeight] = useState<number>(configuration.logoHeight);
30
+ const [newBrandColors, setNewBrandColors] = useState(configuration.colors);
31
+
32
+ const swatches = Object.keys(theme.colors).map((color) => (
33
+ <ColorSwatch
34
+ key={color}
35
+ color={theme.colors[color][6]}
36
+ onClick={() => {
37
+ setNewBrandColors(theme.colors[color])
38
+ }}
39
+ />
40
+ ));
41
+
42
+ const saveClientUIConfiguration = () => {
43
+ if(newLogoBase64 && newLogoHeight && newLogoHeight > 0 && newBrandColors && newBrandColors.length === 10) {
44
+
45
+ FluxoClientSys.Instance.configuration.updateClientUi({
46
+ colors: newBrandColors,
47
+ logo: newLogoBase64,
48
+ logoHeight: newLogoHeight
49
+ })
50
+ .then((res) => {
51
+ showNotification({
52
+ title: 'Salvataggio completato',
53
+ message: 'La configurazione è stata salvata correttamente.',
54
+ color: 'green',
55
+ })
56
+ })
57
+ .catch((err) => {
58
+ showNotification({
59
+ title: 'Errore',
60
+ message: err.message,
61
+ color: 'red',
62
+ })
63
+ })
64
+
65
+ } else {
66
+ showNotification({
67
+ title: 'Errore',
68
+ message: 'Compilare tutti i campi richiesti',
69
+ color: 'red',
70
+ })
71
+ }
72
+ }
73
+
74
+ const toBase64 = (file: File) => new Promise<string>((resolve, reject) => {
75
+ const reader = new FileReader();
76
+ reader.readAsDataURL(file);
77
+ reader.onload = () => resolve(reader.result?.toString() ?? '');
78
+ reader.onerror = error => reject(error);
79
+ });
80
+
81
+ const elaborateColorsArray = (colors: any, position: number, value: string) => {
82
+ const tmpColors = [...colors];
83
+ tmpColors[position] = value;
84
+ return tmpColors;
85
+ }
86
+
87
+ useEffect(() => {
88
+ if(newLogo) {
89
+ toBase64(newLogo)
90
+ .then((res) => {
91
+ setNewLogoBase64(res);
92
+ })
93
+ }
94
+ }, [newLogo])
95
+
96
+ return (
97
+ <Stack align={'stretch'} >
98
+
99
+ <div>
100
+ <DataCard title={'Anteprima'}>
101
+ <Stack>
102
+ <Box style={{border: '0px solid #888'}} p={0}>
103
+ <AppBar previewMode={{logo: newLogoBase64, logoHeight: newLogoHeight, headerColor: newBrandColors[6]}} />
104
+ </Box>
105
+ </Stack>
106
+ </DataCard>
107
+ </div>
108
+
109
+ <div>
110
+ <DataCard title={'Logo'} actions={[{icon: <IconDeviceFloppy />, onClick: () => console.log}]}>
111
+ <Stack align={'end'}>
112
+ <FileButton onChange={setNewLogo} accept="image/png,image/jpeg">
113
+ {(props) => <Button w={'100%'} leftSection={<IconUpload />} {...props}>Carica nuovo logo</Button>}
114
+ </FileButton>
115
+ <NumberInput max={50} min={10} w={'100%'} label={'Altezza del logo'} defaultValue={newLogoHeight} onChange={(v) => setNewLogoHeight(v !== '' ? +v! : 10)} />
116
+ <Divider color={'#e9ecef'} w={'100%'} />
117
+ <Button onClick={() => saveClientUIConfiguration()} leftSection={<IconDeviceFloppy />}>Salva</Button>
118
+ </Stack>
119
+ </DataCard>
120
+ </div>
121
+
122
+ <div>
123
+ <DataCard title={'Colori del brand'}>
124
+ <Stack align={'end'}>
125
+ <Alert w={'100%'} icon={<IconInfoSquare size={18} />} title="Informazioni">
126
+ Nota che tutti i colori dovrebbero sempre avere 10 sfumature. <a href={'https://smart-swatch.netlify.app/'} target={'_blank'}>Clicca qui</a> per generare la tua palette personalizzata.
127
+ </Alert>
128
+ <Text w={'100%'}>Preset</Text>
129
+ <Group w={'100%'} justify="left" gap="xs">
130
+ {swatches}
131
+ </Group>
132
+ <ColorInput w={'100%'} placeholder="Colore 1" label="Colore 1" value={newBrandColors[0]} onChange={(v) => setNewBrandColors((o: any) => [...elaborateColorsArray(o, 0, v)])} />
133
+ <ColorInput w={'100%'} placeholder="Colore 2" label="Colore 2" format={'hex'} value={newBrandColors[1]} onChange={(v) => setNewBrandColors((o: any) => [...elaborateColorsArray(o, 1, v)])} />
134
+ <ColorInput w={'100%'} placeholder="Colore 3" label="Colore 3" format={'hex'} value={newBrandColors[2]} onChange={(v) => setNewBrandColors((o: any) => [...elaborateColorsArray(o, 2, v)])} />
135
+ <ColorInput w={'100%'} placeholder="Colore 4" label="Colore 4" format={'hex'} value={newBrandColors[3]} onChange={(v) => setNewBrandColors((o: any) => [...elaborateColorsArray(o, 3, v)])} />
136
+ <ColorInput w={'100%'} placeholder="Colore 5" label="Colore 5" format={'hex'} value={newBrandColors[4]} onChange={(v) => setNewBrandColors((o: any) => [...elaborateColorsArray(o, 4, v)])} />
137
+ <ColorInput w={'100%'} placeholder="Colore 6" label="Colore 6" format={'hex'} value={newBrandColors[5]} onChange={(v) => setNewBrandColors((o: any) => [...elaborateColorsArray(o, 5, v)])} />
138
+ <ColorInput w={'100%'} placeholder="Colore 7" label="Colore 7" format={'hex'} value={newBrandColors[6]} onChange={(v) => setNewBrandColors((o: any) => [...elaborateColorsArray(o, 6, v)])} description={'Primario, headerbar'} />
139
+ <ColorInput w={'100%'} placeholder="Colore 8" label="Colore 8" format={'hex'} value={newBrandColors[7]} onChange={(v) => setNewBrandColors((o: any) => [...elaborateColorsArray(o, 7, v)])} />
140
+ <ColorInput w={'100%'} placeholder="Colore 9" label="Colore 9" format={'hex'} value={newBrandColors[8]} onChange={(v) => setNewBrandColors((o: any) => [...elaborateColorsArray(o, 8, v)])} />
141
+ <ColorInput w={'100%'} placeholder="Colore 10" label="Colore 10" format={'hex'} value={newBrandColors[9]} onChange={(v) => setNewBrandColors((o: any) => [...elaborateColorsArray(o, 9, v)])} />
142
+ <Divider color={'#e9ecef'} w={'100%'} />
143
+ <Button onClick={() => saveClientUIConfiguration()} leftSection={<IconDeviceFloppy />}>Salva</Button>
144
+ </Stack>
145
+ </DataCard>
146
+ </div>
147
+ </Stack>
148
+ )
149
+ }
@@ -0,0 +1,92 @@
1
+ import {AppJit} from "../models/app-jit";
2
+ import {AppState, AppType, FluxoFrontendAppConfiguration} from "@fluxo-dev/api-client-base";
3
+
4
+ export class AppJitManager {
5
+
6
+ public loadAppConfigurations(): AppJit[] {
7
+ const apps = localStorage.getItem('fluxo-jit-apps');
8
+
9
+ if (apps) {
10
+ const parsedApps: AppJit[] = JSON.parse(apps);
11
+ return parsedApps;
12
+ }
13
+
14
+ return [];
15
+ }
16
+
17
+ public refreshApp(app: AppJit): Promise<boolean> {
18
+ return fetch(`${app.developmentUrl}/fluxo-fe-app-configuration.json`)
19
+ .then((response) => {
20
+ return response.json()
21
+ .then((json) => {
22
+
23
+ this.removeAppConfiguration(json.scope, json.name);
24
+ this.addAppConfiguration(json, app.developmentUrl, app.developmentRoute);
25
+
26
+ return true;
27
+ });
28
+ })
29
+ .catch((error) => {
30
+
31
+ return false;
32
+ });
33
+ }
34
+
35
+ public checkIfAppExists(app: AppJit): boolean {
36
+ const apps = this.loadAppConfigurations();
37
+ return !!apps.find(x => x.scope === app.scope);
38
+ }
39
+
40
+ public addAppConfiguration(app: FluxoFrontendAppConfiguration, devUrl: string, devRoute: string) {
41
+ const apps = this.loadAppConfigurations();
42
+
43
+ if(apps.find(x => x.scope === app.scope && x.name === app.name)) {
44
+ return;
45
+ }
46
+
47
+ const a: AppJit = {
48
+ name: app?.name || '',
49
+ type: AppType.Frontend,
50
+ version: app?.version || '',
51
+ author: app?.author || '',
52
+ scope: app?.scope || '',
53
+ state: AppState.Active,
54
+ description: app?.description || '',
55
+ developmentRoute: devRoute,
56
+ installationDate: new Date(),
57
+ installationDirectory: '',
58
+ developmentUrl: devUrl,
59
+ id: 0,
60
+ menu: app?.menu || [],
61
+ isEmbedded: false
62
+ }
63
+
64
+ apps.push(a);
65
+ localStorage.setItem('fluxo-jit-apps', JSON.stringify(apps));
66
+ }
67
+
68
+ public setAppEnabled(appScope: string, appName: string, enabled: boolean) {
69
+ const apps = this.loadAppConfigurations();
70
+ const app = apps.find(x => x.scope === appScope && x.name === appName);
71
+
72
+ if(!app) {
73
+ return;
74
+ }
75
+
76
+ app.state = enabled ? AppState.Active : AppState.Inactive;
77
+ localStorage.setItem('fluxo-jit-apps', JSON.stringify(apps));
78
+ }
79
+
80
+ public removeAppConfiguration(appScope: string, appName: string) {
81
+ const apps = this.loadAppConfigurations();
82
+ const app = apps.find(x => x.scope === appScope && x.name === appName);
83
+
84
+ if(!app) {
85
+ return;
86
+ }
87
+
88
+ const index = apps.indexOf(app);
89
+ apps.splice(index, 1);
90
+ localStorage.setItem('fluxo-jit-apps', JSON.stringify(apps));
91
+ }
92
+ }