first commit
This commit is contained in:
parent
3ecb892237
commit
0eb48f41d3
56
README.md
Normal file → Executable file
56
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