mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-08 16:46:37 +08:00
[优化] 更新包名
This commit is contained in:
@@ -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 }}
|
||||||
@@ -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 }}
|
||||||
@@ -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 }}
|
||||||
@@ -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 }}
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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"]
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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,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"
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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,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,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,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,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,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,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,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"
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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,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"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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,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,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
@@ -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,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,7 +1,7 @@
|
|||||||
package model
|
package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"gin-template/common"
|
"atsflare/common"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
package model
|
package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"atsflare/common"
|
||||||
"errors"
|
"errors"
|
||||||
"gin-template/common"
|
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -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"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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,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"
|
||||||
|
|||||||
@@ -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,9 +1,9 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"atsflare/model"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"gin-template/model"
|
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"unicode"
|
"unicode"
|
||||||
|
|||||||
@@ -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,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,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"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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;
|
|
||||||
|
|||||||
@@ -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;
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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 反代规则页(第一版,已实现)
|
||||||
|
|
||||||
|
|||||||
@@ -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(第一版):
|
||||||
|
|
||||||
|
|||||||
@@ -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: 配置预览与变更摘要 ✅
|
||||||
|
|
||||||
目标:
|
目标:
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user