@plutonhq/core-frontend 0.1.21 → 0.1.23
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/dist-lib/@types/plans.d.ts +11 -20
- package/dist-lib/@types/plans.d.ts.map +1 -1
- package/dist-lib/components/Plan/PlanSettings/PlanAdvancedSettings.js +49 -47
- package/dist-lib/components/Plan/PlanSettings/PlanAdvancedSettings.js.map +1 -1
- package/dist-lib/components/Plan/PlanSettings/PlanNotificationSettings.d.ts +7 -4
- package/dist-lib/components/Plan/PlanSettings/PlanNotificationSettings.d.ts.map +1 -1
- package/dist-lib/components/Plan/PlanSettings/PlanNotificationSettings.js +177 -52
- package/dist-lib/components/Plan/PlanSettings/PlanNotificationSettings.js.map +1 -1
- package/dist-lib/components/Plan/PlanSettings/PlanNotificationSettingsTester.d.ts +9 -0
- package/dist-lib/components/Plan/PlanSettings/PlanNotificationSettingsTester.d.ts.map +1 -0
- package/dist-lib/components/Plan/PlanSettings/PlanNotificationSettingsTester.js +62 -0
- package/dist-lib/components/Plan/PlanSettings/PlanNotificationSettingsTester.js.map +1 -0
- package/dist-lib/components/Plan/PlanSettings/PlanPruneSettings.js +2 -2
- package/dist-lib/components/Plan/PlanSettings/PlanPruneSettings.js.map +1 -1
- package/dist-lib/components/Plan/PlanSettings/PlanSettings.module.scss.js +56 -40
- package/dist-lib/components/Plan/PlanSettings/PlanSettings.module.scss.js.map +1 -1
- package/dist-lib/components/Settings/GeneralSettings/GeneralSettings.d.ts.map +1 -1
- package/dist-lib/components/Settings/GeneralSettings/GeneralSettings.js +16 -8
- package/dist-lib/components/Settings/GeneralSettings/GeneralSettings.js.map +1 -1
- package/dist-lib/components/Storage/AddStorage/AddStorage.module.scss.js +42 -26
- package/dist-lib/components/Storage/AddStorage/AddStorage.module.scss.js.map +1 -1
- package/dist-lib/components/Storage/StorageAuthSettings/StorageAuthSettings.d.ts.map +1 -1
- package/dist-lib/components/Storage/StorageAuthSettings/StorageAuthSettings.js +119 -53
- package/dist-lib/components/Storage/StorageAuthSettings/StorageAuthSettings.js.map +1 -1
- package/dist-lib/components/common/Icon/Icon.d.ts.map +1 -1
- package/dist-lib/components/common/Icon/Icon.js +15 -1
- package/dist-lib/components/common/Icon/Icon.js.map +1 -1
- package/dist-lib/components/index.d.ts +1 -0
- package/dist-lib/components/index.d.ts.map +1 -1
- package/dist-lib/components.js +86 -84
- package/dist-lib/components.js.map +1 -1
- package/dist-lib/providers/s3compatible.png +0 -0
- package/dist-lib/services/plans.d.ts +13 -1
- package/dist-lib/services/plans.d.ts.map +1 -1
- package/dist-lib/services/plans.js +88 -61
- package/dist-lib/services/plans.js.map +1 -1
- package/dist-lib/services/settings.d.ts.map +1 -1
- package/dist-lib/services/settings.js +25 -24
- package/dist-lib/services/settings.js.map +1 -1
- package/dist-lib/services/storage.d.ts +10 -0
- package/dist-lib/services/storage.d.ts.map +1 -1
- package/dist-lib/services/storage.js +55 -20
- package/dist-lib/services/storage.js.map +1 -1
- package/dist-lib/services.js +106 -101
- package/dist-lib/styles/core-frontend.css +1 -1
- package/dist-lib/utils/constants.d.ts.map +1 -1
- package/dist-lib/utils/constants.js +28 -5
- package/dist-lib/utils/constants.js.map +1 -1
- package/dist-lib/utils/helpers.d.ts +1 -0
- package/dist-lib/utils/helpers.d.ts.map +1 -1
- package/dist-lib/utils/helpers.js +17 -10
- package/dist-lib/utils/helpers.js.map +1 -1
- package/dist-lib/utils.js +28 -27
- package/package.json +1 -1
- package/src/@types/plans.ts +11 -20
- package/src/components/Plan/PlanSettings/PlanAdvancedSettings.tsx +4 -4
- package/src/components/Plan/PlanSettings/PlanNotificationSettings.tsx +179 -47
- package/src/components/Plan/PlanSettings/PlanNotificationSettingsTester.tsx +85 -0
- package/src/components/Plan/PlanSettings/PlanPruneSettings.tsx +2 -2
- package/src/components/Plan/PlanSettings/PlanSettings.module.scss +71 -0
- package/src/components/Settings/GeneralSettings/GeneralSettings.tsx +6 -1
- package/src/components/Storage/AddStorage/AddStorage.module.scss +74 -0
- package/src/components/Storage/StorageAuthSettings/StorageAuthSettings.tsx +136 -3
- package/src/components/common/Icon/Icon.tsx +16 -0
- package/src/components/index.ts +1 -0
- package/src/services/plans.ts +38 -1
- package/src/services/settings.ts +2 -2
- package/src/services/storage.ts +49 -0
- package/src/utils/constants.ts +23 -0
- package/src/utils/helpers.ts +9 -0
|
@@ -2,75 +2,207 @@ import classes from './PlanSettings.module.scss';
|
|
|
2
2
|
import Select from '../../common/form/Select/Select';
|
|
3
3
|
import Input from '../../common/form/Input/Input';
|
|
4
4
|
import Toggle from '../../common/form/Toggle/Toggle';
|
|
5
|
-
import {
|
|
5
|
+
import { PlanNotification, PlanNotificationCase } from '../../../@types/plans';
|
|
6
6
|
import { NavLink } from 'react-router';
|
|
7
|
+
import PlanNotificationSettingsTester from './PlanNotificationSettingsTester';
|
|
8
|
+
import { Icon } from '../..';
|
|
7
9
|
|
|
8
10
|
interface PlanNotificationSettingsProps {
|
|
9
|
-
plan: NewPlanSettings;
|
|
10
11
|
types: string[];
|
|
12
|
+
isSync: boolean;
|
|
13
|
+
notificationType?: 'backup' | 'integrity';
|
|
11
14
|
admin_email?: string;
|
|
12
|
-
|
|
15
|
+
planID?: string;
|
|
16
|
+
notificationSettings: PlanNotification;
|
|
17
|
+
onUpdate: (notificationSettings: PlanNotification) => void;
|
|
13
18
|
}
|
|
14
19
|
|
|
15
|
-
const PlanNotificationSettings = ({
|
|
16
|
-
|
|
20
|
+
const PlanNotificationSettings = ({
|
|
21
|
+
planID,
|
|
22
|
+
isSync,
|
|
23
|
+
types = [],
|
|
24
|
+
admin_email = '',
|
|
25
|
+
notificationSettings,
|
|
26
|
+
notificationType = 'backup',
|
|
27
|
+
onUpdate,
|
|
28
|
+
}: PlanNotificationSettingsProps) => {
|
|
29
|
+
const hasConnectedIntegrations = types.length > 0;
|
|
30
|
+
const defaultEmail = !hasConnectedIntegrations && admin_email ? admin_email : '';
|
|
17
31
|
|
|
18
32
|
const updateNotificationEmails = (emails: string) => {
|
|
19
33
|
onUpdate({ ...notificationSettings, email: { ...notificationSettings.email, emails } });
|
|
20
34
|
};
|
|
21
35
|
|
|
22
|
-
const
|
|
23
|
-
|
|
36
|
+
const caseOptions = [
|
|
37
|
+
{ label: 'On Start', value: 'start' },
|
|
38
|
+
{ label: 'On End', value: 'end' },
|
|
39
|
+
{ label: 'On Both Start & End', value: 'both' },
|
|
40
|
+
{ label: 'On Success Only', value: 'success' },
|
|
41
|
+
{ label: 'On Failure Only', value: 'failure' },
|
|
42
|
+
];
|
|
24
43
|
|
|
25
44
|
return (
|
|
26
45
|
<>
|
|
27
|
-
<div className={classes.
|
|
28
|
-
<
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
46
|
+
<div className={classes.notificationSettingsSection}>
|
|
47
|
+
<div className={`${classes.field} ${classes.notificationToggle}`}>
|
|
48
|
+
<Icon type="email" size={14} />
|
|
49
|
+
<Toggle
|
|
50
|
+
label="Enable Email Notifications"
|
|
51
|
+
fieldValue={notificationSettings?.email?.enabled || false}
|
|
52
|
+
onUpdate={(val: boolean) => onUpdate({ ...notificationSettings, email: { ...notificationSettings.email, enabled: val } })}
|
|
53
|
+
hint={'Notify me via email when Backup fails or succeeds'}
|
|
54
|
+
inline={true}
|
|
55
|
+
/>
|
|
56
|
+
</div>
|
|
57
|
+
{notificationSettings?.email?.enabled && (
|
|
58
|
+
<div className={classes.notificationSettings}>
|
|
59
|
+
{!isSync && (
|
|
60
|
+
<div className={classes.field}>
|
|
61
|
+
<Select
|
|
62
|
+
label="Send Notification"
|
|
63
|
+
fieldValue={notificationSettings?.email?.case || 'failure'}
|
|
64
|
+
options={caseOptions}
|
|
65
|
+
onUpdate={(val: string) =>
|
|
66
|
+
onUpdate({ ...notificationSettings, email: { ...notificationSettings.email, case: val as PlanNotificationCase } })
|
|
67
|
+
}
|
|
68
|
+
inline={true}
|
|
69
|
+
/>
|
|
70
|
+
</div>
|
|
71
|
+
)}
|
|
72
|
+
<div className={classes.field}>
|
|
73
|
+
<Input
|
|
74
|
+
label="Email Addresses"
|
|
75
|
+
fieldValue={notificationSettings?.email?.emails || defaultEmail}
|
|
76
|
+
onUpdate={(val) => updateNotificationEmails(val)}
|
|
77
|
+
placeholder="john@gmail.com, chris@icloud.com"
|
|
78
|
+
full={true}
|
|
79
|
+
inline={true}
|
|
80
|
+
required={true}
|
|
81
|
+
disabled={!hasConnectedIntegrations}
|
|
82
|
+
error={!notificationSettings?.email?.emails ? 'Required' : undefined}
|
|
83
|
+
/>
|
|
84
|
+
{!hasConnectedIntegrations && (
|
|
85
|
+
<div className={classes.fieldNotice}>
|
|
86
|
+
⚠️ SMTP has not been setup yet. Set it up in <NavLink to={`/settings?t=integration`}>Settings</NavLink> to enable Email
|
|
87
|
+
Notification.
|
|
88
|
+
</div>
|
|
89
|
+
)}
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
)}
|
|
34
93
|
</div>
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
94
|
+
|
|
95
|
+
{/* Slack Notification */}
|
|
96
|
+
<div className={classes.notificationSettingsSection}>
|
|
97
|
+
<div className={`${classes.field} ${classes.notificationToggle}`}>
|
|
98
|
+
<Icon type="slack" size={14} />
|
|
99
|
+
<Toggle
|
|
100
|
+
label="Enable Slack Notifications"
|
|
101
|
+
fieldValue={notificationSettings?.slack?.enabled || false}
|
|
102
|
+
onUpdate={(val: boolean) =>
|
|
103
|
+
onUpdate({
|
|
104
|
+
...notificationSettings,
|
|
105
|
+
slack: { ...notificationSettings.slack, enabled: val, url: notificationSettings.slack?.url || '' },
|
|
106
|
+
})
|
|
107
|
+
}
|
|
108
|
+
hint="Send notifications to a Slack channel via webhook"
|
|
109
|
+
inline={true}
|
|
110
|
+
/>
|
|
111
|
+
</div>
|
|
112
|
+
{notificationSettings?.slack?.enabled && (
|
|
113
|
+
<div className={classes.notificationSettings}>
|
|
114
|
+
{!isSync && (
|
|
115
|
+
<div className={classes.field}>
|
|
116
|
+
<Select
|
|
117
|
+
label="Send Slack Notification"
|
|
118
|
+
fieldValue={notificationSettings?.slack?.case || 'failure'}
|
|
119
|
+
options={caseOptions}
|
|
120
|
+
onUpdate={(val: string) =>
|
|
121
|
+
onUpdate({ ...notificationSettings, slack: { ...notificationSettings.slack, case: val as PlanNotificationCase } })
|
|
122
|
+
}
|
|
123
|
+
inline={true}
|
|
124
|
+
/>
|
|
125
|
+
</div>
|
|
126
|
+
)}
|
|
38
127
|
<div className={classes.field}>
|
|
39
|
-
<
|
|
40
|
-
|
|
41
|
-
fieldValue={notificationSettings?.
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
128
|
+
<Input
|
|
129
|
+
label="Slack Webhook URL"
|
|
130
|
+
fieldValue={notificationSettings?.slack?.url || ''}
|
|
131
|
+
onUpdate={(val) => onUpdate({ ...notificationSettings, slack: { ...notificationSettings.slack, url: val } })}
|
|
132
|
+
placeholder="https://hooks.slack.com/services/T00/B00/xxxx"
|
|
133
|
+
required={true}
|
|
134
|
+
inline={true}
|
|
135
|
+
full={true}
|
|
136
|
+
error={notificationSettings?.slack?.enabled && !notificationSettings?.slack?.url ? 'Required' : undefined}
|
|
137
|
+
/>
|
|
138
|
+
</div>
|
|
139
|
+
<div className={`${classes.field} ${classes.notificationTestField}`}>
|
|
140
|
+
<PlanNotificationSettingsTester
|
|
141
|
+
planId={planID || ''}
|
|
142
|
+
notificationChannel="slack"
|
|
143
|
+
notificationSettings={notificationSettings}
|
|
144
|
+
notificationType={notificationType}
|
|
52
145
|
/>
|
|
53
146
|
</div>
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
{
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
147
|
+
</div>
|
|
148
|
+
)}
|
|
149
|
+
</div>
|
|
150
|
+
|
|
151
|
+
{/* Discord Notification */}
|
|
152
|
+
<div className={classes.notificationSettingsSection}>
|
|
153
|
+
<div className={`${classes.field} ${classes.notificationToggle}`}>
|
|
154
|
+
<Icon type="discord" size={14} />
|
|
155
|
+
<Toggle
|
|
156
|
+
label="Send Discord Notifications"
|
|
157
|
+
fieldValue={notificationSettings?.discord?.enabled || false}
|
|
158
|
+
onUpdate={(val: boolean) =>
|
|
159
|
+
onUpdate({
|
|
160
|
+
...notificationSettings,
|
|
161
|
+
discord: { ...notificationSettings.discord, enabled: val, url: notificationSettings.discord?.url || '' },
|
|
162
|
+
})
|
|
163
|
+
}
|
|
164
|
+
hint="Send notifications to a Discord channel via webhook"
|
|
165
|
+
inline={true}
|
|
166
|
+
/>
|
|
167
|
+
</div>
|
|
168
|
+
{notificationSettings?.discord?.enabled && (
|
|
169
|
+
<div className={classes.notificationSettings}>
|
|
170
|
+
{!isSync && (
|
|
171
|
+
<div className={classes.field}>
|
|
172
|
+
<Select
|
|
173
|
+
label="Send Discord Notification"
|
|
174
|
+
fieldValue={notificationSettings?.discord?.case || 'failure'}
|
|
175
|
+
options={caseOptions}
|
|
176
|
+
onUpdate={(val: string) =>
|
|
177
|
+
onUpdate({ ...notificationSettings, discord: { ...notificationSettings.discord, case: val as PlanNotificationCase } })
|
|
178
|
+
}
|
|
179
|
+
inline={true}
|
|
180
|
+
/>
|
|
69
181
|
</div>
|
|
70
182
|
)}
|
|
183
|
+
<div className={classes.field}>
|
|
184
|
+
<Input
|
|
185
|
+
label="Discord Webhook URL"
|
|
186
|
+
fieldValue={notificationSettings?.discord?.url || ''}
|
|
187
|
+
onUpdate={(val) => onUpdate({ ...notificationSettings, discord: { ...notificationSettings.discord, url: val } })}
|
|
188
|
+
placeholder="https://discord.com/api/webhooks/xxxx/xxxx"
|
|
189
|
+
required={true}
|
|
190
|
+
inline={true}
|
|
191
|
+
full={true}
|
|
192
|
+
error={notificationSettings?.discord?.enabled && !notificationSettings?.discord?.url ? 'Required' : undefined}
|
|
193
|
+
/>
|
|
194
|
+
</div>
|
|
195
|
+
<div className={`${classes.field} ${classes.notificationTestField}`}>
|
|
196
|
+
<PlanNotificationSettingsTester
|
|
197
|
+
planId={planID || ''}
|
|
198
|
+
notificationChannel="discord"
|
|
199
|
+
notificationType={notificationType}
|
|
200
|
+
notificationSettings={notificationSettings}
|
|
201
|
+
/>
|
|
202
|
+
</div>
|
|
71
203
|
</div>
|
|
72
|
-
|
|
73
|
-
|
|
204
|
+
)}
|
|
205
|
+
</div>
|
|
74
206
|
</>
|
|
75
207
|
);
|
|
76
208
|
};
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { toast } from 'react-toastify';
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { useTestNotification } from '../../../services';
|
|
4
|
+
import { Icon } from '../..';
|
|
5
|
+
import { NewPlanSettings, PlanNotificationCase } from '../../..';
|
|
6
|
+
import classes from './PlanSettings.module.scss';
|
|
7
|
+
import { isValidURL } from '../../../utils';
|
|
8
|
+
|
|
9
|
+
const PlanNotificationSettingsTester = ({
|
|
10
|
+
planId,
|
|
11
|
+
notificationChannel,
|
|
12
|
+
notificationSettings,
|
|
13
|
+
notificationType = 'backup',
|
|
14
|
+
}: {
|
|
15
|
+
planId: string;
|
|
16
|
+
notificationType?: 'backup' | 'integrity';
|
|
17
|
+
notificationChannel: 'webhook' | 'slack' | 'discord';
|
|
18
|
+
notificationSettings: NewPlanSettings['settings']['notification'];
|
|
19
|
+
}) => {
|
|
20
|
+
const [showTestNotificationOptions, setShowTestNotificationOptions] = useState(false);
|
|
21
|
+
const testNotificationMutation = useTestNotification();
|
|
22
|
+
const channelSettings = notificationSettings[notificationChannel as 'webhook' | 'slack' | 'discord'];
|
|
23
|
+
const integrityNotification = notificationType === 'integrity';
|
|
24
|
+
|
|
25
|
+
const sendTestNotification = (notificationCase: PlanNotificationCase | 'integrity_failure') => {
|
|
26
|
+
setShowTestNotificationOptions(false);
|
|
27
|
+
if (!planId) {
|
|
28
|
+
return toast.error('Test notification can be sent after creating the Plan');
|
|
29
|
+
}
|
|
30
|
+
if (!channelSettings?.url || !isValidURL(channelSettings.url)) {
|
|
31
|
+
return toast.error(`Please enter a valid URL to send test notifications`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
toast.promise(
|
|
35
|
+
testNotificationMutation.mutateAsync({
|
|
36
|
+
planId,
|
|
37
|
+
notificationCase: notificationCase,
|
|
38
|
+
notificationChannel,
|
|
39
|
+
channelSettings: channelSettings,
|
|
40
|
+
}),
|
|
41
|
+
{
|
|
42
|
+
pending: `Sending Test Request to your ${notificationChannel}...`,
|
|
43
|
+
success: 'Test Request Sent Successfully!',
|
|
44
|
+
error: {
|
|
45
|
+
render({ data }: any) {
|
|
46
|
+
return data.message || 'Failed to send test request';
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
);
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<div className={classes.testNotificationContainer}>
|
|
55
|
+
<button
|
|
56
|
+
onClick={() =>
|
|
57
|
+
integrityNotification ? sendTestNotification('integrity_failure') : setShowTestNotificationOptions(!showTestNotificationOptions)
|
|
58
|
+
}
|
|
59
|
+
className={`${classes.testNotificationButton} ${!channelSettings?.url ? classes.disabled : ''}`}
|
|
60
|
+
disabled={!channelSettings?.url || !planId}
|
|
61
|
+
title={
|
|
62
|
+
!planId
|
|
63
|
+
? 'Create the Plan first to send test notifications'
|
|
64
|
+
: !channelSettings?.url
|
|
65
|
+
? `Enter ${notificationChannel === 'webhook' ? 'the' : notificationChannel.charAt(0).toUpperCase() + notificationChannel.slice(1)} Webhook URL to send test notifications`
|
|
66
|
+
: undefined
|
|
67
|
+
}
|
|
68
|
+
>
|
|
69
|
+
<Icon type="send" size={12} /> Send Test Request
|
|
70
|
+
</button>
|
|
71
|
+
{!integrityNotification && showTestNotificationOptions && (
|
|
72
|
+
<div className={classes.testNotificationOptions}>
|
|
73
|
+
<ul>
|
|
74
|
+
<li onClick={() => sendTestNotification('start')}>Start Notification</li>
|
|
75
|
+
<li onClick={() => sendTestNotification('end')}>End Notification</li>
|
|
76
|
+
<li onClick={() => sendTestNotification('success')}>Success Notification</li>
|
|
77
|
+
<li onClick={() => sendTestNotification('failure')}>Failure Notification</li>
|
|
78
|
+
</ul>
|
|
79
|
+
</div>
|
|
80
|
+
)}
|
|
81
|
+
</div>
|
|
82
|
+
);
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
export default PlanNotificationSettingsTester;
|
|
@@ -84,7 +84,7 @@ const PlanPruneSettings = ({ plan, onUpdate }: PlanPruneSettingsProps) => {
|
|
|
84
84
|
fieldValue={pruneSettings.keepWeeklySnaps || ''}
|
|
85
85
|
onUpdate={(val) => onUpdate({ ...pruneSettings, keepWeeklySnaps: val })}
|
|
86
86
|
/>
|
|
87
|
-
<span>
|
|
87
|
+
<span>Weeks</span>
|
|
88
88
|
</div>
|
|
89
89
|
<div className={classes.customPolicyOption}>
|
|
90
90
|
<span>Keep Monthly Backups for </span>
|
|
@@ -92,7 +92,7 @@ const PlanPruneSettings = ({ plan, onUpdate }: PlanPruneSettingsProps) => {
|
|
|
92
92
|
fieldValue={pruneSettings.keepMonthlySnaps || ''}
|
|
93
93
|
onUpdate={(val) => onUpdate({ ...pruneSettings, keepMonthlySnaps: val })}
|
|
94
94
|
/>
|
|
95
|
-
<span>
|
|
95
|
+
<span>Months</span>
|
|
96
96
|
</div>
|
|
97
97
|
</div>
|
|
98
98
|
</>
|
|
@@ -462,3 +462,74 @@
|
|
|
462
462
|
}
|
|
463
463
|
}
|
|
464
464
|
}
|
|
465
|
+
|
|
466
|
+
.notificationTestField {
|
|
467
|
+
display: flex;
|
|
468
|
+
justify-content: right;
|
|
469
|
+
margin-bottom: 0;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
.notificationSettingsSection {
|
|
473
|
+
border-radius: 6px;
|
|
474
|
+
border: 1px solid var(--line-color);
|
|
475
|
+
margin-bottom: 15px;
|
|
476
|
+
.notificationToggle {
|
|
477
|
+
margin-bottom: 0;
|
|
478
|
+
padding: 10px;
|
|
479
|
+
box-sizing: border-box;
|
|
480
|
+
display: flex;
|
|
481
|
+
align-items: center;
|
|
482
|
+
gap: 5px;
|
|
483
|
+
div[class*='_fieldInner'] {
|
|
484
|
+
justify-content: space-between;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
.notificationSettings {
|
|
488
|
+
padding: 15px;
|
|
489
|
+
border-top: 1px solid var(--line-color);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
.testNotificationContainer {
|
|
494
|
+
display: inline-block;
|
|
495
|
+
position: relative;
|
|
496
|
+
.testNotificationButton {
|
|
497
|
+
padding: 6px 12px;
|
|
498
|
+
background-color: var(--primary-color-light);
|
|
499
|
+
color: var(--primary-color);
|
|
500
|
+
font-weight: 600;
|
|
501
|
+
font-size: 0.9em;
|
|
502
|
+
border-radius: 4px;
|
|
503
|
+
cursor: pointer;
|
|
504
|
+
&.disabled {
|
|
505
|
+
cursor: not-allowed;
|
|
506
|
+
opacity: 0.6;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
.testNotificationOptions {
|
|
511
|
+
position: absolute;
|
|
512
|
+
width: 140px;
|
|
513
|
+
background-color: var(--content-background-color);
|
|
514
|
+
border-radius: 6px;
|
|
515
|
+
border: 1px solid var(--primary-color-mid);
|
|
516
|
+
bottom: 30px;
|
|
517
|
+
right: 0px;
|
|
518
|
+
z-index: 9;
|
|
519
|
+
ul {
|
|
520
|
+
list-style-type: none;
|
|
521
|
+
margin: 0;
|
|
522
|
+
padding: 0;
|
|
523
|
+
li {
|
|
524
|
+
font-size: 0.9em;
|
|
525
|
+
cursor: pointer;
|
|
526
|
+
padding: 6px 12px;
|
|
527
|
+
|
|
528
|
+
&:hover {
|
|
529
|
+
background-color: var(--primary-color-light);
|
|
530
|
+
color: var(--primary-color);
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
}
|
|
@@ -77,7 +77,12 @@ const GeneralSettings = ({ settings, settingsID, onUpdate }: GeneralSettingsProp
|
|
|
77
77
|
/>
|
|
78
78
|
</div>
|
|
79
79
|
<div className={classes.field}>
|
|
80
|
-
<Toggle
|
|
80
|
+
<Toggle
|
|
81
|
+
label="Enable Two-Factor Authentication (2FA)"
|
|
82
|
+
fieldValue={settings?.totp?.enabled || false}
|
|
83
|
+
onUpdate={(val) => update2FASetting(val)}
|
|
84
|
+
inline={false}
|
|
85
|
+
/>
|
|
81
86
|
</div>
|
|
82
87
|
{show2FASetupConfirm && (
|
|
83
88
|
<ActionModal
|
|
@@ -166,3 +166,77 @@
|
|
|
166
166
|
}
|
|
167
167
|
}
|
|
168
168
|
}
|
|
169
|
+
|
|
170
|
+
.oauthContainer {
|
|
171
|
+
background-color: var(--background-color);
|
|
172
|
+
border: 1px solid var(--line-color);
|
|
173
|
+
border-radius: 6px;
|
|
174
|
+
box-sizing: border-box;
|
|
175
|
+
padding: 12px;
|
|
176
|
+
word-break: break-all;
|
|
177
|
+
&.success {
|
|
178
|
+
background-color: var(--success-bg-color);
|
|
179
|
+
border-color: var(--success-bg-color);
|
|
180
|
+
color: var(--success-text-color);
|
|
181
|
+
}
|
|
182
|
+
&.error {
|
|
183
|
+
background-color: var(--error-bg-color);
|
|
184
|
+
border-color: var(--error-bg-color);
|
|
185
|
+
color: var(--error-text-color);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.oauthButton {
|
|
190
|
+
display: flex;
|
|
191
|
+
width: 100%;
|
|
192
|
+
flex-direction: column;
|
|
193
|
+
align-items: flex-end;
|
|
194
|
+
.oauthAuthorizeBtn {
|
|
195
|
+
display: flex;
|
|
196
|
+
align-items: center;
|
|
197
|
+
gap: 8px;
|
|
198
|
+
padding: 10px 18px;
|
|
199
|
+
font-size: 13px;
|
|
200
|
+
font-weight: 600;
|
|
201
|
+
color: #fff;
|
|
202
|
+
background-color: var(--primary-color);
|
|
203
|
+
border: none;
|
|
204
|
+
border-radius: 6px;
|
|
205
|
+
cursor: pointer;
|
|
206
|
+
transition: background-color 0.2s;
|
|
207
|
+
&:hover {
|
|
208
|
+
opacity: 0.9;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
.oauthInnerBtn {
|
|
214
|
+
display: inline-flex;
|
|
215
|
+
align-items: center;
|
|
216
|
+
gap: 6px;
|
|
217
|
+
padding: 6px 14px;
|
|
218
|
+
font-size: 12px;
|
|
219
|
+
font-weight: 500;
|
|
220
|
+
color: var(--content-text-color);
|
|
221
|
+
background-color: var(--content-background-color);
|
|
222
|
+
border: none;
|
|
223
|
+
border-radius: 6px;
|
|
224
|
+
cursor: pointer;
|
|
225
|
+
margin-top: 8px;
|
|
226
|
+
&:hover {
|
|
227
|
+
background-color: var(--primary-color);
|
|
228
|
+
color: var(--primary-text-color);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
.oauthProgress {
|
|
233
|
+
p {
|
|
234
|
+
margin: 6px 0;
|
|
235
|
+
font-size: 13px;
|
|
236
|
+
line-height: 1.5;
|
|
237
|
+
a {
|
|
238
|
+
color: var(--primary-color);
|
|
239
|
+
text-decoration: underline;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|