Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
318da135b9 | ||
|
|
da74bf4749 | ||
|
|
3250283ef9 | ||
|
|
2b40040ec5 | ||
|
|
60ada33ee9 | ||
|
|
7940e010e0 | ||
|
|
2df57e6249 | ||
|
|
106d58575b | ||
|
|
bdf36276da | ||
|
|
f32f127dfc | ||
|
|
f78ba281fb | ||
|
|
7f9f9a9129 | ||
|
|
b701efd9b5 | ||
|
|
511f43784e | ||
|
|
3f6112a919 | ||
|
|
23751440c6 |
+117
-71
@@ -4,20 +4,126 @@ on:
|
|||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
- "v*"
|
- "v*"
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release:
|
build:
|
||||||
|
strategy:
|
||||||
|
fail-fast: true
|
||||||
|
matrix:
|
||||||
|
goos: [linux, windows]
|
||||||
|
goarch: [amd64]
|
||||||
|
include:
|
||||||
|
- goos: linux
|
||||||
|
goarch: s390x
|
||||||
|
- goos: linux
|
||||||
|
goarch: arm64
|
||||||
|
|
||||||
|
name: Build artifacts
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
container:
|
||||||
|
image: goreleaser/goreleaser-cross:v1.21
|
||||||
|
env:
|
||||||
|
GOOS: ${{ matrix.goos }}
|
||||||
|
GOARCH: ${{ matrix.goarch }}
|
||||||
|
GOARM: ${{ matrix.goarm }}
|
||||||
|
outputs:
|
||||||
|
tag_name: ${{ steps.extract_branch.outputs.tag }}
|
||||||
steps:
|
steps:
|
||||||
|
- run: git config --global --add safe.directory /__w/nezha/nezha
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Fetch IPInfo GeoIP Database
|
- name: Fetch IPInfo GeoIP Database
|
||||||
|
if: github.event_name == 'push' && contains(github.ref, 'refs/tags/')
|
||||||
env:
|
env:
|
||||||
IPINFO_TOKEN: ${{ secrets.IPINFO_TOKEN }}
|
IPINFO_TOKEN: ${{ secrets.IPINFO_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
rm pkg/geoip/geoip.db
|
rm pkg/geoip/geoip.db
|
||||||
wget -O pkg/geoip/geoip.db https://ipinfo.io/data/free/country.mmdb?token=${IPINFO_TOKEN}
|
wget -qO pkg/geoip/geoip.db https://ipinfo.io/data/free/country.mmdb?token=${IPINFO_TOKEN}
|
||||||
|
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: "1.21.x"
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
uses: goreleaser/goreleaser-action@v6
|
||||||
|
with:
|
||||||
|
distribution: goreleaser
|
||||||
|
version: '~> v2'
|
||||||
|
args: build --single-target --clean --skip=validate
|
||||||
|
|
||||||
|
- name: Upload artifacts
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: dashboard-${{ env.GOOS }}-${{ env.GOARCH }}
|
||||||
|
path: |
|
||||||
|
./dist/*/*
|
||||||
|
|
||||||
|
release:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: github.event_name == 'push' && contains(github.ref, 'refs/tags/')
|
||||||
|
needs: build
|
||||||
|
name: Release
|
||||||
|
steps:
|
||||||
|
- name: Download artifacts
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
path: ./assets
|
||||||
|
|
||||||
|
- name: Archive and compress binaries
|
||||||
|
run: |
|
||||||
|
for file in assets/*/*/*; do
|
||||||
|
if [ -f "$file" ]; then
|
||||||
|
chmod +x "$file"
|
||||||
|
export fileWithoutExt=${file%.*}
|
||||||
|
zip -jr "$fileWithoutExt.zip" "$file"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
- name: Release
|
||||||
|
uses: ncipollo/release-action@v1
|
||||||
|
with:
|
||||||
|
artifacts: "assets/*/*/*.zip"
|
||||||
|
generateReleaseNotes: true
|
||||||
|
|
||||||
|
- name: Purge jsdelivr cache
|
||||||
|
run: |
|
||||||
|
curl -s https://purge.jsdelivr.net/gh/${{ github.repository_owner }}/nezha@master/script/install.sh
|
||||||
|
curl -s https://purge.jsdelivr.net/gh/${{ github.repository_owner }}/nezha@master/script/nezha-agent.service
|
||||||
|
curl -s https://purge.jsdelivr.net/gh/${{ github.repository_owner }}/nezha@master/script/docker-compose.yaml
|
||||||
|
curl -s https://purge.jsdelivr.net/gh/${{ github.repository_owner }}/nezha@master/script/config.yaml
|
||||||
|
LOWER_USERNAME=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]')
|
||||||
|
curl -s https://purge.jsdelivr.net/gh/$LOWER_USERNAME/nezha@master/script/install.sh
|
||||||
|
curl -s https://purge.jsdelivr.net/gh/$LOWER_USERNAME/nezha@master/script/nezha-agent.service
|
||||||
|
curl -s https://purge.jsdelivr.net/gh/$LOWER_USERNAME/nezha@master/script/docker-compose.yaml
|
||||||
|
curl -s https://purge.jsdelivr.net/gh/$LOWER_USERNAME/nezha@master/script/config.yaml
|
||||||
|
|
||||||
|
- name: Trigger sync
|
||||||
|
env:
|
||||||
|
GH_REPO: ${{ github.repository }}
|
||||||
|
GH_TOKEN: ${{ github.token }}
|
||||||
|
GH_DEBUG: api
|
||||||
|
run: |
|
||||||
|
gh workflow run sync-release.yml
|
||||||
|
|
||||||
|
release-docker:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: github.event_name == 'push' && contains(github.ref, 'refs/tags/')
|
||||||
|
needs: build
|
||||||
|
name: Release Docker images
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Download artifacts
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
path: ./assets
|
||||||
|
|
||||||
|
- name: Fix permissions
|
||||||
|
run: |
|
||||||
|
chmod -R +x ./assets/*
|
||||||
|
mkdir dist
|
||||||
|
mv ./assets/*/*/* ./dist
|
||||||
|
|
||||||
- name: Extract branch name
|
- name: Extract branch name
|
||||||
run: |
|
run: |
|
||||||
@@ -25,33 +131,12 @@ jobs:
|
|||||||
echo "tag=$TAG_NAME" >> $GITHUB_OUTPUT
|
echo "tag=$TAG_NAME" >> $GITHUB_OUTPUT
|
||||||
id: extract_branch
|
id: extract_branch
|
||||||
|
|
||||||
- name: xgo build
|
- name: Log into GHCR
|
||||||
uses: crazy-max/ghaction-xgo@v2
|
|
||||||
with:
|
|
||||||
xgo_version: latest
|
|
||||||
go_version: 1.21.x
|
|
||||||
dest: dist
|
|
||||||
pkg: cmd/dashboard
|
|
||||||
prefix: dashboard
|
|
||||||
targets: linux/amd64,linux/arm64,linux/arm-7,linux/s390x,linux/riscv64,windows/amd64,windows/386,windows/arm64 # linux/386,
|
|
||||||
v: true
|
|
||||||
x: false
|
|
||||||
race: false
|
|
||||||
tags: timetzdata
|
|
||||||
ldflags: -s -w --extldflags '-static -fpic' -X github.com/naiba/nezha/service/singleton.Version=${{ steps.extract_branch.outputs.tag }}
|
|
||||||
buildmode: default
|
|
||||||
|
|
||||||
- name: fix dist
|
|
||||||
run: |
|
|
||||||
ls -al dist/
|
|
||||||
mv dist/dashboard-linux-arm-7 dist/dashboard-linux-arm
|
|
||||||
|
|
||||||
- name: Log in to the GHCR
|
|
||||||
uses: docker/login-action@master
|
uses: docker/login-action@master
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ github.token }}
|
||||||
|
|
||||||
- name: Log in to the AliyunCS
|
- name: Log in to the AliyunCS
|
||||||
uses: docker/login-action@master
|
uses: docker/login-action@master
|
||||||
@@ -68,13 +153,13 @@ jobs:
|
|||||||
|
|
||||||
- name: Set up image name
|
- name: Set up image name
|
||||||
run: |
|
run: |
|
||||||
GHRC_IMAGE_NAME=$(echo "ghcr.io/${{ github.repository_owner }}/nezha-dashboard" | tr '[:upper:]' '[:lower:]')
|
GHCR_IMAGE_NAME=$(echo "ghcr.io/${{ github.repository_owner }}/nezha-dashboard" | tr '[:upper:]' '[:lower:]')
|
||||||
if [ ${{ github.repository_owner }} = "naiba" ]
|
if [ ${{ github.repository_owner }} = "naiba" ]
|
||||||
then ALI_IMAGE_NAME=$(echo "registry.cn-shanghai.aliyuncs.com/naibahq/nezha-dashboard")
|
then ALI_IMAGE_NAME=$(echo "registry.cn-shanghai.aliyuncs.com/naibahq/nezha-dashboard")
|
||||||
else ALI_IMAGE_NAME=$(echo "registry.cn-shanghai.aliyuncs.com/${{ github.repository_owner }}/nezha-dashboard" | tr '[:upper:]' '[:lower:]')
|
else ALI_IMAGE_NAME=$(echo "registry.cn-shanghai.aliyuncs.com/${{ github.repository_owner }}/nezha-dashboard" | tr '[:upper:]' '[:lower:]')
|
||||||
fi
|
fi
|
||||||
echo "::set-output name=GHRC_IMAGE_NAME::$GHRC_IMAGE_NAME"
|
echo "GHCR_IMAGE_NAME=$GHCR_IMAGE_NAME" >> $GITHUB_OUTPUT
|
||||||
echo "::set-output name=ALI_IMAGE_NAME::$ALI_IMAGE_NAME"
|
echo "ALI_IMAGE_NAME=$ALI_IMAGE_NAME" >> $GITHUB_OUTPUT
|
||||||
id: image-name
|
id: image-name
|
||||||
|
|
||||||
- name: Build dasbboard image And Push
|
- name: Build dasbboard image And Push
|
||||||
@@ -82,49 +167,10 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: ./Dockerfile
|
file: ./Dockerfile
|
||||||
platforms: linux/amd64,linux/arm64,linux/arm,linux/s390x,linux/riscv64 # linux/386,
|
platforms: linux/amd64,linux/arm64,linux/s390x
|
||||||
push: true
|
push: true
|
||||||
tags: |
|
tags: |
|
||||||
${{ steps.image-name.outputs.GHRC_IMAGE_NAME }}:latest
|
${{ steps.image-name.outputs.GHCR_IMAGE_NAME }}:latest
|
||||||
${{ steps.image-name.outputs.GHRC_IMAGE_NAME }}:${{ steps.extract_branch.outputs.tag }}
|
${{ steps.image-name.outputs.GHCR_IMAGE_NAME }}:${{ steps.extract_branch.outputs.tag }}
|
||||||
${{ steps.image-name.outputs.ALI_IMAGE_NAME }}:latest
|
${{ steps.image-name.outputs.ALI_IMAGE_NAME }}:latest
|
||||||
${{ steps.image-name.outputs.ALI_IMAGE_NAME }}:${{ steps.extract_branch.outputs.tag }}
|
${{ steps.image-name.outputs.ALI_IMAGE_NAME }}:${{ steps.extract_branch.outputs.tag }}
|
||||||
|
|
||||||
- name: Compress dist files
|
|
||||||
run: |
|
|
||||||
for file in dist/*; do
|
|
||||||
if [ -f "$file" ]; then
|
|
||||||
zip -r "$file.zip" "$file"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
- name: Release
|
|
||||||
uses: ncipollo/release-action@v1
|
|
||||||
with:
|
|
||||||
artifacts: "dist/*.zip"
|
|
||||||
generateReleaseNotes: true
|
|
||||||
|
|
||||||
- name: Purge jsdelivr cache
|
|
||||||
run: |
|
|
||||||
curl -s https://purge.jsdelivr.net/gh/${{ github.repository_owner }}/nezha@master/script/install.sh
|
|
||||||
curl -s https://purge.jsdelivr.net/gh/${{ github.repository_owner }}/nezha@master/script/nezha-agent.service
|
|
||||||
curl -s https://purge.jsdelivr.net/gh/${{ github.repository_owner }}/nezha@master/script/docker-compose.yaml
|
|
||||||
curl -s https://purge.jsdelivr.net/gh/${{ github.repository_owner }}/nezha@master/script/config.yaml
|
|
||||||
LOWER_USERNAME=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]')
|
|
||||||
curl -s https://purge.jsdelivr.net/gh/$LOWER_USERNAME/nezha@master/script/install.sh
|
|
||||||
curl -s https://purge.jsdelivr.net/gh/$LOWER_USERNAME/nezha@master/script/nezha-agent.service
|
|
||||||
curl -s https://purge.jsdelivr.net/gh/$LOWER_USERNAME/nezha@master/script/docker-compose.yaml
|
|
||||||
curl -s https://purge.jsdelivr.net/gh/$LOWER_USERNAME/nezha@master/script/config.yaml
|
|
||||||
|
|
||||||
- name: Trigger sync
|
|
||||||
if: ${{ env.SYNCED == 0 }}
|
|
||||||
uses: actions/github-script@v6
|
|
||||||
with:
|
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
script: |
|
|
||||||
await github.rest.actions.createWorkflowDispatch({
|
|
||||||
owner: 'naiba',
|
|
||||||
repo: 'nezha',
|
|
||||||
workflow_id: 'sync-release.yml',
|
|
||||||
ref: 'master'
|
|
||||||
})
|
|
||||||
|
|||||||
@@ -1,43 +0,0 @@
|
|||||||
name: Run Tests on PR
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
jobs:
|
|
||||||
tests:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
env:
|
|
||||||
GO111MODULE: on
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: actions/setup-go@v4
|
|
||||||
with:
|
|
||||||
go-version: "^1.21.3"
|
|
||||||
- name: Unit test
|
|
||||||
run: |
|
|
||||||
go test -v ./...
|
|
||||||
- name: Run Gosec Security Scanner
|
|
||||||
uses: securego/gosec@master
|
|
||||||
with:
|
|
||||||
args: --exclude=G104,G402 ./...
|
|
||||||
- name: xgo build
|
|
||||||
uses: crazy-max/ghaction-xgo@v2
|
|
||||||
with:
|
|
||||||
xgo_version: latest
|
|
||||||
go_version: 1.21.x
|
|
||||||
dest: dist
|
|
||||||
pkg: cmd/dashboard
|
|
||||||
prefix: dashboard
|
|
||||||
targets: linux/amd64,linux/arm64,linux/arm-7,linux/s390x,linux/riscv64,windows/amd64,windows/386,windows/arm64 # linux/386,
|
|
||||||
v: true
|
|
||||||
x: false
|
|
||||||
race: false
|
|
||||||
tags: timetzdata
|
|
||||||
ldflags: -s -w --extldflags '-static -fpic' -X github.com/naiba/nezha/service/singleton.Version=test
|
|
||||||
buildmode: default
|
|
||||||
- name: fix dist
|
|
||||||
run: |
|
|
||||||
ls -al dist/
|
|
||||||
mv dist/dashboard-linux-arm-7 dist/dashboard-linux-arm
|
|
||||||
|
|
||||||
+19
-23
@@ -10,40 +10,36 @@ on:
|
|||||||
- "go.sum"
|
- "go.sum"
|
||||||
- "resource/**"
|
- "resource/**"
|
||||||
- ".github/workflows/test.yml"
|
- ".github/workflows/test.yml"
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
tests:
|
tests:
|
||||||
runs-on: ubuntu-latest
|
strategy:
|
||||||
|
fail-fast: true
|
||||||
|
matrix:
|
||||||
|
os: [ubuntu, windows, macos]
|
||||||
|
|
||||||
|
runs-on: ${{ matrix.os }}-latest
|
||||||
env:
|
env:
|
||||||
GO111MODULE: on
|
GO111MODULE: on
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-go@v4
|
|
||||||
|
- uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: "^1.21.3"
|
go-version: "1.21.x"
|
||||||
|
|
||||||
- name: Unit test
|
- name: Unit test
|
||||||
run: |
|
run: |
|
||||||
go test -v ./...
|
go test -v ./...
|
||||||
|
|
||||||
|
- name: Build test
|
||||||
|
run: go build -v ./cmd/dashboard
|
||||||
|
|
||||||
- name: Run Gosec Security Scanner
|
- name: Run Gosec Security Scanner
|
||||||
|
if: runner.os == 'Linux'
|
||||||
uses: securego/gosec@master
|
uses: securego/gosec@master
|
||||||
with:
|
with:
|
||||||
args: --exclude=G104,G402 ./...
|
args: --exclude=G104,G402,G115,G203 ./...
|
||||||
- name: xgo build
|
|
||||||
uses: crazy-max/ghaction-xgo@v2
|
|
||||||
with:
|
|
||||||
xgo_version: latest
|
|
||||||
go_version: 1.21.x
|
|
||||||
dest: dist
|
|
||||||
pkg: cmd/dashboard
|
|
||||||
prefix: dashboard
|
|
||||||
targets: linux/amd64,linux/arm64,linux/arm-7,linux/s390x,linux/riscv64,windows/amd64,windows/386,windows/arm64 # linux/386,
|
|
||||||
v: true
|
|
||||||
x: false
|
|
||||||
race: false
|
|
||||||
tags: timetzdata
|
|
||||||
ldflags: -s -w --extldflags '-static -fpic' -X github.com/naiba/nezha/service/singleton.Version=test
|
|
||||||
buildmode: default
|
|
||||||
- name: fix dist
|
|
||||||
run: |
|
|
||||||
ls -al dist/
|
|
||||||
mv dist/dashboard-linux-arm-7 dist/dashboard-linux-arm
|
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
version: 2
|
||||||
|
before:
|
||||||
|
hooks:
|
||||||
|
- go mod tidy -v
|
||||||
|
builds:
|
||||||
|
- id: linux_arm64
|
||||||
|
env:
|
||||||
|
- CGO_ENABLED=1
|
||||||
|
- CC=aarch64-linux-gnu-gcc
|
||||||
|
ldflags:
|
||||||
|
- -s -w
|
||||||
|
- -X github.com/naiba/nezha/service/singleton.Version={{.Version}}
|
||||||
|
- -extldflags "-static -fpic"
|
||||||
|
flags:
|
||||||
|
- -trimpath
|
||||||
|
goos:
|
||||||
|
- linux
|
||||||
|
goarch:
|
||||||
|
- arm64
|
||||||
|
main: ./cmd/dashboard
|
||||||
|
binary: dashboard-{{ .Os }}-{{ .Arch }}
|
||||||
|
- id: linux_amd64
|
||||||
|
env:
|
||||||
|
- CGO_ENABLED=1
|
||||||
|
- CC=x86_64-linux-gnu-gcc
|
||||||
|
ldflags:
|
||||||
|
- -s -w
|
||||||
|
- -X github.com/naiba/nezha/service/singleton.Version={{.Version}}
|
||||||
|
- -extldflags "-static -fpic"
|
||||||
|
flags:
|
||||||
|
- -trimpath
|
||||||
|
goos:
|
||||||
|
- linux
|
||||||
|
goarch:
|
||||||
|
- amd64
|
||||||
|
main: ./cmd/dashboard
|
||||||
|
binary: dashboard-{{ .Os }}-{{ .Arch }}
|
||||||
|
- id: linux_s390x
|
||||||
|
env:
|
||||||
|
- CGO_ENABLED=1
|
||||||
|
- CC=s390x-linux-gnu-gcc
|
||||||
|
ldflags:
|
||||||
|
- -s -w
|
||||||
|
- -X github.com/naiba/nezha/service/singleton.Version={{.Version}}
|
||||||
|
- -extldflags "-static -fpic"
|
||||||
|
flags:
|
||||||
|
- -trimpath
|
||||||
|
goos:
|
||||||
|
- linux
|
||||||
|
goarch:
|
||||||
|
- s390x
|
||||||
|
main: ./cmd/dashboard
|
||||||
|
binary: dashboard-{{ .Os }}-{{ .Arch }}
|
||||||
|
- id: windows_amd64
|
||||||
|
env:
|
||||||
|
- CGO_ENABLED=1
|
||||||
|
- CC=x86_64-w64-mingw32-gcc
|
||||||
|
ldflags:
|
||||||
|
- -s -w
|
||||||
|
- -X github.com/naiba/nezha/service/singleton.Version={{.Version}}
|
||||||
|
- -extldflags "-static -fpic"
|
||||||
|
flags:
|
||||||
|
- -trimpath
|
||||||
|
goos:
|
||||||
|
- windows
|
||||||
|
goarch:
|
||||||
|
- amd64
|
||||||
|
main: ./cmd/dashboard
|
||||||
|
binary: dashboard-{{ .Os }}-{{ .Arch }}
|
||||||
|
snapshot:
|
||||||
|
version_template: "dashboard"
|
||||||
+7
-9
@@ -1,16 +1,12 @@
|
|||||||
# latest 镜像还没有 riscv64 https://hub.docker.com/_/alpine/tags
|
FROM alpine AS certs
|
||||||
FROM alpine:edge
|
RUN apk update && apk add ca-certificates
|
||||||
|
|
||||||
|
FROM busybox:stable-musl
|
||||||
|
|
||||||
ARG TARGETOS
|
ARG TARGETOS
|
||||||
ARG TARGETARCH
|
ARG TARGETARCH
|
||||||
|
|
||||||
RUN apk update && \
|
COPY --from=certs /etc/ssl/certs /etc/ssl/certs
|
||||||
apk upgrade --no-cache && \
|
|
||||||
apk add --no-cache tzdata && \
|
|
||||||
cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
|
|
||||||
echo 'Asia/Shanghai' >/etc/timezone && \
|
|
||||||
rm -rf /var/cache/apk/*
|
|
||||||
|
|
||||||
COPY ./script/entrypoint.sh /entrypoint.sh
|
COPY ./script/entrypoint.sh /entrypoint.sh
|
||||||
RUN chmod +x /entrypoint.sh
|
RUN chmod +x /entrypoint.sh
|
||||||
|
|
||||||
@@ -19,4 +15,6 @@ COPY dist/dashboard-${TARGETOS}-${TARGETARCH} ./app
|
|||||||
|
|
||||||
VOLUME ["/dashboard/data"]
|
VOLUME ["/dashboard/data"]
|
||||||
EXPOSE 80 5555
|
EXPOSE 80 5555
|
||||||
|
ARG TZ=Asia/Shanghai
|
||||||
|
ENV TZ=$TZ
|
||||||
ENTRYPOINT ["/entrypoint.sh"]
|
ENTRYPOINT ["/entrypoint.sh"]
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
<br>
|
<br>
|
||||||
<small><i>LOGO designed by <a href="https://xio.ng" target="_blank">熊大</a> .</i></small>
|
<small><i>LOGO designed by <a href="https://xio.ng" target="_blank">熊大</a> .</i></small>
|
||||||
<br><br>
|
<br><br>
|
||||||
<img alt="GitHub release (with filter)" src="https://img.shields.io/github/v/release/naiba/nezha?color=brightgreen&style=for-the-badge&logo=github&label=Dashboard"> <img src="https://img.shields.io/github/v/release/nezhahq/agent?color=brightgreen&label=Agent&style=for-the-badge&logo=github"> <img src="https://img.shields.io/github/actions/workflow/status/nezhahq/agent/agent.yml?label=Agent%20CI&logo=github&style=for-the-badge"> <img src="https://img.shields.io/badge/Installer-v0.18.3-brightgreen?style=for-the-badge&logo=linux">
|
<img alt="GitHub release (with filter)" src="https://img.shields.io/github/v/release/naiba/nezha?color=brightgreen&style=for-the-badge&logo=github&label=Dashboard"> <img src="https://img.shields.io/github/v/release/nezhahq/agent?color=brightgreen&label=Agent&style=for-the-badge&logo=github"> <img src="https://img.shields.io/github/actions/workflow/status/nezhahq/agent/agent.yml?label=Agent%20CI&logo=github&style=for-the-badge"> <img src="https://img.shields.io/badge/Installer-v0.19.0-brightgreen?style=for-the-badge&logo=linux">
|
||||||
<br>
|
<br>
|
||||||
<br>
|
<br>
|
||||||
<p>:trollface: <b>Nezha Monitoring: Self-hostable, lightweight, servers and websites monitoring and O&M tool.</b></p>
|
<p>:trollface: <b>Nezha Monitoring: Self-hostable, lightweight, servers and websites monitoring and O&M tool.</b></p>
|
||||||
@@ -44,8 +44,8 @@ You can change the dashboard language in the settings page (`/setting`) after th
|
|||||||
<a href="https://github.com/AkkiaS7" title="Akkia"><img src="https://avatars.githubusercontent.com/u/68485070?v=4" width="50;" alt="Akkia"/></a>
|
<a href="https://github.com/AkkiaS7" title="Akkia"><img src="https://avatars.githubusercontent.com/u/68485070?v=4" width="50;" alt="Akkia"/></a>
|
||||||
<a href="https://github.com/Erope" title="卖女孩的小火柴"><img src="https://avatars.githubusercontent.com/u/44471469?v=4" width="50;" alt="卖女孩的小火柴"/></a>
|
<a href="https://github.com/Erope" title="卖女孩的小火柴"><img src="https://avatars.githubusercontent.com/u/44471469?v=4" width="50;" alt="卖女孩的小火柴"/></a>
|
||||||
<a href="https://github.com/uubulb" title="UUBulb"><img src="https://avatars.githubusercontent.com/u/35923940?v=4" width="50;" alt="UUBulb"/></a>
|
<a href="https://github.com/uubulb" title="UUBulb"><img src="https://avatars.githubusercontent.com/u/35923940?v=4" width="50;" alt="UUBulb"/></a>
|
||||||
<a href="https://github.com/dysf888" title="黑歌"><img src="https://avatars.githubusercontent.com/u/47450409?v=4" width="50;" alt="黑歌"/></a>
|
|
||||||
<a href="https://github.com/nap0o" title="nap0o"><img src="https://avatars.githubusercontent.com/u/144927971?v=4" width="50;" alt="nap0o"/></a>
|
<a href="https://github.com/nap0o" title="nap0o"><img src="https://avatars.githubusercontent.com/u/144927971?v=4" width="50;" alt="nap0o"/></a>
|
||||||
|
<a href="https://github.com/dysf888" title="黑歌"><img src="https://avatars.githubusercontent.com/u/47450409?v=4" width="50;" alt="黑歌"/></a>
|
||||||
<a href="https://github.com/xykt" title="xykt"><img src="https://avatars.githubusercontent.com/u/152045469?v=4" width="50;" alt="xykt"/></a>
|
<a href="https://github.com/xykt" title="xykt"><img src="https://avatars.githubusercontent.com/u/152045469?v=4" width="50;" alt="xykt"/></a>
|
||||||
<a href="https://github.com/MikoyChinese" title="MikoyChinese"><img src="https://avatars.githubusercontent.com/u/22676744?v=4" width="50;" alt="MikoyChinese"/></a>
|
<a href="https://github.com/MikoyChinese" title="MikoyChinese"><img src="https://avatars.githubusercontent.com/u/22676744?v=4" width="50;" alt="MikoyChinese"/></a>
|
||||||
<a href="https://github.com/JackieSung4ev" title="JackieSung4ev"><img src="https://avatars.githubusercontent.com/u/24974735?v=4" width="50;" alt="JackieSung4ev"/></a>
|
<a href="https://github.com/JackieSung4ev" title="JackieSung4ev"><img src="https://avatars.githubusercontent.com/u/24974735?v=4" width="50;" alt="JackieSung4ev"/></a>
|
||||||
@@ -66,7 +66,6 @@ You can change the dashboard language in the settings page (`/setting`) after th
|
|||||||
<a href="https://github.com/DarcJC" title="Darc Z."><img src="https://avatars.githubusercontent.com/u/53445798?v=4" width="50;" alt="Darc Z."/></a>
|
<a href="https://github.com/DarcJC" title="Darc Z."><img src="https://avatars.githubusercontent.com/u/53445798?v=4" width="50;" alt="Darc Z."/></a>
|
||||||
<a href="https://github.com/Creling" title="Creling"><img src="https://avatars.githubusercontent.com/u/43109504?v=4" width="50;" alt="Creling"/></a>
|
<a href="https://github.com/Creling" title="Creling"><img src="https://avatars.githubusercontent.com/u/43109504?v=4" width="50;" alt="Creling"/></a>
|
||||||
<a href="https://github.com/coreff" title="Core F"><img src="https://avatars.githubusercontent.com/u/38347122?v=4" width="50;" alt="Core F"/></a>
|
<a href="https://github.com/coreff" title="Core F"><img src="https://avatars.githubusercontent.com/u/38347122?v=4" width="50;" alt="Core F"/></a>
|
||||||
<a href="https://github.com/nickfox-taterli" title="Tater Li"><img src="https://avatars.githubusercontent.com/u/19658596?v=4" width="50;" alt="Tater Li"/></a>
|
|
||||||
<a href="https://github.com/hmsjy2017" title="Tony"><img src="https://avatars.githubusercontent.com/u/42692274?v=4" width="50;" alt="Tony"/></a>
|
<a href="https://github.com/hmsjy2017" title="Tony"><img src="https://avatars.githubusercontent.com/u/42692274?v=4" width="50;" alt="Tony"/></a>
|
||||||
<a href="https://github.com/adminsama" title="adminsama"><img src="https://avatars.githubusercontent.com/u/60880076?v=4" width="50;" alt="adminsama"/></a>
|
<a href="https://github.com/adminsama" title="adminsama"><img src="https://avatars.githubusercontent.com/u/60880076?v=4" width="50;" alt="adminsama"/></a>
|
||||||
<a href="https://github.com/acgpiano" title="Acgpiano"><img src="https://avatars.githubusercontent.com/u/15900800?v=4" width="50;" alt="Acgpiano"/></a>
|
<a href="https://github.com/acgpiano" title="Acgpiano"><img src="https://avatars.githubusercontent.com/u/15900800?v=4" width="50;" alt="Acgpiano"/></a>
|
||||||
@@ -75,11 +74,13 @@ You can change the dashboard language in the settings page (`/setting`) after th
|
|||||||
<a href="https://github.com/hiDandelion" title="hiDandelion"><img src="https://avatars.githubusercontent.com/u/77157418?v=4" width="50;" alt="hiDandelion"/></a>
|
<a href="https://github.com/hiDandelion" title="hiDandelion"><img src="https://avatars.githubusercontent.com/u/77157418?v=4" width="50;" alt="hiDandelion"/></a>
|
||||||
<a href="https://github.com/yuanweize" title="I"><img src="https://avatars.githubusercontent.com/u/30067203?v=4" width="50;" alt="I"/></a>
|
<a href="https://github.com/yuanweize" title="I"><img src="https://avatars.githubusercontent.com/u/30067203?v=4" width="50;" alt="I"/></a>
|
||||||
<a href="https://github.com/lvyaoting" title="lvyaoting"><img src="https://avatars.githubusercontent.com/u/166296299?v=4" width="50;" alt="lvyaoting"/></a>
|
<a href="https://github.com/lvyaoting" title="lvyaoting"><img src="https://avatars.githubusercontent.com/u/166296299?v=4" width="50;" alt="lvyaoting"/></a>
|
||||||
|
<a href="https://github.com/lyj0309" title="lyj"><img src="https://avatars.githubusercontent.com/u/50474995?v=4" width="50;" alt="lyj"/></a>
|
||||||
<a href="https://github.com/unclezs" title="unclezs"><img src="https://avatars.githubusercontent.com/u/42318775?v=4" width="50;" alt="unclezs"/></a>
|
<a href="https://github.com/unclezs" title="unclezs"><img src="https://avatars.githubusercontent.com/u/42318775?v=4" width="50;" alt="unclezs"/></a>
|
||||||
<a href="https://github.com/ysicing" title="缘生"><img src="https://avatars.githubusercontent.com/u/8605565?v=4" width="50;" alt="缘生"/></a>
|
<a href="https://github.com/ysicing" title="缘生"><img src="https://avatars.githubusercontent.com/u/8605565?v=4" width="50;" alt="缘生"/></a>
|
||||||
<a href="https://github.com/zhucaidan" title="zhucaidan"><img src="https://avatars.githubusercontent.com/u/47970938?v=4" width="50;" alt="zhucaidan"/></a>
|
<a href="https://github.com/zhucaidan" title="zhucaidan"><img src="https://avatars.githubusercontent.com/u/47970938?v=4" width="50;" alt="zhucaidan"/></a>
|
||||||
<a href="https://github.com/arkylin" title="凌"><img src="https://avatars.githubusercontent.com/u/35104502?v=4" width="50;" alt="凌"/></a>
|
<a href="https://github.com/arkylin" title="凌"><img src="https://avatars.githubusercontent.com/u/35104502?v=4" width="50;" alt="凌"/></a>
|
||||||
<a href="https://github.com/colour93" title="玖叁"><img src="https://avatars.githubusercontent.com/u/64313711?v=4" width="50;" alt="玖叁"/></a>
|
<a href="https://github.com/colour93" title="玖叁"><img src="https://avatars.githubusercontent.com/u/64313711?v=4" width="50;" alt="玖叁"/></a>
|
||||||
|
<a href="https://github.com/nickfox-taterli" title="Tater Li"><img src="https://avatars.githubusercontent.com/u/19658596?v=4" width="50;" alt="Tater Li"/></a>
|
||||||
<a href="https://github.com/IamTaoChen" title="Tao Chen"><img src="https://avatars.githubusercontent.com/u/42793494?v=4" width="50;" alt="Tao Chen"/></a>
|
<a href="https://github.com/IamTaoChen" title="Tao Chen"><img src="https://avatars.githubusercontent.com/u/42793494?v=4" width="50;" alt="Tao Chen"/></a>
|
||||||
<a href="https://github.com/Septrum101" title="Spetrum"><img src="https://avatars.githubusercontent.com/u/11692994?v=4" width="50;" alt="Spetrum"/></a>
|
<a href="https://github.com/Septrum101" title="Spetrum"><img src="https://avatars.githubusercontent.com/u/11692994?v=4" width="50;" alt="Spetrum"/></a>
|
||||||
<a href="https://github.com/dreamingsleeping" title="Nanjing Hopefun Network Technology Co. Ltd."><img src="https://avatars.githubusercontent.com/u/13828658?v=4" width="50;" alt="Nanjing Hopefun Network Technology Co. Ltd."/></a>
|
<a href="https://github.com/dreamingsleeping" title="Nanjing Hopefun Network Technology Co. Ltd."><img src="https://avatars.githubusercontent.com/u/13828658?v=4" width="50;" alt="Nanjing Hopefun Network Technology Co. Ltd."/></a>
|
||||||
|
|||||||
@@ -209,7 +209,7 @@ func (cp *commonPage) network(c *gin.Context) {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cp *commonPage) getServerStat(c *gin.Context) ([]byte, error) {
|
func (cp *commonPage) getServerStat(c *gin.Context, withPublicNote bool) ([]byte, error) {
|
||||||
_, isMember := c.Get(model.CtxKeyAuthorizedUser)
|
_, isMember := c.Get(model.CtxKeyAuthorizedUser)
|
||||||
_, isViewPasswordVerfied := c.Get(model.CtxKeyViewPasswordVerified)
|
_, isViewPasswordVerfied := c.Get(model.CtxKeyViewPasswordVerified)
|
||||||
authorized := isMember || isViewPasswordVerfied
|
authorized := isMember || isViewPasswordVerfied
|
||||||
@@ -217,18 +217,20 @@ func (cp *commonPage) getServerStat(c *gin.Context) ([]byte, error) {
|
|||||||
singleton.SortedServerLock.RLock()
|
singleton.SortedServerLock.RLock()
|
||||||
defer singleton.SortedServerLock.RUnlock()
|
defer singleton.SortedServerLock.RUnlock()
|
||||||
|
|
||||||
var servers []*model.Server
|
var serverList []*model.Server
|
||||||
|
|
||||||
if authorized {
|
if authorized {
|
||||||
servers = singleton.SortedServerList
|
serverList = singleton.SortedServerList
|
||||||
} else {
|
} else {
|
||||||
filteredServers := make([]*model.Server, len(singleton.SortedServerListForGuest))
|
serverList = singleton.SortedServerListForGuest
|
||||||
for i, server := range singleton.SortedServerListForGuest {
|
}
|
||||||
filteredServer := *server
|
|
||||||
filteredServer.DDNSDomain = "redacted"
|
var servers []*model.Server
|
||||||
filteredServers[i] = &filteredServer
|
for _, server := range serverList {
|
||||||
|
item := *server
|
||||||
|
if !withPublicNote {
|
||||||
|
item.PublicNote = ""
|
||||||
}
|
}
|
||||||
servers = filteredServers
|
servers = append(servers, &item)
|
||||||
}
|
}
|
||||||
|
|
||||||
return utils.Json.Marshal(Data{
|
return utils.Json.Marshal(Data{
|
||||||
@@ -240,7 +242,7 @@ func (cp *commonPage) getServerStat(c *gin.Context) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (cp *commonPage) home(c *gin.Context) {
|
func (cp *commonPage) home(c *gin.Context) {
|
||||||
stat, err := cp.getServerStat(c)
|
stat, err := cp.getServerStat(c, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
mygin.ShowErrorPage(c, mygin.ErrInfo{
|
mygin.ShowErrorPage(c, mygin.ErrInfo{
|
||||||
Code: http.StatusInternalServerError,
|
Code: http.StatusInternalServerError,
|
||||||
@@ -285,7 +287,7 @@ func (cp *commonPage) ws(c *gin.Context) {
|
|||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
count := 0
|
count := 0
|
||||||
for {
|
for {
|
||||||
stat, err := cp.getServerStat(c)
|
stat, err := cp.getServerStat(c, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -306,6 +306,7 @@ type serverForm struct {
|
|||||||
Secret string
|
Secret string
|
||||||
Tag string
|
Tag string
|
||||||
Note string
|
Note string
|
||||||
|
PublicNote string
|
||||||
HideForGuest string
|
HideForGuest string
|
||||||
EnableDDNS string
|
EnableDDNS string
|
||||||
EnableIPv4 string
|
EnableIPv4 string
|
||||||
@@ -326,6 +327,7 @@ func (ma *memberAPI) addOrEditServer(c *gin.Context) {
|
|||||||
s.ID = sf.ID
|
s.ID = sf.ID
|
||||||
s.Tag = sf.Tag
|
s.Tag = sf.Tag
|
||||||
s.Note = sf.Note
|
s.Note = sf.Note
|
||||||
|
s.PublicNote = sf.PublicNote
|
||||||
s.HideForGuest = sf.HideForGuest == "on"
|
s.HideForGuest = sf.HideForGuest == "on"
|
||||||
s.EnableDDNS = sf.EnableDDNS == "on"
|
s.EnableDDNS = sf.EnableDDNS == "on"
|
||||||
s.EnableIPv4 = sf.EnableIPv4 == "on"
|
s.EnableIPv4 = sf.EnableIPv4 == "on"
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"time"
|
"time"
|
||||||
|
_ "time/tzdata"
|
||||||
|
|
||||||
"github.com/naiba/nezha/cmd/dashboard/controller"
|
"github.com/naiba/nezha/cmd/dashboard/controller"
|
||||||
"github.com/naiba/nezha/cmd/dashboard/rpc"
|
"github.com/naiba/nezha/cmd/dashboard/rpc"
|
||||||
|
|||||||
+5
-3
@@ -15,7 +15,8 @@ type Server struct {
|
|||||||
Name string
|
Name string
|
||||||
Tag string // 分组名
|
Tag string // 分组名
|
||||||
Secret string `gorm:"uniqueIndex" json:"-"`
|
Secret string `gorm:"uniqueIndex" json:"-"`
|
||||||
Note string `json:"-"` // 管理员可见备注
|
Note string `json:"-"` // 管理员可见备注
|
||||||
|
PublicNote string `json:"PublicNote,omitempty"` // 公开备注
|
||||||
DisplayIndex int // 展示排序,越大越靠前
|
DisplayIndex int // 展示排序,越大越靠前
|
||||||
HideForGuest bool // 对游客隐藏
|
HideForGuest bool // 对游客隐藏
|
||||||
EnableDDNS bool `json:"-"` // 是否启用DDNS 未在配置文件中启用DDNS 或 DDNS检查时间为0时此项无效
|
EnableDDNS bool `json:"-"` // 是否启用DDNS 未在配置文件中启用DDNS 或 DDNS检查时间为0时此项无效
|
||||||
@@ -54,12 +55,13 @@ func boolToString(b bool) string {
|
|||||||
return "false"
|
return "false"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s Server) Marshal() template.JS {
|
func (s Server) MarshalForDashboard() template.JS {
|
||||||
name, _ := utils.Json.Marshal(s.Name)
|
name, _ := utils.Json.Marshal(s.Name)
|
||||||
tag, _ := utils.Json.Marshal(s.Tag)
|
tag, _ := utils.Json.Marshal(s.Tag)
|
||||||
note, _ := utils.Json.Marshal(s.Note)
|
note, _ := utils.Json.Marshal(s.Note)
|
||||||
secret, _ := utils.Json.Marshal(s.Secret)
|
secret, _ := utils.Json.Marshal(s.Secret)
|
||||||
ddnsDomain, _ := utils.Json.Marshal(s.DDNSDomain)
|
ddnsDomain, _ := utils.Json.Marshal(s.DDNSDomain)
|
||||||
ddnsProfile, _ := utils.Json.Marshal(s.DDNSProfile)
|
ddnsProfile, _ := utils.Json.Marshal(s.DDNSProfile)
|
||||||
return template.JS(fmt.Sprintf(`{"ID":%d,"Name":%s,"Secret":%s,"DisplayIndex":%d,"Tag":%s,"Note":%s,"HideForGuest": %s,"EnableDDNS": %s,"EnableIPv4": %s,"EnableIpv6": %s,"DDNSDomain": %s,"DDNSProfile": %s}`, s.ID, name, secret, s.DisplayIndex, tag, note, boolToString(s.HideForGuest), boolToString(s.EnableDDNS), boolToString(s.EnableIPv4), boolToString(s.EnableIpv6), ddnsDomain, ddnsProfile)) // #nosec
|
publicNote, _ := utils.Json.Marshal(s.PublicNote)
|
||||||
|
return template.JS(fmt.Sprintf(`{"ID":%d,"Name":%s,"Secret":%s,"DisplayIndex":%d,"Tag":%s,"Note":%s,"HideForGuest": %s,"EnableDDNS": %s,"EnableIPv4": %s,"EnableIpv6": %s,"DDNSDomain": %s,"DDNSProfile": %s,"PublicNote": %s}`, s.ID, name, secret, s.DisplayIndex, tag, note, boolToString(s.HideForGuest), boolToString(s.EnableDDNS), boolToString(s.EnableIPv4), boolToString(s.EnableIpv6), ddnsDomain, ddnsProfile, publicNote))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ func TestServerMarshal(t *testing.T) {
|
|||||||
Name: patterns[i],
|
Name: patterns[i],
|
||||||
Tag: patterns[i],
|
Tag: patterns[i],
|
||||||
}
|
}
|
||||||
serverStr := string(server.Marshal())
|
serverStr := string(server.MarshalForDashboard())
|
||||||
var serverRestore Server
|
var serverRestore Server
|
||||||
if utils.Json.Unmarshal([]byte(serverStr), &serverRestore) != nil {
|
if utils.Json.Unmarshal([]byte(serverStr), &serverRestore) != nil {
|
||||||
t.Fatalf("Error: %s", serverStr)
|
t.Fatalf("Error: %s", serverStr)
|
||||||
|
|||||||
Vendored
+18
@@ -226,6 +226,9 @@ other = "Secret"
|
|||||||
[Note]
|
[Note]
|
||||||
other = "Note"
|
other = "Note"
|
||||||
|
|
||||||
|
[PublicNote]
|
||||||
|
other = "Public Note"
|
||||||
|
|
||||||
[LinuxOneKeyInstall]
|
[LinuxOneKeyInstall]
|
||||||
other = "Linux One-Command Install"
|
other = "Linux One-Command Install"
|
||||||
|
|
||||||
@@ -687,3 +690,18 @@ other = "Cancel"
|
|||||||
|
|
||||||
[FMError]
|
[FMError]
|
||||||
other = "Agent returned an error, please view the console for details. To open a new connection, reopen the FM again."
|
other = "Agent returned an error, please view the console for details. To open a new connection, reopen the FM again."
|
||||||
|
|
||||||
|
[Remaining]
|
||||||
|
other = "Remaining"
|
||||||
|
|
||||||
|
[Lifetime]
|
||||||
|
other = "Lifetime"
|
||||||
|
|
||||||
|
[Price]
|
||||||
|
other = "Price"
|
||||||
|
|
||||||
|
[Expired]
|
||||||
|
other = "Expired"
|
||||||
|
|
||||||
|
[Days]
|
||||||
|
other = "d"
|
||||||
|
|||||||
Vendored
+18
@@ -226,6 +226,9 @@ other = "Secreto"
|
|||||||
[Note]
|
[Note]
|
||||||
other = "Nota"
|
other = "Nota"
|
||||||
|
|
||||||
|
[PublicNote]
|
||||||
|
other = "Nota Pública"
|
||||||
|
|
||||||
[LinuxOneKeyInstall]
|
[LinuxOneKeyInstall]
|
||||||
other = "Instalación Linux con Un Solo Clic"
|
other = "Instalación Linux con Un Solo Clic"
|
||||||
|
|
||||||
@@ -687,3 +690,18 @@ other = "Cancelar"
|
|||||||
|
|
||||||
[FMError]
|
[FMError]
|
||||||
other = "Agent devolvió un error, consulte la consola para obtener más detalles. Para abrir una nueva conexión, vuelva a abrir el FM."
|
other = "Agent devolvió un error, consulte la consola para obtener más detalles. Para abrir una nueva conexión, vuelva a abrir el FM."
|
||||||
|
|
||||||
|
[Remaining]
|
||||||
|
other = "Remaining"
|
||||||
|
|
||||||
|
[Lifetime]
|
||||||
|
other = "Lifetime"
|
||||||
|
|
||||||
|
[Price]
|
||||||
|
other = "Price"
|
||||||
|
|
||||||
|
[Expired]
|
||||||
|
other = "Expired"
|
||||||
|
|
||||||
|
[Days]
|
||||||
|
other = "d"
|
||||||
|
|||||||
Vendored
+18
@@ -226,6 +226,9 @@ other = "密钥"
|
|||||||
[Note]
|
[Note]
|
||||||
other = "备注"
|
other = "备注"
|
||||||
|
|
||||||
|
[PublicNote]
|
||||||
|
other = "公开备注"
|
||||||
|
|
||||||
[LinuxOneKeyInstall]
|
[LinuxOneKeyInstall]
|
||||||
other = "Linux 一键安装"
|
other = "Linux 一键安装"
|
||||||
|
|
||||||
@@ -687,3 +690,18 @@ other = "取消"
|
|||||||
|
|
||||||
[FMError]
|
[FMError]
|
||||||
other = "Agent 返回了错误,请查看控制台获取详细信息。要建立新连接,请重新打开 FM。"
|
other = "Agent 返回了错误,请查看控制台获取详细信息。要建立新连接,请重新打开 FM。"
|
||||||
|
|
||||||
|
[Remaining]
|
||||||
|
other = "剩余"
|
||||||
|
|
||||||
|
[Lifetime]
|
||||||
|
other = "永续"
|
||||||
|
|
||||||
|
[Price]
|
||||||
|
other = "价格"
|
||||||
|
|
||||||
|
[Expired]
|
||||||
|
other = "已到期"
|
||||||
|
|
||||||
|
[Days]
|
||||||
|
other = "天"
|
||||||
|
|||||||
Vendored
+18
@@ -226,6 +226,9 @@ other = "金鑰"
|
|||||||
[Note]
|
[Note]
|
||||||
other = "備註"
|
other = "備註"
|
||||||
|
|
||||||
|
[PublicNote]
|
||||||
|
other = "公開備註"
|
||||||
|
|
||||||
[LinuxOneKeyInstall]
|
[LinuxOneKeyInstall]
|
||||||
other = "Linux 一鍵安裝"
|
other = "Linux 一鍵安裝"
|
||||||
|
|
||||||
@@ -687,3 +690,18 @@ other = "取消"
|
|||||||
|
|
||||||
[FMError]
|
[FMError]
|
||||||
other = "Agent 回傳了錯誤,請查看主控台獲取詳細資訊。要建立新連線,請重新開啟 FM。"
|
other = "Agent 回傳了錯誤,請查看主控台獲取詳細資訊。要建立新連線,請重新開啟 FM。"
|
||||||
|
|
||||||
|
[Remaining]
|
||||||
|
other = "剩餘"
|
||||||
|
|
||||||
|
[Lifetime]
|
||||||
|
other = "永續"
|
||||||
|
|
||||||
|
[Price]
|
||||||
|
other = "價格"
|
||||||
|
|
||||||
|
[Expired]
|
||||||
|
other = "已到期"
|
||||||
|
|
||||||
|
[Days]
|
||||||
|
other = "天"
|
||||||
|
|||||||
@@ -331,6 +331,7 @@ function addOrEditServer(server, conf) {
|
|||||||
.find("input[name=DisplayIndex]")
|
.find("input[name=DisplayIndex]")
|
||||||
.val(server ? server.DisplayIndex : null);
|
.val(server ? server.DisplayIndex : null);
|
||||||
modal.find("textarea[name=Note]").val(server ? server.Note : null);
|
modal.find("textarea[name=Note]").val(server ? server.Note : null);
|
||||||
|
modal.find("textarea[name=PublicNote]").val(server ? server.PublicNote : null);
|
||||||
if (server) {
|
if (server) {
|
||||||
modal.find(".secret.field").attr("style", "");
|
modal.find(".secret.field").attr("style", "");
|
||||||
modal.find(".command.field").attr("style", "");
|
modal.find(".command.field").attr("style", "");
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 3.9 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
@@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"name": "Nezha",
|
||||||
|
"short_name": "Nezha",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "/static/manifest-192x192.png",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"type": "image/png",
|
||||||
|
"purpose": "any"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/static/manifest-192x192.png",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"type": "image/png",
|
||||||
|
"purpose": "maskable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/static/manifest-512x512.png",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"type": "image/png",
|
||||||
|
"purpose": "any"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/static/manifest-512x512.png",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"type": "image/png",
|
||||||
|
"purpose": "maskable"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"start_url": "/",
|
||||||
|
"display": "standalone",
|
||||||
|
"theme_color": "#000000",
|
||||||
|
"background_color": "#000000",
|
||||||
|
"lang": "eu-US",
|
||||||
|
"dir": "auto"
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"name": "哪吒监控",
|
||||||
|
"short_name": "哪吒监控",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "/static/manifest-192x192.png",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"type": "image/png",
|
||||||
|
"purpose": "any"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/static/manifest-192x192.png",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"type": "image/png",
|
||||||
|
"purpose": "maskable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/static/manifest-512x512.png",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"type": "image/png",
|
||||||
|
"purpose": "any"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/static/manifest-512x512.png",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"type": "image/png",
|
||||||
|
"purpose": "maskable"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"start_url": "/",
|
||||||
|
"display": "standalone",
|
||||||
|
"theme_color": "#000000",
|
||||||
|
"background_color": "#000000",
|
||||||
|
"lang": "zh-CN",
|
||||||
|
"dir": "auto"
|
||||||
|
}
|
||||||
+11
@@ -13,7 +13,18 @@ const mixinsVue = {
|
|||||||
this.isMobile = this.checkIsMobile();
|
this.isMobile = this.checkIsMobile();
|
||||||
this.preferredTemplate = this.getCookie('preferred_theme') ? this.getCookie('preferred_theme') : this.$root.defaultTemplate;
|
this.preferredTemplate = this.getCookie('preferred_theme') ? this.getCookie('preferred_theme') : this.$root.defaultTemplate;
|
||||||
},
|
},
|
||||||
|
mounted() {
|
||||||
|
this.initDropdown();
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
initDropdown() {
|
||||||
|
if(this.isMobile) $('.ui.dropdown').dropdown({
|
||||||
|
action: 'hide',
|
||||||
|
on: 'click',
|
||||||
|
duration: 100,
|
||||||
|
direction: 'direction'
|
||||||
|
});
|
||||||
|
},
|
||||||
toggleTemplate(template) {
|
toggleTemplate(template) {
|
||||||
if( template != this.preferredTemplate){
|
if( template != this.preferredTemplate){
|
||||||
this.preferredTemplate = template;
|
this.preferredTemplate = template;
|
||||||
|
|||||||
+8
-3
@@ -8,6 +8,11 @@ body[theme="dark"] .navbar .navbar-brand {
|
|||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body[theme="dark"] .navbar .navbar-nav li.pc-active a,
|
||||||
|
body[theme="dark"] .navbar .navbar-nav li.m-active a{
|
||||||
|
color: rgba(73, 146, 255, 1);
|
||||||
|
}
|
||||||
|
|
||||||
body[theme="dark"] .navbar .dropdown-menu {
|
body[theme="dark"] .navbar .dropdown-menu {
|
||||||
background-color: rgba(23, 26, 30, 1);
|
background-color: rgba(23, 26, 30, 1);
|
||||||
border-color: rgba(49, 54, 59, 1);
|
border-color: rgba(49, 54, 59, 1);
|
||||||
@@ -88,8 +93,8 @@ body[theme="dark"] .table > tbody > tr.expandRow.even > td:before{
|
|||||||
/* expandRow展开部分样式结束 */
|
/* expandRow展开部分样式结束 */
|
||||||
|
|
||||||
body[theme="dark"] .progress {
|
body[theme="dark"] .progress {
|
||||||
background-image: linear-gradient(#2c2c2c 0,rgba(28, 29, 38, 1) 100%);
|
background-image: none;
|
||||||
background-color: rgba(28, 29, 38, 1);
|
background-color: rgba(255, 255, 255, 0.075);
|
||||||
}
|
}
|
||||||
|
|
||||||
body[theme="dark"] .progress-bar {
|
body[theme="dark"] .progress-bar {
|
||||||
@@ -174,5 +179,5 @@ body[theme="dark"] .toolbox i{
|
|||||||
}
|
}
|
||||||
|
|
||||||
body[theme="dark"] .network-box .network-box-header{
|
body[theme="dark"] .network-box .network-box-header{
|
||||||
border-bottom: 1px solid rgba(110, 112, 121, 0.25);
|
border-bottom-color: rgba(110, 112, 121, 0.25);
|
||||||
}
|
}
|
||||||
+7
-2
@@ -16,10 +16,15 @@ body[theme="light"] .navbar .navbar-brand {
|
|||||||
color: rgba(0, 0, 0, 0.87);
|
color: rgba(0, 0, 0, 0.87);
|
||||||
}
|
}
|
||||||
|
|
||||||
body[theme="light"] .navbar .navbar-nav > li > a{
|
body[theme="light"] .navbar .navbar-nav > li > a {
|
||||||
color: rgba(0, 0, 0, 0.87);
|
color: rgba(0, 0, 0, 0.87);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body[theme="light"] .navbar .navbar-nav li.pc-active a,
|
||||||
|
body[theme="light"] .navbar .navbar-nav li.m-active a {
|
||||||
|
color: rgba(73, 146, 255, 1);
|
||||||
|
}
|
||||||
|
|
||||||
body[theme="light"] .navbar .navbar-nav > .open > a:focus,
|
body[theme="light"] .navbar .navbar-nav > .open > a:focus,
|
||||||
body[theme="light"] .navbar .navbar-nav > .open > a:hover,
|
body[theme="light"] .navbar .navbar-nav > .open > a:hover,
|
||||||
body[theme="light"] .navbar .navbar-nav > .active > a,
|
body[theme="light"] .navbar .navbar-nav > .active > a,
|
||||||
@@ -198,7 +203,7 @@ body[theme="light"] .modal-header i.xclose{
|
|||||||
}
|
}
|
||||||
|
|
||||||
body[theme="light"] .network-box .network-box-header{
|
body[theme="light"] .network-box .network-box-header{
|
||||||
border-bottom: 1px solid rgba(224, 230, 241, 0.6);
|
border-bottom-color: rgba(224, 230, 241, 0.6);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: 767px) {
|
@media only screen and (max-width: 767px) {
|
||||||
|
|||||||
+34
-3
@@ -237,8 +237,8 @@ tr.accordion-toggle{
|
|||||||
}
|
}
|
||||||
|
|
||||||
.node-cell.network {
|
.node-cell.network {
|
||||||
min-width: 100px;
|
min-width: 110px;
|
||||||
max-width: 100px;
|
max-width: 110px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.node-cell.traffic {
|
.node-cell.traffic {
|
||||||
@@ -250,6 +250,24 @@ tr.accordion-toggle{
|
|||||||
max-width: 50px;
|
max-width: 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.node-cell.remaining {
|
||||||
|
min-width: 65px;
|
||||||
|
max-width: 65px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-cell.remaining .additional {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
font-size: 12px;
|
||||||
|
text-align: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-cell.remaining .additional small{
|
||||||
|
white-space: nowrap;
|
||||||
|
font-size: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
.temp-detail {
|
.temp-detail {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
@@ -331,6 +349,8 @@ td.ping-network-quality {
|
|||||||
padding: 5px 0px 15px 5px;
|
padding: 5px 0px 15px 5px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
border-bottom-width: 1px;
|
||||||
|
border-bottom-style: solid;
|
||||||
}
|
}
|
||||||
|
|
||||||
.network-box .network-box-header .dropdown-menu {
|
.network-box .network-box-header .dropdown-menu {
|
||||||
@@ -381,6 +401,7 @@ td.ping-network-quality {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.network-box .chartTitle {
|
.network-box .chartTitle {
|
||||||
|
cursor: pointer;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
margin: 18px 0px 15px 0px;
|
margin: 18px 0px 15px 0px;
|
||||||
@@ -480,7 +501,12 @@ footer p{
|
|||||||
padding-top:60px !important;
|
padding-top:60px !important;
|
||||||
}
|
}
|
||||||
.nezha {
|
.nezha {
|
||||||
min-height: calc(74.5vh);
|
min-height: calc(100vh - 90px);
|
||||||
|
min-height: calc(var(--vh, 1vh) * 100 - 90px);
|
||||||
|
}
|
||||||
|
#chartbox {
|
||||||
|
min-height: calc(100vh - 170px);
|
||||||
|
min-height: calc(var(--vh, 1vh) * 100 - 170px);
|
||||||
}
|
}
|
||||||
.content {
|
.content {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
@@ -523,6 +549,10 @@ footer p{
|
|||||||
display: none;
|
display: none;
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
|
.node-cell.remaining {
|
||||||
|
min-width: 75px;
|
||||||
|
max-width: 75px;
|
||||||
|
}
|
||||||
.accordian-body{
|
.accordian-body{
|
||||||
margin: 5px 0px 5px 10px;
|
margin: 5px 0px 5px 10px;
|
||||||
}
|
}
|
||||||
@@ -566,6 +596,7 @@ footer p{
|
|||||||
.network-box .network-box-header {
|
.network-box .network-box-header {
|
||||||
margin: 8px 0px 0px 8px;
|
margin: 8px 0px 0px 8px;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
|
border-bottom: none;
|
||||||
}
|
}
|
||||||
.network-box .chartTitle {
|
.network-box .chartTitle {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
|
|||||||
+11
@@ -26,7 +26,9 @@ const mixinsVue = {
|
|||||||
this.semiTransparent = this.initSemiTransparent();
|
this.semiTransparent = this.initSemiTransparent();
|
||||||
this.preferredTemplate = this.getCookie('preferred_theme') ? this.getCookie('preferred_theme') : this.$root.defaultTemplate;
|
this.preferredTemplate = this.getCookie('preferred_theme') ? this.getCookie('preferred_theme') : this.$root.defaultTemplate;
|
||||||
this.colors = this.theme == "dark" ? this.colorsDark : this.colorsLight;
|
this.colors = this.theme == "dark" ? this.colorsDark : this.colorsLight;
|
||||||
|
this.setBenchmarkHeight();
|
||||||
window.addEventListener('scroll', this.handleScroll);
|
window.addEventListener('scroll', this.handleScroll);
|
||||||
|
window.addEventListener('resize', this.setBenchmarkHeight());
|
||||||
},
|
},
|
||||||
destroyed() {
|
destroyed() {
|
||||||
window.removeEventListener('scroll', this.handleScroll);
|
window.removeEventListener('scroll', this.handleScroll);
|
||||||
@@ -152,6 +154,15 @@ const mixinsVue = {
|
|||||||
},
|
},
|
||||||
checkIsMobile() { // 检测设备类型,页面宽度小于768px认为是移动设备
|
checkIsMobile() { // 检测设备类型,页面宽度小于768px认为是移动设备
|
||||||
return window.innerWidth <= 768;
|
return window.innerWidth <= 768;
|
||||||
|
},
|
||||||
|
isMenuActive(page){
|
||||||
|
if(page == this.$root.page) {
|
||||||
|
return this.isMobile ? 'm-active' : 'pc-active';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setBenchmarkHeight() {
|
||||||
|
let vh = window.innerHeight * 0.01;
|
||||||
|
document.documentElement.style.setProperty('--vh', `${vh}px`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Vendored
+1
-1
@@ -10,7 +10,7 @@
|
|||||||
<script src="https://unpkg.com/[email protected]/dist/semantic.min.js"></script>
|
<script src="https://unpkg.com/[email protected]/dist/semantic.min.js"></script>
|
||||||
<script src="/static/semantic-ui-alerts.min.js"></script>
|
<script src="/static/semantic-ui-alerts.min.js"></script>
|
||||||
<script src="https://unpkg.com/[email protected]/dist/vue.min.js"></script>
|
<script src="https://unpkg.com/[email protected]/dist/vue.min.js"></script>
|
||||||
<script src="/static/main.js?v20240714"></script>
|
<script src="/static/main.js?v2024927"></script>
|
||||||
<script>
|
<script>
|
||||||
(function () {
|
(function () {
|
||||||
updateLang({{.LANG }});
|
updateLang({{.LANG }});
|
||||||
|
|||||||
+4
@@ -56,6 +56,10 @@
|
|||||||
<label>{{tr "Note"}}</label>
|
<label>{{tr "Note"}}</label>
|
||||||
<textarea name="Note"></textarea>
|
<textarea name="Note"></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label>{{tr "PublicNote"}}</label>
|
||||||
|
<textarea name="PublicNote"></textarea>
|
||||||
|
</div>
|
||||||
<div class="command field">
|
<div class="command field">
|
||||||
<label>{{tr "LinuxOneKeyInstall"}}</label>
|
<label>{{tr "LinuxOneKeyInstall"}}</label>
|
||||||
<div class="ui message">
|
<div class="ui message">
|
||||||
|
|||||||
+14
-10
@@ -30,12 +30,10 @@
|
|||||||
<th>{{tr "VersionNumber"}}</th>
|
<th>{{tr "VersionNumber"}}</th>
|
||||||
<th>{{tr "HideForGuest"}}</th>
|
<th>{{tr "HideForGuest"}}</th>
|
||||||
<th>{{tr "EnableDDNS"}}</th>
|
<th>{{tr "EnableDDNS"}}</th>
|
||||||
<th>{{tr "EnableIPv4"}}</th>
|
|
||||||
<th>{{tr "EnableIpv6"}}</th>
|
|
||||||
<th>{{tr "DDNSDomain"}}</th>
|
|
||||||
<th>{{tr "Secret"}}</th>
|
<th>{{tr "Secret"}}</th>
|
||||||
<th>{{tr "OneKeyInstall"}}</th>
|
<th>{{tr "OneKeyInstall"}}</th>
|
||||||
<th>{{tr "Note"}}</th>
|
<th>{{tr "Note"}}</th>
|
||||||
|
<th>{{tr "PublicNote"}}</th>
|
||||||
<th>{{tr "Administration"}}</th>
|
<th>{{tr "Administration"}}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -50,9 +48,6 @@
|
|||||||
<td>{{$server.Host.Version}}</td>
|
<td>{{$server.Host.Version}}</td>
|
||||||
<td>{{$server.HideForGuest}}</td>
|
<td>{{$server.HideForGuest}}</td>
|
||||||
<td>{{$server.EnableDDNS}}</td>
|
<td>{{$server.EnableDDNS}}</td>
|
||||||
<td>{{$server.EnableIPv4}}</td>
|
|
||||||
<td>{{$server.EnableIpv6}}</td>
|
|
||||||
<td>{{$server.DDNSDomain}}</td>
|
|
||||||
<td>
|
<td>
|
||||||
<button class="ui icon green mini button" data-clipboard-text="{{$server.Secret}}" data-tooltip="{{tr "ClickToCopy"}}">
|
<button class="ui icon green mini button" data-clipboard-text="{{$server.Secret}}" data-tooltip="{{tr "ClickToCopy"}}">
|
||||||
<i class="copy icon"></i>
|
<i class="copy icon"></i>
|
||||||
@@ -75,13 +70,22 @@
|
|||||||
<i class="apple icon"></i>
|
<i class="apple icon"></i>
|
||||||
</button>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
<td style="word-break: break-word;white-space: pre-wrap;">{{$server.Note}}</td>
|
<td>
|
||||||
|
{{if $server.Note}}
|
||||||
|
<button class="ui icon green mini button" title="{{ $server.Note }}" onclick="addOrEditServer({{$server.MarshalForDashboard}})"><i class="sticky note icon"></i></button>
|
||||||
|
{{end}}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{if $server.PublicNote}}
|
||||||
|
<button class="ui icon green mini button" title="{{$server.PublicNote}}" onclick="addOrEditServer({{$server.MarshalForDashboard}})"><i class="sticky clipboard icon"></i></button>
|
||||||
|
{{end}}
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div class="ui mini icon buttons">
|
<div class="ui mini icon buttons">
|
||||||
<button class="ui button" onclick="connectToServer({{$server.ID}})">
|
<button class="ui button" onclick="connectToServer({{$server.ID}})">
|
||||||
<i class="terminal icon"></i>
|
<i class="terminal icon"></i>
|
||||||
</button>
|
</button>
|
||||||
<button class="ui button" onclick="addOrEditServer({{$server.Marshal}})">
|
<button class="ui button" onclick="addOrEditServer({{$server.MarshalForDashboard}})">
|
||||||
<i class="edit icon"></i>
|
<i class="edit icon"></i>
|
||||||
</button>
|
</button>
|
||||||
<button class="ui button"
|
<button class="ui button"
|
||||||
@@ -155,8 +159,8 @@
|
|||||||
}
|
}
|
||||||
function batchEditServerGroup() {
|
function batchEditServerGroup() {
|
||||||
let groupName = prompt('{{tr "InputServerGroupName"}}')
|
let groupName = prompt('{{tr "InputServerGroupName"}}')
|
||||||
if (!groupName) {
|
if (groupName === null) {
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
const servers = []
|
const servers = []
|
||||||
checkBoxList.forEach(cb => {
|
checkBoxList.forEach(cb => {
|
||||||
|
|||||||
+14
-5
@@ -3,12 +3,21 @@
|
|||||||
<html lang="{{.Conf.Language}}">
|
<html lang="{{.Conf.Language}}">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<title>{{ .Title }}</title>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta charset="utf-8">
|
||||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||||
<meta content="telephone=no" name="format-detection">
|
<meta content="telephone=no" name="format-detection">
|
||||||
<title>{{.Title}}</title>
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<link rel="shortcut icon" type="image/png" href="/static/logo.svg" />
|
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||||
|
<meta name="mobile-web-app-capable" content="yes" />
|
||||||
|
<meta name="theme-color" content="#000000" />
|
||||||
|
{{ if eq .Conf.Language "zh-CN" }}
|
||||||
|
<link rel="manifest" href="/static/manifest-zh-CN.json?v20240905" />
|
||||||
|
{{ else }}
|
||||||
|
<link rel="manifest" href="/static/manifest-en-US.json?v20240905" />
|
||||||
|
{{ end }}
|
||||||
|
<link rel="apple-touch-startup-image" href="/static/logo.svg" />
|
||||||
|
<link rel="shortcut icon" type="image/png" href="/static/logo.svg">
|
||||||
<link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/semantic.min.css">
|
<link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/semantic.min.css">
|
||||||
<link rel="stylesheet" href="https://unpkg.com/[email protected]/assets/font-logos.css">
|
<link rel="stylesheet" href="https://unpkg.com/[email protected]/assets/font-logos.css">
|
||||||
<link rel="stylesheet" href="https://unpkg.com/[email protected]/font/bootstrap-icons.min.css">
|
<link rel="stylesheet" href="https://unpkg.com/[email protected]/font/bootstrap-icons.min.css">
|
||||||
@@ -21,7 +30,7 @@
|
|||||||
<script src="https://unpkg.com/[email protected]/dist/vue.min.js"></script>
|
<script src="https://unpkg.com/[email protected]/dist/vue.min.js"></script>
|
||||||
<script src="https://unpkg.com/[email protected]/dist/echarts.min.js"></script>
|
<script src="https://unpkg.com/[email protected]/dist/echarts.min.js"></script>
|
||||||
<script src="/static/semantic-ui-alerts.min.js"></script>
|
<script src="/static/semantic-ui-alerts.min.js"></script>
|
||||||
<script src="/static/theme-default/js/mixin.js?v20240302"></script>
|
<script src="/static/theme-default/js/mixin.js?v20240911"></script>
|
||||||
<script>
|
<script>
|
||||||
document.documentElement.setAttribute('nz-theme', window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
|
document.documentElement.setAttribute('nz-theme', window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
+1
-1
@@ -194,7 +194,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
mixins: [mixinsVue],
|
mixins: [mixinsVue],
|
||||||
created() {
|
created() {
|
||||||
this.servers = JSON.parse('{{.Servers}}').servers;
|
this.servers = JSON.parse('{{.Servers}}').servers;
|
||||||
this.group()
|
this.group()
|
||||||
},
|
},
|
||||||
|
|||||||
+2
-2
@@ -7,7 +7,7 @@
|
|||||||
</a>
|
</a>
|
||||||
<a class='item' href="/"><i class="home icon"></i>{{tr "Home"}}</a>
|
<a class='item' href="/"><i class="home icon"></i>{{tr "Home"}}</a>
|
||||||
<template v-if="isMobile">
|
<template v-if="isMobile">
|
||||||
<div class="item ui simple dropdown">
|
<div class="item ui dropdown" :class="{ simple: !isMobile }">
|
||||||
<div class="text"><i class="bi bi-gear-wide-connected icon" style="margin-right:3px;"></i>{{tr "Feature" }}<i class="dropdown icon" style="margin-right:0px;"></i></div>
|
<div class="text"><i class="bi bi-gear-wide-connected icon" style="margin-right:3px;"></i>{{tr "Feature" }}<i class="dropdown icon" style="margin-right:0px;"></i></div>
|
||||||
<div class="menu">
|
<div class="menu">
|
||||||
<a href="/service" class="item"><i class="rss icon"></i>{{tr "Services" }}</a>
|
<a href="/service" class="item"><i class="rss icon"></i>{{tr "Services" }}</a>
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
<a href="/network" class="item"><i class="bi bi-hdd-network icon"></i>{{tr "NetworkSpiter"}}</a>
|
<a href="/network" class="item"><i class="bi bi-hdd-network icon"></i>{{tr "NetworkSpiter"}}</a>
|
||||||
</template>
|
</template>
|
||||||
{{ if not .Conf.DisableSwitchTemplateInFrontend }}
|
{{ if not .Conf.DisableSwitchTemplateInFrontend }}
|
||||||
<div class="item ui simple dropdown">
|
<div class="item ui dropdown" :class="{ simple: !isMobile }">
|
||||||
<div class="text"><i class="bi bi-incognito icon" style="margin-right:3px;"></i>{{tr "Template" }}<i class="dropdown icon" style="margin-right:0px;"></i></div>
|
<div class="text"><i class="bi bi-incognito icon" style="margin-right:3px;"></i>{{tr "Template" }}<i class="dropdown icon" style="margin-right:0px;"></i></div>
|
||||||
<div class="menu">
|
<div class="menu">
|
||||||
<a v-for="(item, index) in adaptedTemplates" :key="index" @click="toggleTemplate(item.key)" class="item">
|
<a v-for="(item, index) in adaptedTemplates" :key="index" @click="toggleTemplate(item.key)" class="item">
|
||||||
|
|||||||
+22
-14
@@ -18,7 +18,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="ui container">
|
<div class="ui container">
|
||||||
<div ref="chartDom" style="margin-top: 15px;height: 520px;overflow: hidden"></div>
|
<div ref="chartDom" style="margin-top: 15px;height: auto;overflow: hidden"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -36,7 +36,11 @@
|
|||||||
defaultTemplate: {{.Conf.Site.Theme}},
|
defaultTemplate: {{.Conf.Site.Theme}},
|
||||||
templates: {{.Themes}},
|
templates: {{.Themes}},
|
||||||
servers: initData,
|
servers: initData,
|
||||||
option: {
|
option: {}
|
||||||
|
},
|
||||||
|
mixins: [mixinsVue],
|
||||||
|
created() {
|
||||||
|
this.option = {
|
||||||
tooltip: {
|
tooltip: {
|
||||||
trigger: 'axis',
|
trigger: 'axis',
|
||||||
position: function (pt) {
|
position: function (pt) {
|
||||||
@@ -69,17 +73,9 @@
|
|||||||
right: this.isMobile ? '8%' : '3.8%',
|
right: this.isMobile ? '8%' : '3.8%',
|
||||||
},
|
},
|
||||||
backgroundColor: '',
|
backgroundColor: '',
|
||||||
toolbox: {
|
|
||||||
feature: {
|
|
||||||
dataZoom: {
|
|
||||||
yAxisIndex: 'none'
|
|
||||||
},
|
|
||||||
restore: {},
|
|
||||||
saveAsImage: {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
dataZoom: [
|
dataZoom: [
|
||||||
{
|
{
|
||||||
|
type: 'slider',
|
||||||
start: 0,
|
start: 0,
|
||||||
end: 100
|
end: 100
|
||||||
}
|
}
|
||||||
@@ -93,10 +89,8 @@
|
|||||||
boundaryGap: false
|
boundaryGap: false
|
||||||
},
|
},
|
||||||
series: [],
|
series: [],
|
||||||
},
|
}
|
||||||
chartOnOff: true,
|
|
||||||
},
|
},
|
||||||
mixins: [mixinsVue],
|
|
||||||
mounted() {
|
mounted() {
|
||||||
this.renderChart();
|
this.renderChart();
|
||||||
this.parseMonitorInfo(monitorInfo);
|
this.parseMonitorInfo(monitorInfo);
|
||||||
@@ -236,6 +230,20 @@
|
|||||||
this.option.title.text = monitorInfo.result[0].server_name;
|
this.option.title.text = monitorInfo.result[0].server_name;
|
||||||
this.option.series = tSeries;
|
this.option.series = tSeries;
|
||||||
this.option.legend.data = tLegendData;
|
this.option.legend.data = tLegendData;
|
||||||
|
const maxLegendsPerRowMobile = localStorage.getItem("maxLegendsPerRowMobile") ? localStorage.getItem("maxLegendsPerRowMobile") : 2;
|
||||||
|
const maxLegendsPerRowPc = localStorage.getItem("maxLegendsPerRowPc") ? localStorage.getItem("maxLegendsPerRowPc") : 6;
|
||||||
|
const autoIncrement = Math.floor((tLegendData.length - 1) / (this.isMobile ? maxLegendsPerRowMobile : maxLegendsPerRowPc)) * (this.isMobile ? 28 : 34);
|
||||||
|
const height = 520 + autoIncrement;
|
||||||
|
const gridTop = 60 + autoIncrement;
|
||||||
|
this.option.grid = {
|
||||||
|
left: this.isMobile ? '8%' : '3.8%',
|
||||||
|
right: this.isMobile ? '8%' : '3.8%',
|
||||||
|
top: gridTop
|
||||||
|
};
|
||||||
|
this.myChart.resize({
|
||||||
|
width: 'auto',
|
||||||
|
height: height
|
||||||
|
});
|
||||||
this.myChart.clear();
|
this.myChart.clear();
|
||||||
this.myChart.setOption(this.option);
|
this.myChart.setOption(this.option);
|
||||||
},
|
},
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
{{define "theme-server-status/footer"}}
|
{{define "theme-server-status/footer"}}
|
||||||
</div>
|
</section>
|
||||||
<footer class="container-fluid">
|
<footer class="container-fluid">
|
||||||
<p>{{ .Conf.Site.Brand }} | Theme ServerStatus | Powered by <a target="_blank" href="https://github.com/naiba/nezha">{{tr "NezhaMonitoring"}}</a> {{.Version}}</p>
|
<p>{{ .Conf.Site.Brand }} | Theme ServerStatus | Powered by <a target="_blank" href="https://github.com/naiba/nezha">{{tr "NezhaMonitoring"}}</a> {{.Version}}</p>
|
||||||
</footer>
|
</footer>
|
||||||
|
|||||||
+17
-7
@@ -2,11 +2,21 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="{{.Conf.Language}}">
|
<html lang="{{.Conf.Language}}">
|
||||||
<head>
|
<head>
|
||||||
<title>{{ .Title }}</title>
|
<title>{{.Title}}</title>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||||
|
<meta content="telephone=no" name="format-detection">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<link rel="shortcut icon" type="image/png" href="/static/logo.svg" />
|
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||||
|
<meta name="mobile-web-app-capable" content="yes" />
|
||||||
|
<meta name="theme-color" content="#000000" />
|
||||||
|
{{if eq .Conf.Language "zh-CN"}}
|
||||||
|
<link rel="manifest" href="/static/manifest-zh-CN.json?v20240905" />
|
||||||
|
{{else}}
|
||||||
|
<link rel="manifest" href="/static/manifest-en-US.json?v20240905" />
|
||||||
|
{{end}}
|
||||||
|
<link rel="apple-touch-startup-image" href="/static/logo.svg" />
|
||||||
|
<link rel="shortcut icon" type="image/png" href="/static/logo.svg">
|
||||||
<link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/css/bootstrap.min.css">
|
<link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/css/bootstrap.min.css">
|
||||||
<link rel="stylesheet" href="https://unpkg.com/[email protected]/font/bootstrap-icons.min.css">
|
<link rel="stylesheet" href="https://unpkg.com/[email protected]/font/bootstrap-icons.min.css">
|
||||||
<link rel="stylesheet" href="https://unpkg.com/[email protected]/assets/font-logos.css">
|
<link rel="stylesheet" href="https://unpkg.com/[email protected]/assets/font-logos.css">
|
||||||
@@ -16,10 +26,10 @@
|
|||||||
<script src="https://unpkg.com/[email protected]/dist/js/bootstrap.min.js"></script>
|
<script src="https://unpkg.com/[email protected]/dist/js/bootstrap.min.js"></script>
|
||||||
<script src="https://unpkg.com/[email protected]/dist/vue.min.js"></script>
|
<script src="https://unpkg.com/[email protected]/dist/vue.min.js"></script>
|
||||||
<script src="https://unpkg.com/[email protected]/dist/echarts.min.js"></script>
|
<script src="https://unpkg.com/[email protected]/dist/echarts.min.js"></script>
|
||||||
<link rel="stylesheet" href="/static/theme-server-status/css/main.css?v202408016">
|
<link rel="stylesheet" href="/static/theme-server-status/css/main.css?v20240929">
|
||||||
<link rel="stylesheet" href="/static/theme-server-status/css/dark.css?v202408011">
|
<link rel="stylesheet" href="/static/theme-server-status/css/dark.css?v202409029">
|
||||||
<link rel="stylesheet" href="/static/theme-server-status/css/light.css?v20240811">
|
<link rel="stylesheet" href="/static/theme-server-status/css/light.css?v20240916">
|
||||||
<script src="/static/theme-server-status/js/mixin.js?v20240811"></script>
|
<script src="/static/theme-server-status/js/mixin.js?v20240915"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app">
|
<div id="app">
|
||||||
|
|||||||
+25
-2
@@ -6,6 +6,7 @@
|
|||||||
<th class="node-cell name center">{{tr "Name"}}</th>
|
<th class="node-cell name center">{{tr "Name"}}</th>
|
||||||
<th class="node-cell os center">{{tr "Platform"}}</th>
|
<th class="node-cell os center">{{tr "Platform"}}</th>
|
||||||
<th class="node-cell location center">{{tr "Location"}}</th>
|
<th class="node-cell location center">{{tr "Location"}}</th>
|
||||||
|
<th v-if="nodesNoTag.some(item => item.additional && item.additional.price && Object.keys(item.additional.price).length > 0)" class="node-cell price center">{{tr "Price"}}</th>
|
||||||
<th class="node-cell uptime center">{{tr "Uptime"}}</th>
|
<th class="node-cell uptime center">{{tr "Uptime"}}</th>
|
||||||
<th class="node-cell load center">{{tr "Load"}}</th>
|
<th class="node-cell load center">{{tr "Load"}}</th>
|
||||||
<th class="node-cell network center">{{tr "NetSpeed"}}↓|↑</th>
|
<th class="node-cell network center">{{tr "NetSpeed"}}↓|↑</th>
|
||||||
@@ -13,12 +14,13 @@
|
|||||||
<th class="node-cell cpu center">{{tr "CpuUsed"}}</th>
|
<th class="node-cell cpu center">{{tr "CpuUsed"}}</th>
|
||||||
<th class="node-cell memory center">{{tr "MemUsed"}}</th>
|
<th class="node-cell memory center">{{tr "MemUsed"}}</th>
|
||||||
<th class="node-cell hdd center">{{tr "DiskUsed"}}</th>
|
<th class="node-cell hdd center">{{tr "DiskUsed"}}</th>
|
||||||
|
<th v-if="nodesNoTag.some(item => item.additional && item.additional.remaining && Object.keys(item.additional.remaining).length > 0)" class="node-cell remaining center">{{tr "Remaining"}}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody id="servers">
|
<tbody id="servers">
|
||||||
<template v-for="(node,index) in nodesNoTag">
|
<template v-for="(node,index) in nodesNoTag">
|
||||||
<tr :id="'r'+node.ID" data-toggle="collapse" :data-target="'#rt'+node.ID" class="accordion-toggle" :class="index % 2 === 0 ? 'odd': 'even'"
|
<tr :id="'r'+node.ID" data-toggle="collapse" :data-target="'#rt'+node.ID" class="accordion-toggle" :class="index % 2 === 0 ? 'odd': 'even'"
|
||||||
aria-expanded="false" @click="showCharts($event, node.ID)">
|
aria-expanded="false" @click="showCharts(node.ID)">
|
||||||
<td class="node-cell status center">
|
<td class="node-cell status center">
|
||||||
<div class="status-container">
|
<div class="status-container">
|
||||||
<div v-if="node.online" class="status-icon online"></div>
|
<div v-if="node.online" class="status-icon online"></div>
|
||||||
@@ -36,6 +38,12 @@
|
|||||||
<i :class="'fi fi-' + node.location"></i>
|
<i :class="'fi fi-' + node.location"></i>
|
||||||
<span class="node-cell-location-text text-uppercase">@#node.location#@</span>
|
<span class="node-cell-location-text text-uppercase">@#node.location#@</span>
|
||||||
</td>
|
</td>
|
||||||
|
<td v-if="nodesNoTag.some(item => item.additional && item.additional.price && Object.keys(item.additional.price).length > 0)" class="node-cell price center">
|
||||||
|
<template v-if="node.additional && node.additional.price">
|
||||||
|
<span v-if="node.additional.price.amount == 0" class="node-cell-price-text">Free</span>
|
||||||
|
<span v-else class="node-cell-price-text">@#node.additional.price.amount#@@#(node.additional.price.cycle ? '/' + node.additional.price.cycle : '')#@</span>
|
||||||
|
</template>
|
||||||
|
</td>
|
||||||
<td style="text-align: center;" class="node-cell uptime">@#node.uptime#@</td>
|
<td style="text-align: center;" class="node-cell uptime">@#node.uptime#@</td>
|
||||||
<td style="text-align: center;" class="node-cell load">@#node.load#@</td>
|
<td style="text-align: center;" class="node-cell load">@#node.load#@</td>
|
||||||
<td style="text-align: center;" class="node-cell network">@#node.network#@</td>
|
<td style="text-align: center;" class="node-cell network">@#node.network#@</td>
|
||||||
@@ -59,6 +67,21 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
<td v-if="nodesNoTag.some(item => item.additional && item.additional.remaining && Object.keys(item.additional.remaining).length > 0)" class="node-cell remaining">
|
||||||
|
<div :class="['progress', node.online ? 'progress-online' : 'progress-offline']">
|
||||||
|
<template v-if="node.additional && node.additional.remaining.format">
|
||||||
|
<div :style="node.additional.remaining.format.style" :class="node.additional.remaining.days == 'lifetime' ? '' : node.additional.remaining.format.class"></div>
|
||||||
|
<div class="additional">
|
||||||
|
<small v-if="node.additional.remaining.days == 'lifetime'">{{tr "Lifetime"}}</small>
|
||||||
|
<small v-else-if="node.additional.remaining.days < 0">{{tr "Expired"}}</small>
|
||||||
|
<small v-else>
|
||||||
|
<span class="node-cell-remaining-days">@#node.additional.remaining.days#@{{tr "Days"}}</span>
|
||||||
|
<span class="node-cell-remaining-percent">@#node.additional.remaining.percent#@%</span>
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr class="expandRow" :class="index % 2 === 0 ? 'odd': 'even'">
|
<tr class="expandRow" :class="index % 2 === 0 ? 'odd': 'even'">
|
||||||
<td colspan="16">
|
<td colspan="16">
|
||||||
@@ -143,7 +166,7 @@
|
|||||||
@#node.host.Version#@
|
@#node.host.Version#@
|
||||||
</span>
|
</span>
|
||||||
<span class="node-echarts-expand">
|
<span class="node-echarts-expand">
|
||||||
<div class="chartbox" chartbox-show="0" :key="node.ID" :ref="`chart${node.ID}`" style="width: 100%; height: auto;"></div>
|
<div class="chartbox" :id="`chart-${node.ID}`" chartbox-show="0" :key="node.ID" style="width: 100%; height: auto;"></div>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+25
-2
@@ -9,6 +9,7 @@
|
|||||||
<th class="node-cell name center">{{tr "Name"}}</th>
|
<th class="node-cell name center">{{tr "Name"}}</th>
|
||||||
<th class="node-cell os center">{{tr "Platform"}}</th>
|
<th class="node-cell os center">{{tr "Platform"}}</th>
|
||||||
<th class="node-cell location center">{{tr "Location"}}</th>
|
<th class="node-cell location center">{{tr "Location"}}</th>
|
||||||
|
<th v-if="group.data.some(item => item.additional && item.additional.price && Object.keys(item.additional.price).length > 0)" class="node-cell price center">{{tr "Price"}}</th>
|
||||||
<th class="node-cell uptime center">{{tr "Uptime"}}</th>
|
<th class="node-cell uptime center">{{tr "Uptime"}}</th>
|
||||||
<th class="node-cell load center">{{tr "Load"}}</th>
|
<th class="node-cell load center">{{tr "Load"}}</th>
|
||||||
<th class="node-cell network center">{{tr "NetSpeed"}}↓|↑</th>
|
<th class="node-cell network center">{{tr "NetSpeed"}}↓|↑</th>
|
||||||
@@ -16,12 +17,13 @@
|
|||||||
<th class="node-cell cpu center">{{tr "CpuUsed"}}</th>
|
<th class="node-cell cpu center">{{tr "CpuUsed"}}</th>
|
||||||
<th class="node-cell memory center">{{tr "MemUsed"}}</th>
|
<th class="node-cell memory center">{{tr "MemUsed"}}</th>
|
||||||
<th class="node-cell hdd center">{{tr "DiskUsed"}}</th>
|
<th class="node-cell hdd center">{{tr "DiskUsed"}}</th>
|
||||||
|
<th v-if="group.data.some(item => item.additional && item.additional.remaining && Object.keys(item.additional.remaining).length > 0)" class="node-cell remaining center">{{tr "Remaining"}}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody id="servers">
|
<tbody id="servers">
|
||||||
<template v-for="(node,index) in group.data">
|
<template v-for="(node,index) in group.data">
|
||||||
<tr :id="'r'+node.ID" data-toggle="collapse" :data-target="'#rt'+node.ID" class="accordion-toggle"
|
<tr :id="'r'+node.ID" data-toggle="collapse" :data-target="'#rt'+node.ID" class="accordion-toggle"
|
||||||
:class="index % 2 === 0 ? 'odd': 'even'" aria-expanded="false" @click="showCharts($event, node.ID)">
|
:class="index % 2 === 0 ? 'odd': 'even'" aria-expanded="false" @click="showCharts(node.ID)">
|
||||||
<td class="node-cell status center">
|
<td class="node-cell status center">
|
||||||
<div class="status-container">
|
<div class="status-container">
|
||||||
<div v-if="node.online" class="status-icon online"></div>
|
<div v-if="node.online" class="status-icon online"></div>
|
||||||
@@ -39,6 +41,12 @@
|
|||||||
<i :class="'fi fi-' + node.location"></i>
|
<i :class="'fi fi-' + node.location"></i>
|
||||||
<span class="node-cell-location-text text-uppercase"> @#node.location#@</span>
|
<span class="node-cell-location-text text-uppercase"> @#node.location#@</span>
|
||||||
</td>
|
</td>
|
||||||
|
<td v-if="group.data.some(item => item.additional && item.additional.price && Object.keys(item.additional.price).length > 0)" class="node-cell price center">
|
||||||
|
<template v-if="node.additional && node.additional.price">
|
||||||
|
<span v-if="node.additional.price.amount == 0" class="node-cell-price-text">Free</span>
|
||||||
|
<span v-else class="node-cell-price-text">@#node.additional.price.amount#@@#(node.additional.price.cycle ? '/' + node.additional.price.cycle : '')#@</span>
|
||||||
|
</template>
|
||||||
|
</td>
|
||||||
<td style="text-align: center;" class="node-cell uptime">@#node.uptime#@</td>
|
<td style="text-align: center;" class="node-cell uptime">@#node.uptime#@</td>
|
||||||
<td style="text-align: center;" class="node-cell load">@#node.load#@</td>
|
<td style="text-align: center;" class="node-cell load">@#node.load#@</td>
|
||||||
<td style="text-align: center;" class="node-cell network">@#node.network#@</td>
|
<td style="text-align: center;" class="node-cell network">@#node.network#@</td>
|
||||||
@@ -62,6 +70,21 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
<td v-if="group.data.some(item => item.additional && item.additional.remaining && Object.keys(item.additional.remaining).length > 0)" class="node-cell remaining">
|
||||||
|
<div :class="['progress', node.online ? 'progress-online' : 'progress-offline']">
|
||||||
|
<template v-if="node.additional && node.additional.remaining.format">
|
||||||
|
<div :style="node.additional.remaining.format.style" :class="node.additional.remaining.days == 'lifetime' ? '' : node.additional.remaining.format.class"></div>
|
||||||
|
<div class="additional">
|
||||||
|
<small v-if="node.additional.remaining.days == 'lifetime'">{{tr "Lifetime"}}</small>
|
||||||
|
<small v-else-if="node.additional.remaining.days < 0">{{tr "Expired"}}</small>
|
||||||
|
<small v-else>
|
||||||
|
<span class="node-cell-remaining-days">@#node.additional.remaining.days#@{{tr "Days"}}</span>
|
||||||
|
<span class="node-cell-remaining-percent">@#node.additional.remaining.percent#@%</span>
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr class="expandRow" :class="index % 2 === 0 ? 'odd': 'even'">
|
<tr class="expandRow" :class="index % 2 === 0 ? 'odd': 'even'">
|
||||||
<td colspan="16">
|
<td colspan="16">
|
||||||
@@ -146,7 +169,7 @@
|
|||||||
@#node.host.Version#@
|
@#node.host.Version#@
|
||||||
</span>
|
</span>
|
||||||
<span class="node-echarts-expand">
|
<span class="node-echarts-expand">
|
||||||
<div class="chartbox" chartbox-show="0" :key="node.ID" :ref="`chart${node.ID}`" style="width: 100%; height: auto;"></div>
|
<div class="chartbox" :id="`chart-${node.ID}`" chartbox-show="0" :key="node.ID" style="width: 100%; height: auto;"></div>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+583
-94
@@ -3,15 +3,15 @@
|
|||||||
{{template "theme-server-status/menu" .}}
|
{{template "theme-server-status/menu" .}}
|
||||||
<!-- showGroup true -->
|
<!-- showGroup true -->
|
||||||
<template v-if="showGroup">
|
<template v-if="showGroup">
|
||||||
<section class="container-fluid table-responsive content" v-for="group in nodesTag">
|
<div class="container-fluid table-responsive content" v-for="group in nodesTag">
|
||||||
{{template "theme-server-status/home-group-true" .}}
|
{{template "theme-server-status/home-group-true" .}}
|
||||||
</section>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<!-- showGroup false -->
|
<!-- showGroup false -->
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<section class="container-fluid table-responsive content">
|
<div class="container-fluid table-responsive content">
|
||||||
{{template "theme-server-status/home-group-false" .}}
|
{{template "theme-server-status/home-group-false" .}}
|
||||||
</section>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div class="modal fade" id="mapChartBox" tabindex="-1" role="dialog" aria-labelledby="mapChartTitle" aria-hidden="true" style="padding-left:0">
|
<div class="modal fade" id="mapChartBox" tabindex="-1" role="dialog" aria-labelledby="mapChartTitle" aria-hidden="true" style="padding-left:0">
|
||||||
<div class="modal-dialog modal-lg modal-dialog-centered">
|
<div class="modal-dialog modal-lg modal-dialog-centered">
|
||||||
@@ -42,6 +42,7 @@
|
|||||||
servers: [],
|
servers: [],
|
||||||
nodesTag: [],
|
nodesTag: [],
|
||||||
nodesNoTag: [],
|
nodesNoTag: [],
|
||||||
|
additional: {},
|
||||||
chartDataList: [],
|
chartDataList: [],
|
||||||
ws: null,
|
ws: null,
|
||||||
language: {{.Conf.Language}},
|
language: {{.Conf.Language}},
|
||||||
@@ -71,17 +72,32 @@
|
|||||||
mixins: [mixinsVue],
|
mixins: [mixinsVue],
|
||||||
created() {
|
created() {
|
||||||
this.servers = JSON.parse('{{.Servers}}').servers;
|
this.servers = JSON.parse('{{.Servers}}').servers;
|
||||||
|
this.additional = this.initAdditional(this.servers);
|
||||||
if(this.showGroup) {
|
if(this.showGroup) {
|
||||||
this.nodesTag = this.groupingData(this.handleNodes(this.servers),"Tag");
|
this.nodesTag = this.groupingData(this.handleNodes(this.servers),"Tag");
|
||||||
|
this.nodesTag.forEach(group => {
|
||||||
|
group.data.forEach(item => {
|
||||||
|
const additionalData = this.additional[item.ID];
|
||||||
|
if (additionalData) {
|
||||||
|
item.additional = additionalData; // 添加 additional 属性
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
this.nodesNoTag = this.handleNodes(this.servers);
|
this.nodesNoTag = this.handleNodes(this.servers);
|
||||||
|
this.nodesNoTag.forEach(item => {
|
||||||
|
const additionalData = this.additional[item.ID];
|
||||||
|
if (additionalData) {
|
||||||
|
item.additional = additionalData; // 添加 additional 属性
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
this.initCountryMap().then(countryMap => {
|
this.initCountryMap().then(countryMap => {
|
||||||
this.countryMap = countryMap;
|
this.countryMap = countryMap;
|
||||||
this.countryNameMap = this.initCountryNameMap();
|
this.countryNameMap = this.initCountryNameMap();
|
||||||
this.countryServer = this.initCountryServer();
|
this.countryServer = this.initCountryServer();
|
||||||
this.countryMapChartData = this.initCountryMapChartData();
|
this.countryMapChartData = this.initCountryMapChartData();
|
||||||
})
|
});
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
// 初始化时建立WebSocket连接
|
// 初始化时建立WebSocket连接
|
||||||
@@ -98,6 +114,49 @@
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
initAdditional(servers) {
|
||||||
|
let nodes = {};
|
||||||
|
servers?.forEach(server => {
|
||||||
|
if (server.PublicNote) {
|
||||||
|
const remainingFormat = this.getRemainingFormat(server.live, server.PublicNote);
|
||||||
|
const remainingDays = this.getRemainingDays(this.getNoteElementValue(server.PublicNote, "billingDataMod", "endDate"), server.PublicNote);
|
||||||
|
const remainingPercent = this.getRemainingPercent(
|
||||||
|
this.getNoteElementValue(server.PublicNote, "billingDataMod", "startDate"),
|
||||||
|
this.getNoteElementValue(server.PublicNote, "billingDataMod", "endDate"),
|
||||||
|
server.PublicNote
|
||||||
|
);
|
||||||
|
const priceAmount = this.getNoteElementValue(server.PublicNote, "billingDataMod", "amount");
|
||||||
|
const priceCycle = this.getNoteElementValue(server.PublicNote, "billingDataMod", "cycle");
|
||||||
|
|
||||||
|
// 初始化节点
|
||||||
|
nodes[server.ID] = {
|
||||||
|
"remaining": {},
|
||||||
|
"price": {}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (remainingFormat) {
|
||||||
|
nodes[server.ID].remaining.format = remainingFormat;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (remainingDays) {
|
||||||
|
nodes[server.ID].remaining.days = remainingDays;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (remainingPercent) {
|
||||||
|
nodes[server.ID].remaining.percent = this.toFixed2(100 - remainingPercent);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (priceAmount) {
|
||||||
|
nodes[server.ID].price.amount = priceAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (priceCycle && priceAmount) {
|
||||||
|
nodes[server.ID].price.cycle = priceCycle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return nodes;
|
||||||
|
},
|
||||||
initCountryMap() {
|
initCountryMap() {
|
||||||
return fetch(this.staticUrl + '/maps/nezha.countrymap.json')
|
return fetch(this.staticUrl + '/maps/nezha.countrymap.json')
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
@@ -163,6 +222,9 @@
|
|||||||
const tooltipBorderColor = this.theme == "dark" ? "#ffffff" : "#ffffff";
|
const tooltipBorderColor = this.theme == "dark" ? "#ffffff" : "#ffffff";
|
||||||
const fontSize = this.isMobile ? 10 : 12;
|
const fontSize = this.isMobile ? 10 : 12;
|
||||||
const fontColor = this.theme == "dark" ? "#000000" : "#000000";
|
const fontColor = this.theme == "dark" ? "#000000" : "#000000";
|
||||||
|
const showLoadingMaskColor = this.theme == "dark" ? 'rgba(28, 29, 38, 1)' : 'rgba(249, 249, 249, 1)';
|
||||||
|
const showLoadingTextColor = this.theme == "dark" ? 'rgba(241, 241, 241, 1)' : 'rgba(0, 0, 0, 1)';
|
||||||
|
const showLoadingColor = inRangeColor;
|
||||||
const chartContainer = document.getElementById('mapChart');
|
const chartContainer = document.getElementById('mapChart');
|
||||||
const mapChart = echarts.init(chartContainer, '', { // init图表
|
const mapChart = echarts.init(chartContainer, '', { // init图表
|
||||||
renderer: 'canvas',
|
renderer: 'canvas',
|
||||||
@@ -170,6 +232,13 @@
|
|||||||
width: width,
|
width: width,
|
||||||
height: height,
|
height: height,
|
||||||
});
|
});
|
||||||
|
mapChart.showLoading({
|
||||||
|
text: 'loading',
|
||||||
|
textColor: showLoadingTextColor,
|
||||||
|
color: showLoadingColor,
|
||||||
|
maskColor: showLoadingMaskColor,
|
||||||
|
zlevel: 2
|
||||||
|
});
|
||||||
fetch(this.staticUrl + '/maps/' + this.countryMapGeoFile)
|
fetch(this.staticUrl + '/maps/' + this.countryMapGeoFile)
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(worldMap => {
|
.then(worldMap => {
|
||||||
@@ -224,7 +293,10 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
mapChart.setOption(option);
|
setTimeout(() => {
|
||||||
|
mapChart.hideLoading();
|
||||||
|
mapChart.setOption(option);
|
||||||
|
}, 1000);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
isWindowsPlatform(str) {
|
isWindowsPlatform(str) {
|
||||||
@@ -382,8 +454,22 @@
|
|||||||
}
|
}
|
||||||
if(this.showGroup) {
|
if(this.showGroup) {
|
||||||
this.nodesTag = this.groupingData(this.handleNodes(data.servers),"Tag");
|
this.nodesTag = this.groupingData(this.handleNodes(data.servers),"Tag");
|
||||||
|
this.nodesTag.forEach(group => {
|
||||||
|
group.data.forEach(item => {
|
||||||
|
const additionalData = this.additional[item.ID];
|
||||||
|
if (additionalData) {
|
||||||
|
item.additional = additionalData; // 添加 additional 属性
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
this.nodesNoTag = this.handleNodes(data.servers);
|
this.nodesNoTag = this.handleNodes(data.servers);
|
||||||
|
this.nodesNoTag.forEach(item => {
|
||||||
|
const additionalData = this.additional[item.ID];
|
||||||
|
if (additionalData) {
|
||||||
|
item.additional = additionalData; // 添加 additional 属性
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.ws.onclose = () => {
|
this.ws.onclose = () => {
|
||||||
@@ -470,98 +556,141 @@
|
|||||||
// 如果所有元素的 Temperature 都为 0,则返回一个默认值 0
|
// 如果所有元素的 Temperature 都为 0,则返回一个默认值 0
|
||||||
return 0;
|
return 0;
|
||||||
},
|
},
|
||||||
showCharts(event, id) {
|
showCharts(id,changeChartboxShow=true) {
|
||||||
const chartContainer = this.$refs[`chart${id}`][0];
|
const chartContainer = document.getElementById(`chart-${id}`);
|
||||||
const chartboxShow = chartContainer.getAttribute('chartbox-show');
|
if(changeChartboxShow){
|
||||||
chartContainer.setAttribute('chartbox-show', chartboxShow === '0' ? '1' : '0');
|
const chartboxShow = chartContainer.getAttribute('chartbox-show');
|
||||||
const isAriaExpandedFalse = event.currentTarget.getAttribute('aria-expanded') === 'false';
|
chartContainer.setAttribute('chartbox-show', chartboxShow === '0' ? '1' : '0');
|
||||||
if (!isAriaExpandedFalse) return;
|
const collapseContainer = document.getElementById(`r${id}`);
|
||||||
|
const isAriaExpandedFalse = collapseContainer.getAttribute('aria-expanded') === 'false';
|
||||||
|
if (!isAriaExpandedFalse) return;
|
||||||
|
}
|
||||||
// 发起数据请求
|
// 发起数据请求
|
||||||
const url = `/api/v1/monitor/${id}`;
|
const url = `/api/v1/monitor/${id}`;
|
||||||
fetch(url)
|
fetch(url)
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
if (data.result) { // 数据请求成功,更新数据并渲染图表
|
if (data.result) { // 数据请求成功,更新数据并渲染图表
|
||||||
this.chartDataList[id - 1] = data.result;
|
this.chartDataList[id] = data.result;
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
this.renderCharts(id);
|
this.renderCharts(id);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
console.log('this agent (id:'+ id + ') has no monitor.');
|
console.log('this server (id:'+ id + ') has no monitor.');
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
console.error('Error fetching data:', error);
|
console.log('Error fetching data:', error);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
renderCharts(id, reload = false) {
|
renderCharts(id, reload = false) {
|
||||||
if (!this.chartDataList[id - 1]) return;
|
if (!this.chartDataList[id]) return;
|
||||||
const chartData = this.chartDataList[id - 1];
|
const chartData = this.chartDataList[id];
|
||||||
const chartContainer = this.$refs[`chart${id}`][0];
|
const chartContainer = document.getElementById(`chart-${id}`);
|
||||||
if (reload) { //点击切换亮色/暗色风格模式时,重新载入echarts图表的逻辑,
|
if (reload) {
|
||||||
// 第一步,查找已经渲染出的图表容器,并销毁它
|
|
||||||
const existingChart = echarts.getInstanceByDom(chartContainer);
|
const existingChart = echarts.getInstanceByDom(chartContainer);
|
||||||
if (existingChart) existingChart.dispose();
|
if (existingChart) existingChart.dispose();
|
||||||
// 第二步,如果图表容器处于不可见状态chartboxShow=0,不重新渲染出新的图表,
|
|
||||||
// 如果图表容器处于可见状态chartboxShow=1,重新渲染出新的图表
|
|
||||||
const chartboxShow = chartContainer.getAttribute('chartbox-show');
|
const chartboxShow = chartContainer.getAttribute('chartbox-show');
|
||||||
if ( chartboxShow === '0' ) return;
|
if ( chartboxShow === '0' ) return;
|
||||||
}
|
}
|
||||||
// 定义图表参数值
|
|
||||||
const MaxTCPPingValue = {{.Conf.MaxTCPPingValue}} ? {{.Conf.MaxTCPPingValue}} : 300;
|
const MaxTCPPingValue = {{.Conf.MaxTCPPingValue}} ? {{.Conf.MaxTCPPingValue}} : 300;
|
||||||
const fontSize = this.isMobile ? 10 : 14;
|
const fontSize = this.isMobile ? 10 : 14;
|
||||||
const gridLeft = this.isMobile ? 25 : 36;
|
const gridLeft = (MaxTCPPingValue > 500) ? (this.isMobile ? 36 : 42) : (this.isMobile ? 25 : 36);
|
||||||
const gridRight = this.isMobile ? 5 : 20;
|
const gridRight = this.isMobile ? 5 : 20;
|
||||||
const legendLeft = this.isMobile ? 'center' : 'center';
|
const legendLeft = this.isMobile ? 'center' : 'center';
|
||||||
const legendTop = this.isMobile ? 5 : 5;
|
const legendTop = this.isMobile ? 5 : 5;
|
||||||
const legendPadding= this.isMobile ? [5,0,5,0] : [5,0,5,0];
|
const legendPadding= this.isMobile ? [5,0,5,0] : [5,0,5,0];
|
||||||
const systemDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
const systemDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
||||||
const theme = localStorage.getItem("theme") ? localStorage.getItem("theme") : systemDarkMode;
|
const theme = localStorage.getItem("theme") ? localStorage.getItem("theme") : systemDarkMode;
|
||||||
const chartTheme = theme == "dark" ? "dark" : "default";
|
const chartTheme = this.theme == "dark" ? "dark" : "default";
|
||||||
const fontColor = theme == "dark" ? "#f1f1f1" : "#000000";
|
const fontColor = this.theme == "dark" ? "#f1f1f1" : "#000000";
|
||||||
const backgroundColor = theme == "dark" ? '' : '';
|
const backgroundColor = theme == "dark" ? '' : '';
|
||||||
const tooltipBackgroundColor = this.theme == "dark" ? (this.semiTransparent ? "rgba(28,29,38,0.85)" : "rgba(28,29,38,1)") : (this.semiTransparent ? "rgba(255,255,255,0.85)" : "rgba(255,255,255,1)");
|
const tooltipBackgroundColor = this.theme == "dark" ? (this.semiTransparent ? "rgba(28,29,38,0.85)" : "rgba(28,29,38,1)") : (this.semiTransparent ? "rgba(255,255,255,0.85)" : "rgba(255,255,255,1)");
|
||||||
const tooltipBorderColor = this.theme == "dark" ? (this.semiTransparent ? "rgba(28,29,38,0.9)" : "rgba(28,29,38,1)") : (this.semiTransparent ? "rgba(255,255,255,0.9)" : "rgba(255,255,255,1)");
|
const tooltipBorderColor = this.theme == "dark" ? (this.semiTransparent ? "rgba(28,29,38,0.9)" : "rgba(28,29,38,1)") : (this.semiTransparent ? "rgba(255,255,255,0.9)" : "rgba(255,255,255,1)");
|
||||||
const lineStyleWidth = this.isMobile ? 1 : 2;
|
const lineStyleWidth = this.isMobile ? 1 : 2;
|
||||||
const splitLineWidth = this.isMobile ? 0.5 : 1;
|
const splitLineWidth = this.isMobile ? 0.5 : 1;
|
||||||
// 渲染图表
|
const markLineItemStyleOpacity = this.semiTransparent ? 1 : 0.75;
|
||||||
const chart = echarts.init(chartContainer, chartTheme, {
|
const markLineLineStyleWidth = this.isMobile ? 0.15 : 0.3;
|
||||||
|
const chart = echarts.init(chartContainer, chartTheme, { // init图表
|
||||||
renderer: 'canvas',
|
renderer: 'canvas',
|
||||||
useDirtyRect: false,
|
useDirtyRect: false,
|
||||||
width: 'auto',
|
width: 'auto',
|
||||||
height: 300,
|
height: 300
|
||||||
});
|
});
|
||||||
const xAxisData = chartData[0].created_at.map(time => new Date(time).toLocaleString());
|
let legendData = [];
|
||||||
const seriesData = chartData.map(item => {
|
let seriesData = [];
|
||||||
|
chartData.forEach((item,key)=> {
|
||||||
let loss = 0;
|
let loss = 0;
|
||||||
const data = item.avg_delay.map((avgDelay, index) => {
|
let totalLossRate = 0;
|
||||||
if(avgDelay > 0 && avgDelay < MaxTCPPingValue){
|
let legendName = '';
|
||||||
loss += avgDelay > 0.9 * MaxTCPPingValue ? 1 : 0;
|
let data = { main: [], markLine: []};
|
||||||
return [item.created_at[index], avgDelay.toFixed(2)];
|
item.avg_delay.forEach((avgDelay, index) => {
|
||||||
}else{
|
const threshold = 0.9 * MaxTCPPingValue; // 定义阀值,用于判断是否丢包
|
||||||
|
// 定义丢包 1. avgDelay==0 2. avgDelay>=MaxTCPPingValue 3. avgDelay>=threshold
|
||||||
|
if(avgDelay == 0 || avgDelay >= MaxTCPPingValue){ //绝对丢包
|
||||||
loss += 1;
|
loss += 1;
|
||||||
|
const lossrate = 100 * loss / (index + 1);
|
||||||
|
if(lossrate != 100) {
|
||||||
|
data['markLine'].push({
|
||||||
|
xAxis: item.created_at[index],
|
||||||
|
label: { show: false },
|
||||||
|
emphasis: { disabled: true },
|
||||||
|
lineStyle: { type: "solid" }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if (avgDelay >= threshold && avgDelay < MaxTCPPingValue){ // 相对丢包
|
||||||
|
loss += 1;
|
||||||
|
const lossrate = 100 * loss / (index + 1);
|
||||||
|
if(lossrate != 100) {
|
||||||
|
data['main'].push(
|
||||||
|
[item.created_at[index], avgDelay, lossrate]
|
||||||
|
);
|
||||||
|
data['markLine'].push({
|
||||||
|
xAxis: item.created_at[index],
|
||||||
|
label: { show: false },
|
||||||
|
emphasis: { disabled: true },
|
||||||
|
lineStyle: { type: "solid" }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else { // 未丢包
|
||||||
|
const lossrate = 100 * loss / (index + 1);
|
||||||
|
data['main'].push(
|
||||||
|
[item.created_at[index], avgDelay, lossrate]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const lossRate = ((loss / item.created_at.length) * 100).toFixed(1);
|
totalLossRate = ((loss / item.created_at.length) * 100).toFixed(1);
|
||||||
item.monitor_name = item.monitor_name.includes("%") ? item.monitor_name : `${item.monitor_name} ${lossRate}%`;
|
legendName = `${item.monitor_name} ${totalLossRate}%`;
|
||||||
return {
|
legendData.push(legendName);
|
||||||
name: item.monitor_name,
|
seriesData.push(
|
||||||
type: 'line',
|
{
|
||||||
smooth: true,
|
name: legendName,
|
||||||
symbol: 'none',
|
type: 'line',
|
||||||
data: data,
|
smooth: true,
|
||||||
connectNulls: true,
|
symbol: 'none',
|
||||||
legendHoverLink: false,
|
connectNulls: true,
|
||||||
emphasis: {
|
legendHoverLink: false,
|
||||||
disabled: true
|
emphasis: {
|
||||||
},
|
disabled: true
|
||||||
lineStyle: {
|
},
|
||||||
width: lineStyleWidth
|
lineStyle: {
|
||||||
|
width: lineStyleWidth
|
||||||
|
},
|
||||||
|
data: data['main'],
|
||||||
|
markLine: {
|
||||||
|
symbol: "none",
|
||||||
|
symbolSize :0,
|
||||||
|
data: data['markLine'],
|
||||||
|
itemStyle: {
|
||||||
|
opacity: markLineItemStyleOpacity
|
||||||
|
},
|
||||||
|
lineStyle:{
|
||||||
|
width: markLineLineStyleWidth
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const legendData = chartData.map(item => item.monitor_name);
|
|
||||||
const maxLegendsPerRowMobile = localStorage.getItem("maxLegendsPerRowMobile") ? localStorage.getItem("maxLegendsPerRowMobile") : 3;
|
const maxLegendsPerRowMobile = localStorage.getItem("maxLegendsPerRowMobile") ? localStorage.getItem("maxLegendsPerRowMobile") : 3;
|
||||||
const maxLegendsPerRowPc = localStorage.getItem("maxLegendsPerRowPc") ? localStorage.getItem("maxLegendsPerRowPc") : 6;
|
const maxLegendsPerRowPc = localStorage.getItem("maxLegendsPerRowPc") ? localStorage.getItem("maxLegendsPerRowPc") : 6;
|
||||||
const autoIncrement = Math.floor((legendData.length - 1) / (this.isMobile ? maxLegendsPerRowMobile : maxLegendsPerRowPc)) * (this.isMobile ? 20 : 28);
|
const autoIncrement = Math.floor((legendData.length - 1) / (this.isMobile ? maxLegendsPerRowMobile : maxLegendsPerRowPc)) * (this.isMobile ? 20 : 28);
|
||||||
@@ -574,39 +703,24 @@
|
|||||||
width: 'auto',
|
width: 'auto',
|
||||||
height: height
|
height: height
|
||||||
});
|
});
|
||||||
|
|
||||||
const option = {
|
const option = {
|
||||||
|
color: this.colors,
|
||||||
backgroundColor: backgroundColor,
|
backgroundColor: backgroundColor,
|
||||||
|
textStyle: {
|
||||||
|
fontSize: fontSize,
|
||||||
|
color: fontColor
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
top: gridTop,
|
||||||
|
left: gridLeft,
|
||||||
|
right: gridRight,
|
||||||
|
},
|
||||||
title: {
|
title: {
|
||||||
show: false
|
show: false,
|
||||||
},
|
|
||||||
tooltip: {
|
|
||||||
trigger: 'axis',
|
|
||||||
backgroundColor: tooltipBackgroundColor,
|
|
||||||
borderColor: tooltipBorderColor,
|
|
||||||
textStyle: {
|
|
||||||
fontSize: fontSize,
|
|
||||||
color: fontColor
|
|
||||||
}
|
|
||||||
},
|
|
||||||
legend: {
|
|
||||||
data: legendData,
|
|
||||||
show: true,
|
|
||||||
icon: legendIcon,
|
|
||||||
textStyle: {
|
|
||||||
fontSize: fontSize,
|
|
||||||
color: fontColor
|
|
||||||
},
|
|
||||||
top: legendTop,
|
|
||||||
bottom: 0,
|
|
||||||
left: legendLeft,
|
|
||||||
padding: legendPadding,
|
|
||||||
itemWidth: itemWidth,
|
|
||||||
itemHeight: itemHeight,
|
|
||||||
},
|
},
|
||||||
|
series: seriesData.flat(),
|
||||||
xAxis: {
|
xAxis: {
|
||||||
type: 'time',
|
type: 'time',
|
||||||
data: xAxisData,
|
|
||||||
axisLabel: {
|
axisLabel: {
|
||||||
textStyle: {
|
textStyle: {
|
||||||
fontSize: fontSize
|
fontSize: fontSize
|
||||||
@@ -626,34 +740,409 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
legend: {
|
||||||
|
data: legendData,
|
||||||
|
show: true,
|
||||||
|
icon: legendIcon,
|
||||||
|
textStyle: {
|
||||||
|
fontSize: fontSize,
|
||||||
|
color: fontColor
|
||||||
|
},
|
||||||
|
top: legendTop,
|
||||||
|
bottom: 0,
|
||||||
|
left: legendLeft,
|
||||||
|
padding: legendPadding,
|
||||||
|
itemWidth: itemWidth,
|
||||||
|
itemHeight: itemHeight,
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
backgroundColor: tooltipBackgroundColor,
|
||||||
|
borderColor: tooltipBorderColor,
|
||||||
|
textStyle: {
|
||||||
|
fontSize: fontSize,
|
||||||
|
color: fontColor
|
||||||
|
},
|
||||||
|
formatter: function (params) {
|
||||||
|
let tooltipContent = '';
|
||||||
|
const formattedTime = new Date(params[0].value[0]).toLocaleString();
|
||||||
|
tooltipContent += `<span style="line-height:2em">${formattedTime}</span><br>`;
|
||||||
|
params.forEach(param => {
|
||||||
|
const formattedTime = new Date(param.value[0]).toLocaleString();
|
||||||
|
if (!param.seriesName.includes('stack')) {
|
||||||
|
const name = param.seriesName.replace(/\s\d+(\.\d+)?%$/, '');
|
||||||
|
tooltipContent += `<span style="line-height:2em">${param.marker} ${name} ${param.value[2].toFixed(1)}% ${param.value[1].toFixed(2)}</span><br>`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return tooltipContent;
|
||||||
|
}
|
||||||
|
},
|
||||||
dataZoom: [
|
dataZoom: [
|
||||||
{
|
{
|
||||||
type: 'slider',
|
type: 'slider',
|
||||||
start: 0,
|
start: 0,
|
||||||
end: 100
|
end: 100
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
series: seriesData,
|
|
||||||
textStyle: {
|
|
||||||
fontSize: fontSize,
|
|
||||||
color: fontColor
|
|
||||||
},
|
|
||||||
grid: {
|
|
||||||
top: gridTop,
|
|
||||||
left: gridLeft,
|
|
||||||
right: gridRight
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
chart.setOption(option);
|
chart.setOption(option);
|
||||||
},
|
},
|
||||||
reloadCharts() { // 重新加载所有图表
|
reloadCharts() {
|
||||||
this.servers.forEach(node => {
|
this.servers.forEach(node => {
|
||||||
const id = node.ID;
|
const id = node.ID;
|
||||||
const chartData = this.chartDataList[id - 1];
|
const chartData = this.chartDataList[id];
|
||||||
if (chartData) {
|
if (chartData) {
|
||||||
this.renderCharts(id,true);
|
this.renderCharts(id,true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
getNoteElementValue(string, elementName, childElementName) {
|
||||||
|
let obj;
|
||||||
|
|
||||||
|
try {
|
||||||
|
obj = JSON.parse(string);
|
||||||
|
} catch (e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!elementName) return null;
|
||||||
|
|
||||||
|
if (childElementName) {
|
||||||
|
return obj[elementName] && childElementName in obj[elementName] ? obj[elementName][childElementName] : null;
|
||||||
|
} else {
|
||||||
|
return elementName in obj ? obj[elementName] : null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getBillingCycle(billingCycle) {
|
||||||
|
// 统一转换为小写进行比较
|
||||||
|
const cycle = billingCycle.toLowerCase();
|
||||||
|
|
||||||
|
switch (cycle) {
|
||||||
|
case '月':
|
||||||
|
case 'month':
|
||||||
|
case 'monthly':
|
||||||
|
case 'm':
|
||||||
|
return "M";
|
||||||
|
case '季':
|
||||||
|
case 'quarterly':
|
||||||
|
case 'q':
|
||||||
|
return "Q";
|
||||||
|
case '半':
|
||||||
|
case '半年':
|
||||||
|
case 'half':
|
||||||
|
case 'semi-annually':
|
||||||
|
case 'h':
|
||||||
|
return "H";
|
||||||
|
case '年':
|
||||||
|
case 'year':
|
||||||
|
case 'annually':
|
||||||
|
case 'y':
|
||||||
|
return "Y";
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getAdjustTimezone(reference, target) {
|
||||||
|
// 获取时区
|
||||||
|
const referenceTimezoneOffset = reference.getTimezoneOffset();
|
||||||
|
const targetTimezoneOffset = target.getTimezoneOffset();
|
||||||
|
|
||||||
|
// 计算时区差异
|
||||||
|
const timezoneDifference = (referenceTimezoneOffset - targetTimezoneOffset) * 60 * 1000;
|
||||||
|
|
||||||
|
// 将 target 日期调整到 reference 时区
|
||||||
|
return new Date(target.getTime() + timezoneDifference);
|
||||||
|
},
|
||||||
|
getAutoRenewalEndDate(endDate, billingCycle) {
|
||||||
|
const expiration = new Date(endDate);
|
||||||
|
const current = this.getAdjustTimezone(new Date(endDate), new Date());
|
||||||
|
|
||||||
|
// 如果 expiration 无效,返回 null 并记录日志
|
||||||
|
if (isNaN(expiration.getTime())) {
|
||||||
|
console.log("getAutoRenewalEndDate: Invalid expiration format");
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = {
|
||||||
|
flag: 1, // 1表示需要更新
|
||||||
|
check: 0, // 判断逻辑标记
|
||||||
|
count: 0 // 周期累计计数
|
||||||
|
};
|
||||||
|
|
||||||
|
// 如果当前时间还没到到期时间,直接返回到期时间
|
||||||
|
if (current < expiration) {
|
||||||
|
result.flag = 0;
|
||||||
|
result.date = expiration;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
let nextExpiration = new Date(expiration); // 初始化为原到期时间
|
||||||
|
let newExpirationMonth = expiration.getMonth(); // 获取expiration初始月份
|
||||||
|
|
||||||
|
switch (billingCycle.toUpperCase()) {
|
||||||
|
case 'M': // 月度
|
||||||
|
nextExpiration.setFullYear(current.getFullYear());
|
||||||
|
|
||||||
|
const monthCheck = current < new Date(current.getFullYear(), current.getMonth(), expiration.getDate());
|
||||||
|
|
||||||
|
// 检查当前月是否在有效期内
|
||||||
|
if (monthCheck) {
|
||||||
|
nextExpiration.setMonth(current.getMonth());
|
||||||
|
result.check = 1;
|
||||||
|
} else {
|
||||||
|
nextExpiration.setMonth(current.getMonth() + 1);
|
||||||
|
result.check = 2;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'Q': // 季度
|
||||||
|
nextExpiration.setFullYear(current.getFullYear());
|
||||||
|
|
||||||
|
// 每次增加 3 个月,直到新的月份大于当前月份
|
||||||
|
while (newExpirationMonth < current.getMonth()) {
|
||||||
|
newExpirationMonth += 3;
|
||||||
|
result.count += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// nextExpiration设置获取到的新月份
|
||||||
|
nextExpiration.setMonth(newExpirationMonth);
|
||||||
|
|
||||||
|
const quarterlyCheck = current < nextExpiration;
|
||||||
|
|
||||||
|
// 检查新月份是否在有效期内
|
||||||
|
if (quarterlyCheck) {
|
||||||
|
result.check = 3;
|
||||||
|
} else {
|
||||||
|
nextExpiration.setMonth(current.getMonth() + 3);
|
||||||
|
result.check = 4;
|
||||||
|
result.count += 1;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'H': // 半年
|
||||||
|
nextExpiration.setFullYear(current.getFullYear());
|
||||||
|
|
||||||
|
// 每次增加 6 个月,直到新的月份大于当前月份
|
||||||
|
while (newExpirationMonth < current.getMonth()) {
|
||||||
|
newExpirationMonth += 6;
|
||||||
|
result.count += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// nextExpiration设置获取到的新月份
|
||||||
|
nextExpiration.setMonth(newExpirationMonth);
|
||||||
|
|
||||||
|
const halfCheck = current < nextExpiration;
|
||||||
|
|
||||||
|
// 检查新月份是否在有效期内
|
||||||
|
if (halfCheck) {
|
||||||
|
result.check = 5;
|
||||||
|
} else {
|
||||||
|
nextExpiration.setMonth(current.getMonth() + 6);
|
||||||
|
result.check = 6;
|
||||||
|
result.count += 1;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'Y': // 年度
|
||||||
|
const yearCheck = current < new Date(current.getFullYear(), expiration.getMonth(), expiration.getDate());
|
||||||
|
|
||||||
|
// 如果当前时间比这一年有效期早,则到期为本年
|
||||||
|
if (yearCheck) {
|
||||||
|
nextExpiration.setFullYear(current.getFullYear());
|
||||||
|
result.check = 7;
|
||||||
|
} else {
|
||||||
|
// 否则推到下一年
|
||||||
|
nextExpiration.setFullYear(current.getFullYear() + 1);
|
||||||
|
result.check = 8;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new Error("Invalid billing cycle");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保持原到期时间的时分秒
|
||||||
|
nextExpiration.setHours(expiration.getHours());
|
||||||
|
nextExpiration.setMinutes(expiration.getMinutes());
|
||||||
|
nextExpiration.setSeconds(expiration.getSeconds());
|
||||||
|
|
||||||
|
result.date = nextExpiration;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
getAutoRenewalStartDate(flag, startDate, check, count) {
|
||||||
|
//1.判断什么时候改变 2.如何改变
|
||||||
|
const start = new Date(startDate);
|
||||||
|
const current = this.getAdjustTimezone(start, new Date());
|
||||||
|
|
||||||
|
// 检查 startDate 格式是否有效,若无效返回 null
|
||||||
|
if (isNaN(start.getTime())) {
|
||||||
|
console.log("getAutoRenewalStartDate: Invalid startDate format");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果 flag 为 0,直接返回开始日期
|
||||||
|
if (flag === 0) {
|
||||||
|
return start;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化新的开始日期
|
||||||
|
const newStart = new Date(start);
|
||||||
|
|
||||||
|
switch (check) {
|
||||||
|
case 1: // 处理月份:设置为上个月
|
||||||
|
newStart.setFullYear(current.getFullYear());
|
||||||
|
newStart.setMonth(current.getMonth() - 1);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 2: // 处理月份:设置为当前月
|
||||||
|
newStart.setFullYear(current.getFullYear());
|
||||||
|
newStart.setMonth(current.getMonth());
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 3:
|
||||||
|
case 4: // 处理季度
|
||||||
|
newStart.setFullYear(current.getFullYear());
|
||||||
|
newStart.setMonth(start.getMonth() + 3 * count);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 5:
|
||||||
|
case 6: // 处理半年
|
||||||
|
newStart.setFullYear(current.getFullYear());
|
||||||
|
newStart.setMonth(start.getMonth() + 6 * count);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 7: // 处理年份:设置为上一年
|
||||||
|
newStart.setFullYear(current.getFullYear() - 1);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 8: // 处理年份:保持当前年份
|
||||||
|
newStart.setFullYear(current.getFullYear());
|
||||||
|
break;
|
||||||
|
|
||||||
|
default: // 默认处理:直接返回
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return newStart;
|
||||||
|
},
|
||||||
|
getRemainingFormat(online, note) {
|
||||||
|
if (!note) return null;
|
||||||
|
const startDate = this.getNoteElementValue(note, "billingDataMod", "startDate");
|
||||||
|
const endDate = this.getNoteElementValue(note, "billingDataMod", "endDate");
|
||||||
|
|
||||||
|
// 检查 startDate 和 endDate 是否有效
|
||||||
|
if (!startDate || !endDate || typeof startDate !== 'string' || typeof endDate !== 'string') {
|
||||||
|
return null; // 如果无效,返回 null 或其他错误处理逻辑
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理特殊时间格式
|
||||||
|
if (startDate.includes('0000-00-00') || endDate.includes("0000-00-00")) {
|
||||||
|
return this.formatPercents(online, this.toFixed2(100));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查 startDate 和 endDate 是否为合法的Date
|
||||||
|
if (isNaN(new Date(startDate).getTime()) || isNaN(new Date(endDate).getTime())) {
|
||||||
|
return {
|
||||||
|
"class": "",
|
||||||
|
"style": "width: 0%",
|
||||||
|
"percent": "0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//处理特殊时间格式
|
||||||
|
const percent = this.getRemainingPercent(startDate, endDate, note);
|
||||||
|
return this.formatPercents(online, this.toFixed2(percent));
|
||||||
|
|
||||||
|
},
|
||||||
|
getRemainingDays(endDate, note) {
|
||||||
|
// 检查 endDate 是否有效
|
||||||
|
if (!endDate || typeof endDate !== 'string') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理特殊时间格式
|
||||||
|
if (endDate.includes("0000-00-00")) {
|
||||||
|
return "lifetime";
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查 startDate 和 endDate 是否为合法的Date
|
||||||
|
if (isNaN(new Date(endDate).getTime())) {
|
||||||
|
return "NaN";
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取当前时间,并调整时区
|
||||||
|
const currentTime = this.getAdjustTimezone(new Date(endDate), new Date());
|
||||||
|
|
||||||
|
// 获取计费周期和自动续订日期
|
||||||
|
const billingCycle = this.getNoteElementValue(note, "billingDataMod", "cycle") || "月";
|
||||||
|
const autoEndDate = this.getAutoRenewalEndDate(
|
||||||
|
this.getNoteElementValue(note, "billingDataMod", "endDate"),
|
||||||
|
this.getBillingCycle(billingCycle)
|
||||||
|
);
|
||||||
|
|
||||||
|
// 检查 autoRenewal 状态
|
||||||
|
const autoRenewal = this.getNoteElementValue(note, "billingDataMod", "autoRenewal") == 1;
|
||||||
|
|
||||||
|
// 确定到期时间
|
||||||
|
const end = autoRenewal ? autoEndDate.date : new Date(endDate);
|
||||||
|
|
||||||
|
// 计算剩余天数
|
||||||
|
const timeDiff = end - currentTime;
|
||||||
|
const daysDiff = Math.ceil(timeDiff / (1000 * 60 * 60 * 24));
|
||||||
|
|
||||||
|
return daysDiff;
|
||||||
|
},
|
||||||
|
getRemainingPercent(startDate, endDate, note) {
|
||||||
|
// 检查 startDate 和 endDate 是否为有效字符串并处理特殊格式
|
||||||
|
if (!startDate || !endDate || typeof startDate !== 'string' || typeof endDate !== 'string') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理特殊时间格式
|
||||||
|
if (startDate.includes("0000-00-00") || endDate.includes("0000-00-00")) {
|
||||||
|
return 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查 startDate 和 endDate 是否为合法的Date
|
||||||
|
if (isNaN(new Date(startDate).getTime()) || isNaN(new Date(endDate).getTime())) {
|
||||||
|
return "NaN";
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取当前时间并调整时区
|
||||||
|
const now = this.getAdjustTimezone(new Date(endDate), new Date());
|
||||||
|
|
||||||
|
// 获取计费周期
|
||||||
|
const billingCycle = this.getNoteElementValue(note, "billingDataMod", "cycle") || "月";
|
||||||
|
|
||||||
|
// 自动获取结束日期
|
||||||
|
const autoEndDate = this.getAutoRenewalEndDate(
|
||||||
|
this.getNoteElementValue(note, "billingDataMod", "endDate"),
|
||||||
|
this.getBillingCycle(billingCycle)
|
||||||
|
);
|
||||||
|
|
||||||
|
// 自动获取开始日期
|
||||||
|
const autoStartDate = autoEndDate.flag == 1
|
||||||
|
? this.getAutoRenewalStartDate(autoEndDate.flag, this.getNoteElementValue(note, "billingDataMod", "startDate"), autoEndDate.check, autoEndDate.count)
|
||||||
|
: new Date(startDate);
|
||||||
|
|
||||||
|
// 计算开始和结束时间
|
||||||
|
const autoRenewal = this.getNoteElementValue(note, "billingDataMod", "autoRenewal") == 1;
|
||||||
|
const start = autoRenewal ? autoStartDate : new Date(startDate);
|
||||||
|
const end = autoRenewal ? autoEndDate.date : new Date(endDate);
|
||||||
|
|
||||||
|
// 计算剩余百分比
|
||||||
|
if (now < start) {
|
||||||
|
return 0.01; //避免当条件判断!percent被过滤
|
||||||
|
}
|
||||||
|
if (now >= end) {
|
||||||
|
return 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
const totalDuration = end - start;
|
||||||
|
const elapsedDuration = now - start;
|
||||||
|
const percent = (elapsedDuration / totalDuration) * 100;
|
||||||
|
|
||||||
|
// 确保百分比在 0-100 之间
|
||||||
|
return Math.min(Math.max(percent, 0), 100);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
+4
-15
@@ -15,20 +15,9 @@
|
|||||||
</div>
|
</div>
|
||||||
<nav id="navbar" class="navbar-collapse collapse">
|
<nav id="navbar" class="navbar-collapse collapse">
|
||||||
<ul class="nav navbar-nav">
|
<ul class="nav navbar-nav">
|
||||||
<li><a href="/"><i class="home icon"></i>{{tr "Home" }}</a></li>
|
<li :class="isMenuActive('index')"><a href="/"><i class="home icon"></i>{{tr "Home" }}</a></li>
|
||||||
<template v-if="isMobile">
|
<li :class="isMenuActive('service')"><a href="/service"><i class="rss icon"></i>{{tr "Services" }}</a></li>
|
||||||
<li class="dropdown">
|
<li :class="isMenuActive('network')"><a href="/network"><i class="bi bi-hdd-network icon"></i>{{tr "NetworkSpiter"}}</a></li>
|
||||||
<a data-toggle="dropdown"><i class="bi bi-gear-wide-connected" style="position:relative;top:1px;margin-right:3px;font-size:1.1rem;"></i>{{tr "Feature" }}<b class="caret"></b></a>
|
|
||||||
<ul class="dropdown-menu" style="min-width:100px;">
|
|
||||||
<li><a href="/service"><i class="rss icon"></i>{{tr "Services" }}</a></li>
|
|
||||||
<li><a href="/network"><i class="bi bi-hdd-network icon"></i>{{tr "NetworkSpiter"}}</a></li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<li><a href="/service"><i class="rss icon"></i>{{tr "Services" }}</a></li>
|
|
||||||
<li><a href="/network"><i class="bi bi-hdd-network icon"></i>{{tr "NetworkSpiter"}}</a></li>
|
|
||||||
</template>
|
|
||||||
{{ if not .Conf.DisableSwitchTemplateInFrontend }}
|
{{ if not .Conf.DisableSwitchTemplateInFrontend }}
|
||||||
<li class="dropdown">
|
<li class="dropdown">
|
||||||
<a data-toggle="dropdown"><i class="bi bi-incognito" style="position:relative;top:1px;margin-right:3px;font-size:1.2rem;vertical-align:top;"></i>{{tr "Template" }}<b class="caret"></b></a>
|
<a data-toggle="dropdown"><i class="bi bi-incognito" style="position:relative;top:1px;margin-right:3px;font-size:1.2rem;vertical-align:top;"></i>{{tr "Template" }}<b class="caret"></b></a>
|
||||||
@@ -64,6 +53,6 @@
|
|||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<div class="nezha">
|
<section class="nezha">
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
|
|||||||
+49
-46
@@ -15,7 +15,7 @@
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="chartTitle"><i class="chartCountryCode" :class="'fi fi-' + chartCountryCode"></i> @#chartTitle#@</div>
|
<div class="chartTitle" @click="showCharts(nextServerId)"><i class="chartCountryCode" :class="'fi fi-' + chartCountryCode"></i> @#chartTitle#@</div>
|
||||||
<div id="chartbox" style="width:100%;height:auto;"></div>
|
<div id="chartbox" style="width:100%;height:auto;"></div>
|
||||||
</div>
|
</div>
|
||||||
{{template "theme-server-status/footer" .}}
|
{{template "theme-server-status/footer" .}}
|
||||||
@@ -32,7 +32,8 @@
|
|||||||
chartTitle: '',
|
chartTitle: '',
|
||||||
chartCountryCode: '',
|
chartCountryCode: '',
|
||||||
chart: null,
|
chart: null,
|
||||||
currentServerId: ''
|
currentServerId: '',
|
||||||
|
nextServerId: '',
|
||||||
},
|
},
|
||||||
mixins: [mixinsVue],
|
mixins: [mixinsVue],
|
||||||
created() {
|
created() {
|
||||||
@@ -66,37 +67,47 @@
|
|||||||
if(!this.chartDataList[id]) return;
|
if(!this.chartDataList[id]) return;
|
||||||
if(this.chart) this.disposeCharts(this.chart);
|
if(this.chart) this.disposeCharts(this.chart);
|
||||||
this.currentServerId = id;
|
this.currentServerId = id;
|
||||||
|
this.nextServerId = this.getNextServerId(id);
|
||||||
this.chartCountryCode = this.getServerCountryCode(id);
|
this.chartCountryCode = this.getServerCountryCode(id);
|
||||||
this.chartTitle = this.chartDataList[id][0].server_name;
|
this.chartTitle = this.chartDataList[id][0].server_name;
|
||||||
const chartData = this.chartDataList[id];
|
const chartData = this.chartDataList[id];
|
||||||
const chartContainer = document.getElementById('chartbox');
|
const chartContainer = document.getElementById('chartbox');
|
||||||
// 定义图表参数值
|
|
||||||
const MaxTCPPingValue = {{.Conf.MaxTCPPingValue}} ? {{.Conf.MaxTCPPingValue}} : 300;
|
const MaxTCPPingValue = {{.Conf.MaxTCPPingValue}} ? {{.Conf.MaxTCPPingValue}} : 300;
|
||||||
const autoheight = this.isMobile ? (window.innerHeight - 200) : (window.innerHeight - 250);
|
const autoheight = this.isMobile ? (window.innerHeight - 180) : (window.innerHeight - 250);
|
||||||
const fontSize = this.isMobile ? 10 : 14;
|
const fontSize = this.isMobile ? 10 : 14;
|
||||||
const gridLeft = this.isMobile ? 30 : 36;
|
const gridLeft = (MaxTCPPingValue > 500) ? (this.isMobile ? 36 : 42) : (this.isMobile ? 30 : 36);
|
||||||
const gridRight = this.isMobile ? 12 : 20;
|
const gridRight = this.isMobile ? 12 : 20;
|
||||||
const legendLeft = this.isMobile ? 'center' : 'center';
|
const legendLeft = this.isMobile ? 'center' : 'center';
|
||||||
const legendTop = this.isMobile ? 5 : 5;
|
const legendTop = this.isMobile ? 5 : 5;
|
||||||
const legendPadding= this.isMobile ? [5,0,5,0] : [5,0,5,0];
|
const legendPadding= this.isMobile ? [5,0,5,0] : [5,0,5,0];
|
||||||
const fontColor = this.theme == "dark" ? "#f1f1f1" : "#000000";
|
const fontColor = this.theme == "dark" ? "#f1f1f1" : "#000000";
|
||||||
|
const chartTheme = this.theme == "dark" ? "dark" : "default";
|
||||||
const backgroundColor = this.theme == "dark" ? '' : '';
|
const backgroundColor = this.theme == "dark" ? '' : '';
|
||||||
const tooltipBackgroundColor = this.theme == "dark" ? (this.semiTransparent ? "rgba(28,29,38,0.85)" : "rgba(28,29,38,1)") : (this.semiTransparent ? "rgba(255,255,255,0.85)" : "rgba(255,255,255,1)");
|
const tooltipBackgroundColor = this.theme == "dark" ? (this.semiTransparent ? "rgba(28,29,38,0.85)" : "rgba(28,29,38,1)") : (this.semiTransparent ? "rgba(255,255,255,0.85)" : "rgba(255,255,255,1)");
|
||||||
const tooltipBorderColor = this.theme == "dark" ? (this.semiTransparent ? "rgba(28,29,38,0.9)" : "rgba(28,29,38,1)") : (this.semiTransparent ? "rgba(255,255,255,0.9)" : "rgba(255,255,255,1)");
|
const tooltipBorderColor = this.theme == "dark" ? (this.semiTransparent ? "rgba(28,29,38,0.9)" : "rgba(28,29,38,1)") : (this.semiTransparent ? "rgba(255,255,255,0.9)" : "rgba(255,255,255,1)");
|
||||||
const lineStyleWidth = this.isMobile ? 1 : 2;
|
const lineStyleWidth = this.isMobile ? 1 : 2;
|
||||||
const splitLineWidth = this.isMobile ? 0.5 : 1;
|
const splitLineWidth = this.isMobile ? 0.5 : 1;
|
||||||
const splitLineColor = this.theme == "dark" ? "rgba(110, 112, 121, 0.95)" : "rgba(224, 230, 241, 0.95)";
|
const markPointSymbolSize = this.isMobile ? 36 : 42;
|
||||||
const markPointLabelColor = this.theme == "dark" ? "#111111" : "#000000";
|
const markPointItemStyleOpacity = this.semiTransparent ? 1 : 1;
|
||||||
const markPointItemStyleOpacity = this.semiTransparent ? 1 : 0.75;
|
const markPointFontSize = this.isMobile ? 8 : 10;
|
||||||
const markLineItemStyleOpacity = this.semiTransparent ? 1 : 0.75;
|
const markLineItemStyleOpacity = this.semiTransparent ? 1 : 0.75;
|
||||||
const markLineLineStyleWidth = this.isMobile ? 0.15 : 0.3;
|
const markLineLineStyleWidth = this.isMobile ? 0.15 : 0.3;
|
||||||
this.chart = echarts.init(chartContainer, '', { // init图表
|
const showLoadingMaskColor = this.theme == "dark" ? 'rgba(0, 0, 0, 0)' : 'rgba(255, 255, 255, 0)';
|
||||||
|
const showLoadingTextColor = this.theme == "dark" ? 'rgba(241, 241, 241, 1)' : 'rgba(0, 0, 0, 1)';
|
||||||
|
const showLoadingColor = this.theme == "dark" ? '#D2B206' : '#FFDF32';
|
||||||
|
this.chart = echarts.init(chartContainer, chartTheme, { // init图表
|
||||||
renderer: 'canvas',
|
renderer: 'canvas',
|
||||||
useDirtyRect: false,
|
useDirtyRect: false,
|
||||||
width: 'auto',
|
width: 'auto',
|
||||||
height: autoheight,
|
height: autoheight,
|
||||||
});
|
});
|
||||||
// 获取图表数据
|
this.chart.showLoading({
|
||||||
|
text: 'loading',
|
||||||
|
textColor: showLoadingTextColor,
|
||||||
|
color: showLoadingColor,
|
||||||
|
maskColor: showLoadingMaskColor,
|
||||||
|
zlevel: 2
|
||||||
|
});
|
||||||
let legendData = [];
|
let legendData = [];
|
||||||
let seriesData = [];
|
let seriesData = [];
|
||||||
chartData.forEach((item,key)=> {
|
chartData.forEach((item,key)=> {
|
||||||
@@ -111,9 +122,6 @@
|
|||||||
loss += 1;
|
loss += 1;
|
||||||
const lossrate = 100 * loss / (index + 1);
|
const lossrate = 100 * loss / (index + 1);
|
||||||
if(lossrate != 100) {
|
if(lossrate != 100) {
|
||||||
data['main'].push(
|
|
||||||
[item.created_at[index], MaxTCPPingValue, lossrate]
|
|
||||||
);
|
|
||||||
data['markLine'].push({
|
data['markLine'].push({
|
||||||
xAxis: item.created_at[index],
|
xAxis: item.created_at[index],
|
||||||
label: { show: false },
|
label: { show: false },
|
||||||
@@ -142,11 +150,9 @@
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// 处理legendData
|
|
||||||
totalLossRate = ((loss / item.created_at.length) * 100).toFixed(1);
|
totalLossRate = ((loss / item.created_at.length) * 100).toFixed(1);
|
||||||
legendName = `${item.monitor_name} ${totalLossRate}%`;
|
legendName = `${item.monitor_name} ${totalLossRate}%`;
|
||||||
legendData.push(legendName);
|
legendData.push(legendName);
|
||||||
// 处理seriesData
|
|
||||||
seriesData.push(
|
seriesData.push(
|
||||||
{
|
{
|
||||||
name: legendName,
|
name: legendName,
|
||||||
@@ -182,10 +188,9 @@
|
|||||||
itemStyle: {
|
itemStyle: {
|
||||||
opacity: markPointItemStyleOpacity
|
opacity: markPointItemStyleOpacity
|
||||||
},
|
},
|
||||||
symbolSize: 30,
|
symbolSize: markPointSymbolSize,
|
||||||
label: {
|
label: {
|
||||||
fontSize: 8,
|
fontSize: markPointFontSize,
|
||||||
color: markPointLabelColor,
|
|
||||||
formatter: function (params) {
|
formatter: function (params) {
|
||||||
return Math.round(params.value);
|
return Math.round(params.value);
|
||||||
}
|
}
|
||||||
@@ -198,10 +203,9 @@
|
|||||||
itemStyle: {
|
itemStyle: {
|
||||||
opacity: markPointItemStyleOpacity
|
opacity: markPointItemStyleOpacity
|
||||||
},
|
},
|
||||||
symbolSize: 30,
|
symbolSize: markPointSymbolSize,
|
||||||
label: {
|
label: {
|
||||||
fontSize: 8,
|
fontSize: markPointFontSize,
|
||||||
color: markPointLabelColor,
|
|
||||||
offset: [0, 8],
|
offset: [0, 8],
|
||||||
formatter: function (params) {
|
formatter: function (params) {
|
||||||
return Math.round(params.value);
|
return Math.round(params.value);
|
||||||
@@ -218,7 +222,8 @@
|
|||||||
const maxLegendsPerRowPc = localStorage.getItem("maxLegendsPerRowPc") ? localStorage.getItem("maxLegendsPerRowPc") : 6;
|
const maxLegendsPerRowPc = localStorage.getItem("maxLegendsPerRowPc") ? localStorage.getItem("maxLegendsPerRowPc") : 6;
|
||||||
const autoIncrement = Math.floor((legendData.length - 1) / (this.isMobile ? maxLegendsPerRowMobile : maxLegendsPerRowPc)) * (this.isMobile ? 20 : 28);
|
const autoIncrement = Math.floor((legendData.length - 1) / (this.isMobile ? maxLegendsPerRowMobile : maxLegendsPerRowPc)) * (this.isMobile ? 20 : 28);
|
||||||
const height = autoheight + autoIncrement;
|
const height = autoheight + autoIncrement;
|
||||||
const gridTop = 40 + autoIncrement;
|
const gridTop = this.isMobile ? ( 60 + autoIncrement) : (80 + autoIncrement);
|
||||||
|
const gridBottom = this.isMobile ? 70 : 90;
|
||||||
const legendIcon = this.isMobile ? 'rect' : "";
|
const legendIcon = this.isMobile ? 'rect' : "";
|
||||||
const itemWidth = this.isMobile ? 10 : 25;
|
const itemWidth = this.isMobile ? 10 : 25;
|
||||||
const itemHeight = this.isMobile ? 10 : 14;
|
const itemHeight = this.isMobile ? 10 : 14;
|
||||||
@@ -228,28 +233,22 @@
|
|||||||
});
|
});
|
||||||
// 设置图表配置项
|
// 设置图表配置项
|
||||||
const option = {
|
const option = {
|
||||||
// 全局调色盘
|
|
||||||
color: this.colors,
|
color: this.colors,
|
||||||
// 背景颜色
|
|
||||||
backgroundColor: backgroundColor,
|
backgroundColor: backgroundColor,
|
||||||
// 文字样式
|
|
||||||
textStyle: {
|
textStyle: {
|
||||||
fontSize: fontSize,
|
fontSize: fontSize,
|
||||||
color: fontColor
|
color: fontColor
|
||||||
},
|
},
|
||||||
// 图表网格设置
|
|
||||||
grid: {
|
grid: {
|
||||||
top: gridTop,
|
top: gridTop,
|
||||||
left: gridLeft,
|
left: gridLeft,
|
||||||
right: gridRight
|
right: gridRight,
|
||||||
|
bottom: gridBottom
|
||||||
},
|
},
|
||||||
// 图表标题设置
|
|
||||||
title: {
|
title: {
|
||||||
show: false,
|
show: false,
|
||||||
},
|
},
|
||||||
// 图表系列数据设置
|
|
||||||
series: seriesData.flat(),
|
series: seriesData.flat(),
|
||||||
// X轴设置
|
|
||||||
xAxis: {
|
xAxis: {
|
||||||
type: 'time',
|
type: 'time',
|
||||||
axisLabel: {
|
axisLabel: {
|
||||||
@@ -258,7 +257,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// Y轴设置
|
|
||||||
yAxis: {
|
yAxis: {
|
||||||
type: 'value',
|
type: 'value',
|
||||||
axisLabel: {
|
axisLabel: {
|
||||||
@@ -268,12 +266,10 @@
|
|||||||
},
|
},
|
||||||
splitLine: {
|
splitLine: {
|
||||||
lineStyle: {
|
lineStyle: {
|
||||||
width: splitLineWidth,
|
width: splitLineWidth
|
||||||
color: splitLineColor
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// 图例设置
|
|
||||||
legend: {
|
legend: {
|
||||||
data: legendData,
|
data: legendData,
|
||||||
show: true,
|
show: true,
|
||||||
@@ -289,7 +285,6 @@
|
|||||||
itemWidth: itemWidth,
|
itemWidth: itemWidth,
|
||||||
itemHeight: itemHeight,
|
itemHeight: itemHeight,
|
||||||
},
|
},
|
||||||
// 提示框设置
|
|
||||||
tooltip: {
|
tooltip: {
|
||||||
trigger: 'axis',
|
trigger: 'axis',
|
||||||
backgroundColor: tooltipBackgroundColor,
|
backgroundColor: tooltipBackgroundColor,
|
||||||
@@ -312,7 +307,6 @@
|
|||||||
return tooltipContent;
|
return tooltipContent;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// 数据缩放设置
|
|
||||||
dataZoom: [
|
dataZoom: [
|
||||||
{
|
{
|
||||||
type: 'slider',
|
type: 'slider',
|
||||||
@@ -321,17 +315,16 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
// 设置图表的配置选项
|
setTimeout(() => {
|
||||||
this.chart.setOption(option);
|
this.chart.hideLoading();
|
||||||
|
this.chart.setOption(option);
|
||||||
|
}, 1000);
|
||||||
},
|
},
|
||||||
reloadCharts() { // 重新加载所有图表
|
reloadCharts() {
|
||||||
this.servers.forEach(node => {
|
const chartData = this.chartDataList[this.currentServerId];
|
||||||
const id = node.ID;
|
if (chartData) {
|
||||||
const chartData = this.chartDataList[id];
|
this.renderCharts(this.currentServerId,true);
|
||||||
if (chartData) {
|
}
|
||||||
this.renderCharts(id,true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
disposeCharts(chart){
|
disposeCharts(chart){
|
||||||
chart.dispose();
|
chart.dispose();
|
||||||
@@ -341,6 +334,16 @@
|
|||||||
const result = this.servers.find(item => item.ID == id);
|
const result = this.servers.find(item => item.ID == id);
|
||||||
return result.Host.CountryCode ? result.Host.CountryCode : 'rb';
|
return result.Host.CountryCode ? result.Host.CountryCode : 'rb';
|
||||||
},
|
},
|
||||||
|
getNextServerId(id) {
|
||||||
|
const currentIndex = this.servers.findIndex(item => item.ID === id);
|
||||||
|
if (currentIndex === -1) {
|
||||||
|
return this.servers[0].ID;
|
||||||
|
}
|
||||||
|
// 判断是否有下一个元素
|
||||||
|
const nextIndex = currentIndex + 1;
|
||||||
|
// 如果有下一个元素,返回下一个元素的 ID;否则返回第一个元素的 ID
|
||||||
|
return nextIndex < this.servers.length ? this.servers[nextIndex].ID : this.servers[0].ID;
|
||||||
|
},
|
||||||
initSearch() {
|
initSearch() {
|
||||||
$('#dropdown-search').on('keyup', function() {
|
$('#dropdown-search').on('keyup', function() {
|
||||||
var searchTerm = $(this).val().toLowerCase();
|
var searchTerm = $(this).val().toLowerCase();
|
||||||
|
|||||||
+10
-10
@@ -3,24 +3,24 @@
|
|||||||
{{template "theme-server-status/menu" .}}
|
{{template "theme-server-status/menu" .}}
|
||||||
<!-- showGroup true -->
|
<!-- showGroup true -->
|
||||||
<template v-if="showGroup">
|
<template v-if="showGroup">
|
||||||
<section v-if="servicesTag.length === 0" class="container-fluid content" style="min-height: .01%;overflow-x: auto;">
|
<div v-if="servicesTag.length === 0" class="container-fluid content" style="min-height: .01%;overflow-x: auto;">
|
||||||
<p>No Valid Service Monitor Configuration Entries Found. Please Verify in the <a href="/monitor">Admin Panel</a>.</p>
|
<p>No Valid Service Monitor Configuration Entries Found. Please Verify in the <a href="/monitor">Admin Panel</a>.</p>
|
||||||
</section>
|
</div>
|
||||||
<section v-else class="container-fluid content" style="min-height: .01%;overflow-x: auto;" v-for="group in servicesTag">
|
<div v-else class="container-fluid content" style="min-height: .01%;overflow-x: auto;" v-for="group in servicesTag">
|
||||||
{{template "theme-server-status/service-group-true" .}}
|
{{template "theme-server-status/service-group-true" .}}
|
||||||
</section>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<!-- showGroup false -->
|
<!-- showGroup false -->
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<section v-if="servicesNoTag.length === 0" class="container-fluid content" style="min-height: .01%;overflow-x: auto;">
|
<div v-if="servicesNoTag.length === 0" class="container-fluid content" style="min-height: .01%;overflow-x: auto;">
|
||||||
<p>No Valid Service Monitor Configuration Entries Found. Please Verify in the <a href="/monitor">Admin Panel</a>.</p>
|
<p>No Valid Service Monitor Configuration Entries Found. Please Verify in the <a href="/monitor">Admin Panel</a>.</p>
|
||||||
</section>
|
</div>
|
||||||
<section v-else class="container-fluid content" style="min-height: .01%;overflow-x: auto;">
|
<div v-else class="container-fluid content" style="min-height: .01%;overflow-x: auto;">
|
||||||
{{template "theme-server-status/service-group-false" .}}
|
{{template "theme-server-status/service-group-false" .}}
|
||||||
</section>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
{{if .CycleTransferStats}}
|
{{if .CycleTransferStats}}
|
||||||
<section class="container-fluid content table-responsive">
|
<div class="container-fluid content table-responsive">
|
||||||
<table class="table table-striped table-condensed table-hover">
|
<table class="table table-striped table-condensed table-hover">
|
||||||
<thead>
|
<thead>
|
||||||
<tr class="node-group-tag">
|
<tr class="node-group-tag">
|
||||||
@@ -67,7 +67,7 @@
|
|||||||
{{end}}
|
{{end}}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</section>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
{{template "theme-server-status/footer" .}}
|
{{template "theme-server-status/footer" .}}
|
||||||
<script>
|
<script>
|
||||||
|
|||||||
+2
-2
@@ -12,7 +12,7 @@ NZ_DASHBOARD_PATH="${NZ_BASE_PATH}/dashboard"
|
|||||||
NZ_AGENT_PATH="${NZ_BASE_PATH}/agent"
|
NZ_AGENT_PATH="${NZ_BASE_PATH}/agent"
|
||||||
NZ_DASHBOARD_SERVICE="/etc/systemd/system/nezha-dashboard.service"
|
NZ_DASHBOARD_SERVICE="/etc/systemd/system/nezha-dashboard.service"
|
||||||
NZ_DASHBOARD_SERVICERC="/etc/init.d/nezha-dashboard"
|
NZ_DASHBOARD_SERVICERC="/etc/init.d/nezha-dashboard"
|
||||||
NZ_VERSION="v0.18.3"
|
NZ_VERSION="v0.19.0"
|
||||||
|
|
||||||
red='\033[0;31m'
|
red='\033[0;31m'
|
||||||
green='\033[0;32m'
|
green='\033[0;32m'
|
||||||
@@ -604,7 +604,7 @@ restart_and_update_standalone() {
|
|||||||
NZ_DASHBOARD_URL="https://${GITHUB_URL}/naibahq/nezha/releases/download/$version/dashboard-linux-$os_arch.zip"
|
NZ_DASHBOARD_URL="https://${GITHUB_URL}/naibahq/nezha/releases/download/$version/dashboard-linux-$os_arch.zip"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
sudo wget -qO $NZ_DASHBOARD_PATH/app.zip $NZ_DASHBOARD_URL >/dev/null 2>&1 && sudo unzip -qq $NZ_DASHBOARD_PATH/app.zip -d $NZ_DASHBOARD_PATH && sudo mv $NZ_DASHBOARD_PATH/dist/dashboard-linux-$os_arch $NZ_DASHBOARD_PATH/app && sudo rm -r $NZ_DASHBOARD_PATH/app.zip $NZ_DASHBOARD_PATH/dist
|
sudo wget -qO $NZ_DASHBOARD_PATH/app.zip $NZ_DASHBOARD_URL >/dev/null 2>&1 && sudo unzip -qq -o $NZ_DASHBOARD_PATH/app.zip -d $NZ_DASHBOARD_PATH && sudo mv $NZ_DASHBOARD_PATH/dashboard-linux-$os_arch $NZ_DASHBOARD_PATH/app && sudo rm $NZ_DASHBOARD_PATH/app.zip
|
||||||
|
|
||||||
if [ "$os_alpine" != 1 ]; then
|
if [ "$os_alpine" != 1 ]; then
|
||||||
sudo systemctl enable nezha-dashboard
|
sudo systemctl enable nezha-dashboard
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ NZ_DASHBOARD_PATH="${NZ_BASE_PATH}/dashboard"
|
|||||||
NZ_AGENT_PATH="${NZ_BASE_PATH}/agent"
|
NZ_AGENT_PATH="${NZ_BASE_PATH}/agent"
|
||||||
NZ_DASHBOARD_SERVICE="/etc/systemd/system/nezha-dashboard.service"
|
NZ_DASHBOARD_SERVICE="/etc/systemd/system/nezha-dashboard.service"
|
||||||
NZ_DASHBOARD_SERVICERC="/etc/init.d/nezha-dashboard"
|
NZ_DASHBOARD_SERVICERC="/etc/init.d/nezha-dashboard"
|
||||||
NZ_VERSION="v0.18.3"
|
NZ_VERSION="v0.19.0"
|
||||||
|
|
||||||
red='\033[0;31m'
|
red='\033[0;31m'
|
||||||
green='\033[0;32m'
|
green='\033[0;32m'
|
||||||
@@ -598,7 +598,7 @@ restart_and_update_standalone() {
|
|||||||
NZ_DASHBOARD_URL="https://${GITHUB_URL}/naibahq/nezha/releases/download/$version/dashboard-linux-$os_arch.zip"
|
NZ_DASHBOARD_URL="https://${GITHUB_URL}/naibahq/nezha/releases/download/$version/dashboard-linux-$os_arch.zip"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
sudo wget -qO $NZ_DASHBOARD_PATH/app.zip $NZ_DASHBOARD_URL >/dev/null 2>&1 && sudo unzip -qq $NZ_DASHBOARD_PATH/app.zip -d $NZ_DASHBOARD_PATH && sudo mv $NZ_DASHBOARD_PATH/dist/dashboard-linux-$os_arch $NZ_DASHBOARD_PATH/app && sudo rm -r $NZ_DASHBOARD_PATH/app.zip $NZ_DASHBOARD_PATH/dist
|
sudo wget -qO $NZ_DASHBOARD_PATH/app.zip $NZ_DASHBOARD_URL >/dev/null 2>&1 && sudo unzip -qq -o $NZ_DASHBOARD_PATH/app.zip -d $NZ_DASHBOARD_PATH && sudo mv $NZ_DASHBOARD_PATH/dashboard-linux-$os_arch $NZ_DASHBOARD_PATH/app && sudo rm $NZ_DASHBOARD_PATH/app.zip
|
||||||
|
|
||||||
if [ "$os_alpine" != 1 ]; then
|
if [ "$os_alpine" != 1 ]; then
|
||||||
sudo systemctl enable nezha-dashboard
|
sudo systemctl enable nezha-dashboard
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package rpc
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@@ -14,6 +15,18 @@ type ioStreamContext struct {
|
|||||||
agentIoConnectCh chan struct{}
|
agentIoConnectCh chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type bp struct {
|
||||||
|
buf []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
var bufPool = sync.Pool{
|
||||||
|
New: func() any {
|
||||||
|
return &bp{
|
||||||
|
buf: make([]byte, 1024*1024),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
func (s *NezhaHandler) CreateStream(streamId string) {
|
func (s *NezhaHandler) CreateStream(streamId string) {
|
||||||
s.ioStreamMutex.Lock()
|
s.ioStreamMutex.Lock()
|
||||||
defer s.ioStreamMutex.Unlock()
|
defer s.ioStreamMutex.Unlock()
|
||||||
@@ -117,7 +130,9 @@ LOOP:
|
|||||||
endCh := make(chan struct{})
|
endCh := make(chan struct{})
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
_, innerErr := io.CopyBuffer(stream.userIo, stream.agentIo, make([]byte, 1048576))
|
bp := bufPool.Get().(*bp)
|
||||||
|
defer bufPool.Put(bp)
|
||||||
|
_, innerErr := io.CopyBuffer(stream.userIo, stream.agentIo, bp.buf)
|
||||||
if innerErr != nil {
|
if innerErr != nil {
|
||||||
err = innerErr
|
err = innerErr
|
||||||
}
|
}
|
||||||
@@ -126,7 +141,9 @@ LOOP:
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
go func() {
|
go func() {
|
||||||
_, innerErr := io.CopyBuffer(stream.agentIo, stream.userIo, make([]byte, 1048576))
|
bp := bufPool.Get().(*bp)
|
||||||
|
defer bufPool.Put(bp)
|
||||||
|
_, innerErr := io.CopyBuffer(stream.agentIo, stream.userIo, bp.buf)
|
||||||
if innerErr != nil {
|
if innerErr != nil {
|
||||||
err = innerErr
|
err = innerErr
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user