Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ permissions:
jobs:
build-and-deploy:
runs-on: ubuntu-latest

steps:
- name: "🔄 Checkout code"
uses: actions/checkout@v4
Expand All @@ -20,6 +21,14 @@ jobs:
with:
go-version: '1.24'

- name: "🔧 Setup environment variables from secrets"
env:
SECRETS_CONTEXT: ${{ toJSON(secrets) }}
run: |
echo "Setting up environment variables from GitHub secrets..."
echo "$SECRETS_CONTEXT" | jq -r 'to_entries[] | "\(.key)=\(.value)"' >> $GITHUB_ENV
echo "Environment variables configured from secrets"

- name: "🏗️ Build and run PongHub"
run: |
mkdir -p bin data
Expand Down
97 changes: 97 additions & 0 deletions .github/workflows/pr-build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
name: PR Build and Test

on:
pull_request:
branches: [ main, master ]

permissions:
contents: read

jobs:
build-and-test:
runs-on: ubuntu-latest

steps:
- name: "🔄 Checkout code"
uses: actions/checkout@v4

- name: "🐹 Set up Go"
uses: actions/setup-go@v5
with:
go-version: '1.24'

- name: "🔧 Setup environment variables from secrets"
env:
SECRETS_CONTEXT: ${{ toJSON(secrets) }}
run: |
echo "Setting up environment variables from GitHub secrets..."
echo "$SECRETS_CONTEXT" | jq -r 'to_entries[] | "\(.key)=\(.value)"' >> $GITHUB_ENV
echo "Environment variables configured from secrets"

- name: "🧪 Run Go tests"
run: |
go mod tidy
go test -v ./...

- name: "🔨 Build PongHub"
run: |
make build

- name: "🏗️ Build and run PongHub"
run: |
mkdir -p bin data
if [ ! -f data/ponghub_log.json ]; then
echo "[]" > data/ponghub_log.json
echo "Created empty log file for testing"
fi
make run || true

- name: "✅ Verify build artifacts"
run: |
echo "Checking generated files..."
if [ -f data/ponghub_log.json ]; then
echo "✅ Log file generated successfully"
echo "Log file size: $(wc -c < data/ponghub_log.json) bytes"
else
echo "❌ Log file not found"
exit 1
fi

if [ -f data/index.html ]; then
echo "✅ Report HTML generated successfully"
echo "Report file size: $(wc -c < data/index.html) bytes"
else
echo "❌ Report HTML not found"
exit 1
fi

- name: "📋 Build Summary"
if: always()
run: |
echo "## 🎉 Build Summary" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### 📊 Generated Files" >> $GITHUB_STEP_SUMMARY

if [ -f data/ponghub_log.json ]; then
LOG_SIZE=$(wc -c < data/ponghub_log.json)
echo "- ✅ **Log file**: Generated (${LOG_SIZE} bytes)" >> $GITHUB_STEP_SUMMARY
else
echo "- ❌ **Log file**: Failed to generate" >> $GITHUB_STEP_SUMMARY
fi

if [ -f data/index.html ]; then
HTML_SIZE=$(wc -c < data/index.html)
echo "- ✅ **Report HTML**: Generated (${HTML_SIZE} bytes)" >> $GITHUB_STEP_SUMMARY
else
echo "- ❌ **Report HTML**: Failed to generate" >> $GITHUB_STEP_SUMMARY
fi

if [ -f data/notify.txt ] && [ -s data/notify.txt ]; then
echo "- ⚠️ **Notifications**: Some services may be unavailable" >> $GITHUB_STEP_SUMMARY
else
echo "- ✅ **Service Status**: All services appear to be healthy" >> $GITHUB_STEP_SUMMARY
fi

echo "" >> $GITHUB_STEP_SUMMARY
echo "### 🔧 Environment Variables" >> $GITHUB_STEP_SUMMARY
echo "Environment variables were successfully loaded from GitHub secrets." >> $GITHUB_STEP_SUMMARY
80 changes: 80 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ PongHub is an open-source service status monitoring website designed to help use

## Configuration Guide

### Basic Configuration

The `config.yaml` file follows this format:

| Field | Type | Description | Required | Notes |
Expand Down Expand Up @@ -91,6 +93,84 @@ services:
body: '{"key": "value"}'
```

### Special Parameters

ponghub now supports powerful parameterized configuration functionality, allowing the use of various types of dynamic variables in configuration files. These variables are generated and resolved in real-time during program execution.

#### 📅 Date and Time Parameters

Use the `{{%format}}` format to define date and time parameters:

- `{{%Y-%m-%d}}` - Current date, format: 2006-01-02 (e.g., 2025-09-22)
- `{{%H:%M:%S}}` - Current time, format: 15:04:05 (e.g., 17:30:45)
- `{{%s}}` - Unix timestamp (e.g., 1727859600)
- `{{%Y}}` - Current year (e.g., 2025)
- `{{%m}}` - Current month, format: 01-12
- `{{%d}}` - Current day, format: 01-31
- `{{%H}}` - Current hour, format: 00-23
- `{{%M}}` - Current minute, format: 00-59
- `{{%S}}` - Current second, format: 00-59
- `{{%B}}` - Full month name (e.g., September)
- `{{%b}}` - Short month name (e.g., Sep)
- `{{%A}}` - Full weekday name (e.g., Monday)
- `{{%a}}` - Short weekday name (e.g., Mon)

#### 🎲 Random Number Parameters

- `{{rand}}` - Generates a random number in the range 0–1000000
- `{{rand_int}}` - Generates a large-range random integer
- `{{rand(min,max)}}` - Generates a random number within a specified range
- Example: `{{rand(1,100)}}` - Generates a random number between 1 and 100
- Example: `{{rand(1000,9999)}}` - Generates a 4-digit random number

#### 🔤 Random String Parameters

- `{{rand_str}}` - Generates an 8-character random string (letters + numbers)
- `{{rand_str(length)}}` - Generates a random string of specified length
- Example: `{{rand_str(16)}}` - Generates a 16-character random string
- `{{rand_str_secure}}` - Generates a 16-character cryptographically secure random string
- `{{rand_hex(length)}}` - Generates a random hexadecimal string of specified length
- Example: `{{rand_hex(8)}}` - Generates an 8-character hexadecimal string
- Example: `{{rand_hex(32)}}` - Generates a 32-character hexadecimal string

#### 🆔 UUID Parameters

- `{{uuid}}` - Generates a standard UUID (with hyphens)
- Example: `bf3655f7-8a93-4822-a458-2913a6fe4722`
- `{{uuid_short}}` - Generates a short UUID (without hyphens)
- Example: `14d44b7334014484bb81b015fb2401bf`

#### 🌍 Environment Variable Parameters

- `{{env(variable_name)}}` - Reads the value of an environment variable
- Example: `{{env(API_KEY)}}` - Reads the API_KEY environment variable
- Example: `{{env(VERSION)}}` - Reads the VERSION environment variable
- If the environment variable does not exist, returns an empty string

Ensure that the environment variable is set in your GitHub repository settings under "Settings" -> "Secrets and variables" -> "Actions".

#### 📊 Serial Number and Hash Parameters

- `{{seq}}` - Sequence number based on the current time (6-digit number)
- `{{seq_daily}}` - Daily sequence number (seconds since midnight)
- `{{hash_short}}` - Short hash value (6-digit hexadecimal)
- `{{hash_md5_like}}` - MD5-style long hash value (32-digit hexadecimal)

Below is an example configuration file:

```yaml
services:
- name: "Parameterized Service"
endpoints:
- url: "https://api.example.com/data?date={{%Y-%m-%d}}&rand={{rand(1,100)}}"
- url: "https://api.example.com/submit"
method: "POST"
headers:
Content-Type: application/json
X-Request-ID: "{{uuid}}"
body: '{"session_id": "{{rand_str(16)}}", "timestamp": "{{%s}}"}'
```

## Development

This project uses Makefile for local development and testing. You can run the project locally with the following command:
Expand Down
79 changes: 79 additions & 0 deletions README_CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ PongHub 是一个开源的服务状态监控网站,旨在帮助用户监控和

## 配置说明

### 基本配置

配置文件 `config.yaml` 的格式如下:

| 字段 | 类型 | 描述 | 必填 | 备注 |
Expand Down Expand Up @@ -91,6 +93,83 @@ services:
body: '{"key": "value"}'
```

### 特殊参数

ponghub 现已支持强大的参数化配置功能,允许在配置文件中使用多种类型的动态变量,这些变量会在程序运行时实时生成和解析。

#### 📅 日期时间参数
使用 `{{%格式}}` 格式定义日期时间参数:

- `{{%Y-%m-%d}}` - 当前日期,格式:2006-01-02(如:2025-09-22)
- `{{%H:%M:%S}}` - 当前时间,格式:15:04:05(如:17:30:45)
- `{{%s}}` - Unix时间戳(如:1727859600)
- `{{%Y}}` - 当前年份(如:2025)
- `{{%m}}` - 当前月份,格式:01-12
- `{{%d}}` - 当前日期,格式:01-31
- `{{%H}}` - 当前小时,格式:00-23
- `{{%M}}` - 当前分钟,格式:00-59
- `{{%S}}` - 当前秒数,格式:00-59
- `{{%B}}` - 完整月份名称(如:September)
- `{{%b}}` - 简短月份名称(如:Sep)
- `{{%A}}` - 完整星期名称(如:Monday)
- `{{%a}}` - 简短星期名称(如:Mon)

#### 🎲 随机数参数

- `{{rand}}` - 生成0-1000000范围的随机数
- `{{rand_int}}` - 生成大范围随机整数
- `{{rand(min,max)}}` - 生成指定范围的随机数
- 示例:`{{rand(1,100)}}` - 生成1-100之间的随机数
- 示例:`{{rand(1000,9999)}}` - 生成4位随机数

#### 🔤 随机字符串参数

- `{{rand_str}}` - 生成8位随机字符串(字母+数字)
- `{{rand_str(length)}}` - 生成指定长度的随机字符串
- 示例:`{{rand_str(16)}}` - 生成16位随机字符串
- `{{rand_str_secure}}` - 生成16位加密安全的随机字符串
- `{{rand_hex(length)}}` - 生成指定长度的十六进制随机字符串
- 示例:`{{rand_hex(8)}}` - 生成8位十六进制字符串
- 示例:`{{rand_hex(32)}}` - 生成32位十六进制字符串

#### 🆔 UUID参数

- `{{uuid}}` - 生成标准UUID(带连字符)
- 示例:`bf3655f7-8a93-4822-a458-2913a6fe4722`
- `{{uuid_short}}` - 生成短UUID(无连字符)
- 示例:`14d44b7334014484bb81b015fb2401bf`

#### 🌍 环境变量参数

- `{{env(变量名)}}` - 读取环境变量的值
- 示例:`{{env(API_KEY)}}` - 读取API_KEY环境变量
- 示例:`{{env(VERSION)}}` - 读取VERSION环境变量
- 如果环境变量不存在,返回空字符串

环境变量可通过 GitHub Actions 的 Repository Secrets 设置

#### 📊 序列号和哈希参数

- `{{seq}}` - 基于当前时间的序列号(6位数字)
- `{{seq_daily}}` - 每日序列号(自午夜起的秒数)
- `{{hash_short}}` - 短哈希值(6位十六进制)
- `{{hash_md5_like}}` - MD5风格的长哈希值(32位十六进制)

下面是一个示例配置文件:

```yaml
services:
- name: "Parameterized Service"
endpoints:
- url: "https://api.example.com/data?date={{%Y-%m-%d}}&rand={{rand(1,100)}}"
- url: "https://api.example.com/submit"
method: "POST"
headers:
Content-Type: application/json
X-Request-ID: "{{uuid}}"
body: '{"session_id": "{{rand_str(16)}}", "timestamp": "{{%s}}"}'
```

## 本地开发

本项目使用 Makefile 进行本地开发和测试。你可以使用以下命令在本地运行项目:
Expand Down
25 changes: 25 additions & 0 deletions cmd/ponghub/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ func TestMain_new(t *testing.T) {
}

// copyLogFile copies the log file from srcPath to dstPath.
// If srcPath doesn't exist, it creates an empty JSON object file at dstPath.
func copyLogFile(srcPath, dstPath string) error {
// remove dstPath if it exists
if _, err := os.Stat(dstPath); err == nil {
Expand All @@ -91,6 +92,24 @@ func copyLogFile(srcPath, dstPath string) error {
}
}

// Check if source file exists
if _, err := os.Stat(srcPath); os.IsNotExist(err) {
// Source file doesn't exist, create an empty JSON object file
dstFile, err := os.Create(dstPath)
if err != nil {
return err
}
defer func(dstFile *os.File) {
if err := dstFile.Close(); err != nil {
log.Println("Error closing destination file:", err)
}
}(dstFile)

// Write empty JSON object
_, err = dstFile.WriteString("{}")
return err
}

srcFile, err := os.Open(srcPath)
if err != nil {
return err
Expand Down Expand Up @@ -127,5 +146,11 @@ func TestMain(m *testing.M) {
if err := os.Chdir(root); err != nil {
panic(err)
}

// Ensure data directory exists
if err := os.MkdirAll("data", 0755); err != nil {
panic(err)
}

os.Exit(m.Run())
}
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ module github.com/wcy-dt/ponghub
go 1.24.5

require gopkg.in/yaml.v3 v3.0.1

require github.com/google/uuid v1.6.0 // indirect
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
Expand Down
13 changes: 13 additions & 0 deletions internal/checker/endpoints.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"strings"
"time"

"github.com/wcy-dt/ponghub/internal/common"
"github.com/wcy-dt/ponghub/internal/types/structures/checker"
"github.com/wcy-dt/ponghub/internal/types/structures/configure"
)
Expand All @@ -31,6 +32,16 @@ func checkEndpoint(cfg *configure.Endpoint, timeout int, maxRetryTimes int, serv
certRemainingDays := 0
isCertExpired := false

// Generate display URL for smart showing of template vs resolved URL
resolver := common.NewParameterResolver()
displayURL, highlightSegments := resolver.HighlightChanges(cfg.OriginalURL)
originalURL := cfg.OriginalURL
if originalURL == "" {
originalURL = cfg.URL
displayURL = cfg.URL
highlightSegments = nil
}

// Check SSL certificate if it's an HTTPS URL
if urlIsHTTPS {
remainingDays, expired, err := checkSSLCertificates(cfg.URL)
Expand Down Expand Up @@ -128,6 +139,8 @@ func checkEndpoint(cfg *configure.Endpoint, timeout int, maxRetryTimes int, serv
IsHTTPS: urlIsHTTPS,
CertRemainingDays: certRemainingDays,
IsCertExpired: isCertExpired,
DisplayURL: displayURL,
HighlightSegments: highlightSegments,
}
}

Expand Down
Loading