diff --git a/resources/scripts/api/admin/nodes/getNodeInformation.ts b/resources/scripts/api/admin/nodes/getNodeInformation.ts new file mode 100644 index 000000000..771433a5e --- /dev/null +++ b/resources/scripts/api/admin/nodes/getNodeInformation.ts @@ -0,0 +1,19 @@ +import http from '@/api/http'; + +export interface NodeInformation { + version: string; + system: { + type: string; + arch: string; + release: string; + cpus: number; + }; +} + +export default (id: number): Promise => { + return new Promise((resolve, reject) => { + http.get(`/api/application/nodes/${id}/information`) + .then(({ data }) => resolve(data)) + .catch(reject); + }); +}; diff --git a/resources/scripts/components/admin/AdminTable.tsx b/resources/scripts/components/admin/AdminTable.tsx index bbbed54d4..353aab91b 100644 --- a/resources/scripts/components/admin/AdminTable.tsx +++ b/resources/scripts/components/admin/AdminTable.tsx @@ -5,19 +5,11 @@ import styled from 'styled-components/macro'; import tw from 'twin.macro'; import { PaginatedResult } from '@/api/http'; -export const TableHead = ({ children }: { children: React.ReactNode }) => { - return ( - - - +export const TableHeader = ({ name }: { name?: string }) => { + if (!name) { + return ; + } - {children} - - - ); -}; - -export const TableHeader = ({ name }: { name: string }) => { return ( @@ -34,6 +26,17 @@ export const TableHeader = ({ name }: { name: string }) => { ); }; +export const TableHead = ({ children }: { children: React.ReactNode }) => { + return ( + + + + {children} + + + ); +}; + export const TableBody = ({ children }: { children: React.ReactNode }) => { return ( @@ -117,7 +120,7 @@ export function Pagination ({ data: { pagination }, onPageSelect, children }: Showing {((pagination.currentPage - 1) * pagination.perPage) + 1} to {((pagination.currentPage - 1) * pagination.perPage) + pagination.count} of {pagination.total} results

- { isFirstPage && isLastPage ? + {isFirstPage && isLastPage ? null :
@@ -191,7 +194,7 @@ export const ContentWrapper = ({ checked, onSelectAllClick, children }: Params)
-
+ {/*
@@ -199,7 +202,7 @@ export const ContentWrapper = ({ checked, onSelectAllClick, children }: Params) -
+
*/} {children} diff --git a/resources/scripts/components/admin/databases/DatabaseEditContainer.tsx b/resources/scripts/components/admin/databases/DatabaseEditContainer.tsx index d99365e1d..35e95e958 100644 --- a/resources/scripts/components/admin/databases/DatabaseEditContainer.tsx +++ b/resources/scripts/components/admin/databases/DatabaseEditContainer.tsx @@ -75,7 +75,7 @@ export const InformationContainer = ({ title, initialValues, children, onSubmit > { ({ isSubmitting, isValid }) => ( - + <> @@ -140,7 +140,7 @@ export const InformationContainer = ({ title, initialValues, children, onSubmit - + ) } diff --git a/resources/scripts/components/admin/nodes/NodeAboutContainer.tsx b/resources/scripts/components/admin/nodes/NodeAboutContainer.tsx new file mode 100644 index 000000000..0c0af134e --- /dev/null +++ b/resources/scripts/components/admin/nodes/NodeAboutContainer.tsx @@ -0,0 +1,92 @@ +import React, { useEffect, useState } from 'react'; +import tw from 'twin.macro'; +import { Actions, useStoreActions } from 'easy-peasy'; +import { ApplicationStore } from '@/state'; +import AdminBox from '@/components/admin/AdminBox'; +import getNodeInformation, { NodeInformation } from '@/api/admin/nodes/getNodeInformation'; +import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; +import { Context } from '@/components/admin/nodes/NodeRouter'; + +const Code = ({ className, children }: { className?: string, children: React.ReactNode }) => { + return ( + + {children} + + ); +}; + +export default () => { + const { clearFlashes, clearAndAddHttpError } = useStoreActions((actions: Actions) => actions.flashes); + + const [ loading, setLoading ] = useState(true); + const [ info, setInfo ] = useState(null); + + const node = Context.useStoreState(state => state.node); + + if (node === undefined) { + return ( + <> + ); + } + + useEffect(() => { + clearFlashes('node'); + + getNodeInformation(node.id) + .then(info => setInfo(info)) + .catch(error => { + console.error(error); + clearAndAddHttpError({ key: 'node', error }); + }) + .then(() => setLoading(false)); + }, []); + + if (loading) { + return ( + + + + ); + } + + return ( + + + + + + + + + + + + + + + + + + + + + + + + +
Wings Version + {info?.version} +
Operating System + {info?.system.type} +
Architecture + {info?.system.arch} +
Kernel + {info?.system.release} +
CPU Threads + {info?.system.cpus} +
+ + {/* TODO: Description code-block with edit option */} +
+ ); +}; diff --git a/resources/scripts/components/admin/nodes/NodeConfigurationContainer.tsx b/resources/scripts/components/admin/nodes/NodeConfigurationContainer.tsx index 732c1571e..b2c75759a 100644 --- a/resources/scripts/components/admin/nodes/NodeConfigurationContainer.tsx +++ b/resources/scripts/components/admin/nodes/NodeConfigurationContainer.tsx @@ -3,13 +3,13 @@ import AdminBox from '@/components/admin/AdminBox'; import tw from 'twin.macro'; import { faCode, faDragon } from '@fortawesome/free-solid-svg-icons'; import getNodeConfiguration from '@/api/admin/nodes/getNodeConfiguration'; -import { Context } from '@/components/admin/nodes/NodeEditContainer'; +import { Context } from '@/components/admin/nodes/NodeRouter'; import CopyOnClick from '@/components/elements/CopyOnClick'; import { ApplicationStore } from '@/state'; import { Actions, useStoreActions } from 'easy-peasy'; export default () => { - const { clearAndAddHttpError } = useStoreActions((actions: Actions) => actions.flashes); + const { clearFlashes, clearAndAddHttpError } = useStoreActions((actions: Actions) => actions.flashes); const [ configuration, setConfiguration ] = useState(''); @@ -22,6 +22,8 @@ export default () => { } useEffect(() => { + clearFlashes('node'); + getNodeConfiguration(node.id) .then((configuration) => setConfiguration(configuration)) .catch(error => { @@ -31,7 +33,7 @@ export default () => { }, []); return ( -
+ <>
@@ -41,7 +43,7 @@ export default () => {
-
+                    
                         {configuration}
                     
@@ -50,6 +52,6 @@ export default () => { Never™ -
+ ); }; diff --git a/resources/scripts/components/admin/nodes/NodeLimitContainer.tsx b/resources/scripts/components/admin/nodes/NodeLimitContainer.tsx index 862b89fff..69a4d359e 100644 --- a/resources/scripts/components/admin/nodes/NodeLimitContainer.tsx +++ b/resources/scripts/components/admin/nodes/NodeLimitContainer.tsx @@ -1,27 +1,15 @@ import React from 'react'; import AdminBox from '@/components/admin/AdminBox'; import tw from 'twin.macro'; -import { number, object } from 'yup'; import Field from '@/components/elements/Field'; import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; -import { Form, Formik, FormikHelpers } from 'formik'; -import { Context } from '@/components/admin/nodes/NodeEditContainer'; -import { ApplicationStore } from '@/state'; -import { Actions, useStoreActions } from 'easy-peasy'; -import updateNode from '@/api/admin/nodes/updateNode'; - -interface Values { - memory: number; - memoryOverallocate: number; - disk: number; - diskOverallocate: number; -} +import { Form, useFormikContext } from 'formik'; +import { Context } from '@/components/admin/nodes/NodeRouter'; export default () => { - const { clearFlashes, clearAndAddHttpError } = useStoreActions((actions: Actions) => actions.flashes); + const { isSubmitting } = useFormikContext(); const node = Context.useStoreState(state => state.node); - const setNode = Context.useStoreActions(actions => actions.setNode); if (node === undefined) { return ( @@ -29,85 +17,51 @@ export default () => { ); } - const submit = ({ memory, memoryOverallocate, disk, diskOverallocate }: Values, { setSubmitting }: FormikHelpers) => { - clearFlashes('node'); - - updateNode(node.id, { memory, memoryOverallocate, disk, diskOverallocate }) - .then(() => setNode({ ...node, memory, memoryOverallocate, disk, diskOverallocate })) - .catch(error => { - console.error(error); - clearAndAddHttpError({ key: 'node', error }); - }) - .then(() => setSubmitting(false)); - }; - return ( - - { - ({ isSubmitting }) => ( - - - + + -
-
-
- -
+ +
+
+ +
-
- -
-
+
+ +
+
-
-
- -
+
+
+ +
-
- -
-
- - - - ) - } - +
+ +
+
+ +
); }; diff --git a/resources/scripts/components/admin/nodes/NodeListenContainer.tsx b/resources/scripts/components/admin/nodes/NodeListenContainer.tsx index 32c8c2be6..cce46f6ca 100644 --- a/resources/scripts/components/admin/nodes/NodeListenContainer.tsx +++ b/resources/scripts/components/admin/nodes/NodeListenContainer.tsx @@ -1,27 +1,15 @@ import React from 'react'; import AdminBox from '@/components/admin/AdminBox'; import tw from 'twin.macro'; -import { object } from 'yup'; import Field from '@/components/elements/Field'; import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; -import { Form, Formik, FormikHelpers } from 'formik'; -import { Context } from '@/components/admin/nodes/NodeEditContainer'; -import { ApplicationStore } from '@/state'; -import { Actions, useStoreActions } from 'easy-peasy'; -import updateNode from '@/api/admin/nodes/updateNode'; - -interface Values { - listenPortHTTP: number; - publicPortHTTP: number; - listenPortSFTP: number; - publicPortSFTP: number; -} +import { Form, useFormikContext } from 'formik'; +import { Context } from '@/components/admin/nodes/NodeRouter'; export default () => { - const { clearFlashes, clearAndAddHttpError } = useStoreActions((actions: Actions) => actions.flashes); + const { isSubmitting } = useFormikContext(); const node = Context.useStoreState(state => state.node); - const setNode = Context.useStoreActions(actions => actions.setNode); if (node === undefined) { return ( @@ -29,81 +17,51 @@ export default () => { ); } - const submit = ({ listenPortHTTP, publicPortHTTP, listenPortSFTP, publicPortSFTP }: Values, { setSubmitting }: FormikHelpers) => { - clearFlashes('node'); - - updateNode(node.id, { listenPortHTTP, publicPortHTTP, listenPortSFTP, publicPortSFTP }) - .then(() => setNode({ ...node, listenPortHTTP, publicPortHTTP, listenPortSFTP, publicPortSFTP })) - .catch(error => { - console.error(error); - clearAndAddHttpError({ key: 'node', error }); - }) - .then(() => setSubmitting(false)); - }; - return ( - - { - ({ isSubmitting }) => ( - - - + + -
-
-
- -
+ +
+
+ +
-
- -
-
+
+ +
+
-
-
- -
+
+
+ +
-
- -
-
- - - - ) - } - +
+ +
+
+ +
); }; diff --git a/resources/scripts/components/admin/nodes/NodeEditContainer.tsx b/resources/scripts/components/admin/nodes/NodeRouter.tsx similarity index 83% rename from resources/scripts/components/admin/nodes/NodeEditContainer.tsx rename to resources/scripts/components/admin/nodes/NodeRouter.tsx index 38236a102..80baa1ae5 100644 --- a/resources/scripts/components/admin/nodes/NodeEditContainer.tsx +++ b/resources/scripts/components/admin/nodes/NodeRouter.tsx @@ -10,11 +10,9 @@ import Spinner from '@/components/elements/Spinner'; import FlashMessageRender from '@/components/FlashMessageRender'; import { ApplicationStore } from '@/state'; import { SubNavigation, SubNavigationLink } from '@/components/admin/SubNavigation'; -import AdminBox from '@/components/admin/AdminBox'; -import NodeLimitContainer from '@/components/admin/nodes/NodeLimitContainer'; +import NodeAboutContainer from '@/components/admin/nodes/NodeAboutContainer'; import NodeSettingsContainer from '@/components/admin/nodes/NodeSettingsContainer'; import NodeConfigurationContainer from '@/components/admin/nodes/NodeConfigurationContainer'; -import NodeListenContainer from '@/components/admin/nodes/NodeListenContainer'; interface ctx { node: Node | undefined; @@ -29,15 +27,7 @@ export const Context = createContextStore({ }), }); -const Code = ({ children }: { children: React.ReactNode }) => { - return ( - - {children} - - ); -}; - -const NodeEditContainer = () => { +const NodeRouter = () => { const location = useLocation(); const match = useRouteMatch<{ id?: string }>(); @@ -116,27 +106,11 @@ const NodeEditContainer = () => { - -

Version 1.3.1

-
+
-
-
- -
- -
-
- -
- -
- -
-
-
+
@@ -158,7 +132,7 @@ const NodeEditContainer = () => { export default () => { return ( - + ); }; diff --git a/resources/scripts/components/admin/nodes/NodeSettingsContainer.tsx b/resources/scripts/components/admin/nodes/NodeSettingsContainer.tsx index 50fa762a5..f99a672e4 100644 --- a/resources/scripts/components/admin/nodes/NodeSettingsContainer.tsx +++ b/resources/scripts/components/admin/nodes/NodeSettingsContainer.tsx @@ -1,29 +1,159 @@ +import Button from '@/components/elements/Button'; import React from 'react'; import AdminBox from '@/components/admin/AdminBox'; import tw from 'twin.macro'; -import { object, string } from 'yup'; +import { number, object, string } from 'yup'; import updateNode from '@/api/admin/nodes/updateNode'; import Field from '@/components/elements/Field'; import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; -import { Field as FormikField, Form, Formik, FormikHelpers } from 'formik'; -import { Context } from '@/components/admin/nodes/NodeEditContainer'; +import { Field as FormikField, Form, Formik, FormikHelpers, useFormikContext } from 'formik'; +import { Context } from '@/components/admin/nodes/NodeRouter'; import { ApplicationStore } from '@/state'; import { Actions, useStoreActions } from 'easy-peasy'; import LocationSelect from '@/components/admin/nodes/LocationSelect'; import DatabaseSelect from '@/components/admin/nodes/DatabaseSelect'; import Label from '@/components/elements/Label'; +import NodeLimitContainer from '@/components/admin/nodes/NodeLimitContainer'; +import NodeListenContainer from '@/components/admin/nodes/NodeListenContainer'; interface Values { - public: boolean; name: string; - description: string; locationId: number; databaseHostId: number | null; fqdn: string; scheme: string; behindProxy: boolean; + public: boolean; + + memory: number; + memoryOverallocate: number; + disk: number; + diskOverallocate: number; + + listenPortHTTP: number; + publicPortHTTP: number; + listenPortSFTP: number; + publicPortSFTP: number; } +const NodeSettingsContainer = () => { + const { isSubmitting } = useFormikContext(); + + const node = Context.useStoreState(state => state.node); + + if (node === undefined) { + return ( + <> + ); + } + + return ( + + + +
+
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ + +
+ + + +
+
+ +
+ + +
+ + + +
+
+ +
+ + +
+ + + +
+
+
+
+ ); +}; + export default () => { const { clearFlashes, clearAndAddHttpError } = useStoreActions((actions: Actions) => actions.flashes); @@ -36,11 +166,11 @@ export default () => { ); } - const submit = ({ name, description, locationId, databaseHostId, fqdn, scheme, behindProxy }: Values, { setSubmitting }: FormikHelpers) => { + const submit = (values: Values, { setSubmitting }: FormikHelpers) => { clearFlashes('node'); - updateNode(node.id, { name, description, locationId, databaseHostId, fqdn, scheme, behindProxy }) - .then(() => setNode({ ...node, name, description, locationId, fqdn, scheme, behindProxy })) + updateNode(node.id, values) + .then(() => setNode({ ...node, ...values })) .catch(error => { console.error(error); clearAndAddHttpError({ key: 'node', error }); @@ -52,112 +182,63 @@ export default () => { { - ({ isSubmitting }) => ( - - - + ({ isSubmitting, isValid }) => ( +
+
+ +
-
-
- +
+
+ +
+ +
+ +
+ +
+
+
- -
- -
- -
- -
- -
- -
- -
- -
- -
- - -
- - - -
-
- -
- - -
- - - -
-
- - - +
+
+
) } diff --git a/resources/scripts/components/admin/nodes/NodesContainer.tsx b/resources/scripts/components/admin/nodes/NodesContainer.tsx index 41db23b11..ee224d4ae 100644 --- a/resources/scripts/components/admin/nodes/NodesContainer.tsx +++ b/resources/scripts/components/admin/nodes/NodesContainer.tsx @@ -101,7 +101,8 @@ const NodesContainer = () => { - + + @@ -147,7 +148,7 @@ const NodesContainer = () => { {bytesToHuman(megabytesToBytes(node.disk))} - { node.scheme === 'https' ? + {node.scheme === 'https' ? Secure @@ -157,6 +158,13 @@ const NodesContainer = () => { } + + + {/* TODO: Change color based off of online/offline status */} + + + + )) } diff --git a/resources/scripts/routers/AdminRouter.tsx b/resources/scripts/routers/AdminRouter.tsx index c2319a145..5efbf52a1 100644 --- a/resources/scripts/routers/AdminRouter.tsx +++ b/resources/scripts/routers/AdminRouter.tsx @@ -15,7 +15,7 @@ import NewDatabaseContainer from '@/components/admin/databases/NewDatabaseContai import DatabaseEditContainer from '@/components/admin/databases/DatabaseEditContainer'; import NodesContainer from '@/components/admin/nodes/NodesContainer'; import NewNodeContainer from '@/components/admin/nodes/NewNodeContainer'; -import NodeEditContainer from '@/components/admin/nodes/NodeEditContainer'; +import NodeRouter from '@/components/admin/nodes/NodeRouter'; import LocationsContainer from '@/components/admin/locations/LocationsContainer'; import LocationEditContainer from '@/components/admin/locations/LocationEditContainer'; import ServersContainer from '@/components/admin/servers/ServersContainer'; @@ -226,7 +226,7 @@ const AdminRouter = ({ location, match }: RouteComponentProps) => {