feat core
react
: Add even more supported Components
This commit is contained in:
parent
3b43f2873a
commit
5d80d70974
16 changed files with 612 additions and 0 deletions
30
blueprint.sh
30
blueprint.sh
|
@ -655,6 +655,21 @@ if [[ ( $2 == "-i" ) || ( $2 == "-install" ) ]]; then VCMD="y"
|
||||||
PLACE_REACT "$Components_Server_Schedules_Edit_BeforeEdit" "Server/Schedules/Edit/BeforeEdit.tsx" "$OldComponents_Server_Schedules_Edit_BeforeEdit"
|
PLACE_REACT "$Components_Server_Schedules_Edit_BeforeEdit" "Server/Schedules/Edit/BeforeEdit.tsx" "$OldComponents_Server_Schedules_Edit_BeforeEdit"
|
||||||
PLACE_REACT "$Components_Server_Schedules_Edit_AfterEdit" "Server/Schedules/Edit/AfterEdit.tsx" "$OldComponents_Server_Schedules_Edit_AfterEdit"
|
PLACE_REACT "$Components_Server_Schedules_Edit_AfterEdit" "Server/Schedules/Edit/AfterEdit.tsx" "$OldComponents_Server_Schedules_Edit_AfterEdit"
|
||||||
|
|
||||||
|
PLACE_REACT "$Components_Server_Users_BeforeContent" "Server/Users/BeforeContent.tsx" "$OldComponents_Server_Users_BeforeContent"
|
||||||
|
PLACE_REACT "$Components_Server_Users_AfterContent" "Server/Users/AfterContent.tsx" "$OldComponents_Server_Users_AfterContent"
|
||||||
|
|
||||||
|
PLACE_REACT "$Components_Server_Backups_BeforeContent" "Server/Backups/BeforeContent.tsx" "$OldComponents_Server_Backups_BeforeContent"
|
||||||
|
PLACE_REACT "$Components_Server_Backups_AfterContent" "Server/Backups/AfterContent.tsx" "$OldComponents_Server_Backups_AfterContent"
|
||||||
|
|
||||||
|
PLACE_REACT "$Components_Server_Network_BeforeContent" "Server/Network/BeforeContent.tsx" "$OldComponents_Server_Network_BeforeContent"
|
||||||
|
PLACE_REACT "$Components_Server_Network_AfterContent" "Server/Network/AfterContent.tsx" "$OldComponents_Server_Network_AfterContent"
|
||||||
|
|
||||||
|
PLACE_REACT "$Components_Server_Startup_BeforeContent" "Server/Startup/BeforeContent.tsx" "$OldComponents_Server_Startup_BeforeContent"
|
||||||
|
PLACE_REACT "$Components_Server_Startup_AfterContent" "Server/Startup/AfterContent.tsx" "$OldComponents_Server_Startup_AfterContent"
|
||||||
|
|
||||||
|
PLACE_REACT "$Components_Server_Settings_BeforeContent" "Server/Settings/BeforeContent.tsx" "$OldComponents_Server_Settings_BeforeContent"
|
||||||
|
PLACE_REACT "$Components_Server_Settings_AfterContent" "Server/Settings/AfterContent.tsx" "$OldComponents_Server_Settings_AfterContent"
|
||||||
|
|
||||||
else
|
else
|
||||||
# warn about missing components.yml file
|
# warn about missing components.yml file
|
||||||
log_yellow "[WARNING] Could not find '$dashboard_components/Components.yml', component extendability might be limited."
|
log_yellow "[WARNING] Could not find '$dashboard_components/Components.yml', component extendability might be limited."
|
||||||
|
@ -1085,6 +1100,21 @@ if [[ ( $2 == "-r" ) || ( $2 == "-remove" ) ]]; then VCMD="y"
|
||||||
REMOVE_REACT "$Components_Server_Schedules_Edit_BeforeEdit" "Server/Schedules/Edit/BeforeEdit.tsx"
|
REMOVE_REACT "$Components_Server_Schedules_Edit_BeforeEdit" "Server/Schedules/Edit/BeforeEdit.tsx"
|
||||||
REMOVE_REACT "$Components_Server_Schedules_Edit_AfterEdit" "Server/Schedules/Edit/AfterEdit.tsx"
|
REMOVE_REACT "$Components_Server_Schedules_Edit_AfterEdit" "Server/Schedules/Edit/AfterEdit.tsx"
|
||||||
|
|
||||||
|
REMOVE_REACT "$Components_Server_Users_BeforeContent" "Server/Users/BeforeContent.tsx"
|
||||||
|
REMOVE_REACT "$Components_Server_Users_AfterContent" "Server/Users/AfterContent.tsx"
|
||||||
|
|
||||||
|
REMOVE_REACT "$Components_Server_Backups_BeforeContent" "Server/Backups/BeforeContent.tsx"
|
||||||
|
REMOVE_REACT "$Components_Server_Backups_AfterContent" "Server/Backups/AfterContent.tsx"
|
||||||
|
|
||||||
|
REMOVE_REACT "$Components_Server_Network_BeforeContent" "Server/Network/BeforeContent.tsx"
|
||||||
|
REMOVE_REACT "$Components_Server_Network_AfterContent" "Server/Network/AfterContent.tsx"
|
||||||
|
|
||||||
|
REMOVE_REACT "$Components_Server_Startup_BeforeContent" "Server/Startup/BeforeContent.tsx"
|
||||||
|
REMOVE_REACT "$Components_Server_Startup_AfterContent" "Server/Startup/AfterContent.tsx"
|
||||||
|
|
||||||
|
REMOVE_REACT "$Components_Server_Settings_BeforeContent" "Server/Settings/BeforeContent.tsx"
|
||||||
|
REMOVE_REACT "$Components_Server_Settings_AfterContent" "Server/Settings/AfterContent.tsx"
|
||||||
|
|
||||||
YARN="y"
|
YARN="y"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
import React from 'react';
|
||||||
|
/* blueprint/import */
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{/* blueprint/react */}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
|
@ -0,0 +1,10 @@
|
||||||
|
import React from 'react';
|
||||||
|
/* blueprint/import */
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{/* blueprint/react */}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
|
@ -0,0 +1,10 @@
|
||||||
|
import React from 'react';
|
||||||
|
/* blueprint/import */
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{/* blueprint/react */}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
|
@ -0,0 +1,10 @@
|
||||||
|
import React from 'react';
|
||||||
|
/* blueprint/import */
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{/* blueprint/react */}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
|
@ -0,0 +1,10 @@
|
||||||
|
import React from 'react';
|
||||||
|
/* blueprint/import */
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{/* blueprint/react */}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
|
@ -0,0 +1,10 @@
|
||||||
|
import React from 'react';
|
||||||
|
/* blueprint/import */
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{/* blueprint/react */}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
|
@ -0,0 +1,10 @@
|
||||||
|
import React from 'react';
|
||||||
|
/* blueprint/import */
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{/* blueprint/react */}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
|
@ -0,0 +1,10 @@
|
||||||
|
import React from 'react';
|
||||||
|
/* blueprint/import */
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{/* blueprint/react */}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
|
@ -0,0 +1,10 @@
|
||||||
|
import React from 'react';
|
||||||
|
/* blueprint/import */
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{/* blueprint/react */}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
|
@ -0,0 +1,10 @@
|
||||||
|
import React from 'react';
|
||||||
|
/* blueprint/import */
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{/* blueprint/react */}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
|
@ -0,0 +1,90 @@
|
||||||
|
import React, { useContext, useEffect, useState } from 'react';
|
||||||
|
import Spinner from '@/components/elements/Spinner';
|
||||||
|
import useFlash from '@/plugins/useFlash';
|
||||||
|
import Can from '@/components/elements/Can';
|
||||||
|
import CreateBackupButton from '@/components/server/backups/CreateBackupButton';
|
||||||
|
import FlashMessageRender from '@/components/FlashMessageRender';
|
||||||
|
import BackupRow from '@/components/server/backups/BackupRow';
|
||||||
|
import tw from 'twin.macro';
|
||||||
|
import getServerBackups, { Context as ServerBackupContext } from '@/api/swr/getServerBackups';
|
||||||
|
import { ServerContext } from '@/state/server';
|
||||||
|
import ServerContentBlock from '@/components/elements/ServerContentBlock';
|
||||||
|
import Pagination from '@/components/elements/Pagination';
|
||||||
|
|
||||||
|
import BeforeContent from '@/blueprint/components/Server/Backups/BeforeContent';
|
||||||
|
import AfterContent from '@/blueprint/components/Server/Backups/AfterContent';
|
||||||
|
|
||||||
|
const BackupContainer = () => {
|
||||||
|
const { page, setPage } = useContext(ServerBackupContext);
|
||||||
|
const { clearFlashes, clearAndAddHttpError } = useFlash();
|
||||||
|
const { data: backups, error, isValidating } = getServerBackups();
|
||||||
|
|
||||||
|
const backupLimit = ServerContext.useStoreState((state) => state.server.data!.featureLimits.backups);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!error) {
|
||||||
|
clearFlashes('backups');
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
clearAndAddHttpError({ error, key: 'backups' });
|
||||||
|
}, [error]);
|
||||||
|
|
||||||
|
if (!backups || (error && isValidating)) {
|
||||||
|
return <Spinner size={'large'} centered />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ServerContentBlock title={'Backups'}>
|
||||||
|
<FlashMessageRender byKey={'backups'} css={tw`mb-4`} />
|
||||||
|
<BeforeContent />
|
||||||
|
<Pagination data={backups} onPageSelect={setPage}>
|
||||||
|
{({ items }) =>
|
||||||
|
!items.length ? (
|
||||||
|
// Don't show any error messages if the server has no backups and the user cannot
|
||||||
|
// create additional ones for the server.
|
||||||
|
!backupLimit ? null : (
|
||||||
|
<p css={tw`text-center text-sm text-neutral-300`}>
|
||||||
|
{page > 1
|
||||||
|
? "Looks like we've run out of backups to show you, try going back a page."
|
||||||
|
: 'It looks like there are no backups currently stored for this server.'}
|
||||||
|
</p>
|
||||||
|
)
|
||||||
|
) : (
|
||||||
|
items.map((backup, index) => (
|
||||||
|
<BackupRow key={backup.uuid} backup={backup} css={index > 0 ? tw`mt-2` : undefined} />
|
||||||
|
))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</Pagination>
|
||||||
|
{backupLimit === 0 && (
|
||||||
|
<p css={tw`text-center text-sm text-neutral-300`}>
|
||||||
|
Backups cannot be created for this server because the backup limit is set to 0.
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
<Can action={'backup.create'}>
|
||||||
|
<div css={tw`mt-6 sm:flex items-center justify-end`}>
|
||||||
|
{backupLimit > 0 && backups.backupCount > 0 && (
|
||||||
|
<p css={tw`text-sm text-neutral-300 mb-4 sm:mr-6 sm:mb-0`}>
|
||||||
|
{backups.backupCount} of {backupLimit} backups have been created for this server.
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
{backupLimit > 0 && backupLimit > backups.backupCount && (
|
||||||
|
<CreateBackupButton css={tw`w-full sm:w-auto`} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Can>
|
||||||
|
<AfterContent />
|
||||||
|
</ServerContentBlock>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
const [page, setPage] = useState<number>(1);
|
||||||
|
return (
|
||||||
|
<ServerBackupContext.Provider value={{ page, setPage }}>
|
||||||
|
<BackupContainer />
|
||||||
|
</ServerBackupContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
|
@ -0,0 +1,89 @@
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import Spinner from '@/components/elements/Spinner';
|
||||||
|
import { useFlashKey } from '@/plugins/useFlash';
|
||||||
|
import ServerContentBlock from '@/components/elements/ServerContentBlock';
|
||||||
|
import { ServerContext } from '@/state/server';
|
||||||
|
import AllocationRow from '@/components/server/network/AllocationRow';
|
||||||
|
import Button from '@/components/elements/Button';
|
||||||
|
import createServerAllocation from '@/api/server/network/createServerAllocation';
|
||||||
|
import tw from 'twin.macro';
|
||||||
|
import Can from '@/components/elements/Can';
|
||||||
|
import SpinnerOverlay from '@/components/elements/SpinnerOverlay';
|
||||||
|
import getServerAllocations from '@/api/swr/getServerAllocations';
|
||||||
|
import isEqual from 'react-fast-compare';
|
||||||
|
import { useDeepCompareEffect } from '@/plugins/useDeepCompareEffect';
|
||||||
|
|
||||||
|
import BeforeContent from '@/blueprint/components/Server/Network/BeforeContent';
|
||||||
|
import AfterContent from '@/blueprint/components/Server/Network/AfterContent';
|
||||||
|
|
||||||
|
const NetworkContainer = () => {
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid);
|
||||||
|
const allocationLimit = ServerContext.useStoreState((state) => state.server.data!.featureLimits.allocations);
|
||||||
|
const allocations = ServerContext.useStoreState((state) => state.server.data!.allocations, isEqual);
|
||||||
|
const setServerFromState = ServerContext.useStoreActions((actions) => actions.server.setServerFromState);
|
||||||
|
|
||||||
|
const { clearFlashes, clearAndAddHttpError } = useFlashKey('server:network');
|
||||||
|
const { data, error, mutate } = getServerAllocations();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
mutate(allocations);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
clearAndAddHttpError(error);
|
||||||
|
}, [error]);
|
||||||
|
|
||||||
|
useDeepCompareEffect(() => {
|
||||||
|
if (!data) return;
|
||||||
|
|
||||||
|
setServerFromState((state) => ({ ...state, allocations: data }));
|
||||||
|
}, [data]);
|
||||||
|
|
||||||
|
const onCreateAllocation = () => {
|
||||||
|
clearFlashes();
|
||||||
|
|
||||||
|
setLoading(true);
|
||||||
|
createServerAllocation(uuid)
|
||||||
|
.then((allocation) => {
|
||||||
|
setServerFromState((s) => ({ ...s, allocations: s.allocations.concat(allocation) }));
|
||||||
|
return mutate(data?.concat(allocation), false);
|
||||||
|
})
|
||||||
|
.catch((error) => clearAndAddHttpError(error))
|
||||||
|
.then(() => setLoading(false));
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ServerContentBlock showFlashKey={'server:network'} title={'Network'}>
|
||||||
|
{!data ? (
|
||||||
|
<Spinner size={'large'} centered />
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<BeforeContent />
|
||||||
|
{data.map((allocation) => (
|
||||||
|
<AllocationRow key={`${allocation.ip}:${allocation.port}`} allocation={allocation} />
|
||||||
|
))}
|
||||||
|
{allocationLimit > 0 && (
|
||||||
|
<Can action={'allocation.create'}>
|
||||||
|
<SpinnerOverlay visible={loading} />
|
||||||
|
<div css={tw`mt-6 sm:flex items-center justify-end`}>
|
||||||
|
<p css={tw`text-sm text-neutral-300 mb-4 sm:mr-6 sm:mb-0`}>
|
||||||
|
You are currently using {data.length} of {allocationLimit} allowed allocations for
|
||||||
|
this server.
|
||||||
|
</p>
|
||||||
|
{allocationLimit > data.length && (
|
||||||
|
<Button css={tw`w-full sm:w-auto`} color={'primary'} onClick={onCreateAllocation}>
|
||||||
|
Create Allocation
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Can>
|
||||||
|
)}
|
||||||
|
<AfterContent />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</ServerContentBlock>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NetworkContainer;
|
|
@ -0,0 +1,91 @@
|
||||||
|
import React from 'react';
|
||||||
|
import TitledGreyBox from '@/components/elements/TitledGreyBox';
|
||||||
|
import { ServerContext } from '@/state/server';
|
||||||
|
import { useStoreState } from 'easy-peasy';
|
||||||
|
import RenameServerBox from '@/components/server/settings/RenameServerBox';
|
||||||
|
import FlashMessageRender from '@/components/FlashMessageRender';
|
||||||
|
import Can from '@/components/elements/Can';
|
||||||
|
import ReinstallServerBox from '@/components/server/settings/ReinstallServerBox';
|
||||||
|
import tw from 'twin.macro';
|
||||||
|
import Input from '@/components/elements/Input';
|
||||||
|
import Label from '@/components/elements/Label';
|
||||||
|
import ServerContentBlock from '@/components/elements/ServerContentBlock';
|
||||||
|
import isEqual from 'react-fast-compare';
|
||||||
|
import CopyOnClick from '@/components/elements/CopyOnClick';
|
||||||
|
import { ip } from '@/lib/formatters';
|
||||||
|
import { Button } from '@/components/elements/button/index';
|
||||||
|
|
||||||
|
import BeforeContent from '@/blueprint/components/Server/Backups/BeforeContent';
|
||||||
|
import AfterContent from '@/blueprint/components/Server/Backups/AfterContent';
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
const username = useStoreState((state) => state.user.data!.username);
|
||||||
|
const id = ServerContext.useStoreState((state) => state.server.data!.id);
|
||||||
|
const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid);
|
||||||
|
const node = ServerContext.useStoreState((state) => state.server.data!.node);
|
||||||
|
const sftp = ServerContext.useStoreState((state) => state.server.data!.sftpDetails, isEqual);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ServerContentBlock title={'Settings'}>
|
||||||
|
<FlashMessageRender byKey={'settings'} css={tw`mb-4`} />
|
||||||
|
<BeforeContent />
|
||||||
|
<div css={tw`md:flex`}>
|
||||||
|
<div css={tw`w-full md:flex-1 md:mr-10`}>
|
||||||
|
<Can action={'file.sftp'}>
|
||||||
|
<TitledGreyBox title={'SFTP Details'} css={tw`mb-6 md:mb-10`}>
|
||||||
|
<div>
|
||||||
|
<Label>Server Address</Label>
|
||||||
|
<CopyOnClick text={`sftp://${ip(sftp.ip)}:${sftp.port}`}>
|
||||||
|
<Input type={'text'} value={`sftp://${ip(sftp.ip)}:${sftp.port}`} readOnly />
|
||||||
|
</CopyOnClick>
|
||||||
|
</div>
|
||||||
|
<div css={tw`mt-6`}>
|
||||||
|
<Label>Username</Label>
|
||||||
|
<CopyOnClick text={`${username}.${id}`}>
|
||||||
|
<Input type={'text'} value={`${username}.${id}`} readOnly />
|
||||||
|
</CopyOnClick>
|
||||||
|
</div>
|
||||||
|
<div css={tw`mt-6 flex items-center`}>
|
||||||
|
<div css={tw`flex-1`}>
|
||||||
|
<div css={tw`border-l-4 border-cyan-500 p-3`}>
|
||||||
|
<p css={tw`text-xs text-neutral-200`}>
|
||||||
|
Your SFTP password is the same as the password you use to access this panel.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div css={tw`ml-4`}>
|
||||||
|
<a href={`sftp://${username}.${id}@${ip(sftp.ip)}:${sftp.port}`}>
|
||||||
|
<Button.Text variant={Button.Variants.Secondary}>Launch SFTP</Button.Text>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</TitledGreyBox>
|
||||||
|
</Can>
|
||||||
|
<TitledGreyBox title={'Debug Information'} css={tw`mb-6 md:mb-10`}>
|
||||||
|
<div css={tw`flex items-center justify-between text-sm`}>
|
||||||
|
<p>Node</p>
|
||||||
|
<code css={tw`font-mono bg-neutral-900 rounded py-1 px-2`}>{node}</code>
|
||||||
|
</div>
|
||||||
|
<CopyOnClick text={uuid}>
|
||||||
|
<div css={tw`flex items-center justify-between mt-2 text-sm`}>
|
||||||
|
<p>Server ID</p>
|
||||||
|
<code css={tw`font-mono bg-neutral-900 rounded py-1 px-2`}>{uuid}</code>
|
||||||
|
</div>
|
||||||
|
</CopyOnClick>
|
||||||
|
</TitledGreyBox>
|
||||||
|
</div>
|
||||||
|
<div css={tw`w-full mt-6 md:flex-1 md:mt-0`}>
|
||||||
|
<Can action={'settings.rename'}>
|
||||||
|
<div css={tw`mb-6 md:mb-10`}>
|
||||||
|
<RenameServerBox />
|
||||||
|
</div>
|
||||||
|
</Can>
|
||||||
|
<Can action={'settings.reinstall'}>
|
||||||
|
<ReinstallServerBox />
|
||||||
|
</Can>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<AfterContent />
|
||||||
|
</ServerContentBlock>
|
||||||
|
);
|
||||||
|
};
|
142
resources/scripts/components/server/startup/StartupContainer.tsx
Normal file
142
resources/scripts/components/server/startup/StartupContainer.tsx
Normal file
|
@ -0,0 +1,142 @@
|
||||||
|
import React, { useCallback, useEffect, useState } from 'react';
|
||||||
|
import TitledGreyBox from '@/components/elements/TitledGreyBox';
|
||||||
|
import tw from 'twin.macro';
|
||||||
|
import VariableBox from '@/components/server/startup/VariableBox';
|
||||||
|
import ServerContentBlock from '@/components/elements/ServerContentBlock';
|
||||||
|
import getServerStartup from '@/api/swr/getServerStartup';
|
||||||
|
import Spinner from '@/components/elements/Spinner';
|
||||||
|
import { ServerError } from '@/components/elements/ScreenBlock';
|
||||||
|
import { httpErrorToHuman } from '@/api/http';
|
||||||
|
import { ServerContext } from '@/state/server';
|
||||||
|
import { useDeepCompareEffect } from '@/plugins/useDeepCompareEffect';
|
||||||
|
import Select from '@/components/elements/Select';
|
||||||
|
import isEqual from 'react-fast-compare';
|
||||||
|
import Input from '@/components/elements/Input';
|
||||||
|
import setSelectedDockerImage from '@/api/server/setSelectedDockerImage';
|
||||||
|
import InputSpinner from '@/components/elements/InputSpinner';
|
||||||
|
import useFlash from '@/plugins/useFlash';
|
||||||
|
|
||||||
|
import BeforeContent from '@/blueprint/components/Server/Startup/BeforeContent';
|
||||||
|
import AfterContent from '@/blueprint/components/Server/Startup/AfterContent';
|
||||||
|
|
||||||
|
const StartupContainer = () => {
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const { clearFlashes, clearAndAddHttpError } = useFlash();
|
||||||
|
|
||||||
|
const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid);
|
||||||
|
const variables = ServerContext.useStoreState(
|
||||||
|
({ server }) => ({
|
||||||
|
variables: server.data!.variables,
|
||||||
|
invocation: server.data!.invocation,
|
||||||
|
dockerImage: server.data!.dockerImage,
|
||||||
|
}),
|
||||||
|
isEqual
|
||||||
|
);
|
||||||
|
|
||||||
|
const { data, error, isValidating, mutate } = getServerStartup(uuid, {
|
||||||
|
...variables,
|
||||||
|
dockerImages: { [variables.dockerImage]: variables.dockerImage },
|
||||||
|
});
|
||||||
|
|
||||||
|
const setServerFromState = ServerContext.useStoreActions((actions) => actions.server.setServerFromState);
|
||||||
|
const isCustomImage =
|
||||||
|
data &&
|
||||||
|
!Object.values(data.dockerImages)
|
||||||
|
.map((v) => v.toLowerCase())
|
||||||
|
.includes(variables.dockerImage.toLowerCase());
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Since we're passing in initial data this will not trigger on mount automatically. We
|
||||||
|
// want to always fetch fresh information from the API however when we're loading the startup
|
||||||
|
// information.
|
||||||
|
mutate();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useDeepCompareEffect(() => {
|
||||||
|
if (!data) return;
|
||||||
|
|
||||||
|
setServerFromState((s) => ({
|
||||||
|
...s,
|
||||||
|
invocation: data.invocation,
|
||||||
|
variables: data.variables,
|
||||||
|
}));
|
||||||
|
}, [data]);
|
||||||
|
|
||||||
|
const updateSelectedDockerImage = useCallback(
|
||||||
|
(v: React.ChangeEvent<HTMLSelectElement>) => {
|
||||||
|
setLoading(true);
|
||||||
|
clearFlashes('startup:image');
|
||||||
|
|
||||||
|
const image = v.currentTarget.value;
|
||||||
|
setSelectedDockerImage(uuid, image)
|
||||||
|
.then(() => setServerFromState((s) => ({ ...s, dockerImage: image })))
|
||||||
|
.catch((error) => {
|
||||||
|
console.error(error);
|
||||||
|
clearAndAddHttpError({ key: 'startup:image', error });
|
||||||
|
})
|
||||||
|
.then(() => setLoading(false));
|
||||||
|
},
|
||||||
|
[uuid]
|
||||||
|
);
|
||||||
|
|
||||||
|
return !data ? (
|
||||||
|
!error || (error && isValidating) ? (
|
||||||
|
<Spinner centered size={Spinner.Size.LARGE} />
|
||||||
|
) : (
|
||||||
|
<ServerError title={'Oops!'} message={httpErrorToHuman(error)} onRetry={() => mutate()} />
|
||||||
|
)
|
||||||
|
) : (
|
||||||
|
<ServerContentBlock title={'Startup Settings'} showFlashKey={'startup:image'}>
|
||||||
|
<BeforeContent />
|
||||||
|
<div css={tw`md:flex`}>
|
||||||
|
<TitledGreyBox title={'Startup Command'} css={tw`flex-1`}>
|
||||||
|
<div css={tw`px-1 py-2`}>
|
||||||
|
<p css={tw`font-mono bg-neutral-900 rounded py-2 px-4`}>{data.invocation}</p>
|
||||||
|
</div>
|
||||||
|
</TitledGreyBox>
|
||||||
|
<TitledGreyBox title={'Docker Image'} css={tw`flex-1 lg:flex-none lg:w-1/3 mt-8 md:mt-0 md:ml-10`}>
|
||||||
|
{Object.keys(data.dockerImages).length > 1 && !isCustomImage ? (
|
||||||
|
<>
|
||||||
|
<InputSpinner visible={loading}>
|
||||||
|
<Select
|
||||||
|
disabled={Object.keys(data.dockerImages).length < 2}
|
||||||
|
onChange={updateSelectedDockerImage}
|
||||||
|
defaultValue={variables.dockerImage}
|
||||||
|
>
|
||||||
|
{Object.keys(data.dockerImages).map((key) => (
|
||||||
|
<option key={data.dockerImages[key]} value={data.dockerImages[key]}>
|
||||||
|
{key}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</InputSpinner>
|
||||||
|
<p css={tw`text-xs text-neutral-300 mt-2`}>
|
||||||
|
This is an advanced feature allowing you to select a Docker image to use when running
|
||||||
|
this server instance.
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Input disabled readOnly value={variables.dockerImage} />
|
||||||
|
{isCustomImage && (
|
||||||
|
<p css={tw`text-xs text-neutral-300 mt-2`}>
|
||||||
|
This {"server's"} Docker image has been manually set by an administrator and cannot
|
||||||
|
be changed through this UI.
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</TitledGreyBox>
|
||||||
|
</div>
|
||||||
|
<h3 css={tw`mt-8 mb-2 text-2xl`}>Variables</h3>
|
||||||
|
<div css={tw`grid gap-8 md:grid-cols-2`}>
|
||||||
|
{data.variables.map((variable) => (
|
||||||
|
<VariableBox key={variable.envVariable} variable={variable} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<AfterContent />
|
||||||
|
</ServerContentBlock>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default StartupContainer;
|
70
resources/scripts/components/server/users/UsersContainer.tsx
Normal file
70
resources/scripts/components/server/users/UsersContainer.tsx
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { ServerContext } from '@/state/server';
|
||||||
|
import { Actions, useStoreActions, useStoreState } from 'easy-peasy';
|
||||||
|
import { ApplicationStore } from '@/state';
|
||||||
|
import Spinner from '@/components/elements/Spinner';
|
||||||
|
import AddSubuserButton from '@/components/server/users/AddSubuserButton';
|
||||||
|
import UserRow from '@/components/server/users/UserRow';
|
||||||
|
import FlashMessageRender from '@/components/FlashMessageRender';
|
||||||
|
import getServerSubusers from '@/api/server/users/getServerSubusers';
|
||||||
|
import { httpErrorToHuman } from '@/api/http';
|
||||||
|
import Can from '@/components/elements/Can';
|
||||||
|
import ServerContentBlock from '@/components/elements/ServerContentBlock';
|
||||||
|
import tw from 'twin.macro';
|
||||||
|
|
||||||
|
import BeforeContent from '@/blueprint/components/Server/Users/BeforeContent';
|
||||||
|
import AfterContent from '@/blueprint/components/Server/Users/AfterContent';
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
|
const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid);
|
||||||
|
const subusers = ServerContext.useStoreState((state) => state.subusers.data);
|
||||||
|
const setSubusers = ServerContext.useStoreActions((actions) => actions.subusers.setSubusers);
|
||||||
|
|
||||||
|
const permissions = useStoreState((state: ApplicationStore) => state.permissions.data);
|
||||||
|
const getPermissions = useStoreActions((actions: Actions<ApplicationStore>) => actions.permissions.getPermissions);
|
||||||
|
const { addError, clearFlashes } = useStoreActions((actions: Actions<ApplicationStore>) => actions.flashes);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
clearFlashes('users');
|
||||||
|
getServerSubusers(uuid)
|
||||||
|
.then((subusers) => {
|
||||||
|
setSubusers(subusers);
|
||||||
|
setLoading(false);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error(error);
|
||||||
|
addError({ key: 'users', message: httpErrorToHuman(error) });
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getPermissions().catch((error) => {
|
||||||
|
addError({ key: 'users', message: httpErrorToHuman(error) });
|
||||||
|
console.error(error);
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (!subusers.length && (loading || !Object.keys(permissions).length)) {
|
||||||
|
return <Spinner size={'large'} centered />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ServerContentBlock title={'Users'}>
|
||||||
|
<FlashMessageRender byKey={'users'} css={tw`mb-4`} />
|
||||||
|
<BeforeContent />
|
||||||
|
{!subusers.length ? (
|
||||||
|
<p css={tw`text-center text-sm text-neutral-300`}>It looks like you don't have any subusers.</p>
|
||||||
|
) : (
|
||||||
|
subusers.map((subuser) => <UserRow key={subuser.uuid} subuser={subuser} />)
|
||||||
|
)}
|
||||||
|
<Can action={'user.create'}>
|
||||||
|
<div css={tw`flex justify-end mt-6`}>
|
||||||
|
<AddSubuserButton />
|
||||||
|
</div>
|
||||||
|
</Can>
|
||||||
|
<AfterContent />
|
||||||
|
</ServerContentBlock>
|
||||||
|
);
|
||||||
|
};
|
Loading…
Reference in a new issue