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