@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.
- package/.babelrc +12 -0
- package/.eslintrc.json +18 -0
- package/README.md +7 -0
- package/jest.config.ts +11 -0
- package/package.json +4 -0
- package/project.json +43 -0
- package/src/index.ts +1 -0
- package/src/lib/app.module.css +1 -0
- package/src/lib/app.spec.tsx +17 -0
- package/src/lib/app.tsx +374 -0
- package/src/lib/components/error-page/404.tsx +16 -0
- package/src/lib/components/routes/main-routes.tsx +26 -0
- package/src/lib/layouts/blank/blank-layout.tsx +10 -0
- package/src/lib/layouts/main/app-bar.tsx +192 -0
- package/src/lib/layouts/main/app-layout.tsx +52 -0
- package/src/lib/layouts/main/content.tsx +10 -0
- package/src/lib/layouts/main/footer-bar.tsx +76 -0
- package/src/lib/layouts/main/navbar-item.module.css +49 -0
- package/src/lib/layouts/main/navbar-item.tsx +129 -0
- package/src/lib/layouts/main/navbar.tsx +38 -0
- package/src/lib/layouts/main/search-bar.tsx +44 -0
- package/src/lib/layouts/main/shell-spotlight.tsx +58 -0
- package/src/lib/models/app-jit.ts +6 -0
- package/src/lib/pages/general/fluxo-plugin-loader.tsx +22 -0
- package/src/lib/pages/login/account-confirmation.tsx +86 -0
- package/src/lib/pages/login/change-password.tsx +58 -0
- package/src/lib/pages/login/forgot-password.tsx +53 -0
- package/src/lib/pages/login/login-page.tsx +70 -0
- package/src/lib/pages/login/reset-password.tsx +91 -0
- package/src/lib/pages/settings/apps/add-jit-app.tsx +79 -0
- package/src/lib/pages/settings/apps/app-item.tsx +157 -0
- package/src/lib/pages/settings/apps/app-jit-item.tsx +105 -0
- package/src/lib/pages/settings/apps/apps-list.tsx +171 -0
- package/src/lib/pages/settings/apps/embedded-module-item.tsx +66 -0
- package/src/lib/pages/settings/apps/install-or-upgrade-app.tsx +105 -0
- package/src/lib/pages/settings/menu-designer/custom-node.module.css +34 -0
- package/src/lib/pages/settings/menu-designer/custom-node.tsx +160 -0
- package/src/lib/pages/settings/menu-designer/menu-create-form.tsx +109 -0
- package/src/lib/pages/settings/menu-designer/menu-designer.module.css +19 -0
- package/src/lib/pages/settings/menu-designer/menu-designer.tsx +463 -0
- package/src/lib/pages/settings/menu-designer/menu-item-edit.tsx +115 -0
- package/src/lib/pages/settings/menu-designer/placeholder.module.css +8 -0
- package/src/lib/pages/settings/menu-designer/placeholder.tsx +13 -0
- package/src/lib/pages/settings/security/components/domani-role/domain-role-form.tsx +67 -0
- package/src/lib/pages/settings/security/components/domani-role/domain-role-grid.tsx +139 -0
- package/src/lib/pages/settings/security/components/permission/create-custom-permission-form.tsx +64 -0
- package/src/lib/pages/settings/security/components/permission/create-scope-form.tsx +158 -0
- package/src/lib/pages/settings/security/components/permission/permission-grid.tsx +304 -0
- package/src/lib/pages/settings/security/components/permission/permission-navigator.tsx +320 -0
- package/src/lib/pages/settings/security/components/permission/update-permission.tsx +54 -0
- package/src/lib/pages/settings/security/components/route-rule/route-rule-create-form.tsx +59 -0
- package/src/lib/pages/settings/security/components/route-rule/route-rule-grid.tsx +229 -0
- package/src/lib/pages/settings/security/components/user/user-form.tsx +123 -0
- package/src/lib/pages/settings/security/components/user/users-grid.tsx +127 -0
- package/src/lib/pages/settings/settings.tsx +298 -0
- package/src/lib/pages/settings/token/token-row.tsx +69 -0
- package/src/lib/pages/settings/token/tokens.tsx +73 -0
- package/src/lib/pages/settings/ui/ui.tsx +149 -0
- package/src/lib/services/app-jit-manager.ts +92 -0
- package/src/lib/services/app-menu.service.ts +35 -0
- package/src/lib/utils/icon-factory.tsx +25 -0
- package/src/lib/utils/security/only-not-logged-route.tsx +17 -0
- package/src/lib/utils/security/protected-route.tsx +47 -0
- package/tsconfig.json +20 -0
- package/tsconfig.lib.json +24 -0
- package/tsconfig.spec.json +20 -0
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import {AppState, AppType} from "@fluxo-dev/api-client-base";
|
|
2
|
+
import {ActionIcon, Badge, Group, Image, Menu, Stack} from "@mantine/core";
|
|
3
|
+
import React from "react";
|
|
4
|
+
import {IconBug, IconCircleOff, IconDotsVertical, IconTrash} from '@tabler/icons-react';
|
|
5
|
+
import moment from "moment";
|
|
6
|
+
import {ConfirmModal} from "@fluxo-dev/ui-components";
|
|
7
|
+
import {AppJitManager} from "../../../services/app-jit-manager";
|
|
8
|
+
import {AppJit} from "../../../models/app-jit";
|
|
9
|
+
|
|
10
|
+
export interface AppItemProps {
|
|
11
|
+
app: AppJit;
|
|
12
|
+
onDeactivate?: (app: AppJit) => void;
|
|
13
|
+
onActivate?: (app: AppJit) => void;
|
|
14
|
+
|
|
15
|
+
onRemove?: (app: AppJit) => void;
|
|
16
|
+
}
|
|
17
|
+
export function AppJitItem(props: AppItemProps) {
|
|
18
|
+
|
|
19
|
+
const [showDeleteModal, setShowDeleteModal] = React.useState(false);
|
|
20
|
+
const [showDeactivateModal, setShowDeactivateModale] = React.useState(false);
|
|
21
|
+
const [showActivateModal, setShowActivateModal] = React.useState(false);
|
|
22
|
+
|
|
23
|
+
const appJitManager = new AppJitManager();
|
|
24
|
+
|
|
25
|
+
const disable = () => {
|
|
26
|
+
appJitManager.setAppEnabled(props.app.scope, props.app.name, false);
|
|
27
|
+
props.onDeactivate?.(props.app);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const enable = () => {
|
|
31
|
+
appJitManager.setAppEnabled(props.app.scope, props.app.name, true);
|
|
32
|
+
props.onActivate?.(props.app);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const uninstall = () => {
|
|
36
|
+
appJitManager.removeAppConfiguration(props.app.scope, props.app.name);
|
|
37
|
+
props.onRemove?.(props.app);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<Group justify='space-between' style={{ border: '1px solid #dedede', padding: 15 }}>
|
|
42
|
+
<Group>
|
|
43
|
+
<IconBug size={48} color={'gray'} />
|
|
44
|
+
<Stack gap={0}>
|
|
45
|
+
<span>{props.app.description} <small>({props.app.version})</small></span>
|
|
46
|
+
<small>{props.app.name} - <b>{props.app.author}</b></small>
|
|
47
|
+
</Stack>
|
|
48
|
+
</Group>
|
|
49
|
+
<Group>
|
|
50
|
+
{props.app.state == AppState.Active ? <Badge color="green">Attivo</Badge> : <Badge color="red">Disattivo</Badge>}
|
|
51
|
+
<Badge color={'gray'}>Route: {props.app.developmentRoute}</Badge>
|
|
52
|
+
<Badge color={props.app.type === AppType.Frontend ? 'orange' : 'violet'}>{props.app.type === AppType.Frontend ? 'Frontend' : 'Backend'}</Badge>
|
|
53
|
+
<Badge color={'gray'}>{moment(props.app.installationDate).format("DD/MM/yyyy")}</Badge>
|
|
54
|
+
<Menu shadow="md" width={200}>
|
|
55
|
+
<Menu.Target>
|
|
56
|
+
<ActionIcon><IconDotsVertical size={18} /></ActionIcon>
|
|
57
|
+
</Menu.Target>
|
|
58
|
+
|
|
59
|
+
<Menu.Dropdown>
|
|
60
|
+
{/*<Menu.Label>Application</Menu.Label>*/}
|
|
61
|
+
<Menu.Item leftSection={<IconCircleOff size={14} />} onClick={props.app.state == AppState.Active ? disable : enable}>
|
|
62
|
+
{props.app.state == AppState.Active ? 'Disattiva' : 'Attiva'}
|
|
63
|
+
</Menu.Item>
|
|
64
|
+
<Menu.Divider />
|
|
65
|
+
<Menu.Item color={'red'} onClick={() => setShowDeleteModal(true)} leftSection={<IconTrash color={'red'} size={14} />}>Disinstalla</Menu.Item>
|
|
66
|
+
</Menu.Dropdown>
|
|
67
|
+
</Menu>
|
|
68
|
+
</Group>
|
|
69
|
+
|
|
70
|
+
<ConfirmModal
|
|
71
|
+
title={'Elimina JIT App'}
|
|
72
|
+
open={showDeleteModal}
|
|
73
|
+
onButtonCancelClick={() => setShowDeleteModal(false)}
|
|
74
|
+
onButtonConfirmClick={() => {
|
|
75
|
+
setShowDeleteModal(false);
|
|
76
|
+
uninstall();
|
|
77
|
+
}}
|
|
78
|
+
messageText={'Sei sicuro di voler disinstallare l\'applicazione?'}
|
|
79
|
+
/>
|
|
80
|
+
|
|
81
|
+
<ConfirmModal
|
|
82
|
+
title={'Attiva App'}
|
|
83
|
+
open={showActivateModal}
|
|
84
|
+
onButtonCancelClick={() => setShowActivateModal(false)}
|
|
85
|
+
onButtonConfirmClick={() => {
|
|
86
|
+
setShowActivateModal(false);
|
|
87
|
+
enable();
|
|
88
|
+
}}
|
|
89
|
+
messageText={'Sei sicuro di voler attivare l\'applicazione?'}
|
|
90
|
+
/>
|
|
91
|
+
|
|
92
|
+
<ConfirmModal
|
|
93
|
+
title={'Disablita App'}
|
|
94
|
+
open={showDeactivateModal}
|
|
95
|
+
onButtonCancelClick={() => setShowDeactivateModale(false)}
|
|
96
|
+
onButtonConfirmClick={() => {
|
|
97
|
+
setShowDeactivateModale(false);
|
|
98
|
+
disable();
|
|
99
|
+
}}
|
|
100
|
+
messageText={'Sei sicuro di voler attivare l\'applicazione?'}
|
|
101
|
+
/>
|
|
102
|
+
|
|
103
|
+
</Group>
|
|
104
|
+
)
|
|
105
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import {IconPlus} from '@tabler/icons-react';
|
|
2
|
+
import {Loader, Modal, Stack} from "@mantine/core";
|
|
3
|
+
import {DataCard, NoData} from "@fluxo-dev/ui-components";
|
|
4
|
+
import React, {useEffect, useState} from "react";
|
|
5
|
+
import {EmbeddedModule, FluxoApp, FluxoClientSys} from "@fluxo-dev/api-client-base";
|
|
6
|
+
import {AppItem} from "./app-item";
|
|
7
|
+
import {InstallOrUpgradeApp} from "./install-or-upgrade-app";
|
|
8
|
+
import {AddJitApp} from "./add-jit-app";
|
|
9
|
+
import {AppJitManager} from "../../../services/app-jit-manager";
|
|
10
|
+
import {AppJit} from "../../../models/app-jit";
|
|
11
|
+
import {AppJitItem} from "./app-jit-item";
|
|
12
|
+
import {EmbeddedModuleItem} from "./embedded-module-item";
|
|
13
|
+
import {reportsModule} from "@fluxo-fe/shell/reporting";
|
|
14
|
+
import {workflowModule} from "@fluxo-fe/shell/workflow";
|
|
15
|
+
import {ShellHub} from "@fluxo-fe/shell/hub";
|
|
16
|
+
|
|
17
|
+
export interface AppsListProps {
|
|
18
|
+
pluginEndpoint: string;
|
|
19
|
+
modules: EmbeddedModule[],
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function AppsList(props: AppsListProps) {
|
|
23
|
+
|
|
24
|
+
const [apps, setApps] = useState<FluxoApp[]>([]);
|
|
25
|
+
const [showInstallComponent, setShowInstallComponent] = useState(false);
|
|
26
|
+
const [showHubModal, setShowHubModal] = useState(false);
|
|
27
|
+
const [showAddJitApp, setShowAddJitApp] = useState(false);
|
|
28
|
+
const [jitApps, setJitApps] = useState<AppJit[]>([]);
|
|
29
|
+
|
|
30
|
+
const [loading, setLoading] = useState(true);
|
|
31
|
+
|
|
32
|
+
const [embeddedModules] = useState([...props.modules, reportsModule, workflowModule]);
|
|
33
|
+
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
refreshApps();
|
|
36
|
+
refreshJitApps();
|
|
37
|
+
}, [])
|
|
38
|
+
|
|
39
|
+
const checkIfEmbeddedModuleIsUpgradable = (scope: string, name: string, currentVersion: string): {state: 'uptodate' | 'upgradable' | 'installable', version: string} => {
|
|
40
|
+
const appFound = apps.find(m => m.scope === scope && m.name === name);
|
|
41
|
+
|
|
42
|
+
if (!appFound) {
|
|
43
|
+
return {state: 'installable', version: currentVersion};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if(appFound && appFound.version === currentVersion) {
|
|
47
|
+
return {state: 'uptodate', version: currentVersion};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return {state: 'upgradable', version: currentVersion};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const refreshApps = () => {
|
|
54
|
+
setLoading(true);
|
|
55
|
+
FluxoClientSys.Instance.apps.getApps(false).then((apps) => {
|
|
56
|
+
setApps(apps);
|
|
57
|
+
setLoading(false);
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const refreshJitApps = () => {
|
|
62
|
+
const manager = new AppJitManager();
|
|
63
|
+
setJitApps(manager.loadAppConfigurations());
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<Stack>
|
|
68
|
+
<DataCard
|
|
69
|
+
title={'Apps installate'}
|
|
70
|
+
actions={[
|
|
71
|
+
{ icon: <IconPlus size={18} />, label: 'Installa app', onClick: () => setShowInstallComponent(true) },
|
|
72
|
+
{ icon: <IconPlus size={18} />, label: 'Aggiungi app', onClick: () => setShowHubModal(true) },
|
|
73
|
+
]}>
|
|
74
|
+
{showInstallComponent &&
|
|
75
|
+
<InstallOrUpgradeApp
|
|
76
|
+
mode={'install'}
|
|
77
|
+
onInstall={() => {
|
|
78
|
+
refreshApps();
|
|
79
|
+
setShowInstallComponent(false);
|
|
80
|
+
}} />
|
|
81
|
+
}
|
|
82
|
+
<Stack gap={16}>
|
|
83
|
+
{(!loading && (!apps || apps.filter(a => !a.isEmbedded).length === 0)) &&
|
|
84
|
+
<NoData message={'Nessuna applicazione installata'} />
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
{loading &&
|
|
88
|
+
<Loader />
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
{!loading && apps.filter(a => !a.isEmbedded).map((app) => (
|
|
92
|
+
<AppItem
|
|
93
|
+
key={app.id}
|
|
94
|
+
app={app}
|
|
95
|
+
onActivate={refreshApps}
|
|
96
|
+
onDeactivate={refreshApps}
|
|
97
|
+
onUninstall={refreshApps}
|
|
98
|
+
onUpgrade={refreshApps}
|
|
99
|
+
pluginEndpoint={props.pluginEndpoint}
|
|
100
|
+
/>
|
|
101
|
+
))}
|
|
102
|
+
</Stack>
|
|
103
|
+
</DataCard>
|
|
104
|
+
|
|
105
|
+
<DataCard
|
|
106
|
+
title={'Apps di sistema'}>
|
|
107
|
+
<Stack gap={16}>
|
|
108
|
+
{embeddedModules.length === 0 &&
|
|
109
|
+
<NoData message={'Nessun modulo di sistema trovato'} />
|
|
110
|
+
}
|
|
111
|
+
{!loading ?
|
|
112
|
+
<>
|
|
113
|
+
{embeddedModules.map((app, i) => (
|
|
114
|
+
<EmbeddedModuleItem
|
|
115
|
+
key={i}
|
|
116
|
+
app={app}
|
|
117
|
+
onUpgrade={refreshApps}
|
|
118
|
+
upgradable={checkIfEmbeddedModuleIsUpgradable(app.scope, app.name, app.version)}
|
|
119
|
+
/>
|
|
120
|
+
))}
|
|
121
|
+
</> :
|
|
122
|
+
<Loader />
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
</Stack>
|
|
126
|
+
</DataCard>
|
|
127
|
+
|
|
128
|
+
<DataCard
|
|
129
|
+
title={'Apps JIT (Sviluppo)'}
|
|
130
|
+
actions={[
|
|
131
|
+
{ icon: <IconPlus size={18} />, label: 'Aggiungi applicazione', onClick: () => setShowAddJitApp(true) },
|
|
132
|
+
]}>
|
|
133
|
+
{!jitApps || jitApps.length === 0 &&
|
|
134
|
+
<NoData message={'Nessuna applicazione JIT'} />
|
|
135
|
+
}
|
|
136
|
+
{!loading && jitApps.map((app) => (
|
|
137
|
+
<AppJitItem key={app.id} onDeactivate={refreshJitApps} onActivate={refreshJitApps} onRemove={refreshJitApps} app={app} />
|
|
138
|
+
))}
|
|
139
|
+
</DataCard>
|
|
140
|
+
|
|
141
|
+
<Modal centered title={'Aggiungi App JIT'} opened={showAddJitApp} onClose={() => setShowAddJitApp(false)}>
|
|
142
|
+
<AddJitApp
|
|
143
|
+
onAdded={() => {
|
|
144
|
+
setShowAddJitApp(false);
|
|
145
|
+
refreshJitApps();
|
|
146
|
+
}}
|
|
147
|
+
onCanceled={() => setShowAddJitApp(false)}
|
|
148
|
+
/>
|
|
149
|
+
</Modal>
|
|
150
|
+
|
|
151
|
+
<Modal
|
|
152
|
+
title={'Fluxo Apps'}
|
|
153
|
+
opened={showHubModal}
|
|
154
|
+
styles={{
|
|
155
|
+
close: {color: 'black'},
|
|
156
|
+
header: {
|
|
157
|
+
background: '#f6f7f9',
|
|
158
|
+
padding: 8,
|
|
159
|
+
marginBottom: 16,
|
|
160
|
+
color: 'black',
|
|
161
|
+
boxShadow: '0px 0px 16px -4px #888'
|
|
162
|
+
},
|
|
163
|
+
content: {background: '#f6f7f9'}
|
|
164
|
+
}}
|
|
165
|
+
fullScreen
|
|
166
|
+
onClose={() => setShowHubModal(false)} size={'xl'} padding={'none'}>
|
|
167
|
+
<ShellHub />
|
|
168
|
+
</Modal>
|
|
169
|
+
</Stack>
|
|
170
|
+
)
|
|
171
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import {
|
|
2
|
+
EmbeddedModule, FluxoClientSys
|
|
3
|
+
} from "@fluxo-dev/api-client-base";
|
|
4
|
+
import {Badge, Button, Group, Stack} from "@mantine/core";
|
|
5
|
+
import React from "react";
|
|
6
|
+
import {IconRefresh} from '@tabler/icons-react';
|
|
7
|
+
import {showNotification} from "@mantine/notifications";
|
|
8
|
+
|
|
9
|
+
export interface EmbeddedModuleItemProps {
|
|
10
|
+
app: EmbeddedModule;
|
|
11
|
+
onUpgrade?: (app: EmbeddedModule) => void;
|
|
12
|
+
upgradable: {state: 'uptodate' | 'upgradable' | 'installable', version: string};
|
|
13
|
+
}
|
|
14
|
+
export function EmbeddedModuleItem(props: EmbeddedModuleItemProps) {
|
|
15
|
+
|
|
16
|
+
const [updateInProgress, setUpdateInProgress] = React.useState(false);
|
|
17
|
+
|
|
18
|
+
const installOrUpgrade = () => {
|
|
19
|
+
|
|
20
|
+
setUpdateInProgress(true);
|
|
21
|
+
|
|
22
|
+
FluxoClientSys.Instance.apps.installEmbeddedModule(props.app.getEmbeddedModule())
|
|
23
|
+
.then(() => {
|
|
24
|
+
showNotification({
|
|
25
|
+
title: 'Operazione completata',
|
|
26
|
+
message: `Il modulo (${props.app.name} - ${props.app.version}) è stata installata con successo`,
|
|
27
|
+
color: 'green',
|
|
28
|
+
})
|
|
29
|
+
props.onUpgrade?.(props.app);
|
|
30
|
+
setUpdateInProgress(false);
|
|
31
|
+
})
|
|
32
|
+
.catch(() => {
|
|
33
|
+
showNotification({
|
|
34
|
+
title: 'Operazione fallita',
|
|
35
|
+
message: `Il modulo (${props.app.name} - ${props.app.version}) non è stata installata`,
|
|
36
|
+
color: 'red',
|
|
37
|
+
})
|
|
38
|
+
setUpdateInProgress(false);
|
|
39
|
+
})
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<Group justify='space-between' style={{ border: '1px solid #dedede', padding: 15 }}>
|
|
44
|
+
<Group>
|
|
45
|
+
<Stack gap={0}>
|
|
46
|
+
<span>{props.app.description} <small>({props.app.version})</small></span>
|
|
47
|
+
<small>{props.app.name} - <b>{props.app.author}</b></small>
|
|
48
|
+
</Stack>
|
|
49
|
+
</Group>
|
|
50
|
+
<Group>
|
|
51
|
+
{props.upgradable?.state === 'upgradable' &&
|
|
52
|
+
<Button loading={updateInProgress} leftSection={<IconRefresh size={18} />} onClick={installOrUpgrade} color="yellow">Aggiorna → {props.upgradable.version}</Button>
|
|
53
|
+
}
|
|
54
|
+
{props.upgradable?.state === 'uptodate' &&
|
|
55
|
+
<>
|
|
56
|
+
<Badge color="gray">Ultima versione</Badge>
|
|
57
|
+
<Button onClick={installOrUpgrade}>Aggiorna menu</Button>
|
|
58
|
+
</>
|
|
59
|
+
}
|
|
60
|
+
{props.upgradable?.state === 'installable' &&
|
|
61
|
+
<Button loading={updateInProgress} leftSection={<IconRefresh size={18} />} onClick={installOrUpgrade} color="green">Installa → {props.upgradable.version}</Button>
|
|
62
|
+
}
|
|
63
|
+
</Group>
|
|
64
|
+
</Group>
|
|
65
|
+
)
|
|
66
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import {Group, Text, useMantineTheme, rem, useMantineColorScheme} from '@mantine/core';
|
|
2
|
+
import React from "react";
|
|
3
|
+
import {IconApps, IconUpload, IconX} from '@tabler/icons-react';
|
|
4
|
+
import {Dropzone, FileWithPath} from "@mantine/dropzone";
|
|
5
|
+
import {FluxoClientSys} from "@fluxo-dev/api-client-base";
|
|
6
|
+
import {showNotification} from "@mantine/notifications";
|
|
7
|
+
|
|
8
|
+
export interface InstallOrUpgradeAppProps {
|
|
9
|
+
onInstall?: () => void;
|
|
10
|
+
mode: 'install' | 'upgrade';
|
|
11
|
+
appIdToUpgrade?: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function InstallOrUpgradeApp(props: InstallOrUpgradeAppProps) {
|
|
15
|
+
const theme = useMantineTheme();
|
|
16
|
+
const colorScheme = useMantineColorScheme();
|
|
17
|
+
|
|
18
|
+
const installApp = (files: FileWithPath[]) => {
|
|
19
|
+
|
|
20
|
+
const formData = new FormData();
|
|
21
|
+
formData.append('file', files[0]);
|
|
22
|
+
|
|
23
|
+
if(props.mode === 'install') {
|
|
24
|
+
FluxoClientSys.Instance.apps.installApp(formData).then(() => {
|
|
25
|
+
showNotification({
|
|
26
|
+
title: 'Operazione completata',
|
|
27
|
+
message: `L'applicazione è stata installata con successo`,
|
|
28
|
+
color: 'green',
|
|
29
|
+
})
|
|
30
|
+
props.onInstall?.();
|
|
31
|
+
}).catch((err) => {
|
|
32
|
+
showNotification({
|
|
33
|
+
title: 'Operazione fallita',
|
|
34
|
+
message: `L'applicazione non è stata installata: ${err.message}`,
|
|
35
|
+
color: 'red',
|
|
36
|
+
})
|
|
37
|
+
})
|
|
38
|
+
} else {
|
|
39
|
+
|
|
40
|
+
if(!props.appIdToUpgrade)
|
|
41
|
+
return;
|
|
42
|
+
|
|
43
|
+
FluxoClientSys.Instance.apps.upgradeApp(props.appIdToUpgrade, formData).then(() => {
|
|
44
|
+
showNotification({
|
|
45
|
+
title: 'Operazione completata',
|
|
46
|
+
message: `L'applicazione è stata aggiornata con successo`,
|
|
47
|
+
color: 'green',
|
|
48
|
+
})
|
|
49
|
+
props.onInstall?.();
|
|
50
|
+
}).catch((err) => {
|
|
51
|
+
showNotification({
|
|
52
|
+
title: 'Operazione fallita',
|
|
53
|
+
message: `L'applicazione non è stata aggiornata: ${err.message}`,
|
|
54
|
+
color: 'red',
|
|
55
|
+
})
|
|
56
|
+
})
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<Dropzone
|
|
62
|
+
onDrop={installApp}
|
|
63
|
+
onReject={(files) => {
|
|
64
|
+
showNotification({
|
|
65
|
+
title: 'Operazione fallita',
|
|
66
|
+
message: `L'applicazione non è stata installata: ${files[0].file.name} non è un file zip o la dimensione supera i 30mb`,
|
|
67
|
+
color: 'red',
|
|
68
|
+
})
|
|
69
|
+
}}
|
|
70
|
+
maxSize={1024 * 3 * 10000}
|
|
71
|
+
multiple={false}
|
|
72
|
+
accept={['zip']}
|
|
73
|
+
{...props}
|
|
74
|
+
>
|
|
75
|
+
<Group justify="center" gap="xl" style={{ minHeight: rem(220), pointerEvents: 'none' }}>
|
|
76
|
+
<Dropzone.Accept>
|
|
77
|
+
<IconUpload
|
|
78
|
+
size="3.2rem"
|
|
79
|
+
stroke={1.5}
|
|
80
|
+
color={theme.colors[theme.primaryColor][colorScheme.colorScheme === 'dark' ? 4 : 6]}
|
|
81
|
+
/>
|
|
82
|
+
</Dropzone.Accept>
|
|
83
|
+
<Dropzone.Reject>
|
|
84
|
+
<IconX
|
|
85
|
+
size="3.2rem"
|
|
86
|
+
stroke={1.5}
|
|
87
|
+
color={theme.colors.red[colorScheme.colorScheme === 'dark' ? 4 : 6]}
|
|
88
|
+
/>
|
|
89
|
+
</Dropzone.Reject>
|
|
90
|
+
<Dropzone.Idle>
|
|
91
|
+
<IconApps size="3.2rem" stroke={1.5} />
|
|
92
|
+
</Dropzone.Idle>
|
|
93
|
+
|
|
94
|
+
<div>
|
|
95
|
+
<Text size="xl" inline>
|
|
96
|
+
Trascina il bundle dell'applicazione qui.
|
|
97
|
+
</Text>
|
|
98
|
+
<Text size="sm" color="dimmed" inline mt={7}>
|
|
99
|
+
Il bundle deve essere in formato .zip e non deve superare i 30mb.
|
|
100
|
+
</Text>
|
|
101
|
+
</div>
|
|
102
|
+
</Group>
|
|
103
|
+
</Dropzone>
|
|
104
|
+
);
|
|
105
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
.root {
|
|
2
|
+
align-items: center;
|
|
3
|
+
display: grid;
|
|
4
|
+
grid-template-columns: auto auto 1fr auto;
|
|
5
|
+
height: 32px;
|
|
6
|
+
padding-inline-end: 8px;
|
|
7
|
+
cursor: pointer;
|
|
8
|
+
/*border-bottom: solid 1px #eee;*/
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.root:hover {
|
|
12
|
+
background: #e9ecef54;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.expandIconWrapper {
|
|
16
|
+
align-items: center;
|
|
17
|
+
font-size: 0;
|
|
18
|
+
cursor: pointer;
|
|
19
|
+
display: flex;
|
|
20
|
+
height: 24px;
|
|
21
|
+
justify-content: center;
|
|
22
|
+
width: 32px;
|
|
23
|
+
transition: transform linear 0.1s;
|
|
24
|
+
transform: rotate(0deg);
|
|
25
|
+
margin-left: 8px;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.expandIconWrapper.isOpen {
|
|
29
|
+
transform: rotate(90deg);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.labelGridItem {
|
|
33
|
+
padding-inline-start: 8px;
|
|
34
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import React, {useState} from "react";
|
|
2
|
+
import { NodeModel } from "@minoru/react-dnd-treeview";
|
|
3
|
+
import styles from "./custom-node.module.css";
|
|
4
|
+
import {
|
|
5
|
+
IconBoxMultiple,
|
|
6
|
+
IconChevronRight, IconEdit,
|
|
7
|
+
IconExternalLink, IconEye,
|
|
8
|
+
IconLock, IconPlus, IconSearch,
|
|
9
|
+
IconTrash
|
|
10
|
+
} from '@tabler/icons-react';
|
|
11
|
+
import {ActionIcon, Badge, Group, Modal, Text, ThemeIcon, Tooltip} from "@mantine/core";
|
|
12
|
+
import {AppMenuItem} from "@fluxo-dev/api-client-base";
|
|
13
|
+
import {IconFactory} from "../../../utils/icon-factory";
|
|
14
|
+
import {PermissionNavigator} from "../security/components/permission/permission-navigator";
|
|
15
|
+
|
|
16
|
+
type Props = {
|
|
17
|
+
nodes: NodeModel<AppMenuItem>[];
|
|
18
|
+
node: NodeModel<AppMenuItem>;
|
|
19
|
+
depth: number;
|
|
20
|
+
isOpen: boolean;
|
|
21
|
+
onToggle: (id: NodeModel["id"]) => void;
|
|
22
|
+
onEditButtonClick: (item: AppMenuItem) => void;
|
|
23
|
+
onDeleteButtonClick: (nodeId: string | number) => void;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const CustomNode: React.FC<Props> = (props) => {
|
|
27
|
+
const { droppable, data } = props.node;
|
|
28
|
+
const indent = props.depth * 24;
|
|
29
|
+
|
|
30
|
+
const [mouseHover, setMouseHover] = useState<boolean>(false);
|
|
31
|
+
const [updatePermissionModalOpen, setUpdatePermissionModalOpen] = useState<boolean>(false);
|
|
32
|
+
const [hasPermission, setHasPermission] = useState<boolean>(props.node!.data!.hasPermissions ?? false);
|
|
33
|
+
|
|
34
|
+
const handleToggle = (e: React.MouseEvent) => {
|
|
35
|
+
e.stopPropagation();
|
|
36
|
+
props.onToggle(props.node.id);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const isLeaf = (): boolean => {
|
|
40
|
+
const res = props.nodes.find(a => a.parent === props.node.id);
|
|
41
|
+
|
|
42
|
+
return res !== undefined;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const updatePermissionButton = () => {
|
|
46
|
+
return <ActionIcon color={!props.node.data!.id ? 'red' : hasPermission ? 'green' : undefined} onClick={() => {
|
|
47
|
+
if(!props.node.data!.id) return;
|
|
48
|
+
setUpdatePermissionModalOpen(true);
|
|
49
|
+
}}><IconEye size={16} /></ActionIcon>;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<div
|
|
54
|
+
id={props.node.id.toString()}
|
|
55
|
+
className={`tree-node ${styles["root"]}`}
|
|
56
|
+
style={{
|
|
57
|
+
marginLeft: indent,
|
|
58
|
+
marginBottom: 16,
|
|
59
|
+
height: 48,
|
|
60
|
+
border: '1px solid #e9ecef'
|
|
61
|
+
}}
|
|
62
|
+
>
|
|
63
|
+
<div className={styles["expandIconWrapper"]}>
|
|
64
|
+
<ThemeIcon variant="light" size={30}>
|
|
65
|
+
{IconFactory.getIcon(props.node.data!.icon!)}
|
|
66
|
+
</ThemeIcon>
|
|
67
|
+
</div>
|
|
68
|
+
|
|
69
|
+
<div className={styles["labelGridItem"]}
|
|
70
|
+
onClick={handleToggle}>
|
|
71
|
+
<Text variant="text" size={'xs'}>{`${props.node.text}`}</Text>
|
|
72
|
+
</div>
|
|
73
|
+
|
|
74
|
+
<div
|
|
75
|
+
onClick={handleToggle}
|
|
76
|
+
style={{width: isLeaf() ? 24 : 8}}
|
|
77
|
+
className={`${styles["expandIconWrapper"]} ${
|
|
78
|
+
props.isOpen ? styles["isOpen"] : ""
|
|
79
|
+
}`}
|
|
80
|
+
>
|
|
81
|
+
{isLeaf() && (
|
|
82
|
+
<div onClick={handleToggle}>
|
|
83
|
+
<IconChevronRight size={16} />
|
|
84
|
+
</div>
|
|
85
|
+
)}
|
|
86
|
+
</div>
|
|
87
|
+
|
|
88
|
+
<div>
|
|
89
|
+
<Group gap={8}>
|
|
90
|
+
|
|
91
|
+
{props.node.data?.isProtectedByRule &&
|
|
92
|
+
<Badge leftSection={
|
|
93
|
+
<ActionIcon size="xs" radius="xl" variant="transparent">
|
|
94
|
+
<IconLock size={12} />
|
|
95
|
+
</ActionIcon>
|
|
96
|
+
}>Regola attiva</Badge>
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
{!props.node.data!.id &&
|
|
100
|
+
<Badge color={'green'} leftSection={
|
|
101
|
+
<ActionIcon size="xs" radius="xl" variant="transparent">
|
|
102
|
+
<IconPlus color={'green'} size={12} />
|
|
103
|
+
</ActionIcon>
|
|
104
|
+
}>Nuovo</Badge>
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
{props.node.data?.isGroup &&
|
|
108
|
+
<Badge color={'red'} leftSection={
|
|
109
|
+
<ActionIcon size="xs" radius="xl" variant="transparent">
|
|
110
|
+
<IconBoxMultiple color={'red'} size={12} />
|
|
111
|
+
</ActionIcon>
|
|
112
|
+
}>Gruppo</Badge>
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
{!props.node.data?.isGroup && props.node.data?.hasIndex &&
|
|
116
|
+
<Badge color={'yellow'} leftSection={
|
|
117
|
+
<ActionIcon size="xs" radius="xl" variant="transparent">
|
|
118
|
+
<IconSearch color={'orange'} size={12} />
|
|
119
|
+
</ActionIcon>
|
|
120
|
+
}>Indicizzato</Badge>
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
{props.node.data?.featureIdentifier === 'webclient.routing.externallink' &&
|
|
124
|
+
<Badge color={'green'} leftSection={
|
|
125
|
+
<ActionIcon size="xs" radius="xl" variant="transparent">
|
|
126
|
+
<IconExternalLink color={'green'} size={12} />
|
|
127
|
+
</ActionIcon>
|
|
128
|
+
}>Esterno</Badge>
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
{!props.node.data?.id ?
|
|
132
|
+
<Tooltip label={"Devi salvare il menu prima di impostare i permessi"}>
|
|
133
|
+
{updatePermissionButton()}
|
|
134
|
+
</Tooltip> :
|
|
135
|
+
<>{updatePermissionButton()}</>
|
|
136
|
+
}
|
|
137
|
+
<ActionIcon onClick={() => props.onEditButtonClick(props.node.data!)}><IconEdit size={16} /></ActionIcon>
|
|
138
|
+
<ActionIcon onClick={() => props.onDeleteButtonClick(props.node.id)}><IconTrash size={16} /></ActionIcon>
|
|
139
|
+
</Group>
|
|
140
|
+
</div>
|
|
141
|
+
|
|
142
|
+
{props.node!.data!.id &&
|
|
143
|
+
<Modal
|
|
144
|
+
size={'60%'}
|
|
145
|
+
centered
|
|
146
|
+
opened={updatePermissionModalOpen}
|
|
147
|
+
onClose={() => {
|
|
148
|
+
setUpdatePermissionModalOpen(false);
|
|
149
|
+
}}
|
|
150
|
+
title={`Gestione permessi ${props.node!.data!.label}`}>
|
|
151
|
+
<PermissionNavigator
|
|
152
|
+
context={{type: 'menu', id: props.node!.data!.id!.toString()}}
|
|
153
|
+
onUpdate={(pc) => setHasPermission(pc > 0)}
|
|
154
|
+
/>
|
|
155
|
+
</Modal>
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
</div>
|
|
159
|
+
);
|
|
160
|
+
};
|