From 986285402f68b24c3a71e65419d37dd6df617b5d Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Tue, 9 Jul 2019 21:25:57 -0700 Subject: [PATCH 1/7] Switch to a context store for server stuff to better support things in the future --- .../scripts/api/server/getServerDatabases.ts | 31 ++++++++++ .../scripts/components/FlashMessageRender.tsx | 4 +- .../auth/ForgotPasswordContainer.tsx | 4 +- .../auth/LoginCheckpointContainer.tsx | 4 +- .../components/auth/LoginContainer.tsx | 4 +- .../auth/ResetPasswordContainer.tsx | 4 +- .../forms/UpdateEmailAddressForm.tsx | 8 +-- .../dashboard/forms/UpdatePasswordForm.tsx | 6 +- .../scripts/components/server/Console.tsx | 8 +-- .../components/server/ServerConsole.tsx | 5 +- .../components/server/ServerDatabases.tsx | 24 ++++++++ .../components/server/WebsocketHandler.tsx | 13 ++--- resources/scripts/routers/ServerRouter.tsx | 53 ++++++++++------- resources/scripts/state/flashes.ts | 28 +++++++++ resources/scripts/state/index.ts | 14 +++-- resources/scripts/state/models/flashes.ts | 14 ----- resources/scripts/state/models/server.ts | 43 -------------- resources/scripts/state/server/index.ts | 57 +++++++++++++++++++ .../state/{models => server}/socket.ts | 8 +-- resources/scripts/state/types.d.ts | 24 -------- resources/scripts/state/{models => }/user.ts | 10 ++-- 21 files changed, 218 insertions(+), 148 deletions(-) create mode 100644 resources/scripts/api/server/getServerDatabases.ts create mode 100644 resources/scripts/components/server/ServerDatabases.tsx create mode 100644 resources/scripts/state/flashes.ts delete mode 100644 resources/scripts/state/models/flashes.ts delete mode 100644 resources/scripts/state/models/server.ts create mode 100644 resources/scripts/state/server/index.ts rename resources/scripts/state/{models => server}/socket.ts (70%) delete mode 100644 resources/scripts/state/types.d.ts rename resources/scripts/state/{models => }/user.ts (80%) diff --git a/resources/scripts/api/server/getServerDatabases.ts b/resources/scripts/api/server/getServerDatabases.ts new file mode 100644 index 000000000..835964c27 --- /dev/null +++ b/resources/scripts/api/server/getServerDatabases.ts @@ -0,0 +1,31 @@ +import http from '@/api/http'; + +export interface ServerDatabase { + id: string; + name: string; + username: string; + connectionString: string; + allowConnectionsFrom: string; + password?: string; +} + +export const rawDataToServerDatabase = (data: any): ServerDatabase => ({ + id: data.id, + name: data.name, + username: data.username, + connectionString: `${data.host.address}:${data.host.port}`, + allowConnectionsFrom: data.connections_from, + password: data.relationships && data.relationships.password ? data.relationships.password.attributes.password : undefined, +}); + +export default (uuid: string, includePassword: boolean = true): Promise => { + return new Promise((resolve, reject) => { + http.get(`/api/client/servers/${uuid}/databases`, { + params: includePassword ? { include: 'password' } : undefined, + }) + .then(response => resolve( + (response.data.data || []).map((item: any) => rawDataToServerDatabase(item.attributes)) + )) + .catch(reject); + }); +}; diff --git a/resources/scripts/components/FlashMessageRender.tsx b/resources/scripts/components/FlashMessageRender.tsx index 5b9c51d75..adffd09d2 100644 --- a/resources/scripts/components/FlashMessageRender.tsx +++ b/resources/scripts/components/FlashMessageRender.tsx @@ -1,7 +1,7 @@ import React from 'react'; import MessageBox from '@/components/MessageBox'; import { State, useStoreState } from 'easy-peasy'; -import { ApplicationState } from '@/state/types'; +import { ApplicationStore } from '@/state'; type Props = Readonly<{ byKey?: string; @@ -10,7 +10,7 @@ type Props = Readonly<{ }>; export default ({ withBottomSpace, spacerClass, byKey }: Props) => { - const flashes = useStoreState((state: State) => state.flashes.items); + const flashes = useStoreState((state: State) => state.flashes.items); let filtered = flashes; if (byKey) { diff --git a/resources/scripts/components/auth/ForgotPasswordContainer.tsx b/resources/scripts/components/auth/ForgotPasswordContainer.tsx index fbf0eb704..668875ca0 100644 --- a/resources/scripts/components/auth/ForgotPasswordContainer.tsx +++ b/resources/scripts/components/auth/ForgotPasswordContainer.tsx @@ -4,14 +4,14 @@ import requestPasswordResetEmail from '@/api/auth/requestPasswordResetEmail'; import { httpErrorToHuman } from '@/api/http'; import LoginFormContainer from '@/components/auth/LoginFormContainer'; import { Actions, useStoreActions } from 'easy-peasy'; -import { ApplicationState } from '@/state/types'; import FlashMessageRender from '@/components/FlashMessageRender'; +import { ApplicationStore } from '@/state'; export default () => { const [ isSubmitting, setSubmitting ] = React.useState(false); const [ email, setEmail ] = React.useState(''); - const { clearFlashes, addFlash } = useStoreActions((actions: Actions) => actions.flashes); + const { clearFlashes, addFlash } = useStoreActions((actions: Actions) => actions.flashes); const handleFieldUpdate = (e: React.ChangeEvent) => setEmail(e.target.value); diff --git a/resources/scripts/components/auth/LoginCheckpointContainer.tsx b/resources/scripts/components/auth/LoginCheckpointContainer.tsx index ccc53e5bc..54ac3ee48 100644 --- a/resources/scripts/components/auth/LoginCheckpointContainer.tsx +++ b/resources/scripts/components/auth/LoginCheckpointContainer.tsx @@ -4,15 +4,15 @@ import loginCheckpoint from '@/api/auth/loginCheckpoint'; import { httpErrorToHuman } from '@/api/http'; import LoginFormContainer from '@/components/auth/LoginFormContainer'; import { Actions, useStoreActions } from 'easy-peasy'; -import { ApplicationState } from '@/state/types'; import { StaticContext } from 'react-router'; import FlashMessageRender from '@/components/FlashMessageRender'; +import { ApplicationStore } from '@/state'; export default ({ history, location: { state } }: RouteComponentProps<{}, StaticContext, { token?: string }>) => { const [ code, setCode ] = useState(''); const [ isLoading, setIsLoading ] = useState(false); - const { clearFlashes, addFlash } = useStoreActions((actions: Actions) => actions.flashes); + const { clearFlashes, addFlash } = useStoreActions((actions: Actions) => actions.flashes); if (!state || !state.token) { history.replace('/auth/login'); diff --git a/resources/scripts/components/auth/LoginContainer.tsx b/resources/scripts/components/auth/LoginContainer.tsx index f29a8c599..95538d78c 100644 --- a/resources/scripts/components/auth/LoginContainer.tsx +++ b/resources/scripts/components/auth/LoginContainer.tsx @@ -5,14 +5,14 @@ import { httpErrorToHuman } from '@/api/http'; import LoginFormContainer from '@/components/auth/LoginFormContainer'; import FlashMessageRender from '@/components/FlashMessageRender'; import { Actions, useStoreActions } from 'easy-peasy'; -import { ApplicationState } from '@/state/types'; +import { ApplicationStore } from '@/state'; export default ({ history }: RouteComponentProps) => { const [ username, setUsername ] = useState(''); const [ password, setPassword ] = useState(''); const [ isLoading, setLoading ] = useState(false); - const { clearFlashes, addFlash } = useStoreActions((actions: Actions) => actions.flashes); + const { clearFlashes, addFlash } = useStoreActions((actions: Actions) => actions.flashes); const submit = (e: React.FormEvent) => { e.preventDefault(); diff --git a/resources/scripts/components/auth/ResetPasswordContainer.tsx b/resources/scripts/components/auth/ResetPasswordContainer.tsx index 9467bb92b..6954ebd2a 100644 --- a/resources/scripts/components/auth/ResetPasswordContainer.tsx +++ b/resources/scripts/components/auth/ResetPasswordContainer.tsx @@ -7,7 +7,7 @@ import { httpErrorToHuman } from '@/api/http'; import LoginFormContainer from '@/components/auth/LoginFormContainer'; import FlashMessageRender from '@/components/FlashMessageRender'; import { Actions, useStoreActions } from 'easy-peasy'; -import { ApplicationState } from '@/state/types'; +import { ApplicationStore } from '@/state'; type Props = Readonly & {}>; @@ -17,7 +17,7 @@ export default (props: Props) => { const [ password, setPassword ] = useState(''); const [ passwordConfirm, setPasswordConfirm ] = useState(''); - const { clearFlashes, addFlash } = useStoreActions((actions: Actions) => actions.flashes); + const { clearFlashes, addFlash } = useStoreActions((actions: Actions) => actions.flashes); const parsed = parse(props.location.search); if (email.length === 0 && parsed.email) { diff --git a/resources/scripts/components/dashboard/forms/UpdateEmailAddressForm.tsx b/resources/scripts/components/dashboard/forms/UpdateEmailAddressForm.tsx index 4593908a5..1ee88a9ca 100644 --- a/resources/scripts/components/dashboard/forms/UpdateEmailAddressForm.tsx +++ b/resources/scripts/components/dashboard/forms/UpdateEmailAddressForm.tsx @@ -1,11 +1,11 @@ import React from 'react'; import { Actions, State, useStoreActions, useStoreState } from 'easy-peasy'; -import { ApplicationState } from '@/state/types'; import { Form, Formik, FormikActions } from 'formik'; import * as Yup from 'yup'; import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; import Field from '@/components/elements/Field'; import { httpErrorToHuman } from '@/api/http'; +import { ApplicationStore } from '@/state'; interface Values { email: string; @@ -18,10 +18,10 @@ const schema = Yup.object().shape({ }); export default () => { - const user = useStoreState((state: State) => state.user.data); - const updateEmail = useStoreActions((state: Actions) => state.user.updateUserEmail); + const user = useStoreState((state: State) => state.user.data); + const updateEmail = useStoreActions((state: Actions) => state.user.updateUserEmail); - const { clearFlashes, addFlash } = useStoreActions((actions: Actions) => actions.flashes); + const { clearFlashes, addFlash } = useStoreActions((actions: Actions) => actions.flashes); const submit = (values: Values, { resetForm, setSubmitting }: FormikActions) => { clearFlashes('account:email'); diff --git a/resources/scripts/components/dashboard/forms/UpdatePasswordForm.tsx b/resources/scripts/components/dashboard/forms/UpdatePasswordForm.tsx index dce13e650..7c25c1287 100644 --- a/resources/scripts/components/dashboard/forms/UpdatePasswordForm.tsx +++ b/resources/scripts/components/dashboard/forms/UpdatePasswordForm.tsx @@ -1,12 +1,12 @@ import React from 'react'; import { Actions, State, useStoreActions, useStoreState } from 'easy-peasy'; -import { ApplicationState } from '@/state/types'; import { Form, Formik, FormikActions } from 'formik'; import Field from '@/components/elements/Field'; import * as Yup from 'yup'; import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; import updateAccountPassword from '@/api/account/updateAccountPassword'; import { httpErrorToHuman } from '@/api/http'; +import { ApplicationStore } from '@/state'; interface Values { current: string; @@ -23,8 +23,8 @@ const schema = Yup.object().shape({ }); export default () => { - const user = useStoreState((state: State) => state.user.data); - const { clearFlashes, addFlash } = useStoreActions((actions: Actions) => actions.flashes); + const user = useStoreState((state: State) => state.user.data); + const { clearFlashes, addFlash } = useStoreActions((actions: Actions) => actions.flashes); if (!user) { return null; diff --git a/resources/scripts/components/server/Console.tsx b/resources/scripts/components/server/Console.tsx index e21d4d07a..66f0b76bd 100644 --- a/resources/scripts/components/server/Console.tsx +++ b/resources/scripts/components/server/Console.tsx @@ -2,9 +2,9 @@ import React, { createRef } from 'react'; import { Terminal } from 'xterm'; import * as TerminalFit from 'xterm/lib/addons/fit/fit'; import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; -import { ApplicationState } from '@/state/types'; import { connect } from 'react-redux'; import { Websocket } from '@/plugins/Websocket'; +import { ServerStore } from '@/state/server'; const theme = { background: 'transparent', @@ -113,8 +113,8 @@ class Console extends React.PureComponent> { } export default connect( - (state: ApplicationState) => ({ - connected: state.server.socket.connected, - instance: state.server.socket.instance, + (state: ServerStore) => ({ + connected: state.socket.connected, + instance: state.socket.instance, }), )(Console); diff --git a/resources/scripts/components/server/ServerConsole.tsx b/resources/scripts/components/server/ServerConsole.tsx index 0264e0d64..19a8d9d1b 100644 --- a/resources/scripts/components/server/ServerConsole.tsx +++ b/resources/scripts/components/server/ServerConsole.tsx @@ -1,10 +1,9 @@ import React from 'react'; import Console from '@/components/server/Console'; -import { State, useStoreState } from 'easy-peasy'; -import { ApplicationState } from '@/state/types'; +import { ServerContext } from '@/state/server'; export default () => { - const status = useStoreState((state: State) => state.server.status); + const status = ServerContext.useStoreState(state => state.status.value); return (
diff --git a/resources/scripts/components/server/ServerDatabases.tsx b/resources/scripts/components/server/ServerDatabases.tsx new file mode 100644 index 000000000..a0cf75aac --- /dev/null +++ b/resources/scripts/components/server/ServerDatabases.tsx @@ -0,0 +1,24 @@ +import React, { useEffect } from 'react'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faDatabase } from '@fortawesome/free-solid-svg-icons/faDatabase'; +import getServerDatabases from '@/api/server/getServerDatabases'; +import { useStoreState } from 'easy-peasy'; + +export default () => { + useEffect(() => { + getServerDatabases('s'); + }, []); + + return ( +
+
+
+ +
+
+

sfgsfgd

+
+
+
+ ); +}; diff --git a/resources/scripts/components/server/WebsocketHandler.tsx b/resources/scripts/components/server/WebsocketHandler.tsx index 7f74a64e8..a8d2f3003 100644 --- a/resources/scripts/components/server/WebsocketHandler.tsx +++ b/resources/scripts/components/server/WebsocketHandler.tsx @@ -1,13 +1,12 @@ import React, { useEffect } from 'react'; -import { Actions, State, useStoreActions, useStoreState } from 'easy-peasy'; -import { ApplicationState } from '@/state/types'; import { Websocket } from '@/plugins/Websocket'; +import { ServerContext } from '@/state/server'; export default () => { - const server = useStoreState((state: State) => state.server.data); - const instance = useStoreState((state: State) => state.server.socket.instance); - const setServerStatus = useStoreActions((actions: Actions) => actions.server.setServerStatus); - const { setInstance, setConnectionState } = useStoreActions((actions: Actions) => actions.server.socket); + const server = ServerContext.useStoreState(state => state.server.data); + const instance = ServerContext.useStoreState(state => state.socket.instance); + const setServerStatus = ServerContext.useStoreActions(actions => actions.status.setServerStatus); + const { setInstance, setConnectionState } = ServerContext.useStoreActions(actions => actions.socket); useEffect(() => { // If there is already an instance or there is no server, just exit out of this process @@ -20,7 +19,7 @@ export default () => { const socket = new Websocket( `wss://wings.pterodactyl.test:8080/api/servers/${server.uuid}/ws`, - 'CC8kHCuMkXPosgzGO6d37wvhNcksWxG6kTrA' + 'CC8kHCuMkXPosgzGO6d37wvhNcksWxG6kTrA', ); socket.on('SOCKET_OPEN', () => setConnectionState(true)); diff --git a/resources/scripts/routers/ServerRouter.tsx b/resources/scripts/routers/ServerRouter.tsx index 62f5ff8e0..4e2708b86 100644 --- a/resources/scripts/routers/ServerRouter.tsx +++ b/resources/scripts/routers/ServerRouter.tsx @@ -3,14 +3,16 @@ import { NavLink, Route, RouteComponentProps, Switch } from 'react-router-dom'; import NavigationBar from '@/components/NavigationBar'; import ServerConsole from '@/components/server/ServerConsole'; import TransitionRouter from '@/TransitionRouter'; -import { Actions, State, useStoreActions, useStoreState } from 'easy-peasy'; -import { ApplicationState } from '@/state/types'; import Spinner from '@/components/elements/Spinner'; import WebsocketHandler from '@/components/server/WebsocketHandler'; +import ServerDatabases from '@/components/server/ServerDatabases'; +import { ServerContext } from '@/state/server'; +import { Provider } from 'react-redux'; -export default ({ match, location }: RouteComponentProps<{ id: string }>) => { - const server = useStoreState((state: State) => state.server.data); - const { clearServerState, getServer } = useStoreActions((actions: Actions) => actions.server); +const ServerRouter = ({ match, location }: RouteComponentProps<{ id: string }>) => { + const server = ServerContext.useStoreState(state => state.server.data); + const getServer = ServerContext.useStoreActions(actions => actions.server.getServer); + const clearServerState = ServerContext.useStoreActions(actions => actions.clearServerState); if (!server) { getServer(match.params.id); @@ -31,22 +33,31 @@ export default ({ match, location }: RouteComponentProps<{ id: string }>) => {
- -
- {!server ? -
- -
- : - - - - - - - } -
-
+ + +
+ {!server ? +
+ +
+ : + + + + + + + + } +
+
+
); }; + +export default (props: RouteComponentProps) => ( + + + +); diff --git a/resources/scripts/state/flashes.ts b/resources/scripts/state/flashes.ts new file mode 100644 index 000000000..666778a11 --- /dev/null +++ b/resources/scripts/state/flashes.ts @@ -0,0 +1,28 @@ +import { Action, action } from 'easy-peasy'; +import { FlashMessageType } from '@/components/MessageBox'; + +export interface FlashStore { + items: FlashMessage[]; + addFlash: Action; + clearFlashes: Action; +} + +export interface FlashMessage { + id?: string; + key?: string; + type: FlashMessageType; + title?: string; + message: string; +} + +const flashes: FlashStore = { + items: [], + addFlash: action((state, payload) => { + state.items.push(payload); + }), + clearFlashes: action((state, payload) => { + state.items = payload ? state.items.filter(flashes => flashes.key !== payload) : []; + }), +}; + +export default flashes; diff --git a/resources/scripts/state/index.ts b/resources/scripts/state/index.ts index 5e19365f8..db3181aa5 100644 --- a/resources/scripts/state/index.ts +++ b/resources/scripts/state/index.ts @@ -1,13 +1,15 @@ import { createStore } from 'easy-peasy'; -import { ApplicationState } from '@/state/types'; -import flashes from '@/state/models/flashes'; -import user from '@/state/models/user'; -import server from '@/state/models/server'; +import flashes, { FlashStore } from '@/state/flashes'; +import user, { UserStore } from '@/state/user'; -const state: ApplicationState = { +export interface ApplicationStore { + flashes: FlashStore; + user: UserStore; +} + +const state: ApplicationStore = { flashes, user, - server, }; export const store = createStore(state); diff --git a/resources/scripts/state/models/flashes.ts b/resources/scripts/state/models/flashes.ts deleted file mode 100644 index 97c9f080b..000000000 --- a/resources/scripts/state/models/flashes.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { action } from 'easy-peasy'; -import { FlashState } from '@/state/types'; - -const flashes: FlashState = { - items: [], - addFlash: action((state, payload) => { - state.items.push(payload); - }), - clearFlashes: action((state, payload) => { - state.items = payload ? state.items.filter(flashes => flashes.key !== payload) : []; - }), -}; - -export default flashes; diff --git a/resources/scripts/state/models/server.ts b/resources/scripts/state/models/server.ts deleted file mode 100644 index c7b7942dd..000000000 --- a/resources/scripts/state/models/server.ts +++ /dev/null @@ -1,43 +0,0 @@ -import getServer, { Server } from '@/api/server/getServer'; -import { action, Action, thunk, Thunk } from 'easy-peasy'; -import socket, { SocketState } from './socket'; - -export type ServerStatus = 'offline' | 'starting' | 'stopping' | 'running'; - -export interface ServerState { - data?: Server; - status: ServerStatus; - socket: SocketState; - getServer: Thunk>; - setServer: Action; - setServerStatus: Action; - clearServerState: Action; -} - -const server: ServerState = { - socket, - status: 'offline', - getServer: thunk(async (actions, payload) => { - const server = await getServer(payload); - actions.setServer(server); - }), - setServer: action((state, payload) => { - state.data = payload; - }), - setServerStatus: action((state, payload) => { - state.status = payload; - }), - clearServerState: action(state => { - state.data = undefined; - - if (state.socket.instance) { - state.socket.instance.removeAllListeners(); - state.socket.instance.close(); - } - - state.socket.instance = null; - state.socket.connected = false; - }), -}; - -export default server; diff --git a/resources/scripts/state/server/index.ts b/resources/scripts/state/server/index.ts new file mode 100644 index 000000000..c29e61bf8 --- /dev/null +++ b/resources/scripts/state/server/index.ts @@ -0,0 +1,57 @@ +import getServer, { Server } from '@/api/server/getServer'; +import { action, Action, createContextStore, thunk, Thunk } from 'easy-peasy'; +import socket, { SocketStore } from './socket'; + +export type ServerStatus = 'offline' | 'starting' | 'stopping' | 'running'; + +interface ServerDataStore { + data?: Server; + getServer: Thunk>; + setServer: Action; +} + +const server: ServerDataStore = { + getServer: thunk(async (actions, payload) => { + const server = await getServer(payload); + actions.setServer(server); + }), + setServer: action((state, payload) => { + state.data = payload; + }), +}; + +interface ServerStatusStore { + value: ServerStatus; + setServerStatus: Action; +} + +const status: ServerStatusStore = { + value: 'offline', + setServerStatus: action((state, payload) => { + state.value = payload; + }), +}; + +export interface ServerStore { + server: ServerDataStore; + socket: SocketStore; + status: ServerStatusStore; + clearServerState: Action; +} + +export const ServerContext = createContextStore({ + server, + socket, + status, + clearServerState: action(state => { + state.server.data = undefined; + + if (state.socket.instance) { + state.socket.instance.removeAllListeners(); + state.socket.instance.close(); + } + + state.socket.instance = null; + state.socket.connected = false; + }), +}, { name: 'ServerStore' }); diff --git a/resources/scripts/state/models/socket.ts b/resources/scripts/state/server/socket.ts similarity index 70% rename from resources/scripts/state/models/socket.ts rename to resources/scripts/state/server/socket.ts index 10922feda..e67910668 100644 --- a/resources/scripts/state/models/socket.ts +++ b/resources/scripts/state/server/socket.ts @@ -1,14 +1,14 @@ import { Action, action } from 'easy-peasy'; import { Websocket } from '@/plugins/Websocket'; -export interface SocketState { +export interface SocketStore { instance: Websocket | null; connected: boolean; - setInstance: Action; - setConnectionState: Action; + setInstance: Action; + setConnectionState: Action; } -const socket: SocketState = { +const socket: SocketStore = { instance: null, connected: false, setInstance: action((state, payload) => { diff --git a/resources/scripts/state/types.d.ts b/resources/scripts/state/types.d.ts deleted file mode 100644 index d54afc29d..000000000 --- a/resources/scripts/state/types.d.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { FlashMessageType } from '@/components/MessageBox'; -import { Action } from 'easy-peasy'; -import { UserState } from '@/state/models/user'; -import { ServerState } from '@/state/models/server'; - -export interface ApplicationState { - flashes: FlashState; - user: UserState; - server: ServerState; -} - -export interface FlashState { - items: FlashMessage[]; - addFlash: Action; - clearFlashes: Action; -} - -export interface FlashMessage { - id?: string; - key?: string; - type: FlashMessageType; - title?: string; - message: string; -} diff --git a/resources/scripts/state/models/user.ts b/resources/scripts/state/user.ts similarity index 80% rename from resources/scripts/state/models/user.ts rename to resources/scripts/state/user.ts index c7da19fbc..087bfe003 100644 --- a/resources/scripts/state/models/user.ts +++ b/resources/scripts/state/user.ts @@ -12,14 +12,14 @@ export interface UserData { updatedAt: Date; } -export interface UserState { +export interface UserStore { data?: UserData; - setUserData: Action; - updateUserData: Action>; - updateUserEmail: Thunk>; + setUserData: Action; + updateUserData: Action>; + updateUserEmail: Thunk>; } -const user: UserState = { +const user: UserStore = { data: undefined, setUserData: action((state, payload) => { state.data = payload; From e3db56417521133f988fae4eafff0f5364a0d75b Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Tue, 9 Jul 2019 22:00:29 -0700 Subject: [PATCH 2/7] Add basic support for listing a server's databases --- .../dashboard/DashboardContainer.tsx | 8 +- .../scripts/components/elements/Spinner.tsx | 9 +- .../components/server/ServerDatabases.tsx | 24 ---- .../server/databases/DatabaseRow.tsx | 39 ++++++ .../server/databases/DatabasesContainer.tsx | 51 ++++++++ resources/scripts/routers/ServerRouter.tsx | 4 +- resources/styles/components/miscellaneous.css | 113 ++---------------- 7 files changed, 110 insertions(+), 138 deletions(-) delete mode 100644 resources/scripts/components/server/ServerDatabases.tsx create mode 100644 resources/scripts/components/server/databases/DatabaseRow.tsx create mode 100644 resources/scripts/components/server/databases/DatabasesContainer.tsx diff --git a/resources/scripts/components/dashboard/DashboardContainer.tsx b/resources/scripts/components/dashboard/DashboardContainer.tsx index 052d00034..a9c59a679 100644 --- a/resources/scripts/components/dashboard/DashboardContainer.tsx +++ b/resources/scripts/components/dashboard/DashboardContainer.tsx @@ -9,8 +9,8 @@ import { Link } from 'react-router-dom'; export default () => (
- -
+ +
@@ -49,8 +49,8 @@ export default () => (
-
-
+
+
diff --git a/resources/scripts/components/elements/Spinner.tsx b/resources/scripts/components/elements/Spinner.tsx index 090980e3f..d1704916f 100644 --- a/resources/scripts/components/elements/Spinner.tsx +++ b/resources/scripts/components/elements/Spinner.tsx @@ -1,6 +1,11 @@ import React from 'react'; import classNames from 'classnames'; -export default ({ large }: { large?: boolean }) => ( -
+export default ({ large, centered }: { large?: boolean; centered?: boolean }) => ( + centered ? +
+
+
+ : +
); diff --git a/resources/scripts/components/server/ServerDatabases.tsx b/resources/scripts/components/server/ServerDatabases.tsx deleted file mode 100644 index a0cf75aac..000000000 --- a/resources/scripts/components/server/ServerDatabases.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import React, { useEffect } from 'react'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { faDatabase } from '@fortawesome/free-solid-svg-icons/faDatabase'; -import getServerDatabases from '@/api/server/getServerDatabases'; -import { useStoreState } from 'easy-peasy'; - -export default () => { - useEffect(() => { - getServerDatabases('s'); - }, []); - - return ( -
-
-
- -
-
-

sfgsfgd

-
-
-
- ); -}; diff --git a/resources/scripts/components/server/databases/DatabaseRow.tsx b/resources/scripts/components/server/databases/DatabaseRow.tsx new file mode 100644 index 000000000..5638839b9 --- /dev/null +++ b/resources/scripts/components/server/databases/DatabaseRow.tsx @@ -0,0 +1,39 @@ +import React from 'react'; +import { ServerDatabase } from '@/api/server/getServerDatabases'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faDatabase } from '@fortawesome/free-solid-svg-icons/faDatabase'; +import { faTrashAlt } from '@fortawesome/free-solid-svg-icons/faTrashAlt'; +import { faEye } from '@fortawesome/free-solid-svg-icons/faEye'; + +export default ({ database }: { database: ServerDatabase }) => { + return ( +
+
+ +
+
+

{database.name}

+
+
+

Endpoint:

+

{database.connectionString}

+
+
+

Connections From:

+

{database.allowConnectionsFrom}

+
+
+

Username:

+

{database.username}

+
+
+ + +
+
+ ); +}; diff --git a/resources/scripts/components/server/databases/DatabasesContainer.tsx b/resources/scripts/components/server/databases/DatabasesContainer.tsx new file mode 100644 index 000000000..a4843cc80 --- /dev/null +++ b/resources/scripts/components/server/databases/DatabasesContainer.tsx @@ -0,0 +1,51 @@ +import React, { useEffect, useState } from 'react'; +import getServerDatabases, { ServerDatabase } from '@/api/server/getServerDatabases'; +import { ServerContext } from '@/state/server'; +import { Actions, useStoreActions } from 'easy-peasy'; +import { ApplicationStore } from '@/state'; +import { httpErrorToHuman } from '@/api/http'; +import FlashMessageRender from '@/components/FlashMessageRender'; +import DatabaseRow from '@/components/server/databases/DatabaseRow'; +import Spinner from '@/components/elements/Spinner'; +import { CSSTransition } from 'react-transition-group'; + +export default () => { + const [ loading, setLoading ] = useState(true); + const [ databases, setDatabases ] = useState([]); + const server = ServerContext.useStoreState(state => state.server.data!); + const { addFlash, clearFlashes } = useStoreActions((actions: Actions) => actions.flashes); + + useEffect(() => { + clearFlashes('databases'); + getServerDatabases(server.uuid) + .then(databases => { + setDatabases(databases); + setLoading(false); + }) + .catch(error => addFlash({ + key: 'databases', + title: 'Error', + message: httpErrorToHuman(error), + type: 'error', + })); + }, []); + + return ( +
+ + {loading ? + + : + + + {databases.length > 0 ? + databases.map(database => ) + : +

No databases. :(

+ } +
+
+ } +
+ ); +}; diff --git a/resources/scripts/routers/ServerRouter.tsx b/resources/scripts/routers/ServerRouter.tsx index 4e2708b86..4b01f0b63 100644 --- a/resources/scripts/routers/ServerRouter.tsx +++ b/resources/scripts/routers/ServerRouter.tsx @@ -5,9 +5,9 @@ import ServerConsole from '@/components/server/ServerConsole'; import TransitionRouter from '@/TransitionRouter'; import Spinner from '@/components/elements/Spinner'; import WebsocketHandler from '@/components/server/WebsocketHandler'; -import ServerDatabases from '@/components/server/ServerDatabases'; import { ServerContext } from '@/state/server'; import { Provider } from 'react-redux'; +import DatabasesContainer from '@/components/server/databases/DatabasesContainer'; const ServerRouter = ({ match, location }: RouteComponentProps<{ id: string }>) => { const server = ServerContext.useStoreState(state => state.server.data); @@ -45,7 +45,7 @@ const ServerRouter = ({ match, location }: RouteComponentProps<{ id: string }>) - + } diff --git a/resources/styles/components/miscellaneous.css b/resources/styles/components/miscellaneous.css index 158970f9b..f023a834b 100644 --- a/resources/styles/components/miscellaneous.css +++ b/resources/styles/components/miscellaneous.css @@ -7,114 +7,15 @@ code.clean { display: inline-block; } -/** - * Indicators for server online status. - */ -.indicator { - @apply .bg-neutral-800 .border .border-primary-500; - border-radius: 50%; - width: 16px; - height: 16px; +.grey-row-box { + @apply .flex .rounded .no-underline .text-neutral-200 .items-center .bg-neutral-700 .p-4 .border .border-transparent; + transition: border-color 150ms linear; - &.online { - @apply .bg-green-600 .border-green-500; - animation: onlineblink 2s infinite alternate; + &:not(.no-hover):hover { + @apply .border-neutral-500; } - &.offline { - @apply .bg-green-600 .border-red-500; - animation: offlineblink 2s infinite alternate; - } -} - -/** - * Usage indicator labels for the server listing. - */ -.usage { - @apply .flex-1 .text-center .relative; - - & > .indicator-title { - @apply .text-xs .uppercase .font-hairline .bg-white .absolute .text-primary-500; - margin-top:-9px; - padding: 0 8px; - left: 50%; - transform: translate(-50%, 0); - } -} - -/** - * Styling for elements that contain the core page content. - */ -.content-box { - @apply .bg-white .p-6 .rounded .shadow .border .border-neutral-100; -} - -/** - * Flex boxes for server listing on user dashboard. - */ -.server-card-container { - @apply .mb-4 .w-full; - - @screen md { - @apply .w-1/2 .pr-4; - - &:nth-of-type(2n) { - @apply .pr-0; - } - } - - @screen lg { - @apply .w-1/3 .pr-4; - - &:nth-of-type(2n) { - @apply .pr-4; - } - - &:nth-of-type(3n) { - @apply .pr-0; - } - } - - & > div { - @apply .flex .flex-col; - transition: box-shadow 150ms ease-in; - - &:hover { - @apply .shadow-md; - } - } - - & > div > .server-card { - @apply .flex .flex-col .p-4 .border .border-t-4 .border-neutral-100 .bg-white; - transition: all 100ms ease-in; - - & .identifier-icon { - @apply .select-none .inline-block .rounded-full .text-white .text-center .leading-none .justify-center .w-8 .h-8 .mr-2 .flex .flex-row .items-center; - } - - & a, & a:visited { - @apply .no-underline .text-neutral-800; - } - } - - & > div > .footer { - @apply .border .border-neutral-100 .border-t-0 .bg-neutral-50 .shadow-inner; - } -} - -.pillbox { - @apply .rounded-full .px-2 .py-1 .text-white .font-medium .leading-none .text-xs; -} - -.server-search { - @apply .w-full .my-4; - - & > input[type="text"] { - @apply .w-full .p-4 .rounded .border .border-neutral-200 .text-neutral-500; - transition: border 150ms ease-in; - - &:focus { - @apply .border-primary-500; - } + & > div.icon { + @apply .rounded-full .bg-neutral-500 .p-3; } } From bb3486f559f5d50f0c73b622942ebd70fde5fb58 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Tue, 9 Jul 2019 22:06:42 -0700 Subject: [PATCH 3/7] More style cleanup for database listing --- .../components/server/databases/DatabaseRow.tsx | 4 ++-- .../server/databases/DatabasesContainer.tsx | 11 +++++++++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/resources/scripts/components/server/databases/DatabaseRow.tsx b/resources/scripts/components/server/databases/DatabaseRow.tsx index 5638839b9..7e5150e97 100644 --- a/resources/scripts/components/server/databases/DatabaseRow.tsx +++ b/resources/scripts/components/server/databases/DatabaseRow.tsx @@ -28,10 +28,10 @@ export default ({ database }: { database: ServerDatabase }) => {
diff --git a/resources/scripts/components/server/databases/DatabasesContainer.tsx b/resources/scripts/components/server/databases/DatabasesContainer.tsx index a4843cc80..1f16b5a69 100644 --- a/resources/scripts/components/server/databases/DatabasesContainer.tsx +++ b/resources/scripts/components/server/databases/DatabasesContainer.tsx @@ -31,7 +31,7 @@ export default () => { }, []); return ( -
+
{loading ? @@ -41,8 +41,15 @@ export default () => { {databases.length > 0 ? databases.map(database => ) : -

No databases. :(

+

+ It looks like you have no databases. Click the button below to create one now. +

} +
+ +
} From 61dc86421d579610d643c418c93b68b5720f610a Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Tue, 9 Jul 2019 22:41:09 -0700 Subject: [PATCH 4/7] Add basic modal support --- .../scripts/components/elements/Modal.tsx | 61 +++++++++++++++++++ .../server/databases/CreateDatabaseButton.tsx | 18 ++++++ .../server/databases/DatabasesContainer.tsx | 7 +-- resources/styles/components/modal.css | 6 +- 4 files changed, 85 insertions(+), 7 deletions(-) create mode 100644 resources/scripts/components/elements/Modal.tsx create mode 100644 resources/scripts/components/server/databases/CreateDatabaseButton.tsx diff --git a/resources/scripts/components/elements/Modal.tsx b/resources/scripts/components/elements/Modal.tsx new file mode 100644 index 000000000..ce15572f8 --- /dev/null +++ b/resources/scripts/components/elements/Modal.tsx @@ -0,0 +1,61 @@ +import React, { useEffect, useState } from 'react'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faTimes } from '@fortawesome/free-solid-svg-icons/faTimes'; +import { CSSTransition } from 'react-transition-group'; + +interface Props { + visible: boolean; + onDismissed: () => void; + dismissable?: boolean; + closeOnEscape?: boolean; + closeOnBackground?: boolean; + children: React.ReactChild; +} + +export default (props: Props) => { + const [render, setRender] = useState(props.visible); + + const handleEscapeEvent = (e: KeyboardEvent) => { + if (props.dismissable !== false && props.closeOnEscape !== false && e.key === 'Escape') { + setRender(false); + } + }; + + useEffect(() => setRender(props.visible), [props.visible]); + + useEffect(() => { + window.addEventListener('keydown', handleEscapeEvent); + + return () => window.removeEventListener('keydown', handleEscapeEvent); + }, [render]); + + return ( + props.onDismissed()} + > +
{ + if (props.dismissable !== false && props.closeOnBackground !== false) { + e.stopPropagation(); + if (e.target === e.currentTarget) { + setRender(false); + } + } + }}> +
+ {props.dismissable !== false && +
setRender(false)}> + +
+ } +
+ {props.children} +
+
+
+
+ ); +}; diff --git a/resources/scripts/components/server/databases/CreateDatabaseButton.tsx b/resources/scripts/components/server/databases/CreateDatabaseButton.tsx new file mode 100644 index 000000000..30d353e9a --- /dev/null +++ b/resources/scripts/components/server/databases/CreateDatabaseButton.tsx @@ -0,0 +1,18 @@ +import React, { useState } from 'react'; +import { ServerDatabase } from '@/api/server/getServerDatabases'; +import Modal from '@/components/elements/Modal'; + +export default ({ onCreated }: { onCreated: (database: ServerDatabase) => void }) => { + const [ visible, setVisible ] = useState(false); + + return ( + + setVisible(false)}> +

Testing

+
+ +
+ ); +}; diff --git a/resources/scripts/components/server/databases/DatabasesContainer.tsx b/resources/scripts/components/server/databases/DatabasesContainer.tsx index 1f16b5a69..680efc391 100644 --- a/resources/scripts/components/server/databases/DatabasesContainer.tsx +++ b/resources/scripts/components/server/databases/DatabasesContainer.tsx @@ -8,6 +8,7 @@ import FlashMessageRender from '@/components/FlashMessageRender'; import DatabaseRow from '@/components/server/databases/DatabaseRow'; import Spinner from '@/components/elements/Spinner'; import { CSSTransition } from 'react-transition-group'; +import CreateDatabaseButton from '@/components/server/databases/CreateDatabaseButton'; export default () => { const [ loading, setLoading ] = useState(true); @@ -45,10 +46,8 @@ export default () => { It looks like you have no databases. Click the button below to create one now.

} -
- +
+ setDatabases(s => [...s, database])}/>
diff --git a/resources/styles/components/modal.css b/resources/styles/components/modal.css index 4deb58cbf..650a7ec8f 100644 --- a/resources/styles/components/modal.css +++ b/resources/styles/components/modal.css @@ -7,13 +7,13 @@ @apply .relative .w-full .max-w-md .m-auto .flex-col .flex; &.top { - margin-top: 15%; + margin-top: 10%; } & > .modal-close-icon { @apply .absolute .pin-r .p-2 .text-white .cursor-pointer .opacity-50; transition: opacity 150ms linear, transform 150ms ease-in; - top: -2.5rem; + top: -2rem; &:hover { @apply .opacity-100; @@ -22,7 +22,7 @@ } & > .modal-content { - @apply .bg-white .rounded .shadow-md; + @apply .bg-neutral-900 .rounded .shadow-md; transition: all 250ms ease; } From 1f763dc155d88c9f9b34d5cafb5d460f88665fd5 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Tue, 16 Jul 2019 21:43:11 -0700 Subject: [PATCH 5/7] Finish support for creating databases in the UI --- .../api/server/createServerDatabase.ts | 15 +++ .../scripts/components/elements/Modal.tsx | 12 ++- .../server/databases/CreateDatabaseButton.tsx | 102 +++++++++++++++++- .../server/databases/DatabaseRow.tsx | 5 +- .../server/databases/DatabasesContainer.tsx | 8 +- resources/styles/components/modal.css | 4 +- webpack.config.js | 2 +- 7 files changed, 136 insertions(+), 12 deletions(-) create mode 100644 resources/scripts/api/server/createServerDatabase.ts diff --git a/resources/scripts/api/server/createServerDatabase.ts b/resources/scripts/api/server/createServerDatabase.ts new file mode 100644 index 000000000..90103337c --- /dev/null +++ b/resources/scripts/api/server/createServerDatabase.ts @@ -0,0 +1,15 @@ +import { rawDataToServerDatabase, ServerDatabase } from '@/api/server/getServerDatabases'; +import http from '@/api/http'; + +export default (uuid: string, data: { connectionsFrom: string; databaseName: string }): Promise => { + return new Promise((resolve, reject) => { + http.post(`/api/client/servers/${uuid}/databases`, { + database: data.databaseName, + remote: data.connectionsFrom, + }, { + params: { include: 'password' }, + }) + .then(response => resolve(rawDataToServerDatabase(response.data.attributes))) + .catch(reject); + }); +}; diff --git a/resources/scripts/components/elements/Modal.tsx b/resources/scripts/components/elements/Modal.tsx index ce15572f8..be1e62e20 100644 --- a/resources/scripts/components/elements/Modal.tsx +++ b/resources/scripts/components/elements/Modal.tsx @@ -2,6 +2,7 @@ import React, { useEffect, useState } from 'react'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faTimes } from '@fortawesome/free-solid-svg-icons/faTimes'; import { CSSTransition } from 'react-transition-group'; +import Spinner from '@/components/elements/Spinner'; interface Props { visible: boolean; @@ -9,7 +10,8 @@ interface Props { dismissable?: boolean; closeOnEscape?: boolean; closeOnBackground?: boolean; - children: React.ReactChild; + showSpinnerOverlay?: boolean; + children: React.ReactNode; } export default (props: Props) => { @@ -51,6 +53,14 @@ export default (props: Props) => {
} + {props.showSpinnerOverlay && +
+ +
+ }
{props.children}
diff --git a/resources/scripts/components/server/databases/CreateDatabaseButton.tsx b/resources/scripts/components/server/databases/CreateDatabaseButton.tsx index 30d353e9a..8430e492e 100644 --- a/resources/scripts/components/server/databases/CreateDatabaseButton.tsx +++ b/resources/scripts/components/server/databases/CreateDatabaseButton.tsx @@ -1,17 +1,111 @@ import React, { useState } from 'react'; import { ServerDatabase } from '@/api/server/getServerDatabases'; import Modal from '@/components/elements/Modal'; +import { Form, Formik, FormikActions } from 'formik'; +import Field from '@/components/elements/Field'; +import { object, string } from 'yup'; +import createServerDatabase from '@/api/server/createServerDatabase'; +import { ServerContext } from '@/state/server'; +import { Actions, useStoreActions } from 'easy-peasy'; +import { ApplicationStore } from '@/state'; +import { httpErrorToHuman } from '@/api/http'; +import FlashMessageRender from '@/components/FlashMessageRender'; + +interface Values { + databaseName: string; + connectionsFrom: string; +} + +const schema = object().shape({ + databaseName: string() + .required('A database name must be provided.') + .min(5, 'Database name must be at least 5 characters.') + .max(64, 'Database name must not exceed 64 characters.') + .matches(/^[A-Za-z0-9_\-.]{5,64}$/, 'Database name should only contain alphanumeric characters, underscores, dashes, and/or periods.'), + connectionsFrom: string() + .required('A connection value must be provided.') + .matches(/^([1-9]{1,3}|%)(\.([0-9]{1,3}|%))?(\.([0-9]{1,3}|%))?(\.([0-9]{1,3}|%))?$/, 'A valid connection address must be provided.'), +}); export default ({ onCreated }: { onCreated: (database: ServerDatabase) => void }) => { const [ visible, setVisible ] = useState(false); + const { addFlash, clearFlashes } = useStoreActions((actions: Actions) => actions.flashes); + const server = ServerContext.useStoreState(state => state.server.data!); + + const submit = (values: Values, { setSubmitting }: FormikActions) => { + clearFlashes(); + createServerDatabase(server.uuid, { ...values }) + .then(database => { + onCreated(database); + setVisible(false); + }) + .catch(error => { + console.log(error); + addFlash({ + key: 'create-database-modal', + type: 'error', + title: 'Error', + message: httpErrorToHuman(error), + }); + }) + .then(() => setSubmitting(false)); + }; return ( - setVisible(false)}> -

Testing

-
+ + { + ({ isSubmitting, resetForm }) => ( + { + resetForm(); + setVisible(false); + }} + > + +

Create new database

+
+ +
+ +
+
+ + +
+ +
+ ) + } +
); diff --git a/resources/scripts/components/server/databases/DatabaseRow.tsx b/resources/scripts/components/server/databases/DatabaseRow.tsx index 7e5150e97..b385d5942 100644 --- a/resources/scripts/components/server/databases/DatabaseRow.tsx +++ b/resources/scripts/components/server/databases/DatabaseRow.tsx @@ -4,10 +4,11 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faDatabase } from '@fortawesome/free-solid-svg-icons/faDatabase'; import { faTrashAlt } from '@fortawesome/free-solid-svg-icons/faTrashAlt'; import { faEye } from '@fortawesome/free-solid-svg-icons/faEye'; +import classNames from 'classnames'; -export default ({ database }: { database: ServerDatabase }) => { +export default ({ database, className }: { database: ServerDatabase; className?: string }) => { return ( -
+
diff --git a/resources/scripts/components/server/databases/DatabasesContainer.tsx b/resources/scripts/components/server/databases/DatabasesContainer.tsx index 680efc391..66b14e994 100644 --- a/resources/scripts/components/server/databases/DatabasesContainer.tsx +++ b/resources/scripts/components/server/databases/DatabasesContainer.tsx @@ -40,14 +40,18 @@ export default () => { {databases.length > 0 ? - databases.map(database => ) + databases.map((database, index) => 0 ? 'mt-1' : undefined} + />) :

It looks like you have no databases. Click the button below to create one now.

}
- setDatabases(s => [...s, database])}/> + setDatabases(s => [ ...s, database ])}/>
diff --git a/resources/styles/components/modal.css b/resources/styles/components/modal.css index 650a7ec8f..7c3a6ae94 100644 --- a/resources/styles/components/modal.css +++ b/resources/styles/components/modal.css @@ -1,6 +1,6 @@ .modal-mask { @apply .fixed .pin .z-50 .overflow-auto .flex; - background: rgba(0, 0, 0, 0.7); + background: rgba(0, 0, 0, 0.70); transition: opacity 250ms ease; & > .modal-container { @@ -22,7 +22,7 @@ } & > .modal-content { - @apply .bg-neutral-900 .rounded .shadow-md; + @apply .bg-neutral-800 .rounded .shadow-md; transition: all 250ms ease; } diff --git a/webpack.config.js b/webpack.config.js index 0054d91d3..10bdb1c22 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -135,7 +135,7 @@ module.exports = { }, plugins: plugins, optimization: { - minimize: true, + minimize: isProduction, minimizer: [ new TerserPlugin({ cache: true, From d081f328abec7f6b3d79c6f86d3ad8cbe084c04d Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Tue, 16 Jul 2019 22:15:14 -0700 Subject: [PATCH 6/7] Support deleting existing databases --- .../api/server/deleteServerDatabase.ts | 9 + .../scripts/components/FlashMessageRender.tsx | 7 +- .../server/databases/CreateDatabaseButton.tsx | 3 +- .../server/databases/DatabaseRow.tsx | 154 ++++++++++++++---- .../server/databases/DatabasesContainer.tsx | 13 +- resources/styles/components/typography.css | 2 +- tailwind.js | 2 + 7 files changed, 150 insertions(+), 40 deletions(-) create mode 100644 resources/scripts/api/server/deleteServerDatabase.ts diff --git a/resources/scripts/api/server/deleteServerDatabase.ts b/resources/scripts/api/server/deleteServerDatabase.ts new file mode 100644 index 000000000..23275bd36 --- /dev/null +++ b/resources/scripts/api/server/deleteServerDatabase.ts @@ -0,0 +1,9 @@ +import http from '@/api/http'; + +export default (uuid: string, database: string): Promise => { + return new Promise((resolve, reject) => { + http.delete(`/api/client/servers/${uuid}/databases/${database}`) + .then(() => resolve()) + .catch(reject); + }); +}; diff --git a/resources/scripts/components/FlashMessageRender.tsx b/resources/scripts/components/FlashMessageRender.tsx index adffd09d2..cb0b37026 100644 --- a/resources/scripts/components/FlashMessageRender.tsx +++ b/resources/scripts/components/FlashMessageRender.tsx @@ -6,10 +6,10 @@ import { ApplicationStore } from '@/state'; type Props = Readonly<{ byKey?: string; spacerClass?: string; - withBottomSpace?: boolean; + className?: string; }>; -export default ({ withBottomSpace, spacerClass, byKey }: Props) => { +export default ({ className, spacerClass, byKey }: Props) => { const flashes = useStoreState((state: State) => state.flashes.items); let filtered = flashes; @@ -21,9 +21,8 @@ export default ({ withBottomSpace, spacerClass, byKey }: Props) => { return null; } - // noinspection PointlessBooleanExpressionJS return ( -
+
{ filtered.map((flash, index) => ( diff --git a/resources/scripts/components/server/databases/CreateDatabaseButton.tsx b/resources/scripts/components/server/databases/CreateDatabaseButton.tsx index 8430e492e..54261605d 100644 --- a/resources/scripts/components/server/databases/CreateDatabaseButton.tsx +++ b/resources/scripts/components/server/databases/CreateDatabaseButton.tsx @@ -69,7 +69,7 @@ export default ({ onCreated }: { onCreated: (database: ServerDatabase) => void } setVisible(false); }} > - +

Create new database

void }
+ +
+ + + ) + } + +
+
+ +
+
+

{database.name}

+
+
+

Endpoint:

+

{database.connectionString}

+
+
+

Connections + From:

+

{database.allowConnectionsFrom}

+
+
+

Username:

+

{database.username}

+
+
+ + +
-
-

{database.name}

-
-
-

Endpoint:

-

{database.connectionString}

-
-
-

Connections From:

-

{database.allowConnectionsFrom}

-
-
-

Username:

-

{database.username}

-
-
- - -
-
+ ); }; diff --git a/resources/scripts/components/server/databases/DatabasesContainer.tsx b/resources/scripts/components/server/databases/DatabasesContainer.tsx index 66b14e994..a7c2c1d72 100644 --- a/resources/scripts/components/server/databases/DatabasesContainer.tsx +++ b/resources/scripts/components/server/databases/DatabasesContainer.tsx @@ -40,11 +40,14 @@ export default () => { {databases.length > 0 ? - databases.map((database, index) => 0 ? 'mt-1' : undefined} - />) + databases.map((database, index) => ( + setDatabases(s => [ ...s.filter(d => d.id !== database.id) ])} + className={index > 0 ? 'mt-1' : undefined} + /> + )) :

It looks like you have no databases. Click the button below to create one now. diff --git a/resources/styles/components/typography.css b/resources/styles/components/typography.css index 301eabb2a..c6b3f10ff 100644 --- a/resources/styles/components/typography.css +++ b/resources/styles/components/typography.css @@ -13,5 +13,5 @@ h1, h2, h3, h4, h5, h6 { } p { - @apply .text-neutral-200; + @apply .text-neutral-200 .leading-snug; } diff --git a/tailwind.js b/tailwind.js index ec7f6c291..2f3314e52 100644 --- a/tailwind.js +++ b/tailwind.js @@ -290,7 +290,9 @@ module.exports = { leading: { 'none': 1, 'tight': 1.25, + 'snug': 1.375, 'normal': 1.5, + 'relaxed': 1.625, 'loose': 2, }, From f6ee885f26757de7cb8e03f879c232d7a7f6e627 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Tue, 16 Jul 2019 22:29:00 -0700 Subject: [PATCH 7/7] Support for viewing database passwords --- .../server/databases/DatabaseRow.tsx | 29 +++++++++++++++++-- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/resources/scripts/components/server/databases/DatabaseRow.tsx b/resources/scripts/components/server/databases/DatabaseRow.tsx index 48b88c551..3cad11755 100644 --- a/resources/scripts/components/server/databases/DatabaseRow.tsx +++ b/resources/scripts/components/server/databases/DatabaseRow.tsx @@ -24,6 +24,7 @@ interface Props { export default ({ database, className, onDelete }: Props) => { const [visible, setVisible] = useState(false); + const [connectionVisible, setConnectionVisible] = useState(false); const { addFlash, clearFlashes } = useStoreActions((actions: Actions) => actions.flashes); const server = ServerContext.useStoreState(state => state.server.data!); @@ -102,6 +103,27 @@ export default ({ database, className, onDelete }: Props) => { ) } + setConnectionVisible(false)}> +

Database connection details

+
+ + +
+
+ + +
+
+ +
+
@@ -114,8 +136,9 @@ export default ({ database, className, onDelete }: Props) => {

{database.connectionString}

-

Connections - From:

+

+ Connections From: +

{database.allowConnectionsFrom}

@@ -123,7 +146,7 @@ export default ({ database, className, onDelete }: Props) => {

{database.username}

-