Initial commit: Project foundation
- Backend: Go API server with Gin framework - Frontend: React setup (placeholder) - ML Service: Python FastAPI skeleton - Docker Compose: Full stack configuration - Database: PostgreSQL schema with migrations - Documentation: Implementation plan and README Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
23
.env.example
Normal file
23
.env.example
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
# Database Configuration
|
||||||
|
DB_HOST=postgres
|
||||||
|
DB_PORT=5432
|
||||||
|
DB_NAME=vlogs_manager
|
||||||
|
DB_USER=vlogs
|
||||||
|
DB_PASSWORD=your_secure_password_here
|
||||||
|
|
||||||
|
# JWT Configuration
|
||||||
|
JWT_SECRET=your_jwt_secret_key_at_least_32_characters_long
|
||||||
|
|
||||||
|
# VictoriaLogs Configuration
|
||||||
|
VLOGS_URL=http://victorialogs:9428
|
||||||
|
|
||||||
|
# vmalert Configuration
|
||||||
|
VMALERT_URL=http://vmalert:8880
|
||||||
|
VMALERT_RULES_DIR=/app/rules
|
||||||
|
|
||||||
|
# ML Service Configuration
|
||||||
|
ML_SERVICE_URL=http://ml-service:8000
|
||||||
|
|
||||||
|
# Application Configuration
|
||||||
|
ENV=development
|
||||||
|
LOG_LEVEL=info
|
||||||
20
.gitignore
vendored
Normal file
20
.gitignore
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
# Environment variables
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Temporary files
|
||||||
|
*.tmp
|
||||||
|
tmp/
|
||||||
1159
IMPLEMENTATION_PLAN.md
Normal file
1159
IMPLEMENTATION_PLAN.md
Normal file
File diff suppressed because it is too large
Load Diff
93
README.md
Normal file
93
README.md
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
# VictoriaLogs Manager
|
||||||
|
|
||||||
|
A comprehensive web application for managing VictoriaLogs with alerting, advanced reporting, and malicious pattern detection.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **Log Exploration**: Query and search logs using LogsQL with visual query builder
|
||||||
|
- **Alert Management**: Create and manage vmalert rules with YAML editor
|
||||||
|
- **Pattern Detection**: Find malicious patterns using regex and ML-based anomaly detection
|
||||||
|
- **Reports & Dashboards**: Fancy visualizations and custom reports
|
||||||
|
- **Role-Based Access**: Admin, Editor, Analyst, and Viewer roles
|
||||||
|
|
||||||
|
## Tech Stack
|
||||||
|
|
||||||
|
- **Backend**: Go (Gin framework)
|
||||||
|
- **Frontend**: React + TypeScript + Vite
|
||||||
|
- **Database**: PostgreSQL
|
||||||
|
- **ML Service**: Python (FastAPI + scikit-learn)
|
||||||
|
- **Deployment**: Docker Compose
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
victorialogs-manager/
|
||||||
|
├── backend/ # Go API server
|
||||||
|
├── frontend/ # React SPA
|
||||||
|
├── ml-service/ # Python ML service
|
||||||
|
└── docker-compose.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
- Go 1.21+
|
||||||
|
- Node.js 18+
|
||||||
|
- Python 3.11+
|
||||||
|
- Docker & Docker Compose
|
||||||
|
|
||||||
|
### Quick Start with Docker
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Copy environment file
|
||||||
|
cp .env.example .env
|
||||||
|
|
||||||
|
# Edit .env with your configuration
|
||||||
|
nano .env
|
||||||
|
|
||||||
|
# Start all services
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
The application will be available at:
|
||||||
|
- Frontend: http://localhost:3000
|
||||||
|
- Backend API: http://localhost:8080
|
||||||
|
- VictoriaLogs: http://localhost:9428
|
||||||
|
|
||||||
|
### Development Setup
|
||||||
|
|
||||||
|
#### Backend
|
||||||
|
```bash
|
||||||
|
cd backend
|
||||||
|
go mod download
|
||||||
|
go run cmd/server/main.go
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Frontend
|
||||||
|
```bash
|
||||||
|
cd frontend
|
||||||
|
npm install
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
#### ML Service
|
||||||
|
```bash
|
||||||
|
cd ml-service
|
||||||
|
pip install -r requirements.txt
|
||||||
|
uvicorn app.main:app --reload
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
See `.env.example` for all available configuration options.
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
- [API Documentation](./docs/api.md) (coming soon)
|
||||||
|
- [User Guide](./docs/user-guide.md) (coming soon)
|
||||||
|
- [Deployment Guide](./docs/deployment.md) (coming soon)
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT
|
||||||
25
alertmanager.yml
Normal file
25
alertmanager.yml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
global:
|
||||||
|
resolve_timeout: 5m
|
||||||
|
|
||||||
|
route:
|
||||||
|
group_by: ['alertname', 'cluster', 'service']
|
||||||
|
group_wait: 10s
|
||||||
|
group_interval: 10s
|
||||||
|
repeat_interval: 12h
|
||||||
|
receiver: 'default'
|
||||||
|
|
||||||
|
receivers:
|
||||||
|
- name: 'default'
|
||||||
|
# Configure your notification channels here
|
||||||
|
# Examples: email, slack, webhook, etc.
|
||||||
|
# For now, this is a basic configuration
|
||||||
|
webhook_configs:
|
||||||
|
- url: 'http://backend:8080/api/v1/alerts/webhook'
|
||||||
|
send_resolved: true
|
||||||
|
|
||||||
|
inhibit_rules:
|
||||||
|
- source_match:
|
||||||
|
severity: 'critical'
|
||||||
|
target_match:
|
||||||
|
severity: 'warning'
|
||||||
|
equal: ['alertname', 'cluster', 'service']
|
||||||
22
backend/.gitignore
vendored
Normal file
22
backend/.gitignore
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# Go
|
||||||
|
*.exe
|
||||||
|
*.exe~
|
||||||
|
*.dll
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
*.test
|
||||||
|
*.out
|
||||||
|
go.work
|
||||||
|
|
||||||
|
# Binary
|
||||||
|
/bin/
|
||||||
|
/build/
|
||||||
|
server
|
||||||
|
|
||||||
|
# Vendor
|
||||||
|
vendor/
|
||||||
|
|
||||||
|
# vmalert rules (generated)
|
||||||
|
configs/vmalert-rules/*.yml
|
||||||
|
configs/vmalert-rules/*.yaml
|
||||||
|
!configs/vmalert-rules/.gitkeep
|
||||||
40
backend/Dockerfile
Normal file
40
backend/Dockerfile
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
# Build stage
|
||||||
|
FROM golang:1.21-alpine AS builder
|
||||||
|
|
||||||
|
# Install build dependencies
|
||||||
|
RUN apk add --no-cache git
|
||||||
|
|
||||||
|
# Set working directory
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy go mod files
|
||||||
|
COPY go.mod go.sum* ./
|
||||||
|
|
||||||
|
# Download dependencies
|
||||||
|
RUN go mod download
|
||||||
|
|
||||||
|
# Copy source code
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Build the application
|
||||||
|
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o server ./cmd/server
|
||||||
|
|
||||||
|
# Final stage
|
||||||
|
FROM alpine:latest
|
||||||
|
|
||||||
|
# Install ca-certificates for HTTPS
|
||||||
|
RUN apk --no-cache add ca-certificates
|
||||||
|
|
||||||
|
WORKDIR /root/
|
||||||
|
|
||||||
|
# Copy binary from builder
|
||||||
|
COPY --from=builder /app/server .
|
||||||
|
|
||||||
|
# Copy configuration
|
||||||
|
COPY --from=builder /app/configs ./configs
|
||||||
|
|
||||||
|
# Expose port
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
# Run the application
|
||||||
|
CMD ["./server"]
|
||||||
39
backend/configs/config.yaml
Normal file
39
backend/configs/config.yaml
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
server:
|
||||||
|
port: 8080
|
||||||
|
read_timeout: 30s
|
||||||
|
write_timeout: 30s
|
||||||
|
|
||||||
|
database:
|
||||||
|
host: ${DB_HOST:postgres}
|
||||||
|
port: ${DB_PORT:5432}
|
||||||
|
name: ${DB_NAME:vlogs_manager}
|
||||||
|
user: ${DB_USER:vlogs}
|
||||||
|
password: ${DB_PASSWORD}
|
||||||
|
max_connections: 25
|
||||||
|
|
||||||
|
victorialogs:
|
||||||
|
url: ${VLOGS_URL:http://victorialogs:9428}
|
||||||
|
timeout: 30s
|
||||||
|
max_query_size: 10000
|
||||||
|
|
||||||
|
vmalert:
|
||||||
|
url: ${VMALERT_URL:http://vmalert:8880}
|
||||||
|
rules_dir: ${VMALERT_RULES_DIR:/app/rules}
|
||||||
|
reload_endpoint: "/-/reload"
|
||||||
|
|
||||||
|
ml_service:
|
||||||
|
url: ${ML_SERVICE_URL:http://ml-service:8000}
|
||||||
|
timeout: 60s
|
||||||
|
|
||||||
|
auth:
|
||||||
|
jwt_secret: ${JWT_SECRET}
|
||||||
|
access_token_duration: 15m
|
||||||
|
refresh_token_duration: 168h
|
||||||
|
|
||||||
|
rate_limiting:
|
||||||
|
enabled: true
|
||||||
|
requests_per_minute:
|
||||||
|
admin: 1000
|
||||||
|
editor: 500
|
||||||
|
analyst: 300
|
||||||
|
viewer: 200
|
||||||
0
backend/configs/vmalert-rules/.gitkeep
Normal file
0
backend/configs/vmalert-rules/.gitkeep
Normal file
12
backend/go.mod
Normal file
12
backend/go.mod
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
module github.com/yourusername/victorialogs-manager
|
||||||
|
|
||||||
|
go 1.21
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/gin-gonic/gin v1.9.1
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.2.0
|
||||||
|
github.com/lib/pq v1.10.9
|
||||||
|
golang.org/x/crypto v0.18.0
|
||||||
|
github.com/joho/godotenv v1.5.1
|
||||||
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
|
)
|
||||||
186
backend/internal/api/router.go
Normal file
186
backend/internal/api/router.go
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/yourusername/victorialogs-manager/internal/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewRouter creates and configures the main router
|
||||||
|
func NewRouter(cfg *config.Config, db *sql.DB) *gin.Engine {
|
||||||
|
// Set Gin mode
|
||||||
|
gin.SetMode(gin.ReleaseMode)
|
||||||
|
|
||||||
|
router := gin.Default()
|
||||||
|
|
||||||
|
// CORS middleware
|
||||||
|
router.Use(corsMiddleware())
|
||||||
|
|
||||||
|
// Health check endpoint
|
||||||
|
router.GET("/health", func(c *gin.Context) {
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"status": "ok",
|
||||||
|
"service": "victorialogs-manager",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// API v1 routes
|
||||||
|
v1 := router.Group("/api/v1")
|
||||||
|
{
|
||||||
|
// Authentication routes (public)
|
||||||
|
auth := v1.Group("/auth")
|
||||||
|
{
|
||||||
|
auth.POST("/login", func(c *gin.Context) {
|
||||||
|
// TODO: Implement login handler
|
||||||
|
c.JSON(http.StatusNotImplemented, gin.H{"message": "not implemented yet"})
|
||||||
|
})
|
||||||
|
auth.POST("/refresh", func(c *gin.Context) {
|
||||||
|
// TODO: Implement refresh handler
|
||||||
|
c.JSON(http.StatusNotImplemented, gin.H{"message": "not implemented yet"})
|
||||||
|
})
|
||||||
|
auth.GET("/me", func(c *gin.Context) {
|
||||||
|
// TODO: Implement me handler (requires auth)
|
||||||
|
c.JSON(http.StatusNotImplemented, gin.H{"message": "not implemented yet"})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Protected routes (require authentication)
|
||||||
|
// TODO: Add auth middleware here
|
||||||
|
|
||||||
|
// User management routes
|
||||||
|
users := v1.Group("/users")
|
||||||
|
{
|
||||||
|
users.GET("", func(c *gin.Context) {
|
||||||
|
c.JSON(http.StatusNotImplemented, gin.H{"message": "not implemented yet"})
|
||||||
|
})
|
||||||
|
users.POST("", func(c *gin.Context) {
|
||||||
|
c.JSON(http.StatusNotImplemented, gin.H{"message": "not implemented yet"})
|
||||||
|
})
|
||||||
|
users.GET("/:id", func(c *gin.Context) {
|
||||||
|
c.JSON(http.StatusNotImplemented, gin.H{"message": "not implemented yet"})
|
||||||
|
})
|
||||||
|
users.PUT("/:id", func(c *gin.Context) {
|
||||||
|
c.JSON(http.StatusNotImplemented, gin.H{"message": "not implemented yet"})
|
||||||
|
})
|
||||||
|
users.DELETE("/:id", func(c *gin.Context) {
|
||||||
|
c.JSON(http.StatusNotImplemented, gin.H{"message": "not implemented yet"})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log querying routes
|
||||||
|
logs := v1.Group("/logs")
|
||||||
|
{
|
||||||
|
logs.POST("/query", func(c *gin.Context) {
|
||||||
|
c.JSON(http.StatusNotImplemented, gin.H{"message": "not implemented yet"})
|
||||||
|
})
|
||||||
|
logs.POST("/tail", func(c *gin.Context) {
|
||||||
|
c.JSON(http.StatusNotImplemented, gin.H{"message": "not implemented yet"})
|
||||||
|
})
|
||||||
|
logs.GET("/facets", func(c *gin.Context) {
|
||||||
|
c.JSON(http.StatusNotImplemented, gin.H{"message": "not implemented yet"})
|
||||||
|
})
|
||||||
|
logs.POST("/stats", func(c *gin.Context) {
|
||||||
|
c.JSON(http.StatusNotImplemented, gin.H{"message": "not implemented yet"})
|
||||||
|
})
|
||||||
|
logs.POST("/export", func(c *gin.Context) {
|
||||||
|
c.JSON(http.StatusNotImplemented, gin.H{"message": "not implemented yet"})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alert management routes
|
||||||
|
alerts := v1.Group("/alerts")
|
||||||
|
{
|
||||||
|
alerts.GET("/rules", func(c *gin.Context) {
|
||||||
|
c.JSON(http.StatusNotImplemented, gin.H{"message": "not implemented yet"})
|
||||||
|
})
|
||||||
|
alerts.POST("/rules", func(c *gin.Context) {
|
||||||
|
c.JSON(http.StatusNotImplemented, gin.H{"message": "not implemented yet"})
|
||||||
|
})
|
||||||
|
alerts.GET("/rules/:id", func(c *gin.Context) {
|
||||||
|
c.JSON(http.StatusNotImplemented, gin.H{"message": "not implemented yet"})
|
||||||
|
})
|
||||||
|
alerts.PUT("/rules/:id", func(c *gin.Context) {
|
||||||
|
c.JSON(http.StatusNotImplemented, gin.H{"message": "not implemented yet"})
|
||||||
|
})
|
||||||
|
alerts.DELETE("/rules/:id", func(c *gin.Context) {
|
||||||
|
c.JSON(http.StatusNotImplemented, gin.H{"message": "not implemented yet"})
|
||||||
|
})
|
||||||
|
alerts.POST("/reload", func(c *gin.Context) {
|
||||||
|
c.JSON(http.StatusNotImplemented, gin.H{"message": "not implemented yet"})
|
||||||
|
})
|
||||||
|
alerts.GET("/active", func(c *gin.Context) {
|
||||||
|
c.JSON(http.StatusNotImplemented, gin.H{"message": "not implemented yet"})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pattern detection routes
|
||||||
|
patterns := v1.Group("/patterns")
|
||||||
|
{
|
||||||
|
patterns.GET("", func(c *gin.Context) {
|
||||||
|
c.JSON(http.StatusNotImplemented, gin.H{"message": "not implemented yet"})
|
||||||
|
})
|
||||||
|
patterns.POST("", func(c *gin.Context) {
|
||||||
|
c.JSON(http.StatusNotImplemented, gin.H{"message": "not implemented yet"})
|
||||||
|
})
|
||||||
|
patterns.PUT("/:id", func(c *gin.Context) {
|
||||||
|
c.JSON(http.StatusNotImplemented, gin.H{"message": "not implemented yet"})
|
||||||
|
})
|
||||||
|
patterns.DELETE("/:id", func(c *gin.Context) {
|
||||||
|
c.JSON(http.StatusNotImplemented, gin.H{"message": "not implemented yet"})
|
||||||
|
})
|
||||||
|
patterns.POST("/detect", func(c *gin.Context) {
|
||||||
|
c.JSON(http.StatusNotImplemented, gin.H{"message": "not implemented yet"})
|
||||||
|
})
|
||||||
|
patterns.POST("/ml/train", func(c *gin.Context) {
|
||||||
|
c.JSON(http.StatusNotImplemented, gin.H{"message": "not implemented yet"})
|
||||||
|
})
|
||||||
|
patterns.POST("/ml/detect", func(c *gin.Context) {
|
||||||
|
c.JSON(http.StatusNotImplemented, gin.H{"message": "not implemented yet"})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Report routes
|
||||||
|
reports := v1.Group("/reports")
|
||||||
|
{
|
||||||
|
reports.GET("", func(c *gin.Context) {
|
||||||
|
c.JSON(http.StatusNotImplemented, gin.H{"message": "not implemented yet"})
|
||||||
|
})
|
||||||
|
reports.POST("", func(c *gin.Context) {
|
||||||
|
c.JSON(http.StatusNotImplemented, gin.H{"message": "not implemented yet"})
|
||||||
|
})
|
||||||
|
reports.GET("/:id", func(c *gin.Context) {
|
||||||
|
c.JSON(http.StatusNotImplemented, gin.H{"message": "not implemented yet"})
|
||||||
|
})
|
||||||
|
reports.PUT("/:id", func(c *gin.Context) {
|
||||||
|
c.JSON(http.StatusNotImplemented, gin.H{"message": "not implemented yet"})
|
||||||
|
})
|
||||||
|
reports.DELETE("/:id", func(c *gin.Context) {
|
||||||
|
c.JSON(http.StatusNotImplemented, gin.H{"message": "not implemented yet"})
|
||||||
|
})
|
||||||
|
reports.POST("/:id/execute", func(c *gin.Context) {
|
||||||
|
c.JSON(http.StatusNotImplemented, gin.H{"message": "not implemented yet"})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return router
|
||||||
|
}
|
||||||
|
|
||||||
|
// corsMiddleware handles CORS headers
|
||||||
|
func corsMiddleware() gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
|
||||||
|
c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
|
||||||
|
c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With")
|
||||||
|
c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT, DELETE")
|
||||||
|
|
||||||
|
if c.Request.Method == "OPTIONS" {
|
||||||
|
c.AbortWithStatus(204)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
106
backend/internal/config/config.go
Normal file
106
backend/internal/config/config.go
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
Server ServerConfig `yaml:"server"`
|
||||||
|
Database DatabaseConfig `yaml:"database"`
|
||||||
|
VictoriaLogs VictoriaLogsConfig `yaml:"victorialogs"`
|
||||||
|
VMAlert VMAlertConfig `yaml:"vmalert"`
|
||||||
|
MLService MLServiceConfig `yaml:"ml_service"`
|
||||||
|
Auth AuthConfig `yaml:"auth"`
|
||||||
|
RateLimiting RateLimitingConfig `yaml:"rate_limiting"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ServerConfig struct {
|
||||||
|
Port int `yaml:"port"`
|
||||||
|
ReadTimeout time.Duration `yaml:"read_timeout"`
|
||||||
|
WriteTimeout time.Duration `yaml:"write_timeout"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DatabaseConfig struct {
|
||||||
|
Host string `yaml:"host"`
|
||||||
|
Port int `yaml:"port"`
|
||||||
|
Name string `yaml:"name"`
|
||||||
|
User string `yaml:"user"`
|
||||||
|
Password string `yaml:"password"`
|
||||||
|
MaxConnections int `yaml:"max_connections"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type VictoriaLogsConfig struct {
|
||||||
|
URL string `yaml:"url"`
|
||||||
|
Timeout time.Duration `yaml:"timeout"`
|
||||||
|
MaxQuerySize int `yaml:"max_query_size"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type VMAlertConfig struct {
|
||||||
|
URL string `yaml:"url"`
|
||||||
|
RulesDir string `yaml:"rules_dir"`
|
||||||
|
ReloadEndpoint string `yaml:"reload_endpoint"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type MLServiceConfig struct {
|
||||||
|
URL string `yaml:"url"`
|
||||||
|
Timeout time.Duration `yaml:"timeout"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AuthConfig struct {
|
||||||
|
JWTSecret string `yaml:"jwt_secret"`
|
||||||
|
AccessTokenDuration time.Duration `yaml:"access_token_duration"`
|
||||||
|
RefreshTokenDuration time.Duration `yaml:"refresh_token_duration"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RateLimitingConfig struct {
|
||||||
|
Enabled bool `yaml:"enabled"`
|
||||||
|
RequestsPerMinute map[string]int `yaml:"requests_per_minute"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load loads configuration from YAML file and environment variables
|
||||||
|
func Load(configPath string) (*Config, error) {
|
||||||
|
data, err := os.ReadFile(configPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to read config file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expand environment variables in config
|
||||||
|
expandedData := []byte(os.ExpandEnv(string(data)))
|
||||||
|
|
||||||
|
var cfg Config
|
||||||
|
if err := yaml.Unmarshal(expandedData, &cfg); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse config: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate required fields
|
||||||
|
if err := cfg.validate(); err != nil {
|
||||||
|
return nil, fmt.Errorf("config validation failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &cfg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Config) validate() error {
|
||||||
|
if c.Database.Password == "" {
|
||||||
|
return fmt.Errorf("database password is required")
|
||||||
|
}
|
||||||
|
if c.Auth.JWTSecret == "" {
|
||||||
|
return fmt.Errorf("JWT secret is required")
|
||||||
|
}
|
||||||
|
if len(c.Auth.JWTSecret) < 32 {
|
||||||
|
return fmt.Errorf("JWT secret must be at least 32 characters")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDSN returns the PostgreSQL connection string
|
||||||
|
func (c *DatabaseConfig) GetDSN() string {
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"host=%s port=%d user=%s password=%s dbname=%s sslmode=disable",
|
||||||
|
c.Host, c.Port, c.User, c.Password, c.Name,
|
||||||
|
)
|
||||||
|
}
|
||||||
78
backend/internal/models/user.go
Normal file
78
backend/internal/models/user.go
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// Role represents user roles
|
||||||
|
type Role string
|
||||||
|
|
||||||
|
const (
|
||||||
|
RoleAdmin Role = "admin"
|
||||||
|
RoleEditor Role = "editor"
|
||||||
|
RoleAnalyst Role = "analyst"
|
||||||
|
RoleViewer Role = "viewer"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Permission represents a specific permission
|
||||||
|
type Permission string
|
||||||
|
|
||||||
|
const (
|
||||||
|
PermViewLogs Permission = "logs:view"
|
||||||
|
PermExportLogs Permission = "logs:export"
|
||||||
|
PermManageAlerts Permission = "alerts:manage"
|
||||||
|
PermViewAlerts Permission = "alerts:view"
|
||||||
|
PermManagePatterns Permission = "patterns:manage"
|
||||||
|
PermRunML Permission = "patterns:ml"
|
||||||
|
PermManageReports Permission = "reports:manage"
|
||||||
|
PermManageUsers Permission = "users:manage"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RolePermissions maps roles to their permissions
|
||||||
|
var RolePermissions = map[Role][]Permission{
|
||||||
|
RoleAdmin: {
|
||||||
|
PermViewLogs, PermExportLogs, PermManageAlerts,
|
||||||
|
PermViewAlerts, PermManagePatterns, PermRunML,
|
||||||
|
PermManageReports, PermManageUsers,
|
||||||
|
},
|
||||||
|
RoleEditor: {
|
||||||
|
PermViewLogs, PermExportLogs, PermManageAlerts,
|
||||||
|
PermViewAlerts, PermManagePatterns, PermManageReports,
|
||||||
|
},
|
||||||
|
RoleAnalyst: {
|
||||||
|
PermViewLogs, PermViewAlerts, PermManagePatterns, PermRunML,
|
||||||
|
},
|
||||||
|
RoleViewer: {
|
||||||
|
PermViewLogs, PermViewAlerts,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// User represents a user in the system
|
||||||
|
type User struct {
|
||||||
|
ID string `json:"id" db:"id"`
|
||||||
|
Username string `json:"username" db:"username"`
|
||||||
|
Email string `json:"email" db:"email"`
|
||||||
|
PasswordHash string `json:"-" db:"password_hash"`
|
||||||
|
Role Role `json:"role" db:"role"`
|
||||||
|
IsActive bool `json:"is_active" db:"is_active"`
|
||||||
|
CreatedAt time.Time `json:"created_at" db:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasPermission checks if the user has a specific permission
|
||||||
|
func (u *User) HasPermission(perm Permission) bool {
|
||||||
|
permissions, ok := RolePermissions[u.Role]
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, p := range permissions {
|
||||||
|
if p == perm {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPermissions returns all permissions for the user's role
|
||||||
|
func (u *User) GetPermissions() []Permission {
|
||||||
|
return RolePermissions[u.Role]
|
||||||
|
}
|
||||||
132
backend/migrations/001_init.sql
Normal file
132
backend/migrations/001_init.sql
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
-- Create users table
|
||||||
|
CREATE TABLE IF NOT EXISTS users (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
username VARCHAR(255) UNIQUE NOT NULL,
|
||||||
|
email VARCHAR(255) UNIQUE NOT NULL,
|
||||||
|
password_hash VARCHAR(255) NOT NULL,
|
||||||
|
role VARCHAR(50) NOT NULL CHECK (role IN ('admin', 'editor', 'analyst', 'viewer')),
|
||||||
|
is_active BOOLEAN DEFAULT true,
|
||||||
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Create index on username and email for faster lookups
|
||||||
|
CREATE INDEX idx_users_username ON users(username);
|
||||||
|
CREATE INDEX idx_users_email ON users(email);
|
||||||
|
CREATE INDEX idx_users_role ON users(role);
|
||||||
|
|
||||||
|
-- Create alert_rules table
|
||||||
|
CREATE TABLE IF NOT EXISTS alert_rules (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
name VARCHAR(255) NOT NULL,
|
||||||
|
group_name VARCHAR(255) NOT NULL,
|
||||||
|
type VARCHAR(50) NOT NULL CHECK (type IN ('alert', 'record')),
|
||||||
|
expression TEXT NOT NULL,
|
||||||
|
duration VARCHAR(50),
|
||||||
|
labels JSONB DEFAULT '{}',
|
||||||
|
annotations JSONB DEFAULT '{}',
|
||||||
|
severity VARCHAR(50),
|
||||||
|
enabled BOOLEAN DEFAULT true,
|
||||||
|
yaml_content TEXT NOT NULL,
|
||||||
|
created_by UUID REFERENCES users(id) ON DELETE SET NULL,
|
||||||
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Create indexes for alert_rules
|
||||||
|
CREATE INDEX idx_alert_rules_group_name ON alert_rules(group_name);
|
||||||
|
CREATE INDEX idx_alert_rules_enabled ON alert_rules(enabled);
|
||||||
|
CREATE INDEX idx_alert_rules_created_by ON alert_rules(created_by);
|
||||||
|
|
||||||
|
-- Create patterns table
|
||||||
|
CREATE TABLE IF NOT EXISTS patterns (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
name VARCHAR(255) NOT NULL,
|
||||||
|
type VARCHAR(50) NOT NULL CHECK (type IN ('regex', 'ml')),
|
||||||
|
description TEXT,
|
||||||
|
config JSONB NOT NULL,
|
||||||
|
severity VARCHAR(50),
|
||||||
|
tags TEXT[],
|
||||||
|
enabled BOOLEAN DEFAULT true,
|
||||||
|
created_by UUID REFERENCES users(id) ON DELETE SET NULL,
|
||||||
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Create indexes for patterns
|
||||||
|
CREATE INDEX idx_patterns_type ON patterns(type);
|
||||||
|
CREATE INDEX idx_patterns_enabled ON patterns(enabled);
|
||||||
|
CREATE INDEX idx_patterns_created_by ON patterns(created_by);
|
||||||
|
|
||||||
|
-- Create reports table
|
||||||
|
CREATE TABLE IF NOT EXISTS reports (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
name VARCHAR(255) NOT NULL,
|
||||||
|
description TEXT,
|
||||||
|
config JSONB NOT NULL,
|
||||||
|
created_by UUID REFERENCES users(id) ON DELETE SET NULL,
|
||||||
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Create index for reports
|
||||||
|
CREATE INDEX idx_reports_created_by ON reports(created_by);
|
||||||
|
|
||||||
|
-- Create audit_logs table for tracking sensitive operations
|
||||||
|
CREATE TABLE IF NOT EXISTS audit_logs (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
user_id UUID REFERENCES users(id) ON DELETE SET NULL,
|
||||||
|
action VARCHAR(255) NOT NULL,
|
||||||
|
resource_type VARCHAR(100),
|
||||||
|
resource_id UUID,
|
||||||
|
details JSONB,
|
||||||
|
ip_address INET,
|
||||||
|
user_agent TEXT,
|
||||||
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Create indexes for audit_logs
|
||||||
|
CREATE INDEX idx_audit_logs_user_id ON audit_logs(user_id);
|
||||||
|
CREATE INDEX idx_audit_logs_action ON audit_logs(action);
|
||||||
|
CREATE INDEX idx_audit_logs_created_at ON audit_logs(created_at);
|
||||||
|
|
||||||
|
-- Create function to update updated_at timestamp
|
||||||
|
CREATE OR REPLACE FUNCTION update_updated_at_column()
|
||||||
|
RETURNS TRIGGER AS $$
|
||||||
|
BEGIN
|
||||||
|
NEW.updated_at = CURRENT_TIMESTAMP;
|
||||||
|
RETURN NEW;
|
||||||
|
END;
|
||||||
|
$$ language 'plpgsql';
|
||||||
|
|
||||||
|
-- Create triggers for updated_at
|
||||||
|
CREATE TRIGGER update_users_updated_at BEFORE UPDATE ON users
|
||||||
|
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||||
|
|
||||||
|
CREATE TRIGGER update_alert_rules_updated_at BEFORE UPDATE ON alert_rules
|
||||||
|
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||||
|
|
||||||
|
CREATE TRIGGER update_patterns_updated_at BEFORE UPDATE ON patterns
|
||||||
|
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||||
|
|
||||||
|
CREATE TRIGGER update_reports_updated_at BEFORE UPDATE ON reports
|
||||||
|
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||||
|
|
||||||
|
-- Insert default admin user (password: admin123 - CHANGE IN PRODUCTION!)
|
||||||
|
-- Password hash is bcrypt hash of "admin123"
|
||||||
|
INSERT INTO users (username, email, password_hash, role)
|
||||||
|
VALUES (
|
||||||
|
'admin',
|
||||||
|
'admin@example.com',
|
||||||
|
'$2a$12$LQv3c1yqBWVHxkd0LHAkCOYz6TtxMQJqhN8/LewY5lW.HKCFcVpSK',
|
||||||
|
'admin'
|
||||||
|
) ON CONFLICT (username) DO NOTHING;
|
||||||
|
|
||||||
|
-- Insert sample viewer user (password: viewer123)
|
||||||
|
INSERT INTO users (username, email, password_hash, role)
|
||||||
|
VALUES (
|
||||||
|
'viewer',
|
||||||
|
'viewer@example.com',
|
||||||
|
'$2a$12$3pYKzLw9z1FqN8QdKxI.J.nVZY5TqJxM7Y.qQHkZKZhzJ8qQnPZ8W',
|
||||||
|
'viewer'
|
||||||
|
) ON CONFLICT (username) DO NOTHING;
|
||||||
152
docker-compose.yml
Normal file
152
docker-compose.yml
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
# VictoriaLogs - Log database
|
||||||
|
victorialogs:
|
||||||
|
image: victoriametrics/victoria-logs:latest
|
||||||
|
container_name: victorialogs
|
||||||
|
ports:
|
||||||
|
- "9428:9428"
|
||||||
|
volumes:
|
||||||
|
- vlogs-data:/victoria-logs-data
|
||||||
|
command:
|
||||||
|
- "-storageDataPath=/victoria-logs-data"
|
||||||
|
- "-retentionPeriod=30d"
|
||||||
|
networks:
|
||||||
|
- vlogs-network
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
# vmalert - Alerting engine
|
||||||
|
vmalert:
|
||||||
|
image: victoriametrics/vmalert:latest
|
||||||
|
container_name: vmalert
|
||||||
|
ports:
|
||||||
|
- "8880:8880"
|
||||||
|
volumes:
|
||||||
|
- vmalert-rules:/etc/vmalert/rules
|
||||||
|
command:
|
||||||
|
- "-datasource.url=http://victorialogs:9428"
|
||||||
|
- "-rule=/etc/vmalert/rules/*.yml"
|
||||||
|
- "-notifier.url=http://alertmanager:9093"
|
||||||
|
depends_on:
|
||||||
|
- victorialogs
|
||||||
|
- alertmanager
|
||||||
|
networks:
|
||||||
|
- vlogs-network
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
# Alertmanager - Alert notifications
|
||||||
|
alertmanager:
|
||||||
|
image: prom/alertmanager:latest
|
||||||
|
container_name: alertmanager
|
||||||
|
ports:
|
||||||
|
- "9093:9093"
|
||||||
|
volumes:
|
||||||
|
- ./alertmanager.yml:/etc/alertmanager/alertmanager.yml
|
||||||
|
- alertmanager-data:/alertmanager
|
||||||
|
command:
|
||||||
|
- "--config.file=/etc/alertmanager/alertmanager.yml"
|
||||||
|
- "--storage.path=/alertmanager"
|
||||||
|
networks:
|
||||||
|
- vlogs-network
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
# PostgreSQL - Application database
|
||||||
|
postgres:
|
||||||
|
image: postgres:15-alpine
|
||||||
|
container_name: postgres
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: ${DB_NAME:-vlogs_manager}
|
||||||
|
POSTGRES_USER: ${DB_USER:-vlogs}
|
||||||
|
POSTGRES_PASSWORD: ${DB_PASSWORD}
|
||||||
|
volumes:
|
||||||
|
- postgres-data:/var/lib/postgresql/data
|
||||||
|
- ./backend/migrations:/docker-entrypoint-initdb.d
|
||||||
|
ports:
|
||||||
|
- "5432:5432"
|
||||||
|
networks:
|
||||||
|
- vlogs-network
|
||||||
|
restart: unless-stopped
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U ${DB_USER:-vlogs}"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
|
||||||
|
# Go Backend API
|
||||||
|
backend:
|
||||||
|
build:
|
||||||
|
context: ./backend
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
container_name: backend
|
||||||
|
ports:
|
||||||
|
- "8080:8080"
|
||||||
|
environment:
|
||||||
|
DB_HOST: postgres
|
||||||
|
DB_PORT: 5432
|
||||||
|
DB_NAME: ${DB_NAME:-vlogs_manager}
|
||||||
|
DB_USER: ${DB_USER:-vlogs}
|
||||||
|
DB_PASSWORD: ${DB_PASSWORD}
|
||||||
|
VLOGS_URL: http://victorialogs:9428
|
||||||
|
VMALERT_URL: http://vmalert:8880
|
||||||
|
ML_SERVICE_URL: http://ml-service:8000
|
||||||
|
JWT_SECRET: ${JWT_SECRET}
|
||||||
|
VMALERT_RULES_DIR: /app/rules
|
||||||
|
volumes:
|
||||||
|
- vmalert-rules:/app/rules
|
||||||
|
depends_on:
|
||||||
|
postgres:
|
||||||
|
condition: service_healthy
|
||||||
|
victorialogs:
|
||||||
|
condition: service_started
|
||||||
|
vmalert:
|
||||||
|
condition: service_started
|
||||||
|
ml-service:
|
||||||
|
condition: service_started
|
||||||
|
networks:
|
||||||
|
- vlogs-network
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
# Python ML Service
|
||||||
|
ml-service:
|
||||||
|
build:
|
||||||
|
context: ./ml-service
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
container_name: ml-service
|
||||||
|
ports:
|
||||||
|
- "8000:8000"
|
||||||
|
environment:
|
||||||
|
VLOGS_URL: http://victorialogs:9428
|
||||||
|
MODEL_DIR: /app/models
|
||||||
|
volumes:
|
||||||
|
- ml-models:/app/models
|
||||||
|
networks:
|
||||||
|
- vlogs-network
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
# React Frontend
|
||||||
|
frontend:
|
||||||
|
build:
|
||||||
|
context: ./frontend
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
args:
|
||||||
|
VITE_API_BASE_URL: http://localhost:8080/api/v1
|
||||||
|
container_name: frontend
|
||||||
|
ports:
|
||||||
|
- "3000:80"
|
||||||
|
depends_on:
|
||||||
|
- backend
|
||||||
|
networks:
|
||||||
|
- vlogs-network
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
vlogs-data:
|
||||||
|
postgres-data:
|
||||||
|
vmalert-rules:
|
||||||
|
ml-models:
|
||||||
|
alertmanager-data:
|
||||||
|
|
||||||
|
networks:
|
||||||
|
vlogs-network:
|
||||||
|
driver: bridge
|
||||||
25
frontend/.gitignore
vendored
Normal file
25
frontend/.gitignore
vendored
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Dependencies
|
||||||
|
node_modules/
|
||||||
|
.pnp
|
||||||
|
.pnp.js
|
||||||
|
|
||||||
|
# Build
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
|
.vite/
|
||||||
|
|
||||||
|
# Cache
|
||||||
|
.cache/
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# Environment
|
||||||
|
.env.local
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
|
||||||
|
# Debug logs
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
34
frontend/Dockerfile
Normal file
34
frontend/Dockerfile
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# Build stage
|
||||||
|
FROM node:18-alpine AS builder
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy package files
|
||||||
|
COPY package*.json ./
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
RUN npm ci
|
||||||
|
|
||||||
|
# Copy source code
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Build argument for API URL
|
||||||
|
ARG VITE_API_BASE_URL
|
||||||
|
ENV VITE_API_BASE_URL=$VITE_API_BASE_URL
|
||||||
|
|
||||||
|
# Build the application
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
# Production stage
|
||||||
|
FROM nginx:alpine
|
||||||
|
|
||||||
|
# Copy built assets from builder
|
||||||
|
COPY --from=builder /app/dist /usr/share/nginx/html
|
||||||
|
|
||||||
|
# Copy nginx configuration
|
||||||
|
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||||
|
|
||||||
|
# Expose port
|
||||||
|
EXPOSE 80
|
||||||
|
|
||||||
|
CMD ["nginx", "-g", "daemon off;"]
|
||||||
28
frontend/nginx.conf
Normal file
28
frontend/nginx.conf
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name localhost;
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
index index.html;
|
||||||
|
|
||||||
|
# Enable gzip compression
|
||||||
|
gzip on;
|
||||||
|
gzip_vary on;
|
||||||
|
gzip_min_length 1024;
|
||||||
|
gzip_types text/plain text/css text/xml text/javascript application/javascript application/xml+rss application/json;
|
||||||
|
|
||||||
|
# SPA routing - serve index.html for all routes
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ /index.html;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Cache static assets
|
||||||
|
location /assets/ {
|
||||||
|
expires 1y;
|
||||||
|
add_header Cache-Control "public, immutable";
|
||||||
|
}
|
||||||
|
|
||||||
|
# Security headers
|
||||||
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||||
|
add_header X-Content-Type-Options "nosniff" always;
|
||||||
|
add_header X-XSS-Protection "1; mode=block" always;
|
||||||
|
}
|
||||||
26
ml-service/.gitignore
vendored
Normal file
26
ml-service/.gitignore
vendored
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# Python
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
*.so
|
||||||
|
.Python
|
||||||
|
*.egg-info/
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
|
|
||||||
|
# Virtual environment
|
||||||
|
venv/
|
||||||
|
env/
|
||||||
|
ENV/
|
||||||
|
|
||||||
|
# ML models
|
||||||
|
models/*.pkl
|
||||||
|
models/*.joblib
|
||||||
|
!models/.gitkeep
|
||||||
|
|
||||||
|
# Jupyter
|
||||||
|
.ipynb_checkpoints/
|
||||||
|
|
||||||
|
# Cache
|
||||||
|
.pytest_cache/
|
||||||
|
.mypy_cache/
|
||||||
27
ml-service/Dockerfile
Normal file
27
ml-service/Dockerfile
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
FROM python:3.11-slim
|
||||||
|
|
||||||
|
# Set working directory
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Install system dependencies
|
||||||
|
RUN apt-get update && apt-get install -y \
|
||||||
|
gcc \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Copy requirements file
|
||||||
|
COPY requirements.txt .
|
||||||
|
|
||||||
|
# Install Python dependencies
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
# Copy application code
|
||||||
|
COPY app/ ./app/
|
||||||
|
|
||||||
|
# Create models directory
|
||||||
|
RUN mkdir -p models
|
||||||
|
|
||||||
|
# Expose port
|
||||||
|
EXPOSE 8000
|
||||||
|
|
||||||
|
# Run the application
|
||||||
|
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
|
||||||
1
ml-service/app/__init__.py
Normal file
1
ml-service/app/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
# ML Service Package
|
||||||
122
ml-service/app/main.py
Normal file
122
ml-service/app/main.py
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
from fastapi import FastAPI, HTTPException
|
||||||
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
from pydantic import BaseModel
|
||||||
|
from typing import List, Dict, Any, Optional
|
||||||
|
import logging
|
||||||
|
|
||||||
|
# Configure logging
|
||||||
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
app = FastAPI(
|
||||||
|
title="VictoriaLogs ML Service",
|
||||||
|
description="Machine Learning service for anomaly detection in logs",
|
||||||
|
version="1.0.0"
|
||||||
|
)
|
||||||
|
|
||||||
|
# CORS middleware
|
||||||
|
app.add_middleware(
|
||||||
|
CORSMiddleware,
|
||||||
|
allow_origins=["*"],
|
||||||
|
allow_credentials=True,
|
||||||
|
allow_methods=["*"],
|
||||||
|
allow_headers=["*"],
|
||||||
|
)
|
||||||
|
|
||||||
|
# Request/Response models
|
||||||
|
class TrainRequest(BaseModel):
|
||||||
|
model_name: str
|
||||||
|
query: str
|
||||||
|
start_time: str
|
||||||
|
end_time: str
|
||||||
|
contamination: float = 0.1
|
||||||
|
|
||||||
|
class DetectRequest(BaseModel):
|
||||||
|
model_name: str
|
||||||
|
logs: List[Dict[str, Any]]
|
||||||
|
|
||||||
|
class TrainResponse(BaseModel):
|
||||||
|
status: str
|
||||||
|
model_name: str
|
||||||
|
model_path: str
|
||||||
|
message: str
|
||||||
|
|
||||||
|
class DetectResponse(BaseModel):
|
||||||
|
status: str
|
||||||
|
anomalies: List[Dict[str, Any]]
|
||||||
|
anomaly_count: int
|
||||||
|
|
||||||
|
@app.get("/")
|
||||||
|
async def root():
|
||||||
|
return {
|
||||||
|
"service": "VictoriaLogs ML Service",
|
||||||
|
"status": "running",
|
||||||
|
"version": "1.0.0"
|
||||||
|
}
|
||||||
|
|
||||||
|
@app.get("/health")
|
||||||
|
async def health_check():
|
||||||
|
return {"status": "healthy"}
|
||||||
|
|
||||||
|
@app.post("/api/ml/train", response_model=TrainResponse)
|
||||||
|
async def train_model(request: TrainRequest):
|
||||||
|
"""
|
||||||
|
Train an anomaly detection model on historical logs
|
||||||
|
"""
|
||||||
|
logger.info(f"Training model: {request.model_name}")
|
||||||
|
|
||||||
|
# TODO: Implement model training
|
||||||
|
# 1. Fetch logs from VictoriaLogs
|
||||||
|
# 2. Extract features
|
||||||
|
# 3. Train Isolation Forest model
|
||||||
|
# 4. Save model to disk
|
||||||
|
|
||||||
|
return TrainResponse(
|
||||||
|
status="success",
|
||||||
|
model_name=request.model_name,
|
||||||
|
model_path=f"/app/models/{request.model_name}.pkl",
|
||||||
|
message="Model training not yet implemented"
|
||||||
|
)
|
||||||
|
|
||||||
|
@app.post("/api/ml/detect", response_model=DetectResponse)
|
||||||
|
async def detect_anomalies(request: DetectRequest):
|
||||||
|
"""
|
||||||
|
Detect anomalies in a batch of logs using a trained model
|
||||||
|
"""
|
||||||
|
logger.info(f"Detecting anomalies with model: {request.model_name}")
|
||||||
|
|
||||||
|
# TODO: Implement anomaly detection
|
||||||
|
# 1. Load trained model
|
||||||
|
# 2. Extract features from logs
|
||||||
|
# 3. Predict anomalies
|
||||||
|
# 4. Return flagged logs with scores
|
||||||
|
|
||||||
|
return DetectResponse(
|
||||||
|
status="success",
|
||||||
|
anomalies=[],
|
||||||
|
anomaly_count=0
|
||||||
|
)
|
||||||
|
|
||||||
|
@app.get("/api/ml/models")
|
||||||
|
async def list_models():
|
||||||
|
"""
|
||||||
|
List all trained models
|
||||||
|
"""
|
||||||
|
# TODO: List model files from disk
|
||||||
|
return {
|
||||||
|
"models": [],
|
||||||
|
"count": 0
|
||||||
|
}
|
||||||
|
|
||||||
|
@app.delete("/api/ml/models/{model_name}")
|
||||||
|
async def delete_model(model_name: str):
|
||||||
|
"""
|
||||||
|
Delete a trained model
|
||||||
|
"""
|
||||||
|
logger.info(f"Deleting model: {model_name}")
|
||||||
|
|
||||||
|
# TODO: Delete model file
|
||||||
|
return {
|
||||||
|
"status": "success",
|
||||||
|
"message": f"Model {model_name} deleted"
|
||||||
|
}
|
||||||
0
ml-service/app/models/.gitkeep
Normal file
0
ml-service/app/models/.gitkeep
Normal file
9
ml-service/requirements.txt
Normal file
9
ml-service/requirements.txt
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
fastapi==0.109.0
|
||||||
|
uvicorn[standard]==0.27.0
|
||||||
|
pydantic==2.5.3
|
||||||
|
scikit-learn==1.4.0
|
||||||
|
pandas==2.2.0
|
||||||
|
numpy==1.26.3
|
||||||
|
httpx==0.26.0
|
||||||
|
python-multipart==0.0.6
|
||||||
|
joblib==1.3.2
|
||||||
Reference in New Issue
Block a user