diff --git a/backend/controllers/system.go b/backend/controllers/system.go
index 6d83b89..fa615be 100644
--- a/backend/controllers/system.go
+++ b/backend/controllers/system.go
@@ -2,6 +2,7 @@ package controllers
import (
"fmt"
+ "lokistack/pkg/metrics"
"time"
"github.com/gin-gonic/gin"
@@ -108,3 +109,8 @@ func (ctrl *SystemController) SaveRetention(c *gin.Context) {
func (ctrl *SystemController) Health(c *gin.Context) {
utils.Success(c, services.SelfCheck())
}
+
+// MetricsSummary 平台自监控快照(仅 admin;原始 Prometheus 文本在 /api/v1/metrics)
+func (ctrl *SystemController) MetricsSummary(c *gin.Context) {
+ utils.Success(c, metrics.Snapshot())
+}
diff --git a/backend/pkg/metrics/summary.go b/backend/pkg/metrics/summary.go
new file mode 100644
index 0000000..4dc2fa2
--- /dev/null
+++ b/backend/pkg/metrics/summary.go
@@ -0,0 +1,106 @@
+package metrics
+
+import (
+ "sort"
+
+ dto "github.com/prometheus/client_model/go"
+)
+
+// PathCount 单个路由模板的累计请求数
+type PathCount struct {
+ Path string `json:"path"`
+ Count float64 `json:"count"`
+}
+
+// Summary 面板自监控快照:从 registry 聚合出的精简结构(/api/v1/system/metrics-summary)
+type Summary struct {
+ HTTP struct {
+ Total float64 `json:"total"`
+ ByStatus map[string]float64 `json:"byStatus"`
+ InFlight float64 `json:"inFlight"`
+ TopPaths []PathCount `json:"topPaths"`
+ } `json:"http"`
+ DB struct {
+ SizeBytes float64 `json:"sizeBytes"`
+ Saves float64 `json:"saves"`
+ SaveFailures float64 `json:"saveFailures"`
+ Records map[string]float64 `json:"records"`
+ } `json:"db"`
+ Alerts struct {
+ Evaluations map[string]float64 `json:"evaluations"`
+ } `json:"alerts"`
+ Loki struct {
+ Queries map[string]float64 `json:"queries"`
+ } `json:"loki"`
+ Runtime struct {
+ Goroutines float64 `json:"goroutines"`
+ GoVersion string `json:"goVersion"`
+ Version string `json:"version"`
+ } `json:"runtime"`
+}
+
+// Snapshot 聚合当前指标为面板结构(聚合失败返回零值,监控页降级展示)
+func Snapshot() Summary {
+ s := Summary{}
+ s.HTTP.ByStatus = map[string]float64{}
+ s.DB.Records = map[string]float64{}
+ s.Alerts.Evaluations = map[string]float64{}
+ s.Loki.Queries = map[string]float64{}
+
+ families, err := registry.Gather()
+ if err != nil {
+ return s
+ }
+ byPath := map[string]float64{}
+ for _, mf := range families {
+ for _, m := range mf.GetMetric() {
+ labels := labelMap(m)
+ switch mf.GetName() {
+ case "lokistack_http_requests_total":
+ v := m.GetCounter().GetValue()
+ s.HTTP.Total += v
+ s.HTTP.ByStatus[labels["status"]] += v
+ byPath[labels["path"]] += v
+ case "lokistack_http_in_flight_requests":
+ s.HTTP.InFlight = m.GetGauge().GetValue()
+ case "lokistack_db_size_bytes":
+ s.DB.SizeBytes = m.GetGauge().GetValue()
+ case "lokistack_db_save_total":
+ v := m.GetCounter().GetValue()
+ s.DB.Saves += v
+ if labels["result"] != "success" {
+ s.DB.SaveFailures += v
+ }
+ case "lokistack_db_records":
+ s.DB.Records[labels["collection"]] = m.GetGauge().GetValue()
+ case "lokistack_alert_evaluations_total":
+ s.Alerts.Evaluations[labels["result"]] += m.GetCounter().GetValue()
+ case "lokistack_loki_queries_total":
+ s.Loki.Queries[labels["result"]] += m.GetCounter().GetValue()
+ case "go_goroutines":
+ s.Runtime.Goroutines = m.GetGauge().GetValue()
+ case "lokistack_build_info":
+ s.Runtime.GoVersion = labels["go_version"]
+ s.Runtime.Version = labels["version"]
+ }
+ }
+ }
+ paths := make([]PathCount, 0, len(byPath))
+ for p, c := range byPath {
+ paths = append(paths, PathCount{Path: p, Count: c})
+ }
+ sort.Slice(paths, func(i, j int) bool { return paths[i].Count > paths[j].Count })
+ if len(paths) > 8 {
+ paths = paths[:8]
+ }
+ s.HTTP.TopPaths = paths
+ return s
+}
+
+func labelMap(m *dto.Metric) map[string]string {
+ out := map[string]string{}
+ for _, lp := range m.GetLabel() {
+ out[lp.GetName()] = lp.GetValue()
+ }
+ return out
+}
diff --git a/backend/routers/router.go b/backend/routers/router.go
index 52d05a2..db76ead 100644
--- a/backend/routers/router.go
+++ b/backend/routers/router.go
@@ -152,6 +152,7 @@ func SetupRouter() *gin.Engine {
system.POST("/restore/apply", systemCtrl.RestoreApply)
system.GET("/backups", systemCtrl.Backups)
system.GET("/self-check", systemCtrl.Health)
+ system.GET("/metrics-summary", middleware.AdminAuth(), systemCtrl.MetricsSummary)
}
// 审计日志:仅管理员可查(含他人操作记录)
@@ -235,6 +236,11 @@ func SetupRouter() *gin.Engine {
authorized.POST("/collectors/:id/preview", collectorCtrl.Preview)
authorized.GET("/collectors/:id/logs", collectorCtrl.Logs)
+ // 文档站(docsify,docs/)与落地页(homepage/)随二进制一起发布;
+ // 独立于前端 dist 存在与否注册,目录不存在时路由不生效
+ serveStaticDir(r, "/docs", "docs")
+ serveStaticDir(r, "/homepage", "homepage")
+
// Static files - use executable directory as base
distPath := findDistPath()
@@ -267,6 +273,40 @@ func SetupRouter() *gin.Engine {
return r
}
+// serveStaticDir 把本地目录挂为只读静态站点:目录回退到 fallbackFile(SPA/docsify 需要)。
+// baseDir 候选与 findDistPath 同源:可执行文件相对路径优先,再退工作目录。
+func serveStaticDir(r *gin.Engine, urlPrefix, dirName string) {
+ root := findAuxDir(dirName)
+ if root == "" {
+ return
+ }
+ // http.FileServer 自带目录回退:访问 /docs 自动 301 到 /docs/ 并命中 index.html
+ r.Static(urlPrefix, root)
+}
+
+// findAuxDir 定位 dist 之外的附属静态目录(docs/homepage),规则与 findDistPath 对齐
+func findAuxDir(dirName string) string {
+ if ex, err := os.Executable(); err == nil {
+ execDir := filepath.Dir(ex)
+ if !strings.Contains(execDir, "/tmp/go-build") {
+ for _, rel := range []string{dirName, "../" + dirName, "../../" + dirName} {
+ p := filepath.Join(execDir, rel)
+ if _, err := os.Stat(filepath.Join(p, "index.html")); err == nil {
+ return p
+ }
+ }
+ }
+ }
+ for _, p := range []string{"./" + dirName, "../" + dirName, "/app/" + dirName} {
+ if abs, err := filepath.Abs(p); err == nil {
+ if _, err := os.Stat(filepath.Join(abs, "index.html")); err == nil {
+ return abs
+ }
+ }
+ }
+ return ""
+}
+
// findDistPath finds the frontend dist directory
func findDistPath() string {
// Try relative to executable
diff --git a/docs/index.html b/docs/index.html
new file mode 100644
index 0000000..ad58d3a
--- /dev/null
+++ b/docs/index.html
@@ -0,0 +1,31 @@
+
+
+
+
+
+LokiStack 用户手册
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/user-guide/README.md b/docs/user-guide/README.md
new file mode 100644
index 0000000..325c48a
--- /dev/null
+++ b/docs/user-guide/README.md
@@ -0,0 +1,15 @@
+# LokiStack 用户手册
+
+LokiStack 把 Grafana Loki 日志聚合的部署、采集、查询、分析与告警收进一个平台:向导式部署到 Kubernetes 集群或裸金属主机,内置中文/英文界面,单二进制交付。
+
+## 平台能做什么
+
+- **部署 Loki**:三步向导(环境与目标 → 存储 → 部署与采集),支持 Kubernetes(YAML 直发)、裸金属二进制、主机 Docker 三种模式,不需要在平台所在机器安装 kubectl 或 Helm。
+- **采集日志**:Promtail / Fluent Bit 采集策略可视化配置,命名空间多选、app 标签来源四级回退、敏感路径拦截。
+- **查询与分析**:LogQL 高亮补全、快捷查询、Live Tail;分析页自动聚合日志量、错误与 Ingress 访问指标。
+- **告警**:内置九条应用日志规则,绑定默认通道后填入飞书 Webhook 即可接收;支持冷却与风暴抑制。
+- **运维底座**:凭证加密存储、操作审计、备份恢复、Prometheus 自监控指标。
+
+## 下一步
+
+阅读[安装与启动](install.md),或直接进入[部署向导](wizard.md)。
diff --git a/docs/user-guide/_sidebar.md b/docs/user-guide/_sidebar.md
new file mode 100644
index 0000000..730b217
--- /dev/null
+++ b/docs/user-guide/_sidebar.md
@@ -0,0 +1,10 @@
+- [开始](README.md)
+- [安装与启动](install.md)
+- [部署向导](wizard.md)
+- [日志采集](collectors.md)
+- [日志查询](query.md)
+- [日志分析](analysis.md)
+- [告警通知](alerts.md)
+- [查询授权](access.md)
+- [备份与恢复](backup.md)
+- [常见问题](faq.md)
diff --git a/docs/user-guide/access.md b/docs/user-guide/access.md
new file mode 100644
index 0000000..5d0c261
--- /dev/null
+++ b/docs/user-guide/access.md
@@ -0,0 +1,10 @@
+# 查询授权
+
+管理员在「日志查询 → 查询授权」用策略组控制普通用户可见的日志范围:
+
+- 策略组 = 数据源 + 允许的标签范围(命名空间 / App / Pod,均精确匹配,未选不限) + 关联用户。
+- 同一用户在同一数据源只能属于一个策略组;已在其他组的用户置灰。
+- 支持快捷的「所有人」策略(默认策略的常用形态)。
+- 授权在服务端强制执行:普通用户的查询必须以流选择器开头,策略标签自动合并,无法越权。
+
+用户管理(仅管理员)支持创建/重置密码/批量删除;LDAP 用户可在系统设置接入后导入。
diff --git a/docs/user-guide/alerts.md b/docs/user-guide/alerts.md
new file mode 100644
index 0000000..d4e3276
--- /dev/null
+++ b/docs/user-guide/alerts.md
@@ -0,0 +1,24 @@
+# 告警通知
+
+「日志告警」页(`/alerts`)管理规则、通知通道与发送历史。
+
+## 快速开始
+
+1. 在「通知通道」中编辑默认通道,填入飞书群机器人 Webhook 并启用。
+2. 内置九条规则默认启用并已绑定默认通道,命中阈值即发送到群。
+
+## 规则
+
+- 查询为 LogQL 日志流匹配;窗口(分钟)、阈值(条数)、冷却(分钟)三要素。
+- 内置规则已排除 Loki 自身日志(app=loki),避免告警查询日志造成自我误报。
+- 模板按钮一键填充常用规则(命名空间错误 / Ingress 4xx5xx / FATAL 等)。
+
+## 通道与发送历史
+
+- 通道支持飞书 Webhook(可选签名);可建多通道对应不同告警群。
+- 发送历史记录每次触发的命中数、投递结果与失败原因;保留天数在「系统设置 → 日志保留时间」调整。
+
+## 防刷屏机制
+
+- 单规则冷却(默认 ≥ 2 小时);全平台每小时发送上限(风暴抑制)。
+- 通道未配置/未启用时命中只记入历史,不影响规则评估。
diff --git a/docs/user-guide/analysis.md b/docs/user-guide/analysis.md
new file mode 100644
index 0000000..3cadacb
--- /dev/null
+++ b/docs/user-guide/analysis.md
@@ -0,0 +1,13 @@
+# 日志分析
+
+「日志分析」页(`/analysis`,管理员)自动聚合日志指标,无需手动查询:
+
+- **统计卡**:日志总量、错误行数、告警行数、活跃应用
+- **趋势折线**:总量 / 错误 / 告警随时间分布
+- **命名空间分布**:环形图
+- **应用流图**:Top 10 应用日志量堆叠
+- **每日日志量**:近 7 天按日聚合(独立于时间范围筛选)
+- **应用排行**:Top 10 应用,支持跳转查询页
+- **Ingress 访问分析**:自动识别 ingress-nginx 访问日志,PV/UV、状态码、慢请求与接口排行;未安装 ingress-nginx 时自动折叠
+
+时间范围与数据源在页面顶部切换;指标查询经服务端聚合,开销远低于拉取原始日志。
diff --git a/docs/user-guide/backup.md b/docs/user-guide/backup.md
new file mode 100644
index 0000000..e99ffde
--- /dev/null
+++ b/docs/user-guide/backup.md
@@ -0,0 +1,23 @@
+# 备份与恢复
+
+平台状态集中在单个 JSON 文件(`$DATA_DIR/lokistack.json`),备份恢复围绕它展开。
+
+## 备份
+
+- 手动:「系统设置 → 备份」立即创建(写入 `$DATA_DIR/backups/`)。
+- 脚本:`./run.sh backup` 或 `make` 内相应目标,适合 cron 定时。
+
+## 恢复
+
+1. 上传统计备份或选择已有备份,校验后暂存。
+2. 点击应用:平台先把当前库留档(`.before-restore.*`),替换后立即在内存生效,随后建议重启进程。
+3. 恢复文件损坏时平台拒绝应用并提示。
+
+## 数据文件保护
+
+- 启动时若数据文件解析失败,原文件会以 `.corrupt-<时间戳>` 留档后再以空库启动,绝不覆盖可恢复数据。
+- 历史命名数据文件(opsxloki.json / lokideploy.json)在启动时自动升级为 lokistack.json。
+
+## 自监控
+
+`/api/v1/metrics` 输出 Prometheus 指标(HTTP、DB 落盘、告警引擎、Go runtime);「平台监控」页(`/monitor`,管理员)提供可视化快照。
diff --git a/docs/user-guide/collectors.md b/docs/user-guide/collectors.md
new file mode 100644
index 0000000..47698e3
--- /dev/null
+++ b/docs/user-guide/collectors.md
@@ -0,0 +1,18 @@
+# 日志采集
+
+「日志采集」页管理采集策略:主机采集(Promtail)与集群采集(DaemonSet / Fluent Bit Sidecar)。
+
+## 采集范围
+
+- **集群采集**:命名空间多选(默认全集群);app 标签按 `app.kubernetes.io/name → app → controller 名 → pod 名` 四级回退,可自定义来源标签与 app 白名单收敛日志量。
+- **主机采集**:填写业务日志路径(glob 通配,如 `/var/log/app/*.log`);系统日志自动探测(syslog / messages)。
+- 敏感路径(/etc/shadow、私钥、/proc 等)会被平台拦截,防止主机凭据进入日志平台。
+
+## 编辑与下发
+
+保存后点击「部署」下发到目标;「预览 YAML」可先核对将生成的 promtail 配置。修改后同样以「部署」生效。
+
+## 排查
+
+- 采集器卡片可看运行状态与日志(主机 docker logs / journalctl,集群 Pod 日志)。
+- 常见问题:目标命名空间无工作负载(app 候选为空)、路径无写入权限、Loki 端点不可达。
diff --git a/docs/user-guide/faq.md b/docs/user-guide/faq.md
new file mode 100644
index 0000000..ddb44d4
--- /dev/null
+++ b/docs/user-guide/faq.md
@@ -0,0 +1,29 @@
+# 常见问题
+
+## 登录后看不到「部署向导」「用户管理」等菜单?
+
+这些页面仅管理员可见。普通用户默认只有日志查询与日志告警;查询范围由策略组控制(见[查询授权](access.md))。
+
+## 部署的 Loki 怎么访问?必须配 Ingress 吗?
+
+集群内部署经 API Server Service Proxy 查询,不需要 Ingress。跨集群 / 公网查询才需要为 Loki 暴露域名后在「数据源管理」用 URL 直连关联。
+
+## 「停止部署」会删掉日志数据吗?
+
+不会。停止只回收工作负载;数据卷(PVC / 主机数据目录)保留,重新启动即可恢复。仅显式删除部署记录不影响远端资源,卸载需按页面指引执行。
+
+## 告警一直不发送?
+
+按顺序检查:通道是否启用、Webhook 是否正确(通道页有测试)、规则是否启用且命中阈值、发送历史里是否有「冷却抑制」或失败原因。通道未就绪时历史会明确记录失败原因。
+
+## 忘记管理员密码?
+
+管理员可在「用户管理」重置任意账号密码;若唯一管理员丢失密码,参考仓库 README 的离线重置说明。
+
+## 平台可以多实例部署吗?
+
+不可以。JSON 文件存储为单实例设计(防抖串行落盘),水平扩展会互相覆盖;需要高可用时用单实例 + 备份策略。
+
+## 升级安全吗?
+
+配置与数据向后兼容,结构变更按 schema 版本自动迁移;建议升级前在「系统设置」创建一次备份。
diff --git a/docs/user-guide/install.md b/docs/user-guide/install.md
new file mode 100644
index 0000000..2310777
--- /dev/null
+++ b/docs/user-guide/install.md
@@ -0,0 +1,44 @@
+# 安装与启动
+
+## 环境要求
+
+- 平台自身:任意 Linux / macOS(Go 1.24 构建或直接使用发布二进制),内存 ≥ 256MB
+- 目标 Kubernetes 集群:≥ 1.20;或裸金属 Linux 主机(SSH 可达)
+- 端口:平台默认 8080;Loki 默认 3100
+
+## 必备环境变量
+
+平台启动强制要求两个密钥(长度均需 ≥ 32 字符):
+
+```bash
+export JWT_SECRET=$(openssl rand -hex 32)
+export ENCRYPTION_KEY=$(openssl rand -hex 32) # 凭证加密密钥,丢失将无法解密已存凭证
+```
+
+## 方式一:开发模式
+
+```bash
+./run.sh # 同时拉起前端(:3000)与后端(:8080),自动注入开发密钥
+```
+
+## 方式二:生产 Docker
+
+```bash
+make secrets # 生成 ./secrets/*.txt
+docker compose -f docker-compose.prod.yml up -d
+```
+
+## 方式三:单二进制
+
+```bash
+bash build.sh # 产物 backend/bin/lokistack(前端已内嵌)
+export JWT_SECRET=... ENCRYPTION_KEY=... DATA_DIR=/data
+./lokistack
+```
+
+首次启动会自动创建管理员账号,初始密码写入 `$DATA_DIR/initial-admin-password.txt`,请立即修改。
+
+## 数据与升级
+
+- 全部状态存储在 `$DATA_DIR/lokistack.json`(JSON 文件,无外部数据库依赖,单实例运行)
+- 升级:替换二进制后重启即可;历史命名数据文件(opsxloki.json / lokideploy.json)会在启动时自动迁移
diff --git a/docs/user-guide/query.md b/docs/user-guide/query.md
new file mode 100644
index 0000000..3c9310c
--- /dev/null
+++ b/docs/user-guide/query.md
@@ -0,0 +1,20 @@
+# 日志查询
+
+「日志查询」页(`/logs`)按 LogQL 与时间范围检索日志。
+
+## 查询方式
+
+- **LogQL 编辑器**:语法高亮与标签/管道补全;回车执行。
+- **快捷查询**:选择命名空间缩小范围后,选中 App / Pod 即自动填入并执行;Ingress 访问日志按钮在选择 ingress 命名空间或 app 时出现。
+- **Label 浏览器**:按标签与值点选构建查询;点结果行的标签值可快速加过滤。
+- **Live Tail**:3 秒轮询最近 5 分钟,编辑查询自动防抖后切换。
+
+## 结果区
+
+- 顶部时间线展示整个查询范围的日志量,拖选高峰时段自动重查。
+- 原始日志支持关键字高亮、标签过滤、查看上下文(前后 N 行)。
+- 翻页向后游标推进,每页最多 1000 条。
+
+## 数据源
+
+「数据源管理」维护 Loki 连接:平台部署自动注册(随部署托管),也可手动添加 URL 直连或集群 Service 数据源,支持 Basic / Bearer / 自定义 Header 认证与连接测试。
diff --git a/docs/user-guide/wizard.md b/docs/user-guide/wizard.md
new file mode 100644
index 0000000..5e0f422
--- /dev/null
+++ b/docs/user-guide/wizard.md
@@ -0,0 +1,27 @@
+# 部署向导
+
+部署向导(`/wizard`)三步完成 Loki 上线:环境与目标 → 存储 → 部署 Loki 与采集。
+
+## 第一步:环境与目标
+
+- **Kubernetes 集群**:粘贴 kubeconfig,或按页面给出的 RBAC 脚本在集群执行一次后粘贴 ServiceAccount 长期 Token。平台经 API Server 操作集群,无需安装 kubectl/Helm。
+- **裸金属主机**:填写 SSH 地址与凭证(密码或私钥,AES-GCM 加密存储)。适合二进制或 Docker 模式。
+
+创建后平台自动验证连通性;Kubernetes 资源默认落在 `log` 命名空间(可改)。
+
+## 第二步:准备存储
+
+推荐对象存储(MinIO / RustFS / S3 / OSS):多副本与数据保留更可靠,创建时可用「测试连接」验证凭证并自动建桶;跳过则使用本地磁盘。
+
+## 第三步:部署与采集
+
+- 选择目标、Loki 版本(支持 2.8+ / 3.x)、保留天数与资源规格,点击部署。
+- 部署成功后平台自动注册同名数据源,日志查询页立即可用;按提示添加采集策略(见[日志采集](collectors.md))。
+
+## 三种部署模式
+
+| 模式 | 适用 | 说明 |
+| --- | --- | --- |
+| k8s-yaml | Kubernetes | StatefulSet + SSA 直发,平台全托管 |
+| host-binary | 裸金属 | SSH 安装二进制 + systemd 托管 |
+| host-docker | 裸金属 | Docker 容器运行,自动目录迁移 |
diff --git a/homepage/assets/site.css b/homepage/assets/site.css
new file mode 100644
index 0000000..ad537a5
--- /dev/null
+++ b/homepage/assets/site.css
@@ -0,0 +1,58 @@
+:root {
+ --brand: #059669; --brand-dark: #047857; --ink: #0f172a; --muted: #64748b;
+ --bg: #f8fafc; --card: #ffffff; --line: #e2e8f0;
+ --code-bg: #0f172a; --code-ink: #e2e8f0;
+}
+* { margin: 0; padding: 0; box-sizing: border-box; }
+html { scroll-behavior: smooth; }
+body { font-family: -apple-system, "PingFang SC", "Microsoft YaHei", "Segoe UI", sans-serif; color: var(--ink); background: var(--bg); line-height: 1.7; }
+a { color: var(--brand); text-decoration: none; }
+.container { max-width: 1080px; margin: 0 auto; padding: 0 24px; }
+
+.nav { position: sticky; top: 0; z-index: 10; background: rgba(255,255,255,.92); backdrop-filter: blur(8px); border-bottom: 1px solid var(--line); }
+.nav-inner { max-width: 1080px; margin: 0 auto; padding: 14px 24px; display: flex; align-items: center; justify-content: space-between; }
+.brand { display: flex; align-items: center; gap: 10px; font-weight: 700; font-size: 20px; color: var(--ink); }
+.brand-logo { display: inline-flex; align-items: center; justify-content: center; width: 34px; height: 34px; border-radius: 9px; background: linear-gradient(135deg, #10b981, #047857); color: #fff; font-weight: 800; }
+.nav-links { display: flex; gap: 26px; font-size: 15px; }
+.nav-links a { color: var(--muted); }
+.nav-links a:hover { color: var(--ink); }
+
+.hero { padding: 96px 0 84px; text-align: center; background: radial-gradient(1200px 500px at 50% -100px, rgba(16,185,129,.12), transparent); }
+.hero h1 { font-size: 46px; line-height: 1.25; letter-spacing: -1px; }
+.hero h1 em { font-style: normal; color: var(--brand); }
+.hero p.tagline { margin: 18px auto 0; max-width: 640px; font-size: 19px; color: var(--muted); }
+.hero-actions { margin-top: 34px; display: flex; gap: 14px; justify-content: center; flex-wrap: wrap; }
+.btn { display: inline-flex; align-items: center; gap: 8px; padding: 12px 26px; border-radius: 10px; font-size: 16px; font-weight: 600; border: 1px solid transparent; transition: all .15s; }
+.btn-primary { background: var(--brand); color: #fff; }
+.btn-primary:hover { background: var(--brand-dark); }
+.btn-outline { border-color: var(--line); color: var(--ink); background: var(--card); }
+.btn-outline:hover { border-color: var(--brand); color: var(--brand); }
+.hero-meta { margin-top: 26px; font-size: 14px; color: var(--muted); }
+
+section.block { padding: 72px 0; }
+section.block.alt { background: var(--card); border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); }
+.block h2 { font-size: 30px; text-align: center; }
+.block .sub { text-align: center; color: var(--muted); margin-top: 10px; margin-bottom: 44px; }
+
+.grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; }
+.feature { background: var(--card); border: 1px solid var(--line); border-radius: 14px; padding: 26px; }
+.feature .icon { width: 42px; height: 42px; border-radius: 10px; display: flex; align-items: center; justify-content: center; background: rgba(16,185,129,.1); color: var(--brand); margin-bottom: 14px; }
+.feature h3 { font-size: 17px; margin-bottom: 8px; }
+.feature p { font-size: 14.5px; color: var(--muted); }
+
+.steps { max-width: 760px; margin: 0 auto; }
+.step { display: flex; gap: 18px; padding: 18px 0; border-bottom: 1px dashed var(--line); }
+.step:last-child { border-bottom: none; }
+.step .no { flex-shrink: 0; width: 34px; height: 34px; border-radius: 50%; background: var(--brand); color: #fff; font-weight: 700; display: flex; align-items: center; justify-content: center; }
+.step h3 { font-size: 16px; }
+.step p { font-size: 14.5px; color: var(--muted); margin-top: 4px; }
+code.inline { background: rgba(16,185,129,.08); color: var(--brand-dark); padding: 2px 7px; border-radius: 5px; font-size: 13.5px; font-family: ui-monospace, "SF Mono", Menlo, monospace; }
+
+.footer { padding: 40px 0 56px; color: var(--muted); font-size: 14px; text-align: center; }
+.footer .links { display: flex; gap: 22px; justify-content: center; margin-bottom: 14px; }
+
+@media (max-width: 840px) {
+ .grid { grid-template-columns: 1fr; }
+ .hero h1 { font-size: 34px; }
+ .nav-links { display: none; }
+}
diff --git a/homepage/index.html b/homepage/index.html
new file mode 100644
index 0000000..bf2ed40
--- /dev/null
+++ b/homepage/index.html
@@ -0,0 +1,131 @@
+
+
+
+
+
+LokiStack — Loki 日志管理平台
+
+
+
+
+
+
+
+
+
+
+
把 Loki 日志聚合
变成五分钟的事
+
向导式部署 Grafana Loki 到 Kubernetes 集群或裸金属主机,内置日志查询、可视化分析与飞书告警 —— 不写 YAML、不装 kubectl/Helm。
+
+
Go + React 单二进制交付 · JSON 文件存储零依赖 · 中文/英文界面
+
+
+
+
+
+
为运维而生的完整日志链路
+
从部署、采集、查询到告警,一个平台全部覆盖
+
+
+
+
向导式部署
+
三步完成:环境与目标 → 存储 → 部署 Loki 与采集。支持 Kubernetes(YAML 直发)、裸金属二进制与 Docker 三种模式。
+
+
+
+
策略化采集
+
Promtail / Fluent Bit 采集策略可视化配置:命名空间多选、app 标签四级回退、敏感路径拦截、配置预览与一键下发。
+
+
+
+
专业日志查询
+
LogQL 高亮补全、快捷查询选中即执行、Live Tail 实时追踪、时间线拖选、上下文查看,细到按用户的查询授权。
+
+
+
+
日志分析大盘
+
统计卡、趋势折线、命名空间分布、应用流图、每日日志量、Top 10 排行与 Ingress 访问分析,自动聚合无需配置。
+
+
+
+
开箱告警
+
内置九条应用日志告警规则,默认启用并绑定默认通道;填好飞书 Webhook 即可接收,冷却与风暴抑制防刷屏。
+
+
+
+
工程级可靠性
+
Credential 加密存储、操作审计、备份恢复、Prometheus 自监控指标、CI 门禁与测试覆盖,按生产标准构建。
+
+
+
+
+
+
+
+
五分钟启动
+
单二进制或 Docker,一条命令拉起完整平台
+
+
+
1
+
+
准备密钥并启动
+
export JWT_SECRET=... ENCRYPTION_KEY=... 后执行 ./run.sh(开发)或 docker compose up -d(生产)。默认账号在首次启动时自动生成。
+
+
+
+
2
+
+
走完部署向导
+
接入 Kubernetes 集群或 SSH 主机,选择对象存储或本地磁盘,三步部署 Loki 并下发采集策略。
+
+
+
+
3
+
+
查询、分析、告警
+
日志查询页检索与实时追踪,日志分析页看聚合大盘,告警页填入飞书 Webhook 开始接收告警。
+
+
+
+
+
+
+
+
+
文档
+
用户手册覆盖部署、采集、查询、分析与告警全流程
+
+
+
+
+
+
+
+