ui(admin): update button components, fix Editor for eggs
This commit is contained in:
parent
4e56f6dbea
commit
089860b721
35 changed files with 363 additions and 623 deletions
|
@ -85,7 +85,7 @@ export const searchEggs = async (
|
||||||
return data.data.map(Transformers.toEgg);
|
return data.data.map(Transformers.toEgg);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const exportEgg = async (eggId: number): Promise<Record<string, any>> => {
|
export const exportEgg = async (eggId: number): Promise<string> => {
|
||||||
const { data } = await http.get(`/api/application/eggs/${eggId}/export`);
|
const { data } = await http.get(`/api/application/eggs/${eggId}/export`);
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,13 +1,11 @@
|
||||||
import http, {
|
import type { AxiosError } from 'axios';
|
||||||
FractalPaginatedResponse,
|
import type { SWRConfiguration, SWRResponse } from 'swr';
|
||||||
PaginatedResult,
|
import useSWR from 'swr';
|
||||||
QueryBuilderParams,
|
|
||||||
getPaginationSet,
|
import type { FractalPaginatedResponse, PaginatedResult, QueryBuilderParams } from '@/api/http';
|
||||||
withQueryBuilderParams,
|
import http, { getPaginationSet, withQueryBuilderParams } from '@/api/http';
|
||||||
} from '@/api/http';
|
import type { User } from '@definitions/admin';
|
||||||
import { Transformers, User } from '@definitions/admin';
|
import { Transformers } from '@definitions/admin';
|
||||||
import useSWR, { SWRConfiguration, SWRResponse } from 'swr';
|
|
||||||
import { AxiosError } from 'axios';
|
|
||||||
|
|
||||||
export interface UpdateUserValues {
|
export interface UpdateUserValues {
|
||||||
externalId: string;
|
externalId: string;
|
||||||
|
@ -32,7 +30,10 @@ const useGetUsers = (
|
||||||
params: withQueryBuilderParams(params),
|
params: withQueryBuilderParams(params),
|
||||||
});
|
});
|
||||||
|
|
||||||
return getPaginationSet(data, Transformers.toUser);
|
return {
|
||||||
|
items: (data.data || []).map(Transformers.toUser),
|
||||||
|
pagination: getPaginationSet(data.meta.pagination),
|
||||||
|
};
|
||||||
},
|
},
|
||||||
config || { revalidateOnMount: true, revalidateOnFocus: false },
|
config || { revalidateOnMount: true, revalidateOnFocus: false },
|
||||||
);
|
);
|
||||||
|
|
|
@ -28,6 +28,8 @@ interface ExtendedWindow extends Window {
|
||||||
root_admin: boolean;
|
root_admin: boolean;
|
||||||
use_totp: boolean;
|
use_totp: boolean;
|
||||||
language: string;
|
language: string;
|
||||||
|
avatar_url: string;
|
||||||
|
admin_role_name: string;
|
||||||
updated_at: string;
|
updated_at: string;
|
||||||
created_at: string;
|
created_at: string;
|
||||||
/* eslint-enable camelcase */
|
/* eslint-enable camelcase */
|
||||||
|
@ -45,6 +47,8 @@ function App() {
|
||||||
email: PterodactylUser.email,
|
email: PterodactylUser.email,
|
||||||
language: PterodactylUser.language,
|
language: PterodactylUser.language,
|
||||||
rootAdmin: PterodactylUser.root_admin,
|
rootAdmin: PterodactylUser.root_admin,
|
||||||
|
avatarURL: PterodactylUser.avatar_url,
|
||||||
|
roleName: PterodactylUser.admin_role_name,
|
||||||
useTotp: PterodactylUser.use_totp,
|
useTotp: PterodactylUser.use_totp,
|
||||||
createdAt: new Date(PterodactylUser.created_at),
|
createdAt: new Date(PterodactylUser.created_at),
|
||||||
updatedAt: new Date(PterodactylUser.updated_at),
|
updatedAt: new Date(PterodactylUser.updated_at),
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { Link, NavLink } from 'react-router-dom';
|
import { Link, NavLink } from 'react-router-dom';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
import { faCogs, faLayerGroup, faSignOutAlt } from '@fortawesome/free-solid-svg-icons';
|
import { faLayerGroup, faScrewdriverWrench, faSignOutAlt } from '@fortawesome/free-solid-svg-icons';
|
||||||
import { useStoreState } from 'easy-peasy';
|
import { useStoreState } from 'easy-peasy';
|
||||||
import { ApplicationStore } from '@/state';
|
import { ApplicationStore } from '@/state';
|
||||||
import SearchContainer from '@/components/dashboard/search/SearchContainer';
|
import SearchContainer from '@/components/dashboard/search/SearchContainer';
|
||||||
|
@ -57,6 +57,7 @@ export default () => {
|
||||||
{name}
|
{name}
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<RightNavigation className="flex h-full items-center justify-center">
|
<RightNavigation className="flex h-full items-center justify-center">
|
||||||
<SearchContainer />
|
<SearchContainer />
|
||||||
|
|
||||||
|
@ -66,14 +67,6 @@ export default () => {
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
{rootAdmin && (
|
|
||||||
<Tooltip placement="bottom" content="Admin">
|
|
||||||
<a href="/admin" rel="noreferrer">
|
|
||||||
<FontAwesomeIcon icon={faCogs} />
|
|
||||||
</a>
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Tooltip placement="bottom" content="Account Settings">
|
<Tooltip placement="bottom" content="Account Settings">
|
||||||
<NavLink to="/account">
|
<NavLink to="/account">
|
||||||
<span className="flex items-center w-5 h-5">
|
<span className="flex items-center w-5 h-5">
|
||||||
|
@ -82,6 +75,14 @@ export default () => {
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
|
{rootAdmin && (
|
||||||
|
<Tooltip placement="bottom" content="Admin">
|
||||||
|
<a href="/admin" rel="noreferrer">
|
||||||
|
<FontAwesomeIcon icon={faScrewdriverWrench} />
|
||||||
|
</a>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
|
||||||
<Tooltip placement="bottom" content="Sign Out">
|
<Tooltip placement="bottom" content="Sign Out">
|
||||||
<button onClick={onTriggerLogout}>
|
<button onClick={onTriggerLogout}>
|
||||||
<FontAwesomeIcon icon={faSignOutAlt} />
|
<FontAwesomeIcon icon={faSignOutAlt} />
|
||||||
|
|
|
@ -35,7 +35,7 @@ interface PropsWithoutIcon extends Props {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SubNavigationLink = ({ to, name, icon: IconComponent, children }: PropsWithIcon | PropsWithoutIcon) => (
|
export const SubNavigationLink = ({ to, name, icon: IconComponent, children }: PropsWithIcon | PropsWithoutIcon) => (
|
||||||
<NavLink to={to}>
|
<NavLink to={to} end>
|
||||||
{IconComponent ? <IconComponent /> : children}
|
{IconComponent ? <IconComponent /> : children}
|
||||||
{name}
|
{name}
|
||||||
</NavLink>
|
</NavLink>
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
import { Actions, useStoreActions } from 'easy-peasy';
|
import type { Actions } from 'easy-peasy';
|
||||||
|
import { useStoreActions } from 'easy-peasy';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import tw from 'twin.macro';
|
import tw from 'twin.macro';
|
||||||
|
|
||||||
import deleteDatabase from '@/api/admin/databases/deleteDatabase';
|
import deleteDatabase from '@/api/admin/databases/deleteDatabase';
|
||||||
import Button from '@/components/elements/Button';
|
import { Button } from '@/components/elements/button';
|
||||||
|
import { Shape } from '@/components/elements/button/types';
|
||||||
import ConfirmationModal from '@/components/elements/ConfirmationModal';
|
import ConfirmationModal from '@/components/elements/ConfirmationModal';
|
||||||
import type { ApplicationStore } from '@/state';
|
import type { ApplicationStore } from '@/state';
|
||||||
|
|
||||||
|
@ -52,7 +54,7 @@ export default ({ databaseId, onDeleted }: Props) => {
|
||||||
created on this host but not the databases themselves.
|
created on this host but not the databases themselves.
|
||||||
</ConfirmationModal>
|
</ConfirmationModal>
|
||||||
|
|
||||||
<Button type={'button'} size={'xsmall'} color={'red'} onClick={() => setVisible(true)}>
|
<Button.Danger type="button" shape={Shape.IconSquare} onClick={() => setVisible(true)}>
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
fill="none"
|
fill="none"
|
||||||
|
@ -67,7 +69,7 @@ export default ({ databaseId, onDeleted }: Props) => {
|
||||||
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
|
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</Button>
|
</Button.Danger>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -14,7 +14,7 @@ import AdminContentBlock from '@/components/admin/AdminContentBlock';
|
||||||
import Spinner from '@/components/elements/Spinner';
|
import Spinner from '@/components/elements/Spinner';
|
||||||
import FlashMessageRender from '@/components/FlashMessageRender';
|
import FlashMessageRender from '@/components/FlashMessageRender';
|
||||||
import AdminBox from '@/components/admin/AdminBox';
|
import AdminBox from '@/components/admin/AdminBox';
|
||||||
import Button from '@/components/elements/Button';
|
import { Button } from '@/components/elements/button';
|
||||||
import Field from '@/components/elements/Field';
|
import Field from '@/components/elements/Field';
|
||||||
import SpinnerOverlay from '@/components/elements/SpinnerOverlay';
|
import SpinnerOverlay from '@/components/elements/SpinnerOverlay';
|
||||||
import DatabaseDeleteButton from '@/components/admin/databases/DatabaseDeleteButton';
|
import DatabaseDeleteButton from '@/components/admin/databases/DatabaseDeleteButton';
|
||||||
|
@ -115,7 +115,7 @@ export const InformationContainer = ({ title, initialValues, children, onSubmit
|
||||||
<div css={tw`w-full flex flex-row items-center mt-6`}>
|
<div css={tw`w-full flex flex-row items-center mt-6`}>
|
||||||
{children}
|
{children}
|
||||||
<div css={tw`flex ml-auto`}>
|
<div css={tw`flex ml-auto`}>
|
||||||
<Button type={'submit'} disabled={isSubmitting || !isValid}>
|
<Button type="submit" disabled={isSubmitting || !isValid}>
|
||||||
Save Changes
|
Save Changes
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -10,17 +10,18 @@ import FlashMessageRender from '@/components/FlashMessageRender';
|
||||||
import AdminCheckbox from '@/components/admin/AdminCheckbox';
|
import AdminCheckbox from '@/components/admin/AdminCheckbox';
|
||||||
import AdminContentBlock from '@/components/admin/AdminContentBlock';
|
import AdminContentBlock from '@/components/admin/AdminContentBlock';
|
||||||
import AdminTable, {
|
import AdminTable, {
|
||||||
|
ContentWrapper,
|
||||||
|
Loading,
|
||||||
|
NoItems,
|
||||||
|
Pagination,
|
||||||
TableBody,
|
TableBody,
|
||||||
TableHead,
|
TableHead,
|
||||||
TableHeader,
|
TableHeader,
|
||||||
TableRow,
|
TableRow,
|
||||||
Pagination,
|
|
||||||
Loading,
|
|
||||||
NoItems,
|
|
||||||
ContentWrapper,
|
|
||||||
useTableHooks,
|
useTableHooks,
|
||||||
} from '@/components/admin/AdminTable';
|
} from '@/components/admin/AdminTable';
|
||||||
import Button from '@/components/elements/Button';
|
import { Button } from '@/components/elements/button';
|
||||||
|
import { Size } from '@/components/elements/button/types';
|
||||||
import CopyOnClick from '@/components/elements/CopyOnClick';
|
import CopyOnClick from '@/components/elements/CopyOnClick';
|
||||||
|
|
||||||
const RowCheckbox = ({ id }: { id: number }) => {
|
const RowCheckbox = ({ id }: { id: number }) => {
|
||||||
|
@ -93,7 +94,7 @@ const DatabasesContainer = () => {
|
||||||
|
|
||||||
<div css={tw`flex ml-auto pl-4`}>
|
<div css={tw`flex ml-auto pl-4`}>
|
||||||
<NavLink to="/admin/databases/new">
|
<NavLink to="/admin/databases/new">
|
||||||
<Button type={'button'} size={'large'} css={tw`h-10 px-4 py-0 whitespace-nowrap`}>
|
<Button type="button" size={Size.Large} css={tw`h-10 px-4 py-0 whitespace-nowrap`}>
|
||||||
New Database Host
|
New Database Host
|
||||||
</Button>
|
</Button>
|
||||||
</NavLink>
|
</NavLink>
|
||||||
|
|
|
@ -4,7 +4,8 @@ import { useState } from 'react';
|
||||||
import tw from 'twin.macro';
|
import tw from 'twin.macro';
|
||||||
|
|
||||||
import deleteLocation from '@/api/admin/locations/deleteLocation';
|
import deleteLocation from '@/api/admin/locations/deleteLocation';
|
||||||
import Button from '@/components/elements/Button';
|
import { Button } from '@/components/elements/button';
|
||||||
|
import { Shape } from '@/components/elements/button/types';
|
||||||
import ConfirmationModal from '@/components/elements/ConfirmationModal';
|
import ConfirmationModal from '@/components/elements/ConfirmationModal';
|
||||||
import type { ApplicationStore } from '@/state';
|
import type { ApplicationStore } from '@/state';
|
||||||
|
|
||||||
|
@ -53,7 +54,7 @@ export default ({ locationId, onDeleted }: Props) => {
|
||||||
to it.
|
to it.
|
||||||
</ConfirmationModal>
|
</ConfirmationModal>
|
||||||
|
|
||||||
<Button type={'button'} size={'xsmall'} color={'red'} onClick={() => setVisible(true)}>
|
<Button.Danger type="button" shape={Shape.IconSquare} onClick={() => setVisible(true)}>
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
fill="none"
|
fill="none"
|
||||||
|
@ -68,7 +69,7 @@ export default ({ locationId, onDeleted }: Props) => {
|
||||||
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
|
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</Button>
|
</Button.Danger>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -13,7 +13,7 @@ import updateLocation from '@/api/admin/locations/updateLocation';
|
||||||
import AdminBox from '@/components/admin/AdminBox';
|
import AdminBox from '@/components/admin/AdminBox';
|
||||||
import AdminContentBlock from '@/components/admin/AdminContentBlock';
|
import AdminContentBlock from '@/components/admin/AdminContentBlock';
|
||||||
import LocationDeleteButton from '@/components/admin/locations/LocationDeleteButton';
|
import LocationDeleteButton from '@/components/admin/locations/LocationDeleteButton';
|
||||||
import Button from '@/components/elements/Button';
|
import { Button } from '@/components/elements/button';
|
||||||
import Field from '@/components/elements/Field';
|
import Field from '@/components/elements/Field';
|
||||||
import Spinner from '@/components/elements/Spinner';
|
import Spinner from '@/components/elements/Spinner';
|
||||||
import SpinnerOverlay from '@/components/elements/SpinnerOverlay';
|
import SpinnerOverlay from '@/components/elements/SpinnerOverlay';
|
||||||
|
@ -99,7 +99,7 @@ const EditInformationContainer = () => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div css={tw`flex ml-auto`}>
|
<div css={tw`flex ml-auto`}>
|
||||||
<Button type={'submit'} disabled={isSubmitting || !isValid}>
|
<Button type="submit" disabled={isSubmitting || !isValid}>
|
||||||
Save Changes
|
Save Changes
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -24,7 +24,7 @@ import FlashMessageRender from '@/components/FlashMessageRender';
|
||||||
import useFlash from '@/plugins/useFlash';
|
import useFlash from '@/plugins/useFlash';
|
||||||
import { AdminContext } from '@/state/admin';
|
import { AdminContext } from '@/state/admin';
|
||||||
|
|
||||||
const RowCheckbox = ({ id }: { id: number }) => {
|
function RowCheckbox({ id }: { id: number }) {
|
||||||
const isChecked = AdminContext.useStoreState(state => state.locations.selectedLocations.indexOf(id) >= 0);
|
const isChecked = AdminContext.useStoreState(state => state.locations.selectedLocations.indexOf(id) >= 0);
|
||||||
const appendSelectedLocation = AdminContext.useStoreActions(actions => actions.locations.appendSelectedLocation);
|
const appendSelectedLocation = AdminContext.useStoreActions(actions => actions.locations.appendSelectedLocation);
|
||||||
const removeSelectedLocation = AdminContext.useStoreActions(actions => actions.locations.removeSelectedLocation);
|
const removeSelectedLocation = AdminContext.useStoreActions(actions => actions.locations.removeSelectedLocation);
|
||||||
|
@ -42,9 +42,9 @@ const RowCheckbox = ({ id }: { id: number }) => {
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
const LocationsContainer = () => {
|
function LocationsContainer() {
|
||||||
const { page, setPage, setFilters, sort, setSort, sortDirection } = useContext(LocationsContext);
|
const { page, setPage, setFilters, sort, setSort, sortDirection } = useContext(LocationsContext);
|
||||||
const { clearFlashes, clearAndAddHttpError } = useFlash();
|
const { clearFlashes, clearAndAddHttpError } = useFlash();
|
||||||
const { data: locations, error, isValidating } = getLocations();
|
const { data: locations, error, isValidating } = getLocations();
|
||||||
|
@ -173,7 +173,7 @@ const LocationsContainer = () => {
|
||||||
</AdminTable>
|
</AdminTable>
|
||||||
</AdminContentBlock>
|
</AdminContentBlock>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
const hooks = useTableHooks<Filters>();
|
const hooks = useTableHooks<Filters>();
|
||||||
|
|
|
@ -6,7 +6,8 @@ import { object, string } from 'yup';
|
||||||
|
|
||||||
import createLocation from '@/api/admin/locations/createLocation';
|
import createLocation from '@/api/admin/locations/createLocation';
|
||||||
import getLocations from '@/api/admin/locations/getLocations';
|
import getLocations from '@/api/admin/locations/getLocations';
|
||||||
import Button from '@/components/elements/Button';
|
import { Button } from '@/components/elements/button';
|
||||||
|
import { Size, Variant } from '@/components/elements/button/types';
|
||||||
import Field from '@/components/elements/Field';
|
import Field from '@/components/elements/Field';
|
||||||
import Modal from '@/components/elements/Modal';
|
import Modal from '@/components/elements/Modal';
|
||||||
import FlashMessageRender from '@/components/FlashMessageRender';
|
import FlashMessageRender from '@/components/FlashMessageRender';
|
||||||
|
@ -82,14 +83,14 @@ export default () => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div css={tw`flex flex-wrap justify-end mt-6`}>
|
<div css={tw`flex flex-wrap justify-end mt-6`}>
|
||||||
<Button
|
<Button.Text
|
||||||
type={'button'}
|
type="button"
|
||||||
isSecondary
|
variant={Variant.Secondary}
|
||||||
css={tw`w-full sm:w-auto sm:mr-2`}
|
css={tw`w-full sm:w-auto sm:mr-2`}
|
||||||
onClick={() => setVisible(false)}
|
onClick={() => setVisible(false)}
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button.Text>
|
||||||
<Button css={tw`w-full mt-4 sm:w-auto sm:mt-0`} type={'submit'}>
|
<Button css={tw`w-full mt-4 sm:w-auto sm:mt-0`} type={'submit'}>
|
||||||
Create Location
|
Create Location
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -100,8 +101,8 @@ export default () => {
|
||||||
</Formik>
|
</Formik>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
type={'button'}
|
type="button"
|
||||||
size={'large'}
|
size={Size.Large}
|
||||||
css={tw`h-10 px-4 py-0 whitespace-nowrap`}
|
css={tw`h-10 px-4 py-0 whitespace-nowrap`}
|
||||||
onClick={() => setVisible(true)}
|
onClick={() => setVisible(true)}
|
||||||
>
|
>
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { useState } from 'react';
|
||||||
import tw from 'twin.macro';
|
import tw from 'twin.macro';
|
||||||
|
|
||||||
import deleteMount from '@/api/admin/mounts/deleteMount';
|
import deleteMount from '@/api/admin/mounts/deleteMount';
|
||||||
import Button from '@/components/elements/Button';
|
import { Button } from '@/components/elements/button';
|
||||||
import ConfirmationModal from '@/components/elements/ConfirmationModal';
|
import ConfirmationModal from '@/components/elements/ConfirmationModal';
|
||||||
import type { ApplicationStore } from '@/state';
|
import type { ApplicationStore } from '@/state';
|
||||||
|
|
||||||
|
@ -52,7 +52,7 @@ export default ({ mountId, onDeleted }: Props) => {
|
||||||
Are you sure you want to delete this mount? Deleting a mount will not delete files on any nodes.
|
Are you sure you want to delete this mount? Deleting a mount will not delete files on any nodes.
|
||||||
</ConfirmationModal>
|
</ConfirmationModal>
|
||||||
|
|
||||||
<Button type={'button'} size={'xsmall'} color={'red'} onClick={() => setVisible(true)}>
|
<Button.Danger type="button" onClick={() => setVisible(true)}>
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
fill="none"
|
fill="none"
|
||||||
|
@ -67,7 +67,7 @@ export default ({ mountId, onDeleted }: Props) => {
|
||||||
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
|
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</Button>
|
</Button.Danger>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
import type { FormikHelpers } from 'formik';
|
import type { FormikHelpers } from 'formik';
|
||||||
import { Field as FormikField, Form, Formik } from 'formik';
|
import { Field as FormikField, Form, Formik } from 'formik';
|
||||||
|
import type { ReactNode } from 'react';
|
||||||
import tw from 'twin.macro';
|
import tw from 'twin.macro';
|
||||||
import { boolean, object, string } from 'yup';
|
import { boolean, object, string } from 'yup';
|
||||||
|
|
||||||
import AdminBox from '@/components/admin/AdminBox';
|
import AdminBox from '@/components/admin/AdminBox';
|
||||||
import Button from '@/components/elements/Button';
|
import { Button } from '@/components/elements/button';
|
||||||
import Field from '@/components/elements/Field';
|
import Field from '@/components/elements/Field';
|
||||||
import Label from '@/components/elements/Label';
|
import Label from '@/components/elements/Label';
|
||||||
import SpinnerOverlay from '@/components/elements/SpinnerOverlay';
|
import SpinnerOverlay from '@/components/elements/SpinnerOverlay';
|
||||||
|
@ -25,7 +26,7 @@ interface Props {
|
||||||
|
|
||||||
onSubmit: (values: Values, helpers: FormikHelpers<Values>) => void;
|
onSubmit: (values: Values, helpers: FormikHelpers<Values>) => void;
|
||||||
|
|
||||||
children?: React.ReactNode;
|
children?: ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
function MountForm({ action, title, initialValues, children, onSubmit }: Props) {
|
function MountForm({ action, title, initialValues, children, onSubmit }: Props) {
|
||||||
|
@ -118,7 +119,7 @@ function MountForm({ action, title, initialValues, children, onSubmit }: Props)
|
||||||
{children}
|
{children}
|
||||||
|
|
||||||
<div css={tw`flex ml-auto`}>
|
<div css={tw`flex ml-auto`}>
|
||||||
<Button type={'submit'} disabled={isSubmitting || !isValid}>
|
<Button type="submit" disabled={isSubmitting || !isValid}>
|
||||||
{action}
|
{action}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -18,7 +18,8 @@ import AdminTable, {
|
||||||
ContentWrapper,
|
ContentWrapper,
|
||||||
useTableHooks,
|
useTableHooks,
|
||||||
} from '@/components/admin/AdminTable';
|
} from '@/components/admin/AdminTable';
|
||||||
import Button from '@/components/elements/Button';
|
import { Button } from '@/components/elements/button';
|
||||||
|
import { Size } from '@/components/elements/button/types';
|
||||||
import CopyOnClick from '@/components/elements/CopyOnClick';
|
import CopyOnClick from '@/components/elements/CopyOnClick';
|
||||||
import FlashMessageRender from '@/components/FlashMessageRender';
|
import FlashMessageRender from '@/components/FlashMessageRender';
|
||||||
import useFlash from '@/plugins/useFlash';
|
import useFlash from '@/plugins/useFlash';
|
||||||
|
@ -94,7 +95,7 @@ const MountsContainer = () => {
|
||||||
|
|
||||||
<div css={tw`flex ml-auto pl-4`}>
|
<div css={tw`flex ml-auto pl-4`}>
|
||||||
<NavLink to={`/admin/mounts/new`}>
|
<NavLink to={`/admin/mounts/new`}>
|
||||||
<Button type={'button'} size={'large'} css={tw`h-10 px-4 py-0 whitespace-nowrap`}>
|
<Button type={'button'} size={Size.Large} css={tw`h-10 px-4 py-0 whitespace-nowrap`}>
|
||||||
New Mount
|
New Mount
|
||||||
</Button>
|
</Button>
|
||||||
</NavLink>
|
</NavLink>
|
||||||
|
|
|
@ -1,13 +1,17 @@
|
||||||
|
import { LanguageDescription } from '@codemirror/language';
|
||||||
|
import { json } from '@codemirror/lang-json';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { useParams } from 'react-router-dom';
|
||||||
|
import tw from 'twin.macro';
|
||||||
|
|
||||||
import getEggs from '@/api/admin/nests/getEggs';
|
import getEggs from '@/api/admin/nests/getEggs';
|
||||||
import importEgg from '@/api/admin/nests/importEgg';
|
import importEgg from '@/api/admin/nests/importEgg';
|
||||||
import useFlash from '@/plugins/useFlash';
|
import useFlash from '@/plugins/useFlash';
|
||||||
// import { Editor } from '@/components/elements/editor';
|
import { Button } from '@/components/elements/button';
|
||||||
import { useState } from 'react';
|
import { Size, Variant } from '@/components/elements/button/types';
|
||||||
import Button from '@/components/elements/Button';
|
import { Editor } from '@/components/elements/editor';
|
||||||
import Modal from '@/components/elements/Modal';
|
import Modal from '@/components/elements/Modal';
|
||||||
import FlashMessageRender from '@/components/FlashMessageRender';
|
import FlashMessageRender from '@/components/FlashMessageRender';
|
||||||
import { useParams } from 'react-router-dom';
|
|
||||||
import tw from 'twin.macro';
|
|
||||||
|
|
||||||
export default ({ className }: { className?: string }) => {
|
export default ({ className }: { className?: string }) => {
|
||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
|
@ -43,24 +47,24 @@ export default ({ className }: { className?: string }) => {
|
||||||
|
|
||||||
<h2 css={tw`mb-6 text-2xl text-neutral-100`}>Import Egg</h2>
|
<h2 css={tw`mb-6 text-2xl text-neutral-100`}>Import Egg</h2>
|
||||||
|
|
||||||
{/*<Editor*/}
|
<Editor
|
||||||
{/* // overrides={tw`h-64 rounded`}*/}
|
className="h-64 overflow-hidden rounded"
|
||||||
{/* initialContent={''}*/}
|
initialContent={''}
|
||||||
{/* // language={jsonLanguage}*/}
|
fetchContent={value => {
|
||||||
{/* fetchContent={value => {*/}
|
fetchFileContent = value;
|
||||||
{/* fetchFileContent = value;*/}
|
}}
|
||||||
{/* }}*/}
|
language={LanguageDescription.of({ name: 'json', support: json() })}
|
||||||
{/*/>*/}
|
/>
|
||||||
|
|
||||||
<div css={tw`flex flex-wrap justify-end mt-4 sm:mt-6`}>
|
<div css={tw`flex flex-wrap justify-end mt-4 sm:mt-6`}>
|
||||||
<Button
|
<Button.Text
|
||||||
type={'button'}
|
type="button"
|
||||||
|
variant={Variant.Secondary}
|
||||||
css={tw`w-full sm:w-auto sm:mr-2`}
|
css={tw`w-full sm:w-auto sm:mr-2`}
|
||||||
onClick={() => setVisible(false)}
|
onClick={() => setVisible(false)}
|
||||||
isSecondary
|
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button.Text>
|
||||||
<Button css={tw`w-full sm:w-auto mt-4 sm:mt-0`} onClick={submit}>
|
<Button css={tw`w-full sm:w-auto mt-4 sm:mt-0`} onClick={submit}>
|
||||||
Import Egg
|
Import Egg
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -68,12 +72,12 @@ export default ({ className }: { className?: string }) => {
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
type={'button'}
|
type="button"
|
||||||
size={'large'}
|
size={Size.Large}
|
||||||
|
variant={Variant.Secondary}
|
||||||
css={tw`h-10 px-4 py-0 whitespace-nowrap`}
|
css={tw`h-10 px-4 py-0 whitespace-nowrap`}
|
||||||
className={className}
|
className={className}
|
||||||
onClick={() => setVisible(true)}
|
onClick={() => setVisible(true)}
|
||||||
isSecondary
|
|
||||||
>
|
>
|
||||||
Import
|
Import
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
@ -4,7 +4,8 @@ import { useState } from 'react';
|
||||||
import tw from 'twin.macro';
|
import tw from 'twin.macro';
|
||||||
|
|
||||||
import deleteNest from '@/api/admin/nests/deleteNest';
|
import deleteNest from '@/api/admin/nests/deleteNest';
|
||||||
import Button from '@/components/elements/Button';
|
import { Button } from '@/components/elements/button';
|
||||||
|
import { Shape } from '@/components/elements/button/types';
|
||||||
import ConfirmationModal from '@/components/elements/ConfirmationModal';
|
import ConfirmationModal from '@/components/elements/ConfirmationModal';
|
||||||
import type { ApplicationStore } from '@/state';
|
import type { ApplicationStore } from '@/state';
|
||||||
|
|
||||||
|
@ -52,7 +53,7 @@ export default ({ nestId, onDeleted }: Props) => {
|
||||||
Are you sure you want to delete this nest? Deleting a nest will delete all eggs assigned to it.
|
Are you sure you want to delete this nest? Deleting a nest will delete all eggs assigned to it.
|
||||||
</ConfirmationModal>
|
</ConfirmationModal>
|
||||||
|
|
||||||
<Button type={'button'} size={'xsmall'} color={'red'} onClick={() => setVisible(true)}>
|
<Button.Danger type="button" shape={Shape.IconSquare} onClick={() => setVisible(true)}>
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
fill="none"
|
fill="none"
|
||||||
|
@ -67,7 +68,7 @@ export default ({ nestId, onDeleted }: Props) => {
|
||||||
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
|
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</Button>
|
</Button.Danger>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -14,7 +14,8 @@ import FlashMessageRender from '@/components/FlashMessageRender';
|
||||||
import type { Nest } from '@/api/admin/nests/getNests';
|
import type { Nest } from '@/api/admin/nests/getNests';
|
||||||
import getNest from '@/api/admin/nests/getNest';
|
import getNest from '@/api/admin/nests/getNest';
|
||||||
import updateNest from '@/api/admin/nests/updateNest';
|
import updateNest from '@/api/admin/nests/updateNest';
|
||||||
import Button from '@/components/elements/Button';
|
import { Button } from '@/components/elements/button';
|
||||||
|
import { Size } from '@/components/elements/button/types';
|
||||||
import Field from '@/components/elements/Field';
|
import Field from '@/components/elements/Field';
|
||||||
import SpinnerOverlay from '@/components/elements/SpinnerOverlay';
|
import SpinnerOverlay from '@/components/elements/SpinnerOverlay';
|
||||||
import AdminBox from '@/components/admin/AdminBox';
|
import AdminBox from '@/components/admin/AdminBox';
|
||||||
|
@ -117,7 +118,7 @@ const EditInformationContainer = () => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div css={tw`flex ml-auto`}>
|
<div css={tw`flex ml-auto`}>
|
||||||
<Button type={'submit'} disabled={isSubmitting || !isValid}>
|
<Button type="submit" disabled={isSubmitting || !isValid}>
|
||||||
Save Changes
|
Save Changes
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -222,7 +223,7 @@ const NestEditContainer = () => {
|
||||||
<ImportEggButton css={tw`mr-4`} />
|
<ImportEggButton css={tw`mr-4`} />
|
||||||
|
|
||||||
<NavLink to={`/admin/nests/${params.nestId}/new`}>
|
<NavLink to={`/admin/nests/${params.nestId}/new`}>
|
||||||
<Button type={'button'} size={'large'} css={tw`h-10 px-4 py-0 whitespace-nowrap`}>
|
<Button type={'button'} size={Size.Large} css={tw`h-10 px-4 py-0 whitespace-nowrap`}>
|
||||||
New Egg
|
New Egg
|
||||||
</Button>
|
</Button>
|
||||||
</NavLink>
|
</NavLink>
|
||||||
|
|
|
@ -15,7 +15,7 @@ import {
|
||||||
EggProcessContainerRef,
|
EggProcessContainerRef,
|
||||||
EggStartupContainer,
|
EggStartupContainer,
|
||||||
} from '@/components/admin/nests/eggs/EggSettingsContainer';
|
} from '@/components/admin/nests/eggs/EggSettingsContainer';
|
||||||
import Button from '@/components/elements/Button';
|
import { Button } from '@/components/elements/button';
|
||||||
import FlashMessageRender from '@/components/FlashMessageRender';
|
import FlashMessageRender from '@/components/FlashMessageRender';
|
||||||
import useFlash from '@/plugins/useFlash';
|
import useFlash from '@/plugins/useFlash';
|
||||||
|
|
||||||
|
@ -45,7 +45,16 @@ export default () => {
|
||||||
values.configStartup = (await ref.current?.getStartupConfiguration()) || '';
|
values.configStartup = (await ref.current?.getStartupConfiguration()) || '';
|
||||||
values.configFiles = (await ref.current?.getFilesConfiguration()) || '';
|
values.configFiles = (await ref.current?.getFilesConfiguration()) || '';
|
||||||
|
|
||||||
createEgg({ ...values, dockerImages: values.dockerImages.split('\n'), nestId })
|
const dockerImages: Record<string, string> = {};
|
||||||
|
values.dockerImages.split('\n').forEach(v => {
|
||||||
|
dockerImages[v] = v;
|
||||||
|
});
|
||||||
|
|
||||||
|
createEgg({
|
||||||
|
...values,
|
||||||
|
dockerImages,
|
||||||
|
nestId,
|
||||||
|
})
|
||||||
.then(egg => navigate(`/admin/nests/${nestId}/eggs/${egg.id}`))
|
.then(egg => navigate(`/admin/nests/${nestId}/eggs/${egg.id}`))
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
@ -97,12 +106,7 @@ export default () => {
|
||||||
|
|
||||||
<div css={tw`bg-neutral-700 rounded shadow-md py-2 px-6 mb-16`}>
|
<div css={tw`bg-neutral-700 rounded shadow-md py-2 px-6 mb-16`}>
|
||||||
<div css={tw`flex flex-row`}>
|
<div css={tw`flex flex-row`}>
|
||||||
<Button
|
<Button type="submit" css={tw`ml-auto`} disabled={isSubmitting || !isValid}>
|
||||||
type="submit"
|
|
||||||
size="small"
|
|
||||||
css={tw`ml-auto`}
|
|
||||||
disabled={isSubmitting || !isValid}
|
|
||||||
>
|
|
||||||
Create
|
Create
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,4 +1,9 @@
|
||||||
import React, { useState } from 'react';
|
import type { FormikHelpers } from 'formik';
|
||||||
|
import { Form, Formik } from 'formik';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import tw from 'twin.macro';
|
||||||
|
import { object, string } from 'yup';
|
||||||
|
|
||||||
import createNest from '@/api/admin/nests/createNest';
|
import createNest from '@/api/admin/nests/createNest';
|
||||||
import getNests from '@/api/admin/nests/getNests';
|
import getNests from '@/api/admin/nests/getNests';
|
||||||
import Button from '@/components/elements/Button';
|
import Button from '@/components/elements/Button';
|
||||||
|
@ -6,25 +11,19 @@ import Field from '@/components/elements/Field';
|
||||||
import Modal from '@/components/elements/Modal';
|
import Modal from '@/components/elements/Modal';
|
||||||
import FlashMessageRender from '@/components/FlashMessageRender';
|
import FlashMessageRender from '@/components/FlashMessageRender';
|
||||||
import useFlash from '@/plugins/useFlash';
|
import useFlash from '@/plugins/useFlash';
|
||||||
import { Form, Formik, FormikHelpers } from 'formik';
|
|
||||||
import { object, string } from 'yup';
|
|
||||||
import tw from 'twin.macro';
|
|
||||||
|
|
||||||
interface Values {
|
interface Values {
|
||||||
name: string,
|
name: string;
|
||||||
description: string,
|
description: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const schema = object().shape({
|
const schema = object().shape({
|
||||||
name: string()
|
name: string().required('A nest name must be provided.').max(32, 'Nest name must not exceed 32 characters.'),
|
||||||
.required('A nest name must be provided.')
|
description: string().max(255, 'Nest description must not exceed 255 characters.'),
|
||||||
.max(32, 'Nest name must not exceed 32 characters.'),
|
|
||||||
description: string()
|
|
||||||
.max(255, 'Nest description must not exceed 255 characters.'),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
const [ visible, setVisible ] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
const { clearFlashes, clearAndAddHttpError } = useFlash();
|
const { clearFlashes, clearAndAddHttpError } = useFlash();
|
||||||
const { mutate } = getNests();
|
const { mutate } = getNests();
|
||||||
|
|
||||||
|
@ -33,7 +32,7 @@ export default () => {
|
||||||
setSubmitting(true);
|
setSubmitting(true);
|
||||||
|
|
||||||
createNest(name, description)
|
createNest(name, description)
|
||||||
.then(async (nest) => {
|
.then(async nest => {
|
||||||
await mutate(data => ({ ...data!, items: data!.items.concat(nest) }), false);
|
await mutate(data => ({ ...data!, items: data!.items.concat(nest) }), false);
|
||||||
setVisible(false);
|
setVisible(false);
|
||||||
})
|
})
|
||||||
|
@ -45,66 +44,65 @@ export default () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Formik
|
<Formik onSubmit={submit} initialValues={{ name: '', description: '' }} validationSchema={schema}>
|
||||||
onSubmit={submit}
|
{({ isSubmitting, resetForm }) => (
|
||||||
initialValues={{ name: '', description: '' }}
|
<Modal
|
||||||
validationSchema={schema}
|
visible={visible}
|
||||||
>
|
dismissable={!isSubmitting}
|
||||||
{
|
showSpinnerOverlay={isSubmitting}
|
||||||
({ isSubmitting, resetForm }) => (
|
onDismissed={() => {
|
||||||
<Modal
|
resetForm();
|
||||||
visible={visible}
|
setVisible(false);
|
||||||
dismissable={!isSubmitting}
|
}}
|
||||||
showSpinnerOverlay={isSubmitting}
|
>
|
||||||
onDismissed={() => {
|
<FlashMessageRender byKey={'nest:create'} css={tw`mb-6`} />
|
||||||
resetForm();
|
|
||||||
setVisible(false);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<FlashMessageRender byKey={'nest:create'} css={tw`mb-6`}/>
|
|
||||||
|
|
||||||
<h2 css={tw`mb-6 text-2xl text-neutral-100`}>New Nest</h2>
|
<h2 css={tw`mb-6 text-2xl text-neutral-100`}>New Nest</h2>
|
||||||
|
|
||||||
<Form css={tw`m-0`}>
|
<Form css={tw`m-0`}>
|
||||||
|
<Field
|
||||||
|
type={'text'}
|
||||||
|
id={'name'}
|
||||||
|
name={'name'}
|
||||||
|
label={'Name'}
|
||||||
|
description={'A short name used to identify this nest.'}
|
||||||
|
autoFocus
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div css={tw`mt-6`}>
|
||||||
<Field
|
<Field
|
||||||
type={'text'}
|
type={'text'}
|
||||||
id={'name'}
|
id={'description'}
|
||||||
name={'name'}
|
name={'description'}
|
||||||
label={'Name'}
|
label={'Description'}
|
||||||
description={'A short name used to identify this nest.'}
|
description={'A description for this nest.'}
|
||||||
autoFocus
|
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div css={tw`mt-6`}>
|
<div css={tw`flex flex-wrap justify-end mt-6`}>
|
||||||
<Field
|
<Button
|
||||||
type={'text'}
|
type={'button'}
|
||||||
id={'description'}
|
isSecondary
|
||||||
name={'description'}
|
css={tw`w-full sm:w-auto sm:mr-2`}
|
||||||
label={'Description'}
|
onClick={() => setVisible(false)}
|
||||||
description={'A description for this nest.'}
|
>
|
||||||
/>
|
Cancel
|
||||||
</div>
|
</Button>
|
||||||
|
<Button css={tw`w-full mt-4 sm:w-auto sm:mt-0`} type={'submit'}>
|
||||||
<div css={tw`flex flex-wrap justify-end mt-6`}>
|
Create Nest
|
||||||
<Button
|
</Button>
|
||||||
type={'button'}
|
</div>
|
||||||
isSecondary
|
</Form>
|
||||||
css={tw`w-full sm:w-auto sm:mr-2`}
|
</Modal>
|
||||||
onClick={() => setVisible(false)}
|
)}
|
||||||
>
|
|
||||||
Cancel
|
|
||||||
</Button>
|
|
||||||
<Button css={tw`w-full mt-4 sm:w-auto sm:mt-0`} type={'submit'}>
|
|
||||||
Create Nest
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</Form>
|
|
||||||
</Modal>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</Formik>
|
</Formik>
|
||||||
|
|
||||||
<Button type={'button'} size={'large'} css={tw`h-10 px-4 py-0 whitespace-nowrap`} onClick={() => setVisible(true)}>
|
<Button
|
||||||
|
type={'button'}
|
||||||
|
size={'large'}
|
||||||
|
css={tw`h-10 px-4 py-0 whitespace-nowrap`}
|
||||||
|
onClick={() => setVisible(true)}
|
||||||
|
>
|
||||||
New Nest
|
New Nest
|
||||||
</Button>
|
</Button>
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -4,7 +4,8 @@ import { useState } from 'react';
|
||||||
import tw from 'twin.macro';
|
import tw from 'twin.macro';
|
||||||
|
|
||||||
import deleteEgg from '@/api/admin/eggs/deleteEgg';
|
import deleteEgg from '@/api/admin/eggs/deleteEgg';
|
||||||
import Button from '@/components/elements/Button';
|
import { Button } from '@/components/elements/button';
|
||||||
|
import { Shape } from '@/components/elements/button/types';
|
||||||
import ConfirmationModal from '@/components/elements/ConfirmationModal';
|
import ConfirmationModal from '@/components/elements/ConfirmationModal';
|
||||||
import type { ApplicationStore } from '@/state';
|
import type { ApplicationStore } from '@/state';
|
||||||
|
|
||||||
|
@ -52,7 +53,7 @@ export default ({ eggId, onDeleted }: Props) => {
|
||||||
Are you sure you want to delete this egg? You may only delete an egg with no servers using it.
|
Are you sure you want to delete this egg? You may only delete an egg with no servers using it.
|
||||||
</ConfirmationModal>
|
</ConfirmationModal>
|
||||||
|
|
||||||
<Button type={'button'} size={'xsmall'} color={'red'} onClick={() => setVisible(true)}>
|
<Button.Danger type="button" shape={Shape.IconSquare} onClick={() => setVisible(true)}>
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
fill="none"
|
fill="none"
|
||||||
|
@ -67,7 +68,7 @@ export default ({ eggId, onDeleted }: Props) => {
|
||||||
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
|
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</Button>
|
</Button.Danger>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,22 +1,25 @@
|
||||||
import { exportEgg } from '@/api/admin/egg';
|
import { LanguageDescription } from '@codemirror/language';
|
||||||
import SpinnerOverlay from '@/components/elements/SpinnerOverlay';
|
import { json } from '@codemirror/lang-json';
|
||||||
import useFlash from '@/plugins/useFlash';
|
|
||||||
// import { jsonLanguage } from '@codemirror/lang-json';
|
|
||||||
// import Editor from '@/components/elements/Editor';
|
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import Button from '@/components/elements/Button';
|
|
||||||
import Modal from '@/components/elements/Modal';
|
|
||||||
import FlashMessageRender from '@/components/FlashMessageRender';
|
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
import tw from 'twin.macro';
|
import tw from 'twin.macro';
|
||||||
|
|
||||||
|
import { exportEgg } from '@/api/admin/egg';
|
||||||
|
import FlashMessageRender from '@/components/FlashMessageRender';
|
||||||
|
import { Button } from '@/components/elements/button';
|
||||||
|
import { Variant } from '@/components/elements/button/types';
|
||||||
|
import { Editor } from '@/components/elements/editor';
|
||||||
|
import Modal from '@/components/elements/Modal';
|
||||||
|
import SpinnerOverlay from '@/components/elements/SpinnerOverlay';
|
||||||
|
import useFlash from '@/plugins/useFlash';
|
||||||
|
|
||||||
export default ({ className }: { className?: string }) => {
|
export default ({ className }: { className?: string }) => {
|
||||||
const params = useParams<'id'>();
|
const params = useParams<'id'>();
|
||||||
const { clearAndAddHttpError, clearFlashes } = useFlash();
|
const { clearAndAddHttpError, clearFlashes } = useFlash();
|
||||||
|
|
||||||
const [visible, setVisible] = useState<boolean>(false);
|
const [visible, setVisible] = useState<boolean>(false);
|
||||||
const [loading, setLoading] = useState<boolean>(true);
|
const [loading, setLoading] = useState<boolean>(true);
|
||||||
const [_content, setContent] = useState<Record<string, any> | null>(null);
|
const [content, setContent] = useState<string | undefined>(undefined);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!visible) {
|
if (!visible) {
|
||||||
|
@ -45,21 +48,22 @@ export default ({ className }: { className?: string }) => {
|
||||||
<h2 css={tw`mb-6 text-2xl text-neutral-100`}>Export Egg</h2>
|
<h2 css={tw`mb-6 text-2xl text-neutral-100`}>Export Egg</h2>
|
||||||
<FlashMessageRender byKey={'egg:export'} css={tw`mb-6`} />
|
<FlashMessageRender byKey={'egg:export'} css={tw`mb-6`} />
|
||||||
|
|
||||||
{/*<Editor*/}
|
<Editor
|
||||||
{/* overrides={tw`h-[32rem] rounded`}*/}
|
className="h-[32rem] overflow-scroll rounded"
|
||||||
{/* initialContent={content !== null ? JSON.stringify(content, null, '\t') : ''}*/}
|
initialContent={content ?? ''}
|
||||||
{/* mode={jsonLanguage}*/}
|
language={LanguageDescription.of({ name: 'json', support: json() })}
|
||||||
{/*/>*/}
|
/>
|
||||||
|
|
||||||
<div css={tw`flex flex-wrap justify-end mt-4 sm:mt-6`}>
|
<div css={tw`flex flex-wrap justify-end mt-4 sm:mt-6`}>
|
||||||
<Button
|
<Button.Text
|
||||||
type={'button'}
|
type="button"
|
||||||
|
variant={Variant.Secondary}
|
||||||
css={tw`w-full sm:w-auto sm:mr-2`}
|
css={tw`w-full sm:w-auto sm:mr-2`}
|
||||||
onClick={() => setVisible(false)}
|
onClick={() => setVisible(false)}
|
||||||
isSecondary
|
|
||||||
>
|
>
|
||||||
Close
|
Close
|
||||||
</Button>
|
</Button.Text>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
css={tw`w-full sm:w-auto mt-4 sm:mt-0`}
|
css={tw`w-full sm:w-auto mt-4 sm:mt-0`}
|
||||||
// onClick={submit}
|
// onClick={submit}
|
||||||
|
@ -70,16 +74,14 @@ export default ({ className }: { className?: string }) => {
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
<Button
|
<Button.Text
|
||||||
type={'button'}
|
type="button"
|
||||||
size={'small'}
|
|
||||||
css={tw`px-4 py-0 whitespace-nowrap`}
|
css={tw`px-4 py-0 whitespace-nowrap`}
|
||||||
className={className}
|
className={className}
|
||||||
onClick={() => setVisible(true)}
|
onClick={() => setVisible(true)}
|
||||||
isSecondary
|
|
||||||
>
|
>
|
||||||
Export
|
Export
|
||||||
</Button>
|
</Button.Text>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,15 +1,18 @@
|
||||||
|
import { LanguageDescription, LanguageSupport, StreamLanguage } from '@codemirror/language';
|
||||||
|
import { shell } from '@codemirror/legacy-modes/mode/shell';
|
||||||
|
import { faScroll } from '@fortawesome/free-solid-svg-icons';
|
||||||
|
import type { FormikHelpers } from 'formik';
|
||||||
|
import { Form, Formik } from 'formik';
|
||||||
|
import tw from 'twin.macro';
|
||||||
|
|
||||||
import { useEggFromRoute } from '@/api/admin/egg';
|
import { useEggFromRoute } from '@/api/admin/egg';
|
||||||
import updateEgg from '@/api/admin/eggs/updateEgg';
|
import updateEgg from '@/api/admin/eggs/updateEgg';
|
||||||
import Field from '@/components/elements/Field';
|
|
||||||
import useFlash from '@/plugins/useFlash';
|
|
||||||
// import { shell } from '@codemirror/legacy-modes/mode/shell';
|
|
||||||
import { faScroll } from '@fortawesome/free-solid-svg-icons';
|
|
||||||
import { Form, Formik, FormikHelpers } from 'formik';
|
|
||||||
import tw from 'twin.macro';
|
|
||||||
import AdminBox from '@/components/admin/AdminBox';
|
import AdminBox from '@/components/admin/AdminBox';
|
||||||
import Button from '@/components/elements/Button';
|
import { Button } from '@/components/elements/button';
|
||||||
// import Editor from '@/components/elements/Editor';
|
import { Editor } from '@/components/elements/editor';
|
||||||
|
import Field from '@/components/elements/Field';
|
||||||
import SpinnerOverlay from '@/components/elements/SpinnerOverlay';
|
import SpinnerOverlay from '@/components/elements/SpinnerOverlay';
|
||||||
|
import useFlash from '@/plugins/useFlash';
|
||||||
|
|
||||||
interface Values {
|
interface Values {
|
||||||
scriptContainer: string;
|
scriptContainer: string;
|
||||||
|
@ -60,14 +63,17 @@ export default function EggInstallContainer() {
|
||||||
<SpinnerOverlay visible={isSubmitting} />
|
<SpinnerOverlay visible={isSubmitting} />
|
||||||
|
|
||||||
<Form>
|
<Form>
|
||||||
{/*<Editor*/}
|
<Editor
|
||||||
{/* overrides={tw`h-96 mb-4`}*/}
|
className="mb-4 h-96 overflow-scroll"
|
||||||
{/* initialContent={egg.scriptInstall || ''}*/}
|
initialContent={egg.scriptInstall || ''}
|
||||||
{/* mode={shell}*/}
|
fetchContent={value => {
|
||||||
{/* fetchContent={value => {*/}
|
fetchFileContent = value;
|
||||||
{/* fetchFileContent = value;*/}
|
}}
|
||||||
{/* }}*/}
|
language={LanguageDescription.of({
|
||||||
{/*/>*/}
|
name: 'shell',
|
||||||
|
support: new LanguageSupport(StreamLanguage.define(shell)),
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
|
||||||
<div css={tw`mx-6 mb-4`}>
|
<div css={tw`mx-6 mb-4`}>
|
||||||
<div css={tw`grid grid-cols-3 gap-x-8 gap-y-6`}>
|
<div css={tw`grid grid-cols-3 gap-x-8 gap-y-6`}>
|
||||||
|
@ -92,12 +98,7 @@ export default function EggInstallContainer() {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div css={tw`flex flex-row border-t border-neutral-600`}>
|
<div css={tw`flex flex-row border-t border-neutral-600`}>
|
||||||
<Button
|
<Button type="submit" css={tw`ml-auto mr-6 mt-4`} disabled={isSubmitting || !isValid}>
|
||||||
type={'submit'}
|
|
||||||
size={'small'}
|
|
||||||
css={tw`ml-auto mr-6 mt-4`}
|
|
||||||
disabled={isSubmitting || !isValid}
|
|
||||||
>
|
|
||||||
Save Changes
|
Save Changes
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,23 +1,26 @@
|
||||||
|
import { LanguageDescription } from '@codemirror/language';
|
||||||
|
import { json } from '@codemirror/lang-json';
|
||||||
|
import { faDocker } from '@fortawesome/free-brands-svg-icons';
|
||||||
|
import { faEgg, faFireAlt, faMicrochip, faTerminal } from '@fortawesome/free-solid-svg-icons';
|
||||||
|
import type { FormikHelpers } from 'formik';
|
||||||
|
import { Form, Formik, useFormikContext } from 'formik';
|
||||||
|
import { forwardRef, useImperativeHandle, useRef } from 'react';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import tw from 'twin.macro';
|
||||||
|
import { object } from 'yup';
|
||||||
|
|
||||||
import { useEggFromRoute } from '@/api/admin/egg';
|
import { useEggFromRoute } from '@/api/admin/egg';
|
||||||
import updateEgg from '@/api/admin/eggs/updateEgg';
|
import updateEgg from '@/api/admin/eggs/updateEgg';
|
||||||
|
import AdminBox from '@/components/admin/AdminBox';
|
||||||
import EggDeleteButton from '@/components/admin/nests/eggs/EggDeleteButton';
|
import EggDeleteButton from '@/components/admin/nests/eggs/EggDeleteButton';
|
||||||
import EggExportButton from '@/components/admin/nests/eggs/EggExportButton';
|
import EggExportButton from '@/components/admin/nests/eggs/EggExportButton';
|
||||||
import Button from '@/components/elements/Button';
|
import { Button } from '@/components/elements/button';
|
||||||
// import Editor from '@/components/elements/Editor';
|
import { Editor } from '@/components/elements/editor';
|
||||||
import Field, { TextareaField } from '@/components/elements/Field';
|
import Field, { TextareaField } from '@/components/elements/Field';
|
||||||
import Input from '@/components/elements/Input';
|
import Input from '@/components/elements/Input';
|
||||||
import Label from '@/components/elements/Label';
|
import Label from '@/components/elements/Label';
|
||||||
import SpinnerOverlay from '@/components/elements/SpinnerOverlay';
|
import SpinnerOverlay from '@/components/elements/SpinnerOverlay';
|
||||||
import useFlash from '@/plugins/useFlash';
|
import useFlash from '@/plugins/useFlash';
|
||||||
// import { jsonLanguage } from '@codemirror/lang-json';
|
|
||||||
import { faDocker } from '@fortawesome/free-brands-svg-icons';
|
|
||||||
import { faEgg, faFireAlt, faMicrochip, faTerminal } from '@fortawesome/free-solid-svg-icons';
|
|
||||||
import { forwardRef, useImperativeHandle, useRef } from 'react';
|
|
||||||
import AdminBox from '@/components/admin/AdminBox';
|
|
||||||
import { useNavigate } from 'react-router-dom';
|
|
||||||
import tw from 'twin.macro';
|
|
||||||
import { object } from 'yup';
|
|
||||||
import { Form, Formik, FormikHelpers, useFormikContext } from 'formik';
|
|
||||||
|
|
||||||
export function EggInformationContainer() {
|
export function EggInformationContainer() {
|
||||||
const { isSubmitting } = useFormikContext();
|
const { isSubmitting } = useFormikContext();
|
||||||
|
@ -104,8 +107,7 @@ export const EggProcessContainer = forwardRef<any, EggProcessContainerProps>(fun
|
||||||
{ className },
|
{ className },
|
||||||
ref,
|
ref,
|
||||||
) {
|
) {
|
||||||
// const { isSubmitting, values } = useFormikContext<Values>();
|
const { isSubmitting, values } = useFormikContext<Values>();
|
||||||
const { isSubmitting } = useFormikContext<Values>();
|
|
||||||
|
|
||||||
let fetchStartupConfiguration: (() => Promise<string>) | null = null;
|
let fetchStartupConfiguration: (() => Promise<string>) | null = null;
|
||||||
let fetchFilesConfiguration: (() => Promise<string>) | null = null;
|
let fetchFilesConfiguration: (() => Promise<string>) | null = null;
|
||||||
|
@ -132,26 +134,26 @@ export const EggProcessContainer = forwardRef<any, EggProcessContainerProps>(fun
|
||||||
|
|
||||||
<div css={tw`mb-5`}>
|
<div css={tw`mb-5`}>
|
||||||
<Label>Startup Configuration</Label>
|
<Label>Startup Configuration</Label>
|
||||||
{/*<Editor*/}
|
<Editor
|
||||||
{/* mode={jsonLanguage}*/}
|
className="h-32 overflow-scroll rounded"
|
||||||
{/* initialContent={values.configStartup}*/}
|
initialContent={values.configStartup}
|
||||||
{/* overrides={tw`h-32 rounded`}*/}
|
fetchContent={value => {
|
||||||
{/* fetchContent={value => {*/}
|
fetchStartupConfiguration = value;
|
||||||
{/* fetchStartupConfiguration = value;*/}
|
}}
|
||||||
{/* }}*/}
|
language={LanguageDescription.of({ name: 'json', support: json() })}
|
||||||
{/*/>*/}
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div css={tw`mb-1`}>
|
<div css={tw`mb-1`}>
|
||||||
<Label>Configuration Files</Label>
|
<Label>Configuration Files</Label>
|
||||||
{/*<Editor*/}
|
<Editor
|
||||||
{/* mode={jsonLanguage}*/}
|
className="h-48 overflow-scroll rounded"
|
||||||
{/* initialContent={values.configFiles}*/}
|
initialContent={values.configFiles}
|
||||||
{/* overrides={tw`h-48 rounded`}*/}
|
fetchContent={value => {
|
||||||
{/* fetchContent={value => {*/}
|
fetchFilesConfiguration = value;
|
||||||
{/* fetchFilesConfiguration = value;*/}
|
}}
|
||||||
{/* }}*/}
|
language={LanguageDescription.of({ name: 'json', support: json() })}
|
||||||
{/*/>*/}
|
/>
|
||||||
</div>
|
</div>
|
||||||
</AdminBox>
|
</AdminBox>
|
||||||
);
|
);
|
||||||
|
@ -233,7 +235,7 @@ export default function EggSettingsContainer() {
|
||||||
<div css={tw`flex flex-row`}>
|
<div css={tw`flex flex-row`}>
|
||||||
<EggDeleteButton eggId={egg.id} onDeleted={() => navigate('/admin/nests')} />
|
<EggDeleteButton eggId={egg.id} onDeleted={() => navigate('/admin/nests')} />
|
||||||
<EggExportButton css={tw`ml-auto mr-4`} />
|
<EggExportButton css={tw`ml-auto mr-4`} />
|
||||||
<Button type="submit" size="small" disabled={isSubmitting || !isValid}>
|
<Button type="submit" disabled={isSubmitting || !isValid}>
|
||||||
Save Changes
|
Save Changes
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -13,11 +13,12 @@ import type { EggVariable } from '@/api/admin/egg';
|
||||||
import { useEggFromRoute } from '@/api/admin/egg';
|
import { useEggFromRoute } from '@/api/admin/egg';
|
||||||
import NewVariableButton from '@/components/admin/nests/eggs/NewVariableButton';
|
import NewVariableButton from '@/components/admin/nests/eggs/NewVariableButton';
|
||||||
import AdminBox from '@/components/admin/AdminBox';
|
import AdminBox from '@/components/admin/AdminBox';
|
||||||
import Button from '@/components/elements/Button';
|
import { Button } from '@/components/elements/button';
|
||||||
import Checkbox from '@/components/elements/Checkbox';
|
import Checkbox from '@/components/elements/Checkbox';
|
||||||
import Field, { FieldRow, TextareaField } from '@/components/elements/Field';
|
import Field, { FieldRow, TextareaField } from '@/components/elements/Field';
|
||||||
import SpinnerOverlay from '@/components/elements/SpinnerOverlay';
|
import SpinnerOverlay from '@/components/elements/SpinnerOverlay';
|
||||||
import useFlash from '@/plugins/useFlash';
|
import useFlash from '@/plugins/useFlash';
|
||||||
|
import Label from '@/components/elements/Label';
|
||||||
|
|
||||||
export const validationSchema = object().shape({
|
export const validationSchema = object().shape({
|
||||||
name: string().required().min(1).max(191),
|
name: string().required().min(1).max(191),
|
||||||
|
@ -59,14 +60,23 @@ export function EggVariableForm({ prefix }: { prefix: string }) {
|
||||||
</FieldRow>
|
</FieldRow>
|
||||||
|
|
||||||
<div css={tw`flex flex-row mb-6`}>
|
<div css={tw`flex flex-row mb-6`}>
|
||||||
<Checkbox id={`${prefix}isUserViewable`} name={`${prefix}isUserViewable`} label={'User Viewable'} />
|
<div className="ml-auto flex flex-row">
|
||||||
|
{/* TODO: fix Checkbox component, current one is designed for subuser permissions and not for individual values */}
|
||||||
|
<Checkbox id={`${prefix}isUserViewable`} name={`${prefix}isUserViewable`} />
|
||||||
|
|
||||||
<Checkbox
|
<div css={tw`flex-1`}>
|
||||||
id={`${prefix}isUserEditable`}
|
<Label>User Viewable</Label>
|
||||||
name={`${prefix}isUserEditable`}
|
</div>
|
||||||
label={'User Editable'}
|
</div>
|
||||||
css={tw`ml-auto`}
|
|
||||||
/>
|
<div className="ml-auto flex flex-row">
|
||||||
|
{/* TODO: fix Checkbox component, current one is designed for subuser permissions and not for individual values */}
|
||||||
|
<Checkbox id={`${prefix}isUserEditable`} name={`${prefix}isUserEditable`} />
|
||||||
|
|
||||||
|
<div css={tw`flex-1`}>
|
||||||
|
<Label>User Editable</Label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Field
|
<Field
|
||||||
|
@ -111,7 +121,7 @@ function EggVariableDeleteButton({ onClick }: { onClick: (success: () => void) =
|
||||||
css={tw`ml-auto text-neutral-500 hover:text-neutral-300`}
|
css={tw`ml-auto text-neutral-500 hover:text-neutral-300`}
|
||||||
onClick={() => setVisible(true)}
|
onClick={() => setVisible(true)}
|
||||||
>
|
>
|
||||||
<TrashIcon css={tw`h-5 w-5`} />
|
<TrashIcon className="h-5 w-5" />
|
||||||
</button>
|
</button>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -200,12 +210,7 @@ export default function EggVariablesContainer() {
|
||||||
<div css={tw`flex flex-row`}>
|
<div css={tw`flex flex-row`}>
|
||||||
<NewVariableButton />
|
<NewVariableButton />
|
||||||
|
|
||||||
<Button
|
<Button type="submit" className="ml-auto" disabled={isSubmitting || !isValid}>
|
||||||
type={'submit'}
|
|
||||||
size={'small'}
|
|
||||||
css={tw`ml-auto`}
|
|
||||||
disabled={isSubmitting || !isValid}
|
|
||||||
>
|
|
||||||
Save Changes
|
Save Changes
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -9,8 +9,9 @@ import { useEggFromRoute } from '@/api/admin/egg';
|
||||||
import { EggVariableForm, validationSchema } from '@/components/admin/nests/eggs/EggVariablesContainer';
|
import { EggVariableForm, validationSchema } from '@/components/admin/nests/eggs/EggVariablesContainer';
|
||||||
import Modal from '@/components/elements/Modal';
|
import Modal from '@/components/elements/Modal';
|
||||||
import FlashMessageRender from '@/components/FlashMessageRender';
|
import FlashMessageRender from '@/components/FlashMessageRender';
|
||||||
import Button from '@/components/elements/Button';
|
import { Button } from '@/components/elements/button';
|
||||||
import useFlash from '@/plugins/useFlash';
|
import useFlash from '@/plugins/useFlash';
|
||||||
|
import { Variant } from '@/components/elements/button/types';
|
||||||
|
|
||||||
export default function NewVariableButton() {
|
export default function NewVariableButton() {
|
||||||
const { setValues } = useFormikContext();
|
const { setValues } = useFormikContext();
|
||||||
|
@ -75,16 +76,16 @@ export default function NewVariableButton() {
|
||||||
|
|
||||||
<div css={tw`flex flex-wrap justify-end mt-6`}>
|
<div css={tw`flex flex-wrap justify-end mt-6`}>
|
||||||
<Button
|
<Button
|
||||||
type={'button'}
|
type="button"
|
||||||
isSecondary
|
variant={Variant.Secondary}
|
||||||
css={tw`w-full sm:w-auto sm:mr-2`}
|
css={tw`w-full sm:w-auto sm:mr-2`}
|
||||||
onClick={() => setVisible(false)}
|
onClick={() => setVisible(false)}
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
|
type="submit"
|
||||||
css={tw`w-full mt-4 sm:w-auto sm:mt-0`}
|
css={tw`w-full mt-4 sm:w-auto sm:mt-0`}
|
||||||
type={'submit'}
|
|
||||||
disabled={isSubmitting || !isValid}
|
disabled={isSubmitting || !isValid}
|
||||||
>
|
>
|
||||||
Create Variable
|
Create Variable
|
||||||
|
@ -95,7 +96,8 @@ export default function NewVariableButton() {
|
||||||
)}
|
)}
|
||||||
</Formik>
|
</Formik>
|
||||||
|
|
||||||
<Button type={'button'} color={'green'} onClick={() => setVisible(true)}>
|
{/* TODO: make button green */}
|
||||||
|
<Button type="button" onClick={() => setVisible(true)}>
|
||||||
New Variable
|
New Variable
|
||||||
</Button>
|
</Button>
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -4,7 +4,6 @@ import { useState } from 'react';
|
||||||
import Checkbox from '@/components/elements/inputs/Checkbox';
|
import Checkbox from '@/components/elements/inputs/Checkbox';
|
||||||
import { Dropdown } from '@/components/elements/dropdown';
|
import { Dropdown } from '@/components/elements/dropdown';
|
||||||
import { Dialog } from '@/components/elements/dialog';
|
import { Dialog } from '@/components/elements/dialog';
|
||||||
import { Button } from '@/components/elements/button';
|
|
||||||
import { User } from '@definitions/admin';
|
import { User } from '@definitions/admin';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
@ -18,14 +17,17 @@ const UserTableRow = ({ user, selected, onRowChange }: Props) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Dialog title={'Delete account'} visible={visible} onDismissed={() => setVisible(false)}>
|
<Dialog.Confirm
|
||||||
<Dialog.Icon type={'danger'} />
|
title={'Delete account'}
|
||||||
|
open={visible}
|
||||||
|
onClose={() => setVisible(false)}
|
||||||
|
onConfirmed={() => {
|
||||||
|
console.log('yeet');
|
||||||
|
}}
|
||||||
|
>
|
||||||
This account will be permanently deleted.
|
This account will be permanently deleted.
|
||||||
<Dialog.Buttons>
|
</Dialog.Confirm>
|
||||||
<Button.Text onClick={() => setVisible(false)}>Cancel</Button.Text>
|
|
||||||
<Button.Danger>Delete</Button.Danger>
|
|
||||||
</Dialog.Buttons>
|
|
||||||
</Dialog>
|
|
||||||
<tr>
|
<tr>
|
||||||
<td className={'whitespace-nowrap'}>
|
<td className={'whitespace-nowrap'}>
|
||||||
<div className={'flex justify-end items-center w-8'}>
|
<div className={'flex justify-end items-center w-8'}>
|
||||||
|
|
|
@ -12,6 +12,7 @@ import TFootPaginated from '@/components/elements/table/TFootPaginated';
|
||||||
import type { User } from '@definitions/admin';
|
import type { User } from '@definitions/admin';
|
||||||
import extractSearchFilters from '@/helpers/extractSearchFilters';
|
import extractSearchFilters from '@/helpers/extractSearchFilters';
|
||||||
import useDebouncedState from '@/plugins/useDebouncedState';
|
import useDebouncedState from '@/plugins/useDebouncedState';
|
||||||
|
import { Shape } from '@/components/elements/button/types';
|
||||||
|
|
||||||
const filters = ['id', 'uuid', 'external_id', 'username', 'email'] as const;
|
const filters = ['id', 'uuid', 'external_id', 'username', 'email'] as const;
|
||||||
|
|
||||||
|
@ -77,13 +78,13 @@ const UsersContainer = () => {
|
||||||
onChange={onSelectAll}
|
onChange={onSelectAll}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Button.Text square>
|
<Button.Text shape={Shape.IconSquare}>
|
||||||
<SupportIcon className={'w-4 h-4'} />
|
<SupportIcon className={'w-4 h-4'} />
|
||||||
</Button.Text>
|
</Button.Text>
|
||||||
<Button.Text square>
|
<Button.Text shape={Shape.IconSquare}>
|
||||||
<LockOpenIcon className={'w-4 h-4'} />
|
<LockOpenIcon className={'w-4 h-4'} />
|
||||||
</Button.Text>
|
</Button.Text>
|
||||||
<Button.Text square>
|
<Button.Text shape={Shape.IconSquare}>
|
||||||
<TrashIcon className={'w-4 h-4'} />
|
<TrashIcon className={'w-4 h-4'} />
|
||||||
</Button.Text>
|
</Button.Text>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
import { Field, FieldProps } from 'formik';
|
import type { FieldProps } from 'formik';
|
||||||
|
import { Field } from 'formik';
|
||||||
|
|
||||||
import Input from '@/components/elements/Input';
|
import Input from '@/components/elements/Input';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
value: string;
|
value?: string;
|
||||||
|
label?: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,318 +0,0 @@
|
||||||
import { autocompletion, completionKeymap } from '@codemirror/autocomplete';
|
|
||||||
import { closeBrackets, closeBracketsKeymap } from '@codemirror/closebrackets';
|
|
||||||
import { defaultKeymap, indentWithTab } from '@codemirror/commands';
|
|
||||||
import { commentKeymap } from '@codemirror/comment';
|
|
||||||
import { foldGutter, foldKeymap } from '@codemirror/fold';
|
|
||||||
import { lineNumbers, highlightActiveLineGutter } from '@codemirror/gutter';
|
|
||||||
import { defaultHighlightStyle } from '@codemirror/highlight';
|
|
||||||
import { history, historyKeymap } from '@codemirror/history';
|
|
||||||
import { indentOnInput, LanguageSupport, LRLanguage, indentUnit } from '@codemirror/language';
|
|
||||||
import { lintKeymap } from '@codemirror/lint';
|
|
||||||
import { bracketMatching } from '@codemirror/matchbrackets';
|
|
||||||
import { rectangularSelection } from '@codemirror/rectangular-selection';
|
|
||||||
import { searchKeymap, highlightSelectionMatches } from '@codemirror/search';
|
|
||||||
import { Compartment, Extension, EditorState } from '@codemirror/state';
|
|
||||||
import { StreamLanguage, StreamParser } from '@codemirror/stream-parser';
|
|
||||||
import { keymap, highlightSpecialChars, drawSelection, highlightActiveLine, EditorView } from '@codemirror/view';
|
|
||||||
import { clike } from '@codemirror/legacy-modes/mode/clike';
|
|
||||||
import { cpp } from '@codemirror/lang-cpp';
|
|
||||||
import { css } from '@codemirror/lang-css';
|
|
||||||
import { Cassandra, MariaSQL, MSSQL, MySQL, PostgreSQL, sql, SQLite, StandardSQL } from '@codemirror/lang-sql';
|
|
||||||
import { diff } from '@codemirror/legacy-modes/mode/diff';
|
|
||||||
import { dockerFile } from '@codemirror/legacy-modes/mode/dockerfile';
|
|
||||||
import { markdown, markdownLanguage } from '@codemirror/lang-markdown';
|
|
||||||
import { go } from '@codemirror/legacy-modes/mode/go';
|
|
||||||
import { html } from '@codemirror/lang-html';
|
|
||||||
import { http } from '@codemirror/legacy-modes/mode/http';
|
|
||||||
import { javascript, typescriptLanguage } from '@codemirror/lang-javascript';
|
|
||||||
import { json } from '@codemirror/lang-json';
|
|
||||||
import { lua } from '@codemirror/legacy-modes/mode/lua';
|
|
||||||
import { properties } from '@codemirror/legacy-modes/mode/properties';
|
|
||||||
import { python } from '@codemirror/legacy-modes/mode/python';
|
|
||||||
import { ruby } from '@codemirror/legacy-modes/mode/ruby';
|
|
||||||
import { rust } from '@codemirror/lang-rust';
|
|
||||||
import { shell } from '@codemirror/legacy-modes/mode/shell';
|
|
||||||
import { toml } from '@codemirror/legacy-modes/mode/toml';
|
|
||||||
import { xml } from '@codemirror/lang-xml';
|
|
||||||
import { yaml } from '@codemirror/legacy-modes/mode/yaml';
|
|
||||||
import React, { useCallback, useEffect, useState } from 'react';
|
|
||||||
import tw, { styled, TwStyle } from 'twin.macro';
|
|
||||||
import { ayuMirage } from '@/components/elements/EditorTheme';
|
|
||||||
|
|
||||||
type EditorMode = LanguageSupport | LRLanguage | StreamParser<unknown>;
|
|
||||||
|
|
||||||
export interface Mode {
|
|
||||||
name: string;
|
|
||||||
mime: string;
|
|
||||||
mimes?: string[];
|
|
||||||
mode?: EditorMode;
|
|
||||||
ext?: string[];
|
|
||||||
alias?: string[];
|
|
||||||
file?: RegExp;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const modes: Mode[] = [
|
|
||||||
{ name: 'C', mime: 'text/x-csrc', mode: clike({}), ext: [ 'c', 'h', 'ino' ] },
|
|
||||||
{ name: 'C++', mime: 'text/x-c++src', mode: cpp(), ext: [ 'cpp', 'c++', 'cc', 'cxx', 'hpp', 'h++', 'hh', 'hxx' ], alias: [ 'cpp' ] },
|
|
||||||
{ name: 'C#', mime: 'text/x-csharp', mode: clike({}), ext: [ 'cs' ], alias: [ 'csharp', 'cs' ] },
|
|
||||||
{ name: 'CSS', mime: 'text/css', mode: css(), ext: [ 'css' ] },
|
|
||||||
{ name: 'CQL', mime: 'text/x-cassandra', mode: sql({ dialect: Cassandra }), ext: [ 'cql' ] },
|
|
||||||
{ name: 'Diff', mime: 'text/x-diff', mode: diff, ext: [ 'diff', 'patch' ] },
|
|
||||||
{ name: 'Dockerfile', mime: 'text/x-dockerfile', mode: dockerFile, file: /^Dockerfile$/ },
|
|
||||||
{ name: 'Git Markdown', mime: 'text/x-gfm', mode: markdown({ defaultCodeLanguage: markdownLanguage }), file: /^(readme|contributing|history|license).md$/i },
|
|
||||||
{ name: 'Golang', mime: 'text/x-go', mode: go, ext: [ 'go' ] },
|
|
||||||
{ name: 'HTML', mime: 'text/html', mode: html(), ext: [ 'html', 'htm', 'handlebars', 'hbs' ], alias: [ 'xhtml' ] },
|
|
||||||
{ name: 'HTTP', mime: 'message/http', mode: http },
|
|
||||||
{ name: 'JavaScript', mime: 'text/javascript', mimes: [ 'text/javascript', 'text/ecmascript', 'application/javascript', 'application/x-javascript', 'application/ecmascript' ], mode: javascript(), ext: [ 'js' ], alias: [ 'ecmascript', 'js', 'node' ] },
|
|
||||||
{ name: 'JSON', mime: 'application/json', mimes: [ 'application/json', 'application/x-json' ], mode: json(), ext: [ 'json', 'json5', 'map' ], alias: [ 'json5' ] },
|
|
||||||
{ name: 'Lua', mime: 'text/x-lua', mode: lua, ext: [ 'lua' ] },
|
|
||||||
{ name: 'Markdown', mime: 'text/x-markdown', mode: markdown({ defaultCodeLanguage: markdownLanguage }), ext: [ 'markdown', 'md', 'mkd' ] },
|
|
||||||
{ name: 'MariaDB', mime: 'text/x-mariadb', mode: sql({ dialect: MariaSQL }) },
|
|
||||||
{ name: 'MS SQL', mime: 'text/x-mssql', mode: sql({ dialect: MSSQL }) },
|
|
||||||
{ name: 'MySQL', mime: 'text/x-mysql', mode: sql({ dialect: MySQL }) },
|
|
||||||
{ name: 'Plain Text', mime: 'text/plain', mode: undefined, ext: [ 'txt', 'text', 'conf', 'def', 'list', 'log' ] },
|
|
||||||
{ name: 'PostgreSQL', mime: 'text/x-pgsql', mode: sql({ dialect: PostgreSQL }) },
|
|
||||||
{ name: 'Properties', mime: 'text/x-properties', mode: properties, ext: [ 'properties', 'ini', 'in' ], alias: [ 'ini', 'properties' ] },
|
|
||||||
{ name: 'Python', mime: 'text/x-python', mode: python, ext: [ 'BUILD', 'bzl', 'py', 'pyw' ], file: /^(BUCK|BUILD)$/ },
|
|
||||||
{ name: 'Ruby', mime: 'text/x-ruby', mode: ruby, ext: [ 'rb' ], alias: [ 'jruby', 'macruby', 'rake', 'rb', 'rbx' ] },
|
|
||||||
{ name: 'Rust', mime: 'text/x-rustsrc', mode: rust(), ext: [ 'rs' ] },
|
|
||||||
{ name: 'Sass', mime: 'text/x-sass', mode: css(), ext: [ 'sass' ] },
|
|
||||||
{ name: 'SCSS', mime: 'text/x-scss', mode: css(), ext: [ 'scss' ] },
|
|
||||||
{ name: 'Shell', mime: 'text/x-sh', mimes: [ 'text/x-sh', 'application/x-sh' ], mode: shell, ext: [ 'sh', 'ksh', 'bash' ], alias: [ 'bash', 'sh', 'zsh' ], file: /^PKGBUILD$/ },
|
|
||||||
{ name: 'SQL', mime: 'text/x-sql', mode: sql({ dialect: StandardSQL }), ext: [ 'sql' ] },
|
|
||||||
{ name: 'SQLite', mime: 'text/x-sqlite', mode: sql({ dialect: SQLite }) },
|
|
||||||
{ name: 'TOML', mime: 'text/x-toml', mode: toml, ext: [ 'toml' ] },
|
|
||||||
{ name: 'TypeScript', mime: 'application/typescript', mode: typescriptLanguage, ext: [ 'ts' ], alias: [ 'ts' ] },
|
|
||||||
{ name: 'XML', mime: 'application/xml', mimes: [ 'application/xml', 'text/xml' ], mode: xml(), ext: [ 'xml', 'xsl', 'xsd', 'svg' ], alias: [ 'rss', 'wsdl', 'xsd' ] },
|
|
||||||
{ name: 'YAML', mime: 'text/x-yaml', mimes: [ 'text/x-yaml', 'text/yaml' ], mode: yaml, ext: [ 'yaml', 'yml' ], alias: [ 'yml' ] },
|
|
||||||
];
|
|
||||||
|
|
||||||
export const modeToExtension = (m: EditorMode): Extension => {
|
|
||||||
if (m instanceof LanguageSupport) {
|
|
||||||
return m;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m instanceof LRLanguage) {
|
|
||||||
return m;
|
|
||||||
}
|
|
||||||
|
|
||||||
return StreamLanguage.define(m);
|
|
||||||
};
|
|
||||||
|
|
||||||
const findModeByFilename = (filename: string): Mode => {
|
|
||||||
for (let i = 0; i < modes.length; i++) {
|
|
||||||
const info = modes[i];
|
|
||||||
|
|
||||||
if (info.file && info.file.test(filename)) {
|
|
||||||
return info;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const dot = filename.lastIndexOf('.');
|
|
||||||
const ext = dot > -1 && filename.substring(dot + 1, filename.length);
|
|
||||||
|
|
||||||
if (ext) {
|
|
||||||
for (let i = 0; i < modes.length; i++) {
|
|
||||||
const info = modes[i];
|
|
||||||
if (info.ext) {
|
|
||||||
for (let j = 0; j < info.ext.length; j++) {
|
|
||||||
if (info.ext[j] === ext) {
|
|
||||||
return info;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const plainText = modes.find(m => m.mime === 'text/plain');
|
|
||||||
if (plainText === undefined) {
|
|
||||||
throw new Error('failed to find \'text/plain\' mode');
|
|
||||||
}
|
|
||||||
return plainText;
|
|
||||||
};
|
|
||||||
|
|
||||||
const findLanguageExtensionByMode = (mode: Mode): Extension => {
|
|
||||||
if (mode.mode === undefined) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
return modeToExtension(mode.mode);
|
|
||||||
};
|
|
||||||
|
|
||||||
const defaultExtensions: Extension = [
|
|
||||||
ayuMirage,
|
|
||||||
|
|
||||||
lineNumbers(),
|
|
||||||
highlightActiveLineGutter(),
|
|
||||||
highlightSpecialChars(),
|
|
||||||
history(),
|
|
||||||
foldGutter(),
|
|
||||||
drawSelection(),
|
|
||||||
EditorState.allowMultipleSelections.of(true),
|
|
||||||
indentOnInput(),
|
|
||||||
defaultHighlightStyle.fallback,
|
|
||||||
bracketMatching(),
|
|
||||||
closeBrackets(),
|
|
||||||
autocompletion(),
|
|
||||||
rectangularSelection(),
|
|
||||||
highlightActiveLine(),
|
|
||||||
highlightSelectionMatches(),
|
|
||||||
keymap.of([
|
|
||||||
...closeBracketsKeymap,
|
|
||||||
...defaultKeymap,
|
|
||||||
...searchKeymap,
|
|
||||||
...historyKeymap,
|
|
||||||
...foldKeymap,
|
|
||||||
...commentKeymap,
|
|
||||||
...completionKeymap,
|
|
||||||
...lintKeymap,
|
|
||||||
indentWithTab,
|
|
||||||
]),
|
|
||||||
EditorState.tabSize.of(4),
|
|
||||||
// This is gonna piss people off, but that isn't my problem.
|
|
||||||
indentUnit.of('\t'),
|
|
||||||
];
|
|
||||||
|
|
||||||
const EditorContainer = styled.div<{ overrides?: TwStyle }>`
|
|
||||||
//min-height: 12rem;
|
|
||||||
${tw`relative`};
|
|
||||||
|
|
||||||
& > div {
|
|
||||||
${props => props.overrides};
|
|
||||||
|
|
||||||
&.cm-focused {
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export interface Props {
|
|
||||||
className?: string;
|
|
||||||
style?: React.CSSProperties;
|
|
||||||
overrides?: TwStyle;
|
|
||||||
|
|
||||||
initialContent?: string;
|
|
||||||
extensions?: Extension[];
|
|
||||||
mode?: EditorMode;
|
|
||||||
|
|
||||||
filename?: string;
|
|
||||||
onModeChanged?: (mode: Mode) => void;
|
|
||||||
fetchContent?: (callback: () => Promise<string>) => void;
|
|
||||||
onContentSaved?: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ({ className, style, overrides, initialContent, extensions, mode, filename, onModeChanged, fetchContent, onContentSaved }: Props) => {
|
|
||||||
const [ languageConfig ] = useState<Compartment>(new Compartment());
|
|
||||||
const [ keybinds ] = useState<Compartment>(new Compartment());
|
|
||||||
const [ view, setView ] = useState<EditorView>();
|
|
||||||
|
|
||||||
const createEditorState = () => {
|
|
||||||
return EditorState.create({
|
|
||||||
doc: initialContent,
|
|
||||||
extensions: [
|
|
||||||
...defaultExtensions,
|
|
||||||
...(extensions !== undefined ? extensions : []),
|
|
||||||
|
|
||||||
languageConfig.of(mode !== undefined ? modeToExtension(mode) : findLanguageExtensionByMode(findModeByFilename(filename || ''))),
|
|
||||||
keybinds.of([]),
|
|
||||||
],
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const ref = useCallback((node) => {
|
|
||||||
if (!node) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const view = new EditorView({
|
|
||||||
state: createEditorState(),
|
|
||||||
parent: node,
|
|
||||||
});
|
|
||||||
setView(view);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// This useEffect is required to send the proper mode back to the parent element
|
|
||||||
// due to the initial language being set with EditorState#create, rather than in
|
|
||||||
// an useEffect like this one, or one watching `filename`.
|
|
||||||
useEffect(() => {
|
|
||||||
if (onModeChanged === undefined) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
onModeChanged(findModeByFilename(filename || ''));
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (view === undefined) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mode === undefined) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
view.dispatch({
|
|
||||||
effects: languageConfig.reconfigure(modeToExtension(mode)),
|
|
||||||
});
|
|
||||||
}, [ mode ]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (view === undefined) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (filename === undefined) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const mode = findModeByFilename(filename || '');
|
|
||||||
|
|
||||||
view.dispatch({
|
|
||||||
effects: languageConfig.reconfigure(findLanguageExtensionByMode(mode)),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (onModeChanged !== undefined) {
|
|
||||||
onModeChanged(mode);
|
|
||||||
}
|
|
||||||
}, [ filename ]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (view === undefined) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We could dispatch a view update to replace the content, but this would keep the edit history,
|
|
||||||
// and previously would duplicate the content of the editor.
|
|
||||||
view.setState(createEditorState());
|
|
||||||
}, [ initialContent ]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (fetchContent === undefined) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!view) {
|
|
||||||
fetchContent(() => Promise.reject(new Error('no editor session has been configured')));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (onContentSaved !== undefined) {
|
|
||||||
view.dispatch({
|
|
||||||
effects: keybinds.reconfigure(keymap.of([
|
|
||||||
{
|
|
||||||
key: 'Mod-s',
|
|
||||||
run: () => {
|
|
||||||
onContentSaved();
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
])),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fetchContent(() => Promise.resolve(view.state.doc.toString()));
|
|
||||||
}, [ view, fetchContent, onContentSaved ]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<EditorContainer className={className} style={style} overrides={overrides} ref={ref}/>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1,6 +1,8 @@
|
||||||
import { forwardRef } from 'react';
|
import { forwardRef } from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { ButtonProps, Options } from '@/components/elements/button/types';
|
|
||||||
|
import type { ButtonProps } from '@/components/elements/button/types';
|
||||||
|
import { Options } from '@/components/elements/button/types';
|
||||||
import styles from './style.module.css';
|
import styles from './style.module.css';
|
||||||
|
|
||||||
const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
||||||
|
|
|
@ -1,15 +1,25 @@
|
||||||
import { forwardRef } from 'react';
|
|
||||||
import * as React from 'react';
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
import type { ComponentProps } from 'react';
|
||||||
|
import { forwardRef } from 'react';
|
||||||
|
|
||||||
import styles from './styles.module.css';
|
import styles from './styles.module.css';
|
||||||
|
|
||||||
type Props = Omit<React.ComponentProps<'input'>, 'type'>;
|
type Props = Omit<ComponentProps<'input'>, 'type'> & {
|
||||||
|
indeterminate?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
export default forwardRef<HTMLInputElement, Props>(({ className, ...props }, ref) => (
|
export default forwardRef<HTMLInputElement, Props>(({ className, indeterminate, ...props }, ref) => (
|
||||||
<input
|
<input
|
||||||
ref={ref}
|
ref={ref}
|
||||||
type={'checkbox'}
|
type={'checkbox'}
|
||||||
className={classNames('form-input', styles.checkbox_input, className)}
|
className={classNames(
|
||||||
|
'form-checkbox',
|
||||||
|
{
|
||||||
|
[styles.checkbox]: true,
|
||||||
|
[styles.indeterminate]: indeterminate,
|
||||||
|
},
|
||||||
|
className,
|
||||||
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
));
|
));
|
||||||
|
|
|
@ -48,9 +48,9 @@ import Sidebar from '@/components/admin/Sidebar';
|
||||||
import UsersContainer from '@/components/admin/users/UsersContainer';
|
import UsersContainer from '@/components/admin/users/UsersContainer';
|
||||||
|
|
||||||
function AdminRouter() {
|
function AdminRouter() {
|
||||||
// const email = useStoreState((state: State<ApplicationStore>) => state.user.data!.email);
|
const email = useStoreState((state: ApplicationStore) => state.user.data!.email);
|
||||||
// const roleName = useStoreState((state: State<ApplicationStore>) => state.user.data!.roleName);
|
const roleName = useStoreState((state: ApplicationStore) => state.user.data!.roleName);
|
||||||
// const avatarURL = useStoreState((state: State<ApplicationStore>) => state.user.data!.avatarURL);
|
const avatarURL = useStoreState((state: ApplicationStore) => state.user.data!.avatarURL);
|
||||||
const applicationName = useStoreState((state: ApplicationStore) => state.settings.data!.name);
|
const applicationName = useStoreState((state: ApplicationStore) => state.settings.data!.name);
|
||||||
|
|
||||||
// const [collapsed, setCollapsed] = useUserPersistedState('admin_sidebar_collapsed', false);
|
// const [collapsed, setCollapsed] = useUserPersistedState('admin_sidebar_collapsed', false);
|
||||||
|
@ -71,7 +71,7 @@ function AdminRouter() {
|
||||||
</div>
|
</div>
|
||||||
<Sidebar.Wrapper>
|
<Sidebar.Wrapper>
|
||||||
<Sidebar.Section>Administration</Sidebar.Section>
|
<Sidebar.Section>Administration</Sidebar.Section>
|
||||||
<NavLink to="/admin">
|
<NavLink to="/admin" end>
|
||||||
<OfficeBuildingIcon />
|
<OfficeBuildingIcon />
|
||||||
<span>Overview</span>
|
<span>Overview</span>
|
||||||
</NavLink>
|
</NavLink>
|
||||||
|
@ -118,27 +118,27 @@ function AdminRouter() {
|
||||||
<ReplyIcon />
|
<ReplyIcon />
|
||||||
<span>Return</span>
|
<span>Return</span>
|
||||||
</NavLink>
|
</NavLink>
|
||||||
{/*<Sidebar.User>*/}
|
<Sidebar.User>
|
||||||
{/* {avatarURL && (*/}
|
{avatarURL && (
|
||||||
{/* <img*/}
|
<img
|
||||||
{/* src={`${avatarURL}?s=64`}*/}
|
src={`${avatarURL}?s=64`}
|
||||||
{/* alt="Profile Picture"*/}
|
alt="Profile Picture"
|
||||||
{/* css={tw`h-10 w-10 rounded-full select-none`}*/}
|
css={tw`h-10 w-10 rounded-full select-none`}
|
||||||
{/* />*/}
|
/>
|
||||||
{/* )}*/}
|
)}
|
||||||
{/* <div css={tw`flex flex-col ml-3`}>*/}
|
<div css={tw`flex flex-col ml-3`}>
|
||||||
{/* <span*/}
|
<span
|
||||||
{/* css={tw`font-sans font-normal text-sm text-neutral-50 whitespace-nowrap leading-tight select-none`}*/}
|
css={tw`font-sans font-normal text-sm text-neutral-50 whitespace-nowrap leading-tight select-none`}
|
||||||
{/* >*/}
|
>
|
||||||
{/* {email}*/}
|
{email}
|
||||||
{/* </span>*/}
|
</span>
|
||||||
{/* <span*/}
|
<span
|
||||||
{/* css={tw`font-header font-normal text-xs text-neutral-300 whitespace-nowrap leading-tight select-none`}*/}
|
css={tw`font-header font-normal text-xs text-neutral-300 whitespace-nowrap leading-tight select-none`}
|
||||||
{/* >*/}
|
>
|
||||||
{/* {roleName}*/}
|
{roleName}
|
||||||
{/* </span>*/}
|
</span>
|
||||||
{/* </div>*/}
|
</div>
|
||||||
{/*</Sidebar.User>*/}
|
</Sidebar.User>
|
||||||
</Sidebar>
|
</Sidebar>
|
||||||
|
|
||||||
<div css={tw`flex-1 overflow-x-hidden px-6 pt-6 lg:px-10 lg:pt-8 xl:px-16 xl:pt-12`}>
|
<div css={tw`flex-1 overflow-x-hidden px-6 pt-6 lg:px-10 lg:pt-8 xl:px-16 xl:pt-12`}>
|
||||||
|
|
|
@ -64,7 +64,7 @@ function ServerRouter() {
|
||||||
error ? (
|
error ? (
|
||||||
<ServerError message={error} />
|
<ServerError message={error} />
|
||||||
) : (
|
) : (
|
||||||
<Spinner size={'large'} centered />
|
<Spinner size="large" centered />
|
||||||
)
|
)
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
|
@ -75,21 +75,24 @@ function ServerRouter() {
|
||||||
.map(route =>
|
.map(route =>
|
||||||
route.permission ? (
|
route.permission ? (
|
||||||
<Can key={route.path} action={route.permission} matchAny>
|
<Can key={route.path} action={route.permission} matchAny>
|
||||||
<NavLink to={`/server/${id}/${route.path ?? ''}`} end>
|
<NavLink to={`/server/${id}/${route.path ?? ''}`.replace(/\/$/, '')} end>
|
||||||
{route.name}
|
{route.name}
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</Can>
|
</Can>
|
||||||
) : (
|
) : (
|
||||||
<NavLink key={route.path} to={`/server/${id}/${route.path ?? ''}`} end>
|
<NavLink
|
||||||
|
key={route.path}
|
||||||
|
to={`/server/${id}/${route.path ?? ''}`.replace(/\/$/, '')}
|
||||||
|
end
|
||||||
|
>
|
||||||
{route.name}
|
{route.name}
|
||||||
</NavLink>
|
</NavLink>
|
||||||
),
|
),
|
||||||
)}
|
)}
|
||||||
{rootAdmin && (
|
{rootAdmin && (
|
||||||
// eslint-disable-next-line react/jsx-no-target-blank
|
<NavLink to={`/admin/servers/${serverId}`}>
|
||||||
<a href={`/admin/servers/view/${serverId}`} target="_blank">
|
|
||||||
<FontAwesomeIcon icon={faExternalLinkAlt} />
|
<FontAwesomeIcon icon={faExternalLinkAlt} />
|
||||||
</a>
|
</NavLink>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</SubNavigation>
|
</SubNavigation>
|
||||||
|
|
|
@ -8,6 +8,8 @@ export interface UserData {
|
||||||
language: string;
|
language: string;
|
||||||
rootAdmin: boolean;
|
rootAdmin: boolean;
|
||||||
useTotp: boolean;
|
useTotp: boolean;
|
||||||
|
avatarURL: string;
|
||||||
|
roleName: string;
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
updatedAt: Date;
|
updatedAt: Date;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue