ci: ko build

This commit is contained in:
VaalaCat
2024-12-24 14:38:34 +00:00
parent c80788b4ba
commit e9a59a7525
17 changed files with 294 additions and 131 deletions

View File

@@ -41,27 +41,13 @@ jobs:
automatic_release_tag: latest
files: |
dist/*
build-docker:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to DockerHub
uses: docker/login-action@v3
with:
- name: Setup ko
uses: ko-build/setup-ko@v0.6
env:
KO_DOCKER_REPO: docker.io/vaalacat/frp-panel
- env:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v3
with:
context: .
file: ./Dockerfile.standalone
push: true
platforms: linux/amd64,linux/arm64
tags: vaalacat/frp-panel:latest
run: |
echo "${password}" | ko login docker.io --username ${username} --password-stdin
ko build ./cmd/frpp --bare

View File

@@ -42,32 +42,14 @@ jobs:
automatic_release_tag: ${{ steps.get_version.outputs.VERSION }}
files: |
dist/*
build-docker:
runs-on: ubuntu-latest
steps:
- name: Get version
id: get_version
run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\//}
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Get git tag history
run: git fetch -a
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to DockerHub
uses: docker/login-action@v3
with:
- name: Setup ko
uses: ko-build/setup-ko@v0.6
env:
KO_DOCKER_REPO: docker.io/vaalacat/frp-panel
- name: Build image with ko
env:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v3
with:
context: .
file: ./Dockerfile.standalone
push: true
platforms: linux/amd64,linux/arm64
tags: vaalacat/frp-panel:${{ steps.get_version.outputs.VERSION }}
run: |
echo "${password}" | ko login docker.io --username ${username} --password-stdin
ko build ./cmd/frpp --bare -t ${{ steps.get_version.outputs.VERSION }}

15
.ko.yaml Normal file
View File

@@ -0,0 +1,15 @@
defaultBaseImage: alpine
builds:
- id: frpp
dir: .
main: ./cmd/frpp
ldflags:
- -s -w
- -X github.com/VaalaCat/frp-panel/conf.buildDate={{.Date}}
- -X github.com/VaalaCat/frp-panel/conf.gitCommit={{.Git.FullCommit}}
- -X github.com/VaalaCat/frp-panel/conf.gitVersion={{.Git.Tag}}
- -X github.com/VaalaCat/frp-panel/conf.gitBranch={{.Git.Branch}}
defaultPlatforms:
- all

View File

@@ -19,6 +19,7 @@ ARCH="all"
BUILD_DATE="$(date -u +'%Y-%m-%dT%H:%M:%SZ')"
GIT_COMMIT="$(git rev-parse HEAD)"
VERSION="$(git describe --tags --abbrev=0 | tr -d '\n')"
GIT_BRANCH="$(git rev-parse --abbrev-ref HEAD)"
# Parse arguments
while [[ "$#" -gt 0 ]]; do
@@ -50,7 +51,7 @@ echo "Build Date: $BUILD_DATE"
echo "Git Commit: $GIT_COMMIT"
echo "Version: $VERSION"
BUILD_LD_FLAGS="-X 'github.com/VaalaCat/frp-panel/conf.buildDate=${BUILD_DATE}' -X 'github.com/VaalaCat/frp-panel/conf.gitCommit=${GIT_COMMIT}' -X 'github.com/VaalaCat/frp-panel/conf.gitVersion=${VERSION}'"
BUILD_LD_FLAGS="-X 'github.com/VaalaCat/frp-panel/conf.buildDate=${BUILD_DATE}' -X 'github.com/VaalaCat/frp-panel/conf.gitCommit=${GIT_COMMIT}' -X 'github.com/VaalaCat/frp-panel/conf.gitVersion=${VERSION}' -X 'github.com/VaalaCat/frp-panel/conf.gitBranch=${GIT_BRANCH}'"
if [[ "$SKIP_FRONTEND" == "true" ]]; then
echo "Skipping frontend build"

View File

@@ -3,11 +3,13 @@ package main
import (
"context"
"fmt"
"os"
"github.com/VaalaCat/frp-panel/conf"
"github.com/VaalaCat/frp-panel/logger"
"github.com/VaalaCat/frp-panel/utils"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
var (
@@ -140,6 +142,7 @@ func initCommand() {
rootCmd.AddCommand(clientCmd, serverCmd, masterCmd, versionCmd,
installServiceCmd, uninstallServiceCmd,
startServiceCmd, stopServiceCmd, restartServiceCmd)
clientCmd.Flags().StringVarP(&clientSecret, "secret", "s", "", "client secret")
serverCmd.Flags().StringVarP(&clientSecret, "secret", "s", "", "client secret")
clientCmd.Flags().StringVarP(&clientID, "id", "i", "", "client id")
@@ -191,3 +194,11 @@ func patchConfig(host, secret, clientID, clientSecret, apiScheme string, rpcPort
conf.Get().Master.APIHost, conf.Get().Master.APIPort,
conf.Get().Master.APIScheme)
}
func setMasterCommandIfNonePresent() {
cmd, _, err := rootCmd.Find(os.Args[1:])
if err == nil && cmd.Use == rootCmd.Use && cmd.Flags().Parse(os.Args[1:]) != pflag.ErrHelp {
args := append([]string{"master"}, os.Args[1:]...)
rootCmd.SetArgs(args)
}
}

View File

@@ -14,5 +14,6 @@ func main() {
conf.InitConfig()
rpc.InitRPCClients()
setMasterCommandIfNonePresent()
rootCmd.Execute()
}

View File

@@ -3,6 +3,7 @@ package main
import (
"context"
"embed"
"path/filepath"
bizmaster "github.com/VaalaCat/frp-panel/biz/master"
"github.com/VaalaCat/frp-panel/biz/master/auth"
@@ -80,6 +81,12 @@ func initDatabase(c context.Context) {
switch conf.Get().DB.Type {
case "sqlite3":
if err := utils.EnsureDirectoryExists(conf.Get().DB.DSN); err != nil {
logrus.WithError(err).Warnf("ensure directory failed, data location: [%s], keep data in current directory", conf.Get().DB.DSN)
conf.Get().DB.DSN = filepath.Base(conf.Get().DB.DSN)
logrus.Infof("new data location: [%s]", conf.Get().DB.DSN)
}
if sqlitedb, err := gorm.Open(sqlite.Open(conf.Get().DB.DSN), &gorm.Config{}); err != nil {
logrus.Panic(err)
} else {

View File

@@ -43,7 +43,7 @@ type Config struct {
} `env-prefix:"SERVER_"`
DB struct {
Type string `env:"TYPE" env-default:"sqlite3" env-description:"db type, mysql or sqlite3 and so on"`
DSN string `env:"DSN" env-default:"data.db" env-description:"db dsn, for sqlite is path, other is dsn, look at https://github.com/go-sql-driver/mysql#dsn-data-source-name"`
DSN string `env:"DSN" env-default:"/data/data.db" env-description:"db dsn, for sqlite is path, other is dsn, look at https://github.com/go-sql-driver/mysql#dsn-data-source-name"`
} `env-prefix:"DB_"`
Client struct {
ID string `env:"ID" env-description:"client id"`

View File

@@ -12,12 +12,14 @@ import (
var (
gitVersion = "dev-build"
gitCommit = ""
gitBranch = ""
buildDate = "1970-01-01T00:00:00Z"
)
type VersionInfo struct {
GitVersion string `json:"gitVersion" yaml:"gitVersion"`
GitCommit string `json:"gitCommit" yaml:"gitCommit"`
GitBranch string `json:"gitBranch" yaml:"gitBranch"`
BuildDate string `json:"buildDate" yaml:"buildDate"`
GoVersion string `json:"goVersion" yaml:"goVersion"`
Compiler string `json:"compiler" yaml:"compiler"`
@@ -42,6 +44,7 @@ func (v *VersionInfo) ToProto() *pb.ClientVersion {
return &pb.ClientVersion{
GitVersion: v.GitVersion,
GitCommit: v.GitCommit,
GitBranch: v.GitBranch,
BuildDate: v.BuildDate,
GoVersion: v.GoVersion,
Compiler: v.Compiler,
@@ -53,6 +56,7 @@ func GetVersion() *VersionInfo {
return &VersionInfo{
GitVersion: gitVersion,
GitCommit: gitCommit,
GitBranch: gitBranch,
BuildDate: buildDate,
GoVersion: runtime.Version(),
Compiler: runtime.Compiler,

View File

@@ -27,6 +27,7 @@ message ClientVersion {
string GoVersion = 4;
string Compiler = 5;
string Platform = 6;
string GitBranch = 7;
}
message GetClientsStatusRequest {

View File

@@ -176,6 +176,7 @@ type ClientVersion struct {
GoVersion string `protobuf:"bytes,4,opt,name=GoVersion,proto3" json:"GoVersion,omitempty"`
Compiler string `protobuf:"bytes,5,opt,name=Compiler,proto3" json:"Compiler,omitempty"`
Platform string `protobuf:"bytes,6,opt,name=Platform,proto3" json:"Platform,omitempty"`
GitBranch string `protobuf:"bytes,7,opt,name=GitBranch,proto3" json:"GitBranch,omitempty"`
}
func (x *ClientVersion) Reset() {
@@ -250,6 +251,13 @@ func (x *ClientVersion) GetPlatform() string {
return ""
}
func (x *ClientVersion) GetGitBranch() string {
if x != nil {
return x.GitBranch
}
return ""
}
type GetClientsStatusRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
@@ -502,7 +510,7 @@ var file_api_master_proto_rawDesc = []byte{
0x12, 0x10, 0x0a, 0x0c, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52,
0x10, 0x03, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x42, 0x07,
0x0a, 0x05, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x42, 0x0f, 0x0a, 0x0d, 0x5f, 0x63, 0x6f, 0x6e, 0x6e,
0x65, 0x63, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x22, 0xc1, 0x01, 0x0a, 0x0d, 0x43, 0x6c, 0x69,
0x65, 0x63, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x22, 0xdf, 0x01, 0x0a, 0x0d, 0x43, 0x6c, 0x69,
0x65, 0x6e, 0x74, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x0a, 0x0a, 0x47, 0x69,
0x74, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a,
0x47, 0x69, 0x74, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1c, 0x0a, 0x09, 0x47, 0x69,
@@ -514,46 +522,48 @@ var file_api_master_proto_rawDesc = []byte{
0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x43, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x72,
0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x43, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x72,
0x12, 0x1a, 0x0a, 0x08, 0x50, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x18, 0x06, 0x20, 0x01,
0x28, 0x09, 0x52, 0x08, 0x50, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x22, 0x6d, 0x0a, 0x17,
0x47, 0x65, 0x74, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x33, 0x0a, 0x0b, 0x63, 0x6c, 0x69, 0x65, 0x6e,
0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x12, 0x2e, 0x63,
0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65,
0x52, 0x0a, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1d, 0x0a, 0x0a,
0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09,
0x52, 0x09, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x73, 0x22, 0xf5, 0x01, 0x0a, 0x18,
0x47, 0x65, 0x74, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73,
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2b, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74,
0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f,
0x6e, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x48, 0x00, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74,
0x75, 0x73, 0x88, 0x01, 0x01, 0x12, 0x4b, 0x0a, 0x07, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73,
0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x31, 0x2e, 0x61, 0x70, 0x69, 0x5f, 0x6d, 0x61, 0x73,
0x74, 0x65, 0x72, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x53, 0x74,
0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x43, 0x6c, 0x69,
0x65, 0x6e, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x63, 0x6c, 0x69, 0x65, 0x6e,
0x74, 0x73, 0x1a, 0x54, 0x0a, 0x0c, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x45, 0x6e, 0x74,
0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
0x03, 0x6b, 0x65, 0x79, 0x12, 0x2e, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20,
0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x61, 0x70, 0x69, 0x5f, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72,
0x2e, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x05, 0x76,
0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x73, 0x74, 0x61,
0x74, 0x75, 0x73, 0x22, 0x8d, 0x01, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x43, 0x6c, 0x69, 0x65, 0x6e,
0x74, 0x43, 0x65, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x33, 0x0a, 0x0b,
0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
0x0e, 0x32, 0x12, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x43, 0x6c, 0x69, 0x65, 0x6e,
0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0a, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70,
0x65, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02,
0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x23,
0x0a, 0x0d, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x18,
0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x63,
0x72, 0x65, 0x74, 0x22, 0x63, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74,
0x43, 0x65, 0x72, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2b, 0x0a, 0x06,
0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x63,
0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x48, 0x00, 0x52, 0x06,
0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x88, 0x01, 0x01, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x65, 0x72,
0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x63, 0x65, 0x72, 0x74, 0x42, 0x09, 0x0a,
0x07, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x42, 0x07, 0x5a, 0x05, 0x2e, 0x2e, 0x2f, 0x70,
0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
0x28, 0x09, 0x52, 0x08, 0x50, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x12, 0x1c, 0x0a, 0x09,
0x47, 0x69, 0x74, 0x42, 0x72, 0x61, 0x6e, 0x63, 0x68, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52,
0x09, 0x47, 0x69, 0x74, 0x42, 0x72, 0x61, 0x6e, 0x63, 0x68, 0x22, 0x6d, 0x0a, 0x17, 0x47, 0x65,
0x74, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x33, 0x0a, 0x0b, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f,
0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x12, 0x2e, 0x63, 0x6f, 0x6d,
0x6d, 0x6f, 0x6e, 0x2e, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0a,
0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6c,
0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09,
0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x73, 0x22, 0xf5, 0x01, 0x0a, 0x18, 0x47, 0x65,
0x74, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2b, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73,
0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e,
0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x48, 0x00, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73,
0x88, 0x01, 0x01, 0x12, 0x4b, 0x0a, 0x07, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x02,
0x20, 0x03, 0x28, 0x0b, 0x32, 0x31, 0x2e, 0x61, 0x70, 0x69, 0x5f, 0x6d, 0x61, 0x73, 0x74, 0x65,
0x72, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x53, 0x74, 0x61, 0x74,
0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x43, 0x6c, 0x69, 0x65, 0x6e,
0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73,
0x1a, 0x54, 0x0a, 0x0c, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79,
0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b,
0x65, 0x79, 0x12, 0x2e, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28,
0x0b, 0x32, 0x18, 0x2e, 0x61, 0x70, 0x69, 0x5f, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x43,
0x6c, 0x69, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x05, 0x76, 0x61, 0x6c,
0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75,
0x73, 0x22, 0x8d, 0x01, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43,
0x65, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x33, 0x0a, 0x0b, 0x63, 0x6c,
0x69, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32,
0x12, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x54,
0x79, 0x70, 0x65, 0x52, 0x0a, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12,
0x1b, 0x0a, 0x09, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01,
0x28, 0x09, 0x52, 0x08, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d,
0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x18, 0x03, 0x20,
0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65,
0x74, 0x22, 0x63, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, 0x65,
0x72, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2b, 0x0a, 0x06, 0x73, 0x74,
0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x63, 0x6f, 0x6d,
0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x48, 0x00, 0x52, 0x06, 0x73, 0x74,
0x61, 0x74, 0x75, 0x73, 0x88, 0x01, 0x01, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x65, 0x72, 0x74, 0x18,
0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x63, 0x65, 0x72, 0x74, 0x42, 0x09, 0x0a, 0x07, 0x5f,
0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x42, 0x07, 0x5a, 0x05, 0x2e, 0x2e, 0x2f, 0x70, 0x62, 0x62,
0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (

18
utils/files.go Normal file
View File

@@ -0,0 +1,18 @@
package utils
import (
"os"
"path/filepath"
)
func EnsureDirectoryExists(filePath string) error {
directory := filepath.Dir(filePath)
if _, err := os.Stat(directory); os.IsNotExist(err) {
err = os.MkdirAll(directory, os.ModePerm)
if err != nil {
return err
}
}
return nil
}

View File

@@ -12,6 +12,9 @@ import { ProxyInfo } from '@/lib/pb/common'
import { Button } from '../ui/button'
import { CheckCircle2, CircleX, RefreshCcw } from "lucide-react"
import { useTranslation } from 'react-i18next';
import TrafficStatsCard from './stats-item'
import { Separator } from '@radix-ui/react-separator'
import { formatBytes } from '@/lib/utils'
export interface ClientStatsCardProps {
clientID?: string
@@ -19,7 +22,6 @@ export interface ClientStatsCardProps {
export const ClientStatsCard: React.FC<ClientStatsCardProps> = ({ clientID: defaultClientID }: ClientStatsCardProps = {}) => {
const { t } = useTranslation();
const [clientID, setClientID] = useState<string | undefined>()
const [proxyName, setProxyName] = useState<string | undefined>()
const [status, setStatus] = useState<"loading" | "success" | "error" | undefined>()
const [timeoutId, setTimeoutId] = useState<NodeJS.Timeout | null>(null);
@@ -69,11 +71,6 @@ export const ClientStatsCard: React.FC<ClientStatsCardProps> = ({ clientID: defa
return Array.from(mergedMap.values());
};
function removeDuplicateCharacters(input: string): string {
const uniqueChars = new Set(input);
return Array.from(uniqueChars).join('');
}
return (
<Card className="w-full">
<CardHeader>
@@ -88,18 +85,16 @@ export const ClientStatsCard: React.FC<ClientStatsCardProps> = ({ clientID: defa
<Label>{t('client.stats.label')}</Label>
<ClientSelector clientID={clientID} setClientID={handleClientChange} onOpenChange={() => {
refetchClientStats()
setProxyName(undefined)
}} />
<Label>{t('proxy.stats.label')}</Label>
<ProxySelector
// @ts-ignore
proxyNames={Array.from(new Set(clientStatsList?.proxyInfos.map((proxyInfo) => proxyInfo.name).filter((value) => value !== undefined))) || []}
proxyName={proxyName}
setProxyname={setProxyName} />
<div className="w-full grid gap-4 grid-cols-1">
<div className="w-full grid gap-2 grid-cols-1 overflow-x-auto">
{clientStatsList && clientStatsList.proxyInfos.length > 0 &&
<ProxyStatusCard
proxyInfo={mergeProxyInfos(clientStatsList.proxyInfos).find((proxyInfo) => proxyInfo.name === proxyName)} />}
clientStatsList.proxyInfos.map((proxyInfo) => {
return (
<ProxyStatusCard key={proxyInfo.name} proxyInfo={proxyInfo} />
)
})
}
</div>
</CardContent>
<CardFooter>
@@ -135,21 +130,23 @@ const ProxyStatusCard: React.FC<{ proxyInfo: ProxyInfo | undefined }> = ({ proxy
}
return (
<div key={proxyInfo.name} className="flex flex-col space-y-4">
<Card key={proxyInfo.name} className="flex flex-row gap-2 p-4 w-full min-w-[900px] shadow-none justify-between">
<Label>{t('proxy.stats.tunnel_traffic', { name: proxyInfo.name })}</Label>
<ProxyTrafficOverview proxyInfo={proxyInfo} />
<div className='grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4'>
<ProxyTrafficPieChart
title={t('proxy.stats.today_traffic_title')}
chartLabel={t('proxy.stats.today_traffic_total')}
trafficIn={proxyInfo.todayTrafficIn || BigInt(0)}
trafficOut={proxyInfo.todayTrafficOut || BigInt(0)} />
<ProxyTrafficPieChart
title={t('proxy.stats.history_traffic_title')}
chartLabel={t('proxy.stats.history_traffic_total')}
trafficIn={proxyInfo.historyTrafficIn || BigInt(0)}
trafficOut={proxyInfo.historyTrafficOut || BigInt(0)} />
</div>
</div>
<Separator orientation="vertical" />
<ProxyTrafficField label={t('traffic.today.total')} value={formatBytes(Number(proxyInfo.todayTrafficOut || BigInt(0)))} />
<ProxyTrafficField label={t('traffic.today.inbound')} value={formatBytes(Number(proxyInfo.todayTrafficIn || BigInt(0)))} />
<ProxyTrafficField label={t('traffic.today.outbound')} value={formatBytes(Number(proxyInfo.todayTrafficOut || BigInt(0)))} />
<Separator orientation="vertical" />
<ProxyTrafficField label={t('traffic.history.total')} value={formatBytes(Number(proxyInfo.historyTrafficOut || BigInt(0)))} />
<ProxyTrafficField label={t('traffic.history.inbound')} value={formatBytes(Number(proxyInfo.historyTrafficIn || BigInt(0)))} />
<ProxyTrafficField label={t('traffic.history.outbound')} value={formatBytes(Number(proxyInfo.historyTrafficOut || BigInt(0)))} />
</Card>
);
}
const ProxyTrafficField = ({ label, value }: { label: string, value: string }) => {
return <div className="flex w-fit flex-col">
<p className="text-xs text-muted-foreground text-nowrap">{label}</p>
<div className="flex items-center text-xs font-semibold text-nowrap">{value}</div>
</div>
}

View File

@@ -0,0 +1,114 @@
'use client'
import { Card } from "@/components/ui/card"
import { ProxyInfo } from "@/lib/pb/common";
import { formatBytes } from "@/lib/utils";
import { PieChart, Pie, Cell, ResponsiveContainer, Tooltip } from 'recharts'
const COLORS = ['#0088FE', '#00C49F'];
function preparePieData(inTraffic: bigint, outTraffic: bigint) {
return [
{ name: 'In', value: Number(inTraffic) },
{ name: 'Out', value: Number(outTraffic) }
];
}
function calculateTotalTraffic(inTraffic: bigint, outTraffic: bigint): bigint {
return inTraffic + outTraffic;
}
export default function TrafficStatsCard({ proxy }: { proxy: ProxyInfo }) {
return <Card className="p-4 hover:bg-accent/50 transition-colors">
<div className="items-center gap-4 grid grid-cols-4">
{/* Server Info */}
<div className="flex items-center gap-2 min-w-[160px]">
<div className="w-2 h-2 rounded-full bg-green-500" />
<span className="font-medium">{proxy.name}</span>
</div>
{/* Today's Traffic */}
<div className="flex items-center gap-4 ">
<div className="w-16 h-16">
<ResponsiveContainer width="100%" height="100%">
<PieChart>
<Pie
data={preparePieData(proxy.todayTrafficIn || BigInt(0), proxy.todayTrafficOut || BigInt(0))}
cx="50%"
cy="50%"
innerRadius={15}
outerRadius={30}
paddingAngle={2}
dataKey="value"
>
{preparePieData(proxy.todayTrafficIn || BigInt(0), proxy.todayTrafficOut || BigInt(0)).map((entry, index) => (
<Cell key={`cell-${index}`} fill={COLORS[index % COLORS.length]} />
))}
</Pie>
<Tooltip formatter={(value) => formatBytes(value as number)} />
</PieChart>
</ResponsiveContainer>
</div>
<div className="flex flex-col gap-1">
<div className="text-sm font-medium text-muted-foreground">Today&apos;s Traffic</div>
<div className="flex flex-col gap-1">
<div className="flex justify-between items-center">
<span className="text-sm text-muted-foreground">In:</span>
<span className="text-sm tabular-nums">{formatBytes(Number(proxy.todayTrafficIn || BigInt(0)))}</span>
</div>
<div className="flex justify-between items-center">
<span className="text-sm text-muted-foreground">Out:</span>
<span className="text-sm tabular-nums">{formatBytes(Number(proxy.todayTrafficOut || BigInt(0)))}</span>
</div>
<div className="flex justify-between items-center">
<span className="text-sm text-muted-foreground">Total:</span>
<span className="text-sm tabular-nums">{formatBytes(Number(calculateTotalTraffic(proxy.todayTrafficIn || BigInt(0), proxy.todayTrafficOut || BigInt(0))))}</span>
</div>
</div>
</div>
</div>
{/* History Traffic */}
<div className="flex items-center gap-4 min-w-[300px]">
<div className="w-16 h-16">
<ResponsiveContainer width="100%" height="100%">
<PieChart>
<Pie
data={preparePieData(proxy.historyTrafficIn || BigInt(0), proxy.historyTrafficOut || BigInt(0))}
cx="50%"
cy="50%"
innerRadius={15}
outerRadius={30}
paddingAngle={2}
dataKey="value"
>
{preparePieData(proxy.historyTrafficIn || BigInt(0), proxy.historyTrafficOut || BigInt(0)).map((entry, index) => (
<Cell key={`cell-${index}`} fill={COLORS[index % COLORS.length]} />
))}
</Pie>
<Tooltip formatter={(value) => formatBytes(value as number)} />
</PieChart>
</ResponsiveContainer>
</div>
<div className="flex flex-col gap-1">
<div className="text-sm font-medium text-muted-foreground">History Traffic</div>
<div className="flex flex-col gap-1">
<div className="flex justify-between items-center">
<span className="text-sm text-muted-foreground">In:</span>
<span className="text-sm tabular-nums">{formatBytes(Number(proxy.historyTrafficIn || BigInt(0)))}</span>
</div>
<div className="flex justify-between items-center">
<span className="text-sm text-muted-foreground">Out:</span>
<span className="text-sm tabular-nums">{formatBytes(Number(proxy.historyTrafficOut || BigInt(0)))}</span>
</div>
<div className="flex justify-between items-center">
<span className="text-sm text-muted-foreground">Total:</span>
<span className="text-sm tabular-nums">{formatBytes(Number(calculateTotalTraffic(proxy.historyTrafficIn || BigInt(0), proxy.historyTrafficOut || BigInt(0))))}</span>
</div>
</div>
</div>
</div>
</div>
</Card>
}

View File

@@ -25,11 +25,13 @@
"traffic": {
"today": {
"inbound": "Today's Inbound",
"outbound": "Today's Outbound"
"outbound": "Today's Outbound",
"total": "Today's Total"
},
"history": {
"inbound": "Historical Inbound",
"outbound": "Historical Outbound"
"outbound": "Historical Outbound",
"total": "Historical Total"
},
"stats": {
"title": "Traffic Statistics",
@@ -318,7 +320,7 @@
"proxy": {
"stats": {
"label": "Tunnel Name",
"tunnel_traffic": "Tunnel Traffic: {{name}}",
"tunnel_traffic": "Tunnel: {{name}}",
"today_traffic_title": "Today's Traffic",
"today_traffic_total": "Today's Total",
"history_traffic_title": "Historical Traffic",

View File

@@ -24,12 +24,14 @@
},
"traffic": {
"today": {
"inbound": "今日入站流量",
"outbound": "今日出站流量"
"inbound": "今日入站",
"outbound": "今日出站",
"total": "今日总流量"
},
"history": {
"inbound": "历史入站流量",
"outbound": "历史出站流量"
"inbound": "历史入站",
"outbound": "历史出站",
"total": "历史总流量"
},
"stats": {
"title": "流量统计",
@@ -318,7 +320,7 @@
"proxy": {
"stats": {
"label": "隧道名称",
"tunnel_traffic": "隧道流量{{name}}",
"tunnel_traffic": "隧道:{{name}}",
"today_traffic_title": "今日流量",
"today_traffic_total": "今日总计",
"history_traffic_title": "历史流量",

View File

@@ -94,6 +94,10 @@ export interface ClientVersion {
* @generated from protobuf field: string Platform = 6 [json_name = "Platform"];
*/
platform: string;
/**
* @generated from protobuf field: string GitBranch = 7 [json_name = "GitBranch"];
*/
gitBranch: string;
}
/**
* @generated from protobuf message api_master.GetClientsStatusRequest
@@ -254,7 +258,8 @@ class ClientVersion$Type extends MessageType<ClientVersion> {
{ no: 3, name: "BuildDate", kind: "scalar", jsonName: "BuildDate", T: 9 /*ScalarType.STRING*/ },
{ no: 4, name: "GoVersion", kind: "scalar", jsonName: "GoVersion", T: 9 /*ScalarType.STRING*/ },
{ no: 5, name: "Compiler", kind: "scalar", jsonName: "Compiler", T: 9 /*ScalarType.STRING*/ },
{ no: 6, name: "Platform", kind: "scalar", jsonName: "Platform", T: 9 /*ScalarType.STRING*/ }
{ no: 6, name: "Platform", kind: "scalar", jsonName: "Platform", T: 9 /*ScalarType.STRING*/ },
{ no: 7, name: "GitBranch", kind: "scalar", jsonName: "GitBranch", T: 9 /*ScalarType.STRING*/ }
]);
}
create(value?: PartialMessage<ClientVersion>): ClientVersion {
@@ -265,6 +270,7 @@ class ClientVersion$Type extends MessageType<ClientVersion> {
message.goVersion = "";
message.compiler = "";
message.platform = "";
message.gitBranch = "";
if (value !== undefined)
reflectionMergePartial<ClientVersion>(this, message, value);
return message;
@@ -292,6 +298,9 @@ class ClientVersion$Type extends MessageType<ClientVersion> {
case /* string Platform = 6 [json_name = "Platform"];*/ 6:
message.platform = reader.string();
break;
case /* string GitBranch = 7 [json_name = "GitBranch"];*/ 7:
message.gitBranch = reader.string();
break;
default:
let u = options.readUnknownField;
if (u === "throw")
@@ -322,6 +331,9 @@ class ClientVersion$Type extends MessageType<ClientVersion> {
/* string Platform = 6 [json_name = "Platform"]; */
if (message.platform !== "")
writer.tag(6, WireType.LengthDelimited).string(message.platform);
/* string GitBranch = 7 [json_name = "GitBranch"]; */
if (message.gitBranch !== "")
writer.tag(7, WireType.LengthDelimited).string(message.gitBranch);
let u = options.writeUnknownFields;
if (u !== false)
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);