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 开始接收告警。

+
+
+
+
+
+ +
+
+

文档

+

用户手册覆盖部署、采集、查询、分析与告警全流程

+ +
+
+ + + + +