[优化] 更新包名

This commit is contained in:
ryan
2026-03-10 17:07:36 +08:00
parent 08e4de4898
commit 9d4f4450ca
52 changed files with 975 additions and 986 deletions
+53 -53
View File
@@ -1,54 +1,54 @@
name: Publish Docker image (amd64) name: Publish Docker image (amd64)
on: on:
push: push:
tags: tags:
- '*' - '*'
workflow_dispatch: workflow_dispatch:
inputs: inputs:
name: name:
description: 'reason' description: 'reason'
required: false required: false
jobs: jobs:
push_to_registries: push_to_registries:
name: Push Docker image to multiple registries name: Push Docker image to multiple registries
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions: permissions:
packages: write packages: write
contents: read contents: read
steps: steps:
- name: Check out the repo - name: Check out the repo
uses: actions/checkout@v3 uses: actions/checkout@v3
- name: Save version info - name: Save version info
run: | run: |
git describe --tags > VERSION git describe --tags > VERSION
- name: Log in to Docker Hub - name: Log in to Docker Hub
uses: docker/login-action@v2 uses: docker/login-action@v2
with: with:
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Log in to the Container registry - name: Log in to the Container registry
uses: docker/login-action@v2 uses: docker/login-action@v2
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.actor }} username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata (tags, labels) for Docker - name: Extract metadata (tags, labels) for Docker
id: meta id: meta
uses: docker/metadata-action@v4 uses: docker/metadata-action@v4
with: with:
images: | images: |
justsong/gin-template ghcr.io/rain-kl/atsflare
ghcr.io/${{ github.repository }} ghcr.io/${{ github.repository }}
- name: Build and push Docker images - name: Build and push Docker images
uses: docker/build-push-action@v3 uses: docker/build-push-action@v3
with: with:
context: . context: .
push: true push: true
tags: ${{ steps.meta.outputs.tags }} tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }} labels: ${{ steps.meta.outputs.labels }}
+61 -61
View File
@@ -1,62 +1,62 @@
name: Publish Docker image (arm64) name: Publish Docker image (arm64)
on: on:
push: push:
tags: tags:
- '*' - '*'
- '!*-alpha*' - '!*-alpha*'
workflow_dispatch: workflow_dispatch:
inputs: inputs:
name: name:
description: 'reason' description: 'reason'
required: false required: false
jobs: jobs:
push_to_registries: push_to_registries:
name: Push Docker image to multiple registries name: Push Docker image to multiple registries
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions: permissions:
packages: write packages: write
contents: read contents: read
steps: steps:
- name: Check out the repo - name: Check out the repo
uses: actions/checkout@v3 uses: actions/checkout@v3
- name: Save version info - name: Save version info
run: | run: |
git describe --tags > VERSION git describe --tags > VERSION
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v2 uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2 uses: docker/setup-buildx-action@v2
- name: Log in to Docker Hub - name: Log in to Docker Hub
uses: docker/login-action@v2 uses: docker/login-action@v2
with: with:
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Log in to the Container registry - name: Log in to the Container registry
uses: docker/login-action@v2 uses: docker/login-action@v2
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.actor }} username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata (tags, labels) for Docker - name: Extract metadata (tags, labels) for Docker
id: meta id: meta
uses: docker/metadata-action@v4 uses: docker/metadata-action@v4
with: with:
images: | images: |
justsong/gin-template ghcr.io/rain-kl/atsflare
ghcr.io/${{ github.repository }} ghcr.io/${{ github.repository }}
- name: Build and push Docker images - name: Build and push Docker images
uses: docker/build-push-action@v3 uses: docker/build-push-action@v3
with: with:
context: . context: .
platforms: linux/amd64,linux/arm64 platforms: linux/amd64,linux/arm64
push: true push: true
tags: ${{ steps.meta.outputs.tags }} tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }} labels: ${{ steps.meta.outputs.labels }}
+53 -53
View File
@@ -1,54 +1,54 @@
name: Linux Release name: Linux Release
permissions: permissions:
contents: write contents: write
on: on:
push: push:
tags: tags:
- '*' - '*'
- '!*-alpha*' - '!*-alpha*'
jobs: jobs:
release: release:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v3
with: with:
fetch-depth: 0 fetch-depth: 0
- uses: actions/setup-node@v3 - uses: actions/setup-node@v3
with: with:
node-version: 16 node-version: 16
- name: Build Frontend - name: Build Frontend
env: env:
CI: "" CI: ""
run: | run: |
cd web cd web
npm install npm install
REACT_APP_VERSION=$(git describe --tags) npm run build REACT_APP_VERSION=$(git describe --tags) npm run build
cd .. cd ..
- name: Set up Go - name: Set up Go
uses: actions/setup-go@v3 uses: actions/setup-go@v3
with: with:
go-version: '>=1.18.0' go-version: '>=1.18.0'
- name: Build Backend (amd64) - name: Build Backend (amd64)
run: | run: |
go mod download go mod download
go build -ldflags "-s -w -X 'gin-template/common.Version=$(git describe --tags)' -extldflags '-static'" -o gin-template go build -ldflags "-s -w -X 'atsflare/common.Version=$(git describe --tags)' -extldflags '-static'" -o atsflare
- name: Build Backend (arm64) - name: Build Backend (arm64)
run: | run: |
sudo apt-get update sudo apt-get update
sudo apt-get install gcc-aarch64-linux-gnu sudo apt-get install gcc-aarch64-linux-gnu
CC=aarch64-linux-gnu-gcc CGO_ENABLED=1 GOOS=linux GOARCH=arm64 go build -ldflags "-s -w -X 'gin-template/common.Version=$(git describe --tags)' -extldflags '-static'" -o gin-template-arm64 CC=aarch64-linux-gnu-gcc CGO_ENABLED=1 GOOS=linux GOARCH=arm64 go build -ldflags "-s -w -X 'atsflare/common.Version=$(git describe --tags)' -extldflags '-static'" -o atsflare-arm64
- name: Release - name: Release
uses: softprops/action-gh-release@v1 uses: softprops/action-gh-release@v1
if: startsWith(github.ref, 'refs/tags/') if: startsWith(github.ref, 'refs/tags/')
with: with:
files: | files: |
gin-template atsflare
gin-template-arm64 atsflare-arm64
draft: false draft: false
generate_release_notes: true generate_release_notes: true
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+45 -45
View File
@@ -1,45 +1,45 @@
name: macOS Release name: macOS Release
permissions: permissions:
contents: write contents: write
on: on:
push: push:
tags: tags:
- '*' - '*'
- '!*-alpha*' - '!*-alpha*'
jobs: jobs:
release: release:
runs-on: macos-latest runs-on: macos-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v3
with: with:
fetch-depth: 0 fetch-depth: 0
- uses: actions/setup-node@v3 - uses: actions/setup-node@v3
with: with:
node-version: 16 node-version: 16
- name: Build Frontend - name: Build Frontend
env: env:
CI: "" CI: ""
run: | run: |
cd web cd web
npm install npm install
REACT_APP_VERSION=$(git describe --tags) npm run build REACT_APP_VERSION=$(git describe --tags) npm run build
cd .. cd ..
- name: Set up Go - name: Set up Go
uses: actions/setup-go@v3 uses: actions/setup-go@v3
with: with:
go-version: '>=1.18.0' go-version: '>=1.18.0'
- name: Build Backend - name: Build Backend
run: | run: |
go mod download go mod download
go build -ldflags "-X 'gin-template/common.Version=$(git describe --tags)'" -o gin-template-macos go build -ldflags "-X 'atsflare/common.Version=$(git describe --tags)'" -o atsflare-macos
- name: Release - name: Release
uses: softprops/action-gh-release@v1 uses: softprops/action-gh-release@v1
if: startsWith(github.ref, 'refs/tags/') if: startsWith(github.ref, 'refs/tags/')
with: with:
files: gin-template-macos files: atsflare-macos
draft: false draft: false
generate_release_notes: true generate_release_notes: true
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+47 -47
View File
@@ -1,48 +1,48 @@
name: Windows Release name: Windows Release
permissions: permissions:
contents: write contents: write
on: on:
push: push:
tags: tags:
- '*' - '*'
- '!*-alpha*' - '!*-alpha*'
jobs: jobs:
release: release:
runs-on: windows-latest runs-on: windows-latest
defaults: defaults:
run: run:
shell: bash shell: bash
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v3
with: with:
fetch-depth: 0 fetch-depth: 0
- uses: actions/setup-node@v3 - uses: actions/setup-node@v3
with: with:
node-version: 16 node-version: 16
- name: Build Frontend - name: Build Frontend
env: env:
CI: "" CI: ""
run: | run: |
cd web cd web
npm install npm install
REACT_APP_VERSION=$(git describe --tags) npm run build REACT_APP_VERSION=$(git describe --tags) npm run build
cd .. cd ..
- name: Set up Go - name: Set up Go
uses: actions/setup-go@v3 uses: actions/setup-go@v3
with: with:
go-version: '>=1.18.0' go-version: '>=1.18.0'
- name: Build Backend - name: Build Backend
run: | run: |
go mod download go mod download
go build -ldflags "-s -w -X 'gin-template/common.Version=$(git describe --tags)'" -o gin-template.exe go build -ldflags "-s -w -X 'atsflare/common.Version=$(git describe --tags)'" -o atsflare.exe
- name: Release - name: Release
uses: softprops/action-gh-release@v1 uses: softprops/action-gh-release@v1
if: startsWith(github.ref, 'refs/tags/') if: startsWith(github.ref, 'refs/tags/')
with: with:
files: gin-template.exe files: atsflare.exe
draft: false draft: false
generate_release_notes: true generate_release_notes: true
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+92 -97
View File
@@ -1,98 +1,93 @@
<p align="right"> <p align="right">
<a href="./README.md">中文</a> | <strong>English</strong> <a href="./README.md">中文</a> | <strong>English</strong>
</p> </p>
<p align="center"> <p align="center">
<a href="https://github.com/songquanpeng/gin-template"><img src="https://raw.githubusercontent.com/songquanpeng/gin-template/main/web/public/logo.png" width="150" height="150" alt="gin-template logo"></a> <a href="https://github.com/Rain-kl/ATSFlare"><img src="https://raw.githubusercontent.com/Rain-kl/ATSFlare/main/atsf_server/web/public/logo.png" width="150" height="150" alt="ATSFlare logo"></a>
</p> </p>
<div align="center"> <div align="center">
# Gin Template # ATSFlare
_✨ Template for Gin & React projects ✨_ _✨ Internal Nginx control plane for reverse proxy management ✨_
</div> </div>
<p align="center"> <p align="center">
<a href="https://raw.githubusercontent.com/songquanpeng/gin-template/main/LICENSE"> <a href="https://raw.githubusercontent.com/Rain-kl/ATSFlare/main/LICENSE">
<img src="https://img.shields.io/github/license/songquanpeng/gin-template?color=brightgreen" alt="license"> <img src="https://img.shields.io/github/license/Rain-kl/ATSFlare?color=brightgreen" alt="license">
</a> </a>
<a href="https://github.com/songquanpeng/gin-template/releases/latest"> <a href="https://github.com/Rain-kl/ATSFlare/releases/latest">
<img src="https://img.shields.io/github/v/release/songquanpeng/gin-template?color=brightgreen&include_prereleases" alt="release"> <img src="https://img.shields.io/github/v/release/Rain-kl/ATSFlare?color=brightgreen&include_prereleases" alt="release">
</a> </a>
<a href="https://hub.docker.com/repository/docker/justsong/gin-template"> <a href="https://github.com/Rain-kl/ATSFlare/releases/latest">
<img src="https://img.shields.io/docker/pulls/justsong/gin-template?color=brightgreen" alt="docker pull"> <img src="https://img.shields.io/github/downloads/Rain-kl/ATSFlare/total?color=brightgreen&include_prereleases" alt="release">
</a> </a>
<a href="https://github.com/songquanpeng/gin-template/releases/latest"> <a href="https://goreportcard.com/report/github.com/Rain-kl/ATSFlare">
<img src="https://img.shields.io/github/downloads/songquanpeng/gin-template/total?color=brightgreen&include_prereleases" alt="release"> <img src="https://goreportcard.com/badge/github.com/Rain-kl/ATSFlare" alt="GoReportCard">
</a> </a>
<a href="https://goreportcard.com/report/github.com/songquanpeng/go-file"> </p>
<img src="https://goreportcard.com/badge/github.com/songquanpeng/gin-template" alt="GoReportCard">
</a> <p align="center">
</p> <a href="https://github.com/Rain-kl/ATSFlare/releases">Download</a>
·
<p align="center"> <a href="https://github.com/Rain-kl/ATSFlare/blob/main/README.en.md#deployment">Tutorial</a>
<a href="https://github.com/songquanpeng/gin-template/releases">Download</a> ·
· <a href="https://github.com/Rain-kl/ATSFlare/issues">Feedback</a>
<a href="https://github.com/songquanpeng/gin-template/blob/main/README.en.md#deployment">Tutorial</a> </p>
·
<a href="https://github.com/songquanpeng/gin-template/issues">Feedback</a> ## Features
· + [x] Built-in user management.
<a href="https://gin-template.vercel.app/">Demo</a> + [x] Built-in file management.
</p> + [x] [GitHub OAuth](https://github.com/settings/applications/new).
+ [x] WeChat official account authorization (need [wechat-server](https://github.com/songquanpeng/wechat-server)).
## Features + [x] Email verification & password reset.
+ [x] Built-in user management. + [x] Request rate limiting.
+ [x] Built-in file management. + [x] Static files caching.
+ [x] [GitHub OAuth](https://github.com/settings/applications/new). + [x] Mobile friendly UI.
+ [x] WeChat official account authorization (need [wechat-server](https://github.com/songquanpeng/wechat-server)). + [x] Token based authorization.
+ [x] Email verification & password reset. + [x] Use GitHub Actions to build releases & Docker images.
+ [x] Request rate limiting. + [x] Cloudflare Turnstile user validation.
+ [x] Static files caching.
+ [x] Mobile friendly UI. ## Deployment
+ [x] Token based authorization. ### Manual deployment
+ [x] Use GitHub Actions to build releases & Docker images. 1. Download built binary from [GitHub Releases](https://github.com/Rain-kl/ATSFlare/releases/latest) or build from source:
+ [x] Cloudflare Turnstile user validation. ```shell
git clone https://github.com/Rain-kl/ATSFlare.git
## Deployment go mod download
### Manual deployment go build -ldflags "-s -w" -o atsflare
1. Download built binary from [GitHub Releases](https://github.com/songquanpeng/gin-template/releases/latest) or build from source: ````
```shell 2. Run it:
git clone https://github.com/songquanpeng/gin-template.git ```shell
go mod download chmod u+x atsflare
go build -ldflags "-s -w" -o gin-template ./atsflare --port 3000 --log-dir ./logs
```` ```
2. Run it: 3. Visit [http://localhost:3000/](http://localhost:3000/) and login. The username for the initial account is `root` and the password is `123456`.
```shell
chmod u+x gin-template ### Deploy with Docker
./gin-template --port 3000 --log-dir ./logs Execute: `docker run -d --restart always -p 3000:3000 -v /home/ubuntu/data/atsflare:/data -v /etc/ssl/certs:/etc/ssl/certs:ro ghcr.io/rain-kl/atsflare`
```
3. Visit [http://localhost:3000/](http://localhost:3000/) and login. The username for the initial account is `root` and the password is `123456`. Data will be saved in `/home/ubuntu/data/atsflare`.
### Deploy with Docker ## Configurations
Execute: `docker run -d --restart always -p 3000:3000 -v /home/ubuntu/data/gin-template:/data -v /etc/ssl/certs:/etc/ssl/certs:ro justsong/gin-template` The system works out of the box.
Data will be saved in `/home/ubuntu/data/gin-template`. You can configure the system by set environment variables or specify command line arguments.
## Configurations After the system starts, use `root` user to log in to the system and do further configuration.
The system works out of the box.
### Environment Variables
You can configure the system by set environment variables or specify command line arguments. 1. `REDIS_CONN_STRING`: when set, Redis will be used as the storage for request rate limitation instead of memory storage.
+ Example: `REDIS_CONN_STRING=redis://default:redispw@localhost:49153`
After the system starts, use `root` user to log in to the system and do further configuration. 2. `SESSION_SECRET`: when set, a fixed session key will be used so that the logged-in users' cookie remains valid across system reboots.
+ Example: `SESSION_SECRET=random_string`
### Environment Variables 3. `SQL_DSN`: when set, the target SQL database will be used instead of SQLite.
1. `REDIS_CONN_STRING`: when set, Redis will be used as the storage for request rate limitation instead of memory storage. + Example: `SQL_DSN=root:123456@tcp(localhost:3306)/atsflare`
+ Example: `REDIS_CONN_STRING=redis://default:redispw@localhost:49153`
2. `SESSION_SECRET`: when set, a fixed session key will be used so that the logged-in users' cookie remains valid across system reboots. ### Command line Arguments
+ Example: `SESSION_SECRET=random_string` 1. `--port <port_number>`: specify the port number, the default value is `3000`.
3. `SQL_DSN`: when set, the target SQL database will be used instead of SQLite. + Example: `--port 3000`
+ Example: `SQL_DSN=root:123456@tcp(localhost:3306)/gin-template` 2. `--log-dir <log_dir>`: specify the log dir, if not set, the log won't be saved.
+ Example: `--log-dir ./logs`
### Command line Arguments
1. `--port <port_number>`: specify the port number, the default value is `3000`.
+ Example: `--port 3000`
2. `--log-dir <log_dir>`: specify the log dir, if not set, the log won't be saved.
+ Example: `--log-dir ./logs`
3. `--version`: print the version and exit. 3. `--version`: print the version and exit.
+31 -31
View File
@@ -1,31 +1,31 @@
FROM node:16 as builder FROM node:16 as builder
WORKDIR /build WORKDIR /build
COPY ./web . COPY ./web .
COPY ./VERSION . COPY ./VERSION .
RUN npm install RUN npm install
RUN REACT_APP_VERSION=$(cat VERSION) npm run build RUN REACT_APP_VERSION=$(cat VERSION) npm run build
FROM golang AS builder2 FROM golang AS builder2
ENV GO111MODULE=on \ ENV GO111MODULE=on \
CGO_ENABLED=1 \ CGO_ENABLED=1 \
GOOS=linux GOOS=linux
WORKDIR /build WORKDIR /build
COPY . . COPY . .
COPY --from=builder /build/build ./web/build COPY --from=builder /build/build ./web/build
RUN go mod download RUN go mod download
RUN go build -ldflags "-s -w -X 'gin-template/common.Version=$(cat VERSION)' -extldflags '-static'" -o gin-template RUN go build -ldflags "-s -w -X 'atsflare/common.Version=$(cat VERSION)' -extldflags '-static'" -o atsflare
FROM alpine FROM alpine
RUN apk update \ RUN apk update \
&& apk upgrade \ && apk upgrade \
&& apk add --no-cache ca-certificates tzdata \ && apk add --no-cache ca-certificates tzdata \
&& update-ca-certificates 2>/dev/null || true && update-ca-certificates 2>/dev/null || true
ENV PORT=3000 ENV PORT=3000
COPY --from=builder2 /build/gin-template / COPY --from=builder2 /build/atsflare /
EXPOSE 3000 EXPOSE 3000
WORKDIR /data WORKDIR /data
ENTRYPOINT ["/gin-template"] ENTRYPOINT ["/atsflare"]
+3 -3
View File
@@ -20,10 +20,10 @@ var (
var UploadPath = "upload" var UploadPath = "upload"
func printHelp() { func printHelp() {
fmt.Println("Gin Template " + Version + " - Your next project starts from here.") fmt.Println("ATSFlare " + Version + " - Internal Nginx Control Plane.")
fmt.Println("Copyright (C) 2023 JustSong. All rights reserved.") fmt.Println("Copyright (C) 2023 JustSong. All rights reserved.")
fmt.Println("GitHub: https://github.com/songquanpeng/gin-template") fmt.Println("GitHub: https://github.com/Rain-kl/ATSFlare")
fmt.Println("Usage: gin-template [--port <port>] [--log-dir <log directory>] [--version] [--help]") fmt.Println("Usage: atsflare [--port <port>] [--log-dir <log directory>] [--version] [--help]")
} }
func init() { func init() {
+2 -2
View File
@@ -1,9 +1,9 @@
package controller package controller
import ( import (
"atsflare/model"
"atsflare/service"
"encoding/json" "encoding/json"
"gin-template/model"
"gin-template/service"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"net/http" "net/http"
) )
+1 -1
View File
@@ -1,7 +1,7 @@
package controller package controller
import ( import (
"gin-template/service" "atsflare/service"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"net/http" "net/http"
"strconv" "strconv"
+2 -2
View File
@@ -1,9 +1,9 @@
package controller package controller
import ( import (
"atsflare/common"
"atsflare/model"
"fmt" "fmt"
"gin-template/common"
"gin-template/model"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"net/http" "net/http"
"path/filepath" "path/filepath"
+2 -2
View File
@@ -1,12 +1,12 @@
package controller package controller
import ( import (
"atsflare/common"
"atsflare/model"
"bytes" "bytes"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"gin-template/common"
"gin-template/model"
"github.com/gin-contrib/sessions" "github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"net/http" "net/http"
+1 -1
View File
@@ -1,8 +1,8 @@
package controller package controller
import ( import (
"atsflare/service"
"encoding/json" "encoding/json"
"gin-template/service"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"net/http" "net/http"
"strconv" "strconv"
+2 -2
View File
@@ -1,10 +1,10 @@
package controller package controller
import ( import (
"atsflare/common"
"atsflare/model"
"encoding/json" "encoding/json"
"fmt" "fmt"
"gin-template/common"
"gin-template/model"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"net/http" "net/http"
) )
+1 -1
View File
@@ -1,8 +1,8 @@
package controller package controller
import ( import (
"atsflare/service"
"encoding/json" "encoding/json"
"gin-template/service"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"net/http" "net/http"
"strconv" "strconv"
+2 -2
View File
@@ -1,9 +1,9 @@
package controller package controller
import ( import (
"atsflare/common"
"atsflare/model"
"encoding/json" "encoding/json"
"gin-template/common"
"gin-template/model"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"net/http" "net/http"
"strings" "strings"
+1 -1
View File
@@ -1,8 +1,8 @@
package controller package controller
import ( import (
"atsflare/service"
"encoding/json" "encoding/json"
"gin-template/service"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"net/http" "net/http"
"strconv" "strconv"
+1 -1
View File
@@ -1,8 +1,8 @@
package controller package controller
import ( import (
"atsflare/service"
"encoding/json" "encoding/json"
"gin-template/service"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"net/http" "net/http"
"strconv" "strconv"
+2 -2
View File
@@ -1,9 +1,9 @@
package controller package controller
import ( import (
"atsflare/common"
"atsflare/model"
"encoding/json" "encoding/json"
"gin-template/common"
"gin-template/model"
"github.com/gin-contrib/sessions" "github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/google/uuid" "github.com/google/uuid"
+2 -2
View File
@@ -1,11 +1,11 @@
package controller package controller
import ( import (
"atsflare/common"
"atsflare/model"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"gin-template/common"
"gin-template/model"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"net/http" "net/http"
"strconv" "strconv"
+1 -1
View File
@@ -1,4 +1,4 @@
module gin-template module atsflare
// +heroku goVersion go1.18 // +heroku goVersion go1.18
go 1.18 go 1.18
+85 -85
View File
@@ -1,85 +1,85 @@
package main package main
import ( import (
"embed" "atsflare/common"
"fmt" "atsflare/middleware"
"gin-template/common" "atsflare/model"
"gin-template/middleware" "atsflare/router"
"gin-template/model" "embed"
"gin-template/router" "fmt"
"github.com/gin-contrib/sessions" "github.com/gin-contrib/sessions"
"github.com/gin-contrib/sessions/cookie" "github.com/gin-contrib/sessions/cookie"
"github.com/gin-contrib/sessions/redis" "github.com/gin-contrib/sessions/redis"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"log" "log"
"os" "os"
"strconv" "strconv"
) )
//go:embed web/build //go:embed web/build
var buildFS embed.FS var buildFS embed.FS
//go:embed web/build/index.html //go:embed web/build/index.html
var indexPage []byte var indexPage []byte
func main() { func main() {
common.SetupGinLog() common.SetupGinLog()
common.SysLog("Gin Template " + common.Version + " started") common.SysLog("ATSFlare " + common.Version + " started")
if os.Getenv("GIN_MODE") != "debug" { if os.Getenv("GIN_MODE") != "debug" {
gin.SetMode(gin.ReleaseMode) gin.SetMode(gin.ReleaseMode)
} }
// Initialize SQL Database // Initialize SQL Database
err := model.InitDB() err := model.InitDB()
if err != nil { if err != nil {
common.FatalLog(err) common.FatalLog(err)
} }
defer func() { defer func() {
err := model.CloseDB() err := model.CloseDB()
if err != nil { if err != nil {
common.FatalLog(err) common.FatalLog(err)
} }
}() }()
// Initialize Redis // Initialize Redis
err = common.InitRedisClient() err = common.InitRedisClient()
if err != nil { if err != nil {
common.FatalLog(err) common.FatalLog(err)
} }
// Initialize options // Initialize options
model.InitOptionMap() model.InitOptionMap()
// Initialize HTTP server // Initialize HTTP server
server := gin.Default() server := gin.Default()
//server.Use(gzip.Gzip(gzip.DefaultCompression)) //server.Use(gzip.Gzip(gzip.DefaultCompression))
server.Use(middleware.CORS()) server.Use(middleware.CORS())
// Initialize session store // Initialize session store
if common.RedisEnabled { if common.RedisEnabled {
opt := common.ParseRedisOption() opt := common.ParseRedisOption()
store, _ := redis.NewStore(opt.MinIdleConns, opt.Network, opt.Addr, opt.Password, []byte(common.SessionSecret)) store, _ := redis.NewStore(opt.MinIdleConns, opt.Network, opt.Addr, opt.Password, []byte(common.SessionSecret))
server.Use(sessions.Sessions("session", store)) server.Use(sessions.Sessions("session", store))
} else { } else {
store := cookie.NewStore([]byte(common.SessionSecret)) store := cookie.NewStore([]byte(common.SessionSecret))
server.Use(sessions.Sessions("session", store)) server.Use(sessions.Sessions("session", store))
} }
router.SetRouter(server, buildFS, indexPage) router.SetRouter(server, buildFS, indexPage)
var port = os.Getenv("PORT") var port = os.Getenv("PORT")
if port == "" { if port == "" {
port = strconv.Itoa(*common.Port) port = strconv.Itoa(*common.Port)
} }
common.SysLog(fmt.Sprintf("server config: port=%s gin_mode=%s sqlite_path=%s redis_enabled=%t upload_path=%s log_dir=%s agent_token_configured=%t node_offline_threshold=%s", port, gin.Mode(), common.SQLitePath, common.RedisEnabled, common.UploadPath, valueOrDefault(*common.LogDir, "stdout"), common.AgentToken != "", common.NodeOfflineThreshold)) common.SysLog(fmt.Sprintf("server config: port=%s gin_mode=%s sqlite_path=%s redis_enabled=%t upload_path=%s log_dir=%s agent_token_configured=%t node_offline_threshold=%s", port, gin.Mode(), common.SQLitePath, common.RedisEnabled, common.UploadPath, valueOrDefault(*common.LogDir, "stdout"), common.AgentToken != "", common.NodeOfflineThreshold))
common.SysLog(fmt.Sprintf("server listening on :%s", port)) common.SysLog(fmt.Sprintf("server listening on :%s", port))
err = server.Run(":" + port) err = server.Run(":" + port)
if err != nil { if err != nil {
log.Println(err) log.Println(err)
} }
} }
func valueOrDefault(value string, fallback string) string { func valueOrDefault(value string, fallback string) string {
if value == "" { if value == "" {
return fallback return fallback
} }
return value return value
} }
+1 -1
View File
@@ -1,7 +1,7 @@
package middleware package middleware
import ( import (
"gin-template/service" "atsflare/service"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"net/http" "net/http"
) )
+2 -2
View File
@@ -1,8 +1,8 @@
package middleware package middleware
import ( import (
"gin-template/common" "atsflare/common"
"gin-template/model" "atsflare/model"
"github.com/gin-contrib/sessions" "github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"net/http" "net/http"
+1 -1
View File
@@ -7,6 +7,6 @@ import (
func CORS() gin.HandlerFunc { func CORS() gin.HandlerFunc {
config := cors.DefaultConfig() config := cors.DefaultConfig()
config.AllowOrigins = []string{"https://gin-template.vercel.app", "http://localhost:3000/"} config.AllowOrigins = []string{"https://atsflare.vercel.app", "http://localhost:3000/"}
return cors.New(config) return cors.New(config)
} }
+1 -1
View File
@@ -1,9 +1,9 @@
package middleware package middleware
import ( import (
"atsflare/common"
"context" "context"
"fmt" "fmt"
"gin-template/common"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"net/http" "net/http"
"time" "time"
+1 -1
View File
@@ -1,8 +1,8 @@
package middleware package middleware
import ( import (
"atsflare/common"
"encoding/json" "encoding/json"
"gin-template/common"
"github.com/gin-contrib/sessions" "github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"net/http" "net/http"
+50 -50
View File
@@ -1,50 +1,50 @@
package model package model
import ( import (
"gin-template/common" "atsflare/common"
"gorm.io/gorm" "gorm.io/gorm"
"os" "os"
"path" "path"
) )
type File struct { type File struct {
Id int `json:"id"` Id int `json:"id"`
Filename string `json:"filename" gorm:"index"` Filename string `json:"filename" gorm:"index"`
Description string `json:"description"` Description string `json:"description"`
Uploader string `json:"uploader" gorm:"index"` Uploader string `json:"uploader" gorm:"index"`
UploaderId int `json:"uploader_id" gorm:"index"` UploaderId int `json:"uploader_id" gorm:"index"`
Link string `json:"link" gorm:"unique;index"` Link string `json:"link" gorm:"unique;index"`
UploadTime string `json:"upload_time"` UploadTime string `json:"upload_time"`
DownloadCounter int `json:"download_counter"` DownloadCounter int `json:"download_counter"`
} }
func GetAllFiles(startIdx int, num int) ([]*File, error) { func GetAllFiles(startIdx int, num int) ([]*File, error) {
var files []*File var files []*File
var err error var err error
err = DB.Order("id desc").Limit(num).Offset(startIdx).Find(&files).Error err = DB.Order("id desc").Limit(num).Offset(startIdx).Find(&files).Error
return files, err return files, err
} }
func SearchFiles(keyword string) (files []*File, err error) { func SearchFiles(keyword string) (files []*File, err error) {
err = DB.Select([]string{"id", "filename", "description", "uploader", "uploader_id", "link", "upload_time", "download_counter"}).Where( err = DB.Select([]string{"id", "filename", "description", "uploader", "uploader_id", "link", "upload_time", "download_counter"}).Where(
"filename LIKE ? or uploader LIKE ? or uploader_id = ?", keyword+"%", keyword+"%", keyword).Find(&files).Error "filename LIKE ? or uploader LIKE ? or uploader_id = ?", keyword+"%", keyword+"%", keyword).Find(&files).Error
return files, err return files, err
} }
func (file *File) Insert() error { func (file *File) Insert() error {
var err error var err error
err = DB.Create(file).Error err = DB.Create(file).Error
return err return err
} }
// Delete Make sure link is valid! Because we will use os.Remove to delete it! // Delete Make sure link is valid! Because we will use os.Remove to delete it!
func (file *File) Delete() error { func (file *File) Delete() error {
var err error var err error
err = DB.Delete(file).Error err = DB.Delete(file).Error
err = os.Remove(path.Join(common.UploadPath, file.Link)) err = os.Remove(path.Join(common.UploadPath, file.Link))
return err return err
} }
func UpdateDownloadCounter(link string) { func UpdateDownloadCounter(link string) {
DB.Model(&File{}).Where("link = ?", link).UpdateColumn("download_counter", gorm.Expr("download_counter + 1")) DB.Model(&File{}).Where("link = ?", link).UpdateColumn("download_counter", gorm.Expr("download_counter + 1"))
} }
+1 -1
View File
@@ -1,7 +1,7 @@
package model package model
import ( import (
"gin-template/common" "atsflare/common"
"github.com/glebarez/sqlite" "github.com/glebarez/sqlite"
"gorm.io/driver/mysql" "gorm.io/driver/mysql"
"gorm.io/gorm" "gorm.io/gorm"
+1 -1
View File
@@ -1,7 +1,7 @@
package model package model
import ( import (
"gin-template/common" "atsflare/common"
"strconv" "strconv"
"strings" "strings"
) )
+1 -1
View File
@@ -1,8 +1,8 @@
package model package model
import ( import (
"atsflare/common"
"errors" "errors"
"gin-template/common"
"strings" "strings"
) )
+2 -2
View File
@@ -1,8 +1,8 @@
package router package router
import ( import (
"gin-template/controller" "atsflare/controller"
"gin-template/middleware" "atsflare/middleware"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
+4 -4
View File
@@ -1,6 +1,10 @@
package router_test package router_test
import ( import (
"atsflare/common"
"atsflare/model"
"atsflare/router"
"atsflare/service"
"bytes" "bytes"
"crypto/rand" "crypto/rand"
"crypto/rsa" "crypto/rsa"
@@ -8,10 +12,6 @@ import (
"crypto/x509/pkix" "crypto/x509/pkix"
"encoding/json" "encoding/json"
"encoding/pem" "encoding/pem"
"gin-template/common"
"gin-template/model"
"gin-template/router"
"gin-template/service"
"github.com/gin-contrib/sessions" "github.com/gin-contrib/sessions"
"github.com/gin-contrib/sessions/cookie" "github.com/gin-contrib/sessions/cookie"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@@ -1,8 +1,8 @@
package router_test package router_test
import ( import (
"gin-template/common" "atsflare/common"
"gin-template/router" "atsflare/router"
"github.com/gin-contrib/sessions" "github.com/gin-contrib/sessions"
"github.com/gin-contrib/sessions/cookie" "github.com/gin-contrib/sessions/cookie"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
+4 -4
View File
@@ -1,12 +1,12 @@
package router_test package router_test
import ( import (
"atsflare/common"
"atsflare/model"
"atsflare/router"
"atsflare/service"
"bytes" "bytes"
"encoding/json" "encoding/json"
"gin-template/common"
"gin-template/model"
"gin-template/router"
"gin-template/service"
"github.com/gin-contrib/sessions" "github.com/gin-contrib/sessions"
"github.com/gin-contrib/sessions/cookie" "github.com/gin-contrib/sessions/cookie"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
+3 -3
View File
@@ -1,10 +1,10 @@
package router package router
import ( import (
"atsflare/common"
"atsflare/controller"
"atsflare/middleware"
"embed" "embed"
"gin-template/common"
"gin-template/controller"
"gin-template/middleware"
"github.com/gin-contrib/static" "github.com/gin-contrib/static"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"net/http" "net/http"
+2 -2
View File
@@ -1,10 +1,10 @@
package service package service
import ( import (
"atsflare/common"
"atsflare/model"
"encoding/json" "encoding/json"
"errors" "errors"
"gin-template/common"
"gin-template/model"
"strings" "strings"
"time" "time"
+1 -1
View File
@@ -1,12 +1,12 @@
package service package service
import ( import (
"atsflare/model"
"crypto/sha256" "crypto/sha256"
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"gin-template/model"
"sort" "sort"
"strings" "strings"
"time" "time"
+2 -2
View File
@@ -1,13 +1,13 @@
package service package service
import ( import (
"atsflare/common"
"atsflare/model"
"crypto/rand" "crypto/rand"
"crypto/rsa" "crypto/rsa"
"crypto/x509" "crypto/x509"
"crypto/x509/pkix" "crypto/x509/pkix"
"encoding/pem" "encoding/pem"
"gin-template/common"
"gin-template/model"
"math/big" "math/big"
"path/filepath" "path/filepath"
"strings" "strings"
+1 -1
View File
@@ -1,9 +1,9 @@
package service package service
import ( import (
"atsflare/model"
"errors" "errors"
"fmt" "fmt"
"gin-template/model"
"sort" "sort"
"strings" "strings"
"unicode" "unicode"
+2 -2
View File
@@ -1,11 +1,11 @@
package service package service
import ( import (
"atsflare/common"
"atsflare/model"
"crypto/rand" "crypto/rand"
"encoding/hex" "encoding/hex"
"errors" "errors"
"gin-template/common"
"gin-template/model"
"strings" "strings"
"time" "time"
) )
+1 -1
View File
@@ -1,9 +1,9 @@
package service package service
import ( import (
"atsflare/model"
"encoding/json" "encoding/json"
"errors" "errors"
"gin-template/model"
"net/url" "net/url"
"regexp" "regexp"
"strings" "strings"
+1 -1
View File
@@ -1,10 +1,10 @@
package service package service
import ( import (
"atsflare/model"
"crypto/tls" "crypto/tls"
"errors" "errors"
"fmt" "fmt"
"gin-template/model"
"mime/multipart" "mime/multipart"
"strings" "strings"
) )
+1 -3
View File
@@ -41,9 +41,7 @@ function App() {
const res = await API.get('/api/status'); const res = await API.get('/api/status');
const { success, data } = res.data; const { success, data } = res.data;
if (success) { if (success) {
console.log( console.log(`GitHub 仓库地址:https://github.com/Rain-kl/ATSFlare`);
`GitHub 仓库地址:https://github.com/songquanpeng/gin-template`
);
localStorage.setItem('status', JSON.stringify(data)); localStorage.setItem('status', JSON.stringify(data));
statusDispatch({ type: 'set', payload: data }); statusDispatch({ type: 'set', payload: data });
localStorage.setItem('system_name', data.system_name); localStorage.setItem('system_name', data.system_name);
+38 -41
View File
@@ -1,41 +1,38 @@
import React from 'react'; import React from 'react';
import { Container, Segment } from 'semantic-ui-react'; import { Container, Segment } from 'semantic-ui-react';
import { getFooterHTML, getSystemName } from '../helpers'; import { getFooterHTML, getSystemName } from '../helpers';
const Footer = () => { const Footer = () => {
const systemName = getSystemName(); const systemName = getSystemName();
const footer = getFooterHTML(); const footer = getFooterHTML();
return ( return (
<Segment vertical> <Segment vertical>
<Container textAlign='center'> <Container textAlign='center'>
{footer ? ( {footer ? (
<div <div
className='custom-footer' className='custom-footer'
dangerouslySetInnerHTML={{ __html: footer }} dangerouslySetInnerHTML={{ __html: footer }}
></div> ></div>
) : ( ) : (
<div className='custom-footer'> <div className='custom-footer'>
<a <a
href='https://github.com/songquanpeng/gin-template' href='https://github.com/Rain-kl/ATSFlare'
target='_blank' target='_blank'
> rel='noreferrer'
{systemName} {process.env.REACT_APP_VERSION}{' '} >
</a> {systemName} {process.env.REACT_APP_VERSION}{' '}
由{' '} </a>
<a href='https://github.com/songquanpeng' target='_blank'> 开源于 GitHub,源代码遵循{' '}
JustSong <a href='https://opensource.org/licenses/mit-license.php'>
</a>{' '} MIT 协议
构建,源代码遵循{' '} </a>
<a href='https://opensource.org/licenses/mit-license.php'> </div>
MIT 协议 )}
</a> </Container>
</div> </Segment>
)} );
</Container> };
</Segment>
); export default Footer;
};
export default Footer;
+189 -190
View File
@@ -1,190 +1,189 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { Button, Divider, Form, Grid, Header, Modal } from 'semantic-ui-react'; import { Button, Divider, Form, Grid, Header, Modal } from 'semantic-ui-react';
import { API, showError, showSuccess } from '../helpers'; import { API, showError, showSuccess } from '../helpers';
import { marked } from 'marked'; import { marked } from 'marked';
const OtherSetting = () => { const OtherSetting = () => {
let [inputs, setInputs] = useState({ let [inputs, setInputs] = useState({
Footer: '', Footer: '',
Notice: '', Notice: '',
About: '', About: '',
SystemName: '', SystemName: '',
HomePageLink: '', HomePageLink: '',
}); });
let [loading, setLoading] = useState(false); let [loading, setLoading] = useState(false);
const [showUpdateModal, setShowUpdateModal] = useState(false); const [showUpdateModal, setShowUpdateModal] = useState(false);
const [updateData, setUpdateData] = useState({ const [updateData, setUpdateData] = useState({
tag_name: '', tag_name: '',
content: '', content: '',
}); });
const getOptions = async () => { const getOptions = async () => {
const res = await API.get('/api/option/'); const res = await API.get('/api/option/');
const { success, message, data } = res.data; const { success, message, data } = res.data;
if (success) { if (success) {
let newInputs = {}; let newInputs = {};
data.forEach((item) => { data.forEach((item) => {
if (item.key in inputs) { if (item.key in inputs) {
newInputs[item.key] = item.value; newInputs[item.key] = item.value;
} }
}); });
setInputs(newInputs); setInputs(newInputs);
} else { } else {
showError(message); showError(message);
} }
}; };
useEffect(() => { useEffect(() => {
getOptions().then(); getOptions().then();
}, []); }, []);
const updateOption = async (key, value) => { const updateOption = async (key, value) => {
setLoading(true); setLoading(true);
const res = await API.put('/api/option', { const res = await API.put('/api/option', {
key, key,
value, value,
}); });
const { success, message } = res.data; const { success, message } = res.data;
if (success) { if (success) {
setInputs((inputs) => ({ ...inputs, [key]: value })); setInputs((inputs) => ({ ...inputs, [key]: value }));
} else { } else {
showError(message); showError(message);
} }
setLoading(false); setLoading(false);
}; };
const handleInputChange = async (e, { name, value }) => { const handleInputChange = async (e, { name, value }) => {
setInputs((inputs) => ({ ...inputs, [name]: value })); setInputs((inputs) => ({ ...inputs, [name]: value }));
}; };
const submitNotice = async () => { const submitNotice = async () => {
await updateOption('Notice', inputs.Notice); await updateOption('Notice', inputs.Notice);
}; };
const submitFooter = async () => { const submitFooter = async () => {
await updateOption('Footer', inputs.Footer); await updateOption('Footer', inputs.Footer);
}; };
const submitSystemName = async () => { const submitSystemName = async () => {
await updateOption('SystemName', inputs.SystemName); await updateOption('SystemName', inputs.SystemName);
}; };
const submitHomePageLink = async () => { const submitHomePageLink = async () => {
await updateOption('HomePageLink', inputs.HomePageLink); await updateOption('HomePageLink', inputs.HomePageLink);
}; };
const submitAbout = async () => { const submitAbout = async () => {
await updateOption('About', inputs.About); await updateOption('About', inputs.About);
}; };
const openGitHubRelease = () => { const openGitHubRelease = () => {
window.location = window.location = 'https://github.com/Rain-kl/ATSFlare/releases/latest';
'https://github.com/songquanpeng/gin-template/releases/latest'; };
};
const checkUpdate = async () => {
const checkUpdate = async () => { const res = await API.get(
const res = await API.get( 'https://api.github.com/repos/Rain-kl/ATSFlare/releases/latest'
'https://api.github.com/repos/songquanpeng/gin-template/releases/latest' );
); const { tag_name, body } = res.data;
const { tag_name, body } = res.data; if (tag_name === process.env.REACT_APP_VERSION) {
if (tag_name === process.env.REACT_APP_VERSION) { showSuccess(`已是最新版本:${tag_name}`);
showSuccess(`已是最新版本:${tag_name}`); } else {
} else { setUpdateData({
setUpdateData({ tag_name: tag_name,
tag_name: tag_name, content: marked.parse(body),
content: marked.parse(body), });
}); setShowUpdateModal(true);
setShowUpdateModal(true); }
} };
};
return (
return ( <Grid columns={1}>
<Grid columns={1}> <Grid.Column>
<Grid.Column> <Form loading={loading}>
<Form loading={loading}> <Header as='h3'>通用设置</Header>
<Header as='h3'>通用设置</Header> <Form.Button onClick={checkUpdate}>检查更新</Form.Button>
<Form.Button onClick={checkUpdate}>检查更新</Form.Button> <Form.Group widths='equal'>
<Form.Group widths='equal'> <Form.TextArea
<Form.TextArea label='公告'
label='公告' placeholder='在此输入新的公告内容'
placeholder='在此输入新的公告内容' value={inputs.Notice}
value={inputs.Notice} name='Notice'
name='Notice' onChange={handleInputChange}
onChange={handleInputChange} style={{ minHeight: 150, fontFamily: 'JetBrains Mono, Consolas' }}
style={{ minHeight: 150, fontFamily: 'JetBrains Mono, Consolas' }} />
/> </Form.Group>
</Form.Group> <Form.Button onClick={submitNotice}>保存公告</Form.Button>
<Form.Button onClick={submitNotice}>保存公告</Form.Button> <Divider />
<Divider /> <Header as='h3'>个性化设置</Header>
<Header as='h3'>个性化设置</Header> <Form.Group widths='equal'>
<Form.Group widths='equal'> <Form.Input
<Form.Input label='系统名称'
label='系统名称' placeholder='在此输入系统名称'
placeholder='在此输入系统名称' value={inputs.SystemName}
value={inputs.SystemName} name='SystemName'
name='SystemName' onChange={handleInputChange}
onChange={handleInputChange} />
/> </Form.Group>
</Form.Group> <Form.Button onClick={submitSystemName}>设置系统名称</Form.Button>
<Form.Button onClick={submitSystemName}>设置系统名称</Form.Button> <Form.Group widths='equal'>
<Form.Group widths='equal'> <Form.Input
<Form.Input label='首页链接'
label='首页链接' placeholder='在此输入首页链接,设置后将通过 iframe 方式嵌入该网页'
placeholder='在此输入首页链接,设置后将通过 iframe 方式嵌入该网页' value={inputs.HomePageLink}
value={inputs.HomePageLink} name='HomePageLink'
name='HomePageLink' onChange={handleInputChange}
onChange={handleInputChange} type='url'
type='url' />
/> </Form.Group>
</Form.Group> <Form.Button onClick={submitHomePageLink}>设置首页链接</Form.Button>
<Form.Button onClick={submitHomePageLink}>设置首页链接</Form.Button> <Form.Group widths='equal'>
<Form.Group widths='equal'> <Form.TextArea
<Form.TextArea label='关于'
label='关于' placeholder='在此输入新的关于内容,支持 Markdown & HTML 代码'
placeholder='在此输入新的关于内容,支持 Markdown & HTML 代码' value={inputs.About}
value={inputs.About} name='About'
name='About' onChange={handleInputChange}
onChange={handleInputChange} style={{ minHeight: 150, fontFamily: 'JetBrains Mono, Consolas' }}
style={{ minHeight: 150, fontFamily: 'JetBrains Mono, Consolas' }} />
/> </Form.Group>
</Form.Group> <Form.Button onClick={submitAbout}>保存关于</Form.Button>
<Form.Button onClick={submitAbout}>保存关于</Form.Button> <Form.Group widths='equal'>
<Form.Group widths='equal'> <Form.Input
<Form.Input label='页脚'
label='页脚' placeholder='在此输入新的页脚,留空则使用默认页脚,支持 HTML 代码'
placeholder='在此输入新的页脚,留空则使用默认页脚,支持 HTML 代码' value={inputs.Footer}
value={inputs.Footer} name='Footer'
name='Footer' onChange={handleInputChange}
onChange={handleInputChange} />
/> </Form.Group>
</Form.Group> <Form.Button onClick={submitFooter}>设置页脚</Form.Button>
<Form.Button onClick={submitFooter}>设置页脚</Form.Button> </Form>
</Form> </Grid.Column>
</Grid.Column> <Modal
<Modal onClose={() => setShowUpdateModal(false)}
onClose={() => setShowUpdateModal(false)} onOpen={() => setShowUpdateModal(true)}
onOpen={() => setShowUpdateModal(true)} open={showUpdateModal}
open={showUpdateModal} >
> <Modal.Header>新版本:{updateData.tag_name}</Modal.Header>
<Modal.Header>新版本:{updateData.tag_name}</Modal.Header> <Modal.Content>
<Modal.Content> <Modal.Description>
<Modal.Description> <div dangerouslySetInnerHTML={{ __html: updateData.content }}></div>
<div dangerouslySetInnerHTML={{ __html: updateData.content }}></div> </Modal.Description>
</Modal.Description> </Modal.Content>
</Modal.Content> <Modal.Actions>
<Modal.Actions> <Button onClick={() => setShowUpdateModal(false)}>关闭</Button>
<Button onClick={() => setShowUpdateModal(false)}>关闭</Button> <Button
<Button content='详情'
content='详情' onClick={() => {
onClick={() => { setShowUpdateModal(false);
setShowUpdateModal(false); openGitHubRelease();
openGitHubRelease(); }}
}} />
/> </Modal.Actions>
</Modal.Actions> </Modal>
</Modal> </Grid>
</Grid> );
); };
};
export default OtherSetting;
export default OtherSetting;
+51 -51
View File
@@ -1,51 +1,51 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { Header, Segment } from 'semantic-ui-react'; import { Header, Segment } from 'semantic-ui-react';
import { API, showError } from '../../helpers'; import { API, showError } from '../../helpers';
import { marked } from 'marked'; import { marked } from 'marked';
const About = () => { const About = () => {
const [about, setAbout] = useState(''); const [about, setAbout] = useState('');
const [aboutLoaded, setAboutLoaded] = useState(false); const [aboutLoaded, setAboutLoaded] = useState(false);
const displayAbout = async () => { const displayAbout = async () => {
setAbout(localStorage.getItem('about') || ''); setAbout(localStorage.getItem('about') || '');
const res = await API.get('/api/about'); const res = await API.get('/api/about');
const { success, message, data } = res.data; const { success, message, data } = res.data;
if (success) { if (success) {
let HTMLAbout = marked.parse(data); let HTMLAbout = marked.parse(data);
setAbout(HTMLAbout); setAbout(HTMLAbout);
localStorage.setItem('about', HTMLAbout); localStorage.setItem('about', HTMLAbout);
} else { } else {
showError(message); showError(message);
setAbout('加载关于内容失败...'); setAbout('加载关于内容失败...');
} }
setAboutLoaded(true); setAboutLoaded(true);
}; };
useEffect(() => { useEffect(() => {
displayAbout().then(); displayAbout().then();
}, []); }, []);
return ( return (
<> <>
<Segment> <Segment>
{aboutLoaded && about === '' ? ( {aboutLoaded && about === '' ? (
<> <>
<Header as='h3'>关于</Header> <Header as='h3'>关于</Header>
<p>可在设置页面设置关于内容,支持 HTML & Markdown</p> <p>可在设置页面设置关于内容,支持 HTML & Markdown</p>
项目仓库地址: 项目仓库地址:
<a href='https://github.com/songquanpeng/gin-template'> <a href='https://github.com/Rain-kl/ATSFlare'>
https://github.com/songquanpeng/gin-template https://github.com/Rain-kl/ATSFlare
</a> </a>
</> </>
) : ( ) : (
<> <>
<div dangerouslySetInnerHTML={{ __html: about }}></div> <div dangerouslySetInnerHTML={{ __html: about }}></div>
</> </>
)} )}
</Segment> </Segment>
</> </>
); );
}; };
export default About; export default About;
+110 -110
View File
@@ -1,110 +1,110 @@
import React, { useContext, useEffect } from 'react'; import React, { useContext, useEffect } from 'react';
import { Card, Grid, Header, Segment } from 'semantic-ui-react'; import { Card, Grid, Header, Segment } from 'semantic-ui-react';
import { API, showError, showNotice, timestamp2string } from '../../helpers'; import { API, showError, showNotice, timestamp2string } from '../../helpers';
import { StatusContext } from '../../context/Status'; import { StatusContext } from '../../context/Status';
const Home = () => { const Home = () => {
const [statusState, statusDispatch] = useContext(StatusContext); const [statusState, statusDispatch] = useContext(StatusContext);
const homePageLink = localStorage.getItem('home_page_link') || ''; const homePageLink = localStorage.getItem('home_page_link') || '';
const displayNotice = async () => { const displayNotice = async () => {
const res = await API.get('/api/notice'); const res = await API.get('/api/notice');
const { success, message, data } = res.data; const { success, message, data } = res.data;
if (success) { if (success) {
let oldNotice = localStorage.getItem('notice'); let oldNotice = localStorage.getItem('notice');
if (data !== oldNotice && data !== '') { if (data !== oldNotice && data !== '') {
showNotice(data); showNotice(data);
localStorage.setItem('notice', data); localStorage.setItem('notice', data);
} }
} else { } else {
showError(message); showError(message);
} }
}; };
const getStartTimeString = () => { const getStartTimeString = () => {
const timestamp = statusState?.status?.start_time; const timestamp = statusState?.status?.start_time;
return timestamp2string(timestamp); return timestamp2string(timestamp);
}; };
useEffect(() => { useEffect(() => {
displayNotice().then(); displayNotice().then();
}, []); }, []);
return ( return (
<> <>
{homePageLink !== '' ? ( {homePageLink !== '' ? (
<> <>
<iframe <iframe
src={homePageLink} src={homePageLink}
style={{ width: '100%', height: '100vh', border: 'none' }} style={{ width: '100%', height: '100vh', border: 'none' }}
/> />
</> </>
) : ( ) : (
<> <>
<Segment> <Segment>
<Header as='h3'>系统状况</Header> <Header as='h3'>系统状况</Header>
<Grid columns={2} stackable> <Grid columns={2} stackable>
<Grid.Column> <Grid.Column>
<Card fluid> <Card fluid>
<Card.Content> <Card.Content>
<Card.Header>系统信息</Card.Header> <Card.Header>系统信息</Card.Header>
<Card.Meta>系统信息总览</Card.Meta> <Card.Meta>系统信息总览</Card.Meta>
<Card.Description> <Card.Description>
<p>名称:{statusState?.status?.system_name}</p> <p>名称:{statusState?.status?.system_name}</p>
<p>版本:{statusState?.status?.version}</p> <p>版本:{statusState?.status?.version}</p>
<p> <p>
源码: 源码:
<a <a
href='https://github.com/songquanpeng/gin-template' href='https://github.com/Rain-kl/ATSFlare'
target='_blank' target='_blank'
> >
https://github.com/songquanpeng/gin-template https://github.com/Rain-kl/ATSFlare
</a> </a>
</p> </p>
<p>启动时间:{getStartTimeString()}</p> <p>启动时间:{getStartTimeString()}</p>
</Card.Description> </Card.Description>
</Card.Content> </Card.Content>
</Card> </Card>
</Grid.Column> </Grid.Column>
<Grid.Column> <Grid.Column>
<Card fluid> <Card fluid>
<Card.Content> <Card.Content>
<Card.Header>系统配置</Card.Header> <Card.Header>系统配置</Card.Header>
<Card.Meta>系统配置总览</Card.Meta> <Card.Meta>系统配置总览</Card.Meta>
<Card.Description> <Card.Description>
<p> <p>
邮箱验证: 邮箱验证:
{statusState?.status?.email_verification === true {statusState?.status?.email_verification === true
? '已启用' ? '已启用'
: '未启用'} : '未启用'}
</p> </p>
<p> <p>
GitHub 身份验证: GitHub 身份验证:
{statusState?.status?.github_oauth === true {statusState?.status?.github_oauth === true
? '已启用' ? '已启用'
: '未启用'} : '未启用'}
</p> </p>
<p> <p>
微信身份验证: 微信身份验证:
{statusState?.status?.wechat_login === true {statusState?.status?.wechat_login === true
? '已启用' ? '已启用'
: '未启用'} : '未启用'}
</p> </p>
<p> <p>
Turnstile 用户校验: Turnstile 用户校验:
{statusState?.status?.turnstile_check === true {statusState?.status?.turnstile_check === true
? '已启用' ? '已启用'
: '未启用'} : '未启用'}
</p> </p>
</Card.Description> </Card.Description>
</Card.Content> </Card.Content>
</Card> </Card>
</Grid.Column> </Grid.Column>
</Grid> </Grid>
</Segment> </Segment>
</> </>
)} )}
</> </>
); );
}; };
export default Home; export default Home;
+2 -2
View File
@@ -40,13 +40,13 @@ npm run build
```bash ```bash
cd atsf_server cd atsf_server
export SESSION_SECRET='replace-with-random-string' export SESSION_SECRET='replace-with-random-string'
export SQLITE_PATH='./gin-template.db' export SQLITE_PATH='./atsflare.db'
go run . go run .
``` ```
说明: 说明:
- 如果未设置 `SQLITE_PATH`,默认也会落到 `atsf_server/gin-template.db` - 如果未设置 `SQLITE_PATH`,默认也会落到 `atsf_server/atsflare.db`
- 当前不再依赖全局 `AGENT_TOKEN` 环境变量 - 当前不再依赖全局 `AGENT_TOKEN` 环境变量
- 节点接入凭证改由数据库保存:节点专属 `agent_token` + 系统级 `discovery token` - 节点接入凭证改由数据库保存:节点专属 `agent_token` + 系统级 `discovery token`
- 当前默认监听端口为 `3000` - 当前默认监听端口为 `3000`
+5 -5
View File
@@ -94,12 +94,12 @@
### Server ### Server
控制中心直接基于现有 `atsf_server` 的 `gin-template` 工程开发: 控制中心直接基于现有 `atsf_server` 的 `ATSFlare` 工程开发:
* Web 框架:Gin * Web 框架:Gin
* ORM:GORM * ORM:GORM
* 前端:沿用现有 web 管理端 * 前端:沿用现有 web 管理端
* 鉴权:沿用 gin-template 登录体系 * 鉴权:沿用 ATSFlare 登录体系
### 数据库 ### 数据库
@@ -139,7 +139,7 @@ Agent 使用 Go 单体程序:
```text ```text
┌────────────────────────────┐ ┌────────────────────────────┐
│ ATSFlare Server │ │ ATSFlare Server │
│ gin-template + SQLite │ │ ATSFlare + SQLite │
│ Admin UI + Admin API │ │ Admin UI + Admin API │
└──────────────┬─────────────┘ └──────────────┬─────────────┘
│ │
@@ -548,7 +548,7 @@ Agent 每次上报:
管理端: 管理端:
* 直接沿用 gin-template 的登录态 * 直接沿用 ATSFlare 的登录态
Agent(第一版): Agent(第一版):
@@ -567,7 +567,7 @@ Agent(第二版):
### 12.1 登录页 ### 12.1 登录页
沿用 gin-template 现有登录。 沿用 ATSFlare 现有登录。
### 12.2 反代规则页(第一版,已实现) ### 12.2 反代规则页(第一版,已实现)
+3 -3
View File
@@ -41,7 +41,7 @@
* ORM:GORM * ORM:GORM
* 数据库:SQLite * 数据库:SQLite
* 前端:现有 `atsf_server/web` * 前端:现有 `atsf_server/web`
* 登录体系:沿用 gin-template 现有能力 * 登录体系:沿用 ATSFlare 现有能力
约束: 约束:
@@ -126,7 +126,7 @@ Agent 放在 `atsf_agent`,使用 Go 单体程序开发。
* 所有发布都是"新版本激活",不是在线覆盖编辑。 * 所有发布都是"新版本激活",不是在线覆盖编辑。
* 第一版与第二版:所有节点默认拉同一份全量配置,不做节点分组差异化下发。 * 第一版与第二版:所有节点默认拉同一份全量配置,不做节点分组差异化下发。
* 能用 SQLite 解决的问题,不引入额外中间件。 * 能用 SQLite 解决的问题,不引入额外中间件。
* 新功能优先复用现有 gin-template 结构,不平行造第二套框架。 * 新功能优先复用现有 ATSFlare 结构,不平行造第二套框架。
## 5. 数据模型规范 ## 5. 数据模型规范
@@ -205,7 +205,7 @@ Server 代码按以下职责拆分:
管理端: 管理端:
* 继续复用 gin-template 的登录、角色和 session 体系 * 继续复用 ATSFlare 的登录、角色和 session 体系
Agent(第一版): Agent(第一版):
+2 -2
View File
@@ -138,7 +138,7 @@ MVP 已于第一版完成。当前进入第二版迭代。
* 节点配置文件填写节点专属 `agent_token` 时,可直接上线;若 `agent_token` 为空且填写全局 discovery token,则会自动注册并完成 token 置换 * 节点配置文件填写节点专属 `agent_token` 时,可直接上线;若 `agent_token` 为空且填写全局 discovery token,则会自动注册并完成 token 置换
### V2 Phase 4: 路由自定义头 ### V2 Phase 4: 路由自定义头 ✅
目标: 目标:
@@ -155,7 +155,7 @@ MVP 已于第一版完成。当前进入第二版迭代。
* 路由配置自定义头后发布,渲染结果包含对应 `proxy_set_header` 指令 * 路由配置自定义头后发布,渲染结果包含对应 `proxy_set_header` 指令
* 不配置自定义头的路由渲染行为与之前保持一致 * 不配置自定义头的路由渲染行为与之前保持一致
### V2 Phase 5: 配置预览与变更摘要 ### V2 Phase 5: 配置预览与变更摘要 ✅
目标: 目标: