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:
|
allowedServers:
|
||||||
|
- "195.201.123.169:16000"
|
||||||
- "127.0.0.1:16000"
|
- "127.0.0.1:16000"
|
||||||
|
|
|
@ -3,7 +3,7 @@ module git.vh7.uk/jakew/echo-web/bridge
|
||||||
go 1.18
|
go 1.18
|
||||||
|
|
||||||
require (
|
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/gorilla/websocket v1.5.0
|
||||||
github.com/rs/zerolog v1.27.0
|
github.com/rs/zerolog v1.27.0
|
||||||
github.com/samber/lo 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/exp v0.0.0-20220303212507-bbda1eaf7a17 // indirect
|
||||||
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 // 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-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 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-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/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.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
|
|
@ -15,7 +15,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
AllowedServers []string `yaml:"allowedServers"`
|
AllowedServers []string `yaml:"allowedServers" json:"allowedServers"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type websocketHandler struct {
|
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() {
|
func main() {
|
||||||
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
|
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
|
||||||
|
|
||||||
|
@ -130,6 +149,7 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
http.HandleFunc("/", handler.socketHandler)
|
http.HandleFunc("/", handler.socketHandler)
|
||||||
|
http.HandleFunc("/config", handler.configHandler)
|
||||||
|
|
||||||
log.Info().Msg("starting server")
|
log.Info().Msg("starting server")
|
||||||
err = http.ListenAndServe(":4000", nil)
|
err = http.ListenAndServe(":4000", nil)
|
||||||
|
|
|
@ -2,9 +2,8 @@
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<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" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Vite + React + TS</title>
|
<title>Echo Web</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<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 { Box, Button, Navbar, NavLink, Stack } from "@mantine/core";
|
||||||
|
import { openModal } from "@mantine/modals";
|
||||||
import { Root } from "react-dom/client";
|
import { Root } from "react-dom/client";
|
||||||
import { Hash, Settings } from "react-feather";
|
import { Hash, Settings } from "react-feather";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import { chatActions } from "../slices/chat";
|
import { chatActions } from "../slices/chat";
|
||||||
import { RootState } from "../store";
|
import { RootState } from "../store";
|
||||||
|
import ConnectModal from "./ConnectModal";
|
||||||
|
|
||||||
function ChannelList(props: { hidden: boolean }) {
|
function ChannelList(props: { hidden: boolean }) {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
@ -44,7 +46,7 @@ function ChannelList(props: { hidden: boolean }) {
|
||||||
>
|
>
|
||||||
{connectText}
|
{connectText}
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="outline">
|
<Button variant="outline" onClick={() => openModal({ title: "Settings", children: <ConnectModal /> })} >
|
||||||
<Settings size={20} />
|
<Settings size={20} />
|
||||||
</Button>
|
</Button>
|
||||||
</Button.Group>
|
</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 './index.css';
|
||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
import { store } from './store';
|
import { store } from './store';
|
||||||
|
import { ModalsProvider } from '@mantine/modals';
|
||||||
|
import { NotificationsProvider } from '@mantine/notifications';
|
||||||
|
|
||||||
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
|
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<MantineProvider withGlobalStyles withNormalizeCSS>
|
<Provider store={store}>
|
||||||
<Provider store={store}>
|
<MantineProvider withGlobalStyles withNormalizeCSS>
|
||||||
<App />
|
<NotificationsProvider>
|
||||||
</Provider>
|
<ModalsProvider>
|
||||||
</MantineProvider>
|
<App />
|
||||||
|
</ModalsProvider>
|
||||||
|
</NotificationsProvider>
|
||||||
|
</MantineProvider>
|
||||||
|
</Provider>
|
||||||
</React.StrictMode>
|
</React.StrictMode>
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { Middleware } from "@reduxjs/toolkit";
|
import { Middleware } from "@reduxjs/toolkit";
|
||||||
import chat, { chatActions } from "../slices/chat";
|
import chat, { chatActions } from "../slices/chat";
|
||||||
|
import { RootState } from "../store";
|
||||||
|
|
||||||
enum MessageType {
|
enum MessageType {
|
||||||
ReqServerInfo = "serverInfoRequest",
|
ReqServerInfo = "serverInfoRequest",
|
||||||
|
@ -48,7 +49,17 @@ const chatMiddleware: Middleware = (store) => {
|
||||||
|
|
||||||
return (next) => (action) => {
|
return (next) => (action) => {
|
||||||
if (chatActions.connect.match(action) && socket === null) {
|
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 = () => {
|
socket.onopen = () => {
|
||||||
store.dispatch(chatActions.connected());
|
store.dispatch(chatActions.connected());
|
||||||
|
|
|
@ -2,6 +2,11 @@ import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||||
import { ChatMessage } from "../middleware/chat";
|
import { ChatMessage } from "../middleware/chat";
|
||||||
|
|
||||||
export interface ChatState {
|
export interface ChatState {
|
||||||
|
bridgeAddress: string;
|
||||||
|
serverAddress: string;
|
||||||
|
username: string;
|
||||||
|
password?: string;
|
||||||
|
|
||||||
messages: ChatMessage[];
|
messages: ChatMessage[];
|
||||||
users: string[];
|
users: string[];
|
||||||
channels: string[];
|
channels: string[];
|
||||||
|
@ -12,6 +17,10 @@ export interface ChatState {
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialState: ChatState = {
|
const initialState: ChatState = {
|
||||||
|
bridgeAddress: "http://127.0.0.1:4000",
|
||||||
|
serverAddress: "127.0.0.1:16000",
|
||||||
|
username: "echoweb",
|
||||||
|
|
||||||
messages: [],
|
messages: [],
|
||||||
users: [],
|
users: [],
|
||||||
channels: [],
|
channels: [],
|
||||||
|
@ -87,7 +96,18 @@ const chatSlice = createSlice({
|
||||||
},
|
},
|
||||||
sendMessage: (state, action: PayloadAction<{
|
sendMessage: (state, action: PayloadAction<{
|
||||||
content: string
|
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