premier commit
This commit is contained in:
24
buildup/Dockerfile
Normal file
24
buildup/Dockerfile
Normal file
@@ -0,0 +1,24 @@
|
||||
# Image golang pour la compilation
|
||||
FROM golang:1.25 AS builder
|
||||
|
||||
# Copier les fichiers dont ont a besoin
|
||||
COPY src /go/src
|
||||
COPY Makefile /go
|
||||
|
||||
# Compilation
|
||||
RUN make build
|
||||
|
||||
# Image finale
|
||||
FROM busybox
|
||||
|
||||
# Installer le logiciel compilé
|
||||
COPY src/www /www
|
||||
COPY src/.env /
|
||||
COPY src/servers.yaml /
|
||||
COPY --from=builder /go/bin/buildup /buildup
|
||||
|
||||
# Partages
|
||||
EXPOSE 80
|
||||
|
||||
# Démarrage
|
||||
ENTRYPOINT [ "/buildup" ]
|
||||
38
buildup/Makefile
Normal file
38
buildup/Makefile
Normal file
@@ -0,0 +1,38 @@
|
||||
# Variables
|
||||
COMPOSE ?= docker compose
|
||||
DOCKER_DIR ?= docker
|
||||
COMPOSE_FILE ?= docker-compose.yml
|
||||
|
||||
run:
|
||||
@cd src ; go run *.go
|
||||
|
||||
build:
|
||||
@cd src ; go mod tidy ; go build -o ../bin/buildup *.go
|
||||
|
||||
clean:
|
||||
@rm ./bin/buildup
|
||||
|
||||
image:
|
||||
@docker build -t buildup:latest .
|
||||
|
||||
start:
|
||||
@docker run -d -p 80:80 --name buildup buildup:latest
|
||||
|
||||
stop:
|
||||
@docker rm -f buildup
|
||||
|
||||
# Démarrage stack
|
||||
up:
|
||||
@$(COMPOSE) --project-directory $(DOCKER_DIR) -f $(DOCKER_DIR)/$(COMPOSE_FILE) up -d
|
||||
|
||||
# Arrêt de la stack
|
||||
down:
|
||||
@$(COMPOSE) --project-directory $(DOCKER_DIR) -f $(DOCKER_DIR)/$(COMPOSE_FILE) down
|
||||
|
||||
# Journaux de la stack
|
||||
logs:
|
||||
@$(COMPOSE) --project-directory $(DOCKER_DIR) -f $(DOCKER_DIR)/$(COMPOSE_FILE) logs -f
|
||||
|
||||
# Liste des processus de la stack
|
||||
ps:
|
||||
@$(COMPOSE) --project-directory $(DOCKER_DIR) -f $(DOCKER_DIR)/$(COMPOSE_FILE) ps
|
||||
11
buildup/docker/.env
Normal file
11
buildup/docker/.env
Normal file
@@ -0,0 +1,11 @@
|
||||
# Gestion InfluxDB
|
||||
DOCKER_INFLUXDB_INIT_MODE=setup
|
||||
DOCKER_INFLUXDB_INIT_USERNAME=admin
|
||||
DOCKER_INFLUXDB_INIT_PASSWORD=SuperSecret
|
||||
DOCKER_INFLUXDB_INIT_ORG=myorg
|
||||
DOCKER_INFLUXDB_INIT_BUCKET=metrics
|
||||
DOCKER_INFLUXDB_INIT_RETENTION=30d
|
||||
DOCKER_INFLUXDB_INIT_ADMIN_TOKEN=admin-token
|
||||
# Gestion Grafana
|
||||
GF_SECURITY_ADMIN_USER="admin"
|
||||
GF_SECURITY_ADMIN_PASSWORD="SuperSecret"
|
||||
54
buildup/docker/docker-compose.yml
Normal file
54
buildup/docker/docker-compose.yml
Normal file
@@ -0,0 +1,54 @@
|
||||
# Warning : 사이트 이름을 정하는 것을 잊지 마세요!
|
||||
services:
|
||||
# 웹 프런트엔드
|
||||
traefik:
|
||||
image: traefik:v3.6
|
||||
container_name: front
|
||||
restart: unless-stopped
|
||||
command:
|
||||
- "--providers.docker=true"
|
||||
- "--entrypoints.http.address=:80"
|
||||
ports:
|
||||
- 80:80
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
# InfluxDB 데이터베이스
|
||||
influxdb:
|
||||
image: influxdb:2.8.0
|
||||
container_name: influxdb
|
||||
user: "1000:1000"
|
||||
environment:
|
||||
DOCKER_INFLUXDB_INIT_MODE:
|
||||
DOCKER_INFLUXDB_INIT_USERNAME:
|
||||
DOCKER_INFLUXDB_INIT_PASSWORD:
|
||||
DOCKER_INFLUXDB_INIT_ORG:
|
||||
DOCKER_INFLUXDB_INIT_BUCKET:
|
||||
DOCKER_INFLUXDB_INIT_RETENTION:
|
||||
DOCKER_INFLUXDB_INIT_ADMIN_TOKEN:
|
||||
volumes:
|
||||
- ./influxdb2/data:/var/lib/influxdb2
|
||||
- ./influxdb2/config:/etc/influxdb2
|
||||
restart: unless-stopped
|
||||
labels:
|
||||
- "traefik.http.routers.influxdb.rule=Host(`influx.test`)" # 사이트 이름
|
||||
- "traefik.http.services.influxdb.loadbalancer.server.port=8086"
|
||||
# 그래픽 렌더링 서비스
|
||||
grafana:
|
||||
image: grafana/grafana:main
|
||||
container_name: grafana
|
||||
user: "1000:1000"
|
||||
restart: always
|
||||
depends_on:
|
||||
- influxdb
|
||||
environment:
|
||||
- GF_SECURITY_ADMIN_USER
|
||||
- GF_SECURITY_ADMIN_PASSWORD
|
||||
ports:
|
||||
- 3000:3000
|
||||
volumes:
|
||||
- ./grafana/datas:/var/lib/grafana:rw
|
||||
labels:
|
||||
- "traefik.http.routers.grafana.rule=Host(`grafana.test`)" # 사이트 이름
|
||||
- "traefik.http.services.grafana.loadbalancer.server.port=3000"
|
||||
5
buildup/src/.env
Normal file
5
buildup/src/.env
Normal file
@@ -0,0 +1,5 @@
|
||||
# Gestion InfluxDB
|
||||
INFLUXDB_TOKEN="admin-token"
|
||||
INFLUXDB_URL="http://influx.test"
|
||||
INFLUXDB_ORG="myorg"
|
||||
INFLUXDB_BUCKET="metrics"
|
||||
25
buildup/src/config.go
Normal file
25
buildup/src/config.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Servers []string `yaml:"servers"`
|
||||
}
|
||||
|
||||
func LoadConfig(path string) (*Config, error) {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var cfg Config
|
||||
if err := yaml.Unmarshal(data, &cfg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &cfg, nil
|
||||
}
|
||||
51
buildup/src/cpu/cpu.go
Normal file
51
buildup/src/cpu/cpu.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package cpu
|
||||
|
||||
import (
|
||||
"github.com/shirou/gopsutil/v4/cpu"
|
||||
)
|
||||
|
||||
type CPUCore struct {
|
||||
Info cpu.InfoStat `json:"info"`
|
||||
Usage float64 `json:"usage_percent"`
|
||||
Times *cpu.TimesStat `json:"times,omitempty"`
|
||||
}
|
||||
|
||||
type CPUInfo struct {
|
||||
Cores []CPUCore `json:"cores"`
|
||||
}
|
||||
|
||||
func ReadCPU() (*CPUInfo, error) {
|
||||
infos, err := cpu.Info()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
usage, err := cpu.Percent(0, true) // usage par core
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
times, err := cpu.Times(true) // stats par core
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
out := make([]CPUCore, 0, len(infos))
|
||||
for i, info := range infos {
|
||||
core := CPUCore{
|
||||
Info: info,
|
||||
}
|
||||
|
||||
if i < len(usage) {
|
||||
core.Usage = usage[i]
|
||||
}
|
||||
if i < len(times) {
|
||||
tmp := times[i] // adresse stable
|
||||
core.Times = &tmp
|
||||
}
|
||||
|
||||
out = append(out, core)
|
||||
}
|
||||
|
||||
return &CPUInfo{Cores: out}, nil
|
||||
}
|
||||
38
buildup/src/disk/disk.go
Normal file
38
buildup/src/disk/disk.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package disk
|
||||
|
||||
import (
|
||||
"github.com/shirou/gopsutil/v4/disk"
|
||||
)
|
||||
|
||||
type DiskFS struct {
|
||||
Partition disk.PartitionStat `json:"partition"`
|
||||
Usage *disk.UsageStat `json:"usage,omitempty"`
|
||||
}
|
||||
|
||||
func ReadDisk() (*[]DiskFS, error) {
|
||||
parts, err := disk.Partitions(true) // true = toutes les partitions
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var out []DiskFS
|
||||
|
||||
for _, p := range parts {
|
||||
fs := DiskFS{Partition: p}
|
||||
|
||||
// Usage peut échouer (ex: pseudo-fs, permissions)
|
||||
if u, err := disk.Usage(p.Mountpoint); err == nil {
|
||||
fs.Usage = u
|
||||
}
|
||||
|
||||
// Exclusion des FS spécifique au système
|
||||
switch fs.Partition.Fstype {
|
||||
case "none", "proc", "tmpfs", "overlay", "sysfs", "cgroup2", "mqueue", "nsfs":
|
||||
// ignoré
|
||||
default:
|
||||
out = append(out, fs)
|
||||
}
|
||||
}
|
||||
|
||||
return &out, nil
|
||||
}
|
||||
26
buildup/src/go.mod
Normal file
26
buildup/src/go.mod
Normal file
@@ -0,0 +1,26 @@
|
||||
module github.com/evoliatis/buildup
|
||||
|
||||
go 1.25.6
|
||||
|
||||
require (
|
||||
github.com/influxdata/influxdb-client-go/v2 v2.14.0
|
||||
github.com/joho/godotenv v1.5.1
|
||||
github.com/shirou/gopsutil/v4 v4.26.1
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
|
||||
github.com/ebitengine/purego v0.9.1 // indirect
|
||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||
github.com/google/uuid v1.3.1 // indirect
|
||||
github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
||||
github.com/oapi-codegen/runtime v1.0.0 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.16 // indirect
|
||||
github.com/tklauser/numcpus v0.11.0 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
golang.org/x/net v0.23.0 // indirect
|
||||
golang.org/x/sys v0.40.0 // indirect
|
||||
)
|
||||
55
buildup/src/go.sum
Normal file
55
buildup/src/go.sum
Normal file
@@ -0,0 +1,55 @@
|
||||
github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk=
|
||||
github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ=
|
||||
github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk=
|
||||
github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w=
|
||||
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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A=
|
||||
github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
|
||||
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/influxdata/influxdb-client-go/v2 v2.14.0 h1:AjbBfJuq+QoaXNcrova8smSjwJdUHnwvfjMF71M1iI4=
|
||||
github.com/influxdata/influxdb-client-go/v2 v2.14.0/go.mod h1:Ahpm3QXKMJslpXl3IftVLVezreAUtBOTZssDrjZEFHI=
|
||||
github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839 h1:W9WBk7wlPfJLvMCdtV4zPulc4uCPrlywQOmbFOhgQNU=
|
||||
github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo=
|
||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||
github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
||||
github.com/oapi-codegen/runtime v1.0.0 h1:P4rqFX5fMFWqRzY9M/3YF9+aPSPPB06IzP2P7oOxrWo=
|
||||
github.com/oapi-codegen/runtime v1.0.0/go.mod h1:LmCUMQuPB4M/nLXilQXhHw+BLZdDb18B34OO356yJ/A=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
github.com/shirou/gopsutil/v4 v4.26.1 h1:TOkEyriIXk2HX9d4isZJtbjXbEjf5qyKPAzbzY0JWSo=
|
||||
github.com/shirou/gopsutil/v4 v4.26.1/go.mod h1:medLI9/UNAb0dOI9Q3/7yWSqKkj00u+1tgY8nvv41pc=
|
||||
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYICU0nA=
|
||||
github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI=
|
||||
github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9RXw=
|
||||
github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ=
|
||||
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
|
||||
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
|
||||
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
80
buildup/src/goroutines/cpu.go
Normal file
80
buildup/src/goroutines/cpu.go
Normal file
@@ -0,0 +1,80 @@
|
||||
package goroutines
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/evoliatis/buildup/cpu"
|
||||
influxdb2 "github.com/influxdata/influxdb-client-go/v2"
|
||||
)
|
||||
|
||||
func GoCPU(server, INFLUXDB_URL, INFLUXDB_ORG, INFLUXDB_BUCKET, INFLUXDB_TOKEN string, tick int) {
|
||||
|
||||
// Connexion
|
||||
client := influxdb2.NewClient(INFLUXDB_URL, INFLUXDB_TOKEN)
|
||||
defer client.Close()
|
||||
|
||||
writeAPI := client.WriteAPIBlocking(INFLUXDB_ORG, INFLUXDB_BUCKET)
|
||||
|
||||
ticker := time.NewTicker(time.Duration(tick) * time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
for range ticker.C {
|
||||
// Récupération des valeurs
|
||||
reqUrl := "http://" + server + "/cpu"
|
||||
resp, err := http.Get(reqUrl)
|
||||
if err != nil {
|
||||
log.Println("Erreur de communication : " + reqUrl)
|
||||
continue
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var cpu cpu.CPUInfo
|
||||
if err := json.NewDecoder(resp.Body).Decode(&cpu); err != nil {
|
||||
log.Println("Impossible de récupérer : " + reqUrl)
|
||||
continue
|
||||
}
|
||||
|
||||
for _, c := range cpu.Cores {
|
||||
tags := map[string]string{ // tags (ex: host, env…)
|
||||
"cpu": c.Times.CPU, // ex "cpu0"
|
||||
"vendor": c.Info.VendorID, // utile pour multi-host
|
||||
"model": c.Info.ModelName,
|
||||
"host": server,
|
||||
}
|
||||
fields := map[string]interface{}{
|
||||
"cpu": c.Usage,
|
||||
|
||||
// Tous les champs TimesStat (numériques)
|
||||
"user": c.Times.User,
|
||||
"system": c.Times.System,
|
||||
"idle": c.Times.Idle,
|
||||
"nice": c.Times.Nice,
|
||||
"iowait": c.Times.Iowait,
|
||||
"irq": c.Times.Irq,
|
||||
"softirq": c.Times.Softirq,
|
||||
"steal": c.Times.Steal,
|
||||
"guest": c.Times.Guest,
|
||||
"guest_nice": c.Times.GuestNice,
|
||||
}
|
||||
|
||||
// ---- point ----
|
||||
p := influxdb2.NewPoint(
|
||||
"cpu",
|
||||
tags,
|
||||
fields,
|
||||
time.Now(),
|
||||
)
|
||||
|
||||
// Ecriture
|
||||
if err := writeAPI.WritePoint(context.Background(), p); err != nil {
|
||||
log.Printf("write failed: %v \n", err)
|
||||
}
|
||||
}
|
||||
|
||||
log.Println("CPU written to InfluxDB :" + reqUrl)
|
||||
}
|
||||
}
|
||||
76
buildup/src/goroutines/disk.go
Normal file
76
buildup/src/goroutines/disk.go
Normal file
@@ -0,0 +1,76 @@
|
||||
package goroutines
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/evoliatis/buildup/disk"
|
||||
influxdb2 "github.com/influxdata/influxdb-client-go/v2"
|
||||
)
|
||||
|
||||
func GoDisk(server, INFLUXDB_URL, INFLUXDB_ORG, INFLUXDB_BUCKET, INFLUXDB_TOKEN string, tick int) {
|
||||
|
||||
// Connexion
|
||||
client := influxdb2.NewClient(INFLUXDB_URL, INFLUXDB_TOKEN)
|
||||
defer client.Close()
|
||||
|
||||
writeAPI := client.WriteAPIBlocking(INFLUXDB_ORG, INFLUXDB_BUCKET)
|
||||
|
||||
ticker := time.NewTicker(time.Duration(tick) * time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
for range ticker.C {
|
||||
// Récupération des valeurs
|
||||
reqUrl := "http://" + server + "/disk"
|
||||
resp, err := http.Get(reqUrl)
|
||||
if err != nil {
|
||||
log.Println("Erreur de communication : " + reqUrl)
|
||||
continue
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var disks []disk.DiskFS
|
||||
if err := json.NewDecoder(resp.Body).Decode(&disks); err != nil {
|
||||
log.Println("Impossible de récupérer : " + reqUrl)
|
||||
continue
|
||||
}
|
||||
|
||||
for _, disk := range disks {
|
||||
tags := map[string]string{ // tags (ex: host, env…)
|
||||
"host": server,
|
||||
"device": disk.Partition.Device,
|
||||
"mountpoint": disk.Partition.Mountpoint,
|
||||
"fstype": disk.Usage.Fstype,
|
||||
}
|
||||
fields := map[string]interface{}{
|
||||
// Tous les champs TimesStat (numériques)
|
||||
"total": disk.Usage.Total,
|
||||
"free": disk.Usage.Free,
|
||||
"used": disk.Usage.Used,
|
||||
"usedpcent": disk.Usage.UsedPercent,
|
||||
"inodestotal": disk.Usage.InodesTotal,
|
||||
"inodesused": disk.Usage.InodesUsed,
|
||||
"inodesfree": disk.Usage.InodesFree,
|
||||
"inodesusedpcent": disk.Usage.InodesUsedPercent,
|
||||
}
|
||||
|
||||
// ---- point ----
|
||||
p := influxdb2.NewPoint(
|
||||
"disk",
|
||||
tags,
|
||||
fields,
|
||||
time.Now(),
|
||||
)
|
||||
|
||||
// Ecriture
|
||||
if err := writeAPI.WritePoint(context.Background(), p); err != nil {
|
||||
log.Printf("write failed: %v \n", err)
|
||||
}
|
||||
}
|
||||
|
||||
log.Println("(", len(disks), ") Disks written to InfluxDB :"+reqUrl)
|
||||
}
|
||||
}
|
||||
62
buildup/src/goroutines/load.go
Normal file
62
buildup/src/goroutines/load.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package goroutines
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/evoliatis/buildup/load"
|
||||
influxdb2 "github.com/influxdata/influxdb-client-go/v2"
|
||||
)
|
||||
|
||||
func GoLoad(server, INFLUXDB_URL, INFLUXDB_ORG, INFLUXDB_BUCKET, INFLUXDB_TOKEN string, tick int) {
|
||||
|
||||
// Connexion
|
||||
client := influxdb2.NewClient(INFLUXDB_URL, INFLUXDB_TOKEN)
|
||||
defer client.Close()
|
||||
|
||||
writeAPI := client.WriteAPIBlocking(INFLUXDB_ORG, INFLUXDB_BUCKET)
|
||||
|
||||
ticker := time.NewTicker(time.Duration(tick) * time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
for range ticker.C {
|
||||
// Récupération des valeurs
|
||||
reqUrl := "http://" + server + "/load"
|
||||
resp, err := http.Get(reqUrl)
|
||||
if err != nil {
|
||||
log.Println("Erreur de communication : " + reqUrl)
|
||||
continue
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var avg load.LoadInfo
|
||||
if err := json.NewDecoder(resp.Body).Decode(&avg); err != nil {
|
||||
log.Println("Impossible de récupérer : " + reqUrl)
|
||||
continue
|
||||
}
|
||||
|
||||
// ---- point ----
|
||||
p := influxdb2.NewPoint(
|
||||
"load_average",
|
||||
map[string]string{ // tags (ex: host, env…)
|
||||
"host": server,
|
||||
},
|
||||
map[string]interface{}{ // Données à stocker
|
||||
"avg1": avg.Avg.Load1,
|
||||
"avg5": avg.Avg.Load5,
|
||||
"avg15": avg.Avg.Load15,
|
||||
},
|
||||
time.Now(),
|
||||
)
|
||||
|
||||
// Ecriture
|
||||
if err := writeAPI.WritePoint(context.Background(), p); err != nil {
|
||||
log.Printf("write failed: %v\n", err)
|
||||
}
|
||||
|
||||
log.Println("load average written to InfluxDB : ", reqUrl)
|
||||
}
|
||||
}
|
||||
79
buildup/src/goroutines/mem.go
Normal file
79
buildup/src/goroutines/mem.go
Normal file
@@ -0,0 +1,79 @@
|
||||
package goroutines
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/evoliatis/buildup/memory"
|
||||
influxdb2 "github.com/influxdata/influxdb-client-go/v2"
|
||||
)
|
||||
|
||||
func GoMem(server, INFLUXDB_URL, INFLUXDB_ORG, INFLUXDB_BUCKET, INFLUXDB_TOKEN string, tick int) {
|
||||
|
||||
// Connexion
|
||||
client := influxdb2.NewClient(INFLUXDB_URL, INFLUXDB_TOKEN)
|
||||
defer client.Close()
|
||||
|
||||
writeAPI := client.WriteAPIBlocking(INFLUXDB_ORG, INFLUXDB_BUCKET)
|
||||
|
||||
ticker := time.NewTicker(time.Duration(tick) * time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
for range ticker.C {
|
||||
// Récupération des valeurs
|
||||
reqUrl := "http://" + server + "/mem"
|
||||
resp, err := http.Get(reqUrl)
|
||||
if err != nil {
|
||||
log.Println("Erreur de communication : " + reqUrl)
|
||||
continue
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var mem memory.MemInfo
|
||||
if err := json.NewDecoder(resp.Body).Decode(&mem); err != nil {
|
||||
log.Println("Impossible de récupérer : " + reqUrl)
|
||||
continue
|
||||
}
|
||||
|
||||
// ---- point ----
|
||||
tags := map[string]string{ // tags (ex: host, env…)
|
||||
"host": server,
|
||||
}
|
||||
fields := map[string]interface{}{
|
||||
// Tous les champs TimesStat (numériques)
|
||||
"total": mem.Virtual.Total,
|
||||
"available": mem.Virtual.Available,
|
||||
"used": mem.Virtual.Used,
|
||||
"used_pcent": mem.Virtual.UsedPercent,
|
||||
"free": mem.Virtual.Free,
|
||||
"active": mem.Virtual.Active,
|
||||
"inactive": mem.Virtual.Inactive,
|
||||
"cached": mem.Virtual.Cached,
|
||||
"dirty": mem.Virtual.Dirty,
|
||||
"shared": mem.Virtual.Shared,
|
||||
"swap_total": mem.Swap.Total,
|
||||
"swap_used": mem.Swap.Used,
|
||||
"swap_used_pcent": mem.Swap.UsedPercent,
|
||||
"swap_free": mem.Swap.Free,
|
||||
"swap_pgin": mem.Swap.PgIn,
|
||||
"swap_pgout": mem.Swap.PgOut,
|
||||
"swap_pgfault": mem.Swap.PgFault,
|
||||
}
|
||||
p := influxdb2.NewPoint(
|
||||
"memory",
|
||||
tags,
|
||||
fields,
|
||||
time.Now(),
|
||||
)
|
||||
|
||||
// Ecriture
|
||||
if err := writeAPI.WritePoint(context.Background(), p); err != nil {
|
||||
log.Printf("write failed: %v\n", err)
|
||||
}
|
||||
|
||||
log.Println("memory written to InfluxDB : ", reqUrl)
|
||||
}
|
||||
}
|
||||
78
buildup/src/goroutines/net.go
Normal file
78
buildup/src/goroutines/net.go
Normal file
@@ -0,0 +1,78 @@
|
||||
package goroutines
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"log"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/evoliatis/buildup/netcard"
|
||||
influxdb2 "github.com/influxdata/influxdb-client-go/v2"
|
||||
)
|
||||
|
||||
func GoNet(server, INFLUXDB_URL, INFLUXDB_ORG, INFLUXDB_BUCKET, INFLUXDB_TOKEN string, tick int) {
|
||||
|
||||
// Connexion
|
||||
client := influxdb2.NewClient(INFLUXDB_URL, INFLUXDB_TOKEN)
|
||||
defer client.Close()
|
||||
|
||||
writeAPI := client.WriteAPIBlocking(INFLUXDB_ORG, INFLUXDB_BUCKET)
|
||||
|
||||
ticker := time.NewTicker(time.Duration(tick) * time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
for range ticker.C {
|
||||
// Récupération des valeurs
|
||||
reqUrl := "http://" + server + "/net"
|
||||
resp, err := http.Get(reqUrl)
|
||||
if err != nil {
|
||||
log.Println("Erreur de communication : " + reqUrl)
|
||||
continue
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var nets []netcard.NetCard
|
||||
if err := json.NewDecoder(resp.Body).Decode(&nets); err != nil {
|
||||
log.Println("Impossible de récupérer : " + reqUrl)
|
||||
continue
|
||||
}
|
||||
|
||||
for _, net := range nets {
|
||||
|
||||
tags := map[string]string{ // tags (ex: host, env…)
|
||||
"host": "linux",
|
||||
"name": net.Interface.Name,
|
||||
"mtu": strconv.FormatInt(int64(net.Interface.MTU), 10),
|
||||
"hardwareaddr": net.Interface.HardwareAddr,
|
||||
}
|
||||
fields := map[string]interface{}{ // Données à stocker
|
||||
"bytesent": net.IO.BytesSent,
|
||||
"byterecv": net.IO.BytesRecv,
|
||||
"packetsent": net.IO.PacketsSent,
|
||||
"packetrecv": net.IO.PacketsRecv,
|
||||
"errorin": net.IO.Errin,
|
||||
"errorout": net.IO.Errout,
|
||||
"dropin": net.IO.Dropin,
|
||||
"dropout": net.IO.Dropout,
|
||||
"fifoin": net.IO.Fifoin,
|
||||
"fifoout": net.IO.Fifoout,
|
||||
}
|
||||
// ---- points ----
|
||||
p := influxdb2.NewPoint(
|
||||
"network",
|
||||
tags,
|
||||
fields,
|
||||
time.Now(),
|
||||
)
|
||||
|
||||
// Ecriture
|
||||
if err := writeAPI.WritePoint(context.Background(), p); err != nil {
|
||||
log.Printf("write failed: %v \n", err)
|
||||
}
|
||||
}
|
||||
|
||||
log.Println("(", len(nets), ") network written to InfluxDB : "+reqUrl)
|
||||
}
|
||||
}
|
||||
70
buildup/src/goroutines/procs.go
Normal file
70
buildup/src/goroutines/procs.go
Normal file
@@ -0,0 +1,70 @@
|
||||
package goroutines
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"log"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/evoliatis/buildup/proc"
|
||||
influxdb2 "github.com/influxdata/influxdb-client-go/v2"
|
||||
)
|
||||
|
||||
func GoProc(server, INFLUXDB_URL, INFLUXDB_ORG, INFLUXDB_BUCKET, INFLUXDB_TOKEN string, tick int) {
|
||||
|
||||
// Connexion
|
||||
client := influxdb2.NewClient(INFLUXDB_URL, INFLUXDB_TOKEN)
|
||||
defer client.Close()
|
||||
|
||||
writeAPI := client.WriteAPIBlocking(INFLUXDB_ORG, INFLUXDB_BUCKET)
|
||||
|
||||
ticker := time.NewTicker(time.Duration(tick) * time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
for range ticker.C {
|
||||
// Récupération des valeurs
|
||||
reqUrl := "http://" + server + "/ps"
|
||||
resp, err := http.Get(reqUrl)
|
||||
if err != nil {
|
||||
log.Println("Erreur de communication : " + reqUrl)
|
||||
continue
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var procs []proc.Proc
|
||||
if err := json.NewDecoder(resp.Body).Decode(&procs); err != nil {
|
||||
log.Println("Impossible de récupérer : " + reqUrl)
|
||||
continue
|
||||
}
|
||||
|
||||
for _, proc := range procs {
|
||||
|
||||
tags := map[string]string{ // tags (ex: host, env…)
|
||||
"host": "linux",
|
||||
"name": proc.Name,
|
||||
"pid": strconv.FormatInt(int64(proc.Pid), 10),
|
||||
}
|
||||
fields := map[string]interface{}{ // Données à stocker
|
||||
"CPU": proc.CPU,
|
||||
"Memory": proc.Memory,
|
||||
"Status": proc.Status,
|
||||
}
|
||||
// ---- points ----
|
||||
p := influxdb2.NewPoint(
|
||||
"processes",
|
||||
tags,
|
||||
fields,
|
||||
time.Now(),
|
||||
)
|
||||
|
||||
// Ecriture
|
||||
if err := writeAPI.WritePoint(context.Background(), p); err != nil {
|
||||
log.Printf("write failed: %v \n", err)
|
||||
}
|
||||
}
|
||||
|
||||
log.Println("(", len(procs), ") procs written to InfluxDB : "+reqUrl)
|
||||
}
|
||||
}
|
||||
11
buildup/src/handle.go
Normal file
11
buildup/src/handle.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// health URL
|
||||
func HealthHandle(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintf(w, "ok")
|
||||
}
|
||||
27
buildup/src/load/load.go
Normal file
27
buildup/src/load/load.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package load
|
||||
|
||||
import (
|
||||
"github.com/shirou/gopsutil/v4/load"
|
||||
)
|
||||
|
||||
type LoadInfo struct {
|
||||
Avg *load.AvgStat `json:"avg"`
|
||||
Misc *load.MiscStat `json:"misc"`
|
||||
}
|
||||
|
||||
func ReadLoad() (*LoadInfo, error) {
|
||||
avg, err := load.Avg()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
misc, err := load.Misc()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &LoadInfo{
|
||||
Avg: avg,
|
||||
Misc: misc,
|
||||
}, nil
|
||||
}
|
||||
40
buildup/src/main.go
Normal file
40
buildup/src/main.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/evoliatis/buildup/goroutines"
|
||||
"github.com/joho/godotenv"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// .env 파일을 불러오세요
|
||||
if err := godotenv.Load(); err != nil {
|
||||
log.Fatalln("fichier .env non trouvé")
|
||||
}
|
||||
InToken := os.Getenv("INFLUXDB_TOKEN")
|
||||
InUrl := os.Getenv("INFLUXDB_URL")
|
||||
InOrg := os.Getenv("INFLUXDB_ORG")
|
||||
InBucket := os.Getenv("INFLUXDB_BUCKET")
|
||||
|
||||
// 서버 목록을 불러오세요
|
||||
cfg, err := LoadConfig("servers.yaml")
|
||||
if err != nil {
|
||||
log.Fatalln("fichier servers.yaml non trouvé")
|
||||
}
|
||||
|
||||
// Go routines
|
||||
for _, srv := range cfg.Servers {
|
||||
|
||||
go goroutines.GoLoad(srv, InUrl, InOrg, InBucket, InToken, 10)
|
||||
go goroutines.GoProc(srv, InUrl, InOrg, InBucket, InToken, 10)
|
||||
go goroutines.GoCPU(srv, InUrl, InOrg, InBucket, InToken, 10)
|
||||
go goroutines.GoMem(srv, InUrl, InOrg, InBucket, InToken, 10)
|
||||
go goroutines.GoNet(srv, InUrl, InOrg, InBucket, InToken, 10)
|
||||
go goroutines.GoDisk(srv, InUrl, InOrg, InBucket, InToken, 10)
|
||||
}
|
||||
log.Println("listening on :80")
|
||||
log.Fatal(http.ListenAndServe(":80", router()))
|
||||
}
|
||||
27
buildup/src/memory/memory.go
Normal file
27
buildup/src/memory/memory.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package memory
|
||||
|
||||
import (
|
||||
"github.com/shirou/gopsutil/v4/mem"
|
||||
)
|
||||
|
||||
type MemInfo struct {
|
||||
Virtual *mem.VirtualMemoryStat `json:"virtual"`
|
||||
Swap *mem.SwapMemoryStat `json:"swap"`
|
||||
}
|
||||
|
||||
func ReadMemory() (*MemInfo, error) {
|
||||
vm, err := mem.VirtualMemory()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sm, err := mem.SwapMemory()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &MemInfo{
|
||||
Virtual: vm,
|
||||
Swap: sm,
|
||||
}, nil
|
||||
}
|
||||
42
buildup/src/netcard/netcard.go
Normal file
42
buildup/src/netcard/netcard.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package netcard
|
||||
|
||||
import (
|
||||
"github.com/shirou/gopsutil/v4/net"
|
||||
)
|
||||
|
||||
type NetCard struct {
|
||||
Interface net.InterfaceStat `json:"interface"`
|
||||
IO *net.IOCountersStat `json:"io,omitempty"`
|
||||
}
|
||||
|
||||
func ReadNetwork(nom string) (*[]NetCard, error) {
|
||||
ifaces, err := net.Interfaces()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
counters, err := net.IOCounters(true) // true => stats par interface
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// index des compteurs par nom d'interface
|
||||
byName := make(map[string]net.IOCountersStat, len(counters))
|
||||
for _, c := range counters {
|
||||
byName[c.Name] = c
|
||||
}
|
||||
|
||||
// fusion
|
||||
out := make([]NetCard, 0, len(ifaces))
|
||||
for _, itf := range ifaces {
|
||||
card := NetCard{Interface: itf}
|
||||
if c, ok := byName[itf.Name]; ok {
|
||||
tmp := c
|
||||
card.IO = &tmp
|
||||
}
|
||||
if nom == "" || nom == card.Interface.Name {
|
||||
out = append(out, card)
|
||||
}
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
35
buildup/src/proc/proc.go
Normal file
35
buildup/src/proc/proc.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package proc
|
||||
|
||||
import "github.com/shirou/gopsutil/v4/process"
|
||||
|
||||
type Proc struct {
|
||||
Pid int32 `json:"pid"`
|
||||
Name string `json:"name"`
|
||||
User string `json:"user"`
|
||||
CPU float64 `json:"cpu"`
|
||||
Memory float32 `json:"memory"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
// Chargement des Processus
|
||||
func ReadProc(user string) (*[]Proc, error) {
|
||||
var myProcs []Proc
|
||||
procs, err := process.Processes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, proc := range procs {
|
||||
var myProc Proc
|
||||
myProc.User, _ = proc.Username()
|
||||
if user == "" || user == myProc.User {
|
||||
myProc.Pid = proc.Pid
|
||||
myProc.Name, _ = proc.Name()
|
||||
myProc.CPU, _ = proc.CPUPercent()
|
||||
myProc.Memory, _ = proc.MemoryPercent()
|
||||
status, _ := proc.Status()
|
||||
myProc.Status = status[0]
|
||||
myProcs = append(myProcs, myProc)
|
||||
}
|
||||
}
|
||||
return &myProcs, nil
|
||||
}
|
||||
17
buildup/src/routes.go
Normal file
17
buildup/src/routes.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package main
|
||||
|
||||
import "net/http"
|
||||
|
||||
// Routeur pour l'ensemble de nos routes
|
||||
|
||||
func router() http.Handler {
|
||||
mux := http.NewServeMux()
|
||||
// health URL
|
||||
mux.HandleFunc("GET /health", HealthHandle)
|
||||
|
||||
// Autres cas : fichiers statiques
|
||||
fs := http.FileServer(http.Dir("www"))
|
||||
mux.Handle("/", fs)
|
||||
|
||||
return mux
|
||||
}
|
||||
2
buildup/src/servers.yaml
Normal file
2
buildup/src/servers.yaml
Normal file
@@ -0,0 +1,2 @@
|
||||
servers:
|
||||
- linux:8080
|
||||
BIN
buildup/src/www/img/logo.png
Normal file
BIN
buildup/src/www/img/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.6 MiB |
370
buildup/src/www/index.html
Normal file
370
buildup/src/www/index.html
Normal file
@@ -0,0 +1,370 @@
|
||||
<!doctype html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
<meta name="description" content="Reveleant Software Solution — Une vision (très) corporate, avec une touche d'humour geek." />
|
||||
<title>Reveleant Software Solution — Note interne</title>
|
||||
|
||||
<style>
|
||||
:root{
|
||||
--bg0:#0b0f14;
|
||||
--bg1:#0b1a16;
|
||||
--card: rgba(255,255,255,.06);
|
||||
--stroke: rgba(255,255,255,.12);
|
||||
--text: rgba(255,255,255,.92);
|
||||
--muted: rgba(255,255,255,.72);
|
||||
--accent:#38e38d;
|
||||
--accent2:#1fbf6a;
|
||||
--shadow: 0 18px 60px rgba(0,0,0,.45);
|
||||
--radius: 18px;
|
||||
--radius2: 26px;
|
||||
}
|
||||
|
||||
*{box-sizing:border-box}
|
||||
html,body{height:100%}
|
||||
body{
|
||||
margin:0;
|
||||
font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, "Apple Color Emoji","Segoe UI Emoji";
|
||||
color: var(--text);
|
||||
background:
|
||||
radial-gradient(1100px 700px at 18% 10%, rgba(56,227,141,.18), transparent 55%),
|
||||
radial-gradient(900px 600px at 88% 42%, rgba(31,191,106,.14), transparent 60%),
|
||||
linear-gradient(180deg, var(--bg0), var(--bg1));
|
||||
overflow-x:hidden;
|
||||
}
|
||||
|
||||
.grid{
|
||||
position:fixed; inset:0;
|
||||
pointer-events:none;
|
||||
background-image:
|
||||
linear-gradient(to right, rgba(255,255,255,.06) 1px, transparent 1px),
|
||||
linear-gradient(to bottom, rgba(255,255,255,.06) 1px, transparent 1px);
|
||||
background-size: 44px 44px;
|
||||
mask-image: radial-gradient(740px 420px at 32% 18%, black 42%, transparent 88%);
|
||||
opacity:.30;
|
||||
}
|
||||
|
||||
.wrap{max-width:1100px;margin:0 auto;padding:26px 18px 64px}
|
||||
|
||||
header{
|
||||
display:flex;align-items:center;justify-content:space-between;gap:16px;
|
||||
padding:14px 16px;
|
||||
border:1px solid var(--stroke);
|
||||
background: linear-gradient(180deg, rgba(255,255,255,.08), rgba(255,255,255,.04));
|
||||
border-radius: var(--radius2);
|
||||
box-shadow: var(--shadow);
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.brand{
|
||||
display:flex;align-items:center;gap:14px;min-width:240px;
|
||||
}
|
||||
|
||||
.brand-mark{
|
||||
width:40px;height:40px;border-radius:14px;
|
||||
border:1px solid rgba(56,227,141,.30);
|
||||
background: linear-gradient(180deg, rgba(56,227,141,.18), rgba(31,191,106,.08));
|
||||
display:grid;place-items:center;
|
||||
box-shadow: 0 10px 26px rgba(31,191,106,.18);
|
||||
color: rgba(255,255,255,.9);
|
||||
font-weight:900;
|
||||
}
|
||||
|
||||
.brand strong{letter-spacing:.3px}
|
||||
.brand span{display:block;color:var(--muted);font-size:12.5px;margin-top:1px}
|
||||
|
||||
nav{display:flex;gap:10px;flex-wrap:wrap;justify-content:flex-end}
|
||||
.pill{
|
||||
text-decoration:none;
|
||||
font-size:13px;
|
||||
padding:9px 12px;
|
||||
border-radius:999px;
|
||||
border:1px solid var(--stroke);
|
||||
background: rgba(255,255,255,.04);
|
||||
color: rgba(255,255,255,.86);
|
||||
}
|
||||
.pill:hover{background: rgba(255,255,255,.07)}
|
||||
.cta{
|
||||
text-decoration:none;
|
||||
font-weight:700;
|
||||
letter-spacing:.2px;
|
||||
padding:10px 16px;
|
||||
border-radius:999px;
|
||||
|
||||
border:1px solid rgba(56,227,141,.45);
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgba(56,227,141,.35),
|
||||
rgba(31,191,106,.20)
|
||||
);
|
||||
|
||||
color: #ffffff; /* ✅ FIX PRINCIPAL */
|
||||
box-shadow:
|
||||
0 10px 30px rgba(31,191,106,.25),
|
||||
inset 0 0 0 1px rgba(255,255,255,.08);
|
||||
}
|
||||
.cta:hover{
|
||||
filter: brightness(1.08);
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.hero{
|
||||
margin-top:18px;
|
||||
display:grid;
|
||||
grid-template-columns: 1.05fr .95fr;
|
||||
gap:16px;
|
||||
align-items:stretch;
|
||||
}
|
||||
@media (max-width: 920px){
|
||||
.hero{grid-template-columns:1fr}
|
||||
nav{justify-content:flex-start}
|
||||
}
|
||||
|
||||
.panel{
|
||||
border:1px solid var(--stroke);
|
||||
background: linear-gradient(180deg, rgba(255,255,255,.07), rgba(255,255,255,.03));
|
||||
border-radius: var(--radius2);
|
||||
box-shadow: var(--shadow);
|
||||
backdrop-filter: blur(10px);
|
||||
overflow:hidden;
|
||||
}
|
||||
|
||||
.left{padding:24px}
|
||||
.kicker{
|
||||
display:inline-flex;align-items:center;gap:10px;
|
||||
padding:8px 12px;border-radius:999px;
|
||||
border:1px solid rgba(56,227,141,.28);
|
||||
background: rgba(56,227,141,.09);
|
||||
color: rgba(255,255,255,.86);
|
||||
font-size:13px;
|
||||
}
|
||||
.pulse{
|
||||
width:10px;height:10px;border-radius:999px;
|
||||
background: var(--accent);
|
||||
box-shadow: 0 0 0 0 rgba(56,227,141,.45);
|
||||
animation: pulse 1.8s infinite;
|
||||
}
|
||||
@keyframes pulse{
|
||||
0%{box-shadow:0 0 0 0 rgba(56,227,141,.45)}
|
||||
70%{box-shadow:0 0 0 14px rgba(56,227,141,0)}
|
||||
100%{box-shadow:0 0 0 0 rgba(56,227,141,0)}
|
||||
}
|
||||
|
||||
h1{
|
||||
margin:14px 0 10px;
|
||||
font-size:34px;
|
||||
line-height:1.08;
|
||||
letter-spacing:-.6px;
|
||||
}
|
||||
.lead{
|
||||
margin:0;
|
||||
color: var(--muted);
|
||||
font-size:15.8px;
|
||||
line-height:1.6;
|
||||
max-width: 66ch;
|
||||
}
|
||||
|
||||
hr{
|
||||
border:0;
|
||||
height:1px;
|
||||
background: linear-gradient(90deg, transparent, rgba(56,227,141,.25), rgba(255,255,255,.10), transparent);
|
||||
margin:16px 0;
|
||||
}
|
||||
|
||||
.section h2{
|
||||
margin:0 0 8px;
|
||||
font-size:15px;
|
||||
letter-spacing:.2px;
|
||||
text-transform: uppercase;
|
||||
color: rgba(255,255,255,.88);
|
||||
display:flex;align-items:center;gap:10px;
|
||||
}
|
||||
.section p{
|
||||
margin:0;
|
||||
color: rgba(255,255,255,.76);
|
||||
line-height:1.65;
|
||||
font-size:14.5px;
|
||||
}
|
||||
.section p + p{margin-top:10px}
|
||||
|
||||
.right{
|
||||
position:relative;
|
||||
padding:18px;
|
||||
display:flex;
|
||||
align-items:center;
|
||||
justify-content:center;
|
||||
min-height: 360px;
|
||||
}
|
||||
|
||||
.logo-wrap{
|
||||
position:relative;
|
||||
width:min(520px, 96%);
|
||||
padding:16px;
|
||||
border-radius: 18px;
|
||||
border:1px solid rgba(255,255,255,.10);
|
||||
background: rgba(0,0,0,.10);
|
||||
}
|
||||
|
||||
.logo-img{
|
||||
width:100%;
|
||||
height:auto;
|
||||
border-radius: 12px;
|
||||
filter: drop-shadow(0 18px 42px rgba(0,0,0,.45));
|
||||
display:block;
|
||||
}
|
||||
|
||||
.glow{
|
||||
position:absolute; inset:auto;
|
||||
width:560px; height:560px;
|
||||
left: 50%; top: 46%;
|
||||
transform: translate(-50%,-50%);
|
||||
background:
|
||||
radial-gradient(circle at 35% 35%, rgba(56,227,141,.22), transparent 55%),
|
||||
radial-gradient(circle at 60% 55%, rgba(31,191,106,.14), transparent 60%);
|
||||
filter: blur(14px);
|
||||
z-index:-1;
|
||||
opacity:.95;
|
||||
pointer-events:none;
|
||||
}
|
||||
|
||||
.actions{
|
||||
margin-top:14px;
|
||||
display:flex;
|
||||
gap:10px;
|
||||
flex-wrap:wrap;
|
||||
}
|
||||
.btn{
|
||||
display:inline-flex;align-items:center;justify-content:center;gap:10px;
|
||||
text-decoration:none;
|
||||
padding:10px 12px;
|
||||
border-radius: 14px;
|
||||
border:1px solid var(--stroke);
|
||||
background: rgba(255,255,255,.04);
|
||||
color: rgba(255,255,255,.90);
|
||||
font-weight:700;
|
||||
font-size:13.5px;
|
||||
}
|
||||
.btn:hover{background: rgba(255,255,255,.07)}
|
||||
.btn-primary{
|
||||
border-color: rgba(56,227,141,.35);
|
||||
background: linear-gradient(180deg, rgba(56,227,141,.22), rgba(31,191,106,.10));
|
||||
box-shadow: 0 10px 30px rgba(31,191,106,.16);
|
||||
}
|
||||
|
||||
footer{
|
||||
margin-top:16px;
|
||||
padding:14px 16px;
|
||||
border-radius: var(--radius2);
|
||||
border:1px solid var(--stroke);
|
||||
background: rgba(255,255,255,.03);
|
||||
color: rgba(255,255,255,.68);
|
||||
font-size:13px;
|
||||
display:flex;gap:10px;flex-wrap:wrap;align-items:center;justify-content:space-between;
|
||||
}
|
||||
|
||||
.badge{
|
||||
display:inline-flex;align-items:center;gap:10px;
|
||||
padding:8px 10px;border-radius:999px;
|
||||
border:1px solid rgba(56,227,141,.22);
|
||||
background: rgba(56,227,141,.08);
|
||||
color: rgba(255,255,255,.80);
|
||||
}
|
||||
.mini{width:8px;height:8px;border-radius:999px;background:var(--accent);box-shadow:0 0 18px rgba(56,227,141,.35)}
|
||||
.fine a{color: rgba(255,255,255,.86); text-decoration:none; border-bottom:1px dashed rgba(255,255,255,.22)}
|
||||
.fine a:hover{border-bottom-color: rgba(56,227,141,.45)}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="grid" aria-hidden="true"></div>
|
||||
|
||||
<div class="wrap">
|
||||
<header>
|
||||
<div class="brand">
|
||||
<div class="brand-mark" aria-hidden="true">R</div>
|
||||
<div>
|
||||
<strong>Reveleant Software Solution</strong>
|
||||
<span>Note interne • Version corporate, humour contrôlé</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav aria-label="Navigation">
|
||||
<a class="pill" href="#discours">Discours</a>
|
||||
<a class="pill" href="#morale">Morale</a>
|
||||
<!-- Remplace si besoin -->
|
||||
<a class="cta" href="https://poudreverte.org/" target="_blank" rel="noopener noreferrer">Site de l’éditeur</a>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<section class="hero" id="discours" aria-label="Discours">
|
||||
<div class="panel left">
|
||||
<div class="kicker"><span class="pulse" aria-hidden="true"></span> Communication interne</div>
|
||||
|
||||
<h1>🎤 Le développeur, la direction… et le vent.</h1>
|
||||
<p class="lead">
|
||||
Ceci est une prise de parole officielle de l'équipe de développement,
|
||||
mais avec un bug volontaire dans le sarcasme. 🧑💻
|
||||
</p>
|
||||
|
||||
<hr/>
|
||||
|
||||
<div class="section">
|
||||
<h2>🧾 Promesses & livrables</h2>
|
||||
<p>
|
||||
La direction adore dire : <em>“On n’a pas beaucoup de budget, mais c’est un super projet humain.”</em><br/>
|
||||
Traduction : tu seras payé en <strong>expérience</strong>, en <strong>tickets</strong>, et en <strong>promesses compressées en ZIP</strong>.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<hr/>
|
||||
|
||||
<div class="section">
|
||||
<h2>🌬️ La direction</h2>
|
||||
<p>
|
||||
La direction, c’est un peu comme une API REST mal documentée :
|
||||
elle promet beaucoup, répond rarement,
|
||||
et quand elle répond, c’est un 200 OK avec un body vide.
|
||||
</p>
|
||||
<p>
|
||||
Les promesses, c’est un peu comme un site rempli de concepts : beaucoup d’air, peu de portance… 💨
|
||||
</p>
|
||||
<p>
|
||||
On nous vend du <strong>disruptif</strong>, de l’<strong>auto-scalable</strong>… et au final on déploie surtout :
|
||||
une VM trop petite et un développeur trop tard. ⚙️
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<hr/>
|
||||
|
||||
<div class="section" id="morale">
|
||||
<h2>✅ Morale</h2>
|
||||
<p>
|
||||
Nous aimons les rêves. Nous aimons l’ambition. Mais nous aimons aussi les choses testables :
|
||||
des specs claires, des dates réelles, et des promesses qui passent les tests unitaires. 🧪
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<a class="btn btn-primary" href="https://poudreverte.org/" target="_blank" rel="noopener noreferrer">Ouvrir le site éditeur ↗</a>
|
||||
<a class="btn" href="#discours">Revenir en haut</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel right" aria-label="Logo">
|
||||
<div class="glow" aria-hidden="true"></div>
|
||||
<div class="logo-wrap">
|
||||
<img class="logo-img" src="img/logo.png" alt="Logo Reveleant Software Solution" />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<footer>
|
||||
<div class="badge"><span class="mini" aria-hidden="true"></span>Corporate • Stable • (Version en bêta)</div>
|
||||
<div class="fine">
|
||||
© <strong>Reveleant Software Solution</strong> — <a href="https://poudreverte.org/" target="_blank" rel="noopener noreferrer">accéder au site</a>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user