mirror of
https://github.com/Ravinou/borgwarehouse
synced 2026-03-14 14:25:46 +01:00
config: 🔧 update prettier format
This commit is contained in:
parent
e4d9484759
commit
83fe9a5355
93 changed files with 6446 additions and 7274 deletions
|
|
@ -1,21 +1,20 @@
|
|||
{
|
||||
"trailingComma": "es5",
|
||||
"tabWidth": 4,
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"arrowParens": "always",
|
||||
"bracketSpacing": true,
|
||||
"endOfLine": "lf",
|
||||
"htmlWhitespaceSensitivity": "css",
|
||||
"insertPragma": false,
|
||||
"singleAttributePerLine": false,
|
||||
"bracketSameLine": false,
|
||||
"jsxBracketSameLine": false,
|
||||
"jsxSingleQuote": true,
|
||||
"printWidth": 80,
|
||||
"proseWrap": "preserve",
|
||||
"quoteProps": "as-needed",
|
||||
"requirePragma": false,
|
||||
"useTabs": false,
|
||||
"embeddedLanguageFormatting": "auto"
|
||||
"trailingComma": "es5",
|
||||
"tabWidth": 2,
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"arrowParens": "always",
|
||||
"bracketSpacing": true,
|
||||
"endOfLine": "lf",
|
||||
"htmlWhitespaceSensitivity": "css",
|
||||
"insertPragma": false,
|
||||
"singleAttributePerLine": false,
|
||||
"bracketSameLine": false,
|
||||
"jsxSingleQuote": true,
|
||||
"printWidth": 100,
|
||||
"proseWrap": "preserve",
|
||||
"quoteProps": "as-needed",
|
||||
"requirePragma": false,
|
||||
"useTabs": false,
|
||||
"embeddedLanguageFormatting": "auto"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,73 +5,62 @@ import classes from './QuickCommands.module.css';
|
|||
import { IconSettingsAutomation, IconCopy } from '@tabler/icons-react';
|
||||
|
||||
export default function QuickCommands(props) {
|
||||
////Vars
|
||||
const wizardEnv = props.wizardEnv;
|
||||
//Needed to generate command for borg over LAN instead of WAN if env vars are set and option enabled.
|
||||
let FQDN;
|
||||
let SSH_SERVER_PORT;
|
||||
if (
|
||||
props.lanCommand &&
|
||||
wizardEnv.FQDN_LAN &&
|
||||
wizardEnv.SSH_SERVER_PORT_LAN
|
||||
) {
|
||||
FQDN = wizardEnv.FQDN_LAN;
|
||||
SSH_SERVER_PORT =
|
||||
wizardEnv.SSH_SERVER_PORT_LAN === 'false'
|
||||
? ''
|
||||
: ':' + wizardEnv.SSH_SERVER_PORT_LAN;
|
||||
} else {
|
||||
FQDN = wizardEnv.FQDN;
|
||||
SSH_SERVER_PORT =
|
||||
wizardEnv.SSH_SERVER_PORT === 'false'
|
||||
? ''
|
||||
: ':' + wizardEnv.SSH_SERVER_PORT;
|
||||
}
|
||||
////Vars
|
||||
const wizardEnv = props.wizardEnv;
|
||||
//Needed to generate command for borg over LAN instead of WAN if env vars are set and option enabled.
|
||||
let FQDN;
|
||||
let SSH_SERVER_PORT;
|
||||
if (props.lanCommand && wizardEnv.FQDN_LAN && wizardEnv.SSH_SERVER_PORT_LAN) {
|
||||
FQDN = wizardEnv.FQDN_LAN;
|
||||
SSH_SERVER_PORT =
|
||||
wizardEnv.SSH_SERVER_PORT_LAN === 'false' ? '' : ':' + wizardEnv.SSH_SERVER_PORT_LAN;
|
||||
} else {
|
||||
FQDN = wizardEnv.FQDN;
|
||||
SSH_SERVER_PORT = wizardEnv.SSH_SERVER_PORT === 'false' ? '' : ':' + wizardEnv.SSH_SERVER_PORT;
|
||||
}
|
||||
|
||||
//State
|
||||
const [isCopied, setIsCopied] = useState(false);
|
||||
//State
|
||||
const [isCopied, setIsCopied] = useState(false);
|
||||
|
||||
//Functions
|
||||
const handleCopy = async () => {
|
||||
// Asynchronously call copy to clipboard
|
||||
navigator.clipboard
|
||||
.writeText(
|
||||
`ssh://${wizardEnv.UNIX_USER}@${FQDN}${SSH_SERVER_PORT}/./${props.repositoryName}`
|
||||
)
|
||||
.then(() => {
|
||||
// If successful, update the isCopied state value
|
||||
setIsCopied(true);
|
||||
setTimeout(() => {
|
||||
setIsCopied(false);
|
||||
}, 1500);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
});
|
||||
};
|
||||
//Functions
|
||||
const handleCopy = async () => {
|
||||
// Asynchronously call copy to clipboard
|
||||
navigator.clipboard
|
||||
.writeText(`ssh://${wizardEnv.UNIX_USER}@${FQDN}${SSH_SERVER_PORT}/./${props.repositoryName}`)
|
||||
.then(() => {
|
||||
// If successful, update the isCopied state value
|
||||
setIsCopied(true);
|
||||
setTimeout(() => {
|
||||
setIsCopied(false);
|
||||
}, 1500);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={classes.container}>
|
||||
{isCopied ? (
|
||||
<div className={classes.copyValid}>Copied !</div>
|
||||
) : (
|
||||
<div className={classes.tooltip}>
|
||||
ssh://{wizardEnv.UNIX_USER}@{FQDN}
|
||||
{SSH_SERVER_PORT}/./
|
||||
{props.repositoryName}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{props.lanCommand && <div className={classes.lanBadge}>LAN</div>}
|
||||
|
||||
<div className={classes.icons}>
|
||||
<button onClick={handleCopy} className={classes.copyButton}>
|
||||
<IconCopy color='#65748b' stroke={1.25} />
|
||||
</button>
|
||||
<div className={classes.quickSetting}>
|
||||
<IconSettingsAutomation color='#65748b' stroke={1.25} />
|
||||
</div>
|
||||
</div>
|
||||
return (
|
||||
<div className={classes.container}>
|
||||
{isCopied ? (
|
||||
<div className={classes.copyValid}>Copied !</div>
|
||||
) : (
|
||||
<div className={classes.tooltip}>
|
||||
ssh://{wizardEnv.UNIX_USER}@{FQDN}
|
||||
{SSH_SERVER_PORT}/./
|
||||
{props.repositoryName}
|
||||
</div>
|
||||
);
|
||||
)}
|
||||
|
||||
{props.lanCommand && <div className={classes.lanBadge}>LAN</div>}
|
||||
|
||||
<div className={classes.icons}>
|
||||
<button onClick={handleCopy} className={classes.copyButton}>
|
||||
<IconCopy color='#65748b' stroke={1.25} />
|
||||
</button>
|
||||
<div className={classes.quickSetting}>
|
||||
<IconSettingsAutomation color='#65748b' stroke={1.25} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,116 +1,116 @@
|
|||
.container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
align-self: flex-start;
|
||||
margin: auto 47px auto auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
align-self: flex-start;
|
||||
margin: auto 47px auto auto;
|
||||
}
|
||||
|
||||
.icons {
|
||||
position: relative;
|
||||
bottom: 13px;
|
||||
position: relative;
|
||||
bottom: 13px;
|
||||
}
|
||||
|
||||
.quickSetting {
|
||||
position: absolute;
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
position: absolute;
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.lanBadge {
|
||||
border-radius: 5px;
|
||||
border: 1px solid #6d4aff;
|
||||
color: #6d4aff;
|
||||
font-size: 0.9em;
|
||||
padding: 2px 5px;
|
||||
margin-right: 8px;
|
||||
border-radius: 5px;
|
||||
border: 1px solid #6d4aff;
|
||||
color: #6d4aff;
|
||||
font-size: 0.9em;
|
||||
padding: 2px 5px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.tooltip {
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 1px solid #6d4aff21;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 0px 1px rgba(0, 0, 0, 0.1) inset;
|
||||
color: #65748b;
|
||||
font-size: 0.95rem;
|
||||
padding: 5px 5px;
|
||||
transition: 0.5s opacity;
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 1px solid #6d4aff21;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 0px 1px rgba(0, 0, 0, 0.1) inset;
|
||||
color: #65748b;
|
||||
font-size: 0.95rem;
|
||||
padding: 5px 5px;
|
||||
transition: 0.5s opacity;
|
||||
}
|
||||
|
||||
.copyButton {
|
||||
position: absolute;
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
border: none;
|
||||
background-color: none;
|
||||
position: absolute;
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
border: none;
|
||||
background-color: none;
|
||||
}
|
||||
|
||||
.copyValid {
|
||||
margin: auto 8px auto auto;
|
||||
font-size: 0.95rem;
|
||||
color: #6d4aff;
|
||||
animation: scale-in-center 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94) both;
|
||||
margin: auto 8px auto auto;
|
||||
font-size: 0.95rem;
|
||||
color: #6d4aff;
|
||||
animation: scale-in-center 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94) both;
|
||||
}
|
||||
|
||||
@keyframes scale-in-center {
|
||||
0% {
|
||||
-webkit-transform: scale(0);
|
||||
transform: scale(0);
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: scale(1);
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
0% {
|
||||
-webkit-transform: scale(0);
|
||||
transform: scale(0);
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: scale(1);
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* On Hover */
|
||||
|
||||
.container:hover .tooltip {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 1px solid #6d4aff21;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 0px 1px rgba(0, 0, 0, 0.1) inset;
|
||||
color: #65748b;
|
||||
font-size: 0.95rem;
|
||||
padding: 5px 5px;
|
||||
transition: 0.5s opacity;
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 1px solid #6d4aff21;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 0px 1px rgba(0, 0, 0, 0.1) inset;
|
||||
color: #65748b;
|
||||
font-size: 0.95rem;
|
||||
padding: 5px 5px;
|
||||
transition: 0.5s opacity;
|
||||
}
|
||||
|
||||
.container:hover .copyButton {
|
||||
position: absolute;
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.container:hover .quickSetting {
|
||||
position: absolute;
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.container:hover .lanBadge {
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
@media all and (max-width: 1000px) {
|
||||
.container {
|
||||
display: none;
|
||||
}
|
||||
.container {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,216 +2,179 @@
|
|||
import { useState } from 'react';
|
||||
import classes from './Repo.module.css';
|
||||
import {
|
||||
IconSettings,
|
||||
IconInfoCircle,
|
||||
IconChevronDown,
|
||||
IconChevronUp,
|
||||
IconBellOff,
|
||||
IconLockPlus,
|
||||
IconSettings,
|
||||
IconInfoCircle,
|
||||
IconChevronDown,
|
||||
IconChevronUp,
|
||||
IconBellOff,
|
||||
IconLockPlus,
|
||||
} from '@tabler/icons-react';
|
||||
import timestampConverter from '../../helpers/functions/timestampConverter';
|
||||
import StorageBar from '../UI/StorageBar/StorageBar';
|
||||
import QuickCommands from './QuickCommands/QuickCommands';
|
||||
|
||||
export default function Repo(props) {
|
||||
//Load displayDetails from LocalStorage
|
||||
const displayDetailsFromLS = () => {
|
||||
try {
|
||||
if (
|
||||
localStorage.getItem('displayDetailsRepo' + props.id) === null
|
||||
) {
|
||||
localStorage.setItem(
|
||||
'displayDetailsRepo' + props.id,
|
||||
JSON.stringify(true)
|
||||
);
|
||||
return true;
|
||||
} else {
|
||||
return JSON.parse(
|
||||
localStorage.getItem('displayDetailsRepo' + props.id)
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(
|
||||
'LocalStorage error, key',
|
||||
'displayDetailsRepo' + props.id,
|
||||
'will be removed. Try again.',
|
||||
'Error message on this key : ',
|
||||
error
|
||||
);
|
||||
localStorage.removeItem('displayDetailsRepo' + props.id);
|
||||
}
|
||||
};
|
||||
//Load displayDetails from LocalStorage
|
||||
const displayDetailsFromLS = () => {
|
||||
try {
|
||||
if (localStorage.getItem('displayDetailsRepo' + props.id) === null) {
|
||||
localStorage.setItem('displayDetailsRepo' + props.id, JSON.stringify(true));
|
||||
return true;
|
||||
} else {
|
||||
return JSON.parse(localStorage.getItem('displayDetailsRepo' + props.id));
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(
|
||||
'LocalStorage error, key',
|
||||
'displayDetailsRepo' + props.id,
|
||||
'will be removed. Try again.',
|
||||
'Error message on this key : ',
|
||||
error
|
||||
);
|
||||
localStorage.removeItem('displayDetailsRepo' + props.id);
|
||||
}
|
||||
};
|
||||
|
||||
//States
|
||||
const [displayDetails, setDisplayDetails] = useState(displayDetailsFromLS);
|
||||
//States
|
||||
const [displayDetails, setDisplayDetails] = useState(displayDetailsFromLS);
|
||||
|
||||
//BUTTON : Display or not repo details for ONE repo
|
||||
const displayDetailsForOneHandler = (boolean) => {
|
||||
//Update localStorage
|
||||
localStorage.setItem(
|
||||
'displayDetailsRepo' + props.id,
|
||||
JSON.stringify(boolean)
|
||||
);
|
||||
setDisplayDetails(boolean);
|
||||
};
|
||||
//BUTTON : Display or not repo details for ONE repo
|
||||
const displayDetailsForOneHandler = (boolean) => {
|
||||
//Update localStorage
|
||||
localStorage.setItem('displayDetailsRepo' + props.id, JSON.stringify(boolean));
|
||||
setDisplayDetails(boolean);
|
||||
};
|
||||
|
||||
//Status indicator
|
||||
const statusIndicator = () => {
|
||||
return props.status
|
||||
? classes.statusIndicatorGreen
|
||||
: classes.statusIndicatorRed;
|
||||
};
|
||||
//Status indicator
|
||||
const statusIndicator = () => {
|
||||
return props.status ? classes.statusIndicatorGreen : classes.statusIndicatorRed;
|
||||
};
|
||||
|
||||
//Alert indicator
|
||||
const alertIndicator = () => {
|
||||
if (props.alert === 0) {
|
||||
return (
|
||||
<div className={classes.alertIcon}>
|
||||
<IconBellOff size={16} color='grey' />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
//Alert indicator
|
||||
const alertIndicator = () => {
|
||||
if (props.alert === 0) {
|
||||
return (
|
||||
<div className={classes.alertIcon}>
|
||||
<IconBellOff size={16} color='grey' />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const appendOnlyModeIndicator = () => {
|
||||
if (props.appendOnlyMode) {
|
||||
return (
|
||||
<div className={classes.appendOnlyModeIcon}>
|
||||
<IconLockPlus size={16} color='grey' />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
const appendOnlyModeIndicator = () => {
|
||||
if (props.appendOnlyMode) {
|
||||
return (
|
||||
<div className={classes.appendOnlyModeIcon}>
|
||||
<IconLockPlus size={16} color='grey' />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
return (
|
||||
<>
|
||||
{displayDetails ? (
|
||||
<>
|
||||
{displayDetails ? (
|
||||
<>
|
||||
<div className={classes.RepoOpen}>
|
||||
<div className={classes.openFlex}>
|
||||
<div className={statusIndicator()} />
|
||||
<div className={classes.alias}>{props.alias}</div>
|
||||
{appendOnlyModeIndicator()}
|
||||
{alertIndicator()}
|
||||
{props.comment && (
|
||||
<div className={classes.comment}>
|
||||
<IconInfoCircle size={16} color='grey' />
|
||||
<div className={classes.toolTip}>
|
||||
{props.comment}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<QuickCommands
|
||||
repositoryName={props.repositoryName}
|
||||
lanCommand={props.lanCommand}
|
||||
wizardEnv={props.wizardEnv}
|
||||
/>
|
||||
</div>
|
||||
<div className={classes.RepoOpen}>
|
||||
<div className={classes.openFlex}>
|
||||
<div className={statusIndicator()} />
|
||||
<div className={classes.alias}>{props.alias}</div>
|
||||
{appendOnlyModeIndicator()}
|
||||
{alertIndicator()}
|
||||
{props.comment && (
|
||||
<div className={classes.comment}>
|
||||
<IconInfoCircle size={16} color='grey' />
|
||||
<div className={classes.toolTip}>{props.comment}</div>
|
||||
</div>
|
||||
)}
|
||||
<QuickCommands
|
||||
repositoryName={props.repositoryName}
|
||||
lanCommand={props.lanCommand}
|
||||
wizardEnv={props.wizardEnv}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<table className={classes.tabInfo}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style={{ width: '15%' }}>Repository</th>
|
||||
<th style={{ width: '10%' }}>
|
||||
Storage Size
|
||||
</th>
|
||||
<th style={{ width: '30%' }}>
|
||||
Storage Used
|
||||
</th>
|
||||
<th style={{ width: '15%' }}>
|
||||
Last change
|
||||
</th>
|
||||
<th style={{ width: '5%' }}>ID</th>
|
||||
<th style={{ width: '5%' }}>Edit</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>{props.repositoryName}</th>
|
||||
<th>{props.storageSize} GB</th>
|
||||
<th style={{ padding: '0 4% 0 4%' }}>
|
||||
<StorageBar
|
||||
storageUsed={props.storageUsed}
|
||||
storageSize={props.storageSize}
|
||||
/>
|
||||
</th>
|
||||
<th>
|
||||
<div className={classes.lastSave}>
|
||||
{props.lastSave === 0
|
||||
? '-'
|
||||
: timestampConverter(
|
||||
props.lastSave
|
||||
)}
|
||||
</div>
|
||||
</th>
|
||||
<th>#{props.id}</th>
|
||||
<th>
|
||||
<div className={classes.editButton}>
|
||||
<IconSettings
|
||||
width={24}
|
||||
color='#6d4aff'
|
||||
onClick={() =>
|
||||
props.repoManageEditHandler()
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<table className={classes.tabInfo}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style={{ width: '15%' }}>Repository</th>
|
||||
<th style={{ width: '10%' }}>Storage Size</th>
|
||||
<th style={{ width: '30%' }}>Storage Used</th>
|
||||
<th style={{ width: '15%' }}>Last change</th>
|
||||
<th style={{ width: '5%' }}>ID</th>
|
||||
<th style={{ width: '5%' }}>Edit</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>{props.repositoryName}</th>
|
||||
<th>{props.storageSize} GB</th>
|
||||
<th style={{ padding: '0 4% 0 4%' }}>
|
||||
<StorageBar storageUsed={props.storageUsed} storageSize={props.storageSize} />
|
||||
</th>
|
||||
<th>
|
||||
<div className={classes.lastSave}>
|
||||
{props.lastSave === 0 ? '-' : timestampConverter(props.lastSave)}
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div className={classes.RepoClose}>
|
||||
<div className={classes.closeFlex}>
|
||||
<div className={statusIndicator()} />
|
||||
<div className={classes.alias}>{props.alias}</div>
|
||||
{appendOnlyModeIndicator()}
|
||||
{alertIndicator()}
|
||||
{props.comment && (
|
||||
<div className={classes.comment}>
|
||||
<IconInfoCircle size={16} color='#637381' />
|
||||
<div className={classes.toolTip}>
|
||||
{props.comment}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className={classes.lastSave}>
|
||||
{props.lastSave === 0
|
||||
? null
|
||||
: timestampConverter(props.lastSave)}
|
||||
<span
|
||||
style={{ marginLeft: '20px', color: '#637381' }}
|
||||
>
|
||||
#{props.id}
|
||||
</span>
|
||||
</div>
|
||||
</th>
|
||||
<th>#{props.id}</th>
|
||||
<th>
|
||||
<div className={classes.editButton}>
|
||||
<IconSettings
|
||||
width={24}
|
||||
color='#6d4aff'
|
||||
onClick={() => props.repoManageEditHandler()}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{displayDetails ? (
|
||||
<div className={classes.chevron}>
|
||||
<IconChevronUp
|
||||
color='#494b7a'
|
||||
size={28}
|
||||
onClick={() => {
|
||||
displayDetailsForOneHandler(false);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className={classes.chevron}>
|
||||
<IconChevronDown
|
||||
color='#494b7a'
|
||||
size={28}
|
||||
onClick={() => {
|
||||
displayDetailsForOneHandler(true);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</th>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
) : (
|
||||
<>
|
||||
<div className={classes.RepoClose}>
|
||||
<div className={classes.closeFlex}>
|
||||
<div className={statusIndicator()} />
|
||||
<div className={classes.alias}>{props.alias}</div>
|
||||
{appendOnlyModeIndicator()}
|
||||
{alertIndicator()}
|
||||
{props.comment && (
|
||||
<div className={classes.comment}>
|
||||
<IconInfoCircle size={16} color='#637381' />
|
||||
<div className={classes.toolTip}>{props.comment}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className={classes.lastSave}>
|
||||
{props.lastSave === 0 ? null : timestampConverter(props.lastSave)}
|
||||
<span style={{ marginLeft: '20px', color: '#637381' }}>#{props.id}</span>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{displayDetails ? (
|
||||
<div className={classes.chevron}>
|
||||
<IconChevronUp
|
||||
color='#494b7a'
|
||||
size={28}
|
||||
onClick={() => {
|
||||
displayDetailsForOneHandler(false);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className={classes.chevron}>
|
||||
<IconChevronDown
|
||||
color='#494b7a'
|
||||
size={28}
|
||||
onClick={() => {
|
||||
displayDetailsForOneHandler(true);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,250 +1,249 @@
|
|||
/*Repo CLOSE*/
|
||||
.RepoClose {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
box-shadow:
|
||||
0 1px 3px rgba(0, 0, 0, 0.12),
|
||||
0 1px 2px rgba(0, 0, 0, 0.24);
|
||||
width: auto;
|
||||
max-height: 65px;
|
||||
margin: 20px 0px 0px 0px;
|
||||
border-radius: 5px;
|
||||
overflow: visible;
|
||||
/* Need to display comment on hover (which is position : absolute) */
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
box-shadow:
|
||||
0 1px 3px rgba(0, 0, 0, 0.12),
|
||||
0 1px 2px rgba(0, 0, 0, 0.24);
|
||||
width: auto;
|
||||
max-height: 65px;
|
||||
margin: 20px 0px 0px 0px;
|
||||
border-radius: 5px;
|
||||
overflow: visible;
|
||||
/* Need to display comment on hover (which is position : absolute) */
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.closeFlex {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 15px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.RepoClose .lastSave {
|
||||
padding: 15px;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
/* REPO OPEN */
|
||||
.RepoOpen {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
box-shadow:
|
||||
0 1px 3px rgba(0, 0, 0, 0.12),
|
||||
0 1px 2px rgba(0, 0, 0, 0.24);
|
||||
width: auto;
|
||||
max-height: 200px;
|
||||
margin: 20px 0px 0px 0px;
|
||||
padding: 15px;
|
||||
border-radius: 5px;
|
||||
transition: max-height 0.1s linear;
|
||||
overflow: visible;
|
||||
/* Need to display comment on hover (which is position : absolute) */
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
box-shadow:
|
||||
0 1px 3px rgba(0, 0, 0, 0.12),
|
||||
0 1px 2px rgba(0, 0, 0, 0.24);
|
||||
width: auto;
|
||||
max-height: 200px;
|
||||
margin: 20px 0px 0px 0px;
|
||||
padding: 15px;
|
||||
border-radius: 5px;
|
||||
transition: max-height 0.1s linear;
|
||||
overflow: visible;
|
||||
/* Need to display comment on hover (which is position : absolute) */
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.openFlex {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
align-self: flex-start;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
align-self: flex-start;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.tabInfo {
|
||||
width: 100%;
|
||||
overflow-wrap: break-word;
|
||||
border-collapse: collapse;
|
||||
background: #fff;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
margin: 25px auto;
|
||||
table-layout: fixed;
|
||||
width: 100%;
|
||||
overflow-wrap: break-word;
|
||||
border-collapse: collapse;
|
||||
background: #fff;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
margin: 25px auto;
|
||||
table-layout: fixed;
|
||||
}
|
||||
|
||||
.tabInfo thead tr {
|
||||
height: 50px;
|
||||
background: #111827;
|
||||
color: #fff;
|
||||
height: 50px;
|
||||
background: #111827;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.tabInfo thead th {
|
||||
font-size: 1em;
|
||||
color: #fff;
|
||||
line-height: 1.2;
|
||||
font-weight: normal;
|
||||
font-size: 1em;
|
||||
color: #fff;
|
||||
line-height: 1.2;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.tabInfo tbody tr {
|
||||
background-color: #f3f4f6;
|
||||
height: 50px;
|
||||
background-color: #f3f4f6;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.tabInfo tbody tr th {
|
||||
color: #65748b;
|
||||
font-size: 0.95rem;
|
||||
font-weight: 400;
|
||||
color: #65748b;
|
||||
font-size: 0.95rem;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
/*STATUS*/
|
||||
|
||||
.statusIndicatorGreen {
|
||||
background: rgb(9, 255, 0);
|
||||
border-radius: 50%;
|
||||
margin: 10px;
|
||||
height: 15px;
|
||||
width: 15px;
|
||||
box-shadow: 0 0 0 0 rgb(9, 255, 0);
|
||||
transform: scale(1);
|
||||
animation: pulseGreen 5s infinite;
|
||||
animation-delay: 1s;
|
||||
background: rgb(9, 255, 0);
|
||||
border-radius: 50%;
|
||||
margin: 10px;
|
||||
height: 15px;
|
||||
width: 15px;
|
||||
box-shadow: 0 0 0 0 rgb(9, 255, 0);
|
||||
transform: scale(1);
|
||||
animation: pulseGreen 5s infinite;
|
||||
animation-delay: 1s;
|
||||
}
|
||||
|
||||
@keyframes pulseGreen {
|
||||
0% {
|
||||
transform: scale(0.95);
|
||||
box-shadow: 0 0 0 0 rgba(17, 255, 0, 0.7);
|
||||
}
|
||||
0% {
|
||||
transform: scale(0.95);
|
||||
box-shadow: 0 0 0 0 rgba(17, 255, 0, 0.7);
|
||||
}
|
||||
|
||||
10% {
|
||||
transform: scale(1);
|
||||
box-shadow: 0 0 0 10px rgba(17, 255, 0, 0);
|
||||
}
|
||||
10% {
|
||||
transform: scale(1);
|
||||
box-shadow: 0 0 0 10px rgba(17, 255, 0, 0);
|
||||
}
|
||||
|
||||
90% {
|
||||
transform: scale(0.95);
|
||||
box-shadow: 0 0 0 0 rgba(17, 255, 0, 0);
|
||||
}
|
||||
90% {
|
||||
transform: scale(0.95);
|
||||
box-shadow: 0 0 0 0 rgba(17, 255, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
.statusIndicatorRed {
|
||||
background: rgb(255, 0, 0);
|
||||
border-radius: 50%;
|
||||
margin: 10px;
|
||||
height: 15px;
|
||||
width: 15px;
|
||||
background: rgb(255, 0, 0);
|
||||
border-radius: 50%;
|
||||
margin: 10px;
|
||||
height: 15px;
|
||||
width: 15px;
|
||||
|
||||
box-shadow: 0 0 0 0 rgb(255, 0, 0);
|
||||
transform: scale(1);
|
||||
animation: pulseRed 5s infinite;
|
||||
animation-delay: 0.5s;
|
||||
box-shadow: 0 0 0 0 rgb(255, 0, 0);
|
||||
transform: scale(1);
|
||||
animation: pulseRed 5s infinite;
|
||||
animation-delay: 0.5s;
|
||||
}
|
||||
|
||||
@keyframes pulseRed {
|
||||
0% {
|
||||
transform: scale(0.95);
|
||||
box-shadow: 0 0 0 0 rgba(255, 0, 0, 0.7);
|
||||
}
|
||||
0% {
|
||||
transform: scale(0.95);
|
||||
box-shadow: 0 0 0 0 rgba(255, 0, 0, 0.7);
|
||||
}
|
||||
|
||||
10% {
|
||||
transform: scale(1);
|
||||
box-shadow: 0 0 0 10px rgba(255, 0, 0, 0);
|
||||
}
|
||||
10% {
|
||||
transform: scale(1);
|
||||
box-shadow: 0 0 0 10px rgba(255, 0, 0, 0);
|
||||
}
|
||||
|
||||
90% {
|
||||
transform: scale(0.95);
|
||||
box-shadow: 0 0 0 0 rgba(255, 0, 0, 0);
|
||||
}
|
||||
90% {
|
||||
transform: scale(0.95);
|
||||
box-shadow: 0 0 0 0 rgba(255, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/* Alert icon */
|
||||
|
||||
.alertIcon {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
margin-left: 10px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.appendOnlyModeIcon {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
margin-left: 10px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
/* GENERAL */
|
||||
.alias {
|
||||
font-weight: bold;
|
||||
color: #111827;
|
||||
font-size: 1.05em;
|
||||
font-weight: bold;
|
||||
color: #111827;
|
||||
font-size: 1.05em;
|
||||
}
|
||||
|
||||
.lastSave {
|
||||
color: #65748b;
|
||||
color: #65748b;
|
||||
}
|
||||
|
||||
.editButton {
|
||||
cursor: pointer;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Comment */
|
||||
.comment {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
margin-left: 10px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.toolTip {
|
||||
visibility: hidden;
|
||||
width: auto;
|
||||
height: auto;
|
||||
max-width: 400px;
|
||||
max-height: 250px;
|
||||
background-color: #fff;
|
||||
color: #637381;
|
||||
text-align: center;
|
||||
border-radius: 6px;
|
||||
padding: 5px 5px;
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
margin: 0px 0 0 20px;
|
||||
opacity: 1;
|
||||
transition: 0.5s opacity;
|
||||
box-shadow:
|
||||
0 3px 6px rgba(0, 0, 0, 0.16),
|
||||
0 3px 6px rgba(0, 0, 0, 0.23);
|
||||
overflow: auto;
|
||||
visibility: hidden;
|
||||
width: auto;
|
||||
height: auto;
|
||||
max-width: 400px;
|
||||
max-height: 250px;
|
||||
background-color: #fff;
|
||||
color: #637381;
|
||||
text-align: center;
|
||||
border-radius: 6px;
|
||||
padding: 5px 5px;
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
margin: 0px 0 0 20px;
|
||||
opacity: 1;
|
||||
transition: 0.5s opacity;
|
||||
box-shadow:
|
||||
0 3px 6px rgba(0, 0, 0, 0.16),
|
||||
0 3px 6px rgba(0, 0, 0, 0.23);
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.comment:hover .toolTip,
|
||||
.comment:active .toolTip {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.chevron {
|
||||
margin: auto;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.chevron :focus,
|
||||
.chevron :hover {
|
||||
cursor: pointer;
|
||||
filter: invert(27%) sepia(82%) saturate(2209%) hue-rotate(240deg)
|
||||
brightness(99%) contrast(105%);
|
||||
cursor: pointer;
|
||||
filter: invert(27%) sepia(82%) saturate(2209%) hue-rotate(240deg) brightness(99%) contrast(105%);
|
||||
}
|
||||
|
||||
/* MOBILE */
|
||||
@media all and (max-width: 1000px) {
|
||||
.tabInfo {
|
||||
display: none;
|
||||
}
|
||||
.toolTip {
|
||||
display: none;
|
||||
}
|
||||
.comment {
|
||||
display: none;
|
||||
}
|
||||
.lastSave {
|
||||
display: none;
|
||||
}
|
||||
.closeFlex {
|
||||
margin: auto;
|
||||
}
|
||||
.openFlex {
|
||||
margin: auto;
|
||||
width: auto;
|
||||
}
|
||||
.tabInfo {
|
||||
display: none;
|
||||
}
|
||||
.toolTip {
|
||||
display: none;
|
||||
}
|
||||
.comment {
|
||||
display: none;
|
||||
}
|
||||
.lastSave {
|
||||
display: none;
|
||||
}
|
||||
.closeFlex {
|
||||
margin: auto;
|
||||
}
|
||||
.openFlex {
|
||||
margin: auto;
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,51 +4,38 @@ import { useState } from 'react';
|
|||
import { IconChecks, IconCopy } from '@tabler/icons-react';
|
||||
|
||||
export default function CopyButton(props) {
|
||||
//State
|
||||
const [isCopied, setIsCopied] = useState(false);
|
||||
//State
|
||||
const [isCopied, setIsCopied] = useState(false);
|
||||
|
||||
//Function
|
||||
const handleCopy = async (data) => {
|
||||
navigator.clipboard
|
||||
.writeText(data)
|
||||
.then(() => {
|
||||
// If successful, update the isCopied state value
|
||||
setIsCopied(true);
|
||||
setTimeout(() => {
|
||||
setIsCopied(false);
|
||||
}, 1500);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
});
|
||||
};
|
||||
//Function
|
||||
const handleCopy = async (data) => {
|
||||
navigator.clipboard
|
||||
.writeText(data)
|
||||
.then(() => {
|
||||
// If successful, update the isCopied state value
|
||||
setIsCopied(true);
|
||||
setTimeout(() => {
|
||||
setIsCopied(false);
|
||||
}, 1500);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
className={classes.copyButton}
|
||||
onClick={() => handleCopy(props.dataToCopy)}
|
||||
>
|
||||
{props.children}
|
||||
{isCopied && props.displayIconConfirmation ? (
|
||||
<IconChecks
|
||||
color='#07bc0c'
|
||||
stroke={props.stroke || 1.25}
|
||||
size={props.size}
|
||||
/>
|
||||
) : (
|
||||
<IconCopy
|
||||
color='#65748b'
|
||||
stroke={props.stroke || 1.25}
|
||||
size={props.size}
|
||||
/>
|
||||
)}
|
||||
</button>
|
||||
{isCopied
|
||||
? !props.displayIconConfirmation && (
|
||||
<span className={classes.copyValid}>Copied !</span>
|
||||
)
|
||||
: null}
|
||||
</>
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<button className={classes.copyButton} onClick={() => handleCopy(props.dataToCopy)}>
|
||||
{props.children}
|
||||
{isCopied && props.displayIconConfirmation ? (
|
||||
<IconChecks color='#07bc0c' stroke={props.stroke || 1.25} size={props.size} />
|
||||
) : (
|
||||
<IconCopy color='#65748b' stroke={props.stroke || 1.25} size={props.size} />
|
||||
)}
|
||||
</button>
|
||||
{isCopied
|
||||
? !props.displayIconConfirmation && <span className={classes.copyValid}>Copied !</span>
|
||||
: null}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,35 +1,35 @@
|
|||
.copyButton {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.copyButton span {
|
||||
font-size: 0.95rem;
|
||||
color: #6d4aff;
|
||||
margin-right: 5px;
|
||||
user-select: text;
|
||||
font-size: 0.95rem;
|
||||
color: #6d4aff;
|
||||
margin-right: 5px;
|
||||
user-select: text;
|
||||
}
|
||||
|
||||
.copyValid {
|
||||
font-size: 0.95rem;
|
||||
color: #6d4aff;
|
||||
animation: scale-in-center 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94) both;
|
||||
font-size: 0.95rem;
|
||||
color: #6d4aff;
|
||||
animation: scale-in-center 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94) both;
|
||||
}
|
||||
|
||||
@keyframes scale-in-center {
|
||||
0% {
|
||||
-webkit-transform: scale(0);
|
||||
transform: scale(0);
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: scale(1);
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
0% {
|
||||
-webkit-transform: scale(0);
|
||||
transform: scale(0);
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: scale(1);
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,5 +2,5 @@
|
|||
import classes from './Error.module.css';
|
||||
|
||||
export default function Error(props) {
|
||||
return <div className={classes.errorMessage}>{props.message}</div>;
|
||||
return <div className={classes.errorMessage}>{props.message}</div>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,18 @@
|
|||
.errorMessage {
|
||||
margin: 15px 0px;
|
||||
background-color: red;
|
||||
color: white;
|
||||
padding: 15px;
|
||||
border-radius: 5px;
|
||||
animation: myAnim 1s ease 0s 1 normal forwards;
|
||||
margin: 15px 0px;
|
||||
background-color: red;
|
||||
color: white;
|
||||
padding: 15px;
|
||||
border-radius: 5px;
|
||||
animation: myAnim 1s ease 0s 1 normal forwards;
|
||||
}
|
||||
|
||||
@keyframes myAnim {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,13 +2,10 @@
|
|||
import classes from './Info.module.css';
|
||||
|
||||
export default function Info(props) {
|
||||
return (
|
||||
<div
|
||||
className={classes.infoMessage}
|
||||
style={{ backgroundColor: props.color }}
|
||||
>
|
||||
{props.message}
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className={classes.infoMessage} style={{ backgroundColor: props.color }}>
|
||||
{props.message}
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,18 @@
|
|||
.infoMessage {
|
||||
margin: 15px 0px;
|
||||
background-color: rgb(17, 147, 0);
|
||||
color: white;
|
||||
padding: 15px;
|
||||
border-radius: 5px;
|
||||
animation: myAnim 1s ease 0s 1 normal forwards;
|
||||
margin: 15px 0px;
|
||||
background-color: rgb(17, 147, 0);
|
||||
color: white;
|
||||
padding: 15px;
|
||||
border-radius: 5px;
|
||||
animation: myAnim 1s ease 0s 1 normal forwards;
|
||||
}
|
||||
|
||||
@keyframes myAnim {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,22 +3,22 @@ import classes from './Footer.module.css';
|
|||
import packageInfo from '../../../../package.json';
|
||||
|
||||
function Footer() {
|
||||
return (
|
||||
<div className={classes.footer}>
|
||||
<p>
|
||||
About{' '}
|
||||
<a
|
||||
className={classes.site}
|
||||
target='_blank'
|
||||
href='https://borgwarehouse.com/'
|
||||
rel='noreferrer'
|
||||
>
|
||||
BorgWarehouse
|
||||
</a>{' '}
|
||||
- v{packageInfo.version}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className={classes.footer}>
|
||||
<p>
|
||||
About{' '}
|
||||
<a
|
||||
className={classes.site}
|
||||
target='_blank'
|
||||
href='https://borgwarehouse.com/'
|
||||
rel='noreferrer'
|
||||
>
|
||||
BorgWarehouse
|
||||
</a>{' '}
|
||||
- v{packageInfo.version}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Footer;
|
||||
|
|
|
|||
|
|
@ -1,26 +1,26 @@
|
|||
.footer {
|
||||
color: #494b7a;
|
||||
text-align: center;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
color: #494b7a;
|
||||
text-align: center;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.footer p {
|
||||
padding-left: 70px;
|
||||
padding-left: 70px;
|
||||
}
|
||||
|
||||
a.site {
|
||||
color: #6d4aff;
|
||||
text-decoration: none;
|
||||
color: #6d4aff;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
@media all and (max-width: 1000px) {
|
||||
.footer {
|
||||
width: 100%;
|
||||
}
|
||||
.footer p {
|
||||
padding-left: 0;
|
||||
}
|
||||
.footer {
|
||||
width: 100%;
|
||||
}
|
||||
.footer p {
|
||||
padding-left: 0;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,17 +5,17 @@ import classes from './Header.module.css';
|
|||
import Nav from './Nav/Nav';
|
||||
|
||||
function Header() {
|
||||
return (
|
||||
<header className={classes.Header}>
|
||||
<div className={[classes.flex, 'container'].join(' ')}>
|
||||
<div className={classes.logo}>BorgWarehouse</div>
|
||||
return (
|
||||
<header className={classes.Header}>
|
||||
<div className={[classes.flex, 'container'].join(' ')}>
|
||||
<div className={classes.logo}>BorgWarehouse</div>
|
||||
|
||||
<nav>
|
||||
<Nav />
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
<nav>
|
||||
<Nav />
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
|
||||
export default Header;
|
||||
|
|
|
|||
|
|
@ -1,31 +1,31 @@
|
|||
.Header {
|
||||
width: 100%;
|
||||
background: #111827;
|
||||
box-shadow:
|
||||
0 3px 6px rgba(0, 0, 0, 0.16),
|
||||
0 3px 6px rgba(0, 0, 0, 0.23);
|
||||
height: 50px;
|
||||
color: white;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: static;
|
||||
top: 0;
|
||||
z-index: 1000;
|
||||
width: 100%;
|
||||
background: #111827;
|
||||
box-shadow:
|
||||
0 3px 6px rgba(0, 0, 0, 0.16),
|
||||
0 3px 6px rgba(0, 0, 0, 0.23);
|
||||
height: 50px;
|
||||
color: white;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: static;
|
||||
top: 0;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.flex {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
max-width: 1500px;
|
||||
margin: auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
max-width: 1500px;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.logo {
|
||||
font-size: 1.5em;
|
||||
font-weight: bold;
|
||||
color: #6d4aff;
|
||||
text-shadow: #6d4aff 0px 0px 18px;
|
||||
margin-left: 20px;
|
||||
font-size: 1.5em;
|
||||
font-weight: bold;
|
||||
color: #6d4aff;
|
||||
text-shadow: #6d4aff 0px 0px 18px;
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,52 +6,42 @@ import { useRouter } from 'next/router';
|
|||
import { useSession, signOut } from 'next-auth/react';
|
||||
|
||||
export default function Nav() {
|
||||
////Var
|
||||
//Get the current route to light the right Item
|
||||
const router = useRouter();
|
||||
const currentRoute = router.pathname;
|
||||
const { status, data } = useSession();
|
||||
////Var
|
||||
//Get the current route to light the right Item
|
||||
const router = useRouter();
|
||||
const currentRoute = router.pathname;
|
||||
const { status, data } = useSession();
|
||||
|
||||
//Function
|
||||
const onLogoutClickedHandler = async () => {
|
||||
//This bug is open : https://github.com/nextauthjs/next-auth/issues/1542
|
||||
//I put redirect to false and redirect with router.
|
||||
//The result on logout click is an ugly piece of page for a few milliseconds before returning to the login page.
|
||||
//It's ugly if you are perfectionist but functional and invisible for most of users while waiting for a next-auth fix.
|
||||
await signOut({ redirect: false });
|
||||
router.replace('/login');
|
||||
};
|
||||
//Function
|
||||
const onLogoutClickedHandler = async () => {
|
||||
//This bug is open : https://github.com/nextauthjs/next-auth/issues/1542
|
||||
//I put redirect to false and redirect with router.
|
||||
//The result on logout click is an ugly piece of page for a few milliseconds before returning to the login page.
|
||||
//It's ugly if you are perfectionist but functional and invisible for most of users while waiting for a next-auth fix.
|
||||
await signOut({ redirect: false });
|
||||
router.replace('/login');
|
||||
};
|
||||
|
||||
return (
|
||||
<ul className={classes.Nav}>
|
||||
<li
|
||||
style={{ margin: '0px 15px 0px 0px' }}
|
||||
className={classes.account}
|
||||
>
|
||||
<Link
|
||||
href='/account'
|
||||
className={
|
||||
currentRoute === '/account' ? classes.active : null
|
||||
}
|
||||
>
|
||||
<div className={classes.user}>
|
||||
<div>
|
||||
<IconUser size={28} />
|
||||
</div>
|
||||
<div className={classes.username}>
|
||||
{status === 'authenticated' && data.user.name}
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
</li>
|
||||
return (
|
||||
<ul className={classes.Nav}>
|
||||
<li style={{ margin: '0px 15px 0px 0px' }} className={classes.account}>
|
||||
<Link href='/account' className={currentRoute === '/account' ? classes.active : null}>
|
||||
<div className={classes.user}>
|
||||
<div>
|
||||
<IconUser size={28} />
|
||||
</div>
|
||||
<div className={classes.username}>{status === 'authenticated' && data.user.name}</div>
|
||||
</div>
|
||||
</Link>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<div className={classes.logout}>
|
||||
<a onClick={onLogoutClickedHandler}>
|
||||
<IconLogout size={28} />
|
||||
</a>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
);
|
||||
<li>
|
||||
<div className={classes.logout}>
|
||||
<a onClick={onLogoutClickedHandler}>
|
||||
<IconLogout size={28} />
|
||||
</a>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,54 +1,54 @@
|
|||
.Nav {
|
||||
list-style-type: none;
|
||||
margin: 0px 15px 0px 0px;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
list-style-type: none;
|
||||
margin: 0px 15px 0px 0px;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.user {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.username::first-letter {
|
||||
text-transform: capitalize;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.account {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
color: #494b7a;
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
color: #494b7a;
|
||||
}
|
||||
|
||||
.account a {
|
||||
color: #494b7a;
|
||||
text-decoration: none;
|
||||
color: #494b7a;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.account :focus,
|
||||
.account .active,
|
||||
.account :hover {
|
||||
color: #6d4aff;
|
||||
text-shadow: #6d4aff 0px 0px 18px;
|
||||
color: #6d4aff;
|
||||
text-shadow: #6d4aff 0px 0px 18px;
|
||||
}
|
||||
|
||||
.logout {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
color: #494b7a;
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
color: #494b7a;
|
||||
}
|
||||
|
||||
.logout :focus,
|
||||
.logout .active,
|
||||
.logout :hover {
|
||||
color: #6d4aff;
|
||||
text-shadow: #6d4aff 0px 0px 18px;
|
||||
color: #6d4aff;
|
||||
text-shadow: #6d4aff 0px 0px 18px;
|
||||
}
|
||||
|
||||
@media all and (max-width: 1000px) {
|
||||
.account {
|
||||
display: none;
|
||||
}
|
||||
.account {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,25 +6,25 @@ import classes from './Layout.module.css';
|
|||
import { useSession } from 'next-auth/react';
|
||||
|
||||
function Layout(props) {
|
||||
//Var
|
||||
const { status } = useSession();
|
||||
//Var
|
||||
const { status } = useSession();
|
||||
|
||||
if (status === 'authenticated') {
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
<NavSide />
|
||||
<div className={classes.mainWrapper}>{props.children}</div>
|
||||
<Footer />
|
||||
</>
|
||||
);
|
||||
} else if (status === 'unauthenticated') {
|
||||
return (
|
||||
<>
|
||||
<div className={classes.login}>{props.children}</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
if (status === 'authenticated') {
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
<NavSide />
|
||||
<div className={classes.mainWrapper}>{props.children}</div>
|
||||
<Footer />
|
||||
</>
|
||||
);
|
||||
} else if (status === 'unauthenticated') {
|
||||
return (
|
||||
<>
|
||||
<div className={classes.login}>{props.children}</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Layout;
|
||||
|
|
|
|||
|
|
@ -1,23 +1,23 @@
|
|||
.mainWrapper {
|
||||
margin: auto;
|
||||
max-width: 1400px;
|
||||
height: calc(100vh - 100px);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
overflow: auto;
|
||||
/* to prevent main content under navside on little screen */
|
||||
padding-left: 90px;
|
||||
/* Disable scrollbar */
|
||||
scrollbar-width: none;
|
||||
margin: auto;
|
||||
max-width: 1400px;
|
||||
height: calc(100vh - 100px);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
overflow: auto;
|
||||
/* to prevent main content under navside on little screen */
|
||||
padding-left: 90px;
|
||||
/* Disable scrollbar */
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
.login {
|
||||
background-color: #111827;
|
||||
background-color: #111827;
|
||||
}
|
||||
|
||||
@media all and (max-width: 1000px) {
|
||||
.mainWrapper {
|
||||
padding-left: 0px;
|
||||
}
|
||||
.mainWrapper {
|
||||
padding-left: 0px;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,56 +1,40 @@
|
|||
//Lib
|
||||
import classes from './NavSide.module.css';
|
||||
import {
|
||||
IconServer,
|
||||
IconSettingsAutomation,
|
||||
IconActivityHeartbeat,
|
||||
} from '@tabler/icons-react';
|
||||
import { IconServer, IconSettingsAutomation, IconActivityHeartbeat } from '@tabler/icons-react';
|
||||
import Link from 'next/link';
|
||||
import { useRouter } from 'next/router';
|
||||
|
||||
//Composants
|
||||
|
||||
export default function NavSide() {
|
||||
////Var
|
||||
//Get the current route to light the right Item
|
||||
const router = useRouter();
|
||||
const currentRoute = router.pathname;
|
||||
////Var
|
||||
//Get the current route to light the right Item
|
||||
const router = useRouter();
|
||||
const currentRoute = router.pathname;
|
||||
|
||||
return (
|
||||
<ul className={classes.NavSide}>
|
||||
<li className={classes.NavSideItem}>
|
||||
<Link
|
||||
href='/'
|
||||
className={currentRoute === '/' ? classes.active : null}
|
||||
>
|
||||
<IconServer size={40} />
|
||||
</Link>
|
||||
<span className={classes.tooltip}>Repositories</span>
|
||||
</li>
|
||||
<li className={classes.NavSideItem}>
|
||||
<Link
|
||||
href='/setup-wizard/1'
|
||||
className={
|
||||
currentRoute === '/setup-wizard/[slug]'
|
||||
? classes.active
|
||||
: null
|
||||
}
|
||||
>
|
||||
<IconSettingsAutomation size={40} />
|
||||
</Link>
|
||||
<span className={classes.tooltip}>Setup Wizard</span>
|
||||
</li>
|
||||
<li className={classes.NavSideItem}>
|
||||
<Link
|
||||
href='/monitoring'
|
||||
className={
|
||||
currentRoute === '/monitoring' ? classes.active : null
|
||||
}
|
||||
>
|
||||
<IconActivityHeartbeat size={40} />
|
||||
</Link>
|
||||
<span className={classes.tooltip}>Monitoring</span>
|
||||
</li>
|
||||
</ul>
|
||||
);
|
||||
return (
|
||||
<ul className={classes.NavSide}>
|
||||
<li className={classes.NavSideItem}>
|
||||
<Link href='/' className={currentRoute === '/' ? classes.active : null}>
|
||||
<IconServer size={40} />
|
||||
</Link>
|
||||
<span className={classes.tooltip}>Repositories</span>
|
||||
</li>
|
||||
<li className={classes.NavSideItem}>
|
||||
<Link
|
||||
href='/setup-wizard/1'
|
||||
className={currentRoute === '/setup-wizard/[slug]' ? classes.active : null}
|
||||
>
|
||||
<IconSettingsAutomation size={40} />
|
||||
</Link>
|
||||
<span className={classes.tooltip}>Setup Wizard</span>
|
||||
</li>
|
||||
<li className={classes.NavSideItem}>
|
||||
<Link href='/monitoring' className={currentRoute === '/monitoring' ? classes.active : null}>
|
||||
<IconActivityHeartbeat size={40} />
|
||||
</Link>
|
||||
<span className={classes.tooltip}>Monitoring</span>
|
||||
</li>
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,74 +1,74 @@
|
|||
/* NAVSIDE */
|
||||
|
||||
.NavSide {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
position: absolute;
|
||||
top: 50px;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
text-align: center;
|
||||
/* border-right: 2px solid #e5e7eb; */
|
||||
height: calc(100% - 50px);
|
||||
width: 70px;
|
||||
list-style-type: none;
|
||||
/* background: #1b1340; */
|
||||
background: #111827;
|
||||
/* box-shadow: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23); */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
position: absolute;
|
||||
top: 50px;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
text-align: center;
|
||||
/* border-right: 2px solid #e5e7eb; */
|
||||
height: calc(100% - 50px);
|
||||
width: 70px;
|
||||
list-style-type: none;
|
||||
/* background: #1b1340; */
|
||||
background: #111827;
|
||||
/* box-shadow: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23); */
|
||||
}
|
||||
|
||||
ul.NavSide {
|
||||
padding-top: 10px;
|
||||
margin: 0;
|
||||
padding-inline-start: 0px;
|
||||
padding-top: 10px;
|
||||
margin: 0;
|
||||
padding-inline-start: 0px;
|
||||
}
|
||||
|
||||
/* NAV SIDE ITEMS */
|
||||
.NavSideItem {
|
||||
margin: 0px 0px 30px 0px;
|
||||
margin: 0px 0px 30px 0px;
|
||||
}
|
||||
|
||||
.NavSideItem a {
|
||||
text-decoration: none;
|
||||
color: #494b7a;
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
color: #494b7a;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.NavSideItem a:hover,
|
||||
.NavSideItem a:focus,
|
||||
.NavSideItem a.active {
|
||||
color: #6d4aff;
|
||||
font-weight: bold;
|
||||
/* border-bottom: 2px solid #6d4aff; */
|
||||
/* padding-bottom: 15px; */
|
||||
text-shadow: #6d4aff 0px 0px 18px;
|
||||
color: #6d4aff;
|
||||
font-weight: bold;
|
||||
/* border-bottom: 2px solid #6d4aff; */
|
||||
/* padding-bottom: 15px; */
|
||||
text-shadow: #6d4aff 0px 0px 18px;
|
||||
}
|
||||
|
||||
.tooltip {
|
||||
visibility: hidden;
|
||||
width: 120px;
|
||||
/* background-color: #1b1340; */
|
||||
background-color: #111827;
|
||||
color: #d1d5db;
|
||||
text-align: center;
|
||||
border-radius: 6px;
|
||||
padding: 5px 0;
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
margin: 5px 0 0 20px;
|
||||
opacity: 0;
|
||||
transition: 0.5s opacity;
|
||||
box-shadow: 0 1px 8px rgba(0, 0, 0, 0.251);
|
||||
visibility: hidden;
|
||||
width: 120px;
|
||||
/* background-color: #1b1340; */
|
||||
background-color: #111827;
|
||||
color: #d1d5db;
|
||||
text-align: center;
|
||||
border-radius: 6px;
|
||||
padding: 5px 0;
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
margin: 5px 0 0 20px;
|
||||
opacity: 0;
|
||||
transition: 0.5s opacity;
|
||||
box-shadow: 0 1px 8px rgba(0, 0, 0, 0.251);
|
||||
}
|
||||
|
||||
.NavSideItem:hover .tooltip {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
@media all and (max-width: 1000px) {
|
||||
.NavSide {
|
||||
display: none;
|
||||
}
|
||||
.NavSide {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,18 +2,18 @@
|
|||
import classes from './ShimmerRepoList.module.css';
|
||||
|
||||
export default function ShimmerRepoList() {
|
||||
return (
|
||||
<div className={classes.container}>
|
||||
<div className={classes.loadingButtonContainer}>
|
||||
<div className={classes.buttonIsLoading} />
|
||||
</div>
|
||||
<div className={classes.loadingRepoContainer}>
|
||||
<div className={classes.repoIsLoading} />
|
||||
<div className={classes.repoIsLoading} />
|
||||
<div className={classes.repoIsLoading} />
|
||||
<div className={classes.repoIsLoading} />
|
||||
<div className={classes.repoIsLoading} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className={classes.container}>
|
||||
<div className={classes.loadingButtonContainer}>
|
||||
<div className={classes.buttonIsLoading} />
|
||||
</div>
|
||||
<div className={classes.loadingRepoContainer}>
|
||||
<div className={classes.repoIsLoading} />
|
||||
<div className={classes.repoIsLoading} />
|
||||
<div className={classes.repoIsLoading} />
|
||||
<div className={classes.repoIsLoading} />
|
||||
<div className={classes.repoIsLoading} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,50 +1,50 @@
|
|||
.container {
|
||||
display: flex;
|
||||
width: 90%;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
display: flex;
|
||||
width: 90%;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.loadingButtonContainer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 20px auto;
|
||||
width: 90%;
|
||||
justify-content: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 20px auto;
|
||||
width: 90%;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.buttonIsLoading {
|
||||
height: 62px;
|
||||
width: 211px;
|
||||
margin: auto;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.05);
|
||||
background: linear-gradient(110deg, #ececec 8%, #f5f5f5 18%, #ececec 33%);
|
||||
border-radius: 5px;
|
||||
background-size: 200% 100%;
|
||||
animation: 1.5s shine linear infinite;
|
||||
height: 62px;
|
||||
width: 211px;
|
||||
margin: auto;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.05);
|
||||
background: linear-gradient(110deg, #ececec 8%, #f5f5f5 18%, #ececec 33%);
|
||||
border-radius: 5px;
|
||||
background-size: 200% 100%;
|
||||
animation: 1.5s shine linear infinite;
|
||||
}
|
||||
|
||||
.loadingRepoContainer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
margin: auto;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.repoIsLoading {
|
||||
width: 100%;
|
||||
height: 65px;
|
||||
margin: 20px auto;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.05);
|
||||
background: linear-gradient(110deg, #ececec 8%, #f5f5f5 18%, #ececec 33%);
|
||||
border-radius: 5px;
|
||||
background-size: 200% 100%;
|
||||
animation: 1.5s shine linear infinite;
|
||||
width: 100%;
|
||||
height: 65px;
|
||||
margin: 20px auto;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.05);
|
||||
background: linear-gradient(110deg, #ececec 8%, #f5f5f5 18%, #ececec 33%);
|
||||
border-radius: 5px;
|
||||
background-size: 200% 100%;
|
||||
animation: 1.5s shine linear infinite;
|
||||
}
|
||||
|
||||
@keyframes shine {
|
||||
to {
|
||||
background-position-x: -200%;
|
||||
}
|
||||
to {
|
||||
background-position-x: -200%;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,31 +2,27 @@
|
|||
import classes from './StorageBar.module.css';
|
||||
|
||||
export default function StorageBar(props) {
|
||||
//Var
|
||||
//storageUsed is in octet, storageSize is in GB. Round to 1 decimal for %.
|
||||
const storageUsedPercent = (
|
||||
((props.storageUsed / 1000000) * 100) /
|
||||
props.storageSize
|
||||
).toFixed(1);
|
||||
//Var
|
||||
//storageUsed is in octet, storageSize is in GB. Round to 1 decimal for %.
|
||||
const storageUsedPercent = (((props.storageUsed / 1000000) * 100) / props.storageSize).toFixed(1);
|
||||
|
||||
return (
|
||||
<div className={classes.barContainer}>
|
||||
<div className={classes.barBackground}>
|
||||
<div
|
||||
style={{
|
||||
maxWidth: '100%',
|
||||
width: storageUsedPercent + '%',
|
||||
transition: 'width 0.5s 0s ease',
|
||||
}}
|
||||
>
|
||||
<div className={classes.progressionStyle} />
|
||||
</div>
|
||||
<div className={classes.tooltip}>
|
||||
{storageUsedPercent}% (
|
||||
{(props.storageUsed / 1000000).toFixed(1)} GB /{' '}
|
||||
{props.storageSize} GB)
|
||||
</div>
|
||||
</div>
|
||||
return (
|
||||
<div className={classes.barContainer}>
|
||||
<div className={classes.barBackground}>
|
||||
<div
|
||||
style={{
|
||||
maxWidth: '100%',
|
||||
width: storageUsedPercent + '%',
|
||||
transition: 'width 0.5s 0s ease',
|
||||
}}
|
||||
>
|
||||
<div className={classes.progressionStyle} />
|
||||
</div>
|
||||
);
|
||||
<div className={classes.tooltip}>
|
||||
{storageUsedPercent}% ({(props.storageUsed / 1000000).toFixed(1)} GB / {props.storageSize}{' '}
|
||||
GB)
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,36 +1,36 @@
|
|||
.barContainer {
|
||||
margin: auto;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.barBackground {
|
||||
background-color: #704dff5e;
|
||||
border-radius: 3px;
|
||||
height: 19px;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
background-color: #704dff5e;
|
||||
border-radius: 3px;
|
||||
height: 19px;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.progressionStyle {
|
||||
background-color: #704dff;
|
||||
border-radius: 3px;
|
||||
height: 19px;
|
||||
width: 100%;
|
||||
background-color: #704dff;
|
||||
border-radius: 3px;
|
||||
height: 19px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.tooltip {
|
||||
visibility: hidden;
|
||||
width: auto;
|
||||
height: auto;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
opacity: 0;
|
||||
transition: 0.5s opacity;
|
||||
position: absolute;
|
||||
left: calc(30%);
|
||||
top: 0px;
|
||||
visibility: hidden;
|
||||
width: auto;
|
||||
height: auto;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
opacity: 0;
|
||||
transition: 0.5s opacity;
|
||||
position: absolute;
|
||||
left: calc(30%);
|
||||
top: 0px;
|
||||
}
|
||||
|
||||
.barBackground:hover .tooltip {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,25 +2,25 @@
|
|||
import classes from './Switch.module.css';
|
||||
|
||||
export default function Switch(props) {
|
||||
return (
|
||||
<>
|
||||
<div className={classes.switchWrapper}>
|
||||
<div className={classes.switch}>
|
||||
<label className={classes.pureMaterialSwitch}>
|
||||
<input
|
||||
checked={props.checked}
|
||||
disabled={props.disabled}
|
||||
type='checkbox'
|
||||
onChange={(e) => props.onChange(e.target.checked)}
|
||||
/>
|
||||
return (
|
||||
<>
|
||||
<div className={classes.switchWrapper}>
|
||||
<div className={classes.switch}>
|
||||
<label className={classes.pureMaterialSwitch}>
|
||||
<input
|
||||
checked={props.checked}
|
||||
disabled={props.disabled}
|
||||
type='checkbox'
|
||||
onChange={(e) => props.onChange(e.target.checked)}
|
||||
/>
|
||||
|
||||
<span>{props.switchName}</span>
|
||||
</label>
|
||||
</div>
|
||||
<div className={classes.switchDescription}>
|
||||
<span>{props.switchDescription}</span>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
<span>{props.switchName}</span>
|
||||
</label>
|
||||
</div>
|
||||
<div className={classes.switchDescription}>
|
||||
<span>{props.switchDescription}</span>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,141 +1,141 @@
|
|||
.switchWrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-bottom: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.switch {
|
||||
display: flex;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.switchDescription {
|
||||
display: flex;
|
||||
margin: 8px 0px 0px 0px;
|
||||
color: #6c737f;
|
||||
font-size: 0.875rem;
|
||||
display: flex;
|
||||
margin: 8px 0px 0px 0px;
|
||||
color: #6c737f;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.pureMaterialSwitch {
|
||||
z-index: 0;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
color: rgba(var(--pure-material-onsurface-rgb, 0, 0, 0), 0.87);
|
||||
font-family: var(
|
||||
--pure-material-font,
|
||||
'Roboto',
|
||||
'Segoe UI',
|
||||
BlinkMacSystemFont,
|
||||
system-ui,
|
||||
-apple-system
|
||||
);
|
||||
font-size: 16px;
|
||||
line-height: 1.5;
|
||||
z-index: 0;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
color: rgba(var(--pure-material-onsurface-rgb, 0, 0, 0), 0.87);
|
||||
font-family: var(
|
||||
--pure-material-font,
|
||||
'Roboto',
|
||||
'Segoe UI',
|
||||
BlinkMacSystemFont,
|
||||
system-ui,
|
||||
-apple-system
|
||||
);
|
||||
font-size: 16px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* Input */
|
||||
.pureMaterialSwitch > input {
|
||||
appearance: none;
|
||||
-moz-appearance: none;
|
||||
-webkit-appearance: none;
|
||||
z-index: -1;
|
||||
position: absolute;
|
||||
right: 6px;
|
||||
top: -8px;
|
||||
display: block;
|
||||
margin: 0;
|
||||
border-radius: 50%;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background-color: rgba(var(--pure-material-onsurface-rgb, 0, 0, 0), 0.38);
|
||||
outline: none;
|
||||
opacity: 0;
|
||||
transform: scale(1);
|
||||
pointer-events: none;
|
||||
transition:
|
||||
opacity 0.3s 0.1s,
|
||||
transform 0.2s 0.1s;
|
||||
appearance: none;
|
||||
-moz-appearance: none;
|
||||
-webkit-appearance: none;
|
||||
z-index: -1;
|
||||
position: absolute;
|
||||
right: 6px;
|
||||
top: -8px;
|
||||
display: block;
|
||||
margin: 0;
|
||||
border-radius: 50%;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background-color: rgba(var(--pure-material-onsurface-rgb, 0, 0, 0), 0.38);
|
||||
outline: none;
|
||||
opacity: 0;
|
||||
transform: scale(1);
|
||||
pointer-events: none;
|
||||
transition:
|
||||
opacity 0.3s 0.1s,
|
||||
transform 0.2s 0.1s;
|
||||
}
|
||||
|
||||
/* Span */
|
||||
.pureMaterialSwitch > span {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
cursor: pointer;
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
color: #494b7a;
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
cursor: pointer;
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
color: #494b7a;
|
||||
}
|
||||
|
||||
/* Track */
|
||||
.pureMaterialSwitch > span::before {
|
||||
content: '';
|
||||
float: right;
|
||||
display: inline-block;
|
||||
margin: 5px 0 5px 30px;
|
||||
border-radius: 7px;
|
||||
width: 36px;
|
||||
height: 14px;
|
||||
background-color: rgba(var(--pure-material-onsurface-rgb, 0, 0, 0), 0.38);
|
||||
vertical-align: top;
|
||||
transition:
|
||||
background-color 0.2s,
|
||||
opacity 0.2s;
|
||||
content: '';
|
||||
float: right;
|
||||
display: inline-block;
|
||||
margin: 5px 0 5px 30px;
|
||||
border-radius: 7px;
|
||||
width: 36px;
|
||||
height: 14px;
|
||||
background-color: rgba(var(--pure-material-onsurface-rgb, 0, 0, 0), 0.38);
|
||||
vertical-align: top;
|
||||
transition:
|
||||
background-color 0.2s,
|
||||
opacity 0.2s;
|
||||
}
|
||||
|
||||
/* Thumb */
|
||||
.pureMaterialSwitch > span::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
right: 16px;
|
||||
border-radius: 50%;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background-color: rgb(var(--pure-material-onprimary-rgb, 255, 255, 255));
|
||||
box-shadow:
|
||||
0 3px 1px -2px rgba(0, 0, 0, 0.2),
|
||||
0 2px 2px 0 rgba(0, 0, 0, 0.14),
|
||||
0 1px 5px 0 rgba(0, 0, 0, 0.12);
|
||||
transition:
|
||||
background-color 0.2s,
|
||||
transform 0.2s;
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
right: 16px;
|
||||
border-radius: 50%;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background-color: rgb(var(--pure-material-onprimary-rgb, 255, 255, 255));
|
||||
box-shadow:
|
||||
0 3px 1px -2px rgba(0, 0, 0, 0.2),
|
||||
0 2px 2px 0 rgba(0, 0, 0, 0.14),
|
||||
0 1px 5px 0 rgba(0, 0, 0, 0.12);
|
||||
transition:
|
||||
background-color 0.2s,
|
||||
transform 0.2s;
|
||||
}
|
||||
|
||||
/* Checked */
|
||||
.pureMaterialSwitch > input:checked {
|
||||
right: -10px;
|
||||
background-color: rgb(var(--pure-material-primary-rgb, 109, 74, 255));
|
||||
right: -10px;
|
||||
background-color: rgb(var(--pure-material-primary-rgb, 109, 74, 255));
|
||||
}
|
||||
|
||||
.pureMaterialSwitch > input:checked + span::before {
|
||||
background-color: rgba(var(--pure-material-primary-rgb, 109, 74, 255), 0.6);
|
||||
background-color: rgba(var(--pure-material-primary-rgb, 109, 74, 255), 0.6);
|
||||
}
|
||||
|
||||
.pureMaterialSwitch > input:checked + span::after {
|
||||
background-color: rgb(var(--pure-material-primary-rgb, 109, 74, 255));
|
||||
transform: translateX(16px);
|
||||
background-color: rgb(var(--pure-material-primary-rgb, 109, 74, 255));
|
||||
transform: translateX(16px);
|
||||
}
|
||||
|
||||
/* Active */
|
||||
.pureMaterialSwitch > input:active {
|
||||
opacity: 1;
|
||||
transform: scale(0);
|
||||
transition:
|
||||
transform 0s,
|
||||
opacity 0s;
|
||||
opacity: 1;
|
||||
transform: scale(0);
|
||||
transition:
|
||||
transform 0s,
|
||||
opacity 0s;
|
||||
}
|
||||
|
||||
.pureMaterialSwitch > input:active + span::before {
|
||||
background-color: rgba(var(--pure-material-primary-rgb, 109, 74, 255), 0.6);
|
||||
background-color: rgba(var(--pure-material-primary-rgb, 109, 74, 255), 0.6);
|
||||
}
|
||||
|
||||
.pureMaterialSwitch > input:checked:active + span::before {
|
||||
background-color: rgba(var(--pure-material-onsurface-rgb, 0, 0, 0), 0.38);
|
||||
background-color: rgba(var(--pure-material-onsurface-rgb, 0, 0, 0), 0.38);
|
||||
}
|
||||
|
||||
/* Disabled */
|
||||
.pureMaterialSwitch > input:disabled + span {
|
||||
cursor: wait;
|
||||
cursor: wait;
|
||||
}
|
||||
|
||||
/* .pureMaterialSwitch > input:disabled {
|
||||
|
|
|
|||
|
|
@ -4,66 +4,53 @@ import classes from '../WizardStep1/WizardStep1.module.css';
|
|||
import { IconDeviceDesktopAnalytics, IconTerminal2 } from '@tabler/icons-react';
|
||||
|
||||
function WizardStep1() {
|
||||
return (
|
||||
<div className={classes.container}>
|
||||
<h1>
|
||||
<IconTerminal2 className={classes.icon} />
|
||||
Command Line Interface
|
||||
</h1>
|
||||
<div className={classes.description}>
|
||||
We recommend using the official <b>BorgBackup</b> client which
|
||||
is supported by most Linux distributions.
|
||||
<br />
|
||||
More information about installation can be{' '}
|
||||
<a
|
||||
href='https://borgbackup.readthedocs.io/en/stable/installation.html'
|
||||
target='_blank'
|
||||
rel='noreferrer'
|
||||
>
|
||||
found here
|
||||
</a>
|
||||
.<br />
|
||||
To <b>automate your backup</b>, you can also use{' '}
|
||||
<a
|
||||
href='https://torsion.org/borgmatic/'
|
||||
target='_blank'
|
||||
rel='noreferrer'
|
||||
>
|
||||
Borgmatic
|
||||
</a>{' '}
|
||||
which is a{' '}
|
||||
<a
|
||||
href='https://packages.debian.org/buster/borgmatic'
|
||||
target='_blank'
|
||||
rel='noreferrer'
|
||||
>
|
||||
Debian package
|
||||
</a>
|
||||
. On the step 4, you will find a pattern of default config.
|
||||
</div>
|
||||
<div className={classes.separator}></div>
|
||||
<h1>
|
||||
<IconDeviceDesktopAnalytics className={classes.icon} />
|
||||
Graphical User Interface
|
||||
</h1>
|
||||
<div className={classes.description}>
|
||||
<b>Vorta</b> is an opensource (GPLv3) backup client for Borg
|
||||
Backup.
|
||||
<br />
|
||||
It runs on Linux, MacOS and Windows (via Windows’ Linux
|
||||
Subsystem (WSL)). Find the right way to install it{' '}
|
||||
<a
|
||||
href='https://vorta.borgbase.com/'
|
||||
target='_blank'
|
||||
rel='noreferrer'
|
||||
>
|
||||
just here
|
||||
</a>
|
||||
.
|
||||
</div>
|
||||
<img src='/vorta-demo.gif' alt='Vorta GIF' />
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className={classes.container}>
|
||||
<h1>
|
||||
<IconTerminal2 className={classes.icon} />
|
||||
Command Line Interface
|
||||
</h1>
|
||||
<div className={classes.description}>
|
||||
We recommend using the official <b>BorgBackup</b> client which is supported by most Linux
|
||||
distributions.
|
||||
<br />
|
||||
More information about installation can be{' '}
|
||||
<a
|
||||
href='https://borgbackup.readthedocs.io/en/stable/installation.html'
|
||||
target='_blank'
|
||||
rel='noreferrer'
|
||||
>
|
||||
found here
|
||||
</a>
|
||||
.<br />
|
||||
To <b>automate your backup</b>, you can also use{' '}
|
||||
<a href='https://torsion.org/borgmatic/' target='_blank' rel='noreferrer'>
|
||||
Borgmatic
|
||||
</a>{' '}
|
||||
which is a{' '}
|
||||
<a href='https://packages.debian.org/buster/borgmatic' target='_blank' rel='noreferrer'>
|
||||
Debian package
|
||||
</a>
|
||||
. On the step 4, you will find a pattern of default config.
|
||||
</div>
|
||||
<div className={classes.separator}></div>
|
||||
<h1>
|
||||
<IconDeviceDesktopAnalytics className={classes.icon} />
|
||||
Graphical User Interface
|
||||
</h1>
|
||||
<div className={classes.description}>
|
||||
<b>Vorta</b> is an opensource (GPLv3) backup client for Borg Backup.
|
||||
<br />
|
||||
It runs on Linux, MacOS and Windows (via Windows’ Linux Subsystem (WSL)). Find the right way
|
||||
to install it{' '}
|
||||
<a href='https://vorta.borgbase.com/' target='_blank' rel='noreferrer'>
|
||||
just here
|
||||
</a>
|
||||
.
|
||||
</div>
|
||||
<img src='/vorta-demo.gif' alt='Vorta GIF' />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default WizardStep1;
|
||||
|
|
|
|||
|
|
@ -1,137 +1,137 @@
|
|||
.container {
|
||||
margin: 40px 20px 20px 5px;
|
||||
box-shadow:
|
||||
0 1px 3px rgba(0, 0, 0, 0.12),
|
||||
0 1px 2px rgba(0, 0, 0, 0.24);
|
||||
border-radius: 5px;
|
||||
text-align: left;
|
||||
padding: 30px 70px;
|
||||
animation: animStep ease-in 0.3s 1 normal none;
|
||||
margin: 40px 20px 20px 5px;
|
||||
box-shadow:
|
||||
0 1px 3px rgba(0, 0, 0, 0.12),
|
||||
0 1px 2px rgba(0, 0, 0, 0.24);
|
||||
border-radius: 5px;
|
||||
text-align: left;
|
||||
padding: 30px 70px;
|
||||
animation: animStep ease-in 0.3s 1 normal none;
|
||||
}
|
||||
|
||||
@keyframes animStep {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
50% {
|
||||
opacity: 0.5;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.container h1 {
|
||||
text-align: center;
|
||||
font-size: 1.7em;
|
||||
padding-bottom: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
font-size: 1.7em;
|
||||
padding-bottom: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.container a {
|
||||
color: #6d4aff;
|
||||
color: #6d4aff;
|
||||
}
|
||||
|
||||
.separator {
|
||||
height: 15px;
|
||||
height: 15px;
|
||||
}
|
||||
|
||||
.container {
|
||||
line-height: 1.8em;
|
||||
font-size: 1.05em;
|
||||
line-height: 1.8em;
|
||||
font-size: 1.05em;
|
||||
}
|
||||
|
||||
h1 .icon {
|
||||
color: #6d4aff;
|
||||
margin-right: 5px;
|
||||
color: #6d4aff;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.container img {
|
||||
max-width: 650px;
|
||||
max-width: 650px;
|
||||
}
|
||||
|
||||
.code {
|
||||
background-color: #111827;
|
||||
color: #f8f8f2;
|
||||
font-family:
|
||||
ui-monospace,
|
||||
SFMono-Regular,
|
||||
Menlo,
|
||||
Monaco,
|
||||
Consolas,
|
||||
liberation mono,
|
||||
courier new,
|
||||
monospace;
|
||||
padding: 5px 15px;
|
||||
border-radius: 5px;
|
||||
display: inline-block;
|
||||
margin: 5px 0px 10px 0px;
|
||||
font-size: 0.8em;
|
||||
white-space: pre;
|
||||
line-height: 1em;
|
||||
background-color: #111827;
|
||||
color: #f8f8f2;
|
||||
font-family:
|
||||
ui-monospace,
|
||||
SFMono-Regular,
|
||||
Menlo,
|
||||
Monaco,
|
||||
Consolas,
|
||||
liberation mono,
|
||||
courier new,
|
||||
monospace;
|
||||
padding: 5px 15px;
|
||||
border-radius: 5px;
|
||||
display: inline-block;
|
||||
margin: 5px 0px 10px 0px;
|
||||
font-size: 0.8em;
|
||||
white-space: pre;
|
||||
line-height: 1em;
|
||||
}
|
||||
|
||||
.verifyOrange {
|
||||
background-color: #ff7a1b;
|
||||
color: #fff;
|
||||
font-weight: 500;
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
background-color: #ff7a1b;
|
||||
color: #fff;
|
||||
font-weight: 500;
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.verifyRed {
|
||||
background-color: #ea1313;
|
||||
color: #fff;
|
||||
font-weight: 500;
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
margin-top: 10px;
|
||||
background-color: #ea1313;
|
||||
color: #fff;
|
||||
font-weight: 500;
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.verifyOrange li,
|
||||
.container li {
|
||||
list-style: disc;
|
||||
margin-top: 1px;
|
||||
margin-left: 30px;
|
||||
list-style: disc;
|
||||
margin-top: 1px;
|
||||
margin-left: 30px;
|
||||
}
|
||||
|
||||
.verifyOrange li .sshPublicKey {
|
||||
background-color: #282a36;
|
||||
font-family:
|
||||
ui-monospace,
|
||||
SFMono-Regular,
|
||||
Menlo,
|
||||
Monaco,
|
||||
Consolas,
|
||||
liberation mono,
|
||||
courier new,
|
||||
monospace;
|
||||
border-radius: 5px;
|
||||
padding: 5px;
|
||||
background-color: #282a36;
|
||||
font-family:
|
||||
ui-monospace,
|
||||
SFMono-Regular,
|
||||
Menlo,
|
||||
Monaco,
|
||||
Consolas,
|
||||
liberation mono,
|
||||
courier new,
|
||||
monospace;
|
||||
border-radius: 5px;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.verifyRed .alert,
|
||||
.verifyOrange .alert {
|
||||
text-align: center;
|
||||
font-size: 1.1em;
|
||||
padding-bottom: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
font-size: 1.1em;
|
||||
padding-bottom: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.iconAlert {
|
||||
margin-right: 5px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.note {
|
||||
font-style: italic;
|
||||
color: #494b7a4d;
|
||||
font-size: 0.8em;
|
||||
font-style: italic;
|
||||
color: #494b7a4d;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
.note a {
|
||||
color: #6d4aff73;
|
||||
color: #6d4aff73;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,146 +6,132 @@ import CopyButton from '../../UI/CopyButton/CopyButton';
|
|||
import lanCommandOption from '../../../helpers/functions/lanCommandOption';
|
||||
|
||||
function WizardStep2(props) {
|
||||
////Vars
|
||||
const wizardEnv = props.wizardEnv;
|
||||
const UNIX_USER = wizardEnv.UNIX_USER;
|
||||
//Needed to generate command for borg over LAN instead of WAN if env vars are set and option enabled.
|
||||
const { FQDN, SSH_SERVER_PORT } = lanCommandOption(
|
||||
wizardEnv,
|
||||
props.selectedOption.lanCommand
|
||||
);
|
||||
////Vars
|
||||
const wizardEnv = props.wizardEnv;
|
||||
const UNIX_USER = wizardEnv.UNIX_USER;
|
||||
//Needed to generate command for borg over LAN instead of WAN if env vars are set and option enabled.
|
||||
const { FQDN, SSH_SERVER_PORT } = lanCommandOption(wizardEnv, props.selectedOption.lanCommand);
|
||||
|
||||
return (
|
||||
<div className={classes.container}>
|
||||
<h1>
|
||||
<IconTool className={classes.icon} />
|
||||
Initialize a repository
|
||||
</h1>
|
||||
<div className={classes.description}>
|
||||
To initialize your repository with borgbackup :
|
||||
<br />
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'flex-start',
|
||||
}}
|
||||
>
|
||||
<div className={classes.code}>
|
||||
borg init -e repokey-blake2 ssh://
|
||||
{UNIX_USER}@{FQDN}
|
||||
{SSH_SERVER_PORT}/./
|
||||
{props.selectedOption.repositoryName}
|
||||
</div>
|
||||
<CopyButton
|
||||
dataToCopy={`borg init -e repokey-blake2 ssh://${UNIX_USER}@${FQDN}${SSH_SERVER_PORT}/./${props.selectedOption.repositoryName}`}
|
||||
/>
|
||||
</div>
|
||||
<div className={classes.note}>
|
||||
The encryption mode is the one recommended by BorgBackup.
|
||||
For more information,{' '}
|
||||
<a
|
||||
href='https://borgbackup.readthedocs.io/en/stable/usage/init.html?highlight=init#more-encryption-modes'
|
||||
rel='noreferrer'
|
||||
target='_blank'
|
||||
>
|
||||
click here
|
||||
</a>
|
||||
.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={classes.separator}></div>
|
||||
<h2>Borgmatic</h2>
|
||||
<div className={classes.description}>
|
||||
If you are using Borgmatic and have <b>already edited</b> the
|
||||
configuration file (find a sample on the step 4) :
|
||||
<br />
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'flex-start',
|
||||
}}
|
||||
>
|
||||
<div className={classes.code}>
|
||||
borgmatic init -e repokey-blake2
|
||||
</div>
|
||||
<CopyButton dataToCopy='borgmatic init -e repokey-blake2' />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>Vorta</h2>
|
||||
<div className={classes.description}>
|
||||
To "Initialize a new repository" or "Add existing repository",
|
||||
copy this into the field "Repository URL" of Vorta :
|
||||
<br />
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'flex-start',
|
||||
}}
|
||||
>
|
||||
<div className={classes.code}>
|
||||
ssh://
|
||||
{UNIX_USER}@{FQDN}
|
||||
{SSH_SERVER_PORT}/./
|
||||
{props.selectedOption.repositoryName}
|
||||
</div>
|
||||
<CopyButton
|
||||
dataToCopy={`ssh://${UNIX_USER}@${FQDN}${SSH_SERVER_PORT}/./${props.selectedOption.repositoryName}`}
|
||||
/>
|
||||
</div>
|
||||
For more information about the Vorta graphical client, please
|
||||
refer to{' '}
|
||||
<a
|
||||
href='https://vorta.borgbase.com/usage/remote/'
|
||||
rel='noreferrer'
|
||||
target='_blank'
|
||||
>
|
||||
this documentation
|
||||
</a>
|
||||
.
|
||||
</div>
|
||||
|
||||
<div className={classes.separator} />
|
||||
<div className={classes.verifyOrange}>
|
||||
<div className={classes.alert}>
|
||||
<IconAlertCircle className={classes.iconAlert} />
|
||||
<b>Check the fingerprint of server</b>
|
||||
</div>
|
||||
To check that you are talking to the right server, please make
|
||||
sure to validate one of the following key's fingerprint when you
|
||||
first connect :
|
||||
<li>
|
||||
<span className={classes.sshPublicKey}>
|
||||
ECDSA : {wizardEnv.SSH_SERVER_FINGERPRINT_ECDSA}
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<span className={classes.sshPublicKey}>
|
||||
ED25519 : {wizardEnv.SSH_SERVER_FINGERPRINT_ED25519}
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<span className={classes.sshPublicKey}>
|
||||
RSA : {wizardEnv.SSH_SERVER_FINGERPRINT_RSA}
|
||||
</span>
|
||||
</li>
|
||||
</div>
|
||||
|
||||
<div className={classes.verifyRed}>
|
||||
<div className={classes.alert}>
|
||||
<IconAlertCircle className={classes.iconAlert} />
|
||||
<b>Save your passphrase</b>
|
||||
</div>
|
||||
Once again, the server cannot access your encrypted backup data
|
||||
or the encryption passphrase. Remember to put your passphrase in
|
||||
your password manager when you initialise your repository.
|
||||
</div>
|
||||
return (
|
||||
<div className={classes.container}>
|
||||
<h1>
|
||||
<IconTool className={classes.icon} />
|
||||
Initialize a repository
|
||||
</h1>
|
||||
<div className={classes.description}>
|
||||
To initialize your repository with borgbackup :
|
||||
<br />
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'flex-start',
|
||||
}}
|
||||
>
|
||||
<div className={classes.code}>
|
||||
borg init -e repokey-blake2 ssh://
|
||||
{UNIX_USER}@{FQDN}
|
||||
{SSH_SERVER_PORT}/./
|
||||
{props.selectedOption.repositoryName}
|
||||
</div>
|
||||
<CopyButton
|
||||
dataToCopy={`borg init -e repokey-blake2 ssh://${UNIX_USER}@${FQDN}${SSH_SERVER_PORT}/./${props.selectedOption.repositoryName}`}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
<div className={classes.note}>
|
||||
The encryption mode is the one recommended by BorgBackup. For more information,{' '}
|
||||
<a
|
||||
href='https://borgbackup.readthedocs.io/en/stable/usage/init.html?highlight=init#more-encryption-modes'
|
||||
rel='noreferrer'
|
||||
target='_blank'
|
||||
>
|
||||
click here
|
||||
</a>
|
||||
.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={classes.separator}></div>
|
||||
<h2>Borgmatic</h2>
|
||||
<div className={classes.description}>
|
||||
If you are using Borgmatic and have <b>already edited</b> the configuration file (find a
|
||||
sample on the step 4) :
|
||||
<br />
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'flex-start',
|
||||
}}
|
||||
>
|
||||
<div className={classes.code}>borgmatic init -e repokey-blake2</div>
|
||||
<CopyButton dataToCopy='borgmatic init -e repokey-blake2' />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>Vorta</h2>
|
||||
<div className={classes.description}>
|
||||
To "Initialize a new repository" or "Add existing repository", copy this into the field
|
||||
"Repository URL" of Vorta :
|
||||
<br />
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'flex-start',
|
||||
}}
|
||||
>
|
||||
<div className={classes.code}>
|
||||
ssh://
|
||||
{UNIX_USER}@{FQDN}
|
||||
{SSH_SERVER_PORT}/./
|
||||
{props.selectedOption.repositoryName}
|
||||
</div>
|
||||
<CopyButton
|
||||
dataToCopy={`ssh://${UNIX_USER}@${FQDN}${SSH_SERVER_PORT}/./${props.selectedOption.repositoryName}`}
|
||||
/>
|
||||
</div>
|
||||
For more information about the Vorta graphical client, please refer to{' '}
|
||||
<a href='https://vorta.borgbase.com/usage/remote/' rel='noreferrer' target='_blank'>
|
||||
this documentation
|
||||
</a>
|
||||
.
|
||||
</div>
|
||||
|
||||
<div className={classes.separator} />
|
||||
<div className={classes.verifyOrange}>
|
||||
<div className={classes.alert}>
|
||||
<IconAlertCircle className={classes.iconAlert} />
|
||||
<b>Check the fingerprint of server</b>
|
||||
</div>
|
||||
To check that you are talking to the right server, please make sure to validate one of the
|
||||
following key's fingerprint when you first connect :
|
||||
<li>
|
||||
<span className={classes.sshPublicKey}>
|
||||
ECDSA : {wizardEnv.SSH_SERVER_FINGERPRINT_ECDSA}
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<span className={classes.sshPublicKey}>
|
||||
ED25519 : {wizardEnv.SSH_SERVER_FINGERPRINT_ED25519}
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<span className={classes.sshPublicKey}>RSA : {wizardEnv.SSH_SERVER_FINGERPRINT_RSA}</span>
|
||||
</li>
|
||||
</div>
|
||||
|
||||
<div className={classes.verifyRed}>
|
||||
<div className={classes.alert}>
|
||||
<IconAlertCircle className={classes.iconAlert} />
|
||||
<b>Save your passphrase</b>
|
||||
</div>
|
||||
Once again, the server cannot access your encrypted backup data or the encryption
|
||||
passphrase. Remember to put your passphrase in your password manager when you initialise
|
||||
your repository.
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default WizardStep2;
|
||||
|
|
|
|||
|
|
@ -6,184 +6,165 @@ import CopyButton from '../../UI/CopyButton/CopyButton';
|
|||
import lanCommandOption from '../../../helpers/functions/lanCommandOption';
|
||||
|
||||
function WizardStep3(props) {
|
||||
////Vars
|
||||
const wizardEnv = props.wizardEnv;
|
||||
const UNIX_USER = wizardEnv.UNIX_USER;
|
||||
//Needed to generate command for borg over LAN instead of WAN if env vars are set and option enabled.
|
||||
const { FQDN, SSH_SERVER_PORT } = lanCommandOption(
|
||||
wizardEnv,
|
||||
props.selectedOption.lanCommand
|
||||
);
|
||||
////Vars
|
||||
const wizardEnv = props.wizardEnv;
|
||||
const UNIX_USER = wizardEnv.UNIX_USER;
|
||||
//Needed to generate command for borg over LAN instead of WAN if env vars are set and option enabled.
|
||||
const { FQDN, SSH_SERVER_PORT } = lanCommandOption(wizardEnv, props.selectedOption.lanCommand);
|
||||
|
||||
return (
|
||||
<div className={classes.container}>
|
||||
<h1>
|
||||
<IconPlayerPlay className={classes.icon} />
|
||||
Launch a backup
|
||||
</h1>
|
||||
<div className={classes.description}>
|
||||
To launch a backup with borgbackup :
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'flex-start',
|
||||
}}
|
||||
>
|
||||
<div className={classes.code}>
|
||||
borg create ssh://
|
||||
{UNIX_USER}@{FQDN}
|
||||
{SSH_SERVER_PORT}/./
|
||||
{props.selectedOption.repositoryName}
|
||||
::archive1 /your/pathToBackup
|
||||
</div>
|
||||
<CopyButton
|
||||
dataToCopy={`borg create ssh://${UNIX_USER}@${FQDN}${SSH_SERVER_PORT}/./${props.selectedOption.repositoryName}::archive1 /your/pathToBackup`}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className={classes.separator}></div>
|
||||
<h1>
|
||||
<IconChecks className={classes.icon} />
|
||||
Check your backup{' '}
|
||||
<span style={{ color: '#494b7a4d', fontWeight: 'normal' }}>
|
||||
(always)
|
||||
</span>
|
||||
</h1>
|
||||
<div className={classes.description}>
|
||||
BorgWarehouse <b>only stores</b> your backups. They are
|
||||
encrypted and <b>there is no way</b> for BorgWarehouse to know
|
||||
if the backup is intact.
|
||||
<br />
|
||||
You should regularly test your backups and check that the data
|
||||
is recoverable.{' '}
|
||||
<b>
|
||||
BorgWarehouse cannot do this for you and does not guarantee
|
||||
anything.
|
||||
</b>
|
||||
<br />
|
||||
</div>
|
||||
|
||||
<span className={classes.description}>
|
||||
Based on the Borg documentation, you have multiple ways to check
|
||||
that your backups are correct with your tools (tar, rsync, diff
|
||||
or other tools).
|
||||
<br />
|
||||
<li>Check the integrity of a repository with :</li>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'flex-start',
|
||||
}}
|
||||
>
|
||||
<div className={classes.code}>
|
||||
borg check -v --progress ssh://
|
||||
{UNIX_USER}@{FQDN}
|
||||
{SSH_SERVER_PORT}/./
|
||||
{props.selectedOption.repositoryName}
|
||||
</div>
|
||||
<CopyButton
|
||||
dataToCopy={`borg check -v --progress ssh://${UNIX_USER}@${FQDN}${SSH_SERVER_PORT}/./${props.selectedOption.repositoryName}`}
|
||||
/>
|
||||
</div>
|
||||
<li>List the remote archives with :</li>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'flex-start',
|
||||
}}
|
||||
>
|
||||
<div className={classes.code}>
|
||||
borg list ssh://
|
||||
{UNIX_USER}@{FQDN}
|
||||
{SSH_SERVER_PORT}/./
|
||||
{props.selectedOption.repositoryName}
|
||||
</div>
|
||||
<CopyButton
|
||||
dataToCopy={`borg list ssh://${UNIX_USER}@${FQDN}${SSH_SERVER_PORT}/./${props.selectedOption.repositoryName}`}
|
||||
/>
|
||||
</div>
|
||||
<li>Download a remote archive with the following command :</li>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'flex-start',
|
||||
}}
|
||||
>
|
||||
<div className={classes.code}>
|
||||
borg export-tar --tar-filter="gzip -9" ssh://
|
||||
{UNIX_USER}@{FQDN}
|
||||
{SSH_SERVER_PORT}/./
|
||||
{props.selectedOption.repositoryName}
|
||||
::archive1 archive1.tar.gz
|
||||
</div>
|
||||
<CopyButton
|
||||
dataToCopy={`borg export-tar --tar-filter="gzip -9" ssh://${UNIX_USER}@${FQDN}${SSH_SERVER_PORT}/./${props.selectedOption.repositoryName}::archive1 archive1.tar.gz`}
|
||||
/>
|
||||
</div>
|
||||
<li>
|
||||
Mount an archive to compare or backup some files without
|
||||
download all the archive :
|
||||
</li>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'flex-start',
|
||||
}}
|
||||
>
|
||||
<div className={classes.code}>
|
||||
borg mount ssh://
|
||||
{UNIX_USER}@{FQDN}
|
||||
{SSH_SERVER_PORT}/./
|
||||
{props.selectedOption.repositoryName}
|
||||
::archive1 /tmp/yourMountPoint
|
||||
</div>
|
||||
<CopyButton
|
||||
dataToCopy={`borg mount ssh://${UNIX_USER}@${FQDN}${SSH_SERVER_PORT}/./${props.selectedOption.repositoryName}::archive1 /tmp/yourMountPoint`}
|
||||
/>
|
||||
</div>
|
||||
<br />
|
||||
To verify the consistency of a repository and the corresponding
|
||||
archives, please refer to{' '}
|
||||
<a
|
||||
href='https://borgbackup.readthedocs.io/en/stable/usage/check.html'
|
||||
rel='noreferrer'
|
||||
target='_blank'
|
||||
>
|
||||
this documentation
|
||||
</a>
|
||||
</span>
|
||||
<div className={classes.separator}></div>
|
||||
<h2>Borgmatic</h2>
|
||||
<div className={classes.description}>
|
||||
If you are using Borgmatic, please refer to{' '}
|
||||
<a
|
||||
href='https://torsion.org/borgmatic/docs/how-to/deal-with-very-large-backups/#consistency-check-configuration'
|
||||
rel='noreferrer'
|
||||
target='_blank'
|
||||
>
|
||||
this documentation
|
||||
</a>{' '}
|
||||
for a consistency check.
|
||||
</div>
|
||||
<h2>Vorta</h2>
|
||||
<div className={classes.description}>
|
||||
If you are using the Vorta graphical client, please refer to{' '}
|
||||
<a
|
||||
href='https://vorta.borgbase.com/usage/'
|
||||
rel='noreferrer'
|
||||
target='_blank'
|
||||
>
|
||||
this documentation
|
||||
</a>
|
||||
.
|
||||
</div>
|
||||
return (
|
||||
<div className={classes.container}>
|
||||
<h1>
|
||||
<IconPlayerPlay className={classes.icon} />
|
||||
Launch a backup
|
||||
</h1>
|
||||
<div className={classes.description}>
|
||||
To launch a backup with borgbackup :
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'flex-start',
|
||||
}}
|
||||
>
|
||||
<div className={classes.code}>
|
||||
borg create ssh://
|
||||
{UNIX_USER}@{FQDN}
|
||||
{SSH_SERVER_PORT}/./
|
||||
{props.selectedOption.repositoryName}
|
||||
::archive1 /your/pathToBackup
|
||||
</div>
|
||||
<CopyButton
|
||||
dataToCopy={`borg create ssh://${UNIX_USER}@${FQDN}${SSH_SERVER_PORT}/./${props.selectedOption.repositoryName}::archive1 /your/pathToBackup`}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
</div>
|
||||
<div className={classes.separator}></div>
|
||||
<h1>
|
||||
<IconChecks className={classes.icon} />
|
||||
Check your backup{' '}
|
||||
<span style={{ color: '#494b7a4d', fontWeight: 'normal' }}> (always)</span>
|
||||
</h1>
|
||||
<div className={classes.description}>
|
||||
BorgWarehouse <b>only stores</b> your backups. They are encrypted and <b>there is no way</b>{' '}
|
||||
for BorgWarehouse to know if the backup is intact.
|
||||
<br />
|
||||
You should regularly test your backups and check that the data is recoverable.{' '}
|
||||
<b>BorgWarehouse cannot do this for you and does not guarantee anything.</b>
|
||||
<br />
|
||||
</div>
|
||||
|
||||
<span className={classes.description}>
|
||||
Based on the Borg documentation, you have multiple ways to check that your backups are
|
||||
correct with your tools (tar, rsync, diff or other tools).
|
||||
<br />
|
||||
<li>Check the integrity of a repository with :</li>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'flex-start',
|
||||
}}
|
||||
>
|
||||
<div className={classes.code}>
|
||||
borg check -v --progress ssh://
|
||||
{UNIX_USER}@{FQDN}
|
||||
{SSH_SERVER_PORT}/./
|
||||
{props.selectedOption.repositoryName}
|
||||
</div>
|
||||
<CopyButton
|
||||
dataToCopy={`borg check -v --progress ssh://${UNIX_USER}@${FQDN}${SSH_SERVER_PORT}/./${props.selectedOption.repositoryName}`}
|
||||
/>
|
||||
</div>
|
||||
<li>List the remote archives with :</li>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'flex-start',
|
||||
}}
|
||||
>
|
||||
<div className={classes.code}>
|
||||
borg list ssh://
|
||||
{UNIX_USER}@{FQDN}
|
||||
{SSH_SERVER_PORT}/./
|
||||
{props.selectedOption.repositoryName}
|
||||
</div>
|
||||
<CopyButton
|
||||
dataToCopy={`borg list ssh://${UNIX_USER}@${FQDN}${SSH_SERVER_PORT}/./${props.selectedOption.repositoryName}`}
|
||||
/>
|
||||
</div>
|
||||
<li>Download a remote archive with the following command :</li>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'flex-start',
|
||||
}}
|
||||
>
|
||||
<div className={classes.code}>
|
||||
borg export-tar --tar-filter="gzip -9" ssh://
|
||||
{UNIX_USER}@{FQDN}
|
||||
{SSH_SERVER_PORT}/./
|
||||
{props.selectedOption.repositoryName}
|
||||
::archive1 archive1.tar.gz
|
||||
</div>
|
||||
<CopyButton
|
||||
dataToCopy={`borg export-tar --tar-filter="gzip -9" ssh://${UNIX_USER}@${FQDN}${SSH_SERVER_PORT}/./${props.selectedOption.repositoryName}::archive1 archive1.tar.gz`}
|
||||
/>
|
||||
</div>
|
||||
<li>Mount an archive to compare or backup some files without download all the archive :</li>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'flex-start',
|
||||
}}
|
||||
>
|
||||
<div className={classes.code}>
|
||||
borg mount ssh://
|
||||
{UNIX_USER}@{FQDN}
|
||||
{SSH_SERVER_PORT}/./
|
||||
{props.selectedOption.repositoryName}
|
||||
::archive1 /tmp/yourMountPoint
|
||||
</div>
|
||||
<CopyButton
|
||||
dataToCopy={`borg mount ssh://${UNIX_USER}@${FQDN}${SSH_SERVER_PORT}/./${props.selectedOption.repositoryName}::archive1 /tmp/yourMountPoint`}
|
||||
/>
|
||||
</div>
|
||||
<br />
|
||||
To verify the consistency of a repository and the corresponding archives, please refer to{' '}
|
||||
<a
|
||||
href='https://borgbackup.readthedocs.io/en/stable/usage/check.html'
|
||||
rel='noreferrer'
|
||||
target='_blank'
|
||||
>
|
||||
this documentation
|
||||
</a>
|
||||
</span>
|
||||
<div className={classes.separator}></div>
|
||||
<h2>Borgmatic</h2>
|
||||
<div className={classes.description}>
|
||||
If you are using Borgmatic, please refer to{' '}
|
||||
<a
|
||||
href='https://torsion.org/borgmatic/docs/how-to/deal-with-very-large-backups/#consistency-check-configuration'
|
||||
rel='noreferrer'
|
||||
target='_blank'
|
||||
>
|
||||
this documentation
|
||||
</a>{' '}
|
||||
for a consistency check.
|
||||
</div>
|
||||
<h2>Vorta</h2>
|
||||
<div className={classes.description}>
|
||||
If you are using the Vorta graphical client, please refer to{' '}
|
||||
<a href='https://vorta.borgbase.com/usage/' rel='noreferrer' target='_blank'>
|
||||
this documentation
|
||||
</a>
|
||||
.
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default WizardStep3;
|
||||
|
|
|
|||
|
|
@ -6,16 +6,13 @@ import CopyButton from '../../UI/CopyButton/CopyButton';
|
|||
import lanCommandOption from '../../../helpers/functions/lanCommandOption';
|
||||
|
||||
function WizardStep4(props) {
|
||||
////Vars
|
||||
const wizardEnv = props.wizardEnv;
|
||||
const UNIX_USER = wizardEnv.UNIX_USER;
|
||||
//Needed to generate command for borg over LAN instead of WAN if env vars are set and option enabled.
|
||||
const { FQDN, SSH_SERVER_PORT } = lanCommandOption(
|
||||
wizardEnv,
|
||||
props.selectedOption.lanCommand
|
||||
);
|
||||
////Vars
|
||||
const wizardEnv = props.wizardEnv;
|
||||
const UNIX_USER = wizardEnv.UNIX_USER;
|
||||
//Needed to generate command for borg over LAN instead of WAN if env vars are set and option enabled.
|
||||
const { FQDN, SSH_SERVER_PORT } = lanCommandOption(wizardEnv, props.selectedOption.lanCommand);
|
||||
|
||||
const configBorgmatic = `location:
|
||||
const configBorgmatic = `location:
|
||||
# List of source directories to backup.
|
||||
source_directories:
|
||||
- /your-repo-to-backup
|
||||
|
|
@ -54,72 +51,70 @@ consistency:
|
|||
# Third-party services to notify you if backups aren't happening.
|
||||
#healthchecks: https://hc-ping.com/be067061-cf96-4412-8eae-62b0c50d6a8c`;
|
||||
|
||||
return (
|
||||
<div className={classes.container}>
|
||||
<h1>
|
||||
<IconWand className={classes.icon} />
|
||||
Automate your backup
|
||||
</h1>
|
||||
<div className={classes.description}>
|
||||
The official borgbackup project provides a script in its
|
||||
documentation
|
||||
<a
|
||||
href='https://borgbackup.readthedocs.io/en/stable/quickstart.html#automating-backups'
|
||||
rel='noreferrer'
|
||||
target='_blank'
|
||||
>
|
||||
right here
|
||||
</a>
|
||||
.
|
||||
</div>
|
||||
return (
|
||||
<div className={classes.container}>
|
||||
<h1>
|
||||
<IconWand className={classes.icon} />
|
||||
Automate your backup
|
||||
</h1>
|
||||
<div className={classes.description}>
|
||||
The official borgbackup project provides a script in its documentation
|
||||
<a
|
||||
href='https://borgbackup.readthedocs.io/en/stable/quickstart.html#automating-backups'
|
||||
rel='noreferrer'
|
||||
target='_blank'
|
||||
>
|
||||
right here
|
||||
</a>
|
||||
.
|
||||
</div>
|
||||
|
||||
<div className={classes.separator} />
|
||||
<h2>Vorta</h2>
|
||||
<div className={classes.description}>
|
||||
If you are using the Vorta graphical client, please refer
|
||||
to
|
||||
<a
|
||||
href='https://vorta.borgbase.com/usage/#scheduling-automatic-backups'
|
||||
rel='noreferrer'
|
||||
target='_blank'
|
||||
>
|
||||
this documentation
|
||||
</a>
|
||||
.
|
||||
</div>
|
||||
<div className={classes.separator} />
|
||||
<h2>Vorta</h2>
|
||||
<div className={classes.description}>
|
||||
If you are using the Vorta graphical client, please refer to
|
||||
<a
|
||||
href='https://vorta.borgbase.com/usage/#scheduling-automatic-backups'
|
||||
rel='noreferrer'
|
||||
target='_blank'
|
||||
>
|
||||
this documentation
|
||||
</a>
|
||||
.
|
||||
</div>
|
||||
|
||||
<h2>Borgmatic</h2>
|
||||
<div className={classes.description}>
|
||||
If you are using Borgmatic, you can check
|
||||
<a
|
||||
href='https://torsion.org/borgmatic/docs/how-to/set-up-backups/#autopilot'
|
||||
rel='noreferrer'
|
||||
target='_blank'
|
||||
>
|
||||
this documentation
|
||||
</a>
|
||||
and <b>adapt</b> and use the following script :
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'flex-start',
|
||||
}}
|
||||
>
|
||||
<div className={classes.code}>{configBorgmatic}</div>
|
||||
<div
|
||||
style={{
|
||||
margin: '15px 0 auto 0',
|
||||
display: 'flex',
|
||||
alignContent: 'center',
|
||||
}}
|
||||
>
|
||||
<CopyButton dataToCopy={configBorgmatic} size={32} />
|
||||
</div>
|
||||
</div>
|
||||
<h2>Borgmatic</h2>
|
||||
<div className={classes.description}>
|
||||
If you are using Borgmatic, you can check
|
||||
<a
|
||||
href='https://torsion.org/borgmatic/docs/how-to/set-up-backups/#autopilot'
|
||||
rel='noreferrer'
|
||||
target='_blank'
|
||||
>
|
||||
this documentation
|
||||
</a>
|
||||
and <b>adapt</b> and use the following script :
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'flex-start',
|
||||
}}
|
||||
>
|
||||
<div className={classes.code}>{configBorgmatic}</div>
|
||||
<div
|
||||
style={{
|
||||
margin: '15px 0 auto 0',
|
||||
display: 'flex',
|
||||
alignContent: 'center',
|
||||
}}
|
||||
>
|
||||
<CopyButton dataToCopy={configBorgmatic} size={32} />
|
||||
</div>
|
||||
);
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default WizardStep4;
|
||||
|
|
|
|||
|
|
@ -4,100 +4,67 @@ import classes from './WizardStepBar.module.css';
|
|||
import { IconChevronLeft, IconChevronRight } from '@tabler/icons-react';
|
||||
|
||||
function WizardStepBar(props) {
|
||||
////Functions
|
||||
//Color onClick on a step
|
||||
const colorHandler = (step) => {
|
||||
if (step <= props.step) {
|
||||
return classes.active;
|
||||
} else {
|
||||
return classes.inactive;
|
||||
}
|
||||
};
|
||||
//Color onClick on next step button
|
||||
const colorChevronNextStep = () => {
|
||||
if (props.step < 4) {
|
||||
return classes.activeChevron;
|
||||
} else {
|
||||
return classes.inactiveChevron;
|
||||
}
|
||||
};
|
||||
//Color onClick on previous step button
|
||||
const colorChevronPreviousStep = () => {
|
||||
if (props.step > 1) {
|
||||
return classes.activeChevron;
|
||||
} else {
|
||||
return classes.inactiveChevron;
|
||||
}
|
||||
};
|
||||
////Functions
|
||||
//Color onClick on a step
|
||||
const colorHandler = (step) => {
|
||||
if (step <= props.step) {
|
||||
return classes.active;
|
||||
} else {
|
||||
return classes.inactive;
|
||||
}
|
||||
};
|
||||
//Color onClick on next step button
|
||||
const colorChevronNextStep = () => {
|
||||
if (props.step < 4) {
|
||||
return classes.activeChevron;
|
||||
} else {
|
||||
return classes.inactiveChevron;
|
||||
}
|
||||
};
|
||||
//Color onClick on previous step button
|
||||
const colorChevronPreviousStep = () => {
|
||||
if (props.step > 1) {
|
||||
return classes.activeChevron;
|
||||
} else {
|
||||
return classes.inactiveChevron;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={classes.stepBarContainer}>
|
||||
<IconChevronLeft
|
||||
size={32}
|
||||
className={colorChevronPreviousStep()}
|
||||
onClick={props.previousStepHandler}
|
||||
/>
|
||||
<ul>
|
||||
<li
|
||||
className={colorHandler(2)}
|
||||
onClick={() => props.setStep(1)}
|
||||
>
|
||||
<div
|
||||
className={[classes.number, colorHandler(1)].join(' ')}
|
||||
>
|
||||
1
|
||||
</div>
|
||||
<div className={[classes.text, colorHandler(1)].join(' ')}>
|
||||
Client Setup
|
||||
</div>
|
||||
<div className={classes.line}></div>
|
||||
</li>
|
||||
<li
|
||||
className={colorHandler(3)}
|
||||
onClick={() => props.setStep(2)}
|
||||
>
|
||||
<div
|
||||
className={[classes.number, colorHandler(2)].join(' ')}
|
||||
>
|
||||
2
|
||||
</div>
|
||||
<div className={[classes.text, colorHandler(2)].join(' ')}>
|
||||
Init. repository
|
||||
</div>
|
||||
<div className={classes.line}></div>
|
||||
</li>
|
||||
<li
|
||||
className={colorHandler(4)}
|
||||
onClick={() => props.setStep(3)}
|
||||
>
|
||||
<div
|
||||
className={[classes.number, colorHandler(3)].join(' ')}
|
||||
>
|
||||
3
|
||||
</div>
|
||||
<div className={[classes.text, colorHandler(3)].join(' ')}>
|
||||
Launch & Verify
|
||||
</div>
|
||||
<div className={classes.line}></div>
|
||||
</li>
|
||||
<li onClick={() => props.setStep(4)}>
|
||||
<div
|
||||
className={[classes.number, colorHandler(4)].join(' ')}
|
||||
>
|
||||
4
|
||||
</div>
|
||||
<div className={[classes.text, colorHandler(4)].join(' ')}>
|
||||
Automation
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<IconChevronRight
|
||||
size={32}
|
||||
className={colorChevronNextStep()}
|
||||
onClick={props.nextStepHandler}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className={classes.stepBarContainer}>
|
||||
<IconChevronLeft
|
||||
size={32}
|
||||
className={colorChevronPreviousStep()}
|
||||
onClick={props.previousStepHandler}
|
||||
/>
|
||||
<ul>
|
||||
<li className={colorHandler(2)} onClick={() => props.setStep(1)}>
|
||||
<div className={[classes.number, colorHandler(1)].join(' ')}>1</div>
|
||||
<div className={[classes.text, colorHandler(1)].join(' ')}>Client Setup</div>
|
||||
<div className={classes.line}></div>
|
||||
</li>
|
||||
<li className={colorHandler(3)} onClick={() => props.setStep(2)}>
|
||||
<div className={[classes.number, colorHandler(2)].join(' ')}>2</div>
|
||||
<div className={[classes.text, colorHandler(2)].join(' ')}>Init. repository</div>
|
||||
<div className={classes.line}></div>
|
||||
</li>
|
||||
<li className={colorHandler(4)} onClick={() => props.setStep(3)}>
|
||||
<div className={[classes.number, colorHandler(3)].join(' ')}>3</div>
|
||||
<div className={[classes.text, colorHandler(3)].join(' ')}>Launch & Verify</div>
|
||||
<div className={classes.line}></div>
|
||||
</li>
|
||||
<li onClick={() => props.setStep(4)}>
|
||||
<div className={[classes.number, colorHandler(4)].join(' ')}>4</div>
|
||||
<div className={[classes.text, colorHandler(4)].join(' ')}>Automation</div>
|
||||
</li>
|
||||
</ul>
|
||||
<IconChevronRight
|
||||
size={32}
|
||||
className={colorChevronNextStep()}
|
||||
onClick={props.nextStepHandler}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default WizardStepBar;
|
||||
|
|
|
|||
|
|
@ -1,94 +1,94 @@
|
|||
/* General */
|
||||
|
||||
.stepBarContainer {
|
||||
text-transform: uppercase;
|
||||
color: #494b7a;
|
||||
font-size: 1em;
|
||||
margin-top: 40px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
text-transform: uppercase;
|
||||
color: #494b7a;
|
||||
font-size: 1em;
|
||||
margin-top: 40px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.stepBarContainer li {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
margin: 0px 30px;
|
||||
width: 180px;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
margin: 0px 30px;
|
||||
width: 180px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.stepBarContainer ul {
|
||||
padding-top: 10px;
|
||||
margin: 0;
|
||||
padding-inline-start: 0px;
|
||||
padding-top: 10px;
|
||||
margin: 0;
|
||||
padding-inline-start: 0px;
|
||||
}
|
||||
|
||||
/* Transition Active / Inactive */
|
||||
|
||||
.number {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
color: #494b7a4d;
|
||||
background: #fff;
|
||||
border: 1px solid #494b7a4d;
|
||||
border-radius: 50%;
|
||||
width: 37px;
|
||||
padding: 8px 5px;
|
||||
margin-bottom: 4px;
|
||||
font-weight: 700;
|
||||
z-index: 1;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
color: #494b7a4d;
|
||||
background: #fff;
|
||||
border: 1px solid #494b7a4d;
|
||||
border-radius: 50%;
|
||||
width: 37px;
|
||||
padding: 8px 5px;
|
||||
margin-bottom: 4px;
|
||||
font-weight: 700;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.active.number {
|
||||
box-shadow: 0 0 15px 5px rgba(110, 74, 255, 0.405);
|
||||
color: #fff;
|
||||
background: #6d4aff;
|
||||
transition: all 1000ms cubic-bezier(0.25, 0.25, 0.75, 0.75);
|
||||
transition-delay: 500ms;
|
||||
box-shadow: 0 0 15px 5px rgba(110, 74, 255, 0.405);
|
||||
color: #fff;
|
||||
background: #6d4aff;
|
||||
transition: all 1000ms cubic-bezier(0.25, 0.25, 0.75, 0.75);
|
||||
transition-delay: 500ms;
|
||||
}
|
||||
|
||||
.text {
|
||||
padding: 8px 0 0 0;
|
||||
color: #494b7a4d;
|
||||
padding: 8px 0 0 0;
|
||||
color: #494b7a4d;
|
||||
}
|
||||
|
||||
.active.text {
|
||||
transition: all 1000ms cubic-bezier(0.25, 0.25, 0.75, 0.75);
|
||||
transition-delay: 500ms;
|
||||
color: #494b7a;
|
||||
transition: all 1000ms cubic-bezier(0.25, 0.25, 0.75, 0.75);
|
||||
transition-delay: 500ms;
|
||||
color: #494b7a;
|
||||
}
|
||||
|
||||
.line {
|
||||
background: #6d4aff;
|
||||
width: 0;
|
||||
height: 2px;
|
||||
position: absolute;
|
||||
top: 17px;
|
||||
left: 108px;
|
||||
z-index: 0;
|
||||
transition: all 500ms cubic-bezier(0.25, 0.25, 0.75, 0.75);
|
||||
background: #6d4aff;
|
||||
width: 0;
|
||||
height: 2px;
|
||||
position: absolute;
|
||||
top: 17px;
|
||||
left: 108px;
|
||||
z-index: 0;
|
||||
transition: all 500ms cubic-bezier(0.25, 0.25, 0.75, 0.75);
|
||||
}
|
||||
|
||||
.active .line {
|
||||
background: #6d4aff;
|
||||
width: 204px;
|
||||
height: 2px;
|
||||
position: absolute;
|
||||
top: 17px;
|
||||
left: 108px;
|
||||
z-index: 0;
|
||||
background: #6d4aff;
|
||||
width: 204px;
|
||||
height: 2px;
|
||||
position: absolute;
|
||||
top: 17px;
|
||||
left: 108px;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.activeChevron {
|
||||
cursor: pointer;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.activeChevron:hover {
|
||||
color: #6d4aff;
|
||||
color: #6d4aff;
|
||||
}
|
||||
|
||||
.inactiveChevron {
|
||||
color: #494b7a4d;
|
||||
color: #494b7a4d;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,94 +1,84 @@
|
|||
//Lib
|
||||
import {
|
||||
Chart as ChartJS,
|
||||
CategoryScale,
|
||||
LinearScale,
|
||||
BarElement,
|
||||
Title,
|
||||
Tooltip,
|
||||
Legend,
|
||||
Chart as ChartJS,
|
||||
CategoryScale,
|
||||
LinearScale,
|
||||
BarElement,
|
||||
Title,
|
||||
Tooltip,
|
||||
Legend,
|
||||
} from 'chart.js';
|
||||
import { Bar } from 'react-chartjs-2';
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
export default function StorageUsedChartBar() {
|
||||
//States
|
||||
const [data, setData] = useState([]);
|
||||
//States
|
||||
const [data, setData] = useState([]);
|
||||
|
||||
//LifeCycle
|
||||
useEffect(() => {
|
||||
const dataFetch = async () => {
|
||||
try {
|
||||
const response = await fetch('/api/repo', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-type': 'application/json',
|
||||
},
|
||||
});
|
||||
setData((await response.json()).repoList);
|
||||
} catch (error) {
|
||||
console.log('Fetching datas error');
|
||||
}
|
||||
};
|
||||
|
||||
dataFetch();
|
||||
}, []);
|
||||
|
||||
////Chart.js
|
||||
ChartJS.register(
|
||||
CategoryScale,
|
||||
LinearScale,
|
||||
BarElement,
|
||||
Title,
|
||||
Tooltip,
|
||||
Legend
|
||||
);
|
||||
|
||||
const options = {
|
||||
responsive: true,
|
||||
plugins: {
|
||||
legend: {
|
||||
position: 'bottom',
|
||||
},
|
||||
title: {
|
||||
position: 'bottom',
|
||||
display: true,
|
||||
text: 'Storage used for each repository',
|
||||
},
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
max: 100,
|
||||
min: 0,
|
||||
ticks: {
|
||||
// Include a dollar sign in the ticks
|
||||
callback: function (value) {
|
||||
return value + '%';
|
||||
},
|
||||
stepSize: 10,
|
||||
},
|
||||
},
|
||||
},
|
||||
//LifeCycle
|
||||
useEffect(() => {
|
||||
const dataFetch = async () => {
|
||||
try {
|
||||
const response = await fetch('/api/repo', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-type': 'application/json',
|
||||
},
|
||||
});
|
||||
setData((await response.json()).repoList);
|
||||
} catch (error) {
|
||||
console.log('Fetching datas error');
|
||||
}
|
||||
};
|
||||
|
||||
const labels = data.map((repo) => repo.alias);
|
||||
dataFetch();
|
||||
}, []);
|
||||
|
||||
const dataChart = {
|
||||
labels,
|
||||
datasets: [
|
||||
{
|
||||
label: 'Storage used (%)',
|
||||
//storageUsed is in octet, storageSize is in GB. Round to 1 decimal for %.
|
||||
data: data.map((repo) =>
|
||||
(
|
||||
((repo.storageUsed / 1000000) * 100) /
|
||||
repo.storageSize
|
||||
).toFixed(1)
|
||||
),
|
||||
backgroundColor: '#704dff',
|
||||
},
|
||||
],
|
||||
};
|
||||
////Chart.js
|
||||
ChartJS.register(CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend);
|
||||
|
||||
return <Bar options={options} data={dataChart} />;
|
||||
const options = {
|
||||
responsive: true,
|
||||
plugins: {
|
||||
legend: {
|
||||
position: 'bottom',
|
||||
},
|
||||
title: {
|
||||
position: 'bottom',
|
||||
display: true,
|
||||
text: 'Storage used for each repository',
|
||||
},
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
max: 100,
|
||||
min: 0,
|
||||
ticks: {
|
||||
// Include a dollar sign in the ticks
|
||||
callback: function (value) {
|
||||
return value + '%';
|
||||
},
|
||||
stepSize: 10,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const labels = data.map((repo) => repo.alias);
|
||||
|
||||
const dataChart = {
|
||||
labels,
|
||||
datasets: [
|
||||
{
|
||||
label: 'Storage used (%)',
|
||||
//storageUsed is in octet, storageSize is in GB. Round to 1 decimal for %.
|
||||
data: data.map((repo) =>
|
||||
(((repo.storageUsed / 1000000) * 100) / repo.storageSize).toFixed(1)
|
||||
),
|
||||
backgroundColor: '#704dff',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
return <Bar options={options} data={dataChart} />;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,158 +14,146 @@ import RepoManage from '../RepoManage/RepoManage';
|
|||
import ShimmerRepoList from '../../Components/UI/ShimmerRepoList/ShimmerRepoList';
|
||||
|
||||
export default function RepoList() {
|
||||
////Var
|
||||
const router = useRouter();
|
||||
const { mutate } = useSWRConfig();
|
||||
const toastOptions = {
|
||||
position: 'top-right',
|
||||
autoClose: 8000,
|
||||
hideProgressBar: false,
|
||||
closeOnClick: true,
|
||||
pauseOnHover: true,
|
||||
draggable: true,
|
||||
progress: undefined,
|
||||
};
|
||||
////Var
|
||||
const router = useRouter();
|
||||
const { mutate } = useSWRConfig();
|
||||
const toastOptions = {
|
||||
position: 'top-right',
|
||||
autoClose: 8000,
|
||||
hideProgressBar: false,
|
||||
closeOnClick: true,
|
||||
pauseOnHover: true,
|
||||
draggable: true,
|
||||
progress: undefined,
|
||||
};
|
||||
|
||||
////Datas
|
||||
//Write a fetcher function to wrap the native fetch function and return the result of a call to url in json format
|
||||
const fetcher = async (url) => await fetch(url).then((res) => res.json());
|
||||
const { data, error } = useSWR('/api/repo', fetcher);
|
||||
////Datas
|
||||
//Write a fetcher function to wrap the native fetch function and return the result of a call to url in json format
|
||||
const fetcher = async (url) => await fetch(url).then((res) => res.json());
|
||||
const { data, error } = useSWR('/api/repo', fetcher);
|
||||
|
||||
////LifeCycle
|
||||
//Component did mount
|
||||
useEffect(() => {
|
||||
//If the route is home/manage-repo/add, open the RepoAdd box.
|
||||
if (router.pathname === '/manage-repo/add') {
|
||||
setDisplayRepoAdd(!displayRepoAdd);
|
||||
}
|
||||
//If the route is home/manage-repo/edit, open the RepoAdd box.
|
||||
if (router.pathname.startsWith('/manage-repo/edit')) {
|
||||
setDisplayRepoEdit(!displayRepoEdit);
|
||||
}
|
||||
//Fetch wizardEnv to hydrate Repo components
|
||||
const fetchWizardEnv = async () => {
|
||||
try {
|
||||
const response = await fetch('/api/account/getWizardEnv', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-type': 'application/json',
|
||||
},
|
||||
});
|
||||
setWizardEnv((await response.json()).wizardEnv);
|
||||
} catch (error) {
|
||||
console.log('Fetching datas error');
|
||||
}
|
||||
};
|
||||
fetchWizardEnv();
|
||||
}, []);
|
||||
|
||||
////States
|
||||
const [displayRepoAdd, setDisplayRepoAdd] = useState(false);
|
||||
const [displayRepoEdit, setDisplayRepoEdit] = useState(false);
|
||||
const [wizardEnv, setWizardEnv] = useState({});
|
||||
|
||||
////Functions
|
||||
|
||||
//Firstly, check the availability of data and condition it.
|
||||
if (!data) {
|
||||
//Force mutate after login (force a API GET on /api/repo to load repoList)
|
||||
mutate('/api/repo');
|
||||
return <ShimmerRepoList />;
|
||||
////LifeCycle
|
||||
//Component did mount
|
||||
useEffect(() => {
|
||||
//If the route is home/manage-repo/add, open the RepoAdd box.
|
||||
if (router.pathname === '/manage-repo/add') {
|
||||
setDisplayRepoAdd(!displayRepoAdd);
|
||||
}
|
||||
if (error) {
|
||||
toast.error('An error has occurred.', toastOptions);
|
||||
return <ToastContainer />;
|
||||
//If the route is home/manage-repo/edit, open the RepoAdd box.
|
||||
if (router.pathname.startsWith('/manage-repo/edit')) {
|
||||
setDisplayRepoEdit(!displayRepoEdit);
|
||||
}
|
||||
if (data.status == 500) {
|
||||
toast.error('API Error !', toastOptions);
|
||||
return <ToastContainer />;
|
||||
//Fetch wizardEnv to hydrate Repo components
|
||||
const fetchWizardEnv = async () => {
|
||||
try {
|
||||
const response = await fetch('/api/account/getWizardEnv', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-type': 'application/json',
|
||||
},
|
||||
});
|
||||
setWizardEnv((await response.json()).wizardEnv);
|
||||
} catch (error) {
|
||||
console.log('Fetching datas error');
|
||||
}
|
||||
};
|
||||
fetchWizardEnv();
|
||||
}, []);
|
||||
|
||||
////States
|
||||
const [displayRepoAdd, setDisplayRepoAdd] = useState(false);
|
||||
const [displayRepoEdit, setDisplayRepoEdit] = useState(false);
|
||||
const [wizardEnv, setWizardEnv] = useState({});
|
||||
|
||||
////Functions
|
||||
|
||||
//Firstly, check the availability of data and condition it.
|
||||
if (!data) {
|
||||
//Force mutate after login (force a API GET on /api/repo to load repoList)
|
||||
mutate('/api/repo');
|
||||
return <ShimmerRepoList />;
|
||||
}
|
||||
if (error) {
|
||||
toast.error('An error has occurred.', toastOptions);
|
||||
return <ToastContainer />;
|
||||
}
|
||||
if (data.status == 500) {
|
||||
toast.error('API Error !', toastOptions);
|
||||
return <ToastContainer />;
|
||||
}
|
||||
|
||||
//BUTTON : Display RepoManage component box for ADD
|
||||
const manageRepoAddHandler = () => {
|
||||
router.replace('/manage-repo/add');
|
||||
};
|
||||
|
||||
//BUTTON : Display RepoManage component box for EDIT
|
||||
const repoManageEditHandler = (id) => {
|
||||
router.replace('/manage-repo/edit/' + id);
|
||||
};
|
||||
|
||||
//BUTTON : Close RepoManage component box (when cross is clicked)
|
||||
const closeRepoManageBoxHandler = () => {
|
||||
router.replace('/');
|
||||
};
|
||||
|
||||
// UI EFFECT : Display blur when display add repo modale
|
||||
const displayBlur = () => {
|
||||
if (displayRepoAdd || displayRepoEdit) {
|
||||
return classes.containerBlur;
|
||||
} else {
|
||||
return classes.container;
|
||||
}
|
||||
};
|
||||
|
||||
//BUTTON : Display RepoManage component box for ADD
|
||||
const manageRepoAddHandler = () => {
|
||||
router.replace('/manage-repo/add');
|
||||
};
|
||||
|
||||
//BUTTON : Display RepoManage component box for EDIT
|
||||
const repoManageEditHandler = (id) => {
|
||||
router.replace('/manage-repo/edit/' + id);
|
||||
};
|
||||
|
||||
//BUTTON : Close RepoManage component box (when cross is clicked)
|
||||
const closeRepoManageBoxHandler = () => {
|
||||
router.replace('/');
|
||||
};
|
||||
|
||||
// UI EFFECT : Display blur when display add repo modale
|
||||
const displayBlur = () => {
|
||||
if (displayRepoAdd || displayRepoEdit) {
|
||||
return classes.containerBlur;
|
||||
} else {
|
||||
return classes.container;
|
||||
}
|
||||
};
|
||||
|
||||
//Dynamic list of repositories (with a map of Repo components)
|
||||
const renderRepoList = data.repoList.map((repo, index) => {
|
||||
return (
|
||||
<>
|
||||
<Repo
|
||||
key={repo.id}
|
||||
id={repo.id}
|
||||
alias={repo.alias}
|
||||
status={repo.status}
|
||||
lastSave={repo.lastSave}
|
||||
alert={repo.alert}
|
||||
repositoryName={repo.repositoryName}
|
||||
storageSize={repo.storageSize}
|
||||
storageUsed={repo.storageUsed}
|
||||
sshPublicKey={repo.sshPublicKey}
|
||||
comment={repo.comment}
|
||||
lanCommand={repo.lanCommand}
|
||||
appendOnlyMode={repo.appendOnlyMode}
|
||||
repoManageEditHandler={() => repoManageEditHandler(repo.id)}
|
||||
wizardEnv={wizardEnv}
|
||||
></Repo>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
//Dynamic list of repositories (with a map of Repo components)
|
||||
const renderRepoList = data.repoList.map((repo, index) => {
|
||||
return (
|
||||
<>
|
||||
<div className={displayBlur()}>
|
||||
<div className={classes.containerAddRepo}>
|
||||
<Link
|
||||
href='/manage-repo/add'
|
||||
className={classes.newRepoButton}
|
||||
onClick={manageRepoAddHandler}
|
||||
>
|
||||
<IconPlus
|
||||
className={classes.plusIcon}
|
||||
size={24}
|
||||
stroke={2}
|
||||
/>
|
||||
<span>Add a repository</span>
|
||||
</Link>
|
||||
</div>
|
||||
<div className={classes.containerRepoList}>
|
||||
<div className={classes.RepoList}>{renderRepoList}</div>
|
||||
</div>
|
||||
</div>
|
||||
{displayRepoAdd ? (
|
||||
<RepoManage
|
||||
mode='add'
|
||||
repoList={data.repoList}
|
||||
closeHandler={closeRepoManageBoxHandler}
|
||||
/>
|
||||
) : null}
|
||||
{displayRepoEdit ? (
|
||||
<RepoManage
|
||||
mode='edit'
|
||||
repoList={data.repoList}
|
||||
closeHandler={closeRepoManageBoxHandler}
|
||||
/>
|
||||
) : null}
|
||||
</>
|
||||
<>
|
||||
<Repo
|
||||
key={repo.id}
|
||||
id={repo.id}
|
||||
alias={repo.alias}
|
||||
status={repo.status}
|
||||
lastSave={repo.lastSave}
|
||||
alert={repo.alert}
|
||||
repositoryName={repo.repositoryName}
|
||||
storageSize={repo.storageSize}
|
||||
storageUsed={repo.storageUsed}
|
||||
sshPublicKey={repo.sshPublicKey}
|
||||
comment={repo.comment}
|
||||
lanCommand={repo.lanCommand}
|
||||
appendOnlyMode={repo.appendOnlyMode}
|
||||
repoManageEditHandler={() => repoManageEditHandler(repo.id)}
|
||||
wizardEnv={wizardEnv}
|
||||
></Repo>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={displayBlur()}>
|
||||
<div className={classes.containerAddRepo}>
|
||||
<Link
|
||||
href='/manage-repo/add'
|
||||
className={classes.newRepoButton}
|
||||
onClick={manageRepoAddHandler}
|
||||
>
|
||||
<IconPlus className={classes.plusIcon} size={24} stroke={2} />
|
||||
<span>Add a repository</span>
|
||||
</Link>
|
||||
</div>
|
||||
<div className={classes.containerRepoList}>
|
||||
<div className={classes.RepoList}>{renderRepoList}</div>
|
||||
</div>
|
||||
</div>
|
||||
{displayRepoAdd ? (
|
||||
<RepoManage mode='add' repoList={data.repoList} closeHandler={closeRepoManageBoxHandler} />
|
||||
) : null}
|
||||
{displayRepoEdit ? (
|
||||
<RepoManage mode='edit' repoList={data.repoList} closeHandler={closeRepoManageBoxHandler} />
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,125 +1,125 @@
|
|||
.container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.containerBlur {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
filter: blur(3px);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
filter: blur(3px);
|
||||
}
|
||||
|
||||
.containerRepoList {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.containerAddRepo {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin: 20px auto;
|
||||
width: 90%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin: 20px auto;
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
.newRepoButton {
|
||||
position: relative;
|
||||
margin: auto;
|
||||
padding: 19px 22px;
|
||||
transition: all 0.2s ease;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
position: relative;
|
||||
margin: auto;
|
||||
padding: 19px 22px;
|
||||
transition: all 0.2s ease;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
}
|
||||
.newRepoButton:before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
left: 3px;
|
||||
display: block;
|
||||
border-radius: 28px;
|
||||
background: #6d4aff;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
transition: all 0.3s ease;
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
left: 3px;
|
||||
display: block;
|
||||
border-radius: 28px;
|
||||
background: #6d4aff;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.newRepoButton span {
|
||||
position: relative;
|
||||
font-size: 16px;
|
||||
line-height: 18px;
|
||||
font-weight: 600;
|
||||
vertical-align: middle;
|
||||
padding-left: 15px;
|
||||
color: #494b7a;
|
||||
position: relative;
|
||||
font-size: 16px;
|
||||
line-height: 18px;
|
||||
font-weight: 600;
|
||||
vertical-align: middle;
|
||||
padding-left: 15px;
|
||||
color: #494b7a;
|
||||
}
|
||||
.newRepoButton .plusIcon {
|
||||
position: relative;
|
||||
top: 0;
|
||||
fill: none;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: round;
|
||||
stroke: #fff;
|
||||
transform: translateX(-5px);
|
||||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
top: 0;
|
||||
fill: none;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: round;
|
||||
stroke: #fff;
|
||||
transform: translateX(-5px);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.newRepoButton:hover:before {
|
||||
width: 100%;
|
||||
background: #6d4aff;
|
||||
width: 100%;
|
||||
background: #6d4aff;
|
||||
}
|
||||
.newRepoButton:hover span {
|
||||
color: #fff;
|
||||
color: #fff;
|
||||
}
|
||||
.newRepoButton:hover .plusIcon {
|
||||
transform: translateX(0);
|
||||
transform: translateX(0);
|
||||
}
|
||||
.newRepoButton:active {
|
||||
transform: scale(0.96);
|
||||
transform: scale(0.96);
|
||||
}
|
||||
|
||||
.RepoList {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 90%;
|
||||
margin: 5px auto;
|
||||
padding: 15px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 90%;
|
||||
margin: 5px auto;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.unfoldButton {
|
||||
cursor: pointer;
|
||||
position: sticky;
|
||||
color: #a6a6b8;
|
||||
padding-top: 49px;
|
||||
align-self: flex-start;
|
||||
top: 0;
|
||||
cursor: pointer;
|
||||
position: sticky;
|
||||
color: #a6a6b8;
|
||||
padding-top: 49px;
|
||||
align-self: flex-start;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.foldButton {
|
||||
cursor: pointer;
|
||||
position: sticky;
|
||||
color: #a6a6b8;
|
||||
padding-top: 49px;
|
||||
align-self: flex-start;
|
||||
top: 0;
|
||||
cursor: pointer;
|
||||
position: sticky;
|
||||
color: #a6a6b8;
|
||||
padding-top: 49px;
|
||||
align-self: flex-start;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.unfoldButton:active,
|
||||
.foldButton:active {
|
||||
transform: scale(0.96);
|
||||
transform: scale(0.96);
|
||||
}
|
||||
|
||||
@media all and (max-width: 1000px) {
|
||||
.newRepoButton {
|
||||
display: none;
|
||||
}
|
||||
.chevron {
|
||||
display: none;
|
||||
}
|
||||
.containerAddRepo {
|
||||
display: none;
|
||||
}
|
||||
.newRepoButton {
|
||||
display: none;
|
||||
}
|
||||
.chevron {
|
||||
display: none;
|
||||
}
|
||||
.containerAddRepo {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,268 +1,268 @@
|
|||
.modaleWrapper {
|
||||
position: fixed;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 50px 0px 0px 70px;
|
||||
position: fixed;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 50px 0px 0px 70px;
|
||||
}
|
||||
|
||||
.modale {
|
||||
position: fixed;
|
||||
top: 10%;
|
||||
width: 1000px;
|
||||
height: auto;
|
||||
max-width: 75%;
|
||||
max-height: 85%;
|
||||
background: #fff;
|
||||
padding: 20px 20px 20px;
|
||||
overflow: auto;
|
||||
border-radius: 10px;
|
||||
box-shadow: 1px 2px 6px rgba(0, 0, 0, 0.4);
|
||||
margin: 0 auto;
|
||||
animation: append-animate 0.3s linear;
|
||||
position: fixed;
|
||||
top: 10%;
|
||||
width: 1000px;
|
||||
height: auto;
|
||||
max-width: 75%;
|
||||
max-height: 85%;
|
||||
background: #fff;
|
||||
padding: 20px 20px 20px;
|
||||
overflow: auto;
|
||||
border-radius: 10px;
|
||||
box-shadow: 1px 2px 6px rgba(0, 0, 0, 0.4);
|
||||
margin: 0 auto;
|
||||
animation: append-animate 0.3s linear;
|
||||
}
|
||||
|
||||
@keyframes append-animate {
|
||||
from {
|
||||
transform: scale(0);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
from {
|
||||
transform: scale(0);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.close {
|
||||
cursor: pointer;
|
||||
margin-left: 95%;
|
||||
color: #494b7a;
|
||||
cursor: pointer;
|
||||
margin-left: 95%;
|
||||
color: #494b7a;
|
||||
}
|
||||
|
||||
.close :hover {
|
||||
color: #aa60ff;
|
||||
color: #aa60ff;
|
||||
}
|
||||
|
||||
.repoManageForm {
|
||||
margin: auto;
|
||||
width: 80%;
|
||||
padding: 15px 30px 30px 30px;
|
||||
border-radius: 5px;
|
||||
text-align: left;
|
||||
margin: auto;
|
||||
width: 80%;
|
||||
padding: 15px 30px 30px 30px;
|
||||
border-radius: 5px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.formWrapper {
|
||||
text-align: center;
|
||||
margin: auto;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
color: #494b7a;
|
||||
text-align: center;
|
||||
margin: auto;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
color: #494b7a;
|
||||
}
|
||||
|
||||
.repoManageForm label {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
text-align: center;
|
||||
margin-top: 20px;
|
||||
color: #494b7a;
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
text-align: center;
|
||||
margin-top: 20px;
|
||||
color: #494b7a;
|
||||
}
|
||||
|
||||
.repoManageForm input,
|
||||
.repoManageForm textarea,
|
||||
.repoManageForm select {
|
||||
border: 1px solid #6d4aff21;
|
||||
font-size: 16px;
|
||||
height: auto;
|
||||
margin: 0;
|
||||
margin-bottom: 0px;
|
||||
outline: 0;
|
||||
padding: 15px;
|
||||
width: 100%;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 5px;
|
||||
/* color: #1b1340; */
|
||||
color: #494b7a;
|
||||
box-shadow: 0 1px 0 rgba(0, 0, 0, 0.03) inset;
|
||||
border: 1px solid #6d4aff21;
|
||||
font-size: 16px;
|
||||
height: auto;
|
||||
margin: 0;
|
||||
margin-bottom: 0px;
|
||||
outline: 0;
|
||||
padding: 15px;
|
||||
width: 100%;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 5px;
|
||||
/* color: #1b1340; */
|
||||
color: #494b7a;
|
||||
box-shadow: 0 1px 0 rgba(0, 0, 0, 0.03) inset;
|
||||
}
|
||||
|
||||
.repoManageForm textarea {
|
||||
resize: vertical;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
.repoManageForm textarea:focus,
|
||||
.repoManageForm input:focus,
|
||||
.repoManageForm select:focus {
|
||||
outline: 1px solid #6d4aff;
|
||||
box-shadow: 0 0 10px 3px rgba(110, 74, 255, 0.605);
|
||||
outline: 1px solid #6d4aff;
|
||||
box-shadow: 0 0 10px 3px rgba(110, 74, 255, 0.605);
|
||||
}
|
||||
|
||||
.repoManageForm .invalid {
|
||||
background: #f3c7c7;
|
||||
border: 1px solid #e45454;
|
||||
outline: 1px solid #ff4a4a;
|
||||
background: #f3c7c7;
|
||||
border: 1px solid #e45454;
|
||||
outline: 1px solid #ff4a4a;
|
||||
}
|
||||
|
||||
.repoManageForm .invalid:focus {
|
||||
background: #f3c7c7;
|
||||
border: 1px solid #e45454;
|
||||
outline: 1px solid #ff4a4a;
|
||||
box-shadow: 0 0 10px 3px rgba(255, 74, 74, 0.605);
|
||||
background: #f3c7c7;
|
||||
border: 1px solid #e45454;
|
||||
outline: 1px solid #ff4a4a;
|
||||
box-shadow: 0 0 10px 3px rgba(255, 74, 74, 0.605);
|
||||
}
|
||||
|
||||
.repoManageForm button {
|
||||
display: block;
|
||||
margin: 15px auto;
|
||||
display: block;
|
||||
margin: 15px auto;
|
||||
}
|
||||
.repoManageForm button:hover {
|
||||
display: block;
|
||||
margin: 15px auto;
|
||||
display: block;
|
||||
margin: 15px auto;
|
||||
}
|
||||
|
||||
.errorMessage {
|
||||
color: red;
|
||||
display: block;
|
||||
margin-top: 3px;
|
||||
color: red;
|
||||
display: block;
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
||||
.optionCommandWrapper {
|
||||
display: flex;
|
||||
margin-top: 20px;
|
||||
color: #494b7a;
|
||||
display: flex;
|
||||
margin-top: 20px;
|
||||
color: #494b7a;
|
||||
}
|
||||
|
||||
.optionCommandWrapper label {
|
||||
margin: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.optionCommandWrapper input[type='checkbox'] {
|
||||
width: auto;
|
||||
margin-right: 8px;
|
||||
cursor: pointer;
|
||||
accent-color: #6d4aff;
|
||||
width: auto;
|
||||
margin-right: 8px;
|
||||
cursor: pointer;
|
||||
accent-color: #6d4aff;
|
||||
}
|
||||
.optionCommandWrapper input[type='checkbox']:focus {
|
||||
outline: 0;
|
||||
box-shadow: none;
|
||||
accent-color: #6d4aff;
|
||||
outline: 0;
|
||||
box-shadow: none;
|
||||
accent-color: #6d4aff;
|
||||
}
|
||||
|
||||
/* DELETE DIALOG */
|
||||
|
||||
.deleteDialogWrapper {
|
||||
text-align: center;
|
||||
margin: auto;
|
||||
width: 80%;
|
||||
height: 100%;
|
||||
max-height: 590px;
|
||||
color: #111827;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
margin: auto;
|
||||
width: 80%;
|
||||
height: 100%;
|
||||
max-height: 590px;
|
||||
color: #111827;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.deleteDialogMessage {
|
||||
background-color: #ea1313;
|
||||
color: #fff;
|
||||
font-weight: 500;
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
margin-top: 15px;
|
||||
margin-bottom: 15px;
|
||||
font-size: 1.1em;
|
||||
background-color: #ea1313;
|
||||
color: #fff;
|
||||
font-weight: 500;
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
margin-top: 15px;
|
||||
margin-bottom: 15px;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
.cancelButton {
|
||||
border: 0;
|
||||
padding: 10px 15px;
|
||||
background-color: #c1c1c1;
|
||||
color: white;
|
||||
margin: 5px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
font-size: 1em;
|
||||
border: 0;
|
||||
padding: 10px 15px;
|
||||
background-color: #c1c1c1;
|
||||
color: white;
|
||||
margin: 5px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.cancelButton:hover {
|
||||
border: 0;
|
||||
padding: 10px 15px;
|
||||
background-color: #9a9a9a;
|
||||
color: white;
|
||||
margin: 5px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
font-size: 1em;
|
||||
border: 0;
|
||||
padding: 10px 15px;
|
||||
background-color: #9a9a9a;
|
||||
color: white;
|
||||
margin: 5px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.cancelButton:active {
|
||||
border: 0;
|
||||
padding: 10px 15px;
|
||||
background-color: #9a9a9a;
|
||||
color: white;
|
||||
margin: 5px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
font-size: 1em;
|
||||
transform: scale(0.9);
|
||||
border: 0;
|
||||
padding: 10px 15px;
|
||||
background-color: #9a9a9a;
|
||||
color: white;
|
||||
margin: 5px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
font-size: 1em;
|
||||
transform: scale(0.9);
|
||||
}
|
||||
|
||||
.deleteButton {
|
||||
border: 0;
|
||||
padding: 10px 15px;
|
||||
background-color: #ff0000;
|
||||
color: white;
|
||||
margin: 5px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
font-size: 1em;
|
||||
border: 0;
|
||||
padding: 10px 15px;
|
||||
background-color: #ff0000;
|
||||
color: white;
|
||||
margin: 5px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.deleteButton:hover {
|
||||
border: 0;
|
||||
padding: 10px 15px;
|
||||
background-color: #ff4b4b;
|
||||
color: white;
|
||||
margin: 5px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
font-size: 1em;
|
||||
border: 0;
|
||||
padding: 10px 15px;
|
||||
background-color: #ff4b4b;
|
||||
color: white;
|
||||
margin: 5px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.deleteButton:active {
|
||||
border: 0;
|
||||
padding: 10px 15px;
|
||||
background-color: #ff4b4b;
|
||||
color: white;
|
||||
margin: 5px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
font-size: 1em;
|
||||
transform: scale(0.9);
|
||||
border: 0;
|
||||
padding: 10px 15px;
|
||||
background-color: #ff4b4b;
|
||||
color: white;
|
||||
margin: 5px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
font-size: 1em;
|
||||
transform: scale(0.9);
|
||||
}
|
||||
|
||||
.littleDeleteButton {
|
||||
border: none;
|
||||
font-weight: 300;
|
||||
color: red;
|
||||
text-decoration: underline;
|
||||
background: none;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
font-weight: 300;
|
||||
color: red;
|
||||
text-decoration: underline;
|
||||
background: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.selectAlert {
|
||||
margin: auto auto 35px auto;
|
||||
max-width: 160px;
|
||||
margin: auto auto 35px auto;
|
||||
max-width: 160px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,147 +13,132 @@ import WizardStep3 from '../../Components/WizardSteps/WizardStep3/WizardStep3';
|
|||
import WizardStep4 from '../../Components/WizardSteps/WizardStep4/WizardStep4';
|
||||
|
||||
function SetupWizard(props) {
|
||||
////Var
|
||||
const router = useRouter();
|
||||
////Var
|
||||
const router = useRouter();
|
||||
|
||||
////States
|
||||
const [list, setList] = useState([]);
|
||||
const [listIsLoading, setListIsLoading] = useState(true);
|
||||
const [step, setStep] = useState();
|
||||
const [wizardEnv, setWizardEnv] = useState({});
|
||||
const [selectedOption, setSelectedOption] = useState({
|
||||
id: '#id',
|
||||
repository: 'repo',
|
||||
});
|
||||
////States
|
||||
const [list, setList] = useState([]);
|
||||
const [listIsLoading, setListIsLoading] = useState(true);
|
||||
const [step, setStep] = useState();
|
||||
const [wizardEnv, setWizardEnv] = useState({});
|
||||
const [selectedOption, setSelectedOption] = useState({
|
||||
id: '#id',
|
||||
repository: 'repo',
|
||||
});
|
||||
|
||||
////LifeCycle
|
||||
//ComponentDidMount
|
||||
useEffect(() => {
|
||||
//retrieve the repository list
|
||||
const repoList = async () => {
|
||||
try {
|
||||
const response = await fetch('/api/repo', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-type': 'application/json',
|
||||
},
|
||||
});
|
||||
setList((await response.json()).repoList);
|
||||
setListIsLoading(false);
|
||||
} catch (error) {
|
||||
console.log('Fetching datas error');
|
||||
}
|
||||
};
|
||||
repoList();
|
||||
//Fetch wizardEnv to hydrate Wizard' steps
|
||||
const fetchWizardEnv = async () => {
|
||||
try {
|
||||
const response = await fetch('/api/account/getWizardEnv', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-type': 'application/json',
|
||||
},
|
||||
});
|
||||
setWizardEnv((await response.json()).wizardEnv);
|
||||
} catch (error) {
|
||||
console.log('Fetching datas error');
|
||||
}
|
||||
};
|
||||
fetchWizardEnv();
|
||||
}, []);
|
||||
//Component did update
|
||||
useEffect(() => {
|
||||
//Go to the step in the URL param when URL change
|
||||
setStep(props.step);
|
||||
}, [props.step]);
|
||||
|
||||
////Functions
|
||||
|
||||
//Options for react-select
|
||||
const options = list.map((repo) => ({
|
||||
label: `${repo.alias} - #${repo.id}`,
|
||||
value: `${repo.alias} - #${repo.id}`,
|
||||
id: repo.id,
|
||||
repositoryName: repo.repositoryName,
|
||||
lanCommand: repo.lanCommand,
|
||||
}));
|
||||
|
||||
//Step button (free selection of user)
|
||||
const changeStepHandler = (x) => router.push('/setup-wizard/' + x);
|
||||
|
||||
//Next Step button
|
||||
const nextStepHandler = () => {
|
||||
if (step < 4) {
|
||||
router.push('/setup-wizard/' + `${Number(step) + 1}`);
|
||||
}
|
||||
////LifeCycle
|
||||
//ComponentDidMount
|
||||
useEffect(() => {
|
||||
//retrieve the repository list
|
||||
const repoList = async () => {
|
||||
try {
|
||||
const response = await fetch('/api/repo', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-type': 'application/json',
|
||||
},
|
||||
});
|
||||
setList((await response.json()).repoList);
|
||||
setListIsLoading(false);
|
||||
} catch (error) {
|
||||
console.log('Fetching datas error');
|
||||
}
|
||||
};
|
||||
|
||||
//Previous Step button
|
||||
const previousStepHandler = () => {
|
||||
if (step > 1) {
|
||||
router.push('/setup-wizard/' + `${Number(step) - 1}`);
|
||||
}
|
||||
repoList();
|
||||
//Fetch wizardEnv to hydrate Wizard' steps
|
||||
const fetchWizardEnv = async () => {
|
||||
try {
|
||||
const response = await fetch('/api/account/getWizardEnv', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-type': 'application/json',
|
||||
},
|
||||
});
|
||||
setWizardEnv((await response.json()).wizardEnv);
|
||||
} catch (error) {
|
||||
console.log('Fetching datas error');
|
||||
}
|
||||
};
|
||||
fetchWizardEnv();
|
||||
}, []);
|
||||
//Component did update
|
||||
useEffect(() => {
|
||||
//Go to the step in the URL param when URL change
|
||||
setStep(props.step);
|
||||
}, [props.step]);
|
||||
|
||||
//Change Step with State
|
||||
const wizardStep = (step) => {
|
||||
if (step == 1) {
|
||||
return <WizardStep1 />;
|
||||
} else if (step == 2) {
|
||||
return (
|
||||
<WizardStep2
|
||||
selectedOption={selectedOption}
|
||||
wizardEnv={wizardEnv}
|
||||
/>
|
||||
);
|
||||
} else if (step == 3) {
|
||||
return (
|
||||
<WizardStep3
|
||||
selectedOption={selectedOption}
|
||||
wizardEnv={wizardEnv}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<WizardStep4
|
||||
selectedOption={selectedOption}
|
||||
wizardEnv={wizardEnv}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
////Functions
|
||||
|
||||
return (
|
||||
<div className={classes.container}>
|
||||
<WizardStepBar
|
||||
setStep={(x) => changeStepHandler(x)}
|
||||
step={step}
|
||||
nextStepHandler={() => nextStepHandler()}
|
||||
previousStepHandler={() => previousStepHandler()}
|
||||
/>
|
||||
<div className={classes.selectRepo}>
|
||||
<Select
|
||||
onChange={setSelectedOption}
|
||||
isLoading={listIsLoading}
|
||||
isDisabled={listIsLoading}
|
||||
options={options}
|
||||
isSearchable
|
||||
placeholder='Select your repository...'
|
||||
theme={(theme) => ({
|
||||
...theme,
|
||||
borderRadius: '5px',
|
||||
colors: {
|
||||
...theme.colors,
|
||||
primary25: '#c3b6fa',
|
||||
primary: '#6d4aff',
|
||||
},
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
//Options for react-select
|
||||
const options = list.map((repo) => ({
|
||||
label: `${repo.alias} - #${repo.id}`,
|
||||
value: `${repo.alias} - #${repo.id}`,
|
||||
id: repo.id,
|
||||
repositoryName: repo.repositoryName,
|
||||
lanCommand: repo.lanCommand,
|
||||
}));
|
||||
|
||||
{wizardStep(step)}
|
||||
</div>
|
||||
);
|
||||
//Step button (free selection of user)
|
||||
const changeStepHandler = (x) => router.push('/setup-wizard/' + x);
|
||||
|
||||
//Next Step button
|
||||
const nextStepHandler = () => {
|
||||
if (step < 4) {
|
||||
router.push('/setup-wizard/' + `${Number(step) + 1}`);
|
||||
}
|
||||
};
|
||||
|
||||
//Previous Step button
|
||||
const previousStepHandler = () => {
|
||||
if (step > 1) {
|
||||
router.push('/setup-wizard/' + `${Number(step) - 1}`);
|
||||
}
|
||||
};
|
||||
|
||||
//Change Step with State
|
||||
const wizardStep = (step) => {
|
||||
if (step == 1) {
|
||||
return <WizardStep1 />;
|
||||
} else if (step == 2) {
|
||||
return <WizardStep2 selectedOption={selectedOption} wizardEnv={wizardEnv} />;
|
||||
} else if (step == 3) {
|
||||
return <WizardStep3 selectedOption={selectedOption} wizardEnv={wizardEnv} />;
|
||||
} else {
|
||||
return <WizardStep4 selectedOption={selectedOption} wizardEnv={wizardEnv} />;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={classes.container}>
|
||||
<WizardStepBar
|
||||
setStep={(x) => changeStepHandler(x)}
|
||||
step={step}
|
||||
nextStepHandler={() => nextStepHandler()}
|
||||
previousStepHandler={() => previousStepHandler()}
|
||||
/>
|
||||
<div className={classes.selectRepo}>
|
||||
<Select
|
||||
onChange={setSelectedOption}
|
||||
isLoading={listIsLoading}
|
||||
isDisabled={listIsLoading}
|
||||
options={options}
|
||||
isSearchable
|
||||
placeholder='Select your repository...'
|
||||
theme={(theme) => ({
|
||||
...theme,
|
||||
borderRadius: '5px',
|
||||
colors: {
|
||||
...theme.colors,
|
||||
primary25: '#c3b6fa',
|
||||
primary: '#6d4aff',
|
||||
},
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{wizardStep(step)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default SetupWizard;
|
||||
|
|
|
|||
|
|
@ -1,28 +1,28 @@
|
|||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
color: #494b7a;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
color: #494b7a;
|
||||
}
|
||||
.container a {
|
||||
text-decoration: none;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.wizardStepTransition-enter {
|
||||
opacity: 0;
|
||||
opacity: 0;
|
||||
}
|
||||
.wizardStepTransition-enter-active {
|
||||
opacity: 1;
|
||||
transition: opacity 200ms;
|
||||
opacity: 1;
|
||||
transition: opacity 200ms;
|
||||
}
|
||||
.wizardStepTransition-exit {
|
||||
opacity: 1;
|
||||
opacity: 1;
|
||||
}
|
||||
.wizardStepTransition-exit-active {
|
||||
opacity: 0;
|
||||
transition: opacity 200ms;
|
||||
opacity: 0;
|
||||
transition: opacity 200ms;
|
||||
}
|
||||
.selectRepo {
|
||||
margin: 30px auto auto auto;
|
||||
width: 300px;
|
||||
margin: 30px auto auto auto;
|
||||
width: 300px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,206 +15,194 @@ import AppriseURLs from './AppriseURLs/AppriseURLs';
|
|||
import AppriseMode from './AppriseMode/AppriseMode';
|
||||
|
||||
export default function AppriseAlertSettings() {
|
||||
//Var
|
||||
const toastOptions = {
|
||||
position: 'top-right',
|
||||
autoClose: 5000,
|
||||
hideProgressBar: false,
|
||||
closeOnClick: true,
|
||||
pauseOnHover: true,
|
||||
draggable: true,
|
||||
progress: undefined,
|
||||
//Callback > re-enabled button after notification.
|
||||
onClose: () => setDisabled(false),
|
||||
//Var
|
||||
const toastOptions = {
|
||||
position: 'top-right',
|
||||
autoClose: 5000,
|
||||
hideProgressBar: false,
|
||||
closeOnClick: true,
|
||||
pauseOnHover: true,
|
||||
draggable: true,
|
||||
progress: undefined,
|
||||
//Callback > re-enabled button after notification.
|
||||
onClose: () => setDisabled(false),
|
||||
};
|
||||
|
||||
////State
|
||||
const [checkIsLoading, setCheckIsLoading] = useState(true);
|
||||
const [error, setError] = useState();
|
||||
const [disabled, setDisabled] = useState(false);
|
||||
const [checked, setChecked] = useState();
|
||||
const [testIsLoading, setTestIsLoading] = useState(false);
|
||||
const [info, setInfo] = useState(false);
|
||||
|
||||
////LifeCycle
|
||||
//Component did mount
|
||||
useEffect(() => {
|
||||
//Initial fetch to get the status of Apprise Alert
|
||||
const getAppriseAlert = async () => {
|
||||
try {
|
||||
const response = await fetch('/api/account/getAppriseAlert', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-type': 'application/json',
|
||||
},
|
||||
});
|
||||
setChecked((await response.json()).appriseAlert);
|
||||
setCheckIsLoading(false);
|
||||
} catch (error) {
|
||||
setError('Fetching apprise alert setting failed. Contact your administrator.');
|
||||
console.log('Fetching apprise alert setting failed.');
|
||||
setCheckIsLoading(false);
|
||||
}
|
||||
};
|
||||
getAppriseAlert();
|
||||
}, []);
|
||||
|
||||
////State
|
||||
const [checkIsLoading, setCheckIsLoading] = useState(true);
|
||||
const [error, setError] = useState();
|
||||
const [disabled, setDisabled] = useState(false);
|
||||
const [checked, setChecked] = useState();
|
||||
const [testIsLoading, setTestIsLoading] = useState(false);
|
||||
const [info, setInfo] = useState(false);
|
||||
|
||||
////LifeCycle
|
||||
//Component did mount
|
||||
useEffect(() => {
|
||||
//Initial fetch to get the status of Apprise Alert
|
||||
const getAppriseAlert = async () => {
|
||||
try {
|
||||
const response = await fetch('/api/account/getAppriseAlert', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-type': 'application/json',
|
||||
},
|
||||
});
|
||||
setChecked((await response.json()).appriseAlert);
|
||||
setCheckIsLoading(false);
|
||||
} catch (error) {
|
||||
setError(
|
||||
'Fetching apprise alert setting failed. Contact your administrator.'
|
||||
);
|
||||
console.log('Fetching apprise alert setting failed.');
|
||||
setCheckIsLoading(false);
|
||||
}
|
||||
};
|
||||
getAppriseAlert();
|
||||
}, []);
|
||||
|
||||
////Functions
|
||||
//Switch to enable/disable Apprise notifications
|
||||
const onChangeSwitchHandler = async (data) => {
|
||||
//Remove old error
|
||||
setError();
|
||||
//Disabled button
|
||||
setDisabled(true);
|
||||
await fetch('/api/account/updateAppriseAlert', {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(data),
|
||||
})
|
||||
.then((response) => {
|
||||
console.log(response);
|
||||
if (response.ok) {
|
||||
if (data.appriseAlert) {
|
||||
setChecked(!checked);
|
||||
toast.success(
|
||||
'Apprise notifications enabled.',
|
||||
toastOptions
|
||||
);
|
||||
} else {
|
||||
setChecked(!checked);
|
||||
toast.success(
|
||||
'Apprise notifications disabled.',
|
||||
toastOptions
|
||||
);
|
||||
}
|
||||
} else {
|
||||
setError('Update apprise alert setting failed.');
|
||||
setTimeout(() => {
|
||||
setError();
|
||||
setDisabled(false);
|
||||
}, 4000);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error);
|
||||
setError('Update Apprise failed. Contact your administrator.');
|
||||
setTimeout(() => {
|
||||
setError();
|
||||
setDisabled(false);
|
||||
}, 4000);
|
||||
});
|
||||
};
|
||||
|
||||
//Send Apprise test notification to services
|
||||
const onSendTestAppriseHandler = async () => {
|
||||
//Loading
|
||||
setTestIsLoading(true);
|
||||
//Remove old error
|
||||
setError();
|
||||
try {
|
||||
const response = await fetch('/api/account/sendTestApprise', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ sendTestApprise: true }),
|
||||
});
|
||||
const result = await response.json();
|
||||
if (!response.ok) {
|
||||
setTestIsLoading(false);
|
||||
setError(result.message);
|
||||
} else {
|
||||
setTestIsLoading(false);
|
||||
setInfo(true);
|
||||
setTimeout(() => {
|
||||
setInfo(false);
|
||||
}, 4000);
|
||||
}
|
||||
} catch (error) {
|
||||
setTestIsLoading(false);
|
||||
console.log(error);
|
||||
setError('Send notification failed. Contact your administrator.');
|
||||
setTimeout(() => {
|
||||
setError();
|
||||
}, 4000);
|
||||
////Functions
|
||||
//Switch to enable/disable Apprise notifications
|
||||
const onChangeSwitchHandler = async (data) => {
|
||||
//Remove old error
|
||||
setError();
|
||||
//Disabled button
|
||||
setDisabled(true);
|
||||
await fetch('/api/account/updateAppriseAlert', {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(data),
|
||||
})
|
||||
.then((response) => {
|
||||
console.log(response);
|
||||
if (response.ok) {
|
||||
if (data.appriseAlert) {
|
||||
setChecked(!checked);
|
||||
toast.success('Apprise notifications enabled.', toastOptions);
|
||||
} else {
|
||||
setChecked(!checked);
|
||||
toast.success('Apprise notifications disabled.', toastOptions);
|
||||
}
|
||||
} else {
|
||||
setError('Update apprise alert setting failed.');
|
||||
setTimeout(() => {
|
||||
setError();
|
||||
setDisabled(false);
|
||||
}, 4000);
|
||||
}
|
||||
};
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error);
|
||||
setError('Update Apprise failed. Contact your administrator.');
|
||||
setTimeout(() => {
|
||||
setError();
|
||||
setDisabled(false);
|
||||
}, 4000);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* APPRISE ALERT */}
|
||||
<div className={classes.containerSetting}>
|
||||
<div className={classes.settingCategory}>
|
||||
<h2 style={{ alignSelf: 'baseline' }}>Apprise alert</h2>
|
||||
<Link
|
||||
style={{ alignSelf: 'baseline', marginLeft: '5px' }}
|
||||
href='https://borgwarehouse.com/docs/user-manual/account/#apprise'
|
||||
rel='noreferrer'
|
||||
target='_blank'
|
||||
>
|
||||
<IconExternalLink size={16} color='#6c737f' />
|
||||
</Link>
|
||||
</div>
|
||||
<div className={classes.setting}>
|
||||
<div className={classes.bwFormWrapper}>
|
||||
{/* NOTIFY SWITCH */}
|
||||
{checkIsLoading ? (
|
||||
<SpinnerCircularFixed
|
||||
size={30}
|
||||
thickness={150}
|
||||
speed={150}
|
||||
color='#704dff'
|
||||
secondaryColor='#c3b6fa'
|
||||
/>
|
||||
) : (
|
||||
<Switch
|
||||
checked={checked}
|
||||
disabled={disabled}
|
||||
switchName='Notify my Apprise services'
|
||||
switchDescription='You will receive an alert on all your services every 24H if you have a down status.'
|
||||
onChange={(e) =>
|
||||
onChangeSwitchHandler({ appriseAlert: e })
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{/* APPRISE SERVICES URLS */}
|
||||
<AppriseURLs />
|
||||
{/* APPRISE MODE SELECTION */}
|
||||
<AppriseMode />
|
||||
{/* APPRISE TEST BUTTON */}
|
||||
{testIsLoading ? (
|
||||
<SpinnerCircularFixed
|
||||
style={{ marginTop: '20px' }}
|
||||
size={30}
|
||||
thickness={150}
|
||||
speed={150}
|
||||
color='#704dff'
|
||||
secondaryColor='#c3b6fa'
|
||||
/>
|
||||
) : (
|
||||
<button
|
||||
style={{ marginTop: '20px' }}
|
||||
className='defaultButton'
|
||||
onClick={() => onSendTestAppriseHandler()}
|
||||
>
|
||||
Send a test notification
|
||||
</button>
|
||||
)}
|
||||
{info && (
|
||||
<span
|
||||
style={{ marginLeft: '10px', color: '#119300' }}
|
||||
>
|
||||
Notification successfully sent.
|
||||
</span>
|
||||
)}
|
||||
{error && <Error message={error} />}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
//Send Apprise test notification to services
|
||||
const onSendTestAppriseHandler = async () => {
|
||||
//Loading
|
||||
setTestIsLoading(true);
|
||||
//Remove old error
|
||||
setError();
|
||||
try {
|
||||
const response = await fetch('/api/account/sendTestApprise', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ sendTestApprise: true }),
|
||||
});
|
||||
const result = await response.json();
|
||||
if (!response.ok) {
|
||||
setTestIsLoading(false);
|
||||
setError(result.message);
|
||||
} else {
|
||||
setTestIsLoading(false);
|
||||
setInfo(true);
|
||||
setTimeout(() => {
|
||||
setInfo(false);
|
||||
}, 4000);
|
||||
}
|
||||
} catch (error) {
|
||||
setTestIsLoading(false);
|
||||
console.log(error);
|
||||
setError('Send notification failed. Contact your administrator.');
|
||||
setTimeout(() => {
|
||||
setError();
|
||||
}, 4000);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* APPRISE ALERT */}
|
||||
<div className={classes.containerSetting}>
|
||||
<div className={classes.settingCategory}>
|
||||
<h2 style={{ alignSelf: 'baseline' }}>Apprise alert</h2>
|
||||
<Link
|
||||
style={{ alignSelf: 'baseline', marginLeft: '5px' }}
|
||||
href='https://borgwarehouse.com/docs/user-manual/account/#apprise'
|
||||
rel='noreferrer'
|
||||
target='_blank'
|
||||
>
|
||||
<IconExternalLink size={16} color='#6c737f' />
|
||||
</Link>
|
||||
</div>
|
||||
<div className={classes.setting}>
|
||||
<div className={classes.bwFormWrapper}>
|
||||
{/* NOTIFY SWITCH */}
|
||||
{checkIsLoading ? (
|
||||
<SpinnerCircularFixed
|
||||
size={30}
|
||||
thickness={150}
|
||||
speed={150}
|
||||
color='#704dff'
|
||||
secondaryColor='#c3b6fa'
|
||||
/>
|
||||
) : (
|
||||
<Switch
|
||||
checked={checked}
|
||||
disabled={disabled}
|
||||
switchName='Notify my Apprise services'
|
||||
switchDescription='You will receive an alert on all your services every 24H if you have a down status.'
|
||||
onChange={(e) => onChangeSwitchHandler({ appriseAlert: e })}
|
||||
/>
|
||||
)}
|
||||
{/* APPRISE SERVICES URLS */}
|
||||
<AppriseURLs />
|
||||
{/* APPRISE MODE SELECTION */}
|
||||
<AppriseMode />
|
||||
{/* APPRISE TEST BUTTON */}
|
||||
{testIsLoading ? (
|
||||
<SpinnerCircularFixed
|
||||
style={{ marginTop: '20px' }}
|
||||
size={30}
|
||||
thickness={150}
|
||||
speed={150}
|
||||
color='#704dff'
|
||||
secondaryColor='#c3b6fa'
|
||||
/>
|
||||
) : (
|
||||
<button
|
||||
style={{ marginTop: '20px' }}
|
||||
className='defaultButton'
|
||||
onClick={() => onSendTestAppriseHandler()}
|
||||
>
|
||||
Send a test notification
|
||||
</button>
|
||||
)}
|
||||
{info && (
|
||||
<span style={{ marginLeft: '10px', color: '#119300' }}>
|
||||
Notification successfully sent.
|
||||
</span>
|
||||
)}
|
||||
{error && <Error message={error} />}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,165 +9,153 @@ import { useForm } from 'react-hook-form';
|
|||
import Error from '../../../../Components/UI/Error/Error';
|
||||
|
||||
export default function AppriseMode() {
|
||||
//Var
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
formState: { errors },
|
||||
} = useForm({ mode: 'onBlur' });
|
||||
//Var
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
formState: { errors },
|
||||
} = useForm({ mode: 'onBlur' });
|
||||
|
||||
////State
|
||||
const [formIsLoading, setFormIsLoading] = useState(false);
|
||||
const [modeFormIsSaved, setModeFormIsSaved] = useState(false);
|
||||
const [error, setError] = useState(false);
|
||||
const [displayStatelessURL, setDisplayStatelessURL] = useState(false);
|
||||
const [appriseMode, setAppriseMode] = useState('stateless');
|
||||
const [appriseStatelessURL, setAppriseStatelessURL] = useState();
|
||||
////State
|
||||
const [formIsLoading, setFormIsLoading] = useState(false);
|
||||
const [modeFormIsSaved, setModeFormIsSaved] = useState(false);
|
||||
const [error, setError] = useState(false);
|
||||
const [displayStatelessURL, setDisplayStatelessURL] = useState(false);
|
||||
const [appriseMode, setAppriseMode] = useState('stateless');
|
||||
const [appriseStatelessURL, setAppriseStatelessURL] = useState();
|
||||
|
||||
////LifeCycle
|
||||
//Component did mount
|
||||
useEffect(() => {
|
||||
//Initial fetch to get Apprise Mode enabled
|
||||
const getAppriseMode = async () => {
|
||||
try {
|
||||
const response = await fetch('/api/account/getAppriseMode', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-type': 'application/json',
|
||||
},
|
||||
});
|
||||
const { appriseStatelessURL, appriseMode } =
|
||||
await response.json();
|
||||
setAppriseMode(appriseMode);
|
||||
if (appriseMode == 'stateless') {
|
||||
setAppriseStatelessURL(appriseStatelessURL);
|
||||
setDisplayStatelessURL(true);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('Fetching Apprise Mode failed.');
|
||||
}
|
||||
};
|
||||
getAppriseMode();
|
||||
}, []);
|
||||
|
||||
////Functions
|
||||
//Form submit handler to modify Apprise Mode
|
||||
const modeFormSubmitHandler = async (data) => {
|
||||
//Remove old error
|
||||
setError();
|
||||
//Loading button on submit to avoid multiple send.
|
||||
setFormIsLoading(true);
|
||||
//POST API to update Apprise Mode
|
||||
try {
|
||||
const response = await fetch('/api/account/updateAppriseMode', {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
const result = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
setFormIsLoading(false);
|
||||
setError(result.message);
|
||||
setTimeout(() => setError(), 4000);
|
||||
} else {
|
||||
setFormIsLoading(false);
|
||||
setModeFormIsSaved(true);
|
||||
setTimeout(() => setModeFormIsSaved(false), 3000);
|
||||
}
|
||||
} catch (error) {
|
||||
setFormIsLoading(false);
|
||||
setError('Change mode failed. Contact your administrator.');
|
||||
setTimeout(() => {
|
||||
setError();
|
||||
}, 4000);
|
||||
////LifeCycle
|
||||
//Component did mount
|
||||
useEffect(() => {
|
||||
//Initial fetch to get Apprise Mode enabled
|
||||
const getAppriseMode = async () => {
|
||||
try {
|
||||
const response = await fetch('/api/account/getAppriseMode', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-type': 'application/json',
|
||||
},
|
||||
});
|
||||
const { appriseStatelessURL, appriseMode } = await response.json();
|
||||
setAppriseMode(appriseMode);
|
||||
if (appriseMode == 'stateless') {
|
||||
setAppriseStatelessURL(appriseStatelessURL);
|
||||
setDisplayStatelessURL(true);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('Fetching Apprise Mode failed.');
|
||||
}
|
||||
};
|
||||
getAppriseMode();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* APPRISE MODE SELECTION */}
|
||||
<div className={classes.headerFormAppriseUrls}>
|
||||
<div style={{ margin: '0px 10px 0px 0px' }}>Apprise mode</div>
|
||||
<div style={{ display: 'flex' }}>
|
||||
{formIsLoading && (
|
||||
<SpinnerCircularFixed
|
||||
size={18}
|
||||
thickness={150}
|
||||
speed={150}
|
||||
color='#704dff'
|
||||
secondaryColor='#c3b6fa'
|
||||
/>
|
||||
)}
|
||||
{modeFormIsSaved && (
|
||||
<div className={classes.formIsSavedMessage}>
|
||||
✅ Apprise mode has been saved.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
////Functions
|
||||
//Form submit handler to modify Apprise Mode
|
||||
const modeFormSubmitHandler = async (data) => {
|
||||
//Remove old error
|
||||
setError();
|
||||
//Loading button on submit to avoid multiple send.
|
||||
setFormIsLoading(true);
|
||||
//POST API to update Apprise Mode
|
||||
try {
|
||||
const response = await fetch('/api/account/updateAppriseMode', {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
const result = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
setFormIsLoading(false);
|
||||
setError(result.message);
|
||||
setTimeout(() => setError(), 4000);
|
||||
} else {
|
||||
setFormIsLoading(false);
|
||||
setModeFormIsSaved(true);
|
||||
setTimeout(() => setModeFormIsSaved(false), 3000);
|
||||
}
|
||||
} catch (error) {
|
||||
setFormIsLoading(false);
|
||||
setError('Change mode failed. Contact your administrator.');
|
||||
setTimeout(() => {
|
||||
setError();
|
||||
}, 4000);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* APPRISE MODE SELECTION */}
|
||||
<div className={classes.headerFormAppriseUrls}>
|
||||
<div style={{ margin: '0px 10px 0px 0px' }}>Apprise mode</div>
|
||||
<div style={{ display: 'flex' }}>
|
||||
{formIsLoading && (
|
||||
<SpinnerCircularFixed
|
||||
size={18}
|
||||
thickness={150}
|
||||
speed={150}
|
||||
color='#704dff'
|
||||
secondaryColor='#c3b6fa'
|
||||
/>
|
||||
)}
|
||||
{modeFormIsSaved && (
|
||||
<div className={classes.formIsSavedMessage}>✅ Apprise mode has been saved.</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{error && <Error message={error} />}
|
||||
<form className={classes.bwForm} onBlur={handleSubmit(modeFormSubmitHandler)}>
|
||||
<div className='radio-group'>
|
||||
<label style={{ marginRight: '50px' }}>
|
||||
<div style={{ display: 'flex' }}>
|
||||
<input
|
||||
{...register('appriseMode')}
|
||||
type='radio'
|
||||
value='package'
|
||||
onClick={() => {
|
||||
setDisplayStatelessURL(false);
|
||||
setAppriseMode('package');
|
||||
}}
|
||||
checked={appriseMode == 'package' ? true : false}
|
||||
/>
|
||||
<span>Local package</span>
|
||||
</div>
|
||||
{error && <Error message={error} />}
|
||||
<form
|
||||
className={classes.bwForm}
|
||||
onBlur={handleSubmit(modeFormSubmitHandler)}
|
||||
>
|
||||
<div className='radio-group'>
|
||||
<label style={{ marginRight: '50px' }}>
|
||||
<div style={{ display: 'flex' }}>
|
||||
<input
|
||||
{...register('appriseMode')}
|
||||
type='radio'
|
||||
value='package'
|
||||
onClick={() => {
|
||||
setDisplayStatelessURL(false);
|
||||
setAppriseMode('package');
|
||||
}}
|
||||
checked={
|
||||
appriseMode == 'package' ? true : false
|
||||
}
|
||||
/>
|
||||
<span>Local package</span>
|
||||
</div>
|
||||
</label>
|
||||
<label>
|
||||
<div style={{ display: 'flex' }}>
|
||||
<input
|
||||
{...register('appriseMode')}
|
||||
value='stateless'
|
||||
type='radio'
|
||||
onClick={() => {
|
||||
setDisplayStatelessURL(true);
|
||||
setAppriseMode('stateless');
|
||||
}}
|
||||
checked={
|
||||
appriseMode == 'stateless' ? true : false
|
||||
}
|
||||
/>
|
||||
<span>Stateless API server</span>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
{displayStatelessURL && (
|
||||
<input
|
||||
type='text'
|
||||
placeholder='http://localhost:8000'
|
||||
defaultValue={appriseStatelessURL}
|
||||
{...register('appriseStatelessURL', {
|
||||
pattern: {
|
||||
value: /^(http|https):\/\/.+/g,
|
||||
message: 'Invalid URL format.',
|
||||
},
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
{errors.appriseStatelessURL && (
|
||||
<small className={classes.errorMessage}>
|
||||
{errors.appriseStatelessURL.message}
|
||||
</small>
|
||||
)}
|
||||
</form>
|
||||
</>
|
||||
);
|
||||
</label>
|
||||
<label>
|
||||
<div style={{ display: 'flex' }}>
|
||||
<input
|
||||
{...register('appriseMode')}
|
||||
value='stateless'
|
||||
type='radio'
|
||||
onClick={() => {
|
||||
setDisplayStatelessURL(true);
|
||||
setAppriseMode('stateless');
|
||||
}}
|
||||
checked={appriseMode == 'stateless' ? true : false}
|
||||
/>
|
||||
<span>Stateless API server</span>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
{displayStatelessURL && (
|
||||
<input
|
||||
type='text'
|
||||
placeholder='http://localhost:8000'
|
||||
defaultValue={appriseStatelessURL}
|
||||
{...register('appriseStatelessURL', {
|
||||
pattern: {
|
||||
value: /^(http|https):\/\/.+/g,
|
||||
message: 'Invalid URL format.',
|
||||
},
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
{errors.appriseStatelessURL && (
|
||||
<small className={classes.errorMessage}>{errors.appriseStatelessURL.message}</small>
|
||||
)}
|
||||
</form>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,155 +9,148 @@ import { useForm } from 'react-hook-form';
|
|||
import Error from '../../../../Components/UI/Error/Error';
|
||||
|
||||
export default function AppriseURLs() {
|
||||
//Var
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
formState: { errors },
|
||||
} = useForm({ mode: 'onBlur' });
|
||||
//Var
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
formState: { errors },
|
||||
} = useForm({ mode: 'onBlur' });
|
||||
|
||||
////State
|
||||
const [formIsLoading, setFormIsLoading] = useState(false);
|
||||
const [urlsFormIsSaved, setUrlsFormIsSaved] = useState(false);
|
||||
const [appriseServicesList, setAppriseServicesList] = useState();
|
||||
const [error, setError] = useState();
|
||||
////State
|
||||
const [formIsLoading, setFormIsLoading] = useState(false);
|
||||
const [urlsFormIsSaved, setUrlsFormIsSaved] = useState(false);
|
||||
const [appriseServicesList, setAppriseServicesList] = useState();
|
||||
const [error, setError] = useState();
|
||||
|
||||
////LifeCycle
|
||||
//Component did mount
|
||||
useEffect(() => {
|
||||
//Initial fetch to build the list of Apprise Services enabled
|
||||
const getAppriseServices = async () => {
|
||||
try {
|
||||
const response = await fetch(
|
||||
'/api/account/getAppriseServices',
|
||||
{
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-type': 'application/json',
|
||||
},
|
||||
}
|
||||
);
|
||||
let servicesArray = (await response.json()).appriseServices;
|
||||
const AppriseServicesListToText = () => {
|
||||
let list = '';
|
||||
for (let service of servicesArray) {
|
||||
list += service + '\n';
|
||||
}
|
||||
return list;
|
||||
};
|
||||
setAppriseServicesList(AppriseServicesListToText());
|
||||
} catch (error) {
|
||||
console.log('Fetching Apprise services list failed.');
|
||||
}
|
||||
////LifeCycle
|
||||
//Component did mount
|
||||
useEffect(() => {
|
||||
//Initial fetch to build the list of Apprise Services enabled
|
||||
const getAppriseServices = async () => {
|
||||
try {
|
||||
const response = await fetch('/api/account/getAppriseServices', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-type': 'application/json',
|
||||
},
|
||||
});
|
||||
let servicesArray = (await response.json()).appriseServices;
|
||||
const AppriseServicesListToText = () => {
|
||||
let list = '';
|
||||
for (let service of servicesArray) {
|
||||
list += service + '\n';
|
||||
}
|
||||
return list;
|
||||
};
|
||||
getAppriseServices();
|
||||
}, []);
|
||||
|
||||
////Functions
|
||||
//Form submit handler to modify Apprise services
|
||||
const urlsFormSubmitHandler = async (data) => {
|
||||
//Remove old error
|
||||
setError();
|
||||
//Loading button on submit to avoid multiple send.
|
||||
setFormIsLoading(true);
|
||||
//POST API to update Apprise Services
|
||||
try {
|
||||
const response = await fetch('/api/account/updateAppriseServices', {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
const result = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
setFormIsLoading(false);
|
||||
setError(result.message);
|
||||
setTimeout(() => setError(), 4000);
|
||||
} else {
|
||||
setFormIsLoading(false);
|
||||
setUrlsFormIsSaved(true);
|
||||
setTimeout(() => setUrlsFormIsSaved(false), 3000);
|
||||
}
|
||||
} catch (error) {
|
||||
setFormIsLoading(false);
|
||||
setError(
|
||||
'Failed to update your services. Contact your administrator.'
|
||||
);
|
||||
setTimeout(() => {
|
||||
setError();
|
||||
}, 4000);
|
||||
}
|
||||
setAppriseServicesList(AppriseServicesListToText());
|
||||
} catch (error) {
|
||||
console.log('Fetching Apprise services list failed.');
|
||||
}
|
||||
};
|
||||
getAppriseServices();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* APPRISE SERVICES URLS */}
|
||||
<div className={classes.headerFormAppriseUrls}>
|
||||
<div style={{ marginRight: '10px' }}>Apprise URLs</div>
|
||||
{error && <Error message={error} />}
|
||||
<div style={{ display: 'flex' }}>
|
||||
{formIsLoading && (
|
||||
<SpinnerCircularFixed
|
||||
size={18}
|
||||
thickness={150}
|
||||
speed={150}
|
||||
color='#704dff'
|
||||
secondaryColor='#c3b6fa'
|
||||
/>
|
||||
)}
|
||||
{urlsFormIsSaved && (
|
||||
<div className={classes.formIsSavedMessage}>
|
||||
✅ Apprise configuration has been saved.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
////Functions
|
||||
//Form submit handler to modify Apprise services
|
||||
const urlsFormSubmitHandler = async (data) => {
|
||||
//Remove old error
|
||||
setError();
|
||||
//Loading button on submit to avoid multiple send.
|
||||
setFormIsLoading(true);
|
||||
//POST API to update Apprise Services
|
||||
try {
|
||||
const response = await fetch('/api/account/updateAppriseServices', {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
const result = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
setFormIsLoading(false);
|
||||
setError(result.message);
|
||||
setTimeout(() => setError(), 4000);
|
||||
} else {
|
||||
setFormIsLoading(false);
|
||||
setUrlsFormIsSaved(true);
|
||||
setTimeout(() => setUrlsFormIsSaved(false), 3000);
|
||||
}
|
||||
} catch (error) {
|
||||
setFormIsLoading(false);
|
||||
setError('Failed to update your services. Contact your administrator.');
|
||||
setTimeout(() => {
|
||||
setError();
|
||||
}, 4000);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* APPRISE SERVICES URLS */}
|
||||
<div className={classes.headerFormAppriseUrls}>
|
||||
<div style={{ marginRight: '10px' }}>Apprise URLs</div>
|
||||
{error && <Error message={error} />}
|
||||
<div style={{ display: 'flex' }}>
|
||||
{formIsLoading && (
|
||||
<SpinnerCircularFixed
|
||||
size={18}
|
||||
thickness={150}
|
||||
speed={150}
|
||||
color='#704dff'
|
||||
secondaryColor='#c3b6fa'
|
||||
/>
|
||||
)}
|
||||
{urlsFormIsSaved && (
|
||||
<div className={classes.formIsSavedMessage}>
|
||||
✅ Apprise configuration has been saved.
|
||||
</div>
|
||||
<form
|
||||
onBlur={handleSubmit(urlsFormSubmitHandler)}
|
||||
className={classes.bwForm + ' ' + classes.currentSetting}
|
||||
>
|
||||
<textarea
|
||||
style={{ height: '100px' }}
|
||||
type='text'
|
||||
placeholder={
|
||||
'matrixs://{user}:{password}@{matrixhost}\ndiscord://{WebhookID}/{WebhookToken}\nmmosts://user@hostname/authkey'
|
||||
}
|
||||
defaultValue={appriseServicesList}
|
||||
{...register('appriseURLs', {
|
||||
pattern: {
|
||||
value: /^.+:\/\/.+$/gm,
|
||||
message: 'Invalid URLs format.',
|
||||
},
|
||||
})}
|
||||
/>
|
||||
{errors.appriseURLs && (
|
||||
<small className={classes.errorMessage}>
|
||||
{errors.appriseURLs.message}
|
||||
</small>
|
||||
)}
|
||||
</form>
|
||||
<div
|
||||
style={{
|
||||
color: '#6c737f',
|
||||
fontSize: '0.875rem',
|
||||
marginBottom: '20px',
|
||||
}}
|
||||
>
|
||||
Use{' '}
|
||||
<a
|
||||
style={{
|
||||
color: '#6d4aff',
|
||||
textDecoration: 'none',
|
||||
}}
|
||||
href='https://github.com/caronc/apprise#supported-notifications'
|
||||
rel='noreferrer'
|
||||
>
|
||||
Apprise URLs
|
||||
</a>{' '}
|
||||
to send a notification to any service. Only one URL per line.
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<form
|
||||
onBlur={handleSubmit(urlsFormSubmitHandler)}
|
||||
className={classes.bwForm + ' ' + classes.currentSetting}
|
||||
>
|
||||
<textarea
|
||||
style={{ height: '100px' }}
|
||||
type='text'
|
||||
placeholder={
|
||||
'matrixs://{user}:{password}@{matrixhost}\ndiscord://{WebhookID}/{WebhookToken}\nmmosts://user@hostname/authkey'
|
||||
}
|
||||
defaultValue={appriseServicesList}
|
||||
{...register('appriseURLs', {
|
||||
pattern: {
|
||||
value: /^.+:\/\/.+$/gm,
|
||||
message: 'Invalid URLs format.',
|
||||
},
|
||||
})}
|
||||
/>
|
||||
{errors.appriseURLs && (
|
||||
<small className={classes.errorMessage}>{errors.appriseURLs.message}</small>
|
||||
)}
|
||||
</form>
|
||||
<div
|
||||
style={{
|
||||
color: '#6c737f',
|
||||
fontSize: '0.875rem',
|
||||
marginBottom: '20px',
|
||||
}}
|
||||
>
|
||||
Use{' '}
|
||||
<a
|
||||
style={{
|
||||
color: '#6d4aff',
|
||||
textDecoration: 'none',
|
||||
}}
|
||||
href='https://github.com/caronc/apprise#supported-notifications'
|
||||
rel='noreferrer'
|
||||
>
|
||||
Apprise URLs
|
||||
</a>{' '}
|
||||
to send a notification to any service. Only one URL per line.
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,196 +13,179 @@ import Error from '../../../Components/UI/Error/Error';
|
|||
import Switch from '../../../Components/UI/Switch/Switch';
|
||||
|
||||
export default function EmailAlertSettings() {
|
||||
//Var
|
||||
const toastOptions = {
|
||||
position: 'top-right',
|
||||
autoClose: 5000,
|
||||
hideProgressBar: false,
|
||||
closeOnClick: true,
|
||||
pauseOnHover: true,
|
||||
draggable: true,
|
||||
progress: undefined,
|
||||
//Callback > re-enabled button after notification.
|
||||
onClose: () => setDisabled(false),
|
||||
//Var
|
||||
const toastOptions = {
|
||||
position: 'top-right',
|
||||
autoClose: 5000,
|
||||
hideProgressBar: false,
|
||||
closeOnClick: true,
|
||||
pauseOnHover: true,
|
||||
draggable: true,
|
||||
progress: undefined,
|
||||
//Callback > re-enabled button after notification.
|
||||
onClose: () => setDisabled(false),
|
||||
};
|
||||
|
||||
////State
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [testIsLoading, setTestIsLoading] = useState(false);
|
||||
const [error, setError] = useState();
|
||||
const [disabled, setDisabled] = useState(false);
|
||||
const [checked, setChecked] = useState();
|
||||
const [info, setInfo] = useState(false);
|
||||
|
||||
////LifeCycle
|
||||
//Component did mount
|
||||
useEffect(() => {
|
||||
const dataFetch = async () => {
|
||||
try {
|
||||
const response = await fetch('/api/account/getEmailAlert', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-type': 'application/json',
|
||||
},
|
||||
});
|
||||
setChecked((await response.json()).emailAlert);
|
||||
setIsLoading(false);
|
||||
} catch (error) {
|
||||
setError('Fetching email alert setting failed. Contact your administrator.');
|
||||
console.log('Fetching email alert setting failed.');
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
dataFetch();
|
||||
}, []);
|
||||
|
||||
////State
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [testIsLoading, setTestIsLoading] = useState(false);
|
||||
const [error, setError] = useState();
|
||||
const [disabled, setDisabled] = useState(false);
|
||||
const [checked, setChecked] = useState();
|
||||
const [info, setInfo] = useState(false);
|
||||
////Functions
|
||||
//Switch to enable/disable Email notifications
|
||||
const onChangeSwitchHandler = async (data) => {
|
||||
//Remove old error
|
||||
setError();
|
||||
//Disabled button
|
||||
setDisabled(true);
|
||||
await fetch('/api/account/updateEmailAlert', {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(data),
|
||||
})
|
||||
.then((response) => {
|
||||
console.log(response);
|
||||
if (response.ok) {
|
||||
if (data.emailAlert) {
|
||||
setChecked(!checked);
|
||||
toast.success('Email notification enabled !', toastOptions);
|
||||
} else {
|
||||
setChecked(!checked);
|
||||
toast.success('Email notification disabled !', toastOptions);
|
||||
}
|
||||
} else {
|
||||
setError('Update email alert setting failed.');
|
||||
setTimeout(() => {
|
||||
setError();
|
||||
setDisabled(false);
|
||||
}, 4000);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error);
|
||||
setError('Update failed. Contact your administrator.');
|
||||
setTimeout(() => {
|
||||
setError();
|
||||
setDisabled(false);
|
||||
}, 4000);
|
||||
});
|
||||
};
|
||||
|
||||
////LifeCycle
|
||||
//Component did mount
|
||||
useEffect(() => {
|
||||
const dataFetch = async () => {
|
||||
try {
|
||||
const response = await fetch('/api/account/getEmailAlert', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-type': 'application/json',
|
||||
},
|
||||
});
|
||||
setChecked((await response.json()).emailAlert);
|
||||
setIsLoading(false);
|
||||
} catch (error) {
|
||||
setError(
|
||||
'Fetching email alert setting failed. Contact your administrator.'
|
||||
);
|
||||
console.log('Fetching email alert setting failed.');
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
dataFetch();
|
||||
}, []);
|
||||
//Send a test notification by email
|
||||
const onSendTestMailHandler = async () => {
|
||||
//Loading
|
||||
setTestIsLoading(true);
|
||||
//Remove old error
|
||||
setError();
|
||||
await fetch('/api/account/sendTestEmail', {
|
||||
method: 'POST',
|
||||
})
|
||||
.then((response) => {
|
||||
if (!response.ok) {
|
||||
setTestIsLoading(false);
|
||||
setError('Failed to send the notification.');
|
||||
setTimeout(() => {
|
||||
setError();
|
||||
}, 4000);
|
||||
} else {
|
||||
setTestIsLoading(false);
|
||||
setInfo(true);
|
||||
setTimeout(() => {
|
||||
setInfo(false);
|
||||
}, 4000);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
setTestIsLoading(false);
|
||||
console.log(error);
|
||||
setError('Send email failed. Contact your administrator.');
|
||||
setTimeout(() => {
|
||||
setError();
|
||||
}, 4000);
|
||||
});
|
||||
};
|
||||
|
||||
////Functions
|
||||
//Switch to enable/disable Email notifications
|
||||
const onChangeSwitchHandler = async (data) => {
|
||||
//Remove old error
|
||||
setError();
|
||||
//Disabled button
|
||||
setDisabled(true);
|
||||
await fetch('/api/account/updateEmailAlert', {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(data),
|
||||
})
|
||||
.then((response) => {
|
||||
console.log(response);
|
||||
if (response.ok) {
|
||||
if (data.emailAlert) {
|
||||
setChecked(!checked);
|
||||
toast.success(
|
||||
'Email notification enabled !',
|
||||
toastOptions
|
||||
);
|
||||
} else {
|
||||
setChecked(!checked);
|
||||
toast.success(
|
||||
'Email notification disabled !',
|
||||
toastOptions
|
||||
);
|
||||
}
|
||||
} else {
|
||||
setError('Update email alert setting failed.');
|
||||
setTimeout(() => {
|
||||
setError();
|
||||
setDisabled(false);
|
||||
}, 4000);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error);
|
||||
setError('Update failed. Contact your administrator.');
|
||||
setTimeout(() => {
|
||||
setError();
|
||||
setDisabled(false);
|
||||
}, 4000);
|
||||
});
|
||||
};
|
||||
|
||||
//Send a test notification by email
|
||||
const onSendTestMailHandler = async () => {
|
||||
//Loading
|
||||
setTestIsLoading(true);
|
||||
//Remove old error
|
||||
setError();
|
||||
await fetch('/api/account/sendTestEmail', {
|
||||
method: 'POST',
|
||||
})
|
||||
.then((response) => {
|
||||
if (!response.ok) {
|
||||
setTestIsLoading(false);
|
||||
setError('Failed to send the notification.');
|
||||
setTimeout(() => {
|
||||
setError();
|
||||
}, 4000);
|
||||
} else {
|
||||
setTestIsLoading(false);
|
||||
setInfo(true);
|
||||
setTimeout(() => {
|
||||
setInfo(false);
|
||||
}, 4000);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
setTestIsLoading(false);
|
||||
console.log(error);
|
||||
setError('Send email failed. Contact your administrator.');
|
||||
setTimeout(() => {
|
||||
setError();
|
||||
}, 4000);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* EMAIL ALERT */}
|
||||
<div className={classes.containerSetting}>
|
||||
<div className={classes.settingCategory}>
|
||||
<h2 style={{ alignSelf: 'baseline' }}>Email alert</h2>
|
||||
<Link
|
||||
style={{ alignSelf: 'baseline', marginLeft: '5px' }}
|
||||
href='https://borgwarehouse.com/docs/user-manual/account/#alerting'
|
||||
rel='noreferrer'
|
||||
target='_blank'
|
||||
>
|
||||
<IconExternalLink size={16} color='#6c737f' />
|
||||
</Link>
|
||||
</div>
|
||||
<div className={classes.setting}>
|
||||
<div className={classes.bwFormWrapper}>
|
||||
{isLoading ? (
|
||||
<SpinnerCircularFixed
|
||||
size={30}
|
||||
thickness={150}
|
||||
speed={150}
|
||||
color='#704dff'
|
||||
secondaryColor='#c3b6fa'
|
||||
/>
|
||||
) : (
|
||||
<Switch
|
||||
checked={checked}
|
||||
disabled={disabled}
|
||||
switchName='Alert me by email'
|
||||
switchDescription='You will receive an alert every 24H if you have a down status.'
|
||||
onChange={(e) =>
|
||||
onChangeSwitchHandler({ emailAlert: e })
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{testIsLoading ? (
|
||||
<SpinnerCircularFixed
|
||||
size={30}
|
||||
thickness={150}
|
||||
speed={150}
|
||||
color='#704dff'
|
||||
secondaryColor='#c3b6fa'
|
||||
/>
|
||||
) : (
|
||||
<button
|
||||
className='defaultButton'
|
||||
onClick={onSendTestMailHandler}
|
||||
>
|
||||
Send a test mail
|
||||
</button>
|
||||
)}
|
||||
{info && (
|
||||
<span
|
||||
style={{ marginLeft: '10px', color: '#119300' }}
|
||||
>
|
||||
Mail successfully sent.
|
||||
</span>
|
||||
)}
|
||||
{error && <Error message={error} />}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
return (
|
||||
<>
|
||||
{/* EMAIL ALERT */}
|
||||
<div className={classes.containerSetting}>
|
||||
<div className={classes.settingCategory}>
|
||||
<h2 style={{ alignSelf: 'baseline' }}>Email alert</h2>
|
||||
<Link
|
||||
style={{ alignSelf: 'baseline', marginLeft: '5px' }}
|
||||
href='https://borgwarehouse.com/docs/user-manual/account/#alerting'
|
||||
rel='noreferrer'
|
||||
target='_blank'
|
||||
>
|
||||
<IconExternalLink size={16} color='#6c737f' />
|
||||
</Link>
|
||||
</div>
|
||||
<div className={classes.setting}>
|
||||
<div className={classes.bwFormWrapper}>
|
||||
{isLoading ? (
|
||||
<SpinnerCircularFixed
|
||||
size={30}
|
||||
thickness={150}
|
||||
speed={150}
|
||||
color='#704dff'
|
||||
secondaryColor='#c3b6fa'
|
||||
/>
|
||||
) : (
|
||||
<Switch
|
||||
checked={checked}
|
||||
disabled={disabled}
|
||||
switchName='Alert me by email'
|
||||
switchDescription='You will receive an alert every 24H if you have a down status.'
|
||||
onChange={(e) => onChangeSwitchHandler({ emailAlert: e })}
|
||||
/>
|
||||
)}
|
||||
{testIsLoading ? (
|
||||
<SpinnerCircularFixed
|
||||
size={30}
|
||||
thickness={150}
|
||||
speed={150}
|
||||
color='#704dff'
|
||||
secondaryColor='#c3b6fa'
|
||||
/>
|
||||
) : (
|
||||
<button className='defaultButton' onClick={onSendTestMailHandler}>
|
||||
Send a test mail
|
||||
</button>
|
||||
)}
|
||||
{info && (
|
||||
<span style={{ marginLeft: '10px', color: '#119300' }}>Mail successfully sent.</span>
|
||||
)}
|
||||
{error && <Error message={error} />}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,128 +11,117 @@ import Error from '../../../Components/UI/Error/Error';
|
|||
import Info from '../../../Components/UI/Info/Info';
|
||||
|
||||
export default function EmailSettings(props) {
|
||||
//Var
|
||||
const toastOptions = {
|
||||
position: 'top-right',
|
||||
autoClose: 8000,
|
||||
hideProgressBar: false,
|
||||
closeOnClick: true,
|
||||
pauseOnHover: true,
|
||||
draggable: true,
|
||||
progress: undefined,
|
||||
};
|
||||
//Var
|
||||
const toastOptions = {
|
||||
position: 'top-right',
|
||||
autoClose: 8000,
|
||||
hideProgressBar: false,
|
||||
closeOnClick: true,
|
||||
pauseOnHover: true,
|
||||
draggable: true,
|
||||
progress: undefined,
|
||||
};
|
||||
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
reset,
|
||||
formState: { errors, isSubmitting, isValid },
|
||||
} = useForm({ mode: 'onChange' });
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
reset,
|
||||
formState: { errors, isSubmitting, isValid },
|
||||
} = useForm({ mode: 'onChange' });
|
||||
|
||||
////State
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [error, setError] = useState();
|
||||
const [info, setInfo] = useState(false);
|
||||
////State
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [error, setError] = useState();
|
||||
const [info, setInfo] = useState(false);
|
||||
|
||||
////Functions
|
||||
//Form submit Handler for ADD a repo
|
||||
const formSubmitHandler = async (data) => {
|
||||
//Remove old error
|
||||
setError();
|
||||
//Loading button on submit to avoid multiple send.
|
||||
setIsLoading(true);
|
||||
//POST API to send the new mail address
|
||||
try {
|
||||
const response = await fetch('/api/account/updateEmail', {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
const result = await response.json();
|
||||
////Functions
|
||||
//Form submit Handler for ADD a repo
|
||||
const formSubmitHandler = async (data) => {
|
||||
//Remove old error
|
||||
setError();
|
||||
//Loading button on submit to avoid multiple send.
|
||||
setIsLoading(true);
|
||||
//POST API to send the new mail address
|
||||
try {
|
||||
const response = await fetch('/api/account/updateEmail', {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
const result = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
setIsLoading(false);
|
||||
reset();
|
||||
setError(result.message);
|
||||
setTimeout(() => setError(), 4000);
|
||||
} else {
|
||||
reset();
|
||||
setIsLoading(false);
|
||||
setInfo(true);
|
||||
toast.success('Email edited !', toastOptions);
|
||||
}
|
||||
} catch (error) {
|
||||
reset();
|
||||
setIsLoading(false);
|
||||
setError("Can't update your email. Contact your administrator.");
|
||||
setTimeout(() => setError(), 4000);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<>
|
||||
{/* EMAIL */}
|
||||
<div className={classes.containerSetting}>
|
||||
<div className={classes.settingCategory}>
|
||||
<h2>Email</h2>
|
||||
</div>
|
||||
<div className={classes.setting}>
|
||||
<div className={classes.bwFormWrapper}>
|
||||
{info ? ( //For local JWTs (cookie) without an OAuth provider, Next-Auth does not allow
|
||||
//at the time this code is written to refresh client-side session information
|
||||
//without triggering a logout.
|
||||
//I chose to inform the user to reconnect rather than force logout.
|
||||
<Info message='Please, logout to update your session.' />
|
||||
) : (
|
||||
<form
|
||||
onSubmit={handleSubmit(formSubmitHandler)}
|
||||
className={
|
||||
classes.bwForm +
|
||||
' ' +
|
||||
classes.currentSetting
|
||||
}
|
||||
>
|
||||
<p>
|
||||
{error && <Error message={error} />}
|
||||
<input
|
||||
type='email'
|
||||
placeholder={props.email}
|
||||
{...register('email', {
|
||||
required: true,
|
||||
pattern: {
|
||||
value: /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/,
|
||||
message:
|
||||
'Your email is not valid.',
|
||||
},
|
||||
})}
|
||||
/>
|
||||
{errors.email && (
|
||||
<small className={classes.errorMessage}>
|
||||
{errors.email.message}
|
||||
</small>
|
||||
)}
|
||||
</p>
|
||||
<button
|
||||
className={classes.AccountSettingsButton}
|
||||
disabled={!isValid || isSubmitting}
|
||||
>
|
||||
{isLoading ? (
|
||||
<SpinnerDotted
|
||||
size={20}
|
||||
thickness={150}
|
||||
speed={100}
|
||||
color='#fff'
|
||||
/>
|
||||
) : (
|
||||
'Update your email'
|
||||
)}
|
||||
</button>
|
||||
</form>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
if (!response.ok) {
|
||||
setIsLoading(false);
|
||||
reset();
|
||||
setError(result.message);
|
||||
setTimeout(() => setError(), 4000);
|
||||
} else {
|
||||
reset();
|
||||
setIsLoading(false);
|
||||
setInfo(true);
|
||||
toast.success('Email edited !', toastOptions);
|
||||
}
|
||||
} catch (error) {
|
||||
reset();
|
||||
setIsLoading(false);
|
||||
setError("Can't update your email. Contact your administrator.");
|
||||
setTimeout(() => setError(), 4000);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<>
|
||||
{/* EMAIL */}
|
||||
<div className={classes.containerSetting}>
|
||||
<div className={classes.settingCategory}>
|
||||
<h2>Email</h2>
|
||||
</div>
|
||||
<div className={classes.setting}>
|
||||
<div className={classes.bwFormWrapper}>
|
||||
{info ? ( //For local JWTs (cookie) without an OAuth provider, Next-Auth does not allow
|
||||
//at the time this code is written to refresh client-side session information
|
||||
//without triggering a logout.
|
||||
//I chose to inform the user to reconnect rather than force logout.
|
||||
<Info message='Please, logout to update your session.' />
|
||||
) : (
|
||||
<form
|
||||
onSubmit={handleSubmit(formSubmitHandler)}
|
||||
className={classes.bwForm + ' ' + classes.currentSetting}
|
||||
>
|
||||
<p>
|
||||
{error && <Error message={error} />}
|
||||
<input
|
||||
type='email'
|
||||
placeholder={props.email}
|
||||
{...register('email', {
|
||||
required: true,
|
||||
pattern: {
|
||||
value:
|
||||
/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/,
|
||||
message: 'Your email is not valid.',
|
||||
},
|
||||
})}
|
||||
/>
|
||||
{errors.email && (
|
||||
<small className={classes.errorMessage}>{errors.email.message}</small>
|
||||
)}
|
||||
</p>
|
||||
<button
|
||||
className={classes.AccountSettingsButton}
|
||||
disabled={!isValid || isSubmitting}
|
||||
>
|
||||
{isLoading ? (
|
||||
<SpinnerDotted size={20} thickness={150} speed={100} color='#fff' />
|
||||
) : (
|
||||
'Update your email'
|
||||
)}
|
||||
</button>
|
||||
</form>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,348 +15,265 @@ import CopyButton from '../../../Components/UI/CopyButton/CopyButton';
|
|||
import Info from '../../../Components/UI/Info/Info';
|
||||
|
||||
export default function Integrations() {
|
||||
//Var
|
||||
const toastOptions = {
|
||||
position: 'top-right',
|
||||
autoClose: 5000,
|
||||
hideProgressBar: false,
|
||||
closeOnClick: true,
|
||||
pauseOnHover: true,
|
||||
draggable: true,
|
||||
progress: undefined,
|
||||
};
|
||||
//Var
|
||||
const toastOptions = {
|
||||
position: 'top-right',
|
||||
autoClose: 5000,
|
||||
hideProgressBar: false,
|
||||
closeOnClick: true,
|
||||
pauseOnHover: true,
|
||||
draggable: true,
|
||||
progress: undefined,
|
||||
};
|
||||
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
reset,
|
||||
formState: { errors, isSubmitting, isValid },
|
||||
} = useForm({ mode: 'onChange', defaultValues: { authorization: 'read' } });
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
reset,
|
||||
formState: { errors, isSubmitting, isValid },
|
||||
} = useForm({ mode: 'onChange', defaultValues: { authorization: 'read' } });
|
||||
|
||||
////State
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [tokenList, setTokenList] = useState([]);
|
||||
const [error, setError] = useState();
|
||||
const [lastGeneratedToken, setLastGeneratedToken] = useState();
|
||||
const [deletingToken, setDeletingToken] = useState(null);
|
||||
////State
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [tokenList, setTokenList] = useState([]);
|
||||
const [error, setError] = useState();
|
||||
const [lastGeneratedToken, setLastGeneratedToken] = useState();
|
||||
const [deletingToken, setDeletingToken] = useState(null);
|
||||
|
||||
const fetchTokenList = async () => {
|
||||
try {
|
||||
const response = await fetch('/api/account/token-manager', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-type': 'application/json',
|
||||
},
|
||||
});
|
||||
const tokensArray = await response.json();
|
||||
setTokenList(tokensArray);
|
||||
} catch (error) {
|
||||
console.log('Fetching token list failed.');
|
||||
}
|
||||
};
|
||||
const fetchTokenList = async () => {
|
||||
try {
|
||||
const response = await fetch('/api/account/token-manager', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-type': 'application/json',
|
||||
},
|
||||
});
|
||||
const tokensArray = await response.json();
|
||||
setTokenList(tokensArray);
|
||||
} catch (error) {
|
||||
console.log('Fetching token list failed.');
|
||||
}
|
||||
};
|
||||
|
||||
////LifeCycle
|
||||
useEffect(() => {
|
||||
////LifeCycle
|
||||
useEffect(() => {
|
||||
fetchTokenList();
|
||||
}, []);
|
||||
|
||||
//Form submit Handler for ADD a repo
|
||||
const formSubmitHandler = async (data) => {
|
||||
//Remove old error
|
||||
setError();
|
||||
//Loading button on submit to avoid multiple send.
|
||||
setIsLoading(true);
|
||||
console.log(data);
|
||||
//Generate a UUIDv4
|
||||
const token = uuidv4();
|
||||
setLastGeneratedToken({ name: data.tokenName, value: token });
|
||||
|
||||
// Post API to send the new token integration
|
||||
try {
|
||||
const response = await fetch('/api/account/token-manager', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name: data.tokenName,
|
||||
token: token,
|
||||
creation: Math.floor(Date.now() / 1000),
|
||||
expirition: null,
|
||||
permissions: {
|
||||
read: true,
|
||||
write: data.authorization === 'write' ? true : false,
|
||||
},
|
||||
}),
|
||||
});
|
||||
const result = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
setIsLoading(false);
|
||||
reset();
|
||||
toast.error(result.message, toastOptions);
|
||||
setTimeout(() => setError(), 4000);
|
||||
} else {
|
||||
reset();
|
||||
fetchTokenList();
|
||||
}, []);
|
||||
setIsLoading(false);
|
||||
toast.success('🔑 Token generated !', toastOptions);
|
||||
}
|
||||
} catch (error) {
|
||||
reset();
|
||||
setIsLoading(false);
|
||||
console.log(error);
|
||||
toast.error("Can't generate your token. Contact your administrator.", toastOptions);
|
||||
setTimeout(() => setError(), 4000);
|
||||
}
|
||||
};
|
||||
|
||||
//Form submit Handler for ADD a repo
|
||||
const formSubmitHandler = async (data) => {
|
||||
//Remove old error
|
||||
setError();
|
||||
//Loading button on submit to avoid multiple send.
|
||||
setIsLoading(true);
|
||||
console.log(data);
|
||||
//Generate a UUIDv4
|
||||
const token = uuidv4();
|
||||
setLastGeneratedToken({ name: data.tokenName, value: token });
|
||||
//Delete token
|
||||
const deleteTokenHandler = async (tokenName) => {
|
||||
try {
|
||||
const response = await fetch('/api/account/token-manager', {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Content-type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name: tokenName,
|
||||
}),
|
||||
});
|
||||
const result = await response.json();
|
||||
|
||||
// Post API to send the new token integration
|
||||
try {
|
||||
const response = await fetch('/api/account/token-manager', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name: data.tokenName,
|
||||
token: token,
|
||||
creation: Math.floor(Date.now() / 1000),
|
||||
expirition: null,
|
||||
permissions: {
|
||||
read: true,
|
||||
write: data.authorization === 'write' ? true : false,
|
||||
},
|
||||
}),
|
||||
});
|
||||
const result = await response.json();
|
||||
if (!response.ok) {
|
||||
toast.error(result.message, toastOptions);
|
||||
setTimeout(() => setError(), 4000);
|
||||
} else {
|
||||
fetchTokenList();
|
||||
toast.success('🗑️ Token deleted !', toastOptions);
|
||||
}
|
||||
} catch (error) {
|
||||
setIsLoading(false);
|
||||
toast.error("Can't delete your token. Contact your administrator.", toastOptions);
|
||||
setTimeout(() => setError(), 4000);
|
||||
} finally {
|
||||
setDeletingToken(null);
|
||||
}
|
||||
};
|
||||
|
||||
if (!response.ok) {
|
||||
setIsLoading(false);
|
||||
reset();
|
||||
toast.error(result.message, toastOptions);
|
||||
setTimeout(() => setError(), 4000);
|
||||
} else {
|
||||
reset();
|
||||
fetchTokenList();
|
||||
setIsLoading(false);
|
||||
toast.success('🔑 Token generated !', toastOptions);
|
||||
}
|
||||
} catch (error) {
|
||||
reset();
|
||||
setIsLoading(false);
|
||||
console.log(error);
|
||||
toast.error(
|
||||
"Can't generate your token. Contact your administrator.",
|
||||
toastOptions
|
||||
);
|
||||
setTimeout(() => setError(), 4000);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<div className={classes.containerSetting}>
|
||||
<div className={classes.settingCategory}>
|
||||
<h2>Generate token</h2>
|
||||
</div>
|
||||
<div className={classes.setting}>
|
||||
<form
|
||||
onSubmit={handleSubmit(formSubmitHandler)}
|
||||
className={[classes.bwForm, classes.tokenGen].join(' ')}
|
||||
>
|
||||
<input
|
||||
type='text'
|
||||
placeholder='Token name'
|
||||
{...register('tokenName', {
|
||||
required: true,
|
||||
pattern: /^[a-zA-Z0-9_-]*$/,
|
||||
maxLength: 25,
|
||||
})}
|
||||
/>
|
||||
|
||||
//Delete token
|
||||
const deleteTokenHandler = async (tokenName) => {
|
||||
try {
|
||||
const response = await fetch('/api/account/token-manager', {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Content-type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name: tokenName,
|
||||
}),
|
||||
});
|
||||
const result = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
toast.error(result.message, toastOptions);
|
||||
setTimeout(() => setError(), 4000);
|
||||
} else {
|
||||
fetchTokenList();
|
||||
toast.success('🗑️ Token deleted !', toastOptions);
|
||||
}
|
||||
} catch (error) {
|
||||
setIsLoading(false);
|
||||
toast.error(
|
||||
"Can't delete your token. Contact your administrator.",
|
||||
toastOptions
|
||||
);
|
||||
setTimeout(() => setError(), 4000);
|
||||
} finally {
|
||||
setDeletingToken(null);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={classes.containerSetting}>
|
||||
<div className={classes.settingCategory}>
|
||||
<h2>Generate token</h2>
|
||||
<div className='radio-group'>
|
||||
<label style={{ marginRight: '10px' }}>
|
||||
<div style={{ display: 'flex' }}>
|
||||
<input {...register('authorization')} type='radio' value='read' />
|
||||
<span>Read</span>
|
||||
</div>
|
||||
<div className={classes.setting}>
|
||||
<form
|
||||
onSubmit={handleSubmit(formSubmitHandler)}
|
||||
className={[classes.bwForm, classes.tokenGen].join(' ')}
|
||||
>
|
||||
<input
|
||||
type='text'
|
||||
placeholder='Token name'
|
||||
{...register('tokenName', {
|
||||
required: true,
|
||||
pattern: /^[a-zA-Z0-9_-]*$/,
|
||||
maxLength: 25,
|
||||
})}
|
||||
/>
|
||||
|
||||
<div className='radio-group'>
|
||||
<label style={{ marginRight: '10px' }}>
|
||||
<div style={{ display: 'flex' }}>
|
||||
<input
|
||||
{...register('authorization')}
|
||||
type='radio'
|
||||
value='read'
|
||||
/>
|
||||
<span>Read</span>
|
||||
</div>
|
||||
</label>
|
||||
<label>
|
||||
<div style={{ display: 'flex' }}>
|
||||
<input
|
||||
{...register('authorization')}
|
||||
type='radio'
|
||||
value='write'
|
||||
/>
|
||||
<span>Write</span>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<button
|
||||
className={classes.AccountSettingsButton}
|
||||
disabled={!isValid || isSubmitting}
|
||||
>
|
||||
{isLoading ? (
|
||||
<SpinnerDotted
|
||||
size={20}
|
||||
thickness={150}
|
||||
speed={100}
|
||||
color='#fff'
|
||||
/>
|
||||
) : (
|
||||
'Generate'
|
||||
)}
|
||||
</button>
|
||||
</form>
|
||||
{errors.tokenName &&
|
||||
errors.tokenName.type === 'maxLength' && (
|
||||
<small className={classes.errorMessage}>
|
||||
25 characters max.
|
||||
</small>
|
||||
)}
|
||||
{errors.tokenName &&
|
||||
errors.tokenName.type === 'pattern' && (
|
||||
<small className={classes.errorMessage}>
|
||||
Only alphanumeric characters, dashes, and
|
||||
underscores are allowed (no spaces).
|
||||
</small>
|
||||
)}
|
||||
{error && <Error message={error} />}
|
||||
</label>
|
||||
<label>
|
||||
<div style={{ display: 'flex' }}>
|
||||
<input {...register('authorization')} type='radio' value='write' />
|
||||
<span>Write</span>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
{tokenList && tokenList.length > 0 && (
|
||||
<div className={classes.containerSetting}>
|
||||
<div className={classes.settingCategory}>
|
||||
<h2>API Tokens</h2>
|
||||
</div>
|
||||
<div className={classes.tokenCardList}>
|
||||
{tokenList
|
||||
.slice()
|
||||
.sort((a, b) => b.creation - a.creation)
|
||||
.map((token, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={classes.tokenCardWrapper}
|
||||
>
|
||||
<div
|
||||
className={`${classes.tokenCard} ${
|
||||
lastGeneratedToken &&
|
||||
lastGeneratedToken.name ===
|
||||
token.name
|
||||
? classes.tokenCardHighlight
|
||||
: ''
|
||||
} ${
|
||||
deletingToken &&
|
||||
deletingToken.name === token.name
|
||||
? classes.tokenCardBlurred
|
||||
: ''
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
className={classes.tokenCardHeader}
|
||||
>
|
||||
{token.name}
|
||||
</div>
|
||||
<div className={classes.tokenCardBody}>
|
||||
<p>
|
||||
<strong>Created at:</strong>
|
||||
{timestampConverter(
|
||||
token.creation
|
||||
)}
|
||||
</p>
|
||||
<p>
|
||||
<strong>Permission:</strong>
|
||||
<div
|
||||
className={
|
||||
classes.permissionBadge
|
||||
}
|
||||
>
|
||||
{token.permissions.write
|
||||
? 'Write'
|
||||
: 'Read'}
|
||||
</div>
|
||||
</p>
|
||||
{lastGeneratedToken &&
|
||||
lastGeneratedToken.name ===
|
||||
token.name && (
|
||||
<>
|
||||
<p>
|
||||
<strong>
|
||||
Token:
|
||||
</strong>
|
||||
<CopyButton
|
||||
size={22}
|
||||
displayIconConfirmation={
|
||||
true
|
||||
}
|
||||
dataToCopy={
|
||||
lastGeneratedToken.value
|
||||
}
|
||||
>
|
||||
<span>
|
||||
{
|
||||
lastGeneratedToken.value
|
||||
}
|
||||
</span>
|
||||
</CopyButton>
|
||||
</p>
|
||||
<Info color='#3498db'>
|
||||
This token will not
|
||||
be shown again.
|
||||
Please save it.
|
||||
</Info>
|
||||
</>
|
||||
)}
|
||||
{deletingToken &&
|
||||
deletingToken.name ===
|
||||
token.name && (
|
||||
<div
|
||||
className={
|
||||
classes.deleteConfirmationButtons
|
||||
}
|
||||
>
|
||||
<button
|
||||
className={
|
||||
classes.confirmButton
|
||||
}
|
||||
onClick={() =>
|
||||
deleteTokenHandler(
|
||||
token.name
|
||||
)
|
||||
}
|
||||
>
|
||||
Confirm
|
||||
</button>
|
||||
<button
|
||||
className={
|
||||
classes.cancelButton
|
||||
}
|
||||
onClick={() =>
|
||||
setDeletingToken(
|
||||
null
|
||||
)
|
||||
}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className={classes.deleteToken}>
|
||||
<IconTrash
|
||||
cursor={'pointer'}
|
||||
color='#ea1313'
|
||||
strokeWidth={2}
|
||||
onClick={() =>
|
||||
setDeletingToken(token)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
<button className={classes.AccountSettingsButton} disabled={!isValid || isSubmitting}>
|
||||
{isLoading ? (
|
||||
<SpinnerDotted size={20} thickness={150} speed={100} color='#fff' />
|
||||
) : (
|
||||
'Generate'
|
||||
)}
|
||||
</button>
|
||||
</form>
|
||||
{errors.tokenName && errors.tokenName.type === 'maxLength' && (
|
||||
<small className={classes.errorMessage}>25 characters max.</small>
|
||||
)}
|
||||
{errors.tokenName && errors.tokenName.type === 'pattern' && (
|
||||
<small className={classes.errorMessage}>
|
||||
Only alphanumeric characters, dashes, and underscores are allowed (no spaces).
|
||||
</small>
|
||||
)}
|
||||
{error && <Error message={error} />}
|
||||
</div>
|
||||
</div>
|
||||
{tokenList && tokenList.length > 0 && (
|
||||
<div className={classes.containerSetting}>
|
||||
<div className={classes.settingCategory}>
|
||||
<h2>API Tokens</h2>
|
||||
</div>
|
||||
<div className={classes.tokenCardList}>
|
||||
{tokenList
|
||||
.slice()
|
||||
.sort((a, b) => b.creation - a.creation)
|
||||
.map((token, index) => (
|
||||
<div key={index} className={classes.tokenCardWrapper}>
|
||||
<div
|
||||
className={`${classes.tokenCard} ${
|
||||
lastGeneratedToken && lastGeneratedToken.name === token.name
|
||||
? classes.tokenCardHighlight
|
||||
: ''
|
||||
} ${deletingToken && deletingToken.name === token.name ? classes.tokenCardBlurred : ''}`}
|
||||
>
|
||||
<div className={classes.tokenCardHeader}>{token.name}</div>
|
||||
<div className={classes.tokenCardBody}>
|
||||
<p>
|
||||
<strong>Created at:</strong>
|
||||
{timestampConverter(token.creation)}
|
||||
</p>
|
||||
<p>
|
||||
<strong>Permission:</strong>
|
||||
<div className={classes.permissionBadge}>
|
||||
{token.permissions.write ? 'Write' : 'Read'}
|
||||
</div>
|
||||
</p>
|
||||
{lastGeneratedToken && lastGeneratedToken.name === token.name && (
|
||||
<>
|
||||
<p>
|
||||
<strong>Token:</strong>
|
||||
<CopyButton
|
||||
size={22}
|
||||
displayIconConfirmation={true}
|
||||
dataToCopy={lastGeneratedToken.value}
|
||||
>
|
||||
<span>{lastGeneratedToken.value}</span>
|
||||
</CopyButton>
|
||||
</p>
|
||||
<Info color='#3498db'>
|
||||
This token will not be shown again. Please save it.
|
||||
</Info>
|
||||
</>
|
||||
)}
|
||||
{deletingToken && deletingToken.name === token.name && (
|
||||
<div className={classes.deleteConfirmationButtons}>
|
||||
<button
|
||||
className={classes.confirmButton}
|
||||
onClick={() => deleteTokenHandler(token.name)}
|
||||
>
|
||||
Confirm
|
||||
</button>
|
||||
<button
|
||||
className={classes.cancelButton}
|
||||
onClick={() => setDeletingToken(null)}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className={classes.deleteToken}>
|
||||
<IconTrash
|
||||
cursor={'pointer'}
|
||||
color='#ea1313'
|
||||
strokeWidth={2}
|
||||
onClick={() => setDeletingToken(token)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,126 +10,110 @@ import { SpinnerDotted } from 'spinners-react';
|
|||
import Error from '../../../Components/UI/Error/Error';
|
||||
|
||||
export default function PasswordSettings(props) {
|
||||
//Var
|
||||
const toastOptions = {
|
||||
position: 'top-right',
|
||||
autoClose: 5000,
|
||||
hideProgressBar: false,
|
||||
closeOnClick: true,
|
||||
pauseOnHover: true,
|
||||
draggable: true,
|
||||
progress: undefined,
|
||||
};
|
||||
//Var
|
||||
const toastOptions = {
|
||||
position: 'top-right',
|
||||
autoClose: 5000,
|
||||
hideProgressBar: false,
|
||||
closeOnClick: true,
|
||||
pauseOnHover: true,
|
||||
draggable: true,
|
||||
progress: undefined,
|
||||
};
|
||||
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
reset,
|
||||
formState: { errors, isSubmitting, isValid },
|
||||
} = useForm({ mode: 'onChange' });
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
reset,
|
||||
formState: { errors, isSubmitting, isValid },
|
||||
} = useForm({ mode: 'onChange' });
|
||||
|
||||
////State
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [error, setError] = useState();
|
||||
////State
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [error, setError] = useState();
|
||||
|
||||
////Functions
|
||||
//Form submit Handler for ADD a repo
|
||||
const formSubmitHandler = async (data) => {
|
||||
console.log(data);
|
||||
//Remove old error
|
||||
setError();
|
||||
//Loading button on submit to avoid multiple send.
|
||||
setIsLoading(true);
|
||||
//POST API to send the new and old password
|
||||
try {
|
||||
const response = await fetch('/api/account/updatePassword', {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
const result = await response.json();
|
||||
////Functions
|
||||
//Form submit Handler for ADD a repo
|
||||
const formSubmitHandler = async (data) => {
|
||||
console.log(data);
|
||||
//Remove old error
|
||||
setError();
|
||||
//Loading button on submit to avoid multiple send.
|
||||
setIsLoading(true);
|
||||
//POST API to send the new and old password
|
||||
try {
|
||||
const response = await fetch('/api/account/updatePassword', {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
const result = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
setIsLoading(false);
|
||||
reset();
|
||||
setError(result.message);
|
||||
setTimeout(() => setError(), 4000);
|
||||
} else {
|
||||
reset();
|
||||
setIsLoading(false);
|
||||
toast.success('🔑 Password edited !', toastOptions);
|
||||
}
|
||||
} catch (error) {
|
||||
reset();
|
||||
setIsLoading(false);
|
||||
setError("Can't update your password. Contact your administrator.");
|
||||
setTimeout(() => setError(), 4000);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<>
|
||||
{/* PASSWORD */}
|
||||
<div className={classes.containerSetting}>
|
||||
<div className={classes.settingCategory}>
|
||||
<h2>Password</h2>
|
||||
</div>
|
||||
<div className={classes.setting}>
|
||||
<div className={classes.bwFormWrapper}>
|
||||
<form
|
||||
onSubmit={handleSubmit(formSubmitHandler)}
|
||||
className={classes.bwForm}
|
||||
>
|
||||
{error && <Error message={error} />}
|
||||
<p>
|
||||
<input
|
||||
type='password'
|
||||
placeholder='Current password'
|
||||
{...register('oldPassword', {
|
||||
required: true,
|
||||
})}
|
||||
/>
|
||||
{errors.oldPassword &&
|
||||
errors.oldPassword.type === 'required' && (
|
||||
<small className={classes.errorMessage}>
|
||||
This field is required.
|
||||
</small>
|
||||
)}
|
||||
</p>
|
||||
<p>
|
||||
<input
|
||||
type='password'
|
||||
placeholder='New password'
|
||||
{...register('newPassword', {
|
||||
required: true,
|
||||
})}
|
||||
/>
|
||||
{errors.newPassword && (
|
||||
<small className={classes.errorMessage}>
|
||||
This field is required.
|
||||
</small>
|
||||
)}
|
||||
</p>
|
||||
<button
|
||||
className={classes.AccountSettingsButton}
|
||||
disabled={!isValid || isSubmitting}
|
||||
>
|
||||
{isLoading ? (
|
||||
<SpinnerDotted
|
||||
size={20}
|
||||
thickness={150}
|
||||
speed={100}
|
||||
color='#fff'
|
||||
/>
|
||||
) : (
|
||||
'Update your password'
|
||||
)}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
if (!response.ok) {
|
||||
setIsLoading(false);
|
||||
reset();
|
||||
setError(result.message);
|
||||
setTimeout(() => setError(), 4000);
|
||||
} else {
|
||||
reset();
|
||||
setIsLoading(false);
|
||||
toast.success('🔑 Password edited !', toastOptions);
|
||||
}
|
||||
} catch (error) {
|
||||
reset();
|
||||
setIsLoading(false);
|
||||
setError("Can't update your password. Contact your administrator.");
|
||||
setTimeout(() => setError(), 4000);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<>
|
||||
{/* PASSWORD */}
|
||||
<div className={classes.containerSetting}>
|
||||
<div className={classes.settingCategory}>
|
||||
<h2>Password</h2>
|
||||
</div>
|
||||
<div className={classes.setting}>
|
||||
<div className={classes.bwFormWrapper}>
|
||||
<form onSubmit={handleSubmit(formSubmitHandler)} className={classes.bwForm}>
|
||||
{error && <Error message={error} />}
|
||||
<p>
|
||||
<input
|
||||
type='password'
|
||||
placeholder='Current password'
|
||||
{...register('oldPassword', {
|
||||
required: true,
|
||||
})}
|
||||
/>
|
||||
{errors.oldPassword && errors.oldPassword.type === 'required' && (
|
||||
<small className={classes.errorMessage}>This field is required.</small>
|
||||
)}
|
||||
</p>
|
||||
<p>
|
||||
<input
|
||||
type='password'
|
||||
placeholder='New password'
|
||||
{...register('newPassword', {
|
||||
required: true,
|
||||
})}
|
||||
/>
|
||||
{errors.newPassword && (
|
||||
<small className={classes.errorMessage}>This field is required.</small>
|
||||
)}
|
||||
</p>
|
||||
<button className={classes.AccountSettingsButton} disabled={!isValid || isSubmitting}>
|
||||
{isLoading ? (
|
||||
<SpinnerDotted size={20} thickness={150} speed={100} color='#fff' />
|
||||
) : (
|
||||
'Update your password'
|
||||
)}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,73 +12,61 @@ import AppriseAlertSettings from './AppriseAlertSettings/AppriseAlertSettings';
|
|||
import Integrations from './Integrations/Integrations';
|
||||
|
||||
export default function UserSettings(props) {
|
||||
//States
|
||||
const [tab, setTab] = useState('General');
|
||||
const [displayDeleteDialog, setDisplayDeleteDialog] = useState(false);
|
||||
//States
|
||||
const [tab, setTab] = useState('General');
|
||||
const [displayDeleteDialog, setDisplayDeleteDialog] = useState(false);
|
||||
|
||||
return (
|
||||
<div className={classes.containerSettings}>
|
||||
<div>
|
||||
<h1
|
||||
style={{
|
||||
color: '#494b7a',
|
||||
textAlign: 'left',
|
||||
marginLeft: '30px',
|
||||
}}
|
||||
>
|
||||
Account{' '}
|
||||
</h1>
|
||||
</div>
|
||||
<div className={classes.tabList}>
|
||||
<button
|
||||
className={
|
||||
tab == 'General'
|
||||
? classes.tabListButtonActive
|
||||
: classes.tabListButton
|
||||
}
|
||||
onClick={() => setTab('General')}
|
||||
>
|
||||
General
|
||||
</button>
|
||||
<button
|
||||
className={
|
||||
tab == 'Notifications'
|
||||
? classes.tabListButtonActive
|
||||
: classes.tabListButton
|
||||
}
|
||||
onClick={() => setTab('Notifications')}
|
||||
>
|
||||
Notifications
|
||||
</button>
|
||||
<button
|
||||
className={
|
||||
tab == 'Integrations'
|
||||
? classes.tabListButtonActive
|
||||
: classes.tabListButton
|
||||
}
|
||||
onClick={() => setTab('Integrations')}
|
||||
>
|
||||
Integrations
|
||||
</button>
|
||||
</div>
|
||||
{tab == 'General' && (
|
||||
<>
|
||||
<PasswordSettings username={props.data.user.name} />
|
||||
<EmailSettings email={props.data.user.email} />
|
||||
<UsernameSettings username={props.data.user.name} />{' '}
|
||||
</>
|
||||
)}
|
||||
{tab == 'Notifications' && (
|
||||
<>
|
||||
<EmailAlertSettings />
|
||||
<AppriseAlertSettings />
|
||||
</>
|
||||
)}
|
||||
{tab == 'Integrations' && (
|
||||
<>
|
||||
<Integrations />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className={classes.containerSettings}>
|
||||
<div>
|
||||
<h1
|
||||
style={{
|
||||
color: '#494b7a',
|
||||
textAlign: 'left',
|
||||
marginLeft: '30px',
|
||||
}}
|
||||
>
|
||||
Account{' '}
|
||||
</h1>
|
||||
</div>
|
||||
<div className={classes.tabList}>
|
||||
<button
|
||||
className={tab == 'General' ? classes.tabListButtonActive : classes.tabListButton}
|
||||
onClick={() => setTab('General')}
|
||||
>
|
||||
General
|
||||
</button>
|
||||
<button
|
||||
className={tab == 'Notifications' ? classes.tabListButtonActive : classes.tabListButton}
|
||||
onClick={() => setTab('Notifications')}
|
||||
>
|
||||
Notifications
|
||||
</button>
|
||||
<button
|
||||
className={tab == 'Integrations' ? classes.tabListButtonActive : classes.tabListButton}
|
||||
onClick={() => setTab('Integrations')}
|
||||
>
|
||||
Integrations
|
||||
</button>
|
||||
</div>
|
||||
{tab == 'General' && (
|
||||
<>
|
||||
<PasswordSettings username={props.data.user.name} />
|
||||
<EmailSettings email={props.data.user.email} />
|
||||
<UsernameSettings username={props.data.user.name} />{' '}
|
||||
</>
|
||||
)}
|
||||
{tab == 'Notifications' && (
|
||||
<>
|
||||
<EmailAlertSettings />
|
||||
<AppriseAlertSettings />
|
||||
</>
|
||||
)}
|
||||
{tab == 'Integrations' && (
|
||||
<>
|
||||
<Integrations />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,438 +1,426 @@
|
|||
.containerSettings {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
max-width: 1000px;
|
||||
margin-top: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
max-width: 1000px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.containerSetting {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
width: 100%;
|
||||
margin: 40px 20px 0px 5px;
|
||||
text-align: left;
|
||||
padding: 28px 24px;
|
||||
animation: entrance ease-in 0.3s 1 normal none;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
width: 100%;
|
||||
margin: 40px 20px 0px 5px;
|
||||
text-align: left;
|
||||
padding: 28px 24px;
|
||||
animation: entrance ease-in 0.3s 1 normal none;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
@keyframes entrance {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
50% {
|
||||
opacity: 0.5;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.settingCategory {
|
||||
max-width: 33.3333%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
max-width: 33.3333%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
}
|
||||
.settingCategory h2 {
|
||||
color: #494b7a;
|
||||
margin: 0;
|
||||
font-size: 1.3em;
|
||||
color: #494b7a;
|
||||
margin: 0;
|
||||
font-size: 1.3em;
|
||||
}
|
||||
|
||||
.setting {
|
||||
max-width: 66.6666%;
|
||||
width: 100%;
|
||||
max-width: 66.6666%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Tokens generation */
|
||||
|
||||
.tokenGen {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.tokenGen input {
|
||||
flex: 1;
|
||||
margin-right: 10px;
|
||||
flex: 1;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.newTokenWrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 5px;
|
||||
color: #494b7a;
|
||||
outline: 1px solid #6d4aff;
|
||||
box-shadow: 0 0 10px 3px rgba(110, 74, 255, 0.605);
|
||||
animation: entrance ease-in 0.3s 1 normal none;
|
||||
padding: 10px;
|
||||
font-family: (
|
||||
--pure-material-font,
|
||||
'Roboto',
|
||||
'Segoe UI',
|
||||
BlinkMacSystemFont,
|
||||
system-ui
|
||||
);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 5px;
|
||||
color: #494b7a;
|
||||
outline: 1px solid #6d4aff;
|
||||
box-shadow: 0 0 10px 3px rgba(110, 74, 255, 0.605);
|
||||
animation: entrance ease-in 0.3s 1 normal none;
|
||||
padding: 10px;
|
||||
font-family: (--pure-material-font, 'Roboto', 'Segoe UI', BlinkMacSystemFont, system-ui);
|
||||
}
|
||||
|
||||
.tokenCardList {
|
||||
min-width: 50%;
|
||||
min-width: 50%;
|
||||
}
|
||||
|
||||
.tokenCardWrapper {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 20px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.tokenCard {
|
||||
width: 100%;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
||||
width: 100%;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.tokenCardHeader {
|
||||
font-size: 1.2em;
|
||||
margin-bottom: 10px;
|
||||
border-bottom: 1px solid #eee;
|
||||
padding-bottom: 5px;
|
||||
color: #494b7a;
|
||||
font-size: 1.2em;
|
||||
margin-bottom: 10px;
|
||||
border-bottom: 1px solid #eee;
|
||||
padding-bottom: 5px;
|
||||
color: #494b7a;
|
||||
}
|
||||
|
||||
.tokenCardBody {
|
||||
font-size: 0.9em;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.tokenCardBody p {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
margin: 10px 0;
|
||||
color: #494b7a;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
margin: 10px 0;
|
||||
color: #494b7a;
|
||||
}
|
||||
|
||||
.tokenCardHighlight {
|
||||
animation: highlightEffect 1s ease-out forwards;
|
||||
animation: highlightEffect 1s ease-out forwards;
|
||||
}
|
||||
|
||||
@keyframes highlightEffect {
|
||||
0% {
|
||||
outline: 1px solid #6d4aff;
|
||||
box-shadow: 0 0 0 rgba(110, 74, 255, 0.5); /* Pas d'ombre au début */
|
||||
}
|
||||
50% {
|
||||
outline: 1px solid #6d4aff;
|
||||
box-shadow: 0 0 15px rgba(110, 74, 255, 0.6); /* Ombre qui s'agrandit */
|
||||
}
|
||||
100% {
|
||||
outline: 1px solid transparent; /* Bordure devient transparente */
|
||||
box-shadow: 0;
|
||||
}
|
||||
0% {
|
||||
outline: 1px solid #6d4aff;
|
||||
box-shadow: 0 0 0 rgba(110, 74, 255, 0.5); /* Pas d'ombre au début */
|
||||
}
|
||||
50% {
|
||||
outline: 1px solid #6d4aff;
|
||||
box-shadow: 0 0 15px rgba(110, 74, 255, 0.6); /* Ombre qui s'agrandit */
|
||||
}
|
||||
100% {
|
||||
outline: 1px solid transparent; /* Bordure devient transparente */
|
||||
box-shadow: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.cancelButton {
|
||||
border: 0;
|
||||
padding: 10px 15px;
|
||||
background-color: #c1c1c1;
|
||||
color: white;
|
||||
margin: 5px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
font-size: 1em;
|
||||
border: 0;
|
||||
padding: 10px 15px;
|
||||
background-color: #c1c1c1;
|
||||
color: white;
|
||||
margin: 5px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.cancelButton:hover {
|
||||
border: 0;
|
||||
padding: 10px 15px;
|
||||
background-color: #9a9a9a;
|
||||
color: white;
|
||||
margin: 5px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
font-size: 1em;
|
||||
border: 0;
|
||||
padding: 10px 15px;
|
||||
background-color: #9a9a9a;
|
||||
color: white;
|
||||
margin: 5px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.cancelButton:active {
|
||||
border: 0;
|
||||
padding: 10px 15px;
|
||||
background-color: #9a9a9a;
|
||||
color: white;
|
||||
margin: 5px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
font-size: 1em;
|
||||
transform: scale(0.9);
|
||||
border: 0;
|
||||
padding: 10px 15px;
|
||||
background-color: #9a9a9a;
|
||||
color: white;
|
||||
margin: 5px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
font-size: 1em;
|
||||
transform: scale(0.9);
|
||||
}
|
||||
|
||||
.confirmButton {
|
||||
border: 0;
|
||||
padding: 10px 15px;
|
||||
background-color: #ff0000;
|
||||
color: white;
|
||||
margin: 5px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
font-size: 1em;
|
||||
border: 0;
|
||||
padding: 10px 15px;
|
||||
background-color: #ff0000;
|
||||
color: white;
|
||||
margin: 5px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.confirmButton:hover {
|
||||
border: 0;
|
||||
padding: 10px 15px;
|
||||
background-color: #ff4b4b;
|
||||
color: white;
|
||||
margin: 5px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
font-size: 1em;
|
||||
border: 0;
|
||||
padding: 10px 15px;
|
||||
background-color: #ff4b4b;
|
||||
color: white;
|
||||
margin: 5px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.confirmButton:active {
|
||||
border: 0;
|
||||
padding: 10px 15px;
|
||||
background-color: #ff4b4b;
|
||||
color: white;
|
||||
margin: 5px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
font-size: 1em;
|
||||
transform: scale(0.9);
|
||||
border: 0;
|
||||
padding: 10px 15px;
|
||||
background-color: #ff4b4b;
|
||||
color: white;
|
||||
margin: 5px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
font-size: 1em;
|
||||
transform: scale(0.9);
|
||||
}
|
||||
|
||||
.permissionBadge {
|
||||
border-radius: 5px;
|
||||
border: 1px solid #6d4aff;
|
||||
color: #6d4aff;
|
||||
font-size: 0.9em;
|
||||
padding: 2px 5px;
|
||||
margin-right: 8px;
|
||||
border-radius: 5px;
|
||||
border: 1px solid #6d4aff;
|
||||
color: #6d4aff;
|
||||
font-size: 0.9em;
|
||||
padding: 2px 5px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
/* Forms */
|
||||
|
||||
.bwForm {
|
||||
width: 80%;
|
||||
border-radius: 5px;
|
||||
text-align: left;
|
||||
width: 80%;
|
||||
border-radius: 5px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.bwFormWrapper {
|
||||
text-align: left;
|
||||
margin: auto;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
color: #494b7a;
|
||||
font-family: var(
|
||||
--pure-material-font,
|
||||
'Roboto',
|
||||
'Segoe UI',
|
||||
BlinkMacSystemFont,
|
||||
system-ui,
|
||||
-apple-system
|
||||
);
|
||||
text-align: left;
|
||||
margin: auto;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
color: #494b7a;
|
||||
font-family: var(
|
||||
--pure-material-font,
|
||||
'Roboto',
|
||||
'Segoe UI',
|
||||
BlinkMacSystemFont,
|
||||
system-ui,
|
||||
-apple-system
|
||||
);
|
||||
}
|
||||
|
||||
.bwFormWrapper p {
|
||||
margin-block-start: 0em;
|
||||
margin-block-start: 0em;
|
||||
}
|
||||
|
||||
.bwForm label {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
text-align: center;
|
||||
/* margin-top: 20px; */
|
||||
color: #494b7a;
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
text-align: center;
|
||||
/* margin-top: 20px; */
|
||||
color: #494b7a;
|
||||
}
|
||||
|
||||
.bwForm.tokenGen label {
|
||||
margin-bottom: 0px;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
.bwForm input,
|
||||
.bwForm textarea,
|
||||
.bwForm select {
|
||||
border: 1px solid #6d4aff21;
|
||||
font-size: 16px;
|
||||
height: auto;
|
||||
margin: 0;
|
||||
margin-bottom: 0px;
|
||||
outline: 0;
|
||||
padding: 10px;
|
||||
width: 100%;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 5px;
|
||||
/* color: #1b1340; */
|
||||
color: #494b7a;
|
||||
box-shadow: 0 1px 0 rgba(0, 0, 0, 0.03) inset;
|
||||
font-family: (
|
||||
--pure-material-font,
|
||||
'Roboto',
|
||||
'Segoe UI',
|
||||
BlinkMacSystemFont,
|
||||
system-ui
|
||||
);
|
||||
border: 1px solid #6d4aff21;
|
||||
font-size: 16px;
|
||||
height: auto;
|
||||
margin: 0;
|
||||
margin-bottom: 0px;
|
||||
outline: 0;
|
||||
padding: 10px;
|
||||
width: 100%;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 5px;
|
||||
/* color: #1b1340; */
|
||||
color: #494b7a;
|
||||
box-shadow: 0 1px 0 rgba(0, 0, 0, 0.03) inset;
|
||||
font-family: (--pure-material-font, 'Roboto', 'Segoe UI', BlinkMacSystemFont, system-ui);
|
||||
}
|
||||
|
||||
.bwForm textarea {
|
||||
resize: vertical;
|
||||
overflow: auto;
|
||||
white-space: pre;
|
||||
resize: vertical;
|
||||
overflow: auto;
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
.bwForm textarea:focus,
|
||||
.bwForm input:focus,
|
||||
.bwForm select:focus {
|
||||
outline: 1px solid #6d4aff;
|
||||
box-shadow: 0 0 10px 3px rgba(110, 74, 255, 0.605);
|
||||
outline: 1px solid #6d4aff;
|
||||
box-shadow: 0 0 10px 3px rgba(110, 74, 255, 0.605);
|
||||
}
|
||||
|
||||
.bwForm .invalid {
|
||||
background: #f3c7c7;
|
||||
border: 1px solid #e45454;
|
||||
outline: 1px solid #ff4a4a;
|
||||
background: #f3c7c7;
|
||||
border: 1px solid #e45454;
|
||||
outline: 1px solid #ff4a4a;
|
||||
}
|
||||
|
||||
.bwForm .invalid:focus {
|
||||
background: #f3c7c7;
|
||||
border: 1px solid #e45454;
|
||||
outline: 1px solid #ff4a4a;
|
||||
box-shadow: 0 0 10px 3px rgba(255, 74, 74, 0.605);
|
||||
background: #f3c7c7;
|
||||
border: 1px solid #e45454;
|
||||
outline: 1px solid #ff4a4a;
|
||||
box-shadow: 0 0 10px 3px rgba(255, 74, 74, 0.605);
|
||||
}
|
||||
|
||||
.bwForm button {
|
||||
display: block;
|
||||
display: block;
|
||||
}
|
||||
.bwForm button:hover {
|
||||
display: block;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.errorMessage {
|
||||
color: red;
|
||||
display: block;
|
||||
margin-top: 3px;
|
||||
color: red;
|
||||
display: block;
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
||||
.currentSetting input::placeholder {
|
||||
opacity: 1;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.headerFormAppriseUrls {
|
||||
font-weight: 500;
|
||||
color: #494b7a;
|
||||
margin: 40px 0px 10px 0px;
|
||||
display: flex;
|
||||
padding-right: 5px;
|
||||
font-weight: 500;
|
||||
color: #494b7a;
|
||||
margin: 40px 0px 10px 0px;
|
||||
display: flex;
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
.formIsSavedMessage {
|
||||
color: rgb(0, 164, 0);
|
||||
animation: entrance 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94) both;
|
||||
font-weight: 300;
|
||||
color: rgb(0, 164, 0);
|
||||
animation: entrance 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94) both;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.tabList {
|
||||
display: flex;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.tabListButton {
|
||||
color: #494b7a;
|
||||
padding: 12px 0px;
|
||||
min-height: 48px;
|
||||
overflow: hidden;
|
||||
text-align: center;
|
||||
flex-direction: column;
|
||||
font-size: 1em;
|
||||
font-weight: 500;
|
||||
line-height: 1.71;
|
||||
text-transform: none;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
vertical-align: middle;
|
||||
text-decoration: none;
|
||||
border: 0;
|
||||
background-color: transparent;
|
||||
margin-left: 30px;
|
||||
border-bottom: 2px solid transparent;
|
||||
color: #494b7a;
|
||||
padding: 12px 0px;
|
||||
min-height: 48px;
|
||||
overflow: hidden;
|
||||
text-align: center;
|
||||
flex-direction: column;
|
||||
font-size: 1em;
|
||||
font-weight: 500;
|
||||
line-height: 1.71;
|
||||
text-transform: none;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
vertical-align: middle;
|
||||
text-decoration: none;
|
||||
border: 0;
|
||||
background-color: transparent;
|
||||
margin-left: 30px;
|
||||
border-bottom: 2px solid transparent;
|
||||
}
|
||||
|
||||
.tabListButton:hover {
|
||||
color: #6d4aff;
|
||||
border-bottom: 2px solid #6d4aff;
|
||||
color: #6d4aff;
|
||||
border-bottom: 2px solid #6d4aff;
|
||||
}
|
||||
|
||||
.tabListButtonActive {
|
||||
color: #6d4aff;
|
||||
border: 0;
|
||||
border-bottom: 2px solid #6d4aff;
|
||||
padding: 12px 0px;
|
||||
min-height: 48px;
|
||||
overflow: hidden;
|
||||
text-align: center;
|
||||
flex-direction: column;
|
||||
font-size: 1em;
|
||||
font-weight: 500;
|
||||
line-height: 1.71;
|
||||
text-transform: none;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
vertical-align: middle;
|
||||
text-decoration: none;
|
||||
background-color: transparent;
|
||||
margin-left: 30px;
|
||||
color: #6d4aff;
|
||||
border: 0;
|
||||
border-bottom: 2px solid #6d4aff;
|
||||
padding: 12px 0px;
|
||||
min-height: 48px;
|
||||
overflow: hidden;
|
||||
text-align: center;
|
||||
flex-direction: column;
|
||||
font-size: 1em;
|
||||
font-weight: 500;
|
||||
line-height: 1.71;
|
||||
text-transform: none;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
vertical-align: middle;
|
||||
text-decoration: none;
|
||||
background-color: transparent;
|
||||
margin-left: 30px;
|
||||
}
|
||||
|
||||
.AccountSettingsButton {
|
||||
border: 0;
|
||||
padding: 10px 15px;
|
||||
background-color: #6d4aff;
|
||||
color: white;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
font-size: 1em;
|
||||
border: 0;
|
||||
padding: 10px 15px;
|
||||
background-color: #6d4aff;
|
||||
color: white;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.AccountSettingsButton:hover {
|
||||
border: 0;
|
||||
padding: 10px 15px;
|
||||
background-color: #4f31ce;
|
||||
color: white;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
font-size: 1em;
|
||||
border: 0;
|
||||
padding: 10px 15px;
|
||||
background-color: #4f31ce;
|
||||
color: white;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.AccountSettingsButton:active {
|
||||
border: 0;
|
||||
padding: 10px 15px;
|
||||
background-color: #4f31ce;
|
||||
color: white;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
font-size: 1em;
|
||||
transform: scale(0.95);
|
||||
border: 0;
|
||||
padding: 10px 15px;
|
||||
background-color: #4f31ce;
|
||||
color: white;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
font-size: 1em;
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,137 +11,125 @@ import Error from '../../../Components/UI/Error/Error';
|
|||
import Info from '../../../Components/UI/Info/Info';
|
||||
|
||||
export default function UsernameSettings(props) {
|
||||
//Var
|
||||
const toastOptions = {
|
||||
position: 'top-right',
|
||||
autoClose: 8000,
|
||||
hideProgressBar: false,
|
||||
closeOnClick: true,
|
||||
pauseOnHover: true,
|
||||
draggable: true,
|
||||
progress: undefined,
|
||||
};
|
||||
//Var
|
||||
const toastOptions = {
|
||||
position: 'top-right',
|
||||
autoClose: 8000,
|
||||
hideProgressBar: false,
|
||||
closeOnClick: true,
|
||||
pauseOnHover: true,
|
||||
draggable: true,
|
||||
progress: undefined,
|
||||
};
|
||||
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
reset,
|
||||
formState: { errors, isSubmitting, isValid },
|
||||
} = useForm({ mode: 'onChange' });
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
reset,
|
||||
formState: { errors, isSubmitting, isValid },
|
||||
} = useForm({ mode: 'onChange' });
|
||||
|
||||
////State
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [error, setError] = useState();
|
||||
const [info, setInfo] = useState(false);
|
||||
////State
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [error, setError] = useState();
|
||||
const [info, setInfo] = useState(false);
|
||||
|
||||
////Functions
|
||||
//Form submit Handler for ADD a repo
|
||||
const formSubmitHandler = async (data) => {
|
||||
//Remove old error
|
||||
setError();
|
||||
//Loading button on submit to avoid multiple send.
|
||||
setIsLoading(true);
|
||||
//POST API to update the username
|
||||
try {
|
||||
const response = await fetch('/api/account/updateUsername', {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
const result = await response.json();
|
||||
////Functions
|
||||
//Form submit Handler for ADD a repo
|
||||
const formSubmitHandler = async (data) => {
|
||||
//Remove old error
|
||||
setError();
|
||||
//Loading button on submit to avoid multiple send.
|
||||
setIsLoading(true);
|
||||
//POST API to update the username
|
||||
try {
|
||||
const response = await fetch('/api/account/updateUsername', {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
const result = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
setIsLoading(false);
|
||||
reset();
|
||||
setError(result.message);
|
||||
setTimeout(() => setError(), 4000);
|
||||
} else {
|
||||
reset();
|
||||
setIsLoading(false);
|
||||
setInfo(true);
|
||||
toast.success('Username edited !', toastOptions);
|
||||
}
|
||||
} catch (error) {
|
||||
reset();
|
||||
setIsLoading(false);
|
||||
setError("Can't update your username. Contact your administrator.");
|
||||
setTimeout(() => setError(), 4000);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<>
|
||||
{/* Username */}
|
||||
<div className={classes.containerSetting}>
|
||||
<div className={classes.settingCategory}>
|
||||
<h2>Username</h2>
|
||||
</div>
|
||||
<div className={classes.setting}>
|
||||
<div className={classes.bwFormWrapper}>
|
||||
{info ? (
|
||||
//For local JWTs (cookie) without an OAuth provider, Next-Auth does not allow
|
||||
//at the time this code is written to refresh client-side session information
|
||||
//without triggering a logout.
|
||||
//I chose to inform the user to reconnect rather than force logout.
|
||||
<Info message='Please, logout to update your session.' />
|
||||
) : (
|
||||
<form
|
||||
onSubmit={handleSubmit(formSubmitHandler)}
|
||||
className={
|
||||
classes.bwForm +
|
||||
' ' +
|
||||
classes.currentSetting
|
||||
}
|
||||
>
|
||||
<p>
|
||||
{error && <Error message={error} />}
|
||||
<input
|
||||
type='text'
|
||||
placeholder={props.username}
|
||||
{...register('username', {
|
||||
required: 'A username is required.',
|
||||
pattern: {
|
||||
value: /^[a-z]{5,15}$/,
|
||||
message:
|
||||
'Only a-z characters are allowed.',
|
||||
},
|
||||
maxLength: {
|
||||
value: 10,
|
||||
message: '15 characters max.',
|
||||
},
|
||||
minLength: {
|
||||
value: 5,
|
||||
message: '5 characters min.',
|
||||
},
|
||||
})}
|
||||
/>
|
||||
{errors.username && (
|
||||
<small className={classes.errorMessage}>
|
||||
{errors.username.message}
|
||||
</small>
|
||||
)}
|
||||
</p>
|
||||
<button
|
||||
className={classes.AccountSettingsButton}
|
||||
disabled={!isValid || isSubmitting}
|
||||
>
|
||||
{isLoading ? (
|
||||
<SpinnerDotted
|
||||
size={20}
|
||||
thickness={150}
|
||||
speed={100}
|
||||
color='#fff'
|
||||
/>
|
||||
) : (
|
||||
'Update your username'
|
||||
)}
|
||||
</button>
|
||||
</form>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
if (!response.ok) {
|
||||
setIsLoading(false);
|
||||
reset();
|
||||
setError(result.message);
|
||||
setTimeout(() => setError(), 4000);
|
||||
} else {
|
||||
reset();
|
||||
setIsLoading(false);
|
||||
setInfo(true);
|
||||
toast.success('Username edited !', toastOptions);
|
||||
}
|
||||
} catch (error) {
|
||||
reset();
|
||||
setIsLoading(false);
|
||||
setError("Can't update your username. Contact your administrator.");
|
||||
setTimeout(() => setError(), 4000);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<>
|
||||
{/* Username */}
|
||||
<div className={classes.containerSetting}>
|
||||
<div className={classes.settingCategory}>
|
||||
<h2>Username</h2>
|
||||
</div>
|
||||
<div className={classes.setting}>
|
||||
<div className={classes.bwFormWrapper}>
|
||||
{info ? (
|
||||
//For local JWTs (cookie) without an OAuth provider, Next-Auth does not allow
|
||||
//at the time this code is written to refresh client-side session information
|
||||
//without triggering a logout.
|
||||
//I chose to inform the user to reconnect rather than force logout.
|
||||
<Info message='Please, logout to update your session.' />
|
||||
) : (
|
||||
<form
|
||||
onSubmit={handleSubmit(formSubmitHandler)}
|
||||
className={classes.bwForm + ' ' + classes.currentSetting}
|
||||
>
|
||||
<p>
|
||||
{error && <Error message={error} />}
|
||||
<input
|
||||
type='text'
|
||||
placeholder={props.username}
|
||||
{...register('username', {
|
||||
required: 'A username is required.',
|
||||
pattern: {
|
||||
value: /^[a-z]{5,15}$/,
|
||||
message: 'Only a-z characters are allowed.',
|
||||
},
|
||||
maxLength: {
|
||||
value: 10,
|
||||
message: '15 characters max.',
|
||||
},
|
||||
minLength: {
|
||||
value: 5,
|
||||
message: '5 characters min.',
|
||||
},
|
||||
})}
|
||||
/>
|
||||
{errors.username && (
|
||||
<small className={classes.errorMessage}>{errors.username.message}</small>
|
||||
)}
|
||||
</p>
|
||||
<button
|
||||
className={classes.AccountSettingsButton}
|
||||
disabled={!isValid || isSubmitting}
|
||||
>
|
||||
{isLoading ? (
|
||||
<SpinnerDotted size={20} thickness={150} speed={100} color='#fff' />
|
||||
) : (
|
||||
'Update your username'
|
||||
)}
|
||||
</button>
|
||||
</form>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,9 +3,9 @@
|
|||
import { hash, compare } from 'bcryptjs';
|
||||
|
||||
export async function hashPassword(password) {
|
||||
return await hash(password, 12);
|
||||
return await hash(password, 12);
|
||||
}
|
||||
|
||||
export async function verifyPassword(password, hashedPassword) {
|
||||
return await compare(password, hashedPassword);
|
||||
return await compare(password, hashedPassword);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +1,14 @@
|
|||
export default function lanCommandOption(wizardEnv, lanCommand) {
|
||||
let FQDN;
|
||||
let SSH_SERVER_PORT;
|
||||
if (lanCommand && wizardEnv.FQDN_LAN && wizardEnv.SSH_SERVER_PORT_LAN) {
|
||||
FQDN = wizardEnv.FQDN_LAN;
|
||||
SSH_SERVER_PORT =
|
||||
wizardEnv.SSH_SERVER_PORT_LAN === 'false'
|
||||
? ''
|
||||
: ':' + wizardEnv.SSH_SERVER_PORT_LAN;
|
||||
} else {
|
||||
FQDN = wizardEnv.FQDN;
|
||||
SSH_SERVER_PORT =
|
||||
wizardEnv.SSH_SERVER_PORT === 'false'
|
||||
? ''
|
||||
: ':' + wizardEnv.SSH_SERVER_PORT;
|
||||
}
|
||||
let FQDN;
|
||||
let SSH_SERVER_PORT;
|
||||
if (lanCommand && wizardEnv.FQDN_LAN && wizardEnv.SSH_SERVER_PORT_LAN) {
|
||||
FQDN = wizardEnv.FQDN_LAN;
|
||||
SSH_SERVER_PORT =
|
||||
wizardEnv.SSH_SERVER_PORT_LAN === 'false' ? '' : ':' + wizardEnv.SSH_SERVER_PORT_LAN;
|
||||
} else {
|
||||
FQDN = wizardEnv.FQDN;
|
||||
SSH_SERVER_PORT = wizardEnv.SSH_SERVER_PORT === 'false' ? '' : ':' + wizardEnv.SSH_SERVER_PORT;
|
||||
}
|
||||
|
||||
return { FQDN, SSH_SERVER_PORT };
|
||||
return { FQDN, SSH_SERVER_PORT };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,17 +2,17 @@
|
|||
import nodemailer from 'nodemailer';
|
||||
|
||||
export default function nodemailerSMTP() {
|
||||
const transporter = nodemailer.createTransport({
|
||||
port: process.env.MAIL_SMTP_PORT,
|
||||
host: process.env.MAIL_SMTP_HOST,
|
||||
auth: {
|
||||
user: process.env.MAIL_SMTP_LOGIN,
|
||||
pass: process.env.MAIL_SMTP_PWD,
|
||||
},
|
||||
tls: {
|
||||
// do not fail on invalid certs >> allow self-signed or invalid TLS certificate
|
||||
rejectUnauthorized: process.env.MAIL_REJECT_SELFSIGNED_TLS,
|
||||
},
|
||||
});
|
||||
return transporter;
|
||||
const transporter = nodemailer.createTransport({
|
||||
port: process.env.MAIL_SMTP_PORT,
|
||||
host: process.env.MAIL_SMTP_HOST,
|
||||
auth: {
|
||||
user: process.env.MAIL_SMTP_LOGIN,
|
||||
pass: process.env.MAIL_SMTP_PWD,
|
||||
},
|
||||
tls: {
|
||||
// do not fail on invalid certs >> allow self-signed or invalid TLS certificate
|
||||
rejectUnauthorized: process.env.MAIL_REJECT_SELFSIGNED_TLS,
|
||||
},
|
||||
});
|
||||
return transporter;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,44 +2,35 @@ import { promises as fs } from 'fs';
|
|||
import path from 'path';
|
||||
|
||||
export default async function repoHistory(data) {
|
||||
try {
|
||||
const repoHistoryDir = path.join(process.cwd(), '/config/versions');
|
||||
const maxBackupCount = parseInt(process.env.MAX_REPO_BACKUP_COUNT) || 8;
|
||||
const timestamp = new Date().toISOString();
|
||||
const backupDate = timestamp.split('T')[0];
|
||||
try {
|
||||
const repoHistoryDir = path.join(process.cwd(), '/config/versions');
|
||||
const maxBackupCount = parseInt(process.env.MAX_REPO_BACKUP_COUNT) || 8;
|
||||
const timestamp = new Date().toISOString();
|
||||
const backupDate = timestamp.split('T')[0];
|
||||
|
||||
//Create the directory if it does not exist
|
||||
await fs.mkdir(repoHistoryDir, { recursive: true });
|
||||
//Create the directory if it does not exist
|
||||
await fs.mkdir(repoHistoryDir, { recursive: true });
|
||||
|
||||
const existingBackups = await fs.readdir(repoHistoryDir);
|
||||
const existingBackups = await fs.readdir(repoHistoryDir);
|
||||
|
||||
if (existingBackups.length >= maxBackupCount) {
|
||||
existingBackups.sort();
|
||||
const backupsToDelete = existingBackups.slice(
|
||||
0,
|
||||
existingBackups.length - maxBackupCount + 1
|
||||
);
|
||||
for (const backupToDelete of backupsToDelete) {
|
||||
const backupFilePathToDelete = path.join(
|
||||
repoHistoryDir,
|
||||
backupToDelete
|
||||
);
|
||||
await fs.unlink(backupFilePathToDelete);
|
||||
}
|
||||
}
|
||||
|
||||
const backupFileName = `${backupDate}.log`;
|
||||
const backupFilePath = path.join(repoHistoryDir, backupFileName);
|
||||
const jsonData = JSON.stringify(data, null, 2);
|
||||
|
||||
const logData = `\n>>>> History of file repo.json at "${timestamp}" <<<<\n${jsonData}\n`;
|
||||
|
||||
// Écrire ou réécrire le fichier avec le contenu mis à jour
|
||||
await fs.appendFile(backupFilePath, logData);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
'An error occurred while saving the repo history :',
|
||||
error.message
|
||||
);
|
||||
if (existingBackups.length >= maxBackupCount) {
|
||||
existingBackups.sort();
|
||||
const backupsToDelete = existingBackups.slice(0, existingBackups.length - maxBackupCount + 1);
|
||||
for (const backupToDelete of backupsToDelete) {
|
||||
const backupFilePathToDelete = path.join(repoHistoryDir, backupToDelete);
|
||||
await fs.unlink(backupFilePathToDelete);
|
||||
}
|
||||
}
|
||||
|
||||
const backupFileName = `${backupDate}.log`;
|
||||
const backupFilePath = path.join(repoHistoryDir, backupFileName);
|
||||
const jsonData = JSON.stringify(data, null, 2);
|
||||
|
||||
const logData = `\n>>>> History of file repo.json at "${timestamp}" <<<<\n${jsonData}\n`;
|
||||
|
||||
// Écrire ou réécrire le fichier avec le contenu mis à jour
|
||||
await fs.appendFile(backupFilePath, logData);
|
||||
} catch (error) {
|
||||
console.error('An error occurred while saving the repo history :', error.message);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
// This function is used to parse the date and time into a human readable format from the timestamp.
|
||||
export default function timestampConverter(UNIX_timestamp) {
|
||||
const a = new Date(UNIX_timestamp * 1000);
|
||||
const year = a.getFullYear();
|
||||
const month = a.getMonth() + 1;
|
||||
const date = a.getDate();
|
||||
const hour = a.getHours();
|
||||
const min = (a.getMinutes() < 10 ? '0' : '') + a.getMinutes();
|
||||
//const sec = a.getSeconds();
|
||||
const time = year + '/' + month + '/' + date + ' ' + hour + ':' + min;
|
||||
return time;
|
||||
const a = new Date(UNIX_timestamp * 1000);
|
||||
const year = a.getFullYear();
|
||||
const month = a.getMonth() + 1;
|
||||
const date = a.getDate();
|
||||
const hour = a.getHours();
|
||||
const min = (a.getMinutes() < 10 ? '0' : '') + a.getMinutes();
|
||||
//const sec = a.getSeconds();
|
||||
const time = year + '/' + month + '/' + date + ' ' + hour + ':' + min;
|
||||
return time;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +1,19 @@
|
|||
export default function emailTest(mailTo, username, aliasList) {
|
||||
const aliasTemplate = (x) => {
|
||||
let str = '';
|
||||
for (const alias of x) {
|
||||
str = str + '<li>' + alias + '</li>';
|
||||
}
|
||||
return str;
|
||||
};
|
||||
const aliasTemplate = (x) => {
|
||||
let str = '';
|
||||
for (const alias of x) {
|
||||
str = str + '<li>' + alias + '</li>';
|
||||
}
|
||||
return str;
|
||||
};
|
||||
|
||||
const template = {
|
||||
from: 'BorgWarehouse' + '<' + process.env.MAIL_SMTP_FROM + '>',
|
||||
to: mailTo,
|
||||
subject: 'Down status alert !',
|
||||
text: 'Some repositories require your attention ! Please, check your BorgWarehouse interface.',
|
||||
html:
|
||||
`
|
||||
const template = {
|
||||
from: 'BorgWarehouse' + '<' + process.env.MAIL_SMTP_FROM + '>',
|
||||
to: mailTo,
|
||||
subject: 'Down status alert !',
|
||||
text: 'Some repositories require your attention ! Please, check your BorgWarehouse interface.',
|
||||
html:
|
||||
`
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
|
|
@ -101,14 +101,14 @@ export default function emailTest(mailTo, username, aliasList) {
|
|||
</div>
|
||||
<div class="message">
|
||||
<p>Some repositories require your attention, ` +
|
||||
username +
|
||||
`!</p>
|
||||
username +
|
||||
`!</p>
|
||||
</div>
|
||||
<div class="description">
|
||||
<p>List of repositories with down status:</p>
|
||||
<ul>` +
|
||||
aliasTemplate(aliasList) +
|
||||
`</ul>
|
||||
aliasTemplate(aliasList) +
|
||||
`</ul>
|
||||
</div>
|
||||
<div class="alert">
|
||||
<div style="flex-shrink: 1; margin-right: 0.75rem">🚩</div>
|
||||
|
|
@ -124,12 +124,12 @@ export default function emailTest(mailTo, username, aliasList) {
|
|||
</body>
|
||||
</html>
|
||||
`,
|
||||
attachments: [
|
||||
{
|
||||
path: 'helpers/templates/attachments/alert-icon.png',
|
||||
cid: 'alert-icon',
|
||||
},
|
||||
],
|
||||
};
|
||||
return template;
|
||||
attachments: [
|
||||
{
|
||||
path: 'helpers/templates/attachments/alert-icon.png',
|
||||
cid: 'alert-icon',
|
||||
},
|
||||
],
|
||||
};
|
||||
return template;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
export default function emailTest(mailTo, username) {
|
||||
const template = {
|
||||
from: 'BorgWarehouse' + '<' + process.env.MAIL_SMTP_FROM + '>',
|
||||
to: mailTo,
|
||||
subject: 'Testing email settings',
|
||||
text: 'If you received this email then the mail configuration seems to be correct.',
|
||||
html:
|
||||
`
|
||||
const template = {
|
||||
from: 'BorgWarehouse' + '<' + process.env.MAIL_SMTP_FROM + '>',
|
||||
to: mailTo,
|
||||
subject: 'Testing email settings',
|
||||
text: 'If you received this email then the mail configuration seems to be correct.',
|
||||
html:
|
||||
`
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
|
|
@ -78,8 +78,8 @@ export default function emailTest(mailTo, username) {
|
|||
</div>
|
||||
<div class="message">
|
||||
<p>Good job, ` +
|
||||
username +
|
||||
`!</p>
|
||||
username +
|
||||
`!</p>
|
||||
</div>
|
||||
<div class="description">
|
||||
<p>If you received this mail then the configuration seems to be correct.</p>
|
||||
|
|
@ -92,12 +92,12 @@ export default function emailTest(mailTo, username) {
|
|||
</body>
|
||||
</html>
|
||||
`,
|
||||
attachments: [
|
||||
{
|
||||
path: 'helpers/templates/attachments/valid-icon.png',
|
||||
cid: 'valid-icon',
|
||||
},
|
||||
],
|
||||
};
|
||||
return template;
|
||||
attachments: [
|
||||
{
|
||||
path: 'helpers/templates/attachments/valid-icon.png',
|
||||
cid: 'valid-icon',
|
||||
},
|
||||
],
|
||||
};
|
||||
return template;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,8 @@
|
|||
"start": "next start",
|
||||
"lint": "next lint",
|
||||
"setup": "npm install && npm run setup:hooks",
|
||||
"setup:hooks": "npx husky install"
|
||||
"setup:hooks": "npx husky install",
|
||||
"format": "prettier --write \"{Components,Containers,helpers,pages,styles}/**/*.{js,jsx,ts,tsx,json,css,scss,md}\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@tabler/icons-react": "^3.14.0",
|
||||
|
|
@ -34,4 +35,5 @@
|
|||
"husky": "^9.1.5",
|
||||
"prettier": "^3.3.3"
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
52
pages/404.js
52
pages/404.js
|
|
@ -5,33 +5,29 @@ import { useRouter } from 'next/router';
|
|||
import Image from 'next/image';
|
||||
|
||||
export default function Error404() {
|
||||
//Var
|
||||
const { status } = useSession();
|
||||
const router = useRouter();
|
||||
//Var
|
||||
const { status } = useSession();
|
||||
const router = useRouter();
|
||||
|
||||
if (status === 'authenticated') {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>404 - Page not found</title>
|
||||
</Head>
|
||||
<div
|
||||
style={{
|
||||
marginTop: '30px',
|
||||
height: '60%',
|
||||
width: '60%',
|
||||
position: 'absolute',
|
||||
}}
|
||||
>
|
||||
<Image
|
||||
src='/404.svg'
|
||||
alt='404 - Page not found'
|
||||
layout='fill'
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
} else {
|
||||
router.replace('/login');
|
||||
}
|
||||
if (status === 'authenticated') {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>404 - Page not found</title>
|
||||
</Head>
|
||||
<div
|
||||
style={{
|
||||
marginTop: '30px',
|
||||
height: '60%',
|
||||
width: '60%',
|
||||
position: 'absolute',
|
||||
}}
|
||||
>
|
||||
<Image src='/404.svg' alt='404 - Page not found' layout='fill' />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
} else {
|
||||
router.replace('/login');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,20 +9,17 @@ import { SessionProvider } from 'next-auth/react';
|
|||
import Layout from '../Components/UI/Layout/Layout';
|
||||
|
||||
export default function MyApp({ Component, pageProps }) {
|
||||
return (
|
||||
<SessionProvider session={pageProps.session}>
|
||||
<Layout>
|
||||
<Head>
|
||||
<meta
|
||||
name='viewport'
|
||||
content='width=device-width, initial-scale=1'
|
||||
></meta>
|
||||
<link rel='shortcut icon' href='/favicon.ico' />
|
||||
<title>BorgWarehouse</title>
|
||||
</Head>
|
||||
<ToastContainer stacked />
|
||||
<Component {...pageProps} />
|
||||
</Layout>
|
||||
</SessionProvider>
|
||||
);
|
||||
return (
|
||||
<SessionProvider session={pageProps.session}>
|
||||
<Layout>
|
||||
<Head>
|
||||
<meta name='viewport' content='width=device-width, initial-scale=1'></meta>
|
||||
<link rel='shortcut icon' href='/favicon.ico' />
|
||||
<title>BorgWarehouse</title>
|
||||
</Head>
|
||||
<ToastContainer stacked />
|
||||
<Component {...pageProps} />
|
||||
</Layout>
|
||||
</SessionProvider>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,42 +9,38 @@ import { getServerSession } from 'next-auth/next';
|
|||
import UserSettings from '../../Containers/UserSettings/UserSettings';
|
||||
|
||||
export default function Account() {
|
||||
////Var
|
||||
const { status, data } = useSession();
|
||||
////Var
|
||||
const { status, data } = useSession();
|
||||
|
||||
//Function
|
||||
if (status == 'unauthenticated' || status == 'loading') {
|
||||
return <p>Loading...</p>;
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>Account - BorgWarehouse</title>
|
||||
</Head>
|
||||
//Function
|
||||
if (status == 'unauthenticated' || status == 'loading') {
|
||||
return <p>Loading...</p>;
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>Account - BorgWarehouse</title>
|
||||
</Head>
|
||||
|
||||
<UserSettings status={status} data={data} />
|
||||
</>
|
||||
);
|
||||
<UserSettings status={status} data={data} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export async function getServerSideProps(context) {
|
||||
//Var
|
||||
const session = await getServerSession(
|
||||
context.req,
|
||||
context.res,
|
||||
authOptions
|
||||
);
|
||||
|
||||
if (!session) {
|
||||
return {
|
||||
redirect: {
|
||||
destination: '/login',
|
||||
permanent: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
//Var
|
||||
const session = await getServerSession(context.req, context.res, authOptions);
|
||||
|
||||
if (!session) {
|
||||
return {
|
||||
props: {},
|
||||
redirect: {
|
||||
destination: '/login',
|
||||
permanent: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
props: {},
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,59 +5,53 @@ import { authOptions } from '../auth/[...nextauth]';
|
|||
import { getServerSession } from 'next-auth/next';
|
||||
|
||||
export default async function handler(req, res) {
|
||||
if (req.method == 'GET') {
|
||||
//Verify that the user is logged in.
|
||||
const session = await getServerSession(req, res, authOptions);
|
||||
if (!session) {
|
||||
res.status(401).json({ message: 'You must be logged in.' });
|
||||
return;
|
||||
}
|
||||
try {
|
||||
//Read the users file
|
||||
//Find the absolute path of the json directory
|
||||
const jsonDirectory = path.join(process.cwd(), '/config');
|
||||
let usersList = await fs.readFile(
|
||||
jsonDirectory + '/users.json',
|
||||
'utf8'
|
||||
);
|
||||
//Parse the usersList
|
||||
usersList = JSON.parse(usersList);
|
||||
|
||||
//Verify that the user of the session exists
|
||||
const userIndex = usersList
|
||||
.map((user) => user.username)
|
||||
.indexOf(session.user.name);
|
||||
if (userIndex === -1) {
|
||||
res.status(400).json({
|
||||
message:
|
||||
'User is incorrect. Please, logout to update your session.',
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
//Send the appriseAlert bool
|
||||
res.status(200).json({
|
||||
appriseAlert: usersList[userIndex].appriseAlert,
|
||||
});
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
//Log for backend
|
||||
console.log(error);
|
||||
//Log for frontend
|
||||
if (error.code == 'ENOENT') {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: 'No such file or directory',
|
||||
});
|
||||
} else {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: 'API error, contact the administrator',
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
res.status(405).json({ message: 'Bad request on API' });
|
||||
if (req.method == 'GET') {
|
||||
//Verify that the user is logged in.
|
||||
const session = await getServerSession(req, res, authOptions);
|
||||
if (!session) {
|
||||
res.status(401).json({ message: 'You must be logged in.' });
|
||||
return;
|
||||
}
|
||||
try {
|
||||
//Read the users file
|
||||
//Find the absolute path of the json directory
|
||||
const jsonDirectory = path.join(process.cwd(), '/config');
|
||||
let usersList = await fs.readFile(jsonDirectory + '/users.json', 'utf8');
|
||||
//Parse the usersList
|
||||
usersList = JSON.parse(usersList);
|
||||
|
||||
//Verify that the user of the session exists
|
||||
const userIndex = usersList.map((user) => user.username).indexOf(session.user.name);
|
||||
if (userIndex === -1) {
|
||||
res.status(400).json({
|
||||
message: 'User is incorrect. Please, logout to update your session.',
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
//Send the appriseAlert bool
|
||||
res.status(200).json({
|
||||
appriseAlert: usersList[userIndex].appriseAlert,
|
||||
});
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
//Log for backend
|
||||
console.log(error);
|
||||
//Log for frontend
|
||||
if (error.code == 'ENOENT') {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: 'No such file or directory',
|
||||
});
|
||||
} else {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: 'API error, contact the administrator',
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
res.status(405).json({ message: 'Bad request on API' });
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,61 +5,54 @@ import { authOptions } from '../auth/[...nextauth]';
|
|||
import { getServerSession } from 'next-auth/next';
|
||||
|
||||
export default async function handler(req, res) {
|
||||
if (req.method == 'GET') {
|
||||
//Verify that the user is logged in.
|
||||
const session = await getServerSession(req, res, authOptions);
|
||||
if (!session) {
|
||||
res.status(401).json({ message: 'You must be logged in.' });
|
||||
return;
|
||||
}
|
||||
try {
|
||||
//Read the users file
|
||||
//Find the absolute path of the json directory
|
||||
const jsonDirectory = path.join(process.cwd(), '/config');
|
||||
let usersList = await fs.readFile(
|
||||
jsonDirectory + '/users.json',
|
||||
'utf8'
|
||||
);
|
||||
//Parse the usersList
|
||||
usersList = JSON.parse(usersList);
|
||||
|
||||
//Verify that the user of the session exists
|
||||
const userIndex = usersList
|
||||
.map((user) => user.username)
|
||||
.indexOf(session.user.name);
|
||||
if (userIndex === -1) {
|
||||
res.status(400).json({
|
||||
message:
|
||||
'User is incorrect. Please, logout to update your session.',
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
//Send the appriseMode object
|
||||
res.status(200).json({
|
||||
appriseMode: usersList[userIndex].appriseMode,
|
||||
appriseStatelessURL:
|
||||
usersList[userIndex].appriseStatelessURL,
|
||||
});
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
//Log for backend
|
||||
console.log(error);
|
||||
//Log for frontend
|
||||
if (error.code == 'ENOENT') {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: 'No such file or directory',
|
||||
});
|
||||
} else {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: 'API error, contact the administrator',
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
res.status(405).json({ message: 'Bad request on API' });
|
||||
if (req.method == 'GET') {
|
||||
//Verify that the user is logged in.
|
||||
const session = await getServerSession(req, res, authOptions);
|
||||
if (!session) {
|
||||
res.status(401).json({ message: 'You must be logged in.' });
|
||||
return;
|
||||
}
|
||||
try {
|
||||
//Read the users file
|
||||
//Find the absolute path of the json directory
|
||||
const jsonDirectory = path.join(process.cwd(), '/config');
|
||||
let usersList = await fs.readFile(jsonDirectory + '/users.json', 'utf8');
|
||||
//Parse the usersList
|
||||
usersList = JSON.parse(usersList);
|
||||
|
||||
//Verify that the user of the session exists
|
||||
const userIndex = usersList.map((user) => user.username).indexOf(session.user.name);
|
||||
if (userIndex === -1) {
|
||||
res.status(400).json({
|
||||
message: 'User is incorrect. Please, logout to update your session.',
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
//Send the appriseMode object
|
||||
res.status(200).json({
|
||||
appriseMode: usersList[userIndex].appriseMode,
|
||||
appriseStatelessURL: usersList[userIndex].appriseStatelessURL,
|
||||
});
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
//Log for backend
|
||||
console.log(error);
|
||||
//Log for frontend
|
||||
if (error.code == 'ENOENT') {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: 'No such file or directory',
|
||||
});
|
||||
} else {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: 'API error, contact the administrator',
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
res.status(405).json({ message: 'Bad request on API' });
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,59 +5,53 @@ import { authOptions } from '../auth/[...nextauth]';
|
|||
import { getServerSession } from 'next-auth/next';
|
||||
|
||||
export default async function handler(req, res) {
|
||||
if (req.method == 'GET') {
|
||||
//Verify that the user is logged in.
|
||||
const session = await getServerSession(req, res, authOptions);
|
||||
if (!session) {
|
||||
res.status(401).json({ message: 'You must be logged in.' });
|
||||
return;
|
||||
}
|
||||
try {
|
||||
//Read the users file
|
||||
//Find the absolute path of the json directory
|
||||
const jsonDirectory = path.join(process.cwd(), '/config');
|
||||
let usersList = await fs.readFile(
|
||||
jsonDirectory + '/users.json',
|
||||
'utf8'
|
||||
);
|
||||
//Parse the usersList
|
||||
usersList = JSON.parse(usersList);
|
||||
|
||||
//Verify that the user of the session exists
|
||||
const userIndex = usersList
|
||||
.map((user) => user.username)
|
||||
.indexOf(session.user.name);
|
||||
if (userIndex === -1) {
|
||||
res.status(400).json({
|
||||
message:
|
||||
'User is incorrect. Please, logout to update your session.',
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
//Send the appriseServices array
|
||||
res.status(200).json({
|
||||
appriseServices: usersList[userIndex].appriseServices,
|
||||
});
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
//Log for backend
|
||||
console.log(error);
|
||||
//Log for frontend
|
||||
if (error.code == 'ENOENT') {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: 'No such file or directory',
|
||||
});
|
||||
} else {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: 'API error, contact the administrator',
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
res.status(405).json({ message: 'Bad request on API' });
|
||||
if (req.method == 'GET') {
|
||||
//Verify that the user is logged in.
|
||||
const session = await getServerSession(req, res, authOptions);
|
||||
if (!session) {
|
||||
res.status(401).json({ message: 'You must be logged in.' });
|
||||
return;
|
||||
}
|
||||
try {
|
||||
//Read the users file
|
||||
//Find the absolute path of the json directory
|
||||
const jsonDirectory = path.join(process.cwd(), '/config');
|
||||
let usersList = await fs.readFile(jsonDirectory + '/users.json', 'utf8');
|
||||
//Parse the usersList
|
||||
usersList = JSON.parse(usersList);
|
||||
|
||||
//Verify that the user of the session exists
|
||||
const userIndex = usersList.map((user) => user.username).indexOf(session.user.name);
|
||||
if (userIndex === -1) {
|
||||
res.status(400).json({
|
||||
message: 'User is incorrect. Please, logout to update your session.',
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
//Send the appriseServices array
|
||||
res.status(200).json({
|
||||
appriseServices: usersList[userIndex].appriseServices,
|
||||
});
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
//Log for backend
|
||||
console.log(error);
|
||||
//Log for frontend
|
||||
if (error.code == 'ENOENT') {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: 'No such file or directory',
|
||||
});
|
||||
} else {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: 'API error, contact the administrator',
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
res.status(405).json({ message: 'Bad request on API' });
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,59 +5,53 @@ import { authOptions } from '../auth/[...nextauth]';
|
|||
import { getServerSession } from 'next-auth/next';
|
||||
|
||||
export default async function handler(req, res) {
|
||||
if (req.method == 'GET') {
|
||||
//Verify that the user is logged in.
|
||||
const session = await getServerSession(req, res, authOptions);
|
||||
if (!session) {
|
||||
res.status(401).json({ message: 'You must be logged in.' });
|
||||
return;
|
||||
}
|
||||
try {
|
||||
//Read the users file
|
||||
//Find the absolute path of the json directory
|
||||
const jsonDirectory = path.join(process.cwd(), '/config');
|
||||
let usersList = await fs.readFile(
|
||||
jsonDirectory + '/users.json',
|
||||
'utf8'
|
||||
);
|
||||
//Parse the usersList
|
||||
usersList = JSON.parse(usersList);
|
||||
|
||||
//Verify that the user of the session exists
|
||||
const userIndex = usersList
|
||||
.map((user) => user.username)
|
||||
.indexOf(session.user.name);
|
||||
if (userIndex === -1) {
|
||||
res.status(400).json({
|
||||
message:
|
||||
'User is incorrect. Please, logout to update your session.',
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
//Send the emailAlert bool
|
||||
res.status(200).json({
|
||||
emailAlert: usersList[userIndex].emailAlert,
|
||||
});
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
//Log for backend
|
||||
console.log(error);
|
||||
//Log for frontend
|
||||
if (error.code == 'ENOENT') {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: 'No such file or directory',
|
||||
});
|
||||
} else {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: 'API error, contact the administrator',
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
res.status(405).json({ message: 'Bad request on API' });
|
||||
if (req.method == 'GET') {
|
||||
//Verify that the user is logged in.
|
||||
const session = await getServerSession(req, res, authOptions);
|
||||
if (!session) {
|
||||
res.status(401).json({ message: 'You must be logged in.' });
|
||||
return;
|
||||
}
|
||||
try {
|
||||
//Read the users file
|
||||
//Find the absolute path of the json directory
|
||||
const jsonDirectory = path.join(process.cwd(), '/config');
|
||||
let usersList = await fs.readFile(jsonDirectory + '/users.json', 'utf8');
|
||||
//Parse the usersList
|
||||
usersList = JSON.parse(usersList);
|
||||
|
||||
//Verify that the user of the session exists
|
||||
const userIndex = usersList.map((user) => user.username).indexOf(session.user.name);
|
||||
if (userIndex === -1) {
|
||||
res.status(400).json({
|
||||
message: 'User is incorrect. Please, logout to update your session.',
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
//Send the emailAlert bool
|
||||
res.status(200).json({
|
||||
emailAlert: usersList[userIndex].emailAlert,
|
||||
});
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
//Log for backend
|
||||
console.log(error);
|
||||
//Log for frontend
|
||||
if (error.code == 'ENOENT') {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: 'No such file or directory',
|
||||
});
|
||||
} else {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: 'API error, contact the administrator',
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
res.status(405).json({ message: 'Bad request on API' });
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,47 +3,41 @@ import { authOptions } from '../auth/[...nextauth]';
|
|||
import { getServerSession } from 'next-auth/next';
|
||||
|
||||
export default async function handler(req, res) {
|
||||
if (req.method == 'GET') {
|
||||
//Verify that the user is logged in.
|
||||
const session = await getServerSession(req, res, authOptions);
|
||||
if (!session) {
|
||||
res.status(401).json({ message: 'You must be logged in.' });
|
||||
return;
|
||||
}
|
||||
try {
|
||||
function getEnvVariable(envName, defaultValue = '') {
|
||||
return process.env[envName] || defaultValue;
|
||||
}
|
||||
|
||||
const wizardEnv = {
|
||||
UNIX_USER: getEnvVariable('UNIX_USER', 'borgwarehouse'),
|
||||
FQDN: getEnvVariable('FQDN', 'localhost'),
|
||||
SSH_SERVER_PORT: getEnvVariable('SSH_SERVER_PORT', '22'),
|
||||
FQDN_LAN: getEnvVariable('FQDN_LAN'),
|
||||
SSH_SERVER_PORT_LAN: getEnvVariable('SSH_SERVER_PORT_LAN'),
|
||||
SSH_SERVER_FINGERPRINT_RSA: getEnvVariable(
|
||||
'SSH_SERVER_FINGERPRINT_RSA'
|
||||
),
|
||||
SSH_SERVER_FINGERPRINT_ED25519: getEnvVariable(
|
||||
'SSH_SERVER_FINGERPRINT_ED25519'
|
||||
),
|
||||
SSH_SERVER_FINGERPRINT_ECDSA: getEnvVariable(
|
||||
'SSH_SERVER_FINGERPRINT_ECDSA'
|
||||
),
|
||||
};
|
||||
res.status(200).json({ wizardEnv });
|
||||
return;
|
||||
} catch (error) {
|
||||
//Log for backend
|
||||
console.log(error);
|
||||
//Log for frontend
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: 'API error, contact the administrator',
|
||||
});
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
res.status(405).json({ message: 'Bad request on API' });
|
||||
if (req.method == 'GET') {
|
||||
//Verify that the user is logged in.
|
||||
const session = await getServerSession(req, res, authOptions);
|
||||
if (!session) {
|
||||
res.status(401).json({ message: 'You must be logged in.' });
|
||||
return;
|
||||
}
|
||||
try {
|
||||
function getEnvVariable(envName, defaultValue = '') {
|
||||
return process.env[envName] || defaultValue;
|
||||
}
|
||||
|
||||
const wizardEnv = {
|
||||
UNIX_USER: getEnvVariable('UNIX_USER', 'borgwarehouse'),
|
||||
FQDN: getEnvVariable('FQDN', 'localhost'),
|
||||
SSH_SERVER_PORT: getEnvVariable('SSH_SERVER_PORT', '22'),
|
||||
FQDN_LAN: getEnvVariable('FQDN_LAN'),
|
||||
SSH_SERVER_PORT_LAN: getEnvVariable('SSH_SERVER_PORT_LAN'),
|
||||
SSH_SERVER_FINGERPRINT_RSA: getEnvVariable('SSH_SERVER_FINGERPRINT_RSA'),
|
||||
SSH_SERVER_FINGERPRINT_ED25519: getEnvVariable('SSH_SERVER_FINGERPRINT_ED25519'),
|
||||
SSH_SERVER_FINGERPRINT_ECDSA: getEnvVariable('SSH_SERVER_FINGERPRINT_ECDSA'),
|
||||
};
|
||||
res.status(200).json({ wizardEnv });
|
||||
return;
|
||||
} catch (error) {
|
||||
//Log for backend
|
||||
console.log(error);
|
||||
//Log for frontend
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: 'API error, contact the administrator',
|
||||
});
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
res.status(405).json({ message: 'Bad request on API' });
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,158 +6,140 @@ import path from 'path';
|
|||
const { exec } = require('child_process');
|
||||
|
||||
export default async function handler(req, res) {
|
||||
if (req.method == 'POST') {
|
||||
//Verify that the user is logged in.
|
||||
const session = await getServerSession(req, res, authOptions);
|
||||
if (!session) {
|
||||
res.status(401).json({ message: 'You must be logged in.' });
|
||||
return;
|
||||
}
|
||||
|
||||
//The data we expect to receive
|
||||
let { sendTestApprise } = req.body;
|
||||
|
||||
//Read the users file
|
||||
//Find the absolute path of the json directory
|
||||
const jsonDirectory = path.join(process.cwd(), '/config');
|
||||
let usersList = await fs.readFile(
|
||||
jsonDirectory + '/users.json',
|
||||
'utf8'
|
||||
);
|
||||
//Parse the usersList
|
||||
usersList = JSON.parse(usersList);
|
||||
|
||||
//1 : Verify that the user of the session exists
|
||||
const userIndex = usersList
|
||||
.map((user) => user.username)
|
||||
.indexOf(session.user.name);
|
||||
if (userIndex === -1) {
|
||||
res.status(400).json({
|
||||
message:
|
||||
'User is incorrect. Please, logout to update your session.',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
//2 : control the data
|
||||
if (sendTestApprise !== true) {
|
||||
res.status(422).json({ message: 'Unexpected data' });
|
||||
return;
|
||||
}
|
||||
|
||||
//3 : if there is no service URLs, throw error
|
||||
if (
|
||||
!usersList[userIndex].appriseServices ||
|
||||
usersList[userIndex].appriseServices.length === 0
|
||||
) {
|
||||
res.status(422).json({
|
||||
message:
|
||||
'You must provide at least one Apprise URL to send a test.',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
////4 : Send the notification to services
|
||||
//Build the URLs service list as a single string
|
||||
let appriseServicesURLs = '';
|
||||
for (let service of usersList[userIndex].appriseServices) {
|
||||
appriseServicesURLs = appriseServicesURLs + service + ' ';
|
||||
}
|
||||
//Mode : package
|
||||
if (usersList[userIndex].appriseMode === 'package') {
|
||||
try {
|
||||
//Check if apprise is installed as local package.
|
||||
exec('apprise -V', (error, stderr, stdout) => {
|
||||
if (error) {
|
||||
console.log(
|
||||
`Error when checking if Apprise is a local package : ${error}`
|
||||
);
|
||||
res.status(500).json({
|
||||
message:
|
||||
'Apprise is not installed as local package on your server.',
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
//Send notification via local package.
|
||||
exec(
|
||||
`apprise -v -b "This is a test notification from BorgWarehouse !" ${appriseServicesURLs}`,
|
||||
(error, stderr, stdout) => {
|
||||
if (stderr) {
|
||||
res.status(500).json({
|
||||
message:
|
||||
'There are some errors : ' + stderr,
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
res.status(200).json({
|
||||
message:
|
||||
'Notifications successfully sent.',
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
res.status(500).json({
|
||||
message:
|
||||
'Error on sending notification. Contact your administrator.',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
//Mode : stateless
|
||||
} else if (usersList[userIndex].appriseMode === 'stateless') {
|
||||
//If stateless URL is empty
|
||||
if (usersList[userIndex].appriseStatelessURL === '') {
|
||||
res.status(500).json({
|
||||
message: 'Please, provide an Apprise stateless API URL.',
|
||||
});
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await fetch(
|
||||
usersList[userIndex].appriseStatelessURL + '/notify',
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
urls: appriseServicesURLs,
|
||||
body: 'This is a test notification from BorgWarehouse !',
|
||||
}),
|
||||
}
|
||||
).then((response) => {
|
||||
if (response.ok) {
|
||||
res.status(200).json({
|
||||
message: 'Notifications successfully sent.',
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
console.log(response);
|
||||
res.status(500).json({
|
||||
message:
|
||||
'There are some errors : ' +
|
||||
response.statusText,
|
||||
});
|
||||
return;
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
res.status(500).json({
|
||||
message: 'Error : ' + err.message,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
//Mode : unknown
|
||||
} else {
|
||||
res.status(422).json({
|
||||
message: 'No Apprise Mode selected or supported.',
|
||||
});
|
||||
}
|
||||
if (req.method == 'POST') {
|
||||
//Verify that the user is logged in.
|
||||
const session = await getServerSession(req, res, authOptions);
|
||||
if (!session) {
|
||||
res.status(401).json({ message: 'You must be logged in.' });
|
||||
return;
|
||||
}
|
||||
|
||||
//The data we expect to receive
|
||||
let { sendTestApprise } = req.body;
|
||||
|
||||
//Read the users file
|
||||
//Find the absolute path of the json directory
|
||||
const jsonDirectory = path.join(process.cwd(), '/config');
|
||||
let usersList = await fs.readFile(jsonDirectory + '/users.json', 'utf8');
|
||||
//Parse the usersList
|
||||
usersList = JSON.parse(usersList);
|
||||
|
||||
//1 : Verify that the user of the session exists
|
||||
const userIndex = usersList.map((user) => user.username).indexOf(session.user.name);
|
||||
if (userIndex === -1) {
|
||||
res.status(400).json({
|
||||
message: 'User is incorrect. Please, logout to update your session.',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
//2 : control the data
|
||||
if (sendTestApprise !== true) {
|
||||
res.status(422).json({ message: 'Unexpected data' });
|
||||
return;
|
||||
}
|
||||
|
||||
//3 : if there is no service URLs, throw error
|
||||
if (
|
||||
!usersList[userIndex].appriseServices ||
|
||||
usersList[userIndex].appriseServices.length === 0
|
||||
) {
|
||||
res.status(422).json({
|
||||
message: 'You must provide at least one Apprise URL to send a test.',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
////4 : Send the notification to services
|
||||
//Build the URLs service list as a single string
|
||||
let appriseServicesURLs = '';
|
||||
for (let service of usersList[userIndex].appriseServices) {
|
||||
appriseServicesURLs = appriseServicesURLs + service + ' ';
|
||||
}
|
||||
//Mode : package
|
||||
if (usersList[userIndex].appriseMode === 'package') {
|
||||
try {
|
||||
//Check if apprise is installed as local package.
|
||||
exec('apprise -V', (error, stderr, stdout) => {
|
||||
if (error) {
|
||||
console.log(`Error when checking if Apprise is a local package : ${error}`);
|
||||
res.status(500).json({
|
||||
message: 'Apprise is not installed as local package on your server.',
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
//Send notification via local package.
|
||||
exec(
|
||||
`apprise -v -b "This is a test notification from BorgWarehouse !" ${appriseServicesURLs}`,
|
||||
(error, stderr, stdout) => {
|
||||
if (stderr) {
|
||||
res.status(500).json({
|
||||
message: 'There are some errors : ' + stderr,
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
res.status(200).json({
|
||||
message: 'Notifications successfully sent.',
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
res.status(500).json({
|
||||
message: 'Error on sending notification. Contact your administrator.',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
//Mode : stateless
|
||||
} else if (usersList[userIndex].appriseMode === 'stateless') {
|
||||
//If stateless URL is empty
|
||||
if (usersList[userIndex].appriseStatelessURL === '') {
|
||||
res.status(500).json({
|
||||
message: 'Please, provide an Apprise stateless API URL.',
|
||||
});
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await fetch(usersList[userIndex].appriseStatelessURL + '/notify', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
urls: appriseServicesURLs,
|
||||
body: 'This is a test notification from BorgWarehouse !',
|
||||
}),
|
||||
}).then((response) => {
|
||||
if (response.ok) {
|
||||
res.status(200).json({
|
||||
message: 'Notifications successfully sent.',
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
console.log(response);
|
||||
res.status(500).json({
|
||||
message: 'There are some errors : ' + response.statusText,
|
||||
});
|
||||
return;
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
res.status(500).json({
|
||||
message: 'Error : ' + err.message,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
//Mode : unknown
|
||||
} else {
|
||||
res.status(422).json({
|
||||
message: 'No Apprise Mode selected or supported.',
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,44 +5,43 @@ import nodemailerSMTP from '../../../helpers/functions/nodemailerSMTP';
|
|||
import emailTest from '../../../helpers/templates/emailTest';
|
||||
|
||||
export default async function handler(req, res) {
|
||||
if (req.method == 'POST') {
|
||||
//Verify that the user is logged in.
|
||||
const session = await getServerSession(req, res, authOptions);
|
||||
if (!session) {
|
||||
res.status(401).json({ message: 'You must be logged in.' });
|
||||
return;
|
||||
}
|
||||
|
||||
//Create the SMTP Transporter
|
||||
const transporter = nodemailerSMTP();
|
||||
|
||||
//Mail options
|
||||
const mailData = emailTest(session.user.email, session.user.name);
|
||||
|
||||
//Send mail
|
||||
try {
|
||||
transporter.sendMail(mailData, function (err, info) {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
res.status(400).json({
|
||||
message:
|
||||
'An error occured while sending the email : ' + err,
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
console.log(info);
|
||||
res.status(200).json({
|
||||
message: 'Mail successfully sent.',
|
||||
});
|
||||
return;
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: 'API error, contact the administrator.',
|
||||
});
|
||||
}
|
||||
if (req.method == 'POST') {
|
||||
//Verify that the user is logged in.
|
||||
const session = await getServerSession(req, res, authOptions);
|
||||
if (!session) {
|
||||
res.status(401).json({ message: 'You must be logged in.' });
|
||||
return;
|
||||
}
|
||||
|
||||
//Create the SMTP Transporter
|
||||
const transporter = nodemailerSMTP();
|
||||
|
||||
//Mail options
|
||||
const mailData = emailTest(session.user.email, session.user.name);
|
||||
|
||||
//Send mail
|
||||
try {
|
||||
transporter.sendMail(mailData, function (err, info) {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
res.status(400).json({
|
||||
message: 'An error occured while sending the email : ' + err,
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
console.log(info);
|
||||
res.status(200).json({
|
||||
message: 'Mail successfully sent.',
|
||||
});
|
||||
return;
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: 'API error, contact the administrator.',
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,252 +5,223 @@ import { authOptions } from '../auth/[...nextauth]';
|
|||
import { getServerSession } from 'next-auth/next';
|
||||
|
||||
export default async function handler(req, res) {
|
||||
if (req.method == 'POST') {
|
||||
//Verify that the user is logged in.
|
||||
const session = await getServerSession(req, res, authOptions);
|
||||
if (!session) {
|
||||
res.status(401).json({ message: 'You must be logged in.' });
|
||||
return;
|
||||
}
|
||||
|
||||
//The data we expect to receive
|
||||
let { name, token, creation, expiration, permissions } = req.body;
|
||||
|
||||
//Read the users file
|
||||
//Find the absolute path of the json directory
|
||||
const jsonDirectory = path.join(process.cwd(), '/config');
|
||||
let usersList = await fs.readFile(
|
||||
jsonDirectory + '/users.json',
|
||||
'utf8'
|
||||
);
|
||||
//Parse the usersList
|
||||
usersList = JSON.parse(usersList);
|
||||
|
||||
//1 : We check that we receive data for each variable.
|
||||
if (!name || !token || !creation || !permissions) {
|
||||
res.status(400).json({ message: 'A field is missing.' });
|
||||
return;
|
||||
}
|
||||
|
||||
//Control the data
|
||||
const nameRegex = new RegExp('^[a-zA-Z0-9_-]{1,25}$');
|
||||
if (!nameRegex.test(name)) {
|
||||
res.status(400).json({ message: 'Your name is not valid' });
|
||||
return;
|
||||
}
|
||||
|
||||
//2 : Verify that the user of the session exists
|
||||
const userIndex = usersList
|
||||
.map((user) => user.username)
|
||||
.indexOf(session.user.name);
|
||||
if (userIndex === -1) {
|
||||
res.status(400).json({ message: 'User is incorrect.' });
|
||||
return;
|
||||
}
|
||||
const user = usersList[userIndex];
|
||||
|
||||
//3 : Check that the tokenName or tokenValue already exists
|
||||
const tokenExists =
|
||||
user.tokens &&
|
||||
user.tokens.some((existingToken) => existingToken.name === name);
|
||||
if (tokenExists) {
|
||||
res.status(400).json({
|
||||
message: 'A token with this name already exists.',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
//4 : Add the new token
|
||||
try {
|
||||
let newUsersList = usersList.map((user) =>
|
||||
user.username == session.user.name
|
||||
? {
|
||||
...user,
|
||||
tokens: [
|
||||
...(user.tokens || []),
|
||||
{
|
||||
name,
|
||||
token,
|
||||
creation,
|
||||
expiration,
|
||||
permissions,
|
||||
},
|
||||
],
|
||||
}
|
||||
: user
|
||||
);
|
||||
//Stringify the new users list
|
||||
newUsersList = JSON.stringify(newUsersList);
|
||||
//Write the new JSON
|
||||
await fs.writeFile(
|
||||
jsonDirectory + '/users.json',
|
||||
newUsersList,
|
||||
(err) => {
|
||||
if (err) console.log(err);
|
||||
}
|
||||
);
|
||||
res.status(200).json({ message: 'Successful API send' });
|
||||
} catch (error) {
|
||||
//Log for backend
|
||||
console.log(error);
|
||||
//Log for frontend
|
||||
if (error.code == 'ENOENT') {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: 'No such file or directory',
|
||||
});
|
||||
} else {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: 'API error, contact the administrator',
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
} else if (req.method == 'GET') {
|
||||
//Verify that the user is logged in.
|
||||
const session = await getServerSession(req, res, authOptions);
|
||||
if (!session) {
|
||||
res.status(401).json({ message: 'You must be logged in.' });
|
||||
return;
|
||||
}
|
||||
try {
|
||||
//Read the users file
|
||||
//Find the absolute path of the json directory
|
||||
const jsonDirectory = path.join(process.cwd(), '/config');
|
||||
let usersList = await fs.readFile(
|
||||
jsonDirectory + '/users.json',
|
||||
'utf8'
|
||||
);
|
||||
//Parse the usersList
|
||||
usersList = JSON.parse(usersList);
|
||||
|
||||
//Verify that the user of the session exists
|
||||
const userIndex = usersList
|
||||
.map((user) => user.username)
|
||||
.indexOf(session.user.name);
|
||||
if (userIndex === -1) {
|
||||
res.status(400).json({
|
||||
message:
|
||||
'User is incorrect. Please, logout to update your session.',
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
//Send the token list without tokens
|
||||
res.status(200).json([
|
||||
...usersList[userIndex].tokens.map((token) => ({
|
||||
name: token.name,
|
||||
creation: token.creation,
|
||||
expiration: token.expiration,
|
||||
permissions: token.permissions,
|
||||
})),
|
||||
]);
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
//Log for backend
|
||||
console.log(error);
|
||||
//Log for frontend
|
||||
if (error.code == 'ENOENT') {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: 'No such file or directory',
|
||||
});
|
||||
} else {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: 'API error, contact the administrator',
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
} else if (req.method == 'DELETE') {
|
||||
//Verify that the user is logged in.
|
||||
const session = await getServerSession(req, res, authOptions);
|
||||
if (!session) {
|
||||
res.status(401).json({ message: 'You must be logged in.' });
|
||||
return;
|
||||
}
|
||||
|
||||
//The data we expect to receive
|
||||
let { name } = req.body;
|
||||
|
||||
//Read the users file
|
||||
//Find the absolute path of the json directory
|
||||
const jsonDirectory = path.join(process.cwd(), '/config');
|
||||
let usersList = await fs.readFile(
|
||||
jsonDirectory + '/users.json',
|
||||
'utf8'
|
||||
);
|
||||
//Parse the usersList
|
||||
usersList = JSON.parse(usersList);
|
||||
|
||||
//1 : We check that we receive data for each variable.
|
||||
if (!name) {
|
||||
res.status(400).json({ message: 'A field is missing.' });
|
||||
return;
|
||||
}
|
||||
|
||||
//2 : Verify that the user of the session exists
|
||||
const userIndex = usersList
|
||||
.map((user) => user.username)
|
||||
.indexOf(session.user.name);
|
||||
if (userIndex === -1) {
|
||||
res.status(400).json({ message: 'User is incorrect.' });
|
||||
return;
|
||||
}
|
||||
const user = usersList[userIndex];
|
||||
|
||||
//Control the data
|
||||
const tokenExists = user.tokens.some(
|
||||
(existingToken) => existingToken.name === name
|
||||
);
|
||||
if (!tokenExists) {
|
||||
res.status(400).json({ message: 'Token not found.' });
|
||||
return;
|
||||
}
|
||||
|
||||
//3 : Delete the token object if it exists
|
||||
try {
|
||||
let newUsersList = usersList.map((user) =>
|
||||
user.username == session.user.name
|
||||
? {
|
||||
...user,
|
||||
tokens: user.tokens.filter(
|
||||
(token) => token.name != name
|
||||
),
|
||||
}
|
||||
: user
|
||||
);
|
||||
//Stringify the new users list
|
||||
newUsersList = JSON.stringify(newUsersList);
|
||||
//Write the new JSON
|
||||
await fs.writeFile(
|
||||
jsonDirectory + '/users.json',
|
||||
newUsersList,
|
||||
(err) => {
|
||||
if (err) console.log(err);
|
||||
}
|
||||
);
|
||||
res.status(200).json({ message: 'Successful API send' });
|
||||
} catch (error) {
|
||||
//Log for backend
|
||||
console.log(error);
|
||||
//Log for frontend
|
||||
if (error.code == 'ENOENT') {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: 'No such file or directory',
|
||||
});
|
||||
} else {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: 'API error, contact the administrator',
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
res.status(405).json({ message: 'Bad request on API' });
|
||||
if (req.method == 'POST') {
|
||||
//Verify that the user is logged in.
|
||||
const session = await getServerSession(req, res, authOptions);
|
||||
if (!session) {
|
||||
res.status(401).json({ message: 'You must be logged in.' });
|
||||
return;
|
||||
}
|
||||
|
||||
//The data we expect to receive
|
||||
let { name, token, creation, expiration, permissions } = req.body;
|
||||
|
||||
//Read the users file
|
||||
//Find the absolute path of the json directory
|
||||
const jsonDirectory = path.join(process.cwd(), '/config');
|
||||
let usersList = await fs.readFile(jsonDirectory + '/users.json', 'utf8');
|
||||
//Parse the usersList
|
||||
usersList = JSON.parse(usersList);
|
||||
|
||||
//1 : We check that we receive data for each variable.
|
||||
if (!name || !token || !creation || !permissions) {
|
||||
res.status(400).json({ message: 'A field is missing.' });
|
||||
return;
|
||||
}
|
||||
|
||||
//Control the data
|
||||
const nameRegex = new RegExp('^[a-zA-Z0-9_-]{1,25}$');
|
||||
if (!nameRegex.test(name)) {
|
||||
res.status(400).json({ message: 'Your name is not valid' });
|
||||
return;
|
||||
}
|
||||
|
||||
//2 : Verify that the user of the session exists
|
||||
const userIndex = usersList.map((user) => user.username).indexOf(session.user.name);
|
||||
if (userIndex === -1) {
|
||||
res.status(400).json({ message: 'User is incorrect.' });
|
||||
return;
|
||||
}
|
||||
const user = usersList[userIndex];
|
||||
|
||||
//3 : Check that the tokenName or tokenValue already exists
|
||||
const tokenExists =
|
||||
user.tokens && user.tokens.some((existingToken) => existingToken.name === name);
|
||||
if (tokenExists) {
|
||||
res.status(400).json({
|
||||
message: 'A token with this name already exists.',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
//4 : Add the new token
|
||||
try {
|
||||
let newUsersList = usersList.map((user) =>
|
||||
user.username == session.user.name
|
||||
? {
|
||||
...user,
|
||||
tokens: [
|
||||
...(user.tokens || []),
|
||||
{
|
||||
name,
|
||||
token,
|
||||
creation,
|
||||
expiration,
|
||||
permissions,
|
||||
},
|
||||
],
|
||||
}
|
||||
: user
|
||||
);
|
||||
//Stringify the new users list
|
||||
newUsersList = JSON.stringify(newUsersList);
|
||||
//Write the new JSON
|
||||
await fs.writeFile(jsonDirectory + '/users.json', newUsersList, (err) => {
|
||||
if (err) console.log(err);
|
||||
});
|
||||
res.status(200).json({ message: 'Successful API send' });
|
||||
} catch (error) {
|
||||
//Log for backend
|
||||
console.log(error);
|
||||
//Log for frontend
|
||||
if (error.code == 'ENOENT') {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: 'No such file or directory',
|
||||
});
|
||||
} else {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: 'API error, contact the administrator',
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
} else if (req.method == 'GET') {
|
||||
//Verify that the user is logged in.
|
||||
const session = await getServerSession(req, res, authOptions);
|
||||
if (!session) {
|
||||
res.status(401).json({ message: 'You must be logged in.' });
|
||||
return;
|
||||
}
|
||||
try {
|
||||
//Read the users file
|
||||
//Find the absolute path of the json directory
|
||||
const jsonDirectory = path.join(process.cwd(), '/config');
|
||||
let usersList = await fs.readFile(jsonDirectory + '/users.json', 'utf8');
|
||||
//Parse the usersList
|
||||
usersList = JSON.parse(usersList);
|
||||
|
||||
//Verify that the user of the session exists
|
||||
const userIndex = usersList.map((user) => user.username).indexOf(session.user.name);
|
||||
if (userIndex === -1) {
|
||||
res.status(400).json({
|
||||
message: 'User is incorrect. Please, logout to update your session.',
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
//Send the token list without tokens
|
||||
res.status(200).json([
|
||||
...usersList[userIndex].tokens.map((token) => ({
|
||||
name: token.name,
|
||||
creation: token.creation,
|
||||
expiration: token.expiration,
|
||||
permissions: token.permissions,
|
||||
})),
|
||||
]);
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
//Log for backend
|
||||
console.log(error);
|
||||
//Log for frontend
|
||||
if (error.code == 'ENOENT') {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: 'No such file or directory',
|
||||
});
|
||||
} else {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: 'API error, contact the administrator',
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
} else if (req.method == 'DELETE') {
|
||||
//Verify that the user is logged in.
|
||||
const session = await getServerSession(req, res, authOptions);
|
||||
if (!session) {
|
||||
res.status(401).json({ message: 'You must be logged in.' });
|
||||
return;
|
||||
}
|
||||
|
||||
//The data we expect to receive
|
||||
let { name } = req.body;
|
||||
|
||||
//Read the users file
|
||||
//Find the absolute path of the json directory
|
||||
const jsonDirectory = path.join(process.cwd(), '/config');
|
||||
let usersList = await fs.readFile(jsonDirectory + '/users.json', 'utf8');
|
||||
//Parse the usersList
|
||||
usersList = JSON.parse(usersList);
|
||||
|
||||
//1 : We check that we receive data for each variable.
|
||||
if (!name) {
|
||||
res.status(400).json({ message: 'A field is missing.' });
|
||||
return;
|
||||
}
|
||||
|
||||
//2 : Verify that the user of the session exists
|
||||
const userIndex = usersList.map((user) => user.username).indexOf(session.user.name);
|
||||
if (userIndex === -1) {
|
||||
res.status(400).json({ message: 'User is incorrect.' });
|
||||
return;
|
||||
}
|
||||
const user = usersList[userIndex];
|
||||
|
||||
//Control the data
|
||||
const tokenExists = user.tokens.some((existingToken) => existingToken.name === name);
|
||||
if (!tokenExists) {
|
||||
res.status(400).json({ message: 'Token not found.' });
|
||||
return;
|
||||
}
|
||||
|
||||
//3 : Delete the token object if it exists
|
||||
try {
|
||||
let newUsersList = usersList.map((user) =>
|
||||
user.username == session.user.name
|
||||
? {
|
||||
...user,
|
||||
tokens: user.tokens.filter((token) => token.name != name),
|
||||
}
|
||||
: user
|
||||
);
|
||||
//Stringify the new users list
|
||||
newUsersList = JSON.stringify(newUsersList);
|
||||
//Write the new JSON
|
||||
await fs.writeFile(jsonDirectory + '/users.json', newUsersList, (err) => {
|
||||
if (err) console.log(err);
|
||||
});
|
||||
res.status(200).json({ message: 'Successful API send' });
|
||||
} catch (error) {
|
||||
//Log for backend
|
||||
console.log(error);
|
||||
//Log for frontend
|
||||
if (error.code == 'ENOENT') {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: 'No such file or directory',
|
||||
});
|
||||
} else {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: 'API error, contact the administrator',
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
res.status(405).json({ message: 'Bad request on API' });
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,82 +5,70 @@ import { authOptions } from '../auth/[...nextauth]';
|
|||
import { getServerSession } from 'next-auth/next';
|
||||
|
||||
export default async function handler(req, res) {
|
||||
if (req.method == 'PUT') {
|
||||
//Verify that the user is logged in.
|
||||
const session = await getServerSession(req, res, authOptions);
|
||||
if (!session) {
|
||||
res.status(401).json({ message: 'You must be logged in.' });
|
||||
return;
|
||||
}
|
||||
|
||||
//The data we expect to receive
|
||||
let { appriseAlert } = req.body;
|
||||
|
||||
//Read the users file
|
||||
//Find the absolute path of the json directory
|
||||
const jsonDirectory = path.join(process.cwd(), '/config');
|
||||
let usersList = await fs.readFile(
|
||||
jsonDirectory + '/users.json',
|
||||
'utf8'
|
||||
);
|
||||
//Parse the usersList
|
||||
usersList = JSON.parse(usersList);
|
||||
|
||||
//1 : control the data
|
||||
if (typeof appriseAlert != 'boolean') {
|
||||
res.status(422).json({ message: 'Unexpected data' });
|
||||
return;
|
||||
}
|
||||
|
||||
//2 : Verify that the user of the session exists
|
||||
const userIndex = usersList
|
||||
.map((user) => user.username)
|
||||
.indexOf(session.user.name);
|
||||
if (userIndex === -1) {
|
||||
res.status(400).json({
|
||||
message:
|
||||
'User is incorrect. Please, logout to update your session.',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
//3 : Change the appriseAlert settings
|
||||
try {
|
||||
//Modify the appriseAlert bool for the user
|
||||
let newUsersList = usersList.map((user) =>
|
||||
user.username == session.user.name
|
||||
? { ...user, appriseAlert: appriseAlert }
|
||||
: user
|
||||
);
|
||||
//Stringify the new users list
|
||||
newUsersList = JSON.stringify(newUsersList);
|
||||
//Write the new JSON
|
||||
await fs.writeFile(
|
||||
jsonDirectory + '/users.json',
|
||||
newUsersList,
|
||||
(err) => {
|
||||
if (err) console.log(err);
|
||||
}
|
||||
);
|
||||
res.status(200).json({ message: 'Successful API send' });
|
||||
} catch (error) {
|
||||
//Log for backend
|
||||
console.log(error);
|
||||
//Log for frontend
|
||||
if (error.code == 'ENOENT') {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: 'No such file or directory',
|
||||
});
|
||||
} else {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: 'API error, contact the administrator',
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
res.status(405).json({ message: 'Bad request on API' });
|
||||
if (req.method == 'PUT') {
|
||||
//Verify that the user is logged in.
|
||||
const session = await getServerSession(req, res, authOptions);
|
||||
if (!session) {
|
||||
res.status(401).json({ message: 'You must be logged in.' });
|
||||
return;
|
||||
}
|
||||
|
||||
//The data we expect to receive
|
||||
let { appriseAlert } = req.body;
|
||||
|
||||
//Read the users file
|
||||
//Find the absolute path of the json directory
|
||||
const jsonDirectory = path.join(process.cwd(), '/config');
|
||||
let usersList = await fs.readFile(jsonDirectory + '/users.json', 'utf8');
|
||||
//Parse the usersList
|
||||
usersList = JSON.parse(usersList);
|
||||
|
||||
//1 : control the data
|
||||
if (typeof appriseAlert != 'boolean') {
|
||||
res.status(422).json({ message: 'Unexpected data' });
|
||||
return;
|
||||
}
|
||||
|
||||
//2 : Verify that the user of the session exists
|
||||
const userIndex = usersList.map((user) => user.username).indexOf(session.user.name);
|
||||
if (userIndex === -1) {
|
||||
res.status(400).json({
|
||||
message: 'User is incorrect. Please, logout to update your session.',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
//3 : Change the appriseAlert settings
|
||||
try {
|
||||
//Modify the appriseAlert bool for the user
|
||||
let newUsersList = usersList.map((user) =>
|
||||
user.username == session.user.name ? { ...user, appriseAlert: appriseAlert } : user
|
||||
);
|
||||
//Stringify the new users list
|
||||
newUsersList = JSON.stringify(newUsersList);
|
||||
//Write the new JSON
|
||||
await fs.writeFile(jsonDirectory + '/users.json', newUsersList, (err) => {
|
||||
if (err) console.log(err);
|
||||
});
|
||||
res.status(200).json({ message: 'Successful API send' });
|
||||
} catch (error) {
|
||||
//Log for backend
|
||||
console.log(error);
|
||||
//Log for frontend
|
||||
if (error.code == 'ENOENT') {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: 'No such file or directory',
|
||||
});
|
||||
} else {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: 'API error, contact the administrator',
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
res.status(405).json({ message: 'Bad request on API' });
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,86 +5,76 @@ import { authOptions } from '../auth/[...nextauth]';
|
|||
import { getServerSession } from 'next-auth/next';
|
||||
|
||||
export default async function handler(req, res) {
|
||||
if (req.method == 'PUT') {
|
||||
//Verify that the user is logged in.
|
||||
const session = await getServerSession(req, res, authOptions);
|
||||
if (!session) {
|
||||
res.status(401).json({ message: 'You must be logged in.' });
|
||||
return;
|
||||
}
|
||||
|
||||
//The data we expect to receive
|
||||
let { appriseMode, appriseStatelessURL } = req.body;
|
||||
|
||||
//Read the users file
|
||||
//Find the absolute path of the json directory
|
||||
const jsonDirectory = path.join(process.cwd(), '/config');
|
||||
let usersList = await fs.readFile(
|
||||
jsonDirectory + '/users.json',
|
||||
'utf8'
|
||||
);
|
||||
//Parse the usersList
|
||||
usersList = JSON.parse(usersList);
|
||||
|
||||
//1 : control the data
|
||||
if (appriseMode != 'package' && appriseMode != 'stateless') {
|
||||
res.status(422).json({ message: 'Unexpected data' });
|
||||
return;
|
||||
}
|
||||
|
||||
//2 : Verify that the user of the session exists
|
||||
const userIndex = usersList
|
||||
.map((user) => user.username)
|
||||
.indexOf(session.user.name);
|
||||
if (userIndex === -1) {
|
||||
res.status(400).json({
|
||||
message:
|
||||
'User is incorrect. Please, logout to update your session.',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
//3 : Change the appriseMode
|
||||
try {
|
||||
//Modify the appriseMode for the user
|
||||
let newUsersList = usersList.map((user) =>
|
||||
user.username == session.user.name
|
||||
? {
|
||||
...user,
|
||||
appriseMode: appriseMode,
|
||||
appriseStatelessURL: appriseStatelessURL,
|
||||
}
|
||||
: user
|
||||
);
|
||||
//Stringify the new users list
|
||||
newUsersList = JSON.stringify(newUsersList);
|
||||
//Write the new JSON
|
||||
await fs.writeFile(
|
||||
jsonDirectory + '/users.json',
|
||||
newUsersList,
|
||||
(err) => {
|
||||
if (err) console.log(err);
|
||||
}
|
||||
);
|
||||
res.status(200).json({ message: 'Successful API send' });
|
||||
} catch (error) {
|
||||
//Log for backend
|
||||
console.log(error);
|
||||
//Log for frontend
|
||||
if (error.code == 'ENOENT') {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: 'No such file or directory',
|
||||
});
|
||||
} else {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: 'API error, contact the administrator',
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
res.status(405).json({ message: 'Bad request on API' });
|
||||
if (req.method == 'PUT') {
|
||||
//Verify that the user is logged in.
|
||||
const session = await getServerSession(req, res, authOptions);
|
||||
if (!session) {
|
||||
res.status(401).json({ message: 'You must be logged in.' });
|
||||
return;
|
||||
}
|
||||
|
||||
//The data we expect to receive
|
||||
let { appriseMode, appriseStatelessURL } = req.body;
|
||||
|
||||
//Read the users file
|
||||
//Find the absolute path of the json directory
|
||||
const jsonDirectory = path.join(process.cwd(), '/config');
|
||||
let usersList = await fs.readFile(jsonDirectory + '/users.json', 'utf8');
|
||||
//Parse the usersList
|
||||
usersList = JSON.parse(usersList);
|
||||
|
||||
//1 : control the data
|
||||
if (appriseMode != 'package' && appriseMode != 'stateless') {
|
||||
res.status(422).json({ message: 'Unexpected data' });
|
||||
return;
|
||||
}
|
||||
|
||||
//2 : Verify that the user of the session exists
|
||||
const userIndex = usersList.map((user) => user.username).indexOf(session.user.name);
|
||||
if (userIndex === -1) {
|
||||
res.status(400).json({
|
||||
message: 'User is incorrect. Please, logout to update your session.',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
//3 : Change the appriseMode
|
||||
try {
|
||||
//Modify the appriseMode for the user
|
||||
let newUsersList = usersList.map((user) =>
|
||||
user.username == session.user.name
|
||||
? {
|
||||
...user,
|
||||
appriseMode: appriseMode,
|
||||
appriseStatelessURL: appriseStatelessURL,
|
||||
}
|
||||
: user
|
||||
);
|
||||
//Stringify the new users list
|
||||
newUsersList = JSON.stringify(newUsersList);
|
||||
//Write the new JSON
|
||||
await fs.writeFile(jsonDirectory + '/users.json', newUsersList, (err) => {
|
||||
if (err) console.log(err);
|
||||
});
|
||||
res.status(200).json({ message: 'Successful API send' });
|
||||
} catch (error) {
|
||||
//Log for backend
|
||||
console.log(error);
|
||||
//Log for frontend
|
||||
if (error.code == 'ENOENT') {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: 'No such file or directory',
|
||||
});
|
||||
} else {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: 'API error, contact the administrator',
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
res.status(405).json({ message: 'Bad request on API' });
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,85 +5,75 @@ import { authOptions } from '../auth/[...nextauth]';
|
|||
import { getServerSession } from 'next-auth/next';
|
||||
|
||||
export default async function handler(req, res) {
|
||||
if (req.method == 'PUT') {
|
||||
//Verify that the user is logged in.
|
||||
const session = await getServerSession(req, res, authOptions);
|
||||
if (!session) {
|
||||
res.status(401).json({ message: 'You must be logged in.' });
|
||||
return;
|
||||
}
|
||||
|
||||
//The data we expect to receive
|
||||
let { appriseURLs } = req.body;
|
||||
|
||||
//Read the users file
|
||||
//Find the absolute path of the json directory
|
||||
const jsonDirectory = path.join(process.cwd(), '/config');
|
||||
let usersList = await fs.readFile(
|
||||
jsonDirectory + '/users.json',
|
||||
'utf8'
|
||||
);
|
||||
//Parse the usersList
|
||||
usersList = JSON.parse(usersList);
|
||||
|
||||
//1 : Verify that the user of the session exists
|
||||
const userIndex = usersList
|
||||
.map((user) => user.username)
|
||||
.indexOf(session.user.name);
|
||||
if (userIndex === -1) {
|
||||
res.status(400).json({
|
||||
message:
|
||||
'User is incorrect. Please, logout to update your session.',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
//2 : Update Apprise URLs list
|
||||
try {
|
||||
//Build the services URLs list from form
|
||||
const appriseURLsArray = appriseURLs
|
||||
.replace(/ /g, '')
|
||||
.split('\n')
|
||||
.filter((el) => el != '');
|
||||
|
||||
//Save the list for the user
|
||||
let newUsersList = usersList.map((user) =>
|
||||
user.username == session.user.name
|
||||
? {
|
||||
...user,
|
||||
appriseServices: appriseURLsArray,
|
||||
}
|
||||
: user
|
||||
);
|
||||
//Stringify the new users list
|
||||
newUsersList = JSON.stringify(newUsersList);
|
||||
//Write the new JSON
|
||||
await fs.writeFile(
|
||||
jsonDirectory + '/users.json',
|
||||
newUsersList,
|
||||
(err) => {
|
||||
if (err) console.log(err);
|
||||
}
|
||||
);
|
||||
res.status(200).json({ message: 'Successful API send' });
|
||||
} catch (error) {
|
||||
//Log for backend
|
||||
console.log(error);
|
||||
//Log for frontend
|
||||
if (error.code == 'ENOENT') {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: 'No such file or directory',
|
||||
});
|
||||
} else {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: 'API error, contact the administrator',
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
res.status(405).json({ message: 'Bad request on API' });
|
||||
if (req.method == 'PUT') {
|
||||
//Verify that the user is logged in.
|
||||
const session = await getServerSession(req, res, authOptions);
|
||||
if (!session) {
|
||||
res.status(401).json({ message: 'You must be logged in.' });
|
||||
return;
|
||||
}
|
||||
|
||||
//The data we expect to receive
|
||||
let { appriseURLs } = req.body;
|
||||
|
||||
//Read the users file
|
||||
//Find the absolute path of the json directory
|
||||
const jsonDirectory = path.join(process.cwd(), '/config');
|
||||
let usersList = await fs.readFile(jsonDirectory + '/users.json', 'utf8');
|
||||
//Parse the usersList
|
||||
usersList = JSON.parse(usersList);
|
||||
|
||||
//1 : Verify that the user of the session exists
|
||||
const userIndex = usersList.map((user) => user.username).indexOf(session.user.name);
|
||||
if (userIndex === -1) {
|
||||
res.status(400).json({
|
||||
message: 'User is incorrect. Please, logout to update your session.',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
//2 : Update Apprise URLs list
|
||||
try {
|
||||
//Build the services URLs list from form
|
||||
const appriseURLsArray = appriseURLs
|
||||
.replace(/ /g, '')
|
||||
.split('\n')
|
||||
.filter((el) => el != '');
|
||||
|
||||
//Save the list for the user
|
||||
let newUsersList = usersList.map((user) =>
|
||||
user.username == session.user.name
|
||||
? {
|
||||
...user,
|
||||
appriseServices: appriseURLsArray,
|
||||
}
|
||||
: user
|
||||
);
|
||||
//Stringify the new users list
|
||||
newUsersList = JSON.stringify(newUsersList);
|
||||
//Write the new JSON
|
||||
await fs.writeFile(jsonDirectory + '/users.json', newUsersList, (err) => {
|
||||
if (err) console.log(err);
|
||||
});
|
||||
res.status(200).json({ message: 'Successful API send' });
|
||||
} catch (error) {
|
||||
//Log for backend
|
||||
console.log(error);
|
||||
//Log for frontend
|
||||
if (error.code == 'ENOENT') {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: 'No such file or directory',
|
||||
});
|
||||
} else {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: 'API error, contact the administrator',
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
res.status(405).json({ message: 'Bad request on API' });
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,92 +5,80 @@ import { authOptions } from '../auth/[...nextauth]';
|
|||
import { getServerSession } from 'next-auth/next';
|
||||
|
||||
export default async function handler(req, res) {
|
||||
if (req.method == 'PUT') {
|
||||
//Verify that the user is logged in.
|
||||
const session = await getServerSession(req, res, authOptions);
|
||||
if (!session) {
|
||||
res.status(401).json({ message: 'You must be logged in.' });
|
||||
return;
|
||||
}
|
||||
|
||||
//The data we expect to receive
|
||||
let { email } = req.body;
|
||||
|
||||
//Read the users file
|
||||
//Find the absolute path of the json directory
|
||||
const jsonDirectory = path.join(process.cwd(), '/config');
|
||||
let usersList = await fs.readFile(
|
||||
jsonDirectory + '/users.json',
|
||||
'utf8'
|
||||
);
|
||||
//Parse the usersList
|
||||
usersList = JSON.parse(usersList);
|
||||
|
||||
//1 : We check that we receive data.
|
||||
if (!email) {
|
||||
//If a variable is empty.
|
||||
res.status(400).json({ message: 'A field is missing.' });
|
||||
return;
|
||||
}
|
||||
|
||||
//2 : control the data
|
||||
const emailRegex = new RegExp(
|
||||
/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
|
||||
);
|
||||
if (!emailRegex.test(email)) {
|
||||
res.status(400).json({ message: 'Your email is not valid' });
|
||||
return;
|
||||
}
|
||||
|
||||
//3 : Verify that the user of the session exists
|
||||
const userIndex = usersList
|
||||
.map((user) => user.username)
|
||||
.indexOf(session.user.name);
|
||||
if (userIndex === -1) {
|
||||
res.status(400).json({
|
||||
message:
|
||||
'User is incorrect. Please, logout to update your session.',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
//4 : Change the email
|
||||
try {
|
||||
//Modify the email for the user
|
||||
let newUsersList = usersList.map((user) =>
|
||||
user.username == session.user.name
|
||||
? { ...user, email: email }
|
||||
: user
|
||||
);
|
||||
//Stringify the new users list
|
||||
newUsersList = JSON.stringify(newUsersList);
|
||||
//Write the new JSON
|
||||
await fs.writeFile(
|
||||
jsonDirectory + '/users.json',
|
||||
newUsersList,
|
||||
(err) => {
|
||||
if (err) console.log(err);
|
||||
}
|
||||
);
|
||||
res.status(200).json({ message: 'Successful API send' });
|
||||
} catch (error) {
|
||||
//Log for backend
|
||||
console.log(error);
|
||||
//Log for frontend
|
||||
if (error.code == 'ENOENT') {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: 'No such file or directory',
|
||||
});
|
||||
} else {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: 'API error, contact the administrator',
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
res.status(405).json({ message: 'Bad request on API' });
|
||||
if (req.method == 'PUT') {
|
||||
//Verify that the user is logged in.
|
||||
const session = await getServerSession(req, res, authOptions);
|
||||
if (!session) {
|
||||
res.status(401).json({ message: 'You must be logged in.' });
|
||||
return;
|
||||
}
|
||||
|
||||
//The data we expect to receive
|
||||
let { email } = req.body;
|
||||
|
||||
//Read the users file
|
||||
//Find the absolute path of the json directory
|
||||
const jsonDirectory = path.join(process.cwd(), '/config');
|
||||
let usersList = await fs.readFile(jsonDirectory + '/users.json', 'utf8');
|
||||
//Parse the usersList
|
||||
usersList = JSON.parse(usersList);
|
||||
|
||||
//1 : We check that we receive data.
|
||||
if (!email) {
|
||||
//If a variable is empty.
|
||||
res.status(400).json({ message: 'A field is missing.' });
|
||||
return;
|
||||
}
|
||||
|
||||
//2 : control the data
|
||||
const emailRegex = new RegExp(
|
||||
/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
|
||||
);
|
||||
if (!emailRegex.test(email)) {
|
||||
res.status(400).json({ message: 'Your email is not valid' });
|
||||
return;
|
||||
}
|
||||
|
||||
//3 : Verify that the user of the session exists
|
||||
const userIndex = usersList.map((user) => user.username).indexOf(session.user.name);
|
||||
if (userIndex === -1) {
|
||||
res.status(400).json({
|
||||
message: 'User is incorrect. Please, logout to update your session.',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
//4 : Change the email
|
||||
try {
|
||||
//Modify the email for the user
|
||||
let newUsersList = usersList.map((user) =>
|
||||
user.username == session.user.name ? { ...user, email: email } : user
|
||||
);
|
||||
//Stringify the new users list
|
||||
newUsersList = JSON.stringify(newUsersList);
|
||||
//Write the new JSON
|
||||
await fs.writeFile(jsonDirectory + '/users.json', newUsersList, (err) => {
|
||||
if (err) console.log(err);
|
||||
});
|
||||
res.status(200).json({ message: 'Successful API send' });
|
||||
} catch (error) {
|
||||
//Log for backend
|
||||
console.log(error);
|
||||
//Log for frontend
|
||||
if (error.code == 'ENOENT') {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: 'No such file or directory',
|
||||
});
|
||||
} else {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: 'API error, contact the administrator',
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
res.status(405).json({ message: 'Bad request on API' });
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,82 +5,70 @@ import { authOptions } from '../auth/[...nextauth]';
|
|||
import { getServerSession } from 'next-auth/next';
|
||||
|
||||
export default async function handler(req, res) {
|
||||
if (req.method == 'PUT') {
|
||||
//Verify that the user is logged in.
|
||||
const session = await getServerSession(req, res, authOptions);
|
||||
if (!session) {
|
||||
res.status(401).json({ message: 'You must be logged in.' });
|
||||
return;
|
||||
}
|
||||
|
||||
//The data we expect to receive
|
||||
let { emailAlert } = req.body;
|
||||
|
||||
//Read the users file
|
||||
//Find the absolute path of the json directory
|
||||
const jsonDirectory = path.join(process.cwd(), '/config');
|
||||
let usersList = await fs.readFile(
|
||||
jsonDirectory + '/users.json',
|
||||
'utf8'
|
||||
);
|
||||
//Parse the usersList
|
||||
usersList = JSON.parse(usersList);
|
||||
|
||||
//1 : control the data
|
||||
if (typeof emailAlert != 'boolean') {
|
||||
res.status(422).json({ message: 'Unexpected data' });
|
||||
return;
|
||||
}
|
||||
|
||||
//2 : Verify that the user of the session exists
|
||||
const userIndex = usersList
|
||||
.map((user) => user.username)
|
||||
.indexOf(session.user.name);
|
||||
if (userIndex === -1) {
|
||||
res.status(400).json({
|
||||
message:
|
||||
'User is incorrect. Please, logout to update your session.',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
//3 : Change the emailAlert settings
|
||||
try {
|
||||
//Modify the email for the user
|
||||
let newUsersList = usersList.map((user) =>
|
||||
user.username == session.user.name
|
||||
? { ...user, emailAlert: emailAlert }
|
||||
: user
|
||||
);
|
||||
//Stringify the new users list
|
||||
newUsersList = JSON.stringify(newUsersList);
|
||||
//Write the new JSON
|
||||
await fs.writeFile(
|
||||
jsonDirectory + '/users.json',
|
||||
newUsersList,
|
||||
(err) => {
|
||||
if (err) console.log(err);
|
||||
}
|
||||
);
|
||||
res.status(200).json({ message: 'Successful API send' });
|
||||
} catch (error) {
|
||||
//Log for backend
|
||||
console.log(error);
|
||||
//Log for frontend
|
||||
if (error.code == 'ENOENT') {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: 'No such file or directory',
|
||||
});
|
||||
} else {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: 'API error, contact the administrator',
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
res.status(405).json({ message: 'Bad request on API' });
|
||||
if (req.method == 'PUT') {
|
||||
//Verify that the user is logged in.
|
||||
const session = await getServerSession(req, res, authOptions);
|
||||
if (!session) {
|
||||
res.status(401).json({ message: 'You must be logged in.' });
|
||||
return;
|
||||
}
|
||||
|
||||
//The data we expect to receive
|
||||
let { emailAlert } = req.body;
|
||||
|
||||
//Read the users file
|
||||
//Find the absolute path of the json directory
|
||||
const jsonDirectory = path.join(process.cwd(), '/config');
|
||||
let usersList = await fs.readFile(jsonDirectory + '/users.json', 'utf8');
|
||||
//Parse the usersList
|
||||
usersList = JSON.parse(usersList);
|
||||
|
||||
//1 : control the data
|
||||
if (typeof emailAlert != 'boolean') {
|
||||
res.status(422).json({ message: 'Unexpected data' });
|
||||
return;
|
||||
}
|
||||
|
||||
//2 : Verify that the user of the session exists
|
||||
const userIndex = usersList.map((user) => user.username).indexOf(session.user.name);
|
||||
if (userIndex === -1) {
|
||||
res.status(400).json({
|
||||
message: 'User is incorrect. Please, logout to update your session.',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
//3 : Change the emailAlert settings
|
||||
try {
|
||||
//Modify the email for the user
|
||||
let newUsersList = usersList.map((user) =>
|
||||
user.username == session.user.name ? { ...user, emailAlert: emailAlert } : user
|
||||
);
|
||||
//Stringify the new users list
|
||||
newUsersList = JSON.stringify(newUsersList);
|
||||
//Write the new JSON
|
||||
await fs.writeFile(jsonDirectory + '/users.json', newUsersList, (err) => {
|
||||
if (err) console.log(err);
|
||||
});
|
||||
res.status(200).json({ message: 'Successful API send' });
|
||||
} catch (error) {
|
||||
//Log for backend
|
||||
console.log(error);
|
||||
//Log for frontend
|
||||
if (error.code == 'ENOENT') {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: 'No such file or directory',
|
||||
});
|
||||
} else {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: 'API error, contact the administrator',
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
res.status(405).json({ message: 'Bad request on API' });
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,90 +6,79 @@ import { authOptions } from '../auth/[...nextauth]';
|
|||
import { getServerSession } from 'next-auth/next';
|
||||
|
||||
export default async function handler(req, res) {
|
||||
if (req.method == 'PUT') {
|
||||
//Verify that the user is logged in.
|
||||
const session = await getServerSession(req, res, authOptions);
|
||||
if (!session) {
|
||||
res.status(401).json({ message: 'You must be logged in.' });
|
||||
return;
|
||||
}
|
||||
|
||||
//The data we expect to receive
|
||||
let { oldPassword, newPassword } = req.body;
|
||||
|
||||
//Read the users file
|
||||
//Find the absolute path of the json directory
|
||||
const jsonDirectory = path.join(process.cwd(), '/config');
|
||||
let usersList = await fs.readFile(
|
||||
jsonDirectory + '/users.json',
|
||||
'utf8'
|
||||
);
|
||||
//Parse the usersList
|
||||
usersList = JSON.parse(usersList);
|
||||
|
||||
//1 : We check that we receive data for each variable.
|
||||
if (!oldPassword || !newPassword) {
|
||||
//If a variable is empty.
|
||||
res.status(400).json({ message: 'A field is missing.' });
|
||||
return;
|
||||
}
|
||||
//Hash the new password
|
||||
newPassword = await hashPassword(newPassword);
|
||||
|
||||
//2 : Verify that the user of the session exists
|
||||
const userIndex = usersList
|
||||
.map((user) => user.username)
|
||||
.indexOf(session.user.name);
|
||||
if (userIndex === -1) {
|
||||
res.status(400).json({ message: 'User is incorrect.' });
|
||||
return;
|
||||
}
|
||||
const user = usersList[userIndex];
|
||||
|
||||
//3 : Check that the old password is correct
|
||||
const isValid = await verifyPassword(oldPassword, user.password);
|
||||
if (!isValid) {
|
||||
res.status(400).json({ message: 'Old password is incorrect.' });
|
||||
return;
|
||||
}
|
||||
|
||||
//4 : Change the password
|
||||
try {
|
||||
//Modify the password for the user
|
||||
let newUsersList = usersList.map((user) =>
|
||||
user.username == session.user.name
|
||||
? { ...user, password: newPassword }
|
||||
: user
|
||||
);
|
||||
//Stringify the new users list
|
||||
newUsersList = JSON.stringify(newUsersList);
|
||||
//Write the new JSON
|
||||
await fs.writeFile(
|
||||
jsonDirectory + '/users.json',
|
||||
newUsersList,
|
||||
(err) => {
|
||||
if (err) console.log(err);
|
||||
}
|
||||
);
|
||||
res.status(200).json({ message: 'Successful API send' });
|
||||
} catch (error) {
|
||||
//Log for backend
|
||||
console.log(error);
|
||||
//Log for frontend
|
||||
if (error.code == 'ENOENT') {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: 'No such file or directory',
|
||||
});
|
||||
} else {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: 'API error, contact the administrator',
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
res.status(405).json({ message: 'Bad request on API' });
|
||||
if (req.method == 'PUT') {
|
||||
//Verify that the user is logged in.
|
||||
const session = await getServerSession(req, res, authOptions);
|
||||
if (!session) {
|
||||
res.status(401).json({ message: 'You must be logged in.' });
|
||||
return;
|
||||
}
|
||||
|
||||
//The data we expect to receive
|
||||
let { oldPassword, newPassword } = req.body;
|
||||
|
||||
//Read the users file
|
||||
//Find the absolute path of the json directory
|
||||
const jsonDirectory = path.join(process.cwd(), '/config');
|
||||
let usersList = await fs.readFile(jsonDirectory + '/users.json', 'utf8');
|
||||
//Parse the usersList
|
||||
usersList = JSON.parse(usersList);
|
||||
|
||||
//1 : We check that we receive data for each variable.
|
||||
if (!oldPassword || !newPassword) {
|
||||
//If a variable is empty.
|
||||
res.status(400).json({ message: 'A field is missing.' });
|
||||
return;
|
||||
}
|
||||
//Hash the new password
|
||||
newPassword = await hashPassword(newPassword);
|
||||
|
||||
//2 : Verify that the user of the session exists
|
||||
const userIndex = usersList.map((user) => user.username).indexOf(session.user.name);
|
||||
if (userIndex === -1) {
|
||||
res.status(400).json({ message: 'User is incorrect.' });
|
||||
return;
|
||||
}
|
||||
const user = usersList[userIndex];
|
||||
|
||||
//3 : Check that the old password is correct
|
||||
const isValid = await verifyPassword(oldPassword, user.password);
|
||||
if (!isValid) {
|
||||
res.status(400).json({ message: 'Old password is incorrect.' });
|
||||
return;
|
||||
}
|
||||
|
||||
//4 : Change the password
|
||||
try {
|
||||
//Modify the password for the user
|
||||
let newUsersList = usersList.map((user) =>
|
||||
user.username == session.user.name ? { ...user, password: newPassword } : user
|
||||
);
|
||||
//Stringify the new users list
|
||||
newUsersList = JSON.stringify(newUsersList);
|
||||
//Write the new JSON
|
||||
await fs.writeFile(jsonDirectory + '/users.json', newUsersList, (err) => {
|
||||
if (err) console.log(err);
|
||||
});
|
||||
res.status(200).json({ message: 'Successful API send' });
|
||||
} catch (error) {
|
||||
//Log for backend
|
||||
console.log(error);
|
||||
//Log for frontend
|
||||
if (error.code == 'ENOENT') {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: 'No such file or directory',
|
||||
});
|
||||
} else {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: 'API error, contact the administrator',
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
res.status(405).json({ message: 'Bad request on API' });
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,92 +5,80 @@ import { authOptions } from '../auth/[...nextauth]';
|
|||
import { getServerSession } from 'next-auth/next';
|
||||
|
||||
export default async function handler(req, res) {
|
||||
if (req.method == 'PUT') {
|
||||
//Verify that the user is logged in.
|
||||
const session = await getServerSession(req, res, authOptions);
|
||||
if (!session) {
|
||||
res.status(401).json({ message: 'You must be logged in.' });
|
||||
return;
|
||||
}
|
||||
|
||||
//The data we expect to receive
|
||||
let { username } = req.body;
|
||||
|
||||
//Read the users file
|
||||
//Find the absolute path of the json directory
|
||||
const jsonDirectory = path.join(process.cwd(), '/config');
|
||||
let usersList = await fs.readFile(
|
||||
jsonDirectory + '/users.json',
|
||||
'utf8'
|
||||
);
|
||||
//Parse the usersList
|
||||
usersList = JSON.parse(usersList);
|
||||
|
||||
//1 : We check that we receive data.
|
||||
if (!username) {
|
||||
//If a variable is empty.
|
||||
res.status(400).json({ message: 'A field is missing.' });
|
||||
return;
|
||||
}
|
||||
|
||||
//2 : control the data
|
||||
const usernameRegex = new RegExp(/^[a-z]{5,15}$/);
|
||||
if (!usernameRegex.test(username)) {
|
||||
res.status(400).json({
|
||||
message: 'Only a-z characters are allowed (5 to 15 char.)',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
//3 : Verify that the user of the session exists
|
||||
const userIndex = usersList
|
||||
.map((user) => user.username)
|
||||
.indexOf(session.user.name);
|
||||
if (userIndex === -1) {
|
||||
res.status(400).json({
|
||||
message:
|
||||
'User is incorrect. Please, logout to update your session.',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
//4 : Change the username
|
||||
try {
|
||||
//Modify the username for the user
|
||||
let newUsersList = usersList.map((user) =>
|
||||
user.username == session.user.name
|
||||
? { ...user, username: username }
|
||||
: user
|
||||
);
|
||||
//Stringify the new users list
|
||||
newUsersList = JSON.stringify(newUsersList);
|
||||
//Write the new JSON
|
||||
await fs.writeFile(
|
||||
jsonDirectory + '/users.json',
|
||||
newUsersList,
|
||||
(err) => {
|
||||
if (err) console.log(err);
|
||||
}
|
||||
);
|
||||
res.status(200).json({ message: 'Successful API send' });
|
||||
} catch (error) {
|
||||
//Log for backend
|
||||
console.log(error);
|
||||
//Log for frontend
|
||||
if (error.code == 'ENOENT') {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: 'No such file or directory',
|
||||
});
|
||||
} else {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: 'API error, contact the administrator',
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
res.status(405).json({ message: 'Bad request on API' });
|
||||
if (req.method == 'PUT') {
|
||||
//Verify that the user is logged in.
|
||||
const session = await getServerSession(req, res, authOptions);
|
||||
if (!session) {
|
||||
res.status(401).json({ message: 'You must be logged in.' });
|
||||
return;
|
||||
}
|
||||
|
||||
//The data we expect to receive
|
||||
let { username } = req.body;
|
||||
|
||||
//Read the users file
|
||||
//Find the absolute path of the json directory
|
||||
const jsonDirectory = path.join(process.cwd(), '/config');
|
||||
let usersList = await fs.readFile(jsonDirectory + '/users.json', 'utf8');
|
||||
//Parse the usersList
|
||||
usersList = JSON.parse(usersList);
|
||||
|
||||
//1 : We check that we receive data.
|
||||
if (!username) {
|
||||
//If a variable is empty.
|
||||
res.status(400).json({ message: 'A field is missing.' });
|
||||
return;
|
||||
}
|
||||
|
||||
//2 : control the data
|
||||
const usernameRegex = new RegExp(/^[a-z]{5,15}$/);
|
||||
if (!usernameRegex.test(username)) {
|
||||
res.status(400).json({
|
||||
message: 'Only a-z characters are allowed (5 to 15 char.)',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
//3 : Verify that the user of the session exists
|
||||
const userIndex = usersList.map((user) => user.username).indexOf(session.user.name);
|
||||
if (userIndex === -1) {
|
||||
res.status(400).json({
|
||||
message: 'User is incorrect. Please, logout to update your session.',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
//4 : Change the username
|
||||
try {
|
||||
//Modify the username for the user
|
||||
let newUsersList = usersList.map((user) =>
|
||||
user.username == session.user.name ? { ...user, username: username } : user
|
||||
);
|
||||
//Stringify the new users list
|
||||
newUsersList = JSON.stringify(newUsersList);
|
||||
//Write the new JSON
|
||||
await fs.writeFile(jsonDirectory + '/users.json', newUsersList, (err) => {
|
||||
if (err) console.log(err);
|
||||
});
|
||||
res.status(200).json({ message: 'Successful API send' });
|
||||
} catch (error) {
|
||||
//Log for backend
|
||||
console.log(error);
|
||||
//Log for frontend
|
||||
if (error.code == 'ENOENT') {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: 'No such file or directory',
|
||||
});
|
||||
} else {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: 'API error, contact the administrator',
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
res.status(405).json({ message: 'Bad request on API' });
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,100 +6,91 @@ import fs from 'fs';
|
|||
import path from 'path';
|
||||
|
||||
const logLogin = async (message, req, success = false) => {
|
||||
const ipAddress = req.headers['x-forwarded-for'] || 'unknown';
|
||||
if (success) {
|
||||
console.log(`Login success from ${ipAddress} with user ${message}`);
|
||||
} else {
|
||||
console.log(`Login failed from ${ipAddress} : ${message}`);
|
||||
}
|
||||
const ipAddress = req.headers['x-forwarded-for'] || 'unknown';
|
||||
if (success) {
|
||||
console.log(`Login success from ${ipAddress} with user ${message}`);
|
||||
} else {
|
||||
console.log(`Login failed from ${ipAddress} : ${message}`);
|
||||
}
|
||||
};
|
||||
|
||||
////Use if need getServerSideProps and therefore getServerSession
|
||||
export const authOptions = {
|
||||
providers: [
|
||||
CredentialsProvider({
|
||||
async authorize(credentials, req) {
|
||||
const { username, password } = credentials;
|
||||
//Read the users file
|
||||
//Find the absolute path of the json directory
|
||||
const jsonDirectory = path.join(process.cwd(), '/config');
|
||||
//Check if the users.json file exists and initialize it if not with admin/admin.
|
||||
if (!fs.existsSync(jsonDirectory + '/users.json')) {
|
||||
fs.writeFileSync(
|
||||
jsonDirectory + '/users.json',
|
||||
JSON.stringify([
|
||||
{
|
||||
id: 0,
|
||||
email: '',
|
||||
username: 'admin',
|
||||
password:
|
||||
'$2a$12$20yqRnuaDBH6AE0EvIUcEOzqkuBtn1wDzJdw2Beg8w9S.vEqdso0a',
|
||||
roles: ['admin'],
|
||||
emailAlert: false,
|
||||
appriseAlert: false,
|
||||
},
|
||||
])
|
||||
);
|
||||
}
|
||||
let usersList = await fs.promises.readFile(
|
||||
jsonDirectory + '/users.json',
|
||||
'utf8'
|
||||
);
|
||||
//Parse the usersList
|
||||
usersList = JSON.parse(usersList);
|
||||
providers: [
|
||||
CredentialsProvider({
|
||||
async authorize(credentials, req) {
|
||||
const { username, password } = credentials;
|
||||
//Read the users file
|
||||
//Find the absolute path of the json directory
|
||||
const jsonDirectory = path.join(process.cwd(), '/config');
|
||||
//Check if the users.json file exists and initialize it if not with admin/admin.
|
||||
if (!fs.existsSync(jsonDirectory + '/users.json')) {
|
||||
fs.writeFileSync(
|
||||
jsonDirectory + '/users.json',
|
||||
JSON.stringify([
|
||||
{
|
||||
id: 0,
|
||||
email: '',
|
||||
username: 'admin',
|
||||
password: '$2a$12$20yqRnuaDBH6AE0EvIUcEOzqkuBtn1wDzJdw2Beg8w9S.vEqdso0a',
|
||||
roles: ['admin'],
|
||||
emailAlert: false,
|
||||
appriseAlert: false,
|
||||
},
|
||||
])
|
||||
);
|
||||
}
|
||||
let usersList = await fs.promises.readFile(jsonDirectory + '/users.json', 'utf8');
|
||||
//Parse the usersList
|
||||
usersList = JSON.parse(usersList);
|
||||
|
||||
//Step 1 : does the user exist ?
|
||||
const userIndex = usersList
|
||||
.map((user) => user.username)
|
||||
.indexOf(username.toLowerCase());
|
||||
if (userIndex === -1) {
|
||||
await logLogin(`Bad username ${req.body.username}`, req);
|
||||
throw new Error('Incorrect credentials.');
|
||||
}
|
||||
const user = usersList[userIndex];
|
||||
//Step 1 : does the user exist ?
|
||||
const userIndex = usersList.map((user) => user.username).indexOf(username.toLowerCase());
|
||||
if (userIndex === -1) {
|
||||
await logLogin(`Bad username ${req.body.username}`, req);
|
||||
throw new Error('Incorrect credentials.');
|
||||
}
|
||||
const user = usersList[userIndex];
|
||||
|
||||
//Step 2 : Is the password correct ?
|
||||
const isValid = await verifyPassword(password, user.password);
|
||||
if (!isValid) {
|
||||
await logLogin(
|
||||
`Wrong password for ${req.body.username}`,
|
||||
req
|
||||
);
|
||||
throw new Error('Incorrect credentials.');
|
||||
}
|
||||
//Step 2 : Is the password correct ?
|
||||
const isValid = await verifyPassword(password, user.password);
|
||||
if (!isValid) {
|
||||
await logLogin(`Wrong password for ${req.body.username}`, req);
|
||||
throw new Error('Incorrect credentials.');
|
||||
}
|
||||
|
||||
//Success
|
||||
const account = {
|
||||
name: user.username,
|
||||
email: user.email,
|
||||
id: user.id,
|
||||
roles: user.roles,
|
||||
};
|
||||
//Success
|
||||
const account = {
|
||||
name: user.username,
|
||||
email: user.email,
|
||||
id: user.id,
|
||||
roles: user.roles,
|
||||
};
|
||||
|
||||
await logLogin(req.body.username, req, true);
|
||||
return account;
|
||||
},
|
||||
}),
|
||||
],
|
||||
callbacks: {
|
||||
async jwt({ token, user }) {
|
||||
// Persist the role and the ID to the token right after signin. "user" is the response from signin, and we return account.
|
||||
if (user) {
|
||||
token.roles = user.roles;
|
||||
token.id = user.id;
|
||||
}
|
||||
return token;
|
||||
},
|
||||
async session({ session, token }) {
|
||||
// Send properties to the client to access to the token info through session().
|
||||
if (token) {
|
||||
session.user.roles = token.roles;
|
||||
session.user.id = token.id;
|
||||
}
|
||||
return session;
|
||||
},
|
||||
await logLogin(req.body.username, req, true);
|
||||
return account;
|
||||
},
|
||||
}),
|
||||
],
|
||||
callbacks: {
|
||||
async jwt({ token, user }) {
|
||||
// Persist the role and the ID to the token right after signin. "user" is the response from signin, and we return account.
|
||||
if (user) {
|
||||
token.roles = user.roles;
|
||||
token.id = user.id;
|
||||
}
|
||||
return token;
|
||||
},
|
||||
secret: process.env.NEXTAUTH_SECRET,
|
||||
async session({ session, token }) {
|
||||
// Send properties to the client to access to the token info through session().
|
||||
if (token) {
|
||||
session.user.roles = token.roles;
|
||||
session.user.id = token.id;
|
||||
}
|
||||
return session;
|
||||
},
|
||||
},
|
||||
secret: process.env.NEXTAUTH_SECRET,
|
||||
};
|
||||
|
||||
export default NextAuth(authOptions);
|
||||
|
|
|
|||
|
|
@ -10,239 +10,214 @@ import nodemailerSMTP from '../../../helpers/functions/nodemailerSMTP';
|
|||
import emailAlertStatus from '../../../helpers/templates/emailAlertStatus';
|
||||
|
||||
export default async function handler(req, res) {
|
||||
if (req.headers.authorization == null) {
|
||||
res.status(401).json({
|
||||
status: 401,
|
||||
message: 'Unauthorized',
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (req.headers.authorization == null) {
|
||||
res.status(401).json({
|
||||
status: 401,
|
||||
message: 'Unauthorized',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const CRONJOB_KEY = process.env.CRONJOB_KEY;
|
||||
const ACTION_KEY = req.headers.authorization.split(' ')[1];
|
||||
const CRONJOB_KEY = process.env.CRONJOB_KEY;
|
||||
const ACTION_KEY = req.headers.authorization.split(' ')[1];
|
||||
|
||||
if (req.method == 'POST' && ACTION_KEY === CRONJOB_KEY) {
|
||||
//Var
|
||||
let newRepoList;
|
||||
let repoListToSendAlert = [];
|
||||
let usersList;
|
||||
const date = Math.round(Date.now() / 1000);
|
||||
const jsonDirectory = path.join(process.cwd(), '/config');
|
||||
if (req.method == 'POST' && ACTION_KEY === CRONJOB_KEY) {
|
||||
//Var
|
||||
let newRepoList;
|
||||
let repoListToSendAlert = [];
|
||||
let usersList;
|
||||
const date = Math.round(Date.now() / 1000);
|
||||
const jsonDirectory = path.join(process.cwd(), '/config');
|
||||
|
||||
////PART 1 : Status
|
||||
try {
|
||||
//Check if there are some repositories
|
||||
let repoList = await fs.readFile(
|
||||
jsonDirectory + '/repo.json',
|
||||
'utf8'
|
||||
);
|
||||
repoList = JSON.parse(repoList);
|
||||
if (repoList.length === 0) {
|
||||
res.status(200).json({
|
||||
success:
|
||||
'Status cron has been executed. No repository to check.',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
//Call the shell : getLastSave.sh
|
||||
//Find the absolute path of the shells directory
|
||||
const shellsDirectory = path.join(process.cwd(), '/helpers');
|
||||
//Exec the shell
|
||||
const { stdout, stderr } = await exec(
|
||||
`${shellsDirectory}/shells/getLastSave.sh`
|
||||
);
|
||||
if (stderr) {
|
||||
console.log('stderr:', stderr);
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message:
|
||||
'Error on getting the date for last save, contact the administrator.',
|
||||
});
|
||||
return;
|
||||
}
|
||||
//Parse the JSON output of getLastSave.sh to use it
|
||||
const lastSave = JSON.parse(stdout);
|
||||
|
||||
//Rebuild a newRepoList with the lastSave timestamp updated and the status updated.
|
||||
newRepoList = repoList;
|
||||
for (let index in newRepoList) {
|
||||
const repoFiltered = lastSave.filter(
|
||||
(x) =>
|
||||
x.repositoryName === newRepoList[index].repositoryName
|
||||
);
|
||||
if (repoFiltered.length === 1) {
|
||||
//Write the timestamp of the last save
|
||||
newRepoList[index].lastSave = repoFiltered[0].lastSave;
|
||||
//Trigger the status if the last save is older than alert setting.
|
||||
if (
|
||||
date - newRepoList[index].lastSave >
|
||||
newRepoList[index].alert
|
||||
) {
|
||||
newRepoList[index].status = false;
|
||||
} else if (
|
||||
date - newRepoList[index].lastSave <
|
||||
newRepoList[index].alert
|
||||
) {
|
||||
newRepoList[index].status = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: "API error : can't update the status.",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
//// PART 2 : check if there is a repo that need an alert
|
||||
try {
|
||||
//Here, a mail is sent every 24H (90000) if a repo has down status
|
||||
for (let index in newRepoList) {
|
||||
if (
|
||||
!newRepoList[index].status &&
|
||||
newRepoList[index].alert !== 0 &&
|
||||
(!newRepoList[index].lastStatusAlertSend ||
|
||||
date - newRepoList[index].lastStatusAlertSend > 90000)
|
||||
) {
|
||||
repoListToSendAlert.push(newRepoList[index].alias);
|
||||
newRepoList[index].lastStatusAlertSend = date;
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message:
|
||||
"API error : can't check if a repo needs an email alert.",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
//PART 3 : Save the new repoList
|
||||
try {
|
||||
//Stringify the repoList to write it into the json file.
|
||||
newRepoList = JSON.stringify(newRepoList);
|
||||
//Write the new json
|
||||
await fs.writeFile(
|
||||
jsonDirectory + '/repo.json',
|
||||
newRepoList,
|
||||
(err) => {
|
||||
if (err) console.log(err);
|
||||
}
|
||||
);
|
||||
} catch (err) {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: "API error : can't write the new repoList.",
|
||||
});
|
||||
return;
|
||||
}
|
||||
//PART 4 : Send the alerts
|
||||
if (repoListToSendAlert.length > 0) {
|
||||
// Read user informations
|
||||
try {
|
||||
//Read the email of the user
|
||||
usersList = await fs.readFile(
|
||||
jsonDirectory + '/users.json',
|
||||
'utf8'
|
||||
);
|
||||
//Parse the usersList
|
||||
usersList = JSON.parse(usersList);
|
||||
} catch (err) {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: "API error : can't read user information.",
|
||||
});
|
||||
return;
|
||||
}
|
||||
////EMAIL
|
||||
// If the user has enabled email alerts
|
||||
if (usersList[0].emailAlert) {
|
||||
//Send mail
|
||||
//Create the SMTP Transporter
|
||||
const transporter = nodemailerSMTP();
|
||||
//Mail options
|
||||
const mailData = emailAlertStatus(
|
||||
usersList[0].email,
|
||||
usersList[0].username,
|
||||
repoListToSendAlert
|
||||
);
|
||||
transporter.sendMail(mailData, function (err, info) {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
} else {
|
||||
console.log(info);
|
||||
}
|
||||
});
|
||||
}
|
||||
////APPRISE
|
||||
// If the user has enabled Apprise alerts
|
||||
if (usersList[0].appriseAlert) {
|
||||
let appriseServicesURLs = '';
|
||||
for (let service of usersList[0].appriseServices) {
|
||||
appriseServicesURLs = appriseServicesURLs + service + ' ';
|
||||
}
|
||||
//Mode : package
|
||||
if (usersList[0].appriseMode === 'package') {
|
||||
try {
|
||||
//Send notification via local package.
|
||||
await exec(
|
||||
`apprise -v -b '🔴 Some repositories on BorgWarehouse need attention !\nList of down repositories :\n ${repoListToSendAlert}' ${appriseServicesURLs}`
|
||||
);
|
||||
} catch (err) {
|
||||
console.log(err.stderr);
|
||||
res.status(500).json({
|
||||
message: 'Error : ' + err.stderr,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
//Mode : stateless
|
||||
} else if (usersList[0].appriseMode === 'stateless') {
|
||||
try {
|
||||
await fetch(
|
||||
usersList[0].appriseStatelessURL + '/notify',
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
urls: appriseServicesURLs,
|
||||
body:
|
||||
'🔴 Some repositories on BorgWarehouse need attention !\nList of down repositories :\n' +
|
||||
repoListToSendAlert,
|
||||
}),
|
||||
}
|
||||
);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
res.status(500).json({
|
||||
message: 'Error : ' + err.message,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
//Mode : unknown
|
||||
} else {
|
||||
res.status(422).json({
|
||||
message: 'No Apprise Mode selected or supported.',
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//PART 5 : Sucess
|
||||
////PART 1 : Status
|
||||
try {
|
||||
//Check if there are some repositories
|
||||
let repoList = await fs.readFile(jsonDirectory + '/repo.json', 'utf8');
|
||||
repoList = JSON.parse(repoList);
|
||||
if (repoList.length === 0) {
|
||||
res.status(200).json({
|
||||
success: 'Status cron has been executed.',
|
||||
success: 'Status cron has been executed. No repository to check.',
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
res.status(401).json({
|
||||
status: 401,
|
||||
message: 'Unauthorized',
|
||||
}
|
||||
|
||||
//Call the shell : getLastSave.sh
|
||||
//Find the absolute path of the shells directory
|
||||
const shellsDirectory = path.join(process.cwd(), '/helpers');
|
||||
//Exec the shell
|
||||
const { stdout, stderr } = await exec(`${shellsDirectory}/shells/getLastSave.sh`);
|
||||
if (stderr) {
|
||||
console.log('stderr:', stderr);
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: 'Error on getting the date for last save, contact the administrator.',
|
||||
});
|
||||
return;
|
||||
}
|
||||
//Parse the JSON output of getLastSave.sh to use it
|
||||
const lastSave = JSON.parse(stdout);
|
||||
|
||||
//Rebuild a newRepoList with the lastSave timestamp updated and the status updated.
|
||||
newRepoList = repoList;
|
||||
for (let index in newRepoList) {
|
||||
const repoFiltered = lastSave.filter(
|
||||
(x) => x.repositoryName === newRepoList[index].repositoryName
|
||||
);
|
||||
if (repoFiltered.length === 1) {
|
||||
//Write the timestamp of the last save
|
||||
newRepoList[index].lastSave = repoFiltered[0].lastSave;
|
||||
//Trigger the status if the last save is older than alert setting.
|
||||
if (date - newRepoList[index].lastSave > newRepoList[index].alert) {
|
||||
newRepoList[index].status = false;
|
||||
} else if (date - newRepoList[index].lastSave < newRepoList[index].alert) {
|
||||
newRepoList[index].status = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: "API error : can't update the status.",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
//// PART 2 : check if there is a repo that need an alert
|
||||
try {
|
||||
//Here, a mail is sent every 24H (90000) if a repo has down status
|
||||
for (let index in newRepoList) {
|
||||
if (
|
||||
!newRepoList[index].status &&
|
||||
newRepoList[index].alert !== 0 &&
|
||||
(!newRepoList[index].lastStatusAlertSend ||
|
||||
date - newRepoList[index].lastStatusAlertSend > 90000)
|
||||
) {
|
||||
repoListToSendAlert.push(newRepoList[index].alias);
|
||||
newRepoList[index].lastStatusAlertSend = date;
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: "API error : can't check if a repo needs an email alert.",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
//PART 3 : Save the new repoList
|
||||
try {
|
||||
//Stringify the repoList to write it into the json file.
|
||||
newRepoList = JSON.stringify(newRepoList);
|
||||
//Write the new json
|
||||
await fs.writeFile(jsonDirectory + '/repo.json', newRepoList, (err) => {
|
||||
if (err) console.log(err);
|
||||
});
|
||||
} catch (err) {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: "API error : can't write the new repoList.",
|
||||
});
|
||||
return;
|
||||
}
|
||||
//PART 4 : Send the alerts
|
||||
if (repoListToSendAlert.length > 0) {
|
||||
// Read user informations
|
||||
try {
|
||||
//Read the email of the user
|
||||
usersList = await fs.readFile(jsonDirectory + '/users.json', 'utf8');
|
||||
//Parse the usersList
|
||||
usersList = JSON.parse(usersList);
|
||||
} catch (err) {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: "API error : can't read user information.",
|
||||
});
|
||||
return;
|
||||
}
|
||||
////EMAIL
|
||||
// If the user has enabled email alerts
|
||||
if (usersList[0].emailAlert) {
|
||||
//Send mail
|
||||
//Create the SMTP Transporter
|
||||
const transporter = nodemailerSMTP();
|
||||
//Mail options
|
||||
const mailData = emailAlertStatus(
|
||||
usersList[0].email,
|
||||
usersList[0].username,
|
||||
repoListToSendAlert
|
||||
);
|
||||
transporter.sendMail(mailData, function (err, info) {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
} else {
|
||||
console.log(info);
|
||||
}
|
||||
});
|
||||
}
|
||||
////APPRISE
|
||||
// If the user has enabled Apprise alerts
|
||||
if (usersList[0].appriseAlert) {
|
||||
let appriseServicesURLs = '';
|
||||
for (let service of usersList[0].appriseServices) {
|
||||
appriseServicesURLs = appriseServicesURLs + service + ' ';
|
||||
}
|
||||
//Mode : package
|
||||
if (usersList[0].appriseMode === 'package') {
|
||||
try {
|
||||
//Send notification via local package.
|
||||
await exec(
|
||||
`apprise -v -b '🔴 Some repositories on BorgWarehouse need attention !\nList of down repositories :\n ${repoListToSendAlert}' ${appriseServicesURLs}`
|
||||
);
|
||||
} catch (err) {
|
||||
console.log(err.stderr);
|
||||
res.status(500).json({
|
||||
message: 'Error : ' + err.stderr,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
//Mode : stateless
|
||||
} else if (usersList[0].appriseMode === 'stateless') {
|
||||
try {
|
||||
await fetch(usersList[0].appriseStatelessURL + '/notify', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
urls: appriseServicesURLs,
|
||||
body:
|
||||
'🔴 Some repositories on BorgWarehouse need attention !\nList of down repositories :\n' +
|
||||
repoListToSendAlert,
|
||||
}),
|
||||
});
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
res.status(500).json({
|
||||
message: 'Error : ' + err.message,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
//Mode : unknown
|
||||
} else {
|
||||
res.status(422).json({
|
||||
message: 'No Apprise Mode selected or supported.',
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//PART 5 : Sucess
|
||||
res.status(200).json({
|
||||
success: 'Status cron has been executed.',
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
res.status(401).json({
|
||||
status: 401,
|
||||
message: 'Unauthorized',
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,89 +8,79 @@ const util = require('node:util');
|
|||
const exec = util.promisify(require('node:child_process').exec);
|
||||
|
||||
export default async function handler(req, res) {
|
||||
if (req.headers.authorization == null) {
|
||||
res.status(401).json({
|
||||
status: 401,
|
||||
message: 'Unauthorized',
|
||||
if (req.headers.authorization == null) {
|
||||
res.status(401).json({
|
||||
status: 401,
|
||||
message: 'Unauthorized',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const CRONJOB_KEY = process.env.CRONJOB_KEY;
|
||||
const ACTION_KEY = req.headers.authorization.split(' ')[1];
|
||||
|
||||
try {
|
||||
if (req.method == 'POST' && ACTION_KEY === CRONJOB_KEY) {
|
||||
//Check the repoList
|
||||
const jsonDirectory = path.join(process.cwd(), '/config');
|
||||
let repoList = await fs.readFile(jsonDirectory + '/repo.json', 'utf8');
|
||||
//Parse the repoList
|
||||
repoList = JSON.parse(repoList);
|
||||
//If repoList is empty we stop here.
|
||||
if (repoList.length === 0) {
|
||||
res.status(200).json({
|
||||
success: 'No repositories to analyse yet.',
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const CRONJOB_KEY = process.env.CRONJOB_KEY;
|
||||
const ACTION_KEY = req.headers.authorization.split(' ')[1];
|
||||
|
||||
try {
|
||||
if (req.method == 'POST' && ACTION_KEY === CRONJOB_KEY) {
|
||||
//Check the repoList
|
||||
const jsonDirectory = path.join(process.cwd(), '/config');
|
||||
let repoList = await fs.readFile(
|
||||
jsonDirectory + '/repo.json',
|
||||
'utf8'
|
||||
);
|
||||
//Parse the repoList
|
||||
repoList = JSON.parse(repoList);
|
||||
//If repoList is empty we stop here.
|
||||
if (repoList.length === 0) {
|
||||
res.status(200).json({
|
||||
success: 'No repositories to analyse yet.',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
////Call the shell : getStorageUsed.sh
|
||||
//Find the absolute path of the shells directory
|
||||
const shellsDirectory = path.join(process.cwd(), '/helpers');
|
||||
//Exec the shell
|
||||
const { stdout, stderr } = await exec(
|
||||
`${shellsDirectory}/shells/getStorageUsed.sh`
|
||||
);
|
||||
if (stderr) {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message:
|
||||
'Error on getting storage, contact the administrator.',
|
||||
});
|
||||
return;
|
||||
}
|
||||
//Parse the JSON output of getStorageUsed.sh to use it
|
||||
const storageUsed = JSON.parse(stdout);
|
||||
|
||||
//Rebuild a newRepoList with the storageUsed value updated
|
||||
let newRepoList = repoList;
|
||||
for (let index in newRepoList) {
|
||||
const repoFiltered = storageUsed.filter(
|
||||
(x) => x.name === newRepoList[index].repositoryName
|
||||
);
|
||||
if (repoFiltered.length === 1) {
|
||||
newRepoList[index].storageUsed = repoFiltered[0].size;
|
||||
}
|
||||
}
|
||||
|
||||
//Stringify the repoList to write it into the json file.
|
||||
newRepoList = JSON.stringify(newRepoList);
|
||||
//Write the new json
|
||||
await fs.writeFile(
|
||||
jsonDirectory + '/repo.json',
|
||||
newRepoList,
|
||||
(err) => {
|
||||
if (err) console.log(err);
|
||||
}
|
||||
);
|
||||
|
||||
res.status(200).json({
|
||||
success: 'Storage cron has been executed.',
|
||||
});
|
||||
} else {
|
||||
res.status(401).json({
|
||||
status: 401,
|
||||
message: 'Unauthorized',
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
////Call the shell : getStorageUsed.sh
|
||||
//Find the absolute path of the shells directory
|
||||
const shellsDirectory = path.join(process.cwd(), '/helpers');
|
||||
//Exec the shell
|
||||
const { stdout, stderr } = await exec(`${shellsDirectory}/shells/getStorageUsed.sh`);
|
||||
if (stderr) {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: 'API error, contact the administrator.',
|
||||
status: 500,
|
||||
message: 'Error on getting storage, contact the administrator.',
|
||||
});
|
||||
return;
|
||||
}
|
||||
//Parse the JSON output of getStorageUsed.sh to use it
|
||||
const storageUsed = JSON.parse(stdout);
|
||||
|
||||
//Rebuild a newRepoList with the storageUsed value updated
|
||||
let newRepoList = repoList;
|
||||
for (let index in newRepoList) {
|
||||
const repoFiltered = storageUsed.filter(
|
||||
(x) => x.name === newRepoList[index].repositoryName
|
||||
);
|
||||
if (repoFiltered.length === 1) {
|
||||
newRepoList[index].storageUsed = repoFiltered[0].size;
|
||||
}
|
||||
}
|
||||
|
||||
//Stringify the repoList to write it into the json file.
|
||||
newRepoList = JSON.stringify(newRepoList);
|
||||
//Write the new json
|
||||
await fs.writeFile(jsonDirectory + '/repo.json', newRepoList, (err) => {
|
||||
if (err) console.log(err);
|
||||
});
|
||||
|
||||
res.status(200).json({
|
||||
success: 'Storage cron has been executed.',
|
||||
});
|
||||
} else {
|
||||
res.status(401).json({
|
||||
status: 401,
|
||||
message: 'Unauthorized',
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: 'API error, contact the administrator.',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,124 +7,109 @@ const util = require('node:util');
|
|||
const exec = util.promisify(require('node:child_process').exec);
|
||||
|
||||
export default async function handler(req, res) {
|
||||
if (req.method == 'POST') {
|
||||
//Verify that the user is logged in.
|
||||
const session = await getServerSession(req, res, authOptions);
|
||||
if (!session) {
|
||||
res.status(401).json({ message: 'You must be logged in.' });
|
||||
return;
|
||||
}
|
||||
|
||||
//The data we expect to receive
|
||||
const {
|
||||
alias,
|
||||
sshPublicKey,
|
||||
size,
|
||||
comment,
|
||||
alert,
|
||||
lanCommand,
|
||||
appendOnlyMode,
|
||||
} = req.body;
|
||||
//We check that we receive data for each variable. Only "comment" and "lanCommand" are optional in the form.
|
||||
if (
|
||||
!alias ||
|
||||
!sshPublicKey ||
|
||||
!size ||
|
||||
typeof appendOnlyMode !== 'boolean' ||
|
||||
(!alert && alert !== 0)
|
||||
) {
|
||||
//If a variable is empty.
|
||||
res.status(422).json({
|
||||
message: 'Unexpected data',
|
||||
});
|
||||
//A return to make sure we don't go any further if data are incorrect.
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
//console.log('API call (PUT)');
|
||||
//Find the absolute path of the json directory
|
||||
const jsonDirectory = path.join(process.cwd(), '/config');
|
||||
let repoList = await fs.readFile(
|
||||
jsonDirectory + '/repo.json',
|
||||
'utf8'
|
||||
);
|
||||
//Parse the repoList
|
||||
repoList = JSON.parse(repoList);
|
||||
|
||||
//Find the first biggest ID available to assign it, so the highest ID is already the last added.
|
||||
let newID = 0;
|
||||
for (let element in repoList) {
|
||||
if (newID <= repoList[element].id) {
|
||||
newID = repoList[element].id + 1;
|
||||
}
|
||||
}
|
||||
//Create the new repo object
|
||||
const newRepo = {
|
||||
id: newID,
|
||||
alias: alias,
|
||||
repositoryName: '',
|
||||
status: false,
|
||||
lastSave: 0,
|
||||
alert: alert,
|
||||
storageSize: Number(size),
|
||||
storageUsed: 0,
|
||||
sshPublicKey: sshPublicKey,
|
||||
comment: comment,
|
||||
displayDetails: true,
|
||||
lanCommand: lanCommand,
|
||||
appendOnlyMode: appendOnlyMode,
|
||||
};
|
||||
|
||||
////Call the shell : createRepo.sh
|
||||
//Find the absolute path of the shells directory
|
||||
const shellsDirectory = path.join(process.cwd(), '/helpers');
|
||||
//Exec the shell
|
||||
const { stdout } = await exec(
|
||||
`${shellsDirectory}/shells/createRepo.sh "${newRepo.sshPublicKey}" ${newRepo.storageSize} ${newRepo.appendOnlyMode}`
|
||||
);
|
||||
|
||||
newRepo.repositoryName = stdout.trim();
|
||||
|
||||
//Create the new repoList with the new repo
|
||||
let newRepoList = [newRepo, ...repoList];
|
||||
|
||||
//History the new repoList
|
||||
await repoHistory(newRepoList);
|
||||
|
||||
//Stringify the newRepoList to write it into the json file.
|
||||
newRepoList = JSON.stringify(newRepoList);
|
||||
|
||||
//Write the new json
|
||||
await fs.writeFile(
|
||||
jsonDirectory + '/repo.json',
|
||||
newRepoList,
|
||||
(err) => {
|
||||
if (err) console.log(err);
|
||||
}
|
||||
);
|
||||
res.status(200).json({ message: 'Envoi API réussi' });
|
||||
} catch (error) {
|
||||
//Log for backend
|
||||
console.log(error);
|
||||
//Log for frontend
|
||||
if (error.code == 'ENOENT') {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: 'No such file or directory',
|
||||
});
|
||||
} else {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: error.stdout,
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
res.status(405).json({
|
||||
status: 405,
|
||||
message: 'Method Not Allowed ',
|
||||
});
|
||||
if (req.method == 'POST') {
|
||||
//Verify that the user is logged in.
|
||||
const session = await getServerSession(req, res, authOptions);
|
||||
if (!session) {
|
||||
res.status(401).json({ message: 'You must be logged in.' });
|
||||
return;
|
||||
}
|
||||
|
||||
//The data we expect to receive
|
||||
const { alias, sshPublicKey, size, comment, alert, lanCommand, appendOnlyMode } = req.body;
|
||||
//We check that we receive data for each variable. Only "comment" and "lanCommand" are optional in the form.
|
||||
if (
|
||||
!alias ||
|
||||
!sshPublicKey ||
|
||||
!size ||
|
||||
typeof appendOnlyMode !== 'boolean' ||
|
||||
(!alert && alert !== 0)
|
||||
) {
|
||||
//If a variable is empty.
|
||||
res.status(422).json({
|
||||
message: 'Unexpected data',
|
||||
});
|
||||
//A return to make sure we don't go any further if data are incorrect.
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
//console.log('API call (PUT)');
|
||||
//Find the absolute path of the json directory
|
||||
const jsonDirectory = path.join(process.cwd(), '/config');
|
||||
let repoList = await fs.readFile(jsonDirectory + '/repo.json', 'utf8');
|
||||
//Parse the repoList
|
||||
repoList = JSON.parse(repoList);
|
||||
|
||||
//Find the first biggest ID available to assign it, so the highest ID is already the last added.
|
||||
let newID = 0;
|
||||
for (let element in repoList) {
|
||||
if (newID <= repoList[element].id) {
|
||||
newID = repoList[element].id + 1;
|
||||
}
|
||||
}
|
||||
//Create the new repo object
|
||||
const newRepo = {
|
||||
id: newID,
|
||||
alias: alias,
|
||||
repositoryName: '',
|
||||
status: false,
|
||||
lastSave: 0,
|
||||
alert: alert,
|
||||
storageSize: Number(size),
|
||||
storageUsed: 0,
|
||||
sshPublicKey: sshPublicKey,
|
||||
comment: comment,
|
||||
displayDetails: true,
|
||||
lanCommand: lanCommand,
|
||||
appendOnlyMode: appendOnlyMode,
|
||||
};
|
||||
|
||||
////Call the shell : createRepo.sh
|
||||
//Find the absolute path of the shells directory
|
||||
const shellsDirectory = path.join(process.cwd(), '/helpers');
|
||||
//Exec the shell
|
||||
const { stdout } = await exec(
|
||||
`${shellsDirectory}/shells/createRepo.sh "${newRepo.sshPublicKey}" ${newRepo.storageSize} ${newRepo.appendOnlyMode}`
|
||||
);
|
||||
|
||||
newRepo.repositoryName = stdout.trim();
|
||||
|
||||
//Create the new repoList with the new repo
|
||||
let newRepoList = [newRepo, ...repoList];
|
||||
|
||||
//History the new repoList
|
||||
await repoHistory(newRepoList);
|
||||
|
||||
//Stringify the newRepoList to write it into the json file.
|
||||
newRepoList = JSON.stringify(newRepoList);
|
||||
|
||||
//Write the new json
|
||||
await fs.writeFile(jsonDirectory + '/repo.json', newRepoList, (err) => {
|
||||
if (err) console.log(err);
|
||||
});
|
||||
res.status(200).json({ message: 'Envoi API réussi' });
|
||||
} catch (error) {
|
||||
//Log for backend
|
||||
console.log(error);
|
||||
//Log for frontend
|
||||
if (error.code == 'ENOENT') {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: 'No such file or directory',
|
||||
});
|
||||
} else {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: error.stdout,
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
res.status(405).json({
|
||||
status: 405,
|
||||
message: 'Method Not Allowed ',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,112 +7,103 @@ const util = require('node:util');
|
|||
const exec = util.promisify(require('node:child_process').exec);
|
||||
|
||||
export default async function handler(req, res) {
|
||||
if (req.method == 'DELETE') {
|
||||
//Verify that the user is logged in.
|
||||
const session = await getServerSession(req, res, authOptions);
|
||||
if (!session) {
|
||||
res.status(401).json({ message: 'You must be logged in.' });
|
||||
return;
|
||||
}
|
||||
//If deletion is disabled on the server, return error
|
||||
if (process.env.DISABLE_DELETE_REPO === 'true') {
|
||||
res.status(403).json({
|
||||
status: 403,
|
||||
message: 'Deletion is disabled on this server',
|
||||
});
|
||||
return;
|
||||
}
|
||||
//The data we expect to receive
|
||||
const { toDelete } = req.body;
|
||||
////We check that we receive toDelete and it must be a bool.
|
||||
if (typeof toDelete != 'boolean' || toDelete === false) {
|
||||
//If a variable is empty.
|
||||
res.status(422).json({
|
||||
message: 'Unexpected data',
|
||||
});
|
||||
//A return to make sure we don't go any further if data are incorrect.
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
//console.log('API call (DELETE)');
|
||||
//Find the absolute path of the json directory
|
||||
const jsonDirectory = path.join(process.cwd(), '/config');
|
||||
let repoList = await fs.readFile(
|
||||
jsonDirectory + '/repo.json',
|
||||
'utf8'
|
||||
);
|
||||
//Parse the repoList
|
||||
repoList = JSON.parse(repoList);
|
||||
|
||||
//Find the ID in the repoList and delete the repo.
|
||||
//NOTE : req.query.slug return a string, so parseInt to use with indexOf.
|
||||
const indexToDelete = repoList
|
||||
.map((repo) => repo.id)
|
||||
.indexOf(parseInt(req.query.slug));
|
||||
|
||||
////Call the shell : deleteRepo.sh
|
||||
//Find the absolute path of the shells directory
|
||||
const shellsDirectory = path.join(process.cwd(), '/helpers');
|
||||
//Exec the shell
|
||||
const { stdout, stderr } = await exec(
|
||||
`${shellsDirectory}/shells/deleteRepo.sh ${repoList[indexToDelete].repositoryName}`
|
||||
);
|
||||
if (stderr) {
|
||||
console.log('stderr:', stderr);
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: 'Error on delete, contact the administrator.',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
//Delete the repo in the repoList
|
||||
if (indexToDelete !== -1) {
|
||||
repoList.splice(indexToDelete, 1);
|
||||
} else {
|
||||
console.log('The index to delete does not existe (-1)');
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: 'API error, contact the administrator',
|
||||
});
|
||||
return;
|
||||
}
|
||||
//History the repoList
|
||||
await repoHistory(repoList);
|
||||
//Stringify the repoList to write it into the json file.
|
||||
repoList = JSON.stringify(repoList);
|
||||
//Write the new json
|
||||
await fs.writeFile(
|
||||
jsonDirectory + '/repo.json',
|
||||
repoList,
|
||||
(err) => {
|
||||
if (err) console.log(err);
|
||||
}
|
||||
);
|
||||
|
||||
res.status(200).json({ message: 'Envoi API réussi' });
|
||||
} catch (error) {
|
||||
//Log for backend
|
||||
console.log(error);
|
||||
//Log for frontend
|
||||
if (error.code == 'ENOENT') {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: 'No such file or directory',
|
||||
});
|
||||
} else {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: 'API error, contact the administrator',
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
res.status(405).json({
|
||||
status: 405,
|
||||
message: 'Method Not Allowed ',
|
||||
});
|
||||
if (req.method == 'DELETE') {
|
||||
//Verify that the user is logged in.
|
||||
const session = await getServerSession(req, res, authOptions);
|
||||
if (!session) {
|
||||
res.status(401).json({ message: 'You must be logged in.' });
|
||||
return;
|
||||
}
|
||||
//If deletion is disabled on the server, return error
|
||||
if (process.env.DISABLE_DELETE_REPO === 'true') {
|
||||
res.status(403).json({
|
||||
status: 403,
|
||||
message: 'Deletion is disabled on this server',
|
||||
});
|
||||
return;
|
||||
}
|
||||
//The data we expect to receive
|
||||
const { toDelete } = req.body;
|
||||
////We check that we receive toDelete and it must be a bool.
|
||||
if (typeof toDelete != 'boolean' || toDelete === false) {
|
||||
//If a variable is empty.
|
||||
res.status(422).json({
|
||||
message: 'Unexpected data',
|
||||
});
|
||||
//A return to make sure we don't go any further if data are incorrect.
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
//console.log('API call (DELETE)');
|
||||
//Find the absolute path of the json directory
|
||||
const jsonDirectory = path.join(process.cwd(), '/config');
|
||||
let repoList = await fs.readFile(jsonDirectory + '/repo.json', 'utf8');
|
||||
//Parse the repoList
|
||||
repoList = JSON.parse(repoList);
|
||||
|
||||
//Find the ID in the repoList and delete the repo.
|
||||
//NOTE : req.query.slug return a string, so parseInt to use with indexOf.
|
||||
const indexToDelete = repoList.map((repo) => repo.id).indexOf(parseInt(req.query.slug));
|
||||
|
||||
////Call the shell : deleteRepo.sh
|
||||
//Find the absolute path of the shells directory
|
||||
const shellsDirectory = path.join(process.cwd(), '/helpers');
|
||||
//Exec the shell
|
||||
const { stdout, stderr } = await exec(
|
||||
`${shellsDirectory}/shells/deleteRepo.sh ${repoList[indexToDelete].repositoryName}`
|
||||
);
|
||||
if (stderr) {
|
||||
console.log('stderr:', stderr);
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: 'Error on delete, contact the administrator.',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
//Delete the repo in the repoList
|
||||
if (indexToDelete !== -1) {
|
||||
repoList.splice(indexToDelete, 1);
|
||||
} else {
|
||||
console.log('The index to delete does not existe (-1)');
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: 'API error, contact the administrator',
|
||||
});
|
||||
return;
|
||||
}
|
||||
//History the repoList
|
||||
await repoHistory(repoList);
|
||||
//Stringify the repoList to write it into the json file.
|
||||
repoList = JSON.stringify(repoList);
|
||||
//Write the new json
|
||||
await fs.writeFile(jsonDirectory + '/repo.json', repoList, (err) => {
|
||||
if (err) console.log(err);
|
||||
});
|
||||
|
||||
res.status(200).json({ message: 'Envoi API réussi' });
|
||||
} catch (error) {
|
||||
//Log for backend
|
||||
console.log(error);
|
||||
//Log for frontend
|
||||
if (error.code == 'ENOENT') {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: 'No such file or directory',
|
||||
});
|
||||
} else {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: 'API error, contact the administrator',
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
res.status(405).json({
|
||||
status: 405,
|
||||
message: 'Method Not Allowed ',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,112 +7,95 @@ const util = require('node:util');
|
|||
const exec = util.promisify(require('node:child_process').exec);
|
||||
|
||||
export default async function handler(req, res) {
|
||||
if (req.method == 'PUT') {
|
||||
//Verify that the user is logged in.
|
||||
const session = await getServerSession(req, res, authOptions);
|
||||
if (!session) {
|
||||
res.status(401).json({ message: 'You must be logged in.' });
|
||||
return;
|
||||
}
|
||||
|
||||
//The data we expect to receive
|
||||
const {
|
||||
alias,
|
||||
sshPublicKey,
|
||||
size,
|
||||
comment,
|
||||
alert,
|
||||
lanCommand,
|
||||
appendOnlyMode,
|
||||
} = req.body;
|
||||
//Only "comment" and "lanCommand" are optional in the form.
|
||||
if (
|
||||
!alias ||
|
||||
!sshPublicKey ||
|
||||
!size ||
|
||||
typeof appendOnlyMode !== 'boolean' ||
|
||||
(!alert && alert !== 0)
|
||||
) {
|
||||
res.status(422).json({
|
||||
message: 'Unexpected data',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
//Find the absolute path of the json directory
|
||||
const jsonDirectory = path.join(process.cwd(), '/config');
|
||||
let repoList = await fs.readFile(
|
||||
jsonDirectory + '/repo.json',
|
||||
'utf8'
|
||||
);
|
||||
//Parse the repoList
|
||||
repoList = JSON.parse(repoList);
|
||||
|
||||
//Find the index of the repo in repoList
|
||||
//NOTE : req.query.slug return a string, so parseInt to use with indexOf.
|
||||
const repoIndex = repoList
|
||||
.map((repo) => repo.id)
|
||||
.indexOf(parseInt(req.query.slug));
|
||||
|
||||
////Call the shell : updateRepo.sh
|
||||
//Find the absolute path of the shells directory
|
||||
const shellsDirectory = path.join(process.cwd(), '/helpers');
|
||||
// //Exec the shell
|
||||
await exec(
|
||||
`${shellsDirectory}/shells/updateRepo.sh ${repoList[repoIndex].repositoryName} "${sshPublicKey}" ${size} ${appendOnlyMode}`
|
||||
);
|
||||
|
||||
//Find the ID in the data and change the values transmitted by the form
|
||||
let newRepoList = repoList.map((repo) =>
|
||||
repo.id == req.query.slug
|
||||
? {
|
||||
...repo,
|
||||
alias: alias,
|
||||
sshPublicKey: sshPublicKey,
|
||||
storageSize: Number(size),
|
||||
comment: comment,
|
||||
alert: alert,
|
||||
lanCommand: lanCommand,
|
||||
appendOnlyMode: appendOnlyMode,
|
||||
}
|
||||
: repo
|
||||
);
|
||||
//History the new repoList
|
||||
await repoHistory(newRepoList);
|
||||
//Stringify the newRepoList to write it into the json file.
|
||||
newRepoList = JSON.stringify(newRepoList);
|
||||
//Write the new json
|
||||
await fs.writeFile(
|
||||
jsonDirectory + '/repo.json',
|
||||
newRepoList,
|
||||
(err) => {
|
||||
if (err) console.log(err);
|
||||
}
|
||||
);
|
||||
|
||||
res.status(200).json({ message: 'Envoi API réussi' });
|
||||
} catch (error) {
|
||||
//Log for backend
|
||||
console.log(error);
|
||||
//Log for frontend
|
||||
if (error.code == 'ENOENT') {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: 'No such file or directory',
|
||||
});
|
||||
} else {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: error.stdout,
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
res.status(405).json({
|
||||
status: 405,
|
||||
message: 'Method Not Allowed ',
|
||||
});
|
||||
if (req.method == 'PUT') {
|
||||
//Verify that the user is logged in.
|
||||
const session = await getServerSession(req, res, authOptions);
|
||||
if (!session) {
|
||||
res.status(401).json({ message: 'You must be logged in.' });
|
||||
return;
|
||||
}
|
||||
|
||||
//The data we expect to receive
|
||||
const { alias, sshPublicKey, size, comment, alert, lanCommand, appendOnlyMode } = req.body;
|
||||
//Only "comment" and "lanCommand" are optional in the form.
|
||||
if (
|
||||
!alias ||
|
||||
!sshPublicKey ||
|
||||
!size ||
|
||||
typeof appendOnlyMode !== 'boolean' ||
|
||||
(!alert && alert !== 0)
|
||||
) {
|
||||
res.status(422).json({
|
||||
message: 'Unexpected data',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
//Find the absolute path of the json directory
|
||||
const jsonDirectory = path.join(process.cwd(), '/config');
|
||||
let repoList = await fs.readFile(jsonDirectory + '/repo.json', 'utf8');
|
||||
//Parse the repoList
|
||||
repoList = JSON.parse(repoList);
|
||||
|
||||
//Find the index of the repo in repoList
|
||||
//NOTE : req.query.slug return a string, so parseInt to use with indexOf.
|
||||
const repoIndex = repoList.map((repo) => repo.id).indexOf(parseInt(req.query.slug));
|
||||
|
||||
////Call the shell : updateRepo.sh
|
||||
//Find the absolute path of the shells directory
|
||||
const shellsDirectory = path.join(process.cwd(), '/helpers');
|
||||
// //Exec the shell
|
||||
await exec(
|
||||
`${shellsDirectory}/shells/updateRepo.sh ${repoList[repoIndex].repositoryName} "${sshPublicKey}" ${size} ${appendOnlyMode}`
|
||||
);
|
||||
|
||||
//Find the ID in the data and change the values transmitted by the form
|
||||
let newRepoList = repoList.map((repo) =>
|
||||
repo.id == req.query.slug
|
||||
? {
|
||||
...repo,
|
||||
alias: alias,
|
||||
sshPublicKey: sshPublicKey,
|
||||
storageSize: Number(size),
|
||||
comment: comment,
|
||||
alert: alert,
|
||||
lanCommand: lanCommand,
|
||||
appendOnlyMode: appendOnlyMode,
|
||||
}
|
||||
: repo
|
||||
);
|
||||
//History the new repoList
|
||||
await repoHistory(newRepoList);
|
||||
//Stringify the newRepoList to write it into the json file.
|
||||
newRepoList = JSON.stringify(newRepoList);
|
||||
//Write the new json
|
||||
await fs.writeFile(jsonDirectory + '/repo.json', newRepoList, (err) => {
|
||||
if (err) console.log(err);
|
||||
});
|
||||
|
||||
res.status(200).json({ message: 'Envoi API réussi' });
|
||||
} catch (error) {
|
||||
//Log for backend
|
||||
console.log(error);
|
||||
//Log for frontend
|
||||
if (error.code == 'ENOENT') {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: 'No such file or directory',
|
||||
});
|
||||
} else {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: error.stdout,
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
res.status(405).json({
|
||||
status: 405,
|
||||
message: 'Method Not Allowed ',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,57 +4,54 @@ import { authOptions } from '../../../auth/[...nextauth]';
|
|||
import { getServerSession } from 'next-auth/next';
|
||||
|
||||
export default async function handler(req, res) {
|
||||
if (req.method == 'GET') {
|
||||
//Verify that the user is logged in.
|
||||
const session = await getServerSession(req, res, authOptions);
|
||||
if (!session) {
|
||||
res.status(401).json({ message: 'You must be logged in.' });
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
//console.log('API call (GET)');
|
||||
//Find the absolute path of the json directory
|
||||
const jsonDirectory = path.join(process.cwd(), '/config');
|
||||
//Read the json data file data.json
|
||||
let repoList = await fs.readFile(
|
||||
jsonDirectory + '/repo.json',
|
||||
'utf8'
|
||||
);
|
||||
//Parse the json data file who has been read
|
||||
repoList = JSON.parse(repoList);
|
||||
//Find the ID (req.query.slug) in RepoList and put the repo in a single object (repo).
|
||||
let repo;
|
||||
for (let element in repoList) {
|
||||
if (repoList[element].id == req.query.slug) {
|
||||
repo = repoList[element];
|
||||
}
|
||||
}
|
||||
//If no repo is found --> 404.
|
||||
if (!repo) {
|
||||
res.status(404).json({
|
||||
message: 'No repository with id #' + req.query.slug,
|
||||
});
|
||||
return;
|
||||
}
|
||||
// Send the response and return the repo object --> 200
|
||||
res.status(200).json({ repo });
|
||||
} catch (error) {
|
||||
//Log for backend
|
||||
console.log(error);
|
||||
//Log for frontend
|
||||
if (error.code == 'ENOENT') {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: 'No such file or directory',
|
||||
});
|
||||
} else {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: 'API error, contact the administrator',
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (req.method == 'GET') {
|
||||
//Verify that the user is logged in.
|
||||
const session = await getServerSession(req, res, authOptions);
|
||||
if (!session) {
|
||||
res.status(401).json({ message: 'You must be logged in.' });
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
//console.log('API call (GET)');
|
||||
//Find the absolute path of the json directory
|
||||
const jsonDirectory = path.join(process.cwd(), '/config');
|
||||
//Read the json data file data.json
|
||||
let repoList = await fs.readFile(jsonDirectory + '/repo.json', 'utf8');
|
||||
//Parse the json data file who has been read
|
||||
repoList = JSON.parse(repoList);
|
||||
//Find the ID (req.query.slug) in RepoList and put the repo in a single object (repo).
|
||||
let repo;
|
||||
for (let element in repoList) {
|
||||
if (repoList[element].id == req.query.slug) {
|
||||
repo = repoList[element];
|
||||
}
|
||||
}
|
||||
//If no repo is found --> 404.
|
||||
if (!repo) {
|
||||
res.status(404).json({
|
||||
message: 'No repository with id #' + req.query.slug,
|
||||
});
|
||||
return;
|
||||
}
|
||||
// Send the response and return the repo object --> 200
|
||||
res.status(200).json({ repo });
|
||||
} catch (error) {
|
||||
//Log for backend
|
||||
console.log(error);
|
||||
//Log for frontend
|
||||
if (error.code == 'ENOENT') {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: 'No such file or directory',
|
||||
});
|
||||
} else {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: 'API error, contact the administrator',
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,51 +4,45 @@ import { authOptions } from '../../../pages/api/auth/[...nextauth]';
|
|||
import { getServerSession } from 'next-auth/next';
|
||||
|
||||
export default async function handler(req, res) {
|
||||
if (req.method == 'GET') {
|
||||
//Verify that the user is logged in.
|
||||
const session = await getServerSession(req, res, authOptions);
|
||||
if (!session) {
|
||||
// res.status(401).json({ message: 'You must be logged in.' });
|
||||
res.status(401).end();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
//console.log('API call (GET)');
|
||||
//Find the absolute path of the json directory
|
||||
const jsonDirectory = path.join(process.cwd(), '/config');
|
||||
//Check if the repo.json file exists and initialize it if not.
|
||||
if (!fs.existsSync(jsonDirectory + '/repo.json')) {
|
||||
fs.writeFileSync(
|
||||
jsonDirectory + '/repo.json',
|
||||
JSON.stringify([])
|
||||
);
|
||||
}
|
||||
//Read the file repo.json
|
||||
let repoList = await fs.promises.readFile(
|
||||
jsonDirectory + '/repo.json',
|
||||
'utf8'
|
||||
);
|
||||
//Parse the JSON
|
||||
repoList = JSON.parse(repoList);
|
||||
//Send the response
|
||||
res.status(200).json({ repoList });
|
||||
} catch (error) {
|
||||
//Log for backend
|
||||
console.log(error);
|
||||
//Log for frontend
|
||||
if (error.code == 'ENOENT') {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: 'No such file or directory',
|
||||
});
|
||||
} else {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: 'API error, contact the administrator !',
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (req.method == 'GET') {
|
||||
//Verify that the user is logged in.
|
||||
const session = await getServerSession(req, res, authOptions);
|
||||
if (!session) {
|
||||
// res.status(401).json({ message: 'You must be logged in.' });
|
||||
res.status(401).end();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
//console.log('API call (GET)');
|
||||
//Find the absolute path of the json directory
|
||||
const jsonDirectory = path.join(process.cwd(), '/config');
|
||||
//Check if the repo.json file exists and initialize it if not.
|
||||
if (!fs.existsSync(jsonDirectory + '/repo.json')) {
|
||||
fs.writeFileSync(jsonDirectory + '/repo.json', JSON.stringify([]));
|
||||
}
|
||||
//Read the file repo.json
|
||||
let repoList = await fs.promises.readFile(jsonDirectory + '/repo.json', 'utf8');
|
||||
//Parse the JSON
|
||||
repoList = JSON.parse(repoList);
|
||||
//Send the response
|
||||
res.status(200).json({ repoList });
|
||||
} catch (error) {
|
||||
//Log for backend
|
||||
console.log(error);
|
||||
//Log for frontend
|
||||
if (error.code == 'ENOENT') {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: 'No such file or directory',
|
||||
});
|
||||
} else {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: 'API error, contact the administrator !',
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,16 @@
|
|||
import packageInfo from '../../../package.json';
|
||||
|
||||
export default async function handler(req, res) {
|
||||
if (req.method === 'GET') {
|
||||
try {
|
||||
res.status(200).json({ version: packageInfo.version });
|
||||
return;
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: 'API error, contact the administrator !',
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (req.method === 'GET') {
|
||||
try {
|
||||
res.status(200).json({ version: packageInfo.version });
|
||||
return;
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: 'API error, contact the administrator !',
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,46 +8,42 @@ import Head from 'next/head';
|
|||
import RepoList from '../Containers/RepoList/RepoList';
|
||||
|
||||
export default function Index() {
|
||||
const { status } = useSession();
|
||||
const { status } = useSession();
|
||||
|
||||
return (
|
||||
return (
|
||||
<>
|
||||
{status === 'unauthenticated' || status === 'loading' ? null : (
|
||||
<>
|
||||
{status === 'unauthenticated' || status === 'loading' ? null : (
|
||||
<>
|
||||
<Head>
|
||||
{/* <link
|
||||
<Head>
|
||||
{/* <link
|
||||
rel='preload'
|
||||
href='/api/repo'
|
||||
as='fetch'
|
||||
crossorigin='anonymous'
|
||||
></link> */}
|
||||
<title>Repositories - BorgWarehouse</title>
|
||||
</Head>
|
||||
<RepoList />
|
||||
</>
|
||||
)}
|
||||
<title>Repositories - BorgWarehouse</title>
|
||||
</Head>
|
||||
<RepoList />
|
||||
</>
|
||||
);
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export async function getServerSideProps(context) {
|
||||
//Var
|
||||
const session = await getServerSession(
|
||||
context.req,
|
||||
context.res,
|
||||
authOptions
|
||||
);
|
||||
|
||||
if (!session) {
|
||||
return {
|
||||
redirect: {
|
||||
destination: '/login',
|
||||
permanent: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
//Var
|
||||
const session = await getServerSession(context.req, context.res, authOptions);
|
||||
|
||||
if (!session) {
|
||||
return {
|
||||
props: {},
|
||||
redirect: {
|
||||
destination: '/login',
|
||||
permanent: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
props: {},
|
||||
};
|
||||
}
|
||||
|
|
|
|||
317
pages/login.js
317
pages/login.js
|
|
@ -11,182 +11,169 @@ import { getServerSession } from 'next-auth/next';
|
|||
import Error from '../Components/UI/Error/Error';
|
||||
|
||||
export default function Login() {
|
||||
//Var
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
formState: { errors },
|
||||
reset,
|
||||
} = useForm();
|
||||
const router = useRouter();
|
||||
//Var
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
formState: { errors },
|
||||
reset,
|
||||
} = useForm();
|
||||
const router = useRouter();
|
||||
|
||||
//State
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [error, setError] = useState();
|
||||
//State
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [error, setError] = useState();
|
||||
|
||||
//Functions
|
||||
const formSubmitHandler = async (data) => {
|
||||
setIsLoading(true);
|
||||
setError(null);
|
||||
const resultat = await signIn('credentials', {
|
||||
username: data.username,
|
||||
password: data.password,
|
||||
redirect: false,
|
||||
});
|
||||
//Functions
|
||||
const formSubmitHandler = async (data) => {
|
||||
setIsLoading(true);
|
||||
setError(null);
|
||||
const resultat = await signIn('credentials', {
|
||||
username: data.username,
|
||||
password: data.password,
|
||||
redirect: false,
|
||||
});
|
||||
|
||||
setIsLoading(false);
|
||||
setIsLoading(false);
|
||||
|
||||
if (resultat.error) {
|
||||
reset();
|
||||
setError(resultat.error);
|
||||
setTimeout(() => setError(), 4000);
|
||||
} else {
|
||||
router.replace('/');
|
||||
}
|
||||
};
|
||||
if (resultat.error) {
|
||||
reset();
|
||||
setError(resultat.error);
|
||||
setTimeout(() => setError(), 4000);
|
||||
} else {
|
||||
router.replace('/');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className='signInContainer'
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
height: '100vh',
|
||||
}}
|
||||
return (
|
||||
<div
|
||||
className='signInContainer'
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
height: '100vh',
|
||||
}}
|
||||
>
|
||||
<section style={{ display: 'flex', justifyContent: 'center' }}>
|
||||
<main
|
||||
style={{
|
||||
backgroundColor: '#212942',
|
||||
padding: '30px',
|
||||
borderRadius: '10px',
|
||||
boxShadow: '0 14px 28px rgba(0, 0, 0, .2), 0 10px 10px rgba(0, 0, 0, .2)',
|
||||
height: '100%',
|
||||
borderTop: '10px solid #704dff',
|
||||
animation: 'ease-in 0.3s 1 normal none',
|
||||
width: '310px',
|
||||
}}
|
||||
>
|
||||
<section style={{ display: 'flex', justifyContent: 'center' }}>
|
||||
<main
|
||||
style={{
|
||||
backgroundColor: '#212942',
|
||||
padding: '30px',
|
||||
borderRadius: '10px',
|
||||
boxShadow:
|
||||
'0 14px 28px rgba(0, 0, 0, .2), 0 10px 10px rgba(0, 0, 0, .2)',
|
||||
height: '100%',
|
||||
borderTop: '10px solid #704dff',
|
||||
animation: 'ease-in 0.3s 1 normal none',
|
||||
width: '310px',
|
||||
}}
|
||||
<h1
|
||||
style={{
|
||||
fontSize: '1.8em',
|
||||
fontWeight: 'bold',
|
||||
color: '#6d4aff',
|
||||
textShadow: '#6d4aff 0px 0px 18px',
|
||||
textAlign: 'center',
|
||||
}}
|
||||
>
|
||||
BorgWarehouse
|
||||
</h1>
|
||||
<h5
|
||||
style={{
|
||||
color: '#a1a4ad',
|
||||
letterSpacing: '1.5px',
|
||||
textAlign: 'center',
|
||||
marginBottom: '3.5em',
|
||||
}}
|
||||
>
|
||||
Sign in to your account.
|
||||
</h5>
|
||||
{error && <Error message={error} />}
|
||||
<form onSubmit={handleSubmit(formSubmitHandler)}>
|
||||
<p>
|
||||
<input
|
||||
type='text'
|
||||
placeholder='Username'
|
||||
className='signInInput'
|
||||
{...register('username', {
|
||||
required: 'This field is required.',
|
||||
pattern: {
|
||||
value: /^[^\s]+$/g,
|
||||
message: 'No space allowed.',
|
||||
},
|
||||
})}
|
||||
/>
|
||||
{errors.username && (
|
||||
<small
|
||||
style={{
|
||||
color: 'red',
|
||||
display: 'block',
|
||||
marginTop: '3px',
|
||||
}}
|
||||
>
|
||||
<h1
|
||||
style={{
|
||||
fontSize: '1.8em',
|
||||
fontWeight: 'bold',
|
||||
color: '#6d4aff',
|
||||
textShadow: '#6d4aff 0px 0px 18px',
|
||||
textAlign: 'center',
|
||||
}}
|
||||
>
|
||||
BorgWarehouse
|
||||
</h1>
|
||||
<h5
|
||||
style={{
|
||||
color: '#a1a4ad',
|
||||
letterSpacing: '1.5px',
|
||||
textAlign: 'center',
|
||||
marginBottom: '3.5em',
|
||||
}}
|
||||
>
|
||||
Sign in to your account.
|
||||
</h5>
|
||||
{error && <Error message={error} />}
|
||||
<form onSubmit={handleSubmit(formSubmitHandler)}>
|
||||
<p>
|
||||
<input
|
||||
type='text'
|
||||
placeholder='Username'
|
||||
className='signInInput'
|
||||
{...register('username', {
|
||||
required: 'This field is required.',
|
||||
pattern: {
|
||||
value: /^[^\s]+$/g,
|
||||
message: 'No space allowed.',
|
||||
},
|
||||
})}
|
||||
/>
|
||||
{errors.username && (
|
||||
<small
|
||||
style={{
|
||||
color: 'red',
|
||||
display: 'block',
|
||||
marginTop: '3px',
|
||||
}}
|
||||
>
|
||||
{errors.username.message}
|
||||
</small>
|
||||
)}
|
||||
</p>
|
||||
<p>
|
||||
<input
|
||||
type='password'
|
||||
placeholder='Password'
|
||||
className='signInInput'
|
||||
{...register('password', {
|
||||
required: 'This field is required.',
|
||||
})}
|
||||
/>
|
||||
{errors.password && (
|
||||
<small
|
||||
style={{
|
||||
color: 'red',
|
||||
display: 'block',
|
||||
marginTop: '3px',
|
||||
}}
|
||||
>
|
||||
{errors.password.message}
|
||||
</small>
|
||||
)}
|
||||
</p>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<button
|
||||
className='signInButton'
|
||||
disabled={isLoading}
|
||||
>
|
||||
{isLoading ? (
|
||||
<SpinnerDotted
|
||||
size={20}
|
||||
thickness={150}
|
||||
speed={100}
|
||||
color='#fff'
|
||||
/>
|
||||
) : (
|
||||
'Sign in'
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</main>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
{errors.username.message}
|
||||
</small>
|
||||
)}
|
||||
</p>
|
||||
<p>
|
||||
<input
|
||||
type='password'
|
||||
placeholder='Password'
|
||||
className='signInInput'
|
||||
{...register('password', {
|
||||
required: 'This field is required.',
|
||||
})}
|
||||
/>
|
||||
{errors.password && (
|
||||
<small
|
||||
style={{
|
||||
color: 'red',
|
||||
display: 'block',
|
||||
marginTop: '3px',
|
||||
}}
|
||||
>
|
||||
{errors.password.message}
|
||||
</small>
|
||||
)}
|
||||
</p>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<button className='signInButton' disabled={isLoading}>
|
||||
{isLoading ? (
|
||||
<SpinnerDotted size={20} thickness={150} speed={100} color='#fff' />
|
||||
) : (
|
||||
'Sign in'
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</main>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export async function getServerSideProps(context) {
|
||||
//Var
|
||||
const session = await getServerSession(
|
||||
context.req,
|
||||
context.res,
|
||||
authOptions
|
||||
);
|
||||
|
||||
//Here, if I am connected, I redirect to the home page.
|
||||
if (session) {
|
||||
return {
|
||||
redirect: {
|
||||
destination: '/',
|
||||
permanent: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
//Var
|
||||
const session = await getServerSession(context.req, context.res, authOptions);
|
||||
|
||||
//Here, if I am connected, I redirect to the home page.
|
||||
if (session) {
|
||||
return {
|
||||
props: { session },
|
||||
redirect: {
|
||||
destination: '/',
|
||||
permanent: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
props: { session },
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,27 +3,23 @@ import { authOptions } from '../../pages/api/auth/[...nextauth]';
|
|||
import { getServerSession } from 'next-auth/next';
|
||||
|
||||
export default function Add() {
|
||||
return <RepoList />;
|
||||
return <RepoList />;
|
||||
}
|
||||
|
||||
export async function getServerSideProps(context) {
|
||||
//Var
|
||||
const session = await getServerSession(
|
||||
context.req,
|
||||
context.res,
|
||||
authOptions
|
||||
);
|
||||
|
||||
if (!session) {
|
||||
return {
|
||||
redirect: {
|
||||
destination: '/login',
|
||||
permanent: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
//Var
|
||||
const session = await getServerSession(context.req, context.res, authOptions);
|
||||
|
||||
if (!session) {
|
||||
return {
|
||||
props: {},
|
||||
redirect: {
|
||||
destination: '/login',
|
||||
permanent: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
props: {},
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,27 +3,23 @@ import { authOptions } from '../../../pages/api/auth/[...nextauth]';
|
|||
import { getServerSession } from 'next-auth/next';
|
||||
|
||||
export default function Add() {
|
||||
return <RepoList />;
|
||||
return <RepoList />;
|
||||
}
|
||||
|
||||
export async function getServerSideProps(context) {
|
||||
//Var
|
||||
const session = await getServerSession(
|
||||
context.req,
|
||||
context.res,
|
||||
authOptions
|
||||
);
|
||||
|
||||
if (!session) {
|
||||
return {
|
||||
redirect: {
|
||||
destination: '/login',
|
||||
permanent: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
//Var
|
||||
const session = await getServerSession(context.req, context.res, authOptions);
|
||||
|
||||
if (!session) {
|
||||
return {
|
||||
props: {},
|
||||
redirect: {
|
||||
destination: '/login',
|
||||
permanent: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
props: {},
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,63 +6,59 @@ import { getServerSession } from 'next-auth/next';
|
|||
import StorageUsedChartBar from '../../Containers/Monitoring/StorageUsedChartBar/StorageUsedChartBar';
|
||||
|
||||
export default function Monitoring() {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>Monitoring - BorgWarehouse</title>
|
||||
</Head>
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>Monitoring - BorgWarehouse</title>
|
||||
</Head>
|
||||
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
//justifyContent: 'center',
|
||||
width: '80%',
|
||||
height: '100%',
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<h1
|
||||
style={{
|
||||
color: '#494b7a',
|
||||
textAlign: 'center',
|
||||
marginTop: '5%',
|
||||
marginBottom: '10%',
|
||||
}}
|
||||
>
|
||||
📊 Storage used
|
||||
</h1>
|
||||
<div
|
||||
style={{
|
||||
margin: 'auto',
|
||||
}}
|
||||
>
|
||||
<StorageUsedChartBar />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
//justifyContent: 'center',
|
||||
width: '80%',
|
||||
height: '100%',
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<h1
|
||||
style={{
|
||||
color: '#494b7a',
|
||||
textAlign: 'center',
|
||||
marginTop: '5%',
|
||||
marginBottom: '10%',
|
||||
}}
|
||||
>
|
||||
📊 Storage used
|
||||
</h1>
|
||||
<div
|
||||
style={{
|
||||
margin: 'auto',
|
||||
}}
|
||||
>
|
||||
<StorageUsedChartBar />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export async function getServerSideProps(context) {
|
||||
//Var
|
||||
const session = await getServerSession(
|
||||
context.req,
|
||||
context.res,
|
||||
authOptions
|
||||
);
|
||||
|
||||
if (!session) {
|
||||
return {
|
||||
redirect: {
|
||||
destination: '/login',
|
||||
permanent: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
//Var
|
||||
const session = await getServerSession(context.req, context.res, authOptions);
|
||||
|
||||
if (!session) {
|
||||
return {
|
||||
props: {},
|
||||
redirect: {
|
||||
destination: '/login',
|
||||
permanent: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
props: {},
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,38 +6,34 @@ import { authOptions } from '../../pages/api/auth/[...nextauth]';
|
|||
import { getServerSession } from 'next-auth/next';
|
||||
|
||||
export default function SetupWizardStep() {
|
||||
////Var
|
||||
const router = useRouter();
|
||||
const step = router.query.slug;
|
||||
////Var
|
||||
const router = useRouter();
|
||||
const step = router.query.slug;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>Setup Wizard - BorgWarehouse</title>
|
||||
</Head>
|
||||
<SetupWizard step={step} />
|
||||
</>
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>Setup Wizard - BorgWarehouse</title>
|
||||
</Head>
|
||||
<SetupWizard step={step} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export async function getServerSideProps(context) {
|
||||
//Var
|
||||
const session = await getServerSession(
|
||||
context.req,
|
||||
context.res,
|
||||
authOptions
|
||||
);
|
||||
|
||||
if (!session) {
|
||||
return {
|
||||
redirect: {
|
||||
destination: '/login',
|
||||
permanent: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
//Var
|
||||
const session = await getServerSession(context.req, context.res, authOptions);
|
||||
|
||||
if (!session) {
|
||||
return {
|
||||
props: {},
|
||||
redirect: {
|
||||
destination: '/login',
|
||||
permanent: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
props: {},
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,213 +1,212 @@
|
|||
* {
|
||||
box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
src: url('/font/Inter/Inter-VariableFont_slnt,wght.ttf') format('truetype');
|
||||
font-family: 'Inter';
|
||||
src: url('/font/Inter/Inter-VariableFont_slnt,wght.ttf') format('truetype');
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: Inter, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
background-color: #fafafa;
|
||||
margin: 0;
|
||||
font-family: Inter, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
/* Disable scrollbar for chrome */
|
||||
::-webkit-scrollbar {
|
||||
display: none;
|
||||
display: none;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||
monospace;
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace;
|
||||
}
|
||||
|
||||
.defaultButton {
|
||||
border: 0;
|
||||
padding: 10px 15px;
|
||||
background-color: #6d4aff;
|
||||
color: white;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
font-size: 1em;
|
||||
border: 0;
|
||||
padding: 10px 15px;
|
||||
background-color: #6d4aff;
|
||||
color: white;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.defaultButton:hover {
|
||||
border: 0;
|
||||
padding: 10px 15px;
|
||||
background-color: #4f31ce;
|
||||
color: white;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
font-size: 1em;
|
||||
border: 0;
|
||||
padding: 10px 15px;
|
||||
background-color: #4f31ce;
|
||||
color: white;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.defaultButton:active {
|
||||
border: 0;
|
||||
padding: 10px 15px;
|
||||
background-color: #4f31ce;
|
||||
color: white;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
font-size: 1em;
|
||||
transform: scale(0.9);
|
||||
border: 0;
|
||||
padding: 10px 15px;
|
||||
background-color: #4f31ce;
|
||||
color: white;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
font-size: 1em;
|
||||
transform: scale(0.9);
|
||||
}
|
||||
|
||||
.defaultButton:disabled {
|
||||
opacity: 0.3;
|
||||
cursor: not-allowed;
|
||||
pointer-events: none;
|
||||
opacity: 0.3;
|
||||
cursor: not-allowed;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* signIn */
|
||||
|
||||
.signInContainer {
|
||||
display: 'flex';
|
||||
flex-direction: 'column';
|
||||
justify-content: 'center';
|
||||
align-items: 'center';
|
||||
height: calc(100vh - 100px);
|
||||
animation: signInAnim 1s ease 0s 1 normal forwards;
|
||||
display: 'flex';
|
||||
flex-direction: 'column';
|
||||
justify-content: 'center';
|
||||
align-items: 'center';
|
||||
height: calc(100vh - 100px);
|
||||
animation: signInAnim 1s ease 0s 1 normal forwards;
|
||||
}
|
||||
|
||||
@keyframes signInAnim {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateY(-250px);
|
||||
}
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateY(-250px);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.signInInput {
|
||||
background: #262e49;
|
||||
background-color: #262e49;
|
||||
border: 1px solid #6d4aff21;
|
||||
font-size: 16px;
|
||||
height: auto;
|
||||
margin: 0;
|
||||
margin-bottom: 0px;
|
||||
outline: 0;
|
||||
padding: 15px;
|
||||
width: 100%;
|
||||
border-radius: 5px;
|
||||
color: #d6d6d6;
|
||||
box-shadow: 0 1px 0 rgba(0, 0, 0, 0.03) inset;
|
||||
background: #262e49;
|
||||
background-color: #262e49;
|
||||
border: 1px solid #6d4aff21;
|
||||
font-size: 16px;
|
||||
height: auto;
|
||||
margin: 0;
|
||||
margin-bottom: 0px;
|
||||
outline: 0;
|
||||
padding: 15px;
|
||||
width: 100%;
|
||||
border-radius: 5px;
|
||||
color: #d6d6d6;
|
||||
box-shadow: 0 1px 0 rgba(0, 0, 0, 0.03) inset;
|
||||
}
|
||||
|
||||
.signInInput:focus {
|
||||
outline: 1px solid #6d4aff;
|
||||
box-shadow: 0 0 10px 3px rgba(110, 74, 255, 0.605);
|
||||
outline: 1px solid #6d4aff;
|
||||
box-shadow: 0 0 10px 3px rgba(110, 74, 255, 0.605);
|
||||
}
|
||||
|
||||
.signInInput.invalid {
|
||||
background: #f3c7c7;
|
||||
border: 1px solid #e45454;
|
||||
outline: 1px solid #ff4a4a;
|
||||
background: #f3c7c7;
|
||||
border: 1px solid #e45454;
|
||||
outline: 1px solid #ff4a4a;
|
||||
}
|
||||
|
||||
.signInInput.invalid:focus {
|
||||
background: #f3c7c7;
|
||||
border: 1px solid #e45454;
|
||||
outline: 1px solid #ff4a4a;
|
||||
box-shadow: 0 0 10px 3px rgba(255, 74, 74, 0.605);
|
||||
background: #f3c7c7;
|
||||
border: 1px solid #e45454;
|
||||
outline: 1px solid #ff4a4a;
|
||||
box-shadow: 0 0 10px 3px rgba(255, 74, 74, 0.605);
|
||||
}
|
||||
|
||||
.signInButton {
|
||||
border: 0;
|
||||
padding: 10px 15px;
|
||||
background-color: #6d4aff;
|
||||
color: #dfdeee;
|
||||
margin: 5px;
|
||||
border-radius: 100px;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
font-size: 1em;
|
||||
border: 0;
|
||||
padding: 10px 15px;
|
||||
background-color: #6d4aff;
|
||||
color: #dfdeee;
|
||||
margin: 5px;
|
||||
border-radius: 100px;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.signInButton:hover {
|
||||
border: 0;
|
||||
padding: 10px 15px;
|
||||
background-color: #4f31ce;
|
||||
color: #dfdeee;
|
||||
margin: 5px;
|
||||
border-radius: 100px;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
font-size: 1em;
|
||||
border: 0;
|
||||
padding: 10px 15px;
|
||||
background-color: #4f31ce;
|
||||
color: #dfdeee;
|
||||
margin: 5px;
|
||||
border-radius: 100px;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.signInButton:active {
|
||||
border: 0;
|
||||
padding: 10px 15px;
|
||||
background-color: #4f31ce;
|
||||
color: #dfdeee;
|
||||
margin: 5px;
|
||||
border-radius: 100px;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
font-size: 1em;
|
||||
transform: scale(0.9);
|
||||
border: 0;
|
||||
padding: 10px 15px;
|
||||
background-color: #4f31ce;
|
||||
color: #dfdeee;
|
||||
margin: 5px;
|
||||
border-radius: 100px;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
font-size: 1em;
|
||||
transform: scale(0.9);
|
||||
}
|
||||
|
||||
.signInButton:disabled {
|
||||
opacity: 0.3;
|
||||
cursor: not-allowed;
|
||||
pointer-events: none;
|
||||
opacity: 0.3;
|
||||
cursor: not-allowed;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.heart {
|
||||
color: #6d4aff;
|
||||
color: #6d4aff;
|
||||
}
|
||||
.heart::before {
|
||||
content: '\f004';
|
||||
content: '\f004';
|
||||
}
|
||||
|
||||
/* Radio group and radio button */
|
||||
.radio-group {
|
||||
display: flex;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.radio-group input[type='radio']:checked + span::before {
|
||||
background-color: #6d4aff;
|
||||
box-shadow: inset white 0 0 0 2px;
|
||||
background-color: #6d4aff;
|
||||
box-shadow: inset white 0 0 0 2px;
|
||||
}
|
||||
.radio-group span::before {
|
||||
border: 2px solid #6d4aff;
|
||||
content: '';
|
||||
display: flex;
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
margin: 0 5px;
|
||||
border-radius: 50%;
|
||||
box-sizing: border-box;
|
||||
transition: all ease 0.2s;
|
||||
box-shadow: inset white 0 0 0 10px;
|
||||
border: 2px solid #6d4aff;
|
||||
content: '';
|
||||
display: flex;
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
margin: 0 5px;
|
||||
border-radius: 50%;
|
||||
box-sizing: border-box;
|
||||
transition: all ease 0.2s;
|
||||
box-shadow: inset white 0 0 0 10px;
|
||||
}
|
||||
|
||||
.radio-group label {
|
||||
margin: 0 8px 0 0;
|
||||
cursor: pointer;
|
||||
margin: 0 8px 0 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.radio-group input[type='radio'] {
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.radio-group span {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue