Add connection window
This commit is contained in:
parent
deb55c2c6d
commit
04396259a0
11 changed files with 138 additions and 13 deletions
|
@ -1,2 +1,3 @@
|
|||
allowedServers:
|
||||
- "195.201.123.169:16000"
|
||||
- "127.0.0.1:16000"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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=
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -2,9 +2,8 @@
|
|||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Vite + React + TS</title>
|
||||
<title>Echo Web</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
Before Width: | Height: | Size: 1.5 KiB |
|
@ -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}
|
||||
</Button>
|
||||
<Button variant="outline">
|
||||
<Button variant="outline" onClick={() => openModal({ title: "Settings", children: <ConnectModal /> })} >
|
||||
<Settings size={20} />
|
||||
</Button>
|
||||
</Button.Group>
|
||||
|
|
63
frontend/src/components/ConnectModal.tsx
Normal file
63
frontend/src/components/ConnectModal.tsx
Normal file
|
@ -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<string>(stateBridgeAddress);
|
||||
const [serverAddress, setServerAddress] = useState<string>(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: <X />
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(chatActions.setConnectionParameters({
|
||||
bridgeAddress,
|
||||
serverAddress,
|
||||
...credentials
|
||||
}));
|
||||
showNotification({
|
||||
title: "Connecton Parameters Saved",
|
||||
message: `Connecting to ${serverAddress} via ${bridgeAddress} as ${credentials.username}.`,
|
||||
color: "green",
|
||||
icon: <Check />
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Text size="md" mb="sm">Connection Settings</Text>
|
||||
<TextInput label="Bridge URL" required value={bridgeAddress} onChange={(event) => setBridgeAddress(event.target.value)} />
|
||||
<TextInput label="Server" required value={serverAddress} onChange={(event) => setServerAddress(event.target.value)} />
|
||||
<TextInput label="Username" required value={credentials.username} onChange={(event) => setCredentials({ ...credentials, username: event.target.value })} />
|
||||
<TextInput label="Password" value={credentials.password} onChange={(event) => setCredentials({ ...credentials, password: event.target.value })} />
|
||||
<Button mt="sm" sx={{ float: "right" }} onClick={saveConnectionDetails}>Save</Button>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default ConnectModal;
|
|
@ -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(
|
||||
<React.StrictMode>
|
||||
<MantineProvider withGlobalStyles withNormalizeCSS>
|
||||
<Provider store={store}>
|
||||
<App />
|
||||
</Provider>
|
||||
</MantineProvider>
|
||||
<Provider store={store}>
|
||||
<MantineProvider withGlobalStyles withNormalizeCSS>
|
||||
<NotificationsProvider>
|
||||
<ModalsProvider>
|
||||
<App />
|
||||
</ModalsProvider>
|
||||
</NotificationsProvider>
|
||||
</MantineProvider>
|
||||
</Provider>
|
||||
</React.StrictMode>
|
||||
)
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
|
Loading…
Reference in a new issue