diff --git a/package.json b/package.json index 0ca8c2e9b..e939af813 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,8 @@ "react": "^16.13.1", "react-dom": "npm:@hot-loader/react-dom", "react-fast-compare": "^3.2.0", + "react-google-recaptcha": "^2.0.1", + "react-helmet": "^6.1.0", "react-ga": "^3.1.2", "react-hot-loader": "^4.12.21", "react-i18next": "^11.2.1", @@ -61,6 +63,7 @@ "@types/query-string": "^6.3.0", "@types/react": "^16.9.41", "@types/react-dom": "^16.9.8", + "@types/react-helmet": "^6.0.0", "@types/react-redux": "^7.1.1", "@types/react-router": "^5.1.3", "@types/react-router-dom": "^5.1.3", diff --git a/resources/scripts/components/dashboard/AccountApiContainer.tsx b/resources/scripts/components/dashboard/AccountApiContainer.tsx index f3ceb66f0..c80a51a20 100644 --- a/resources/scripts/components/dashboard/AccountApiContainer.tsx +++ b/resources/scripts/components/dashboard/AccountApiContainer.tsx @@ -1,4 +1,5 @@ import React, { useEffect, useState } from 'react'; +import { Helmet } from 'react-helmet'; import ContentBox from '@/components/elements/ContentBox'; import CreateApiKeyForm from '@/components/dashboard/forms/CreateApiKeyForm'; import getApiKeys, { ApiKey } from '@/api/account/getApiKeys'; @@ -7,7 +8,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faKey, faTrashAlt } from '@fortawesome/free-solid-svg-icons'; import ConfirmationModal from '@/components/elements/ConfirmationModal'; import deleteApiKey from '@/api/account/deleteApiKey'; -import { Actions, useStoreActions } from 'easy-peasy'; +import { Actions, useStoreActions, useStoreState } from 'easy-peasy'; import { ApplicationStore } from '@/state'; import FlashMessageRender from '@/components/FlashMessageRender'; import { httpErrorToHuman } from '@/api/http'; @@ -21,6 +22,7 @@ export default () => { const [ keys, setKeys ] = useState([]); const [ loading, setLoading ] = useState(true); const { addError, clearFlashes } = useStoreActions((actions: Actions) => actions.flashes); + const name = useStoreState((state: ApplicationStore) => state.settings.data!.name); useEffect(() => { clearFlashes('account'); @@ -49,6 +51,9 @@ export default () => { return ( + + {name} | API +
diff --git a/resources/scripts/components/dashboard/AccountOverviewContainer.tsx b/resources/scripts/components/dashboard/AccountOverviewContainer.tsx index e98ddd4a6..d495400b4 100644 --- a/resources/scripts/components/dashboard/AccountOverviewContainer.tsx +++ b/resources/scripts/components/dashboard/AccountOverviewContainer.tsx @@ -1,4 +1,6 @@ import * as React from 'react'; +import { Helmet } from 'react-helmet'; +import { ApplicationStore } from '@/state'; import ContentBox from '@/components/elements/ContentBox'; import UpdatePasswordForm from '@/components/dashboard/forms/UpdatePasswordForm'; import UpdateEmailAddressForm from '@/components/dashboard/forms/UpdateEmailAddressForm'; @@ -7,6 +9,7 @@ import PageContentBlock from '@/components/elements/PageContentBlock'; import tw from 'twin.macro'; import { breakpoint } from '@/theme'; import styled from 'styled-components/macro'; +import { useStoreState } from 'easy-peasy'; const Container = styled.div` ${tw`flex flex-wrap my-10`}; @@ -25,8 +28,12 @@ const Container = styled.div` `; export default () => { + const name = useStoreState((state: ApplicationStore) => state.settings.data!.name); return ( + + {name} | Account Overview + diff --git a/resources/scripts/components/dashboard/DashboardContainer.tsx b/resources/scripts/components/dashboard/DashboardContainer.tsx index a20472604..1e1e702ca 100644 --- a/resources/scripts/components/dashboard/DashboardContainer.tsx +++ b/resources/scripts/components/dashboard/DashboardContainer.tsx @@ -1,5 +1,7 @@ import React, { useEffect, useState } from 'react'; +import { Helmet } from 'react-helmet'; import { Server } from '@/api/server/getServer'; +import { ApplicationStore } from '@/state'; import getServers from '@/api/getServers'; import ServerRow from '@/components/dashboard/ServerRow'; import Spinner from '@/components/elements/Spinner'; @@ -18,6 +20,7 @@ export default () => { const [ page, setPage ] = useState(1); const { rootAdmin } = useStoreState(state => state.user.data!); const [ showOnlyAdmin, setShowOnlyAdmin ] = usePersistedState('show_all_servers', false); + const name = useStoreState((state: ApplicationStore) => state.settings.data!.name); const { data: servers, error } = useSWR>( [ '/api/client/servers', showOnlyAdmin, page ], @@ -31,6 +34,9 @@ export default () => { return ( + + {name} | Dashboard + {rootAdmin &&

diff --git a/resources/scripts/components/server/ServerConsole.tsx b/resources/scripts/components/server/ServerConsole.tsx index e90c86035..74ba4d750 100644 --- a/resources/scripts/components/server/ServerConsole.tsx +++ b/resources/scripts/components/server/ServerConsole.tsx @@ -1,4 +1,5 @@ import React, { lazy, useEffect, useState } from 'react'; +import { Helmet } from 'react-helmet'; import { ServerContext } from '@/state/server'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faCircle, faHdd, faMemory, faMicrochip, faServer } from '@fortawesome/free-solid-svg-icons'; @@ -61,6 +62,9 @@ export default () => { return ( + + {server.name} | Console +

diff --git a/resources/scripts/components/server/backups/BackupContainer.tsx b/resources/scripts/components/server/backups/BackupContainer.tsx index feb4e5f2e..bcead7abb 100644 --- a/resources/scripts/components/server/backups/BackupContainer.tsx +++ b/resources/scripts/components/server/backups/BackupContainer.tsx @@ -1,4 +1,5 @@ import React, { useEffect, useState } from 'react'; +import { Helmet } from 'react-helmet'; import Spinner from '@/components/elements/Spinner'; import getServerBackups from '@/api/server/backups/getServerBackups'; import useServer from '@/plugins/useServer'; @@ -13,7 +14,7 @@ import PageContentBlock from '@/components/elements/PageContentBlock'; import tw from 'twin.macro'; export default () => { - const { uuid, featureLimits } = useServer(); + const { uuid, featureLimits, name: serverName } = useServer(); const { addError, clearFlashes } = useFlash(); const [ loading, setLoading ] = useState(true); @@ -37,6 +38,9 @@ export default () => { return ( + + {serverName} | Backups + {!backups.length ?

diff --git a/resources/scripts/components/server/databases/DatabasesContainer.tsx b/resources/scripts/components/server/databases/DatabasesContainer.tsx index 486072598..922f0a364 100644 --- a/resources/scripts/components/server/databases/DatabasesContainer.tsx +++ b/resources/scripts/components/server/databases/DatabasesContainer.tsx @@ -1,4 +1,5 @@ import React, { useEffect, useState } from 'react'; +import { Helmet } from 'react-helmet'; import getServerDatabases from '@/api/server/getServerDatabases'; import { ServerContext } from '@/state/server'; import { httpErrorToHuman } from '@/api/http'; @@ -14,7 +15,7 @@ import tw from 'twin.macro'; import Fade from '@/components/elements/Fade'; export default () => { - const { uuid, featureLimits } = useServer(); + const { uuid, featureLimits, name: serverName } = useServer(); const { addError, clearFlashes } = useFlash(); const [ loading, setLoading ] = useState(true); @@ -36,6 +37,9 @@ export default () => { return ( + + {serverName} | Databases + {(!databases.length && loading) ? diff --git a/resources/scripts/components/server/files/FileManagerContainer.tsx b/resources/scripts/components/server/files/FileManagerContainer.tsx index a6d6b89af..d25ef3c1c 100644 --- a/resources/scripts/components/server/files/FileManagerContainer.tsx +++ b/resources/scripts/components/server/files/FileManagerContainer.tsx @@ -1,4 +1,5 @@ import React, { useEffect } from 'react'; +import { Helmet } from 'react-helmet'; import { httpErrorToHuman } from '@/api/http'; import { CSSTransition } from 'react-transition-group'; import Spinner from '@/components/elements/Spinner'; @@ -23,9 +24,10 @@ const sortFiles = (files: FileObject[]): FileObject[] => { }; export default () => { - const { id } = useServer(); + const { id, name: serverName } = useServer(); const { hash } = useLocation(); const { data: files, error, mutate } = useFileManagerSwr(); + const setDirectory = ServerContext.useStoreActions(actions => actions.files.setDirectory); const setSelectedFiles = ServerContext.useStoreActions(actions => actions.files.setSelectedFiles); @@ -42,6 +44,9 @@ export default () => { return ( + + {serverName} | File Manager + { !files ? diff --git a/resources/scripts/components/server/network/NetworkContainer.tsx b/resources/scripts/components/server/network/NetworkContainer.tsx index 1723f9352..a330685b1 100644 --- a/resources/scripts/components/server/network/NetworkContainer.tsx +++ b/resources/scripts/components/server/network/NetworkContainer.tsx @@ -1,4 +1,5 @@ import React, { useEffect, useState } from 'react'; +import { Helmet } from 'react-helmet'; import tw from 'twin.macro'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faNetworkWired } from '@fortawesome/free-solid-svg-icons'; @@ -23,7 +24,7 @@ const Code = styled.code`${tw`font-mono py-1 px-2 bg-neutral-900 rounded text-sm const Label = styled.label`${tw`uppercase text-xs mt-1 text-neutral-400 block px-1 select-none transition-colors duration-150`}`; const NetworkContainer = () => { - const { uuid, allocations } = useServer(); + const { uuid, allocations, name: serverName } = useServer(); const { clearFlashes, clearAndAddHttpError } = useFlash(); const [ loading, setLoading ] = useState(false); const { data, error, mutate } = useSWR(uuid, key => getServerAllocations(key), { initialData: allocations }); @@ -61,6 +62,9 @@ const NetworkContainer = () => { return ( + + {serverName} | Network + {!data ? : diff --git a/resources/scripts/components/server/schedules/ScheduleContainer.tsx b/resources/scripts/components/server/schedules/ScheduleContainer.tsx index 0e4ff6bd2..77e31b590 100644 --- a/resources/scripts/components/server/schedules/ScheduleContainer.tsx +++ b/resources/scripts/components/server/schedules/ScheduleContainer.tsx @@ -1,4 +1,5 @@ import React, { useEffect, useState } from 'react'; +import { Helmet } from 'react-helmet'; import getServerSchedules from '@/api/server/schedules/getServerSchedules'; import { ServerContext } from '@/state/server'; import Spinner from '@/components/elements/Spinner'; @@ -16,7 +17,7 @@ import GreyRowBox from '@/components/elements/GreyRowBox'; import Button from '@/components/elements/Button'; export default ({ match, history }: RouteComponentProps) => { - const { uuid } = useServer(); + const { uuid, name: serverName } = useServer(); const { clearFlashes, addError } = useFlash(); const [ loading, setLoading ] = useState(true); const [ visible, setVisible ] = useState(false); @@ -37,6 +38,9 @@ export default ({ match, history }: RouteComponentProps) => { return ( + + {serverName} | Schedules + {(!schedules.length && loading) ? diff --git a/resources/scripts/components/server/settings/SettingsContainer.tsx b/resources/scripts/components/server/settings/SettingsContainer.tsx index e060fedf8..edaa3503f 100644 --- a/resources/scripts/components/server/settings/SettingsContainer.tsx +++ b/resources/scripts/components/server/settings/SettingsContainer.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import { Helmet } from 'react-helmet'; import TitledGreyBox from '@/components/elements/TitledGreyBox'; import { ServerContext } from '@/state/server'; import { useStoreState } from 'easy-peasy'; @@ -20,6 +21,9 @@ export default () => { return ( + + {server.name} | Settings +

diff --git a/resources/scripts/components/server/users/UsersContainer.tsx b/resources/scripts/components/server/users/UsersContainer.tsx index 55f60b449..a58d9e904 100644 --- a/resources/scripts/components/server/users/UsersContainer.tsx +++ b/resources/scripts/components/server/users/UsersContainer.tsx @@ -1,4 +1,5 @@ import React, { useEffect, useState } from 'react'; +import { Helmet } from 'react-helmet'; import { ServerContext } from '@/state/server'; import { Actions, useStoreActions, useStoreState } from 'easy-peasy'; import { ApplicationStore } from '@/state'; @@ -17,6 +18,7 @@ export default () => { const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); const subusers = ServerContext.useStoreState(state => state.subusers.data); + const servername = ServerContext.useStoreState(state => state.server.data!.name); const setSubusers = ServerContext.useStoreActions(actions => actions.subusers.setSubusers); const permissions = useStoreState((state: ApplicationStore) => state.permissions.data); @@ -49,6 +51,9 @@ export default () => { return ( + + {servername} | Subusers + {!subusers.length ?

diff --git a/yarn.lock b/yarn.lock index 73e239aef..0bba5d32b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1013,6 +1013,13 @@ dependencies: "@types/react" "*" +"@types/react-helmet@^6.0.0": + version "6.0.0" + resolved "https://registry.yarnpkg.com/@types/react-helmet/-/react-helmet-6.0.0.tgz#5b74e44a12662ffb12d1c97ee702cf4e220958cf" + integrity sha512-NBMPAxgjpaMooXa51cU1BTgrX6T+hQbMiLm77JhBbfOzPQea3RB5rNpPOD5xGWHIVpGXHd59cltEzIq0qglGcQ== + dependencies: + "@types/react" "*" + "@types/react-native@*": version "0.60.2" resolved "https://registry.yarnpkg.com/@types/react-native/-/react-native-0.60.2.tgz#2dca78481a904419c2a5907288dd97d1090c6e3c" @@ -5393,7 +5400,7 @@ promise-inflight@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" -prop-types@^15.5.10, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2: +prop-types@^15.5.0, prop-types@^15.5.10, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2: version "15.7.2" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== @@ -5538,6 +5545,13 @@ rc@^1.1.7: minimist "^1.2.0" strip-json-comments "~2.0.1" +react-async-script@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/react-async-script/-/react-async-script-1.1.1.tgz#f481c6c5f094bf4b94a9d52da0d0dda2e1a74bdf" + dependencies: + hoist-non-react-statics "^3.3.0" + prop-types "^15.5.0" + "react-dom@npm:@hot-loader/react-dom": version "16.11.0" resolved "https://registry.yarnpkg.com/@hot-loader/react-dom/-/react-dom-16.11.0.tgz#c0b483923b289db5431516f56ee2a69448ebf9bd" @@ -5551,15 +5565,27 @@ react-fast-compare@^2.0.1: version "2.0.4" resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-2.0.4.tgz#e84b4d455b0fec113e0402c329352715196f81f9" -react-fast-compare@^3.2.0: +react-fast-compare@^3.1.1, react-fast-compare@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.0.tgz#641a9da81b6a6320f270e89724fb45a0b39e43bb" integrity sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA== -react-ga@^3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/react-ga/-/react-ga-3.1.2.tgz#e13f211c51a2e5c401ea69cf094b9501fe3c51ce" - integrity sha512-OJrMqaHEHbodm+XsnjA6ISBEHTwvpFrxco65mctzl/v3CASMSLSyUkFqz9yYrPDKGBUfNQzKCjuMJwctjlWBbw== +react-google-recaptcha@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/react-google-recaptcha/-/react-google-recaptcha-2.0.1.tgz#3276b29659493f7ca2a5b7739f6c239293cdf1d8" + dependencies: + prop-types "^15.5.0" + react-async-script "^1.1.1" + +react-helmet@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/react-helmet/-/react-helmet-6.1.0.tgz#a750d5165cb13cf213e44747502652e794468726" + integrity sha512-4uMzEY9nlDlgxr61NL3XbKRy1hEkXmKNXhjbAIOVw5vcFrsdYbH2FEwcNyWvWinl103nXgzYNlns9ca+8kFiWw== + dependencies: + object-assign "^4.1.1" + prop-types "^15.7.2" + react-fast-compare "^3.1.1" + react-side-effect "^2.1.0" react-hot-loader@^4.12.21: version "4.12.21" @@ -5628,6 +5654,11 @@ react-router@5.1.2: tiny-invariant "^1.0.2" tiny-warning "^1.0.0" +react-side-effect@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/react-side-effect/-/react-side-effect-2.1.0.tgz#1ce4a8b4445168c487ed24dab886421f74d380d3" + integrity sha512-IgmcegOSi5SNX+2Snh1vqmF0Vg/CbkycU9XZbOHJlZ6kMzTmi3yc254oB1WCkgA7OQtIAoLmcSFuHTc/tlcqXg== + react-transition-group@^4.4.1: version "4.4.1" resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.1.tgz#63868f9325a38ea5ee9535d828327f85773345c9"