SocketIO based chat client as PoC for backend/frontend communication
The chat client itself is just a throwaway project. The SocketIO system will be used to send realtime updates about jobs, tasks, and workers to the web frontend.
This commit is contained in:
parent
2964f37dce
commit
96023932da
1
.gitignore
vendored
1
.gitignore
vendored
@ -4,3 +4,4 @@
|
||||
|
||||
/flamenco-worker.yaml
|
||||
/flamenco-worker-credentials.yaml
|
||||
node_modules/
|
||||
|
1
Makefile
1
Makefile
@ -31,6 +31,7 @@ with-deps:
|
||||
application: ${RESOURCES} generate
|
||||
go build -v ${BUILD_FLAGS} ${PKG}/cmd/flamenco-manager-poc
|
||||
go build -v ${BUILD_FLAGS} ${PKG}/cmd/flamenco-worker-poc
|
||||
go build -v ${BUILD_FLAGS} ${PKG}/cmd/socketio-poc
|
||||
|
||||
generate:
|
||||
go generate ${PKG}/...
|
||||
|
18
README.md
18
README.md
@ -4,13 +4,27 @@ This repository contains a proof of concept of a next-generation Flamenco implem
|
||||
|
||||
## Building
|
||||
|
||||
1. Install [Go 1.17 or newer](https://go.dev/).
|
||||
1. Install [Go 1.17 or newer](https://go.dev/) and Node 16 (see below)
|
||||
2. Set the environment variable `GOPATH` to where you want Go to put its packages. Defaults to `$HOME/go` if not set. Run `go env GOPATH` if you're not sure.
|
||||
3. Ensure `$GOPATH/bin` is included in your `$PATH` environment variable.
|
||||
4. Run `make with-deps` to install build-time dependencies and build the application. Subsequent builds can just run `make` without arguments.
|
||||
4. Magically build the web frontend (still under development, no concrete steps documentable quite yet)
|
||||
5. Run `make with-deps` to install build-time dependencies and build the application. Subsequent builds can just run `make` without arguments.
|
||||
|
||||
You should now have two executables: `flamenco-manager-poc` and `flamenco-worker-poc`.
|
||||
|
||||
|
||||
## Node / Web UI
|
||||
|
||||
The web UI is built with Vue, Bootstrap, and Socket.IO for communication with the backend.
|
||||
|
||||
NodeJS is used to collect all of those and build the frontend files. It's recommended to install Node v16 via Snap:
|
||||
|
||||
```
|
||||
sudo snap install node --classic --channel=16
|
||||
```
|
||||
|
||||
This also gives you the Yarn package manager, which can be used to install web dependencies and build the frontend files.
|
||||
|
||||
## Swagger UI
|
||||
|
||||
Flamenco Manager has a SwaggerUI interface at http://localhost:8080/api/swagger-ui/
|
||||
|
115
cmd/socketio-poc/main.go
Normal file
115
cmd/socketio-poc/main.go
Normal file
@ -0,0 +1,115 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/labstack/echo/v4/middleware"
|
||||
"github.com/mattn/go-colorable"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/ziflex/lecho/v3"
|
||||
"gitlab.com/blender/flamenco-ng-poc/internal/appinfo"
|
||||
|
||||
gosocketio "github.com/graarh/golang-socketio"
|
||||
"github.com/graarh/golang-socketio/transport"
|
||||
)
|
||||
|
||||
type Message struct {
|
||||
Name string `json:"name"`
|
||||
Text string `json:"text"`
|
||||
}
|
||||
|
||||
func socketIOServer() *gosocketio.Server {
|
||||
sio := gosocketio.NewServer(transport.GetDefaultWebsocketTransport())
|
||||
log.Info().Msg("initialising SocketIO")
|
||||
|
||||
// socket connection
|
||||
sio.On(gosocketio.OnConnection, func(c *gosocketio.Channel) {
|
||||
log.Info().Str("clientID", c.Id()).Msg("connected")
|
||||
c.Join("Room")
|
||||
})
|
||||
|
||||
// socket disconnection
|
||||
sio.On(gosocketio.OnDisconnection, func(c *gosocketio.Channel) {
|
||||
log.Info().Str("clientID", c.Id()).Msg("disconnected")
|
||||
c.Leave("Room")
|
||||
})
|
||||
|
||||
sio.On(gosocketio.OnError, func(c *gosocketio.Channel) {
|
||||
log.Warn().Interface("c", *c).Msg("socketio error")
|
||||
})
|
||||
|
||||
// chat socket
|
||||
sio.On("/chat", func(c *gosocketio.Channel, message Message) string {
|
||||
log.Info().Str("clientID", c.Id()).
|
||||
Str("text", message.Text).
|
||||
Str("name", message.Name).
|
||||
Msg("message received")
|
||||
c.BroadcastTo("Room", "/message", message.Text)
|
||||
return "message sent successfully."
|
||||
})
|
||||
|
||||
return sio
|
||||
}
|
||||
|
||||
func addRoutes(router *echo.Echo, server *gosocketio.Server) {
|
||||
cors := middleware.CORSWithConfig(middleware.CORSConfig{
|
||||
AllowOrigins: []string{"http://localhost:8080/", "http://localhost:8081/"},
|
||||
|
||||
// List taken from https://www.bacancytechnology.com/blog/real-time-chat-application-using-socketio-golang-vuejs/
|
||||
AllowHeaders: []string{
|
||||
echo.HeaderAccept,
|
||||
echo.HeaderAcceptEncoding,
|
||||
echo.HeaderAccessControlAllowOrigin,
|
||||
echo.HeaderAccessControlRequestHeaders,
|
||||
echo.HeaderAccessControlRequestMethod,
|
||||
echo.HeaderAuthorization,
|
||||
echo.HeaderContentLength,
|
||||
echo.HeaderContentType,
|
||||
echo.HeaderOrigin,
|
||||
echo.HeaderXCSRFToken,
|
||||
echo.HeaderXRequestedWith,
|
||||
"Cache-Control",
|
||||
"Connection",
|
||||
"Host",
|
||||
"Referer",
|
||||
"User-Agent",
|
||||
"X-header",
|
||||
},
|
||||
AllowMethods: []string{"POST", "GET", "OPTIONS", "PUT", "DELETE"},
|
||||
})
|
||||
|
||||
router.Any("/socket.io/", echo.WrapHandler(server), cors)
|
||||
|
||||
}
|
||||
|
||||
func main() {
|
||||
output := zerolog.ConsoleWriter{Out: colorable.NewColorableStdout(), TimeFormat: time.RFC3339}
|
||||
log.Logger = log.Output(output)
|
||||
log.Info().Str("version", appinfo.ApplicationVersion).Msgf("starting Socket.IO PoC %v", appinfo.ApplicationName)
|
||||
|
||||
socketio := socketIOServer()
|
||||
|
||||
e := echo.New()
|
||||
e.HideBanner = true
|
||||
e.HidePort = true
|
||||
|
||||
// Hook Zerolog onto Echo:
|
||||
e.Use(lecho.Middleware(lecho.Config{
|
||||
Logger: lecho.From(log.Logger),
|
||||
}))
|
||||
|
||||
// Ensure panics when serving a web request won't bring down the server.
|
||||
e.Use(middleware.Recover())
|
||||
|
||||
addRoutes(e, socketio)
|
||||
|
||||
listen := ":8081"
|
||||
log.Info().Str("listen", listen).Msg("server starting")
|
||||
log.Info().Msg("Run `yarn serve` from the 'web' dir to run the frontend server")
|
||||
|
||||
// Start the web server.
|
||||
finalErr := e.Start(listen)
|
||||
log.Warn().Err(finalErr).Msg("shutting down")
|
||||
}
|
4
go.mod
4
go.mod
@ -10,6 +10,10 @@ require (
|
||||
github.com/getkin/kin-openapi v0.88.0
|
||||
github.com/gofrs/uuid v4.0.0+incompatible // indirect
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/gorilla/handlers v1.5.1
|
||||
github.com/gorilla/mux v1.8.0
|
||||
github.com/gorilla/websocket v1.4.2 // indirect
|
||||
github.com/graarh/golang-socketio v0.0.0-20170510162725-2c44953b9b5f
|
||||
github.com/jackc/pgproto3/v2 v2.0.7 // indirect
|
||||
github.com/labstack/echo/v4 v4.6.1
|
||||
github.com/lib/pq v1.10.0 // indirect
|
||||
|
8
go.sum
8
go.sum
@ -23,6 +23,8 @@ github.com/dop251/goja v0.0.0-20211217115348-3f9136fa235d h1:XT7Qdmcuwgsgz4GXejX
|
||||
github.com/dop251/goja v0.0.0-20211217115348-3f9136fa235d/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk=
|
||||
github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7 h1:tYwu/z8Y0NkkzGEh3z21mSWggMg4LwLRFucLS7TjARg=
|
||||
github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y=
|
||||
github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ=
|
||||
github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/getkin/kin-openapi v0.80.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg=
|
||||
github.com/getkin/kin-openapi v0.88.0 h1:BjJ2JERWJbYE1o1RGEj/5LmR5qw7ecfl3O3su4ImR+0=
|
||||
github.com/getkin/kin-openapi v0.88.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg=
|
||||
@ -61,8 +63,14 @@ github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4=
|
||||
github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q=
|
||||
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
|
||||
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/graarh/golang-socketio v0.0.0-20170510162725-2c44953b9b5f h1:utzdm9zUvVWGRtIpkdE4+36n+Gv60kNb7mFvgGxLElY=
|
||||
github.com/graarh/golang-socketio v0.0.0-20170510162725-2c44953b9b5f/go.mod h1:8gudiNCFh3ZfvInknmoXzPeV17FSH+X2J5k2cUPIwnA=
|
||||
github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0=
|
||||
github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
|
||||
github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
|
||||
|
53
web/package.json
Normal file
53
web/package.json
Normal file
@ -0,0 +1,53 @@
|
||||
{
|
||||
"name": "flamenco-manager-web-frontend",
|
||||
"version": "3.0.0",
|
||||
"private": true,
|
||||
"author": {
|
||||
"name": "Sybren Stüvel"
|
||||
},
|
||||
"dependencies": {
|
||||
"bootstrap-vue": "^2.21.2",
|
||||
"core-js": "^3.6.5",
|
||||
"socket.io-client": "2.4.0",
|
||||
"vue": "^2.6.11"
|
||||
},
|
||||
"licenses": [
|
||||
{
|
||||
"type": "GPL-3.0",
|
||||
"url": "https://opensource.org/licenses/GPL-3.0"
|
||||
}
|
||||
],
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build",
|
||||
"lint": "vue-cli-service lint"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/cli-plugin-babel": "~4.5.0",
|
||||
"@vue/cli-plugin-eslint": "~4.5.0",
|
||||
"@vue/cli-service": "~4.5.0",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"eslint": "^6.7.2",
|
||||
"eslint-plugin-vue": "^6.2.2",
|
||||
"vue-template-compiler": "^2.6.11"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"root": true,
|
||||
"env": {
|
||||
"node": true
|
||||
},
|
||||
"extends": [
|
||||
"plugin:vue/essential",
|
||||
"eslint:recommended"
|
||||
],
|
||||
"parserOptions": {
|
||||
"parser": "babel-eslint"
|
||||
},
|
||||
"rules": {}
|
||||
},
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
"last 2 versions",
|
||||
"not dead"
|
||||
]
|
||||
}
|
68
web/src/App.vue
Normal file
68
web/src/App.vue
Normal file
@ -0,0 +1,68 @@
|
||||
<template>
|
||||
<div id="app">
|
||||
<chat-navbar></chat-navbar>
|
||||
<br />
|
||||
<chat-chatbox
|
||||
@sendMessage="sendMessage"
|
||||
:chatHistory="messages"
|
||||
></chat-chatbox>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import io from "socket.io-client";
|
||||
import ChatNavbar from "./components/ChatNavbar.vue";
|
||||
import ChatChatbox from "./components/ChatChatbox.vue";
|
||||
|
||||
export default {
|
||||
name: "App",
|
||||
components: {
|
||||
ChatNavbar,
|
||||
ChatChatbox,
|
||||
},
|
||||
data: () => {
|
||||
return {
|
||||
socket: null,
|
||||
serverUrl: process.env.VUE_APP_SOCKET_URL || "ws://localhost:8081",
|
||||
messages: [],
|
||||
};
|
||||
},
|
||||
mounted: function () {
|
||||
this.connectToWebsocket();
|
||||
},
|
||||
methods: {
|
||||
connectToWebsocket() {
|
||||
console.log("connecting to WS", this.serverUrl);
|
||||
this.socket = io(this.serverUrl, {
|
||||
transports: ["websocket"],
|
||||
});
|
||||
this.socket.on("/message", (message) => {
|
||||
console.log("message received: ", message);
|
||||
this.messages.push(message);
|
||||
});
|
||||
},
|
||||
sendMessage(message) {
|
||||
const payload = { name: "Nikita", text: message };
|
||||
console.log("sending:", payload);
|
||||
this.socket.emit("/chat", payload);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
#app {
|
||||
font-family: Avenir, Helvetica, Arial, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
text-align: center;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
html,
|
||||
body,
|
||||
#app,
|
||||
.card {
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
61
web/src/components/ChatChatbox.vue
Normal file
61
web/src/components/ChatChatbox.vue
Normal file
@ -0,0 +1,61 @@
|
||||
<template>
|
||||
<b-container>
|
||||
<b-card>
|
||||
<div class="chatbox">
|
||||
<b-row
|
||||
class="no-gutters"
|
||||
align-h="start"
|
||||
v-for="(message, inx) in chatHistory"
|
||||
:key="inx"
|
||||
>
|
||||
<b-col class="no-gutters" cols="8">
|
||||
<div>
|
||||
<p class="received-chat">{{ message }}</p>
|
||||
</div>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</div>
|
||||
<ChatMessageBox @sendMessage="(msg) => $emit('sendMessage', msg)" />
|
||||
</b-card>
|
||||
</b-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ChatMessageBox from "./ChatMessagebox.vue";
|
||||
export default {
|
||||
emits: ["sendMessage"],
|
||||
props: ["chatHistory"],
|
||||
components: {
|
||||
ChatMessageBox,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.container {
|
||||
height: calc(100% - 100px);
|
||||
}
|
||||
.chatbox {
|
||||
padding: 10px;
|
||||
height: calc(100% - 35px);
|
||||
overflow-y: auto;
|
||||
/* background-image: url("https://i0.wp.com/www.tortugacreative.com/wp-content/uploads/social-media-background-dark-2.png?ssl=1"); */
|
||||
}
|
||||
.sender-name {
|
||||
margin: 0px;
|
||||
}
|
||||
.chat {
|
||||
background-color: lightgreen;
|
||||
padding: 10px;
|
||||
border-top-left-radius: 10px;
|
||||
border-bottom-left-radius: 10px;
|
||||
border-bottom-right-radius: 10px;
|
||||
}
|
||||
.received-chat {
|
||||
background-color: lightblue;
|
||||
padding: 10px;
|
||||
border-top-right-radius: 10px;
|
||||
border-bottom-left-radius: 10px;
|
||||
border-bottom-right-radius: 10px;
|
||||
}
|
||||
</style>
|
17
web/src/components/ChatHeader.vue
Normal file
17
web/src/components/ChatHeader.vue
Normal file
@ -0,0 +1,17 @@
|
||||
<template>
|
||||
<b-card title="Chat-App">
|
||||
<b-form>
|
||||
<label class="sr-only" for="inline-form-input-username">Username</label>
|
||||
<b-input-group prepend="@" class="mb-2 mr-sm-2 mb-sm-0">
|
||||
<b-form-input
|
||||
id="inline-form-input-username"
|
||||
placeholder="Username"
|
||||
v-model.trim="username"
|
||||
></b-form-input>
|
||||
<b-button @click="joinRoom">Join</b-button>
|
||||
</b-input-group>
|
||||
</b-form>
|
||||
</b-card>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
38
web/src/components/ChatMessagebox.vue
Normal file
38
web/src/components/ChatMessagebox.vue
Normal file
@ -0,0 +1,38 @@
|
||||
<template>
|
||||
<b-input-group class="mb-2 mr-sm-2 mb-sm-0">
|
||||
<b-form-input
|
||||
placeholder="type here.."
|
||||
v-model.trim="newMsg"
|
||||
@keyup.enter="sendMessage"
|
||||
></b-form-input>
|
||||
<b-button
|
||||
size="md"
|
||||
variant="primary"
|
||||
class="mb-2"
|
||||
@click="sendMessage"
|
||||
:disabled="!newMsg"
|
||||
>
|
||||
Send
|
||||
</b-button>
|
||||
</b-input-group>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
emits: ["sendMessage"],
|
||||
data() {
|
||||
return {
|
||||
newMsg: "",
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
sendMessage() {
|
||||
if (!this.newMsg) return;
|
||||
console.log("emitting message to send:", this.newMsg);
|
||||
|
||||
this.$emit("sendMessage", this.newMsg);
|
||||
this.newMsg = "";
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
5
web/src/components/ChatNavbar.vue
Normal file
5
web/src/components/ChatNavbar.vue
Normal file
@ -0,0 +1,5 @@
|
||||
<template>
|
||||
<b-navbar toggleable type="dark" variant="dark">
|
||||
<b-navbar-brand href="#">Chat Application</b-navbar-brand>
|
||||
</b-navbar>
|
||||
</template>
|
31
web/src/main.js
Normal file
31
web/src/main.js
Normal file
@ -0,0 +1,31 @@
|
||||
import Vue from 'vue'
|
||||
import App from './App.vue'
|
||||
import {
|
||||
FormInputPlugin,
|
||||
NavbarPlugin,
|
||||
LayoutPlugin,
|
||||
IconsPlugin,
|
||||
BCard,
|
||||
BInputGroup,
|
||||
BButton,
|
||||
} from "bootstrap-vue";
|
||||
|
||||
import "bootstrap/dist/css/bootstrap.css";
|
||||
import "bootstrap-vue/dist/bootstrap-vue.css";
|
||||
|
||||
Vue.config.productionTip = false
|
||||
|
||||
Vue.use(FormInputPlugin);
|
||||
Vue.use(NavbarPlugin);
|
||||
Vue.use(LayoutPlugin);
|
||||
Vue.component("b-card", BCard);
|
||||
Vue.component("b-input-group", BInputGroup);
|
||||
Vue.component("b-button", BButton);
|
||||
Vue.use(IconsPlugin);
|
||||
|
||||
var app = new Vue({
|
||||
render: h => h(App),
|
||||
|
||||
});
|
||||
|
||||
app.$mount("#app");
|
BIN
web/static/favicon.ico
Normal file
BIN
web/static/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
17
web/static/index.html
Normal file
17
web/static/index.html
Normal file
@ -0,0 +1,17 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||
<title><%= htmlWebpackPlugin.options.title %></title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
</html>
|
8801
web/yarn.lock
Normal file
8801
web/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user