mirror of
https://github.com/Ravinou/borgwarehouse
synced 2026-03-14 14:25:46 +01:00
refactor: ⚡ component repoList
This commit is contained in:
parent
b32318ccc7
commit
d66e7a2263
4 changed files with 96 additions and 87 deletions
|
|
@ -4,10 +4,12 @@ import { useState } from 'react';
|
|||
import classes from './QuickCommands.module.css';
|
||||
import { IconSettingsAutomation, IconCopy } from '@tabler/icons-react';
|
||||
import lanCommandOption from '../../../helpers/functions/lanCommandOption';
|
||||
import { Optional } from '~/types';
|
||||
import { WizardEnvType } from '~/domain/config.types';
|
||||
|
||||
type QuickCommandsProps = {
|
||||
repositoryName: string;
|
||||
wizardEnv: any;
|
||||
wizardEnv: Optional<WizardEnvType>;
|
||||
lanCommand: boolean;
|
||||
};
|
||||
|
||||
|
|
@ -24,7 +26,9 @@ export default function QuickCommands(props: QuickCommandsProps) {
|
|||
const handleCopy = async () => {
|
||||
// Asynchronously call copy to clipboard
|
||||
navigator.clipboard
|
||||
.writeText(`ssh://${wizardEnv.UNIX_USER}@${FQDN}${SSH_SERVER_PORT}/./${props.repositoryName}`)
|
||||
.writeText(
|
||||
`ssh://${wizardEnv?.UNIX_USER}@${FQDN}${SSH_SERVER_PORT}/./${props.repositoryName}`
|
||||
)
|
||||
.then(() => {
|
||||
// If successful, update the isCopied state value
|
||||
setIsCopied(true);
|
||||
|
|
@ -43,7 +47,7 @@ export default function QuickCommands(props: QuickCommandsProps) {
|
|||
<div className={classes.copyValid}>Copied !</div>
|
||||
) : (
|
||||
<div className={classes.tooltip}>
|
||||
ssh://{wizardEnv.UNIX_USER}@{FQDN}
|
||||
ssh://{wizardEnv?.UNIX_USER}@{FQDN}
|
||||
{SSH_SERVER_PORT}/./
|
||||
{props.repositoryName}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -12,11 +12,12 @@ import {
|
|||
import { timestampConverter } from '~/helpers/functions/timestampConverter';
|
||||
import StorageBar from '../UI/StorageBar/StorageBar';
|
||||
import QuickCommands from './QuickCommands/QuickCommands';
|
||||
import { Repository } from '~/domain/config.types';
|
||||
import { Repository, WizardEnvType } from '~/domain/config.types';
|
||||
import { Optional } from '~/types';
|
||||
|
||||
type RepoProps = Repository & {
|
||||
type RepoProps = Omit<Repository, 'unixUser' | 'displayDetails'> & {
|
||||
repoManageEditHandler: () => void;
|
||||
wizardEnv: string;
|
||||
wizardEnv: Optional<WizardEnvType>;
|
||||
};
|
||||
|
||||
export default function Repo(props: RepoProps) {
|
||||
|
|
|
|||
|
|
@ -5,19 +5,21 @@ import { IconPlus } from '@tabler/icons-react';
|
|||
import { useRouter } from 'next/router';
|
||||
import Link from 'next/link';
|
||||
import useSWR, { useSWRConfig } from 'swr';
|
||||
import { ToastContainer, toast } from 'react-toastify';
|
||||
import { ToastContainer, ToastOptions, toast } from 'react-toastify';
|
||||
import 'react-toastify/dist/ReactToastify.css';
|
||||
|
||||
//Composants
|
||||
import Repo from '../../Components/Repo/Repo';
|
||||
import RepoManage from '../RepoManage/RepoManage';
|
||||
import ShimmerRepoList from '../../Components/UI/ShimmerRepoList/ShimmerRepoList';
|
||||
import { Repository, WizardEnvType } from '~/domain/config.types';
|
||||
import { Optional } from '~/types';
|
||||
|
||||
export default function RepoList() {
|
||||
////Var
|
||||
const router = useRouter();
|
||||
const { mutate } = useSWRConfig();
|
||||
const toastOptions = {
|
||||
const toastOptions: ToastOptions = {
|
||||
position: 'top-right',
|
||||
autoClose: 8000,
|
||||
hideProgressBar: false,
|
||||
|
|
@ -29,7 +31,7 @@ export default function RepoList() {
|
|||
|
||||
////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 fetcher = async (url: string) => await fetch(url).then((res) => res.json());
|
||||
const { data, error } = useSWR('/api/repo', fetcher);
|
||||
|
||||
////LifeCycle
|
||||
|
|
@ -63,7 +65,7 @@ export default function RepoList() {
|
|||
////States
|
||||
const [displayRepoAdd, setDisplayRepoAdd] = useState(false);
|
||||
const [displayRepoEdit, setDisplayRepoEdit] = useState(false);
|
||||
const [wizardEnv, setWizardEnv] = useState({});
|
||||
const [wizardEnv, setWizardEnv] = useState<Optional<WizardEnvType>>();
|
||||
|
||||
////Functions
|
||||
|
||||
|
|
@ -88,7 +90,7 @@ export default function RepoList() {
|
|||
};
|
||||
|
||||
//BUTTON : Display RepoManage component box for EDIT
|
||||
const repoManageEditHandler = (id: number) => {
|
||||
const manageRepoEditHandler = (id: number) => {
|
||||
router.replace('/manage-repo/edit/' + id);
|
||||
};
|
||||
|
||||
|
|
@ -107,7 +109,7 @@ export default function RepoList() {
|
|||
};
|
||||
|
||||
//Dynamic list of repositories (with a map of Repo components)
|
||||
const renderRepoList = data.repoList.map((repo, index) => {
|
||||
const renderRepoList = data.repoList.map((repo: Repository) => {
|
||||
return (
|
||||
<React.Fragment key={repo.id}>
|
||||
<Repo
|
||||
|
|
@ -124,7 +126,7 @@ export default function RepoList() {
|
|||
comment={repo.comment}
|
||||
lanCommand={repo.lanCommand}
|
||||
appendOnlyMode={repo.appendOnlyMode}
|
||||
repoManageEditHandler={() => repoManageEditHandler(repo.id)}
|
||||
repoManageEditHandler={() => manageRepoEditHandler(repo.id)}
|
||||
wizardEnv={wizardEnv}
|
||||
></Repo>
|
||||
</React.Fragment>
|
||||
|
|
@ -148,12 +150,12 @@ export default function RepoList() {
|
|||
<div className={classes.RepoList}>{renderRepoList}</div>
|
||||
</div>
|
||||
</div>
|
||||
{displayRepoAdd ? (
|
||||
{displayRepoAdd && (
|
||||
<RepoManage mode='add' repoList={data.repoList} closeHandler={closeRepoManageBoxHandler} />
|
||||
) : null}
|
||||
{displayRepoEdit ? (
|
||||
)}
|
||||
{displayRepoEdit && (
|
||||
<RepoManage mode='edit' repoList={data.repoList} closeHandler={closeRepoManageBoxHandler} />
|
||||
) : null}
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import classes from './RepoManage.module.css';
|
|||
import { IconAlertCircle, IconX } from '@tabler/icons-react';
|
||||
import { useState } from 'react';
|
||||
import { useRouter } from 'next/router';
|
||||
import { toast } from 'react-toastify';
|
||||
import { toast, ToastOptions } from 'react-toastify';
|
||||
import 'react-toastify/dist/ReactToastify.css';
|
||||
import { useForm, Controller } from 'react-hook-form';
|
||||
import { SpinnerDotted } from 'spinners-react';
|
||||
|
|
@ -11,19 +11,41 @@ import Select from 'react-select';
|
|||
import Link from 'next/link';
|
||||
import { IconExternalLink } from '@tabler/icons-react';
|
||||
import { alertOptions } from '../../domain/constants';
|
||||
import { Repository } from '~/domain/config.types';
|
||||
import { Optional } from '~/types';
|
||||
|
||||
export default function RepoManage(props) {
|
||||
type RepoManageProps = {
|
||||
mode: 'add' | 'edit';
|
||||
repoList: Optional<Array<Repository>>;
|
||||
closeHandler: () => void;
|
||||
};
|
||||
|
||||
type DataForm = {
|
||||
alias: string;
|
||||
storageSize: string;
|
||||
sshkey: string;
|
||||
comment: string;
|
||||
alert: { value: Optional<number>; label: string };
|
||||
lanCommand: boolean;
|
||||
appendOnlyMode: boolean;
|
||||
};
|
||||
|
||||
export default function RepoManage(props: RepoManageProps) {
|
||||
////Var
|
||||
let targetRepo;
|
||||
const router = useRouter();
|
||||
const targetRepo =
|
||||
props.mode === 'edit' && router.query.slug
|
||||
? props.repoList?.find((repo) => repo.id.toString() === router.query.slug)
|
||||
: undefined;
|
||||
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
control,
|
||||
formState: { errors, isSubmitting, isValid },
|
||||
} = useForm({ mode: 'onChange' });
|
||||
} = useForm<DataForm>({ mode: 'onChange' });
|
||||
|
||||
const toastOptions = {
|
||||
const toastOptions: ToastOptions = {
|
||||
position: 'top-right',
|
||||
autoClose: 5000,
|
||||
hideProgressBar: false,
|
||||
|
|
@ -40,18 +62,11 @@ export default function RepoManage(props) {
|
|||
////Functions
|
||||
//router.query.slug is undefined for few milliseconds on first render for a direct URL access (https://github.com/vercel/next.js/discussions/11484).
|
||||
//If I call repoManage with edit mode (props), i'm firstly waiting that router.query.slug being available before rendering.
|
||||
if (!router.query.slug && props.mode == 'edit') {
|
||||
return <SpinnerDotted size={30} thickness={100} speed={180} color='rgba(109, 74, 255, 1)' />;
|
||||
} else if (props.mode == 'edit') {
|
||||
for (let element in props.repoList) {
|
||||
if (props.repoList[element].id == router.query.slug) {
|
||||
targetRepo = props.repoList[element];
|
||||
}
|
||||
}
|
||||
//If the ID does not exist > 404
|
||||
if (!targetRepo) {
|
||||
if (props.mode === 'edit') {
|
||||
if (!router.query.slug) {
|
||||
return <SpinnerDotted size={30} thickness={100} speed={180} color='rgba(109, 74, 255, 1)' />;
|
||||
} else if (!targetRepo) {
|
||||
router.push('/404');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -89,48 +104,39 @@ export default function RepoManage(props) {
|
|||
});
|
||||
};
|
||||
|
||||
//Verify that the SSH key is unique
|
||||
const isSSHKeyUnique = async (sshPublicKey) => {
|
||||
let isUnique = true;
|
||||
const isSSHKeyUnique = async (sshPublicKey: string): Promise<boolean> => {
|
||||
try {
|
||||
// Extract the first two columns of the SSH key in the form
|
||||
const publicKeyPrefix = sshPublicKey.split(' ').slice(0, 2).join(' ');
|
||||
|
||||
// Extract the first two columns of the SSH key in the form
|
||||
const publicKeyPrefix = sshPublicKey.split(' ').slice(0, 2).join(' ');
|
||||
const response = await fetch('/api/repo', { method: 'GET' });
|
||||
const data = await response.json();
|
||||
|
||||
await fetch('/api/repo', { method: 'GET' })
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
for (let element in data.repoList) {
|
||||
// Extract the first two columns of the SSH key in the repoList
|
||||
const repoPublicKeyPrefix = data.repoList[element].sshPublicKey
|
||||
.split(' ')
|
||||
.slice(0, 2)
|
||||
.join(' ');
|
||||
|
||||
if (
|
||||
repoPublicKeyPrefix === publicKeyPrefix && // Compare the first two columns of the SSH key
|
||||
(!targetRepo || data.repoList[element].id != targetRepo.id)
|
||||
) {
|
||||
toast.error(
|
||||
'The SSH key is already used in repository #' +
|
||||
data.repoList[element].id +
|
||||
'. Please use another key or delete the key from the other repository.',
|
||||
toastOptions
|
||||
);
|
||||
isUnique = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error);
|
||||
toast.error('An error has occurred', toastOptions);
|
||||
isUnique = false;
|
||||
const conflictingRepo = data.repoList.find((repo: { sshPublicKey: string; id: number }) => {
|
||||
const repoPublicKeyPrefix = repo.sshPublicKey.split(' ').slice(0, 2).join(' ');
|
||||
return (
|
||||
repoPublicKeyPrefix === publicKeyPrefix && (!targetRepo || repo.id !== targetRepo.id)
|
||||
);
|
||||
});
|
||||
return isUnique;
|
||||
|
||||
if (conflictingRepo) {
|
||||
toast.error(
|
||||
`The SSH key is already used in repository #${conflictingRepo.id}. Please use another key or delete the key from the other repository.`,
|
||||
toastOptions
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
toast.error('An error has occurred', toastOptions);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
//Form submit Handler for ADD or EDIT a repo
|
||||
const formSubmitHandler = async (dataForm) => {
|
||||
const formSubmitHandler = async (dataForm: DataForm) => {
|
||||
//Loading button on submit to avoid multiple send.
|
||||
setIsLoading(true);
|
||||
//Verify that the SSH key is unique
|
||||
|
|
@ -194,7 +200,7 @@ export default function RepoManage(props) {
|
|||
.then(async (response) => {
|
||||
if (response.ok) {
|
||||
toast.success(
|
||||
'The repository #' + targetRepo.id + ' has been successfully edited !',
|
||||
'The repository #' + targetRepo?.id + ' has been successfully edited !',
|
||||
toastOptions
|
||||
);
|
||||
router.replace('/');
|
||||
|
|
@ -231,14 +237,14 @@ export default function RepoManage(props) {
|
|||
color: 'rgba(99, 115, 129, 0.38)',
|
||||
}}
|
||||
>
|
||||
#{targetRepo.id}
|
||||
#{targetRepo?.id}
|
||||
</span>{' '}
|
||||
?
|
||||
</h1>
|
||||
</div>
|
||||
<div className={classes.deleteDialogMessage}>
|
||||
<div style={{ marginBottom: '5px' }}>
|
||||
You are about to permanently delete the repository <b>#{targetRepo.id}</b> and all
|
||||
You are about to permanently delete the repository <b>#{targetRepo?.id}</b> and all
|
||||
the backups it contains.
|
||||
</div>
|
||||
<div>The data will not be recoverable and it will not be possible to go back.</div>
|
||||
|
|
@ -274,7 +280,7 @@ export default function RepoManage(props) {
|
|||
color: 'rgba(99, 115, 129, 0.38)',
|
||||
}}
|
||||
>
|
||||
#{targetRepo.id}
|
||||
#{targetRepo?.id}
|
||||
</span>
|
||||
</h1>
|
||||
)}
|
||||
|
|
@ -286,7 +292,7 @@ export default function RepoManage(props) {
|
|||
className='form-control is-invalid'
|
||||
placeholder='Alias for the repository, e.g."Server 1"'
|
||||
type='text'
|
||||
defaultValue={props.mode == 'edit' ? targetRepo.alias : null}
|
||||
defaultValue={props.mode == 'edit' ? targetRepo?.alias : undefined}
|
||||
{...register('alias', {
|
||||
required: 'An alias is required.',
|
||||
minLength: {
|
||||
|
|
@ -304,8 +310,7 @@ export default function RepoManage(props) {
|
|||
<label htmlFor='sshkey'>SSH public key</label>
|
||||
<textarea
|
||||
placeholder='Public key in OpenSSH format (rsa, ed25519, ed25519-sk)'
|
||||
type='text'
|
||||
defaultValue={props.mode == 'edit' ? targetRepo.sshPublicKey : null}
|
||||
defaultValue={props.mode == 'edit' ? targetRepo?.sshPublicKey : undefined}
|
||||
{...register('sshkey', {
|
||||
required: 'SSH public key is required.',
|
||||
pattern: {
|
||||
|
|
@ -324,7 +329,7 @@ export default function RepoManage(props) {
|
|||
<input
|
||||
type='number'
|
||||
min='1'
|
||||
defaultValue={props.mode == 'edit' ? targetRepo.storageSize : null}
|
||||
defaultValue={props.mode == 'edit' ? targetRepo?.storageSize : undefined}
|
||||
{...register('storageSize', {
|
||||
required: 'A storage size is required.',
|
||||
})}
|
||||
|
|
@ -335,9 +340,8 @@ export default function RepoManage(props) {
|
|||
{/* COMMENT */}
|
||||
<label htmlFor='comment'>Comment</label>
|
||||
<textarea
|
||||
type='text'
|
||||
placeholder='Little comment for your repository...'
|
||||
defaultValue={props.mode == 'edit' ? targetRepo.comment : null}
|
||||
defaultValue={props.mode == 'edit' ? targetRepo?.comment : undefined}
|
||||
{...register('comment', {
|
||||
required: false,
|
||||
maxLength: {
|
||||
|
|
@ -353,8 +357,7 @@ export default function RepoManage(props) {
|
|||
<div className={classes.optionCommandWrapper}>
|
||||
<input
|
||||
type='checkbox'
|
||||
name='lanCommand'
|
||||
defaultChecked={props.mode == 'edit' ? targetRepo.lanCommand : false}
|
||||
defaultChecked={props.mode == 'edit' ? targetRepo?.lanCommand : false}
|
||||
{...register('lanCommand')}
|
||||
/>
|
||||
<label htmlFor='lanCommand'>Generates commands for use over LAN.</label>
|
||||
|
|
@ -374,8 +377,7 @@ export default function RepoManage(props) {
|
|||
<div className={classes.optionCommandWrapper}>
|
||||
<input
|
||||
type='checkbox'
|
||||
name='appendOnlyMode'
|
||||
defaultChecked={props.mode == 'edit' ? targetRepo.appendOnlyMode : false}
|
||||
defaultChecked={props.mode == 'edit' ? targetRepo?.appendOnlyMode : false}
|
||||
{...register('appendOnlyMode')}
|
||||
/>
|
||||
<label htmlFor='appendOnlyMode'>Enable append-only mode.</label>
|
||||
|
|
@ -400,9 +402,9 @@ export default function RepoManage(props) {
|
|||
name='alert'
|
||||
defaultValue={
|
||||
props.mode == 'edit'
|
||||
? alertOptions.find((x) => x.value === targetRepo.alert) || {
|
||||
value: targetRepo.alert,
|
||||
label: `${targetRepo.alert} seconds (custom)`,
|
||||
? alertOptions.find((x) => x.value === targetRepo?.alert) || {
|
||||
value: targetRepo?.alert,
|
||||
label: `Custom value (${targetRepo?.alert} seconds)`,
|
||||
}
|
||||
: alertOptions[4]
|
||||
}
|
||||
|
|
@ -417,7 +419,7 @@ export default function RepoManage(props) {
|
|||
menuPlacement='top'
|
||||
theme={(theme) => ({
|
||||
...theme,
|
||||
borderRadius: '5px',
|
||||
borderRadius: 5,
|
||||
colors: {
|
||||
...theme.colors,
|
||||
primary25: '#c3b6fa',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue