diff --git a/bridge/config.yml b/bridge/config.yml index aa4ba1c..5502407 100644 --- a/bridge/config.yml +++ b/bridge/config.yml @@ -1,2 +1,3 @@ allowedServers: + - "195.201.123.169:16000" - "127.0.0.1:16000" diff --git a/bridge/go.mod b/bridge/go.mod index 710c045..2699d83 100644 --- a/bridge/go.mod +++ b/bridge/go.mod @@ -3,7 +3,7 @@ module git.vh7.uk/jakew/echo-web/bridge go 1.18 require ( - git.vh7.uk/jakew/echo-go v0.0.0-20220805101505-158189ae7d58 + git.vh7.uk/jakew/echo-go v0.0.0-20220809112338-5bd7802455cb github.com/gorilla/websocket v1.5.0 github.com/rs/zerolog v1.27.0 github.com/samber/lo v1.27.0 @@ -17,3 +17,5 @@ require ( golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 // indirect golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 // indirect ) + +replace git.vh7.uk/jakew/echo-go => ../../echo-go diff --git a/bridge/go.sum b/bridge/go.sum index a79757f..4a3ebca 100644 --- a/bridge/go.sum +++ b/bridge/go.sum @@ -2,6 +2,8 @@ git.vh7.uk/jakew/echo-go v0.0.0-20220803185705-357282c0e444 h1:/GiYRoIHnYqoF9bAH git.vh7.uk/jakew/echo-go v0.0.0-20220803185705-357282c0e444/go.mod h1:Q4YqOodoX+qYvfM0gSf78cCk0o7dkg1GLNYUFgB6qhU= git.vh7.uk/jakew/echo-go v0.0.0-20220805101505-158189ae7d58 h1:gMa91PLcG4jRunbntD/YSt/7U3eSyAHUNxIdWu4ELjU= git.vh7.uk/jakew/echo-go v0.0.0-20220805101505-158189ae7d58/go.mod h1:Q4YqOodoX+qYvfM0gSf78cCk0o7dkg1GLNYUFgB6qhU= +git.vh7.uk/jakew/echo-go v0.0.0-20220809112338-5bd7802455cb h1:wE2bgtd4F/dJKVmMkxqHjQs16vsrqo4TuNbLIM4QX28= +git.vh7.uk/jakew/echo-go v0.0.0-20220809112338-5bd7802455cb/go.mod h1:Q4YqOodoX+qYvfM0gSf78cCk0o7dkg1GLNYUFgB6qhU= github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= diff --git a/bridge/main.go b/bridge/main.go index ad2b1af..3cae160 100644 --- a/bridge/main.go +++ b/bridge/main.go @@ -15,7 +15,7 @@ import ( ) type Config struct { - AllowedServers []string `yaml:"allowedServers"` + AllowedServers []string `yaml:"allowedServers" json:"allowedServers"` } type websocketHandler struct { @@ -110,6 +110,25 @@ func (h *websocketHandler) socketHandler(w http.ResponseWriter, r *http.Request) } } +func (h *websocketHandler) configHandler(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.Header().Set("Access-Control-Allow-Origin", "*") + + bytes, err := json.Marshal(h.config) + if err != nil { + log.Error().Err(err).Msg("failed to marshal config") + w.WriteHeader(500) + return + } + + _, err = w.Write(bytes) + if err != nil { + log.Error().Err(err).Msg("failed to write bytes to response") + w.WriteHeader(500) + return + } +} + func main() { log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}) @@ -130,6 +149,7 @@ func main() { } http.HandleFunc("/", handler.socketHandler) + http.HandleFunc("/config", handler.configHandler) log.Info().Msg("starting server") err = http.ListenAndServe(":4000", nil) diff --git a/frontend/index.html b/frontend/index.html index e0d1c84..e1e182c 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -2,9 +2,8 @@ - - Vite + React + TS + Echo Web
diff --git a/frontend/public/vite.svg b/frontend/public/vite.svg deleted file mode 100644 index e7b8dfb..0000000 --- a/frontend/public/vite.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/frontend/src/components/ChannelList.tsx b/frontend/src/components/ChannelList.tsx index afd7319..4ce345a 100644 --- a/frontend/src/components/ChannelList.tsx +++ b/frontend/src/components/ChannelList.tsx @@ -1,9 +1,11 @@ import { Box, Button, Navbar, NavLink, Stack } from "@mantine/core"; +import { openModal } from "@mantine/modals"; import { Root } from "react-dom/client"; import { Hash, Settings } from "react-feather"; import { useDispatch, useSelector } from "react-redux"; import { chatActions } from "../slices/chat"; import { RootState } from "../store"; +import ConnectModal from "./ConnectModal"; function ChannelList(props: { hidden: boolean }) { const dispatch = useDispatch(); @@ -44,7 +46,7 @@ function ChannelList(props: { hidden: boolean }) { > {connectText} - diff --git a/frontend/src/components/ConnectModal.tsx b/frontend/src/components/ConnectModal.tsx new file mode 100644 index 0000000..00548d1 --- /dev/null +++ b/frontend/src/components/ConnectModal.tsx @@ -0,0 +1,63 @@ +import { Button, Select, SelectItem, Text, TextInput } from "@mantine/core"; +import { showNotification } from "@mantine/notifications"; +import { ChangeEvent, useCallback, useEffect, useRef, useState } from "react"; +import { Check, CheckCircle, X, XCircle } from "react-feather"; +import { useDispatch, useSelector } from "react-redux"; +import { chatActions } from "../slices/chat"; +import { RootState } from "../store"; + +function ConnectModal() { + const dispatch = useDispatch(); + + const stateBridgeAddress = useSelector((state: RootState) => state.chat.bridgeAddress); + const stateServerAddress = useSelector((state: RootState) => state.chat.serverAddress); + const stateUsername = useSelector((state: RootState) => state.chat.username); + const statePassword = useSelector((state: RootState) => state.chat.password); + + const [bridgeAddress, setBridgeAddress] = useState(stateBridgeAddress); + const [serverAddress, setServerAddress] = useState(stateServerAddress || ""); + const [credentials, setCredentials] = useState<{ + username: string, + password: string + }>({ + username: stateUsername, + password: statePassword || "" + }); + + const saveConnectionDetails = () => { + if (bridgeAddress.trim() === "" || serverAddress.trim() === "" || credentials.username.trim() === "") { + showNotification({ + title: "Invalid Connection Parameters", + message: "One of the required connection parameters is empty or not valid.", + color: "red", + icon: + }); + return; + } + + dispatch(chatActions.setConnectionParameters({ + bridgeAddress, + serverAddress, + ...credentials + })); + showNotification({ + title: "Connecton Parameters Saved", + message: `Connecting to ${serverAddress} via ${bridgeAddress} as ${credentials.username}.`, + color: "green", + icon: + }) + } + + return ( + <> + Connection Settings + setBridgeAddress(event.target.value)} /> + setServerAddress(event.target.value)} /> + setCredentials({ ...credentials, username: event.target.value })} /> + setCredentials({ ...credentials, password: event.target.value })} /> + + + ); +} + +export default ConnectModal; diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 7b7ebcd..6770a8f 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -5,13 +5,19 @@ import App from './App' import './index.css'; import { Provider } from 'react-redux'; import { store } from './store'; +import { ModalsProvider } from '@mantine/modals'; +import { NotificationsProvider } from '@mantine/notifications'; ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( - - - - - + + + + + + + + + ) diff --git a/frontend/src/middleware/chat.tsx b/frontend/src/middleware/chat.tsx index 539a920..80c55e8 100644 --- a/frontend/src/middleware/chat.tsx +++ b/frontend/src/middleware/chat.tsx @@ -1,5 +1,6 @@ import { Middleware } from "@reduxjs/toolkit"; import chat, { chatActions } from "../slices/chat"; +import { RootState } from "../store"; enum MessageType { ReqServerInfo = "serverInfoRequest", @@ -48,7 +49,17 @@ const chatMiddleware: Middleware = (store) => { return (next) => (action) => { if (chatActions.connect.match(action) && socket === null) { - socket = new WebSocket("ws://127.0.0.1:4000?server=127.0.0.1:16000&username=jake&password=password"); + const nextState: RootState = store.getState(); + + let connectionString = `${nextState.chat.bridgeAddress}?server=${nextState.chat.serverAddress}&username=${nextState.chat.username}`; + connectionString = connectionString.replace("http://", "ws://").replace("https://", "wss://"); + if (nextState.chat.password && nextState.chat.password !== "") { + connectionString += `&password=${nextState.chat.password}`; + } + + console.log(`Connecting to ${connectionString}...`); + + socket = new WebSocket(connectionString); socket.onopen = () => { store.dispatch(chatActions.connected()); diff --git a/frontend/src/slices/chat.tsx b/frontend/src/slices/chat.tsx index e122324..1f8a497 100644 --- a/frontend/src/slices/chat.tsx +++ b/frontend/src/slices/chat.tsx @@ -2,6 +2,11 @@ import { createSlice, PayloadAction } from "@reduxjs/toolkit"; import { ChatMessage } from "../middleware/chat"; export interface ChatState { + bridgeAddress: string; + serverAddress: string; + username: string; + password?: string; + messages: ChatMessage[]; users: string[]; channels: string[]; @@ -12,6 +17,10 @@ export interface ChatState { } const initialState: ChatState = { + bridgeAddress: "http://127.0.0.1:4000", + serverAddress: "127.0.0.1:16000", + username: "echoweb", + messages: [], users: [], channels: [], @@ -87,7 +96,18 @@ const chatSlice = createSlice({ }, sendMessage: (state, action: PayloadAction<{ content: string - }>) => {} + }>) => {}, + setConnectionParameters: (state, action: PayloadAction<{ + bridgeAddress: string, + serverAddress: string, + username: string, + password?: string + }>) => { + state.bridgeAddress = action.payload.bridgeAddress; + state.serverAddress = action.payload.serverAddress; + state.username = action.payload.username; + state.password = action.payload.password; + } } });