first commit
This commit is contained in:
parent
3ecb892237
commit
0eb48f41d3
54
README.md
Normal file → Executable file
54
README.md
Normal file → Executable file
@ -1,2 +1,54 @@
|
|||||||
# govite
|
# Real-time Random Number Visualization
|
||||||
|
|
||||||
|
This application demonstrates real-time data visualization using WebSocket communication between a Go backend and Vue.js frontend.
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
- `main.go` - Go backend server that generates random numbers and sends them via WebSocket
|
||||||
|
- `frontend/` - Vue.js frontend with TypeScript that visualizes the data in real-time
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- Go 1.21 or later
|
||||||
|
- Node.js 16 or later
|
||||||
|
- npm or yarn
|
||||||
|
|
||||||
|
## Backend Setup
|
||||||
|
|
||||||
|
1. Install Go dependencies:
|
||||||
|
```bash
|
||||||
|
go mod tidy
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Run the Go server:
|
||||||
|
```bash
|
||||||
|
go run main.go
|
||||||
|
```
|
||||||
|
|
||||||
|
The server will start on port 8080.
|
||||||
|
|
||||||
|
## Frontend Setup
|
||||||
|
|
||||||
|
1. Navigate to the frontend directory:
|
||||||
|
```bash
|
||||||
|
cd frontend
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Install dependencies:
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Start the development server:
|
||||||
|
```bash
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
The frontend will be available at http://localhost:3000
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Backend generates random numbers between 1 and 100 every second
|
||||||
|
- Frontend displays the numbers in a real-time line chart
|
||||||
|
- WebSocket connection automatically reconnects if disconnected
|
||||||
|
- Chart shows the last 20 data points
|
||||||
13
frontend/index.html
Executable file
13
frontend/index.html
Executable file
@ -0,0 +1,13 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<link rel="icon" href="/favicon.ico">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Real-time Random Numbers</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module" src="/src/main.ts"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
1016
frontend/package-lock.json
generated
Executable file
1016
frontend/package-lock.json
generated
Executable file
File diff suppressed because it is too large
Load Diff
21
frontend/package.json
Executable file
21
frontend/package.json
Executable file
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"name": "websocket-client",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vue-tsc && vite build",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"vue": "^3.3.4",
|
||||||
|
"chart.js": "^4.4.0",
|
||||||
|
"vue-chartjs": "^5.2.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@vitejs/plugin-vue": "^4.2.3",
|
||||||
|
"typescript": "^4.9.5",
|
||||||
|
"vite": "^4.4.5",
|
||||||
|
"vue-tsc": "^1.6.5"
|
||||||
|
}
|
||||||
|
}
|
||||||
137
frontend/src/App.vue
Executable file
137
frontend/src/App.vue
Executable file
@ -0,0 +1,137 @@
|
|||||||
|
<template>
|
||||||
|
<div class="app">
|
||||||
|
<h1>Real-time random</h1>
|
||||||
|
<div class="chart-container">
|
||||||
|
<Line
|
||||||
|
:data="chartData"
|
||||||
|
:options="chartOptions"
|
||||||
|
:height="400"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent, ref, onMounted, onUnmounted } from 'vue'
|
||||||
|
import { Line } from 'vue-chartjs'
|
||||||
|
import { Chart as ChartJS, CategoryScale, LinearScale, PointElement, LineElement, Title, Tooltip, Legend } from 'chart.js'
|
||||||
|
|
||||||
|
ChartJS.register(CategoryScale, LinearScale, PointElement, LineElement, Title, Tooltip, Legend)
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'App',
|
||||||
|
components: { Line },
|
||||||
|
setup() {
|
||||||
|
const chartData = ref({
|
||||||
|
labels: [] as string[],
|
||||||
|
datasets: [{
|
||||||
|
label: 'Random Numbers',
|
||||||
|
data: [] as number[],
|
||||||
|
borderColor: 'rgb(75, 192, 192)',
|
||||||
|
backgroundColor: 'rgba(75, 192, 192, 0.1)',
|
||||||
|
tension: 0.1,
|
||||||
|
fill: true
|
||||||
|
}]
|
||||||
|
})
|
||||||
|
|
||||||
|
const chartOptions = {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
animation: {
|
||||||
|
duration: 0
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
y: {
|
||||||
|
beginAtZero: true,
|
||||||
|
max: 100,
|
||||||
|
grid: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.1)'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
x: {
|
||||||
|
grid: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.1)'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let ws: WebSocket | null = null
|
||||||
|
let timeCounter = 0
|
||||||
|
let wurl = window.location.hostname
|
||||||
|
console.log(wurl)
|
||||||
|
|
||||||
|
const connectWebSocket = () => {
|
||||||
|
ws = new WebSocket(`ws://${wurl}:8080/ws`)
|
||||||
|
//ws = new WebSocket(`ws://127.0.0.1:8080/ws`)
|
||||||
|
ws.onmessage = (event) => {
|
||||||
|
const response = JSON.parse(event.data)
|
||||||
|
const value = response.value
|
||||||
|
|
||||||
|
// Create new arrays instead of modifying existing ones
|
||||||
|
const newLabels = [...chartData.value.labels, timeCounter.toString()]
|
||||||
|
const newData = [...chartData.value.datasets[0].data, value]
|
||||||
|
|
||||||
|
// Keep only last 20 points
|
||||||
|
if (newLabels.length > 20) {
|
||||||
|
newLabels.shift()
|
||||||
|
newData.shift()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update chart data with new arrays
|
||||||
|
chartData.value = {
|
||||||
|
labels: newLabels,
|
||||||
|
datasets: [{
|
||||||
|
...chartData.value.datasets[0],
|
||||||
|
data: newData
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
|
||||||
|
timeCounter++
|
||||||
|
}
|
||||||
|
|
||||||
|
ws.onclose = () => {
|
||||||
|
console.log('WebSocket connection closed')
|
||||||
|
setTimeout(connectWebSocket, 1000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
connectWebSocket()
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
if (ws) {
|
||||||
|
ws.close()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
chartData,
|
||||||
|
chartOptions
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.app {
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-container {
|
||||||
|
height: 400px;
|
||||||
|
margin-top: 20px;
|
||||||
|
background: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
text-align: center;
|
||||||
|
color: #2c3e50;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
5
frontend/src/env.d.ts
vendored
Executable file
5
frontend/src/env.d.ts
vendored
Executable file
@ -0,0 +1,5 @@
|
|||||||
|
declare module '*.vue' {
|
||||||
|
import type { DefineComponent } from 'vue'
|
||||||
|
const component: DefineComponent<{}, {}, any>
|
||||||
|
export default component
|
||||||
|
}
|
||||||
4
frontend/src/main.ts
Executable file
4
frontend/src/main.ts
Executable file
@ -0,0 +1,4 @@
|
|||||||
|
import { createApp } from 'vue'
|
||||||
|
import App from './App.vue'
|
||||||
|
|
||||||
|
createApp(App).mount('#app')
|
||||||
23
frontend/tsconfig.json
Executable file
23
frontend/tsconfig.json
Executable file
@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ESNext",
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "Node",
|
||||||
|
"strict": true,
|
||||||
|
"jsx": "preserve",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"lib": ["ESNext", "DOM"],
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["src/*"]
|
||||||
|
},
|
||||||
|
"types": ["vite/client", "vue"]
|
||||||
|
},
|
||||||
|
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
|
||||||
|
"references": [{ "path": "./tsconfig.node.json" }]
|
||||||
|
}
|
||||||
9
frontend/tsconfig.node.json
Executable file
9
frontend/tsconfig.node.json
Executable file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "Node",
|
||||||
|
"allowSyntheticDefaultImports": true
|
||||||
|
},
|
||||||
|
"include": ["vite.config.ts"]
|
||||||
|
}
|
||||||
9
frontend/vite.config.ts
Executable file
9
frontend/vite.config.ts
Executable file
@ -0,0 +1,9 @@
|
|||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [vue()],
|
||||||
|
server: {
|
||||||
|
port: 3000
|
||||||
|
}
|
||||||
|
})
|
||||||
7
go.mod
Executable file
7
go.mod
Executable file
@ -0,0 +1,7 @@
|
|||||||
|
module websocket-server
|
||||||
|
|
||||||
|
go 1.21
|
||||||
|
|
||||||
|
require github.com/gorilla/websocket v1.5.1
|
||||||
|
|
||||||
|
require golang.org/x/net v0.17.0 // indirect
|
||||||
7
go.mod_
Executable file
7
go.mod_
Executable file
@ -0,0 +1,7 @@
|
|||||||
|
module websocket-server
|
||||||
|
|
||||||
|
go 1.21
|
||||||
|
|
||||||
|
require github.com/gorilla/websocket v1.5.1
|
||||||
|
|
||||||
|
require golang.org/x/net v0.17.0 // indirect
|
||||||
4
go.sum
Executable file
4
go.sum
Executable file
@ -0,0 +1,4 @@
|
|||||||
|
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
|
||||||
|
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
|
||||||
|
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
|
||||||
|
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||||
60
main.go
Executable file
60
main.go
Executable file
@ -0,0 +1,60 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"math/rand"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
|
)
|
||||||
|
|
||||||
|
var upgrader = websocket.Upgrader{
|
||||||
|
CheckOrigin: func(r *http.Request) bool {
|
||||||
|
return true // Allow all connections
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleWebSocket(w http.ResponseWriter, r *http.Request) {
|
||||||
|
conn, err := upgrader.Upgrade(w, r, nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Upgrade error:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
ticker := time.NewTicker(1 * time.Second)
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ticker.C:
|
||||||
|
// Generate random number between 1 and 100
|
||||||
|
randomNumber := rand.Intn(100) + 1
|
||||||
|
|
||||||
|
// Send the number to the client
|
||||||
|
err := conn.WriteJSON(map[string]int{
|
||||||
|
"value": randomNumber,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Write error:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Initialize random seed
|
||||||
|
rand.Seed(time.Now().UnixNano())
|
||||||
|
|
||||||
|
// Serve static files
|
||||||
|
fs := http.FileServer(http.Dir("./frontend/dist"))
|
||||||
|
http.Handle("/", fs)
|
||||||
|
|
||||||
|
// WebSocket endpoint
|
||||||
|
http.HandleFunc("/ws", handleWebSocket)
|
||||||
|
|
||||||
|
log.Println("Server starting on :8080...")
|
||||||
|
log.Fatal(http.ListenAndServe(":8080", nil))
|
||||||
|
}
|
||||||
108
nginx/default
Normal file
108
nginx/default
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
##
|
||||||
|
# You should look at the following URL's in order to grasp a solid understanding
|
||||||
|
# of Nginx configuration files in order to fully unleash the power of Nginx.
|
||||||
|
# https://www.nginx.com/resources/wiki/start/
|
||||||
|
# https://www.nginx.com/resources/wiki/start/topics/tutorials/config_pitfalls/
|
||||||
|
# https://wiki.debian.org/Nginx/DirectoryStructure
|
||||||
|
#
|
||||||
|
# In most cases, administrators will remove this file from sites-enabled/ and
|
||||||
|
# leave it as reference inside of sites-available where it will continue to be
|
||||||
|
# updated by the nginx packaging team.
|
||||||
|
#
|
||||||
|
# This file will automatically load configuration files provided by other
|
||||||
|
# applications, such as Drupal or Wordpress. These applications will be made
|
||||||
|
# available underneath a path with that package name, such as /drupal8.
|
||||||
|
#
|
||||||
|
# Please see /usr/share/doc/nginx-doc/examples/ for more detailed examples.
|
||||||
|
##
|
||||||
|
|
||||||
|
# Default server configuration
|
||||||
|
#
|
||||||
|
server {
|
||||||
|
listen 80 default_server;
|
||||||
|
listen [::]:80 default_server;
|
||||||
|
|
||||||
|
# SSL configuration
|
||||||
|
#
|
||||||
|
# listen 443 ssl default_server;
|
||||||
|
# listen [::]:443 ssl default_server;
|
||||||
|
#
|
||||||
|
# Note: You should disable gzip for SSL traffic.
|
||||||
|
# See: https://bugs.debian.org/773332
|
||||||
|
#
|
||||||
|
# Read up on ssl_ciphers to ensure a secure configuration.
|
||||||
|
# See: https://bugs.debian.org/765782
|
||||||
|
#
|
||||||
|
# Self signed certs generated by the ssl-cert package
|
||||||
|
# Don't use them in a production server!
|
||||||
|
#
|
||||||
|
# include snippets/snakeoil.conf;
|
||||||
|
|
||||||
|
root /var/www/html;
|
||||||
|
|
||||||
|
# Add index.php to the list if you are using PHP
|
||||||
|
index index.html index.htm index.nginx-debian.html;
|
||||||
|
|
||||||
|
server_name _;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
# First attempt to serve request as file, then
|
||||||
|
# as directory, then fall back to displaying a 404.
|
||||||
|
try_files $uri $uri/ =404;
|
||||||
|
}
|
||||||
|
location /go/ {
|
||||||
|
#rewrite ^ $request_uri;
|
||||||
|
#rewrite ^/git(/.*) $1 break;
|
||||||
|
#proxy_pass http://127.0.0.1:8080$uri;
|
||||||
|
proxy_pass http://127.0.0.1:8080/;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
rewrite ^/git/(.*) /$1 break;
|
||||||
|
}
|
||||||
|
location /assets/ {
|
||||||
|
proxy_pass http://127.0.0.1:8080/assets/;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
}
|
||||||
|
# pass PHP scripts to FastCGI server
|
||||||
|
#
|
||||||
|
#location ~ \.php$ {
|
||||||
|
# include snippets/fastcgi-php.conf;
|
||||||
|
#
|
||||||
|
# # With php-fpm (or other unix sockets):
|
||||||
|
# fastcgi_pass unix:/run/php/php7.4-fpm.sock;
|
||||||
|
# # With php-cgi (or other tcp sockets):
|
||||||
|
# fastcgi_pass 127.0.0.1:9000;
|
||||||
|
#}
|
||||||
|
|
||||||
|
# deny access to .htaccess files, if Apache's document root
|
||||||
|
# concurs with nginx's one
|
||||||
|
#
|
||||||
|
#location ~ /\.ht {
|
||||||
|
# deny all;
|
||||||
|
#}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Virtual Host configuration for example.com
|
||||||
|
#
|
||||||
|
# You can move that to a different file under sites-available/ and symlink that
|
||||||
|
# to sites-enabled/ to enable it.
|
||||||
|
#
|
||||||
|
#server {
|
||||||
|
# listen 80;
|
||||||
|
# listen [::]:80;
|
||||||
|
#
|
||||||
|
# server_name example.com;
|
||||||
|
#
|
||||||
|
# root /var/www/example.com;
|
||||||
|
# index index.html;
|
||||||
|
#
|
||||||
|
# location / {
|
||||||
|
# try_files $uri $uri/ =404;
|
||||||
|
# }
|
||||||
|
#}
|
||||||
Loading…
x
Reference in New Issue
Block a user