引入lumberjack和fluentbit自动分发
This commit is contained in:
@@ -34,7 +34,7 @@ function build() {
|
||||
done
|
||||
|
||||
VERSION=$(lookup-version "$ROOT"/../internal/const/const.go)
|
||||
# 生成 zip 文件名时不包含 plus 标记
|
||||
# 鐢熸垚 zip 鏂囦欢鍚嶆椂涓嶅寘鍚?plus 鏍囪
|
||||
if [ "${TAG}" = "plus" ]; then
|
||||
ZIP="${NAME}-${OS}-${ARCH}-v${VERSION}.zip"
|
||||
else
|
||||
@@ -97,7 +97,7 @@ function build() {
|
||||
done
|
||||
fi
|
||||
|
||||
# 查找 EdgeAPI zip 文件时不包含 plus 标记
|
||||
# 鏌ユ壘 EdgeAPI zip 鏂囦欢鏃朵笉鍖呭惈 plus 鏍囪
|
||||
if [ "${TAG}" = "plus" ]; then
|
||||
EDGE_API_ZIP_FILE=$ROOT"/../../EdgeAPI/dist/edge-api-${OS}-${ARCH}-v${APINodeVersion}.zip"
|
||||
else
|
||||
@@ -107,7 +107,44 @@ function build() {
|
||||
cd "$DIST"/ || exit
|
||||
unzip -q "$(basename "$EDGE_API_ZIP_FILE")"
|
||||
rm -f "$(basename "$EDGE_API_ZIP_FILE")"
|
||||
# 删除 MaxMind 数据库文件(使用嵌入的数据库,不需要外部文件)
|
||||
|
||||
# ensure edge-api package always contains fluent-bit templates/packages
|
||||
FLUENT_ROOT="$ROOT/../../deploy/fluent-bit"
|
||||
FLUENT_DIST="$DIST/edge-api/deploy/fluent-bit"
|
||||
if [ -d "$FLUENT_ROOT" ]; then
|
||||
verify_fluent_bit_package_matrix "$FLUENT_ROOT" "$ARCH" || exit 1
|
||||
rm -rf "$FLUENT_DIST"
|
||||
mkdir -p "$FLUENT_DIST"
|
||||
|
||||
FLUENT_FILES=(
|
||||
"fluent-bit.conf"
|
||||
"fluent-bit-dns.conf"
|
||||
"fluent-bit-https.conf"
|
||||
"fluent-bit-dns-https.conf"
|
||||
"fluent-bit-windows.conf"
|
||||
"fluent-bit-windows-https.conf"
|
||||
"parsers.conf"
|
||||
"clickhouse-upstream.conf"
|
||||
"clickhouse-upstream-windows.conf"
|
||||
"logrotate.conf"
|
||||
"README.md"
|
||||
)
|
||||
for file in "${FLUENT_FILES[@]}"; do
|
||||
if [ -f "$FLUENT_ROOT/$file" ]; then
|
||||
cp "$FLUENT_ROOT/$file" "$FLUENT_DIST/"
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -d "$FLUENT_ROOT/packages" ]; then
|
||||
cp -R "$FLUENT_ROOT/packages" "$FLUENT_DIST/"
|
||||
fi
|
||||
|
||||
rm -f "$FLUENT_DIST/.gitignore"
|
||||
rm -f "$FLUENT_DIST"/logs.db*
|
||||
rm -rf "$FLUENT_DIST/storage"
|
||||
fi
|
||||
|
||||
# 鍒犻櫎 MaxMind 鏁版嵁搴撴枃浠讹紙浣跨敤宓屽叆鐨勬暟鎹簱锛屼笉闇€瑕佸閮ㄦ枃浠讹級
|
||||
find . -name "*.mmdb" -type f -delete
|
||||
find . -type d -name "iplibrary" -empty -delete
|
||||
cd - || exit
|
||||
@@ -150,7 +187,7 @@ function build() {
|
||||
#find "$DIST" -name "*.css.map" -delete
|
||||
#find "$DIST" -name "*.js.map" -delete
|
||||
|
||||
# 删除 MaxMind 数据库文件(使用嵌入的数据库,不需要外部文件)
|
||||
# 鍒犻櫎 MaxMind 鏁版嵁搴撴枃浠讹紙浣跨敤宓屽叆鐨勬暟鎹簱锛屼笉闇€瑕佸閮ㄦ枃浠讹級
|
||||
find "$DIST" -name "*.mmdb" -type f -delete
|
||||
find "$DIST" -type d -name "iplibrary" -empty -delete
|
||||
|
||||
@@ -167,6 +204,39 @@ function build() {
|
||||
echo "[done]"
|
||||
}
|
||||
|
||||
function verify_fluent_bit_package_matrix() {
|
||||
FLUENT_ROOT=$1
|
||||
ARCH=$2
|
||||
REQUIRED_FILES=()
|
||||
if [ "$ARCH" = "amd64" ]; then
|
||||
REQUIRED_FILES=(
|
||||
"packages/linux-amd64/fluent-bit_4.2.2_amd64.deb"
|
||||
"packages/linux-amd64/fluent-bit-4.2.2-1.x86_64.rpm"
|
||||
)
|
||||
elif [ "$ARCH" = "arm64" ]; then
|
||||
REQUIRED_FILES=(
|
||||
"packages/linux-arm64/fluent-bit_4.2.2_arm64.deb"
|
||||
"packages/linux-arm64/fluent-bit-4.2.2-1.aarch64.rpm"
|
||||
)
|
||||
else
|
||||
echo "[error] unsupported arch for fluent-bit package validation: $ARCH"
|
||||
return 1
|
||||
fi
|
||||
|
||||
MISSING=0
|
||||
for FILE in "${REQUIRED_FILES[@]}"; do
|
||||
if [ ! -f "$FLUENT_ROOT/$FILE" ]; then
|
||||
echo "[error] fluent-bit matrix package missing: $FLUENT_ROOT/$FILE"
|
||||
MISSING=1
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$MISSING" -ne 0 ]; then
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
function lookup-version() {
|
||||
FILE=$1
|
||||
VERSION_DATA=$(cat "$FILE")
|
||||
|
||||
@@ -3,13 +3,18 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const clickhouseConfigCode = "clickhouseConfig"
|
||||
@@ -29,29 +34,37 @@ func (this *ClickHouseAction) RunGet(params struct{}) {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
cfg := &systemconfigs.ClickHouseSetting{Port: 8123, Database: "default", Scheme: "http"}
|
||||
cfg := &systemconfigs.ClickHouseSetting{Port: 8443, Database: "default", Scheme: "https"}
|
||||
if len(resp.ValueJSON) > 0 {
|
||||
_ = json.Unmarshal(resp.ValueJSON, cfg)
|
||||
}
|
||||
if cfg.Port <= 0 {
|
||||
cfg.Port = 8123
|
||||
cfg.Port = 8443
|
||||
}
|
||||
if cfg.Database == "" {
|
||||
cfg.Database = "default"
|
||||
}
|
||||
if strings.TrimSpace(cfg.Scheme) == "" {
|
||||
cfg.Scheme = "http"
|
||||
cfg.Scheme = "https"
|
||||
}
|
||||
this.Data["config"] = map[string]interface{}{
|
||||
"host": cfg.Host,
|
||||
"port": cfg.Port,
|
||||
"user": cfg.User,
|
||||
"password": cfg.Password,
|
||||
"database": cfg.Database,
|
||||
"scheme": cfg.Scheme,
|
||||
"tlsSkipVerify": cfg.TLSSkipVerify,
|
||||
"tlsServerName": cfg.TLSServerName,
|
||||
"host": cfg.Host,
|
||||
"port": cfg.Port,
|
||||
"user": cfg.User,
|
||||
"password": cfg.Password,
|
||||
"database": cfg.Database,
|
||||
"scheme": cfg.Scheme,
|
||||
}
|
||||
|
||||
// 自动检测连接状态
|
||||
connStatus := "unconfigured" // unconfigured / connected / disconnected
|
||||
connError := ""
|
||||
if strings.TrimSpace(cfg.Host) != "" {
|
||||
connStatus, connError = this.probeClickHouse(cfg)
|
||||
}
|
||||
this.Data["connStatus"] = connStatus
|
||||
this.Data["connError"] = connError
|
||||
|
||||
this.Show()
|
||||
}
|
||||
|
||||
@@ -62,20 +75,18 @@ func (this *ClickHouseAction) RunPost(params struct {
|
||||
Password string
|
||||
Database string
|
||||
Scheme string
|
||||
TLSSkipVerify bool
|
||||
TLSServerName string
|
||||
|
||||
Must *actions.Must
|
||||
}) {
|
||||
defer this.CreateLogInfo(codes.DBNode_LogUpdateDBNode, 0)
|
||||
if params.Port <= 0 {
|
||||
params.Port = 8123
|
||||
}
|
||||
if params.Database == "" {
|
||||
params.Database = "default"
|
||||
}
|
||||
if params.Scheme != "https" {
|
||||
params.Scheme = "http"
|
||||
if params.Scheme != "http" {
|
||||
params.Scheme = "https"
|
||||
}
|
||||
if params.Port <= 0 {
|
||||
params.Port = 8443
|
||||
}
|
||||
password := params.Password
|
||||
if password == "" {
|
||||
@@ -94,8 +105,8 @@ func (this *ClickHouseAction) RunPost(params struct {
|
||||
Password: password,
|
||||
Database: params.Database,
|
||||
Scheme: params.Scheme,
|
||||
TLSSkipVerify: params.TLSSkipVerify,
|
||||
TLSServerName: strings.TrimSpace(params.TLSServerName),
|
||||
TLSSkipVerify: true,
|
||||
TLSServerName: "",
|
||||
}
|
||||
valueJSON, err := json.Marshal(cfg)
|
||||
if err != nil {
|
||||
@@ -112,3 +123,51 @@ func (this *ClickHouseAction) RunPost(params struct {
|
||||
}
|
||||
this.Success()
|
||||
}
|
||||
|
||||
// probeClickHouse 快速检测 ClickHouse 连接状态(SELECT 1)
|
||||
func (this *ClickHouseAction) probeClickHouse(cfg *systemconfigs.ClickHouseSetting) (status string, errMsg string) {
|
||||
scheme := strings.ToLower(strings.TrimSpace(cfg.Scheme))
|
||||
if scheme == "" {
|
||||
scheme = "https"
|
||||
}
|
||||
port := cfg.Port
|
||||
if port <= 0 {
|
||||
port = 8443
|
||||
}
|
||||
db := cfg.Database
|
||||
if db == "" {
|
||||
db = "default"
|
||||
}
|
||||
|
||||
testURL := fmt.Sprintf("%s://%s:%d/?query=SELECT+1&database=%s", scheme, cfg.Host, port, db)
|
||||
|
||||
transport := &http.Transport{}
|
||||
if scheme == "https" {
|
||||
transport.TLSClientConfig = &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
}
|
||||
}
|
||||
client := &http.Client{
|
||||
Timeout: 3 * time.Second,
|
||||
Transport: transport,
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, testURL, nil)
|
||||
if err != nil {
|
||||
return "disconnected", err.Error()
|
||||
}
|
||||
if cfg.User != "" || cfg.Password != "" {
|
||||
req.SetBasicAuth(cfg.User, cfg.Password)
|
||||
}
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return "disconnected", err.Error()
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return "disconnected", fmt.Sprintf("HTTP %d", resp.StatusCode)
|
||||
}
|
||||
return "connected", ""
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ func (this *ClickHouseAction) Init() {
|
||||
func (this *ClickHouseAction) RunGet(params struct{}) {
|
||||
this.Data["mainTab"] = "clickhouse"
|
||||
this.Data["config"] = map[string]interface{}{
|
||||
"host": "", "port": 8123, "user": "", "password": "", "database": "default",
|
||||
"host": "", "port": 8443, "user": "", "password": "", "database": "default", "scheme": "https",
|
||||
}
|
||||
this.Show()
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ func init() {
|
||||
Get("/logs", new(LogsAction)).
|
||||
Post("/status", new(StatusAction)).
|
||||
GetPost("/clickhouse", new(ClickHouseAction)).
|
||||
Post("/testClickhouse", new(TestClickHouseAction)).
|
||||
EndAll()
|
||||
})
|
||||
}
|
||||
|
||||
92
EdgeAdmin/internal/web/actions/default/db/testClickhouse.go
Normal file
92
EdgeAdmin/internal/web/actions/default/db/testClickhouse.go
Normal file
@@ -0,0 +1,92 @@
|
||||
//go:build plus
|
||||
|
||||
package db
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
)
|
||||
|
||||
type TestClickHouseAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *TestClickHouseAction) Init() {
|
||||
this.Nav("db", "db", "clickhouse")
|
||||
}
|
||||
|
||||
func (this *TestClickHouseAction) RunPost(params struct {
|
||||
Host string
|
||||
Port int
|
||||
User string
|
||||
Password string
|
||||
Database string
|
||||
Scheme string
|
||||
|
||||
Must *actions.Must
|
||||
}) {
|
||||
params.Must.
|
||||
Field("host", params.Host).
|
||||
Require("请输入 ClickHouse 连接地址")
|
||||
|
||||
if params.Database == "" {
|
||||
params.Database = "default"
|
||||
}
|
||||
scheme := "https"
|
||||
if strings.EqualFold(params.Scheme, "http") {
|
||||
scheme = "http"
|
||||
}
|
||||
if params.Port <= 0 {
|
||||
params.Port = 8443
|
||||
}
|
||||
|
||||
// 构造测试请求
|
||||
testURL := fmt.Sprintf("%s://%s:%d/?query=SELECT+1&database=%s",
|
||||
scheme, params.Host, params.Port, params.Database)
|
||||
|
||||
transport := &http.Transport{}
|
||||
if scheme == "https" {
|
||||
transport.TLSClientConfig = &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
}
|
||||
}
|
||||
client := &http.Client{
|
||||
Timeout: 5 * time.Second,
|
||||
Transport: transport,
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, testURL, nil)
|
||||
if err != nil {
|
||||
this.Fail("请求构造失败: " + err.Error())
|
||||
return
|
||||
}
|
||||
if params.User != "" || params.Password != "" {
|
||||
req.SetBasicAuth(params.User, params.Password)
|
||||
}
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
this.Fail("连接失败: " + err.Error())
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
msg := strings.TrimSpace(string(body))
|
||||
if len(msg) > 200 {
|
||||
msg = msg[:200] + "..."
|
||||
}
|
||||
this.Fail(fmt.Sprintf("ClickHouse 返回 HTTP %d: %s", resp.StatusCode, msg))
|
||||
return
|
||||
}
|
||||
|
||||
this.Success()
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
//go:build !plus
|
||||
|
||||
package db
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
)
|
||||
|
||||
type TestClickHouseAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *TestClickHouseAction) Init() {
|
||||
this.Nav("db", "db", "clickhouse")
|
||||
}
|
||||
|
||||
func (this *TestClickHouseAction) RunPost(params struct {
|
||||
Host string
|
||||
Must *actions.Must
|
||||
}) {
|
||||
this.Fail("请使用商业版以测试 ClickHouse 连接")
|
||||
}
|
||||
@@ -10422,9 +10422,7 @@ Vue.component("http-access-log-box", {
|
||||
<div>
|
||||
<a v-if="accessLog.node != null && accessLog.node.nodeCluster != null" :href="'/clusters/cluster/node?nodeId=' + accessLog.node.id + '&clusterId=' + accessLog.node.nodeCluster.id" title="点击查看节点详情" target="_top"><span class="grey">[{{accessLog.node.name}}<span v-if="!accessLog.node.name.endsWith('节点')">节点</span>]</span></a>
|
||||
|
||||
<!-- 网站 -->
|
||||
<a :href="'/servers/server/log?serverId=' + accessLog.serverId" title="点击到网站" v-if="vShowServerLink && accessLog.serverId > 0"><span class="grey">[网站]</span></a>
|
||||
<span v-if="vShowServerLink && (accessLog.serverId == null || accessLog.serverId == 0)" @click.prevent="mismatch()"><span class="disabled">[网站]</span></span>
|
||||
|
||||
|
||||
<span v-if="accessLog.region != null && accessLog.region.length > 0" class="grey"><ip-box :v-ip="accessLog.remoteAddr">[{{accessLog.region}}]</ip-box></span>
|
||||
<ip-box><keyword :v-word="vKeyword">{{accessLog.remoteAddr}}</keyword></ip-box> [{{accessLog.timeLocal}}] <em>"<keyword :v-word="vKeyword">{{accessLog.requestMethod}}</keyword> {{accessLog.scheme}}://<keyword :v-word="vKeyword">{{accessLog.host}}</keyword><keyword :v-word="vKeyword">{{accessLog.requestURI}}</keyword> <a :href="accessLog.scheme + '://' + accessLog.host + accessLog.requestURI" target="_blank" title="新窗口打开" class="disabled"><i class="external icon tiny"></i> </a> {{accessLog.proto}}" </em> <keyword :v-word="vKeyword">{{accessLog.status}}</keyword>
|
||||
@@ -10452,6 +10450,7 @@ Vue.component("http-access-log-box", {
|
||||
</span>
|
||||
|
||||
<span v-if="accessLog.requestTime != null"> - 耗时:{{formatCost(accessLog.requestTime)}} ms </span><span v-if="accessLog.humanTime != null && accessLog.humanTime.length > 0" class="grey small"> ({{accessLog.humanTime}})</span>
|
||||
<a :href="'/servers/server/log?serverId=' + accessLog.serverId" title="仅看此网站日志" v-if="vShowServerLink && accessLog.serverId > 0" class="ui label tiny blue basic" style="font-weight: normal; margin-left: 0.5em; padding: 2px 5px !important">网站</a>
|
||||
<a href="" @click.prevent="showLog" title="查看详情"><i class="icon expand"></i></a>
|
||||
</div>
|
||||
</div>`
|
||||
|
||||
@@ -10422,9 +10422,7 @@ Vue.component("http-access-log-box", {
|
||||
<div>
|
||||
<a v-if="accessLog.node != null && accessLog.node.nodeCluster != null" :href="'/clusters/cluster/node?nodeId=' + accessLog.node.id + '&clusterId=' + accessLog.node.nodeCluster.id" title="点击查看节点详情" target="_top"><span class="grey">[{{accessLog.node.name}}<span v-if="!accessLog.node.name.endsWith('节点')">节点</span>]</span></a>
|
||||
|
||||
<!-- 网站 -->
|
||||
<a :href="'/servers/server/log?serverId=' + accessLog.serverId" title="点击到网站" v-if="vShowServerLink && accessLog.serverId > 0"><span class="grey">[网站]</span></a>
|
||||
<span v-if="vShowServerLink && (accessLog.serverId == null || accessLog.serverId == 0)" @click.prevent="mismatch()"><span class="disabled">[网站]</span></span>
|
||||
|
||||
|
||||
<span v-if="accessLog.region != null && accessLog.region.length > 0" class="grey"><ip-box :v-ip="accessLog.remoteAddr">[{{accessLog.region}}]</ip-box></span>
|
||||
<ip-box><keyword :v-word="vKeyword">{{accessLog.remoteAddr}}</keyword></ip-box> [{{accessLog.timeLocal}}] <em>"<keyword :v-word="vKeyword">{{accessLog.requestMethod}}</keyword> {{accessLog.scheme}}://<keyword :v-word="vKeyword">{{accessLog.host}}</keyword><keyword :v-word="vKeyword">{{accessLog.requestURI}}</keyword> <a :href="accessLog.scheme + '://' + accessLog.host + accessLog.requestURI" target="_blank" title="新窗口打开" class="disabled"><i class="external icon tiny"></i> </a> {{accessLog.proto}}" </em> <keyword :v-word="vKeyword">{{accessLog.status}}</keyword>
|
||||
@@ -10452,6 +10450,7 @@ Vue.component("http-access-log-box", {
|
||||
</span>
|
||||
|
||||
<span v-if="accessLog.requestTime != null"> - 耗时:{{formatCost(accessLog.requestTime)}} ms </span><span v-if="accessLog.humanTime != null && accessLog.humanTime.length > 0" class="grey small"> ({{accessLog.humanTime}})</span>
|
||||
<a :href="'/servers/server/log?serverId=' + accessLog.serverId" title="仅看此网站日志" v-if="vShowServerLink && accessLog.serverId > 0" class="ui label tiny blue basic" style="font-weight: normal; margin-left: 0.5em; padding: 2px 5px !important">网站</a>
|
||||
<a href="" @click.prevent="showLog" title="查看详情"><i class="icon expand"></i></a>
|
||||
</div>
|
||||
</div>`
|
||||
|
||||
@@ -1,70 +1,76 @@
|
||||
{$layout}
|
||||
{$template "menu"}
|
||||
|
||||
<h3>ClickHouse 配置</h3>
|
||||
<p class="comment">用于访问日志列表查询(logs_ingest 表)。配置后,访问日志列表将优先从 ClickHouse 读取;不配置则仅从 MySQL 读取。留空表示不使用 ClickHouse。</p>
|
||||
<div style="display:flex;align-items:baseline;gap:0.8em;margin-bottom:0.5em">
|
||||
<h3 style="margin:0">ClickHouse 配置</h3>
|
||||
<span v-if="connStatus === 'connected'" class="ui label green tiny" style="vertical-align:baseline">
|
||||
<i class="icon circle" style="margin-right:0.25em"></i>已连接
|
||||
</span>
|
||||
<span v-else-if="connStatus === 'disconnected'" class="ui label red tiny" style="vertical-align:baseline"
|
||||
:title="connError">
|
||||
<i class="icon circle" style="margin-right:0.25em"></i>已断开
|
||||
</span>
|
||||
<span v-else class="ui label grey tiny" style="vertical-align:baseline">
|
||||
<i class="icon circle outline" style="margin-right:0.25em"></i>未配置
|
||||
</span>
|
||||
</div>
|
||||
<p class="comment">配置后,需要在网站列表-日志策略中创建"文件+ClickHouse"的策略,才能将访问日志绕过api组件直接由边缘节点发送给ClickHouse。</p>
|
||||
|
||||
<form method="post" class="ui form" data-tea-action="$" data-tea-success="success" @submit.prevent="onSubmit">
|
||||
<form method="post" class="ui form" @submit.prevent="onSubmit">
|
||||
<csrf-token></csrf-token>
|
||||
<table class="ui table definition selectable">
|
||||
<tr>
|
||||
<td class="title">连接地址(Host)</td>
|
||||
<td>
|
||||
<input type="text" name="host" maxlength="200" ref="focus" placeholder="如 127.0.0.1 或 clickhouse.example.com" :value="config.host"/>
|
||||
<input type="text" name="host" maxlength="200" ref="focus"
|
||||
placeholder="如 127.0.0.1 或 clickhouse.example.com" v-model="form.host" />
|
||||
<p class="comment">ClickHouse 服务器地址。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>协议(Scheme)</td>
|
||||
<td>
|
||||
<select name="scheme" class="ui dropdown auto-width">
|
||||
<option value="http" :selected="config.scheme != 'https'">HTTP</option>
|
||||
<option value="https" :selected="config.scheme == 'https'">HTTPS</option>
|
||||
<select name="scheme" class="ui dropdown auto-width" v-model="form.scheme">
|
||||
<option value="http">HTTP</option>
|
||||
<option value="https">HTTPS</option>
|
||||
</select>
|
||||
<p class="comment">默认 HTTP;选择 HTTPS 时将启用 TLS 连接。</p>
|
||||
<p class="comment">默认 HTTPS;当前后台固定跳过证书校验。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>端口(Port)</td>
|
||||
<td>
|
||||
<input type="number" name="port" min="1" max="65535" style="width:6em" :value="config.port"/>
|
||||
<p class="comment">接口端口,HTTP 默认 8123,HTTPS 常用 8443(以你的 ClickHouse 实际配置为准)。</p>
|
||||
<input type="number" name="port" min="1" max="65535" style="width:6em" v-model.number="form.port" />
|
||||
<p class="comment">接口端口默认 8443(HTTPS);请与 ClickHouse 实际开放端口保持一致。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>用户名(User)</td>
|
||||
<td>
|
||||
<input type="text" name="user" maxlength="100" :value="config.user"/>
|
||||
<input type="text" name="user" maxlength="100" v-model="form.user" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>密码(Password)</td>
|
||||
<td>
|
||||
<input type="password" name="password" maxlength="200" placeholder="不修改请留空" value=""/>
|
||||
<input type="password" name="password" maxlength="200" placeholder="不修改请留空" v-model="form.password" />
|
||||
<p class="comment">留空则不修改已保存的密码。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>TLS 跳过证书校验</td>
|
||||
<td>
|
||||
<checkbox name="tlsSkipVerify" value="1" :checked="config.tlsSkipVerify"></checkbox>
|
||||
<p class="comment">仅测试环境建议开启;生产建议关闭并使用受信任证书。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>TLS Server Name</td>
|
||||
<td>
|
||||
<input type="text" name="tlsServerName" maxlength="200" placeholder="可选:证书校验域名(SNI)" :value="config.tlsServerName"/>
|
||||
<p class="comment">可选;当 ClickHouse 证书域名与连接 Host 不一致时使用。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>数据库名(Database)</td>
|
||||
<td>
|
||||
<input type="text" name="database" maxlength="100" placeholder="default" :value="config.database"/>
|
||||
<input type="text" name="database" maxlength="100" placeholder="default" v-model="form.database" />
|
||||
<p class="comment">logs_ingest 表所在库,默认 default。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<submit-btn></submit-btn>
|
||||
</form>
|
||||
<div style="display:flex; align-items:center; gap:0.5em; margin-top:1em">
|
||||
<submit-btn></submit-btn>
|
||||
<button type="button" class="ui button basic blue" @click="testConnection" :disabled="isTesting">
|
||||
<i :class="isTesting ? 'icon spinner loading' : 'icon plug'"></i> 测试连接
|
||||
</button>
|
||||
<span v-if="testResult"
|
||||
:style="{color: testOk ? '#21ba45' : '#db2828', fontWeight:'bold'}">{{testResult}}</span>
|
||||
</div>
|
||||
</form>
|
||||
@@ -1,4 +1,22 @@
|
||||
Tea.context(function () {
|
||||
var config = this.config || {}
|
||||
this.form = {
|
||||
host: config.host || "",
|
||||
scheme: config.scheme || "https",
|
||||
port: config.port > 0 ? config.port : 8443,
|
||||
user: config.user || "",
|
||||
password: "",
|
||||
database: config.database || "default",
|
||||
}
|
||||
|
||||
this.isTesting = false
|
||||
this.testResult = ""
|
||||
this.testOk = false
|
||||
|
||||
// 页面加载时的连接状态(后端自动检测)
|
||||
this.connStatus = this.connStatus || "unconfigured"
|
||||
this.connError = this.connError || ""
|
||||
|
||||
this.success = function () {
|
||||
teaweb.success("保存成功")
|
||||
}
|
||||
@@ -9,4 +27,50 @@ Tea.context(function () {
|
||||
Tea.Vue.success()
|
||||
})
|
||||
}
|
||||
this.testConnection = function () {
|
||||
var that = Tea.Vue
|
||||
that.isTesting = true
|
||||
that.testResult = ""
|
||||
|
||||
var form = document.querySelector("form")
|
||||
var fd = new FormData(form)
|
||||
fd.set("host", that.form.host || "")
|
||||
fd.set("scheme", that.form.scheme || "https")
|
||||
fd.set("port", String(that.form.port > 0 ? that.form.port : 8443))
|
||||
fd.set("user", that.form.user || "")
|
||||
fd.set("password", that.form.password || "")
|
||||
fd.set("database", that.form.database || "default")
|
||||
|
||||
var xhr = new XMLHttpRequest()
|
||||
xhr.open("POST", Tea.url("/db/testClickhouse"), true)
|
||||
xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest")
|
||||
xhr.timeout = 10000
|
||||
xhr.onload = function () {
|
||||
that.isTesting = false
|
||||
try {
|
||||
var resp = JSON.parse(xhr.responseText)
|
||||
if (resp.code === 200) {
|
||||
that.testOk = true
|
||||
that.testResult = "✅ 连接成功"
|
||||
} else {
|
||||
that.testOk = false
|
||||
that.testResult = "❌ " + (resp.message || "连接失败")
|
||||
}
|
||||
} catch (e) {
|
||||
that.testOk = false
|
||||
that.testResult = "❌ 响应解析失败"
|
||||
}
|
||||
}
|
||||
xhr.onerror = function () {
|
||||
that.isTesting = false
|
||||
that.testOk = false
|
||||
that.testResult = "❌ 网络请求失败"
|
||||
}
|
||||
xhr.ontimeout = function () {
|
||||
that.isTesting = false
|
||||
that.testOk = false
|
||||
that.testResult = "❌ 请求超时"
|
||||
}
|
||||
xhr.send(fd)
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user