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:
Sybren A. Stüvel 2022-02-11 14:47:26 +01:00
parent 2964f37dce
commit 96023932da
16 changed files with 9236 additions and 2 deletions

1
.gitignore vendored
View File

@ -4,3 +4,4 @@
/flamenco-worker.yaml
/flamenco-worker-credentials.yaml
node_modules/

View File

@ -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}/...

View File

@ -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
View 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
View File

@ -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
View File

@ -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
View 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
View 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>

View 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>

View 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>

View 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>

View 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
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

17
web/static/index.html Normal file
View 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

File diff suppressed because it is too large Load Diff