add clickhouse

This commit is contained in:
mila 2026-06-04 21:27:32 +02:00
parent ca9f644bcc
commit 3bd38b6367
10 changed files with 188 additions and 0 deletions

View file

@ -86,3 +86,19 @@ func savePrometheusHandler(c *gin.Context) {
c.Redirect(http.StatusFound, c.Request.Referer())
}
func saveClickhouseHandler(c *gin.Context) {
conf.AppConfig.ClickhouseAddr = c.PostForm("addr")
conf.AppConfig.ClickhouseUser = c.PostForm("user")
conf.AppConfig.ClickhousePassword = c.PostForm("password")
conf.AppConfig.ClickhouseDB = c.PostForm("db")
conf.AppConfig.ClickhouseTable = c.PostForm("table")
enable := c.PostForm("enable")
conf.AppConfig.ClickhouseEnable = enable == "on"
conf.Write(conf.AppConfig)
c.Redirect(http.StatusFound, c.Request.Referer())
}

View file

@ -35,6 +35,7 @@ func Routes(router *gin.Engine) {
r0.POST("/config_settings/", saveSettingsHandler) // config.go
r0.POST("/config_influx/", saveInfluxHandler) // config.go
r0.POST("/config_prometheus/", savePrometheusHandler) // config.go
r0.POST("/config_clickhouse/", saveClickhouseHandler) // config.go
}
router.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))

View file

@ -0,0 +1,73 @@
package clickhouse
import (
"bytes"
"encoding/json"
"fmt"
"io"
"log/slog"
"net/http"
"net/url"
"time"
"github.com/aceberg/WatchYourLAN/internal/models"
)
// Global HTTP Client to enable Keep-Alive Connection-Reuse
var httpClient = &http.Client{
Timeout: 10 * time.Second,
}
// Add - write data to ClickHouse
func Add(appConfig models.Conf, oneHist models.Host) {
if appConfig.ClickhouseAddr == "" {
return
}
db := appConfig.ClickhouseDB
if db == "" {
db = "default"
}
table := appConfig.ClickhouseTable
if table == "" {
table = "watch_your_lan"
}
insertSQL := fmt.Sprintf("INSERT INTO %s.%s (ts, known, mac, iface, ip, name) FORMAT JSONEachRow", db, table)
jsonRow := map[string]any{
"ts": time.Now().UTC().Format("2006-01-02 15:04:05"),
"known": oneHist.Known,
"mac": oneHist.Mac,
"iface": oneHist.Iface,
"ip": oneHist.IP,
"name": oneHist.Name,
}
jsonData, err := json.Marshal(jsonRow)
if err != nil {
slog.Error("ClickHouse JSON error", "err", err)
return
}
reqURL := fmt.Sprintf("%s/?query=%s", appConfig.ClickhouseAddr, url.QueryEscape(insertSQL))
req, err := http.NewRequest(http.MethodPost, reqURL, bytes.NewBuffer(jsonData))
if err != nil {
slog.Error("ClickHouse Request error", "err", err)
return
}
req.Header.Set("X-ClickHouse-User", appConfig.ClickhouseUser)
req.Header.Set("X-ClickHouse-Key", appConfig.ClickhousePassword)
res, err := httpClient.Do(req)
if err != nil {
slog.Error("ClickHouse network error", "err", err)
return
}
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
if res.StatusCode != http.StatusOK {
slog.Error("ClickHouse HTTP error", "status", res.StatusCode, "body", string(body))
}
}

View file

@ -31,6 +31,8 @@ func read(path string) (config models.Conf) {
viper.SetDefault("PROMETHEUS_ENABLE", false)
viper.SetDefault("CLICKHOUSE_ENABLE", false)
viper.SetConfigFile(path)
viper.SetConfigType("yaml")
err := viper.ReadInConfig()
@ -63,6 +65,13 @@ func read(path string) (config models.Conf) {
config.PrometheusEnable = viper.GetBool("PROMETHEUS_ENABLE")
config.ClickhouseEnable = viper.GetBool("CLICKHOUSE_ENABLE")
config.ClickhouseAddr, _ = viper.Get("CLICKHOUSE_ADDR").(string)
config.ClickhouseUser, _ = viper.Get("CLICKHOUSE_USER").(string)
config.ClickhousePassword, _ = viper.Get("CLICKHOUSE_PASSWORD").(string)
config.ClickhouseDB, _ = viper.Get("CLICKHOUSE_DB").(string)
config.ClickhouseTable, _ = viper.Get("CLICKHOUSE_TABLE").(string)
joined := viper.Get("ARP_STRS_JOINED").(string)
// slog.Info("ARP_STRS_JOINED: " + joined)

View file

@ -43,6 +43,13 @@ func Write(config models.Conf) {
viper.Set("PROMETHEUS_ENABLE", config.PrometheusEnable)
viper.Set("clickhouse_enable", config.ClickhouseEnable)
viper.Set("clickhouse_addr", config.ClickhouseAddr)
viper.Set("clickhouse_user", config.ClickhouseUser)
viper.Set("clickhouse_password", config.ClickhousePassword)
viper.Set("clickhouse_db", config.ClickhouseDB)
viper.Set("clickhouse_table", config.ClickhouseTable)
err := viper.WriteConfig()
check.IfError(err)
}

View file

@ -30,6 +30,13 @@ type Conf struct {
InfluxSkipTLS bool
// Prometheus
PrometheusEnable bool
// Clickhouse
ClickhouseEnable bool
ClickhouseAddr string
ClickhouseUser string
ClickhousePassword string
ClickhouseDB string
ClickhouseTable string
}
// Host - one host

View file

@ -5,6 +5,7 @@ import (
"github.com/aceberg/WatchYourLAN/internal/arp"
"github.com/aceberg/WatchYourLAN/internal/check"
"github.com/aceberg/WatchYourLAN/internal/clickhouse"
"github.com/aceberg/WatchYourLAN/internal/conf"
"github.com/aceberg/WatchYourLAN/internal/gdb"
"github.com/aceberg/WatchYourLAN/internal/influx"
@ -79,6 +80,9 @@ func compareHosts(foundHostsMap map[string]models.Host) {
if conf.AppConfig.PrometheusEnable {
prometheus.Add(aHost)
}
if conf.AppConfig.ClickhouseEnable {
clickhouse.Add(conf.AppConfig, aHost)
}
}
for _, fHost := range foundHostsMap {

View file

@ -0,0 +1,54 @@
import { apiPath } from "../../functions/api"
import { appConfig } from "../../functions/exports"
function Clickhouse() {
return (
<div class="card border-primary">
<div class="card-header">ClickHouse config</div>
<div class="card-body table-responsive">
<form action={apiPath + '/api/config_clickhouse/'} method="post">
<table class="table table-borderless"><tbody>
<tr>
<td>Enable</td>
<td>
<div class="form-check form-switch">
{appConfig().ClickhouseEnable
? <input class="form-check-input" type="checkbox" name="enable" checked></input>
: <input class="form-check-input" type="checkbox" name="enable"></input>
}
</div>
</td>
</tr>
<tr>
<td>Address</td>
<td><input name="addr" type="text" class="form-control" value={appConfig().ClickhouseAddr}></input></td>
</tr>
<tr>
<td>User</td>
<td><input name="user" type="text" class="form-control" value={appConfig().ClickhouseUser}></input></td>
</tr>
<tr>
<td>Password</td>
<td><input name="password" type="password" class="form-control" value={appConfig().ClickhousePassword}></input></td>
</tr>
<tr>
<td>Database</td>
<td><input name="db" type="text" class="form-control" value={appConfig().ClickhouseDB}></input></td>
</tr>
<tr>
<td>Table</td>
<td><input name="table" type="text" class="form-control" value={appConfig().ClickhouseTable}></input></td>
</tr>
<tr>
<td><button type="submit" class="btn btn-primary">Save</button></td>
<td></td>
</tr>
</tbody></table>
</form>
</div>
</div>
)
}
export default Clickhouse

View file

@ -39,6 +39,13 @@ export interface Conf {
InfluxSkipTLS: boolean;
// Prometheus
PrometheusEnable: boolean;
// Clickhouse
ClickhouseEnable: boolean;
ClickhouseAddr: string;
ClickhouseUser: string;
ClickhousePassword: string;
ClickhouseDB: string;
ClickhouseTable: string;
};
export const emptyHost:Host = {
@ -77,6 +84,12 @@ export const emptyConf:Conf = {
InfluxBucket: "",
InfluxSkipTLS: false,
PrometheusEnable: false,
ClickhouseEnable: false,
ClickhouseAddr: "",
ClickhouseUser: "",
ClickhousePassword: "",
ClickhouseDB: "",
ClickhouseTable: "",
};
export const [allHosts, setAllHosts] = createStore<Host[]>([]);

View file

@ -1,5 +1,6 @@
import About from "../components/Config/About"
import Basic from "../components/Config/Basic"
import Clickhouse from "../components/Config/Clickhouse"
import Donate from "../components/Config/Donate"
import Influx from "../components/Config/Influx"
import Prometheus from "../components/Config/Prometheus"
@ -27,6 +28,9 @@ function Config() {
<div class="mt-4">
<Prometheus></Prometheus>
</div>
<div class="mt-4">
<Clickhouse></Clickhouse>
</div>
<div class="mt-4">
<About></About>
</div>