Compare commits
6 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
2f79dd04c0 | ||
|
e5ed9736b3 | ||
|
c8353b85ae | ||
|
6142031387 | ||
|
dd86d0ff49 | ||
|
bdd426a679 |
92
.circleci/config.yml
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
version: 2.1
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-latest:
|
||||||
|
steps:
|
||||||
|
- checkout
|
||||||
|
- restore_cache:
|
||||||
|
keys:
|
||||||
|
- go-mod-latest-v4-{{ checksum "go.sum" }}
|
||||||
|
- run:
|
||||||
|
name: Install Dependencies
|
||||||
|
command: |
|
||||||
|
make build-dependencies
|
||||||
|
- save_cache:
|
||||||
|
key: go-mod-latest-v4-{{ checksum "go.sum" }}
|
||||||
|
paths:
|
||||||
|
- "~/go/pkg/mod"
|
||||||
|
- run:
|
||||||
|
name: Build AMD64
|
||||||
|
command: |
|
||||||
|
VERSION=$CIRCLE_BRANCH
|
||||||
|
if [ ! -z "${CIRCLE_TAG}" ]; then VERSION=$CIRCLE_TAG; fi
|
||||||
|
make ENV_BUILD_IDENTIFIER=$VERSION ENV_BUILD_VERSION=$(echo $CIRCLE_SHA1 | cut -c1-7) build-amd64
|
||||||
|
- run:
|
||||||
|
name: Install Cross-Platform Dependencies
|
||||||
|
command: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo -E apt-get -yq --no-install-suggests --no-install-recommends --force-yes install gcc-aarch64-linux-gnu libc6-dev-arm64-cross
|
||||||
|
sudo -E apt-get -yq --no-install-suggests --no-install-recommends --force-yes install gcc-arm-linux-gnueabi libc6-dev-armel-cross gcc-arm-linux-gnueabihf libc6-dev-armhf-cross
|
||||||
|
sudo ln -s /usr/include/asm-generic /usr/include/asm
|
||||||
|
- run:
|
||||||
|
name: Build ARM64
|
||||||
|
command: |
|
||||||
|
VERSION=$CIRCLE_BRANCH
|
||||||
|
if [ ! -z "${CIRCLE_TAG}" ]; then VERSION=$CIRCLE_TAG; fi
|
||||||
|
make ENV_BUILD_IDENTIFIER=$VERSION ENV_BUILD_VERSION=$(echo $CIRCLE_SHA1 | cut -c1-7) build-arm64
|
||||||
|
- run:
|
||||||
|
name: Build ARM
|
||||||
|
command: |
|
||||||
|
VERSION=$CIRCLE_BRANCH
|
||||||
|
if [ ! -z "${CIRCLE_TAG}" ]; then VERSION=$CIRCLE_TAG; fi
|
||||||
|
make ENV_BUILD_IDENTIFIER=$VERSION ENV_BUILD_VERSION=$(echo $CIRCLE_SHA1 | cut -c1-7) build-arm
|
||||||
|
- store_artifacts:
|
||||||
|
path: ~/repo/dist
|
||||||
|
- run:
|
||||||
|
name: "Publish Release on GitHub"
|
||||||
|
command: |
|
||||||
|
if [ ! -z "${CIRCLE_TAG}" ]; then
|
||||||
|
go install github.com/tcnksm/ghr@latest
|
||||||
|
ghr -t ${GITHUB_TOKEN} -u ${CIRCLE_PROJECT_USERNAME} -r ${CIRCLE_PROJECT_REPONAME} -c ${CIRCLE_SHA1} -replace $CIRCLE_TAG ~/repo/dist
|
||||||
|
fi
|
||||||
|
working_directory: ~/repo
|
||||||
|
docker:
|
||||||
|
- image: cimg/go:1.19
|
||||||
|
build-118: # just to validate compatibility with minimum go version
|
||||||
|
steps:
|
||||||
|
- checkout
|
||||||
|
- restore_cache:
|
||||||
|
keys:
|
||||||
|
- go-mod-118-v4-{{ checksum "go.sum" }}
|
||||||
|
- run:
|
||||||
|
name: Install Dependencies
|
||||||
|
command: |
|
||||||
|
make build-dependencies
|
||||||
|
- save_cache:
|
||||||
|
key: go-mod-118-v4-{{ checksum "go.sum" }}
|
||||||
|
paths:
|
||||||
|
- "~/go/pkg/mod"
|
||||||
|
- run:
|
||||||
|
name: Build
|
||||||
|
command: |
|
||||||
|
VERSION=$CIRCLE_BRANCH
|
||||||
|
if [ ! -z "${CIRCLE_TAG}" ]; then VERSION=$CIRCLE_TAG; fi
|
||||||
|
make ENV_BUILD_IDENTIFIER=$VERSION ENV_BUILD_VERSION=$(echo $CIRCLE_SHA1 | cut -c1-7) build
|
||||||
|
working_directory: ~/repo118
|
||||||
|
docker:
|
||||||
|
- image: cimg/go:1.18
|
||||||
|
|
||||||
|
workflows:
|
||||||
|
build-and-release:
|
||||||
|
jobs:
|
||||||
|
#--------------- BUILD ---------------#
|
||||||
|
- build-latest:
|
||||||
|
filters:
|
||||||
|
tags:
|
||||||
|
only: /^v.*/
|
||||||
|
- build-118:
|
||||||
|
requires:
|
||||||
|
- build-latest
|
||||||
|
filters:
|
||||||
|
tags:
|
||||||
|
only: /^v.*/
|
@@ -1,14 +0,0 @@
|
|||||||
# Ignore everything
|
|
||||||
*
|
|
||||||
|
|
||||||
# Allow backend files
|
|
||||||
!cmd/
|
|
||||||
!internal/
|
|
||||||
!go.mod
|
|
||||||
!go.sum
|
|
||||||
|
|
||||||
# Allow frontend files
|
|
||||||
!frontend/
|
|
||||||
|
|
||||||
# Ignore node_modules
|
|
||||||
**/node_modules/
|
|
35
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,35 +0,0 @@
|
|||||||
---
|
|
||||||
name: Bug report
|
|
||||||
about: Create a report to help us improve WG-Portal
|
|
||||||
labels: bug
|
|
||||||
|
|
||||||
---
|
|
||||||
<!-- Tip: you can use code blocks
|
|
||||||
for better formatting of yaml config or logs
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
# config.yaml
|
|
||||||
```
|
|
||||||
|
|
||||||
```console
|
|
||||||
logs here
|
|
||||||
``` -->
|
|
||||||
|
|
||||||
**Describe the bug**
|
|
||||||
<!-- A clear and concise description of what the bug is. -->
|
|
||||||
|
|
||||||
**Expected behavior**
|
|
||||||
<!-- A clear and concise description of what you expected to happen. -->
|
|
||||||
|
|
||||||
**Steps to reproduce**
|
|
||||||
<!--Steps to reproduce the bug should be clear and easily reproducible to help people
|
|
||||||
gain an understanding of the problem.-->
|
|
||||||
|
|
||||||
**Screenshots**
|
|
||||||
<!-- If applicable, add screenshots to help explain your problem. -->
|
|
||||||
|
|
||||||
**Additional context**
|
|
||||||
<!-- Add any other context about the problem here. -->
|
|
||||||
- Application version: v
|
|
||||||
- Install method: binary/docker/helm/sources
|
|
||||||
<!-- - OS: -->
|
|
18
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -1,18 +0,0 @@
|
|||||||
---
|
|
||||||
name: Feature request
|
|
||||||
about: Suggest an idea for this project
|
|
||||||
labels: 'enhancement'
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Is your feature request related to a problem? Please describe.**
|
|
||||||
<!-- A clear and concise description of what the problem is. -->
|
|
||||||
|
|
||||||
**Describe the solution you'd like**
|
|
||||||
<!-- A clear and concise description of what you want to happen. -->
|
|
||||||
|
|
||||||
**Describe alternatives you've considered**
|
|
||||||
<!-- A clear and concise description of any alternative solutions or features you've considered. -->
|
|
||||||
|
|
||||||
**Additional context**
|
|
||||||
<!-- Add any other context or screenshots about the feature request here. -->
|
|
35
.github/dependabot.yml
vendored
@@ -1,35 +0,0 @@
|
|||||||
# To get started with Dependabot version updates, you'll need to specify which
|
|
||||||
# package ecosystems to update and where the package manifests are located.
|
|
||||||
# Please see the documentation for all configuration options:
|
|
||||||
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
|
||||||
|
|
||||||
version: 2
|
|
||||||
updates:
|
|
||||||
- package-ecosystem: github-actions
|
|
||||||
directory: /
|
|
||||||
schedule:
|
|
||||||
interval: weekly
|
|
||||||
groups:
|
|
||||||
actions:
|
|
||||||
patterns:
|
|
||||||
- "*"
|
|
||||||
|
|
||||||
- package-ecosystem: gomod
|
|
||||||
directory: /
|
|
||||||
schedule:
|
|
||||||
interval: weekly
|
|
||||||
groups:
|
|
||||||
golang:
|
|
||||||
patterns:
|
|
||||||
- golang.org*
|
|
||||||
gorm:
|
|
||||||
patterns:
|
|
||||||
- gorm.io*
|
|
||||||
patch:
|
|
||||||
update-types:
|
|
||||||
- patch
|
|
||||||
|
|
||||||
- package-ecosystem: "docker"
|
|
||||||
directory: /
|
|
||||||
schedule:
|
|
||||||
interval: weekly
|
|
18
.github/pull_request_template.md
vendored
@@ -1,18 +0,0 @@
|
|||||||
## Problem Statement
|
|
||||||
|
|
||||||
What is the problem you're trying to solve?
|
|
||||||
|
|
||||||
## Related Issue
|
|
||||||
|
|
||||||
Fixes #...
|
|
||||||
|
|
||||||
## Proposed Changes
|
|
||||||
|
|
||||||
How do you like to solve the issue and why?
|
|
||||||
|
|
||||||
## Checklist
|
|
||||||
|
|
||||||
- [ ] Commits are signed with `git commit --signoff`
|
|
||||||
- [ ] Changes have reasonable test coverage
|
|
||||||
- [ ] Tests pass with `make test`
|
|
||||||
- [ ] Helm docs are up-to-date with `make helm-docs`
|
|
75
.github/workflows/chart.yml
vendored
@@ -1,75 +0,0 @@
|
|||||||
# Publish chart to the GitHub Container Registry (GHCR) on push to master
|
|
||||||
# Run the following tests on PRs:
|
|
||||||
# - Check if chart's documentation is up to date
|
|
||||||
# - Check chart linting
|
|
||||||
# - Check chart installation in a Kind cluster
|
|
||||||
# - Check chart packaging
|
|
||||||
|
|
||||||
name: Chart
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
branches: [master]
|
|
||||||
paths: ['deploy/helm/**']
|
|
||||||
push:
|
|
||||||
branches: [master]
|
|
||||||
paths: ['deploy/helm/**']
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
lint-test:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
if: ${{ github.event_name == 'pull_request' }}
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Check docs
|
|
||||||
run: |
|
|
||||||
make helm-docs
|
|
||||||
if ! git diff --exit-code; then
|
|
||||||
echo "error::Documentation is not up to date. Please run helm-docs and commit changes."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# ct lint requires Python 3.x to run following packages:
|
|
||||||
# - yamale (https://github.com/23andMe/Yamale)
|
|
||||||
# - yamllint (https://github.com/adrienverge/yamllint)
|
|
||||||
- uses: actions/setup-python@v5
|
|
||||||
with:
|
|
||||||
python-version: '3.x'
|
|
||||||
|
|
||||||
- uses: helm/chart-testing-action@v2
|
|
||||||
|
|
||||||
- name: Run chart-testing (lint)
|
|
||||||
run: ct lint --config ct.yaml
|
|
||||||
|
|
||||||
- uses: nolar/setup-k3d-k3s@v1
|
|
||||||
with:
|
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Run chart-testing (install)
|
|
||||||
run: ct install --config ct.yaml
|
|
||||||
|
|
||||||
- name: Check chart packaging
|
|
||||||
run: helm package deploy/helm
|
|
||||||
|
|
||||||
publish:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
if: ${{ github.event_name == 'push' }}
|
|
||||||
permissions:
|
|
||||||
packages: write
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- uses: docker/login-action@v3
|
|
||||||
with:
|
|
||||||
registry: ghcr.io
|
|
||||||
username: ${{ github.actor }}
|
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Package helm chart
|
|
||||||
run: helm package deploy/helm
|
|
||||||
|
|
||||||
- name: Push chart to GHCR
|
|
||||||
run: helm push wg-portal-*.tgz oci://ghcr.io/${{ github.repository_owner }}/charts
|
|
67
.github/workflows/codeql-analysis.yml
vendored
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
# For most projects, this workflow file will not need changing; you simply need
|
||||||
|
# to commit it to your repository.
|
||||||
|
#
|
||||||
|
# You may wish to alter this file to override the set of languages analyzed,
|
||||||
|
# or to provide custom queries or build logic.
|
||||||
|
#
|
||||||
|
# ******** NOTE ********
|
||||||
|
# We have attempted to detect the languages in your repository. Please check
|
||||||
|
# the `language` matrix defined below to confirm you have the correct set of
|
||||||
|
# supported CodeQL languages.
|
||||||
|
#
|
||||||
|
name: "CodeQL"
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ master ]
|
||||||
|
pull_request:
|
||||||
|
# The branches below must be a subset of the branches above
|
||||||
|
branches: [ master ]
|
||||||
|
schedule:
|
||||||
|
- cron: '35 15 * * 4'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
analyze:
|
||||||
|
name: Analyze
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
language: [ 'go', 'javascript' ]
|
||||||
|
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
|
||||||
|
# Learn more:
|
||||||
|
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
# Initializes the CodeQL tools for scanning.
|
||||||
|
- name: Initialize CodeQL
|
||||||
|
uses: github/codeql-action/init@v1
|
||||||
|
with:
|
||||||
|
languages: ${{ matrix.language }}
|
||||||
|
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||||
|
# By default, queries listed here will override any specified in a config file.
|
||||||
|
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||||
|
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
||||||
|
|
||||||
|
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||||
|
# If this step fails, then you should remove it and run the build manually (see below)
|
||||||
|
- name: Autobuild
|
||||||
|
uses: github/codeql-action/autobuild@v1
|
||||||
|
|
||||||
|
# ℹ️ Command-line programs to run using the OS shell.
|
||||||
|
# 📚 https://git.io/JvXDl
|
||||||
|
|
||||||
|
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
||||||
|
# and modify them (or add more) to build your code if your project
|
||||||
|
# uses a compiled language
|
||||||
|
|
||||||
|
#- run: |
|
||||||
|
# make bootstrap
|
||||||
|
# make release
|
||||||
|
|
||||||
|
- name: Perform CodeQL Analysis
|
||||||
|
uses: github/codeql-action/analyze@v1
|
172
.github/workflows/docker-publish.yml
vendored
@@ -1,125 +1,139 @@
|
|||||||
name: Docker
|
name: Docker
|
||||||
|
|
||||||
on:
|
# This workflow uses actions that are not certified by GitHub.
|
||||||
pull_request:
|
# They are provided by a third-party and are governed by
|
||||||
branches: [master]
|
# separate terms of service, privacy policy, and support
|
||||||
push:
|
# documentation.
|
||||||
branches: [master]
|
|
||||||
# Publish vX.X.X tags as releases.
|
on:
|
||||||
tags: ["v*.*.*"]
|
push:
|
||||||
|
branches: [ master, stable ]
|
||||||
|
# Publish vX.X.X tags as releases.
|
||||||
|
tags: [ 'v*.*.*' ]
|
||||||
|
|
||||||
|
env:
|
||||||
|
# Use docker.io for Docker Hub if empty
|
||||||
|
REGISTRY: ghcr.io
|
||||||
|
# github.repository as <account>/<repo>
|
||||||
|
IMAGE_NAME: ${{ github.repository }}
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
packages: write
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-n-push:
|
build-dockerhub:
|
||||||
name: Build and Push
|
name: Push Docker image to Docker Hub
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Check out the repo
|
- name: Check out the repo
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v2
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v2
|
||||||
|
|
||||||
- name: Get Version
|
- name: Get Version
|
||||||
shell: bash
|
shell: bash
|
||||||
run: echo "BUILD_VERSION=${GITHUB_REF_NAME}-${GITHUB_SHA::7}" >> $GITHUB_ENV
|
run: |
|
||||||
|
echo "::set-output name=identifier::$(echo ${GITHUB_REF##*/})"
|
||||||
|
echo "::set-output name=hash::$(echo ${GITHUB_SHA} | cut -c1-7)"
|
||||||
|
id: get_version
|
||||||
|
|
||||||
- name: Login to Docker Hub
|
- name: Log in to Docker Hub
|
||||||
if: github.event_name != 'pull_request'
|
if: github.event_name != 'pull_request'
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v2
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKER_USERNAME }}
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
|
||||||
if: github.event_name != 'pull_request'
|
|
||||||
uses: docker/login-action@v3
|
|
||||||
with:
|
|
||||||
registry: ghcr.io
|
|
||||||
username: ${{ github.actor }}
|
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Extract metadata (tags, labels) for Docker
|
- name: Extract metadata (tags, labels) for Docker
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@v5
|
uses: docker/metadata-action@v4
|
||||||
with:
|
with:
|
||||||
images: |
|
images: wgportal/wg-portal
|
||||||
wgportal/wg-portal
|
|
||||||
ghcr.io/${{ github.repository }}
|
|
||||||
flavor: |
|
flavor: |
|
||||||
latest=auto
|
latest=false
|
||||||
prefix=
|
prefix=
|
||||||
suffix=
|
suffix=
|
||||||
tags: |
|
tags: |
|
||||||
type=ref,event=tag
|
|
||||||
type=ref,event=branch
|
type=ref,event=branch
|
||||||
# semver tags, without v prefix
|
|
||||||
type=semver,pattern={{version}}
|
type=semver,pattern={{version}}
|
||||||
# major and major.minor tags are not available for alpha or beta releases
|
|
||||||
type=semver,pattern={{major}}.{{minor}}
|
type=semver,pattern={{major}}.{{minor}}
|
||||||
type=semver,pattern={{major}}
|
|
||||||
type=semver,pattern=v{{major}}.{{minor}}
|
|
||||||
type=semver,pattern=v{{major}}
|
type=semver,pattern=v{{major}}
|
||||||
# add v{{major}} tag, even for beta or release-canidate releases
|
|
||||||
type=match,pattern=(v\d),group=1,enable=${{ contains(github.ref, 'beta') || contains(github.ref, 'rc') }}
|
|
||||||
# add {{major}} tag, even for beta releases or release-canidate releases
|
|
||||||
type=match,pattern=v(\d),group=1,enable=${{ contains(github.ref, 'beta') || contains(github.ref, 'rc') }}
|
|
||||||
|
|
||||||
- name: Build and push Docker image
|
- name: Build and push Docker image
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v3
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
push: ${{ github.event_name != 'pull_request' }}
|
push: ${{ github.event_name != 'pull_request' }}
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
annotations: ${{ steps.meta.outputs.annotations }}
|
|
||||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||||
build-args: |
|
build-args: |
|
||||||
BUILD_VERSION=${{ env.BUILD_VERSION }}
|
BUILD_IDENTIFIER=${{ steps.get_version.outputs.identifier }}
|
||||||
|
BUILD_VERSION=${{ steps.get_version.outputs.hash }}
|
||||||
|
|
||||||
- name: Export binaries from images
|
build-github:
|
||||||
uses: docker/build-push-action@v6
|
name: Push Docker image to Github Container Registry
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v2
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v2
|
||||||
|
|
||||||
|
- name: Get Version
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
echo "::set-output name=identifier::$(echo ${GITHUB_REF##*/})"
|
||||||
|
echo "::set-output name=hash::$(echo ${GITHUB_SHA} | cut -c1-7)"
|
||||||
|
id: get_version
|
||||||
|
|
||||||
|
# Login against a Docker registry except on PR
|
||||||
|
# https://github.com/docker/login-action
|
||||||
|
- name: Log into registry ${{ env.REGISTRY }}
|
||||||
|
if: github.event_name != 'pull_request'
|
||||||
|
uses: docker/login-action@v2
|
||||||
|
with:
|
||||||
|
registry: ${{ env.REGISTRY }}
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
# Extract metadata (tags, labels) for Docker
|
||||||
|
# https://github.com/docker/metadata-action
|
||||||
|
- name: Extract Docker metadata
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v4
|
||||||
|
with:
|
||||||
|
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||||
|
flavor: |
|
||||||
|
latest=false
|
||||||
|
prefix=
|
||||||
|
suffix=
|
||||||
|
tags: |
|
||||||
|
type=ref,event=branch
|
||||||
|
type=semver,pattern={{version}}
|
||||||
|
type=semver,pattern={{major}}.{{minor}}
|
||||||
|
type=semver,pattern=v{{major}}
|
||||||
|
|
||||||
|
# Build and push Docker image with Buildx (don't push on PR)
|
||||||
|
# https://github.com/docker/build-push-action
|
||||||
|
- name: Build and push Docker image
|
||||||
|
uses: docker/build-push-action@v3
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
|
push: ${{ github.event_name != 'pull_request' }}
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||||
target: binaries
|
|
||||||
outputs: type=local,dest=./binaries
|
|
||||||
build-args: |
|
build-args: |
|
||||||
BUILD_VERSION=${{ env.BUILD_VERSION }}
|
BUILD_IDENTIFIER=${{ steps.get_version.outputs.identifier }}
|
||||||
|
BUILD_VERSION=${{ steps.get_version.outputs.hash }}
|
||||||
- name: Rename binaries
|
|
||||||
run: |
|
|
||||||
for file in binaries/linux*/wg-portal; do
|
|
||||||
mv $file binaries/wg-portal_$(basename $(dirname $file))
|
|
||||||
done
|
|
||||||
|
|
||||||
- name: Upload binaries
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: binaries
|
|
||||||
path: binaries/wg-portal_linux*
|
|
||||||
retention-days: 10
|
|
||||||
|
|
||||||
release:
|
|
||||||
if: startsWith(github.ref, 'refs/tags/v')
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: build-n-push
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
steps:
|
|
||||||
- name: Download binaries
|
|
||||||
uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
name: binaries
|
|
||||||
|
|
||||||
- name: Create GitHub Release
|
|
||||||
uses: softprops/action-gh-release@v2
|
|
||||||
with:
|
|
||||||
files: 'wg-portal_linux*'
|
|
||||||
generate_release_notes: true
|
|
40
.github/workflows/pages.yml
vendored
@@ -1,40 +0,0 @@
|
|||||||
name: github-pages
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [master]
|
|
||||||
tags:
|
|
||||||
- 'v*'
|
|
||||||
- '!v*-alpha*'
|
|
||||||
- '!v*-beta*'
|
|
||||||
- '!v*-rc*'
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
deploy:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- uses: actions/setup-python@v5
|
|
||||||
with:
|
|
||||||
python-version: 3.x
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: pip install mike mkdocs-material[imaging] mkdocs-minify-plugin mkdocs-swagger-ui-tag
|
|
||||||
|
|
||||||
- name: Publish documentation
|
|
||||||
if: ${{ ! startsWith(github.ref, 'refs/tags/') }}
|
|
||||||
run: mike deploy --push ${{ github.ref_name }}
|
|
||||||
env:
|
|
||||||
GIT_COMMITTER_NAME: "github-actions[bot]"
|
|
||||||
GIT_COMMITTER_EMAIL: "41898282+github-actions[bot]@users.noreply.github.com"
|
|
||||||
- name: Publish latest documentation
|
|
||||||
if: ${{ startsWith(github.ref, 'refs/tags/') }}
|
|
||||||
run: mike deploy --push --update-aliases ${{ github.ref_name }} latest
|
|
||||||
env:
|
|
||||||
GIT_COMMITTER_NAME: "github-actions[bot]"
|
|
||||||
GIT_COMMITTER_EMAIL: "41898282+github-actions[bot]@users.noreply.github.com"
|
|
14
.gitignore
vendored
@@ -28,16 +28,12 @@
|
|||||||
out/
|
out/
|
||||||
dist/
|
dist/
|
||||||
data/
|
data/
|
||||||
|
docker_images/
|
||||||
ssh.key
|
ssh.key
|
||||||
.testCoverage.txt
|
.testCoverage.txt
|
||||||
wg_portal.db
|
wg_portal.db
|
||||||
sqlite.db
|
swagger.json
|
||||||
|
swagger.yaml
|
||||||
/config.yml
|
/config.yml
|
||||||
/config.yaml
|
sqlite.db
|
||||||
/config/
|
node_modules/
|
||||||
venv/
|
|
||||||
.cache/
|
|
||||||
# ignore local frontend dist directory
|
|
||||||
internal/app/api/core/frontend-dist
|
|
||||||
# mkdocs output directory
|
|
||||||
site/
|
|
@@ -1,11 +0,0 @@
|
|||||||
<component name="ProjectRunConfigurationManager">
|
|
||||||
<configuration default="false" name="swag_build_tool" type="GoApplicationRunConfiguration" factoryName="Go Application">
|
|
||||||
<module name="wg-portal" />
|
|
||||||
<working_directory value="$PROJECT_DIR$" />
|
|
||||||
<kind value="PACKAGE" />
|
|
||||||
<package value="github.com/h44z/wg-portal/cmd/api_build_tool" />
|
|
||||||
<directory value="$PROJECT_DIR$" />
|
|
||||||
<filePath value="$PROJECT_DIR$/internal/ports/api/build_tool/main.go" />
|
|
||||||
<method v="2" />
|
|
||||||
</configuration>
|
|
||||||
</component>
|
|
@@ -1,17 +0,0 @@
|
|||||||
<component name="ProjectRunConfigurationManager">
|
|
||||||
<configuration default="false" name="wg-portal-migrate" type="GoApplicationRunConfiguration" factoryName="Go Application">
|
|
||||||
<module name="wg-portal" />
|
|
||||||
<working_directory value="$PROJECT_DIR$" />
|
|
||||||
<parameters value="-migrateFrom=wg_portal.db" />
|
|
||||||
<envs>
|
|
||||||
<env name="SESSION_SECRET" value="extremlybad" />
|
|
||||||
<env name="LOG_LEVEL" value="trace" />
|
|
||||||
</envs>
|
|
||||||
<sudo value="true" />
|
|
||||||
<kind value="PACKAGE" />
|
|
||||||
<package value="github.com/h44z/wg-portal/cmd/wg-portal" />
|
|
||||||
<directory value="$PROJECT_DIR$" />
|
|
||||||
<filePath value="$PROJECT_DIR$/cmd/wg-portal/main.go" />
|
|
||||||
<method v="2" />
|
|
||||||
</configuration>
|
|
||||||
</component>
|
|
@@ -1,16 +0,0 @@
|
|||||||
<component name="ProjectRunConfigurationManager">
|
|
||||||
<configuration default="false" name="wg-portal" type="GoApplicationRunConfiguration" factoryName="Go Application">
|
|
||||||
<module name="wg-portal" />
|
|
||||||
<working_directory value="$PROJECT_DIR$" />
|
|
||||||
<envs>
|
|
||||||
<env name="SESSION_SECRET" value="extremlybad" />
|
|
||||||
<env name="LOG_LEVEL" value="trace" />
|
|
||||||
</envs>
|
|
||||||
<sudo value="true" />
|
|
||||||
<kind value="PACKAGE" />
|
|
||||||
<package value="github.com/h44z/wg-portal/cmd/wg-portal" />
|
|
||||||
<directory value="$PROJECT_DIR$" />
|
|
||||||
<filePath value="$PROJECT_DIR$/cmd/wg-portal/main.go" />
|
|
||||||
<method v="2" />
|
|
||||||
</configuration>
|
|
||||||
</component>
|
|
96
Dockerfile
@@ -1,69 +1,53 @@
|
|||||||
# Dockerfile References: https://docs.docker.com/engine/reference/builder/
|
# Dockerfile References: https://docs.docker.com/engine/reference/builder/
|
||||||
# This dockerfile uses a multi-stage build system to reduce the image footprint.
|
# This dockerfile uses a multi-stage build system to reduce the image footprint.
|
||||||
|
|
||||||
######
|
######-
|
||||||
# Build frontend
|
# Start from the latest golang base image as builder image (only used to compile the code)
|
||||||
######
|
######-
|
||||||
FROM --platform=${BUILDPLATFORM} node:lts-alpine AS frontend
|
FROM golang:1.18 as builder
|
||||||
# Set the working directory
|
|
||||||
WORKDIR /build
|
ARG BUILD_IDENTIFIER
|
||||||
# Download dependencies
|
ENV ENV_BUILD_IDENTIFIER=$BUILD_IDENTIFIER
|
||||||
COPY frontend/package.json frontend/package-lock.json ./
|
|
||||||
RUN npm ci
|
|
||||||
# Set dist output directory
|
|
||||||
ENV DIST_OUT_DIR="dist"
|
|
||||||
# Copy the sources to the working directory
|
|
||||||
COPY frontend .
|
|
||||||
# Build the frontend
|
|
||||||
RUN npm run build
|
|
||||||
|
|
||||||
######
|
|
||||||
# Build backend
|
|
||||||
######
|
|
||||||
FROM --platform=${BUILDPLATFORM} golang:1.24-alpine AS builder
|
|
||||||
# Set the working directory
|
|
||||||
WORKDIR /build
|
|
||||||
# Download dependencies
|
|
||||||
COPY go.mod go.sum ./
|
|
||||||
RUN go mod download
|
|
||||||
# Copy the sources to the working directory
|
|
||||||
COPY ./cmd ./cmd
|
|
||||||
COPY ./internal ./internal
|
|
||||||
# Copy the frontend build result
|
|
||||||
COPY --from=frontend /build/dist/ ./internal/app/api/core/frontend-dist/
|
|
||||||
# Set the build version from arguments
|
|
||||||
ARG BUILD_VERSION
|
ARG BUILD_VERSION
|
||||||
# Split to cross-platform build
|
ENV ENV_BUILD_VERSION=$BUILD_VERSION
|
||||||
ARG TARGETARCH
|
|
||||||
# Build the application
|
|
||||||
RUN CGO_ENABLED=0 GOARCH=${TARGETARCH} go build -o /build/dist/wg-portal \
|
|
||||||
-ldflags "-w -s -extldflags '-static' -X 'github.com/h44z/wg-portal/internal.Version=${BUILD_VERSION}'" \
|
|
||||||
-tags netgo \
|
|
||||||
cmd/wg-portal/main.go
|
|
||||||
|
|
||||||
######
|
# populated by BuildKit
|
||||||
# Export binaries
|
ARG TARGETPLATFORM
|
||||||
######
|
ENV ENV_TARGETPLATFORM=$TARGETPLATFORM
|
||||||
FROM scratch AS binaries
|
|
||||||
COPY --from=builder /build/dist/wg-portal /
|
RUN mkdir /build
|
||||||
|
|
||||||
|
# Copy the source from the current directory to the Working Directory inside the container
|
||||||
|
ADD . /build/
|
||||||
|
|
||||||
|
# Set the Current Working Directory inside the container
|
||||||
|
WORKDIR /build
|
||||||
|
|
||||||
|
# Build the Go app
|
||||||
|
RUN echo "Building version '$ENV_BUILD_IDENTIFIER-$ENV_BUILD_VERSION' for platform $ENV_TARGETPLATFORM"; make build
|
||||||
|
|
||||||
|
######-
|
||||||
|
# Here starts the main image
|
||||||
|
######-
|
||||||
|
FROM scratch
|
||||||
|
|
||||||
######
|
|
||||||
# Final image
|
|
||||||
######
|
|
||||||
FROM alpine:3.22
|
|
||||||
# Install OS-level dependencies
|
|
||||||
RUN apk add --no-cache bash curl iptables nftables openresolv wireguard-tools
|
|
||||||
# Setup timezone
|
# Setup timezone
|
||||||
ENV TZ=UTC
|
ENV TZ=Europe/Vienna
|
||||||
|
|
||||||
|
# Import linux stuff from builder.
|
||||||
|
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
|
||||||
|
COPY --from=builder /etc/passwd /etc/passwd
|
||||||
|
COPY --from=builder /etc/group /etc/group
|
||||||
|
|
||||||
# Copy binaries
|
# Copy binaries
|
||||||
COPY --from=builder /build/dist/wg-portal /app/wg-portal
|
COPY --from=builder /build/dist/wg-portal /app/wg-portal
|
||||||
|
COPY --from=builder /build/dist/hc /app/hc
|
||||||
|
|
||||||
# Set the Current Working Directory inside the container
|
# Set the Current Working Directory inside the container
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
# Expose default ports for metrics, web and wireguard
|
|
||||||
EXPOSE 8787/tcp
|
|
||||||
EXPOSE 8888/tcp
|
|
||||||
EXPOSE 51820/udp
|
|
||||||
# the database and config file can be mounted from the host
|
|
||||||
VOLUME [ "/app/data", "/app/config" ]
|
|
||||||
# Command to run the executable
|
# Command to run the executable
|
||||||
ENTRYPOINT [ "/app/wg-portal" ]
|
CMD [ "/app/wg-portal" ]
|
||||||
|
|
||||||
|
HEALTHCHECK --interval=30s --timeout=5s --start-period=30s --retries=3 CMD [ "/app/hc", "http://localhost:11223/health" ]
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
Copyright (c) 2020-2023 Christoph Haas
|
Copyright (c) 2020 Christoph Haas
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
a copy of this software and associated documentation files (the
|
a copy of this software and associated documentation files (the
|
||||||
|
56
Makefile
@@ -5,7 +5,6 @@ GOFILES:=$(shell go list ./... | grep -v /vendor/)
|
|||||||
BUILDDIR=dist
|
BUILDDIR=dist
|
||||||
BINARIES=$(subst cmd/,,$(wildcard cmd/*))
|
BINARIES=$(subst cmd/,,$(wildcard cmd/*))
|
||||||
IMAGE=h44z/wg-portal
|
IMAGE=h44z/wg-portal
|
||||||
NPMCMD=npm
|
|
||||||
|
|
||||||
all: help
|
all: help
|
||||||
|
|
||||||
@@ -26,6 +25,7 @@ help:
|
|||||||
#> codegen: Re-generate autogenerated files (like API docs)
|
#> codegen: Re-generate autogenerated files (like API docs)
|
||||||
.PHONY: codegen
|
.PHONY: codegen
|
||||||
codegen: $(SUBDIRS)
|
codegen: $(SUBDIRS)
|
||||||
|
$(GOCMD) install github.com/swaggo/swag/cmd/swag@v1.8.10
|
||||||
cd internal; swag init --propertyStrategy pascalcase --parseInternal --generalInfo server/api.go --output server/docs/
|
cd internal; swag init --propertyStrategy pascalcase --parseInternal --generalInfo server/api.go --output server/docs/
|
||||||
$(GOCMD) fmt internal/server/docs/docs.go
|
$(GOCMD) fmt internal/server/docs/docs.go
|
||||||
|
|
||||||
@@ -75,67 +75,55 @@ clean:
|
|||||||
#< build: Build all executables (architecture depends on build system)
|
#< build: Build all executables (architecture depends on build system)
|
||||||
.PHONY: build
|
.PHONY: build
|
||||||
build: build-dependencies
|
build: build-dependencies
|
||||||
CGO_ENABLED=0 $(GOCMD) build -o $(BUILDDIR)/wg-portal \
|
CGO_ENABLED=1 $(GOCMD) build -o $(BUILDDIR)/wg-portal \
|
||||||
-ldflags "-w -s -extldflags \"-static\" -X 'github.com/h44z/wg-portal/internal/server.Version=${ENV_BUILD_IDENTIFIER}-${ENV_BUILD_VERSION}'" \
|
-ldflags "-w -s -extldflags \"-static\" -X 'github.com/h44z/wg-portal/internal/server.Version=${ENV_BUILD_IDENTIFIER}-${ENV_BUILD_VERSION}'" \
|
||||||
-tags netgo \
|
-tags netgo \
|
||||||
cmd/wg-portal/main.go
|
cmd/wg-portal/main.go
|
||||||
|
|
||||||
|
CGO_ENABLED=0 $(GOCMD) build -o $(BUILDDIR)/hc \
|
||||||
|
-ldflags "-w -s -extldflags \"-static\"" \
|
||||||
|
cmd/hc/main.go
|
||||||
|
|
||||||
#< build-amd64: Build all executables for AMD64
|
#< build-amd64: Build all executables for AMD64
|
||||||
.PHONY: build-amd64
|
.PHONY: build-amd64
|
||||||
build-amd64: build-dependencies
|
build-amd64: build-dependencies
|
||||||
CGO_ENABLED=0 $(GOCMD) build -o $(BUILDDIR)/wg-portal-amd64 \
|
CGO_ENABLED=1 $(GOCMD) build -o $(BUILDDIR)/wg-portal-amd64 \
|
||||||
-ldflags "-w -s -extldflags \"-static\" -X 'github.com/h44z/wg-portal/internal/server.Version=${ENV_BUILD_IDENTIFIER}-${ENV_BUILD_VERSION}'" \
|
-ldflags "-w -s -extldflags \"-static\" -X 'github.com/h44z/wg-portal/internal/server.Version=${ENV_BUILD_IDENTIFIER}-${ENV_BUILD_VERSION}'" \
|
||||||
-tags netgo \
|
-tags netgo \
|
||||||
cmd/wg-portal/main.go
|
cmd/wg-portal/main.go
|
||||||
|
|
||||||
|
CGO_ENABLED=0 $(GOCMD) build -o $(BUILDDIR)/hc-amd64 \
|
||||||
|
-ldflags "-w -s -extldflags \"-static\"" \
|
||||||
|
cmd/hc/main.go
|
||||||
|
|
||||||
#< build-arm64: Build all executables for ARM64
|
#< build-arm64: Build all executables for ARM64
|
||||||
.PHONY: build-arm64
|
.PHONY: build-arm64
|
||||||
build-arm64: build-dependencies
|
build-arm64: build-dependencies
|
||||||
CGO_ENABLED=0 CC=aarch64-linux-gnu-gcc GOOS=linux GOARCH=arm64 $(GOCMD) build -o $(BUILDDIR)/wg-portal-arm64 \
|
CGO_ENABLED=1 CC=aarch64-linux-gnu-gcc GOOS=linux GOARCH=arm64 $(GOCMD) build -o $(BUILDDIR)/wg-portal-arm64 \
|
||||||
-ldflags "-w -s -extldflags \"-static\" -X 'github.com/h44z/wg-portal/internal/server.Version=${ENV_BUILD_IDENTIFIER}-${ENV_BUILD_VERSION}'" \
|
-ldflags "-w -s -extldflags \"-static\" -X 'github.com/h44z/wg-portal/internal/server.Version=${ENV_BUILD_IDENTIFIER}-${ENV_BUILD_VERSION}'" \
|
||||||
-tags netgo \
|
-tags netgo \
|
||||||
cmd/wg-portal/main.go
|
cmd/wg-portal/main.go
|
||||||
|
|
||||||
|
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 $(GOCMD) build -o $(BUILDDIR)/hc-arm64 \
|
||||||
|
-ldflags "-w -s -extldflags \"-static\"" \
|
||||||
|
cmd/hc/main.go
|
||||||
|
|
||||||
#< build-arm: Build all executables for ARM32
|
#< build-arm: Build all executables for ARM32
|
||||||
.PHONY: build-arm
|
.PHONY: build-arm
|
||||||
build-arm: build-dependencies
|
build-arm: build-dependencies
|
||||||
CGO_ENABLED=0 CC=arm-linux-gnueabi-gcc GOOS=linux GOARCH=arm GOARM=7 $(GOCMD) build -o $(BUILDDIR)/wg-portal-arm \
|
CGO_ENABLED=1 CC=arm-linux-gnueabi-gcc GOOS=linux GOARCH=arm GOARM=7 $(GOCMD) build -o $(BUILDDIR)/wg-portal-arm \
|
||||||
-ldflags "-w -s -extldflags \"-static\" -X 'github.com/h44z/wg-portal/internal/server.Version=${ENV_BUILD_IDENTIFIER}-${ENV_BUILD_VERSION}'" \
|
-ldflags "-w -s -extldflags \"-static\" -X 'github.com/h44z/wg-portal/internal/server.Version=${ENV_BUILD_IDENTIFIER}-${ENV_BUILD_VERSION}'" \
|
||||||
-tags netgo \
|
-tags netgo \
|
||||||
cmd/wg-portal/main.go
|
cmd/wg-portal/main.go
|
||||||
|
|
||||||
|
CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=7 $(GOCMD) build -o $(BUILDDIR)/hc-arm \
|
||||||
|
-ldflags "-w -s -extldflags \"-static\"" \
|
||||||
|
cmd/hc/main.go
|
||||||
|
|
||||||
#< build-dependencies: Generate the output directory for compiled executables and download dependencies
|
#< build-dependencies: Generate the output directory for compiled executables and download dependencies
|
||||||
.PHONY: build-dependencies
|
.PHONY: build-dependencies
|
||||||
build-dependencies:
|
build-dependencies:
|
||||||
@$(GOCMD) mod download -x
|
@$(GOCMD) mod download -x
|
||||||
@mkdir -p $(BUILDDIR)
|
@mkdir -p $(BUILDDIR)
|
||||||
cp scripts/wg-portal.service $(BUILDDIR)
|
cp scripts/wg-portal.service $(BUILDDIR)
|
||||||
|
cp scripts/wg-portal.env $(BUILDDIR)
|
||||||
#< frontend: Build Vue.js frontend
|
|
||||||
frontend: frontend-dependencies
|
|
||||||
cd frontend; $(NPMCMD) run build
|
|
||||||
|
|
||||||
#< frontend-dependencies: Generate the output directory for compiled executables and download frontend dependencies
|
|
||||||
.PHONY: frontend-dependencies
|
|
||||||
frontend-dependencies:
|
|
||||||
@mkdir -p $(BUILDDIR)
|
|
||||||
cd frontend; $(NPMCMD) install
|
|
||||||
|
|
||||||
#< build-docker: Build a docker image on the current host system
|
|
||||||
.PHONY: build-docker
|
|
||||||
build-docker:
|
|
||||||
docker build --progress=plain \
|
|
||||||
--build-arg BUILD_IDENTIFIER=${ENV_BUILD_IDENTIFIER} --build-arg BUILD_VERSION=${ENV_BUILD_VERSION} \
|
|
||||||
--build-arg TARGETPLATFORM=unknown . \
|
|
||||||
-t h44z/wg-portal:local
|
|
||||||
|
|
||||||
#< helm-docs: Generate the helm chart documentation
|
|
||||||
.PHONY: helm-docs
|
|
||||||
helm-docs:
|
|
||||||
docker run --rm --volume "${PWD}/deploy:/helm-docs" -u "$$(id -u)" jnorwood/helm-docs -s file
|
|
||||||
|
|
||||||
#< run-mkdocs: Run a local instance of MkDocs
|
|
||||||
.PHONY: run-mkdocs
|
|
||||||
run-mkdocs:
|
|
||||||
python -m venv venv; source venv/bin/activate; pip install mike cairosvg mkdocs-material mkdocs-minify-plugin mkdocs-swagger-ui-tag
|
|
||||||
venv/bin/mkdocs serve
|
|
||||||
|
51
README-RASPBERRYPI.md
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
# WireGuard Portal on Raspberry Pi
|
||||||
|
|
||||||
|
This readme only contains a detailed explanation of how to set up the WireGuard Portal service on a raspberry pi (>= 3).
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
You can either download prebuild binaries from the [release page](https://github.com/h44z/wg-portal/releases) or use Docker images for ARM.
|
||||||
|
If you want to build the binary yourself, use the following building instructions.
|
||||||
|
|
||||||
|
### Building
|
||||||
|
This section describes how to build the WireGuard Portal code.
|
||||||
|
To compile the final binary, use the Makefile provided in the repository.
|
||||||
|
As WireGuard Portal is written in Go, **golang >= 1.18** must be installed prior to building.
|
||||||
|
If you want to cross compile ARM binaries from AMD64 systems, install *arm-linux-gnueabi-gcc* (armv7) or *aarch64-linux-gnu-gcc* (arm64).
|
||||||
|
|
||||||
|
```
|
||||||
|
# for 64 bit OS
|
||||||
|
make build-arm64
|
||||||
|
|
||||||
|
# for 32 bit OS
|
||||||
|
make build-arm
|
||||||
|
```
|
||||||
|
|
||||||
|
The compiled binary and all necessary assets will be located in the dist folder.
|
||||||
|
|
||||||
|
### Service setup
|
||||||
|
|
||||||
|
- Copy the contents from the dist folder (or from the downloaded zip file) to `/opt/wg-portal`. You can choose a different path as well, but make sure to update the systemd service file accordingly.
|
||||||
|
- Update the provided systemd `wg-portal.service` file:
|
||||||
|
- Make sure that the binary matches the system architecture.
|
||||||
|
- There are three pre-build binaries available: wg-portal-**amd64**, wg-portal-**arm64** and wg-portal-**arm**.
|
||||||
|
- For a raspberry pi use the arm binary if you are using armv7l architecture. If armv8 is used, the arm64 version should work.
|
||||||
|
- Make sure that the paths to the binary and the working directory are set correctly (defaults to /opt/wg-portal/wg-portal-amd64):
|
||||||
|
- ConditionPathExists
|
||||||
|
- WorkingDirectory
|
||||||
|
- ExecStart
|
||||||
|
- EnvironmentFile
|
||||||
|
- Update environment variables in the `wg-portal.env` file to fit your needs
|
||||||
|
- Make sure that the binary application file is executable
|
||||||
|
- `sudo chmod +x /opt/wg-portal/wg-portal-*`
|
||||||
|
- Link the system service file to the correct folder:
|
||||||
|
- `sudo ln -s /opt/wg-portal/wg-portal.service /etc/systemd/system/wg-portal.service`
|
||||||
|
- Reload the systemctl daemon:
|
||||||
|
- `sudo systemctl daemon-reload`
|
||||||
|
|
||||||
|
### Manage the service
|
||||||
|
Once the service has been setup, you can simply manage the service using `systemctl`:
|
||||||
|
- Enable on startup: `systemctl enable wg-portal.service`
|
||||||
|
- Start: `systemctl start wg-portal.service`
|
||||||
|
- Stop: `systemctl stop wg-portal.service`
|
||||||
|
- Status: `systemctl status wg-portal.service`
|
277
README.md
@@ -1,67 +1,260 @@
|
|||||||
# WireGuard Portal v2
|
# WireGuard Portal (v1)
|
||||||
|
|
||||||
[](https://github.com/h44z/wg-portal/actions/workflows/docker-publish.yml)
|
[](https://travis-ci.com/h44z/wg-portal)
|
||||||
[](https://opensource.org/licenses/MIT)
|
[](https://opensource.org/licenses/MIT)
|
||||||

|

|
||||||
[](https://goreportcard.com/report/github.com/h44z/wg-portal)
|
[](https://goreportcard.com/report/github.com/h44z/wg-portal)
|
||||||

|

|
||||||

|

|
||||||
[](https://hub.docker.com/r/wgportal/wg-portal/)
|
[](https://hub.docker.com/r/h44z/wg-portal/)
|
||||||
|
|
||||||
## Introduction
|
A simple, web based configuration portal for [WireGuard](https://wireguard.com).
|
||||||
<!-- Text from this line # is included in docs/documentation/overview.md -->
|
|
||||||
**WireGuard Portal** is a simple, web-based configuration portal for [WireGuard](https://wireguard.com) server management.
|
|
||||||
The portal uses the WireGuard [wgctrl](https://github.com/WireGuard/wgctrl-go) library to manage existing VPN
|
The portal uses the WireGuard [wgctrl](https://github.com/WireGuard/wgctrl-go) library to manage existing VPN
|
||||||
interfaces. This allows for the seamless activation or deactivation of new users without disturbing existing VPN
|
interfaces. This allows for seamless activation or deactivation of new users, without disturbing existing VPN
|
||||||
connections.
|
connections.
|
||||||
|
|
||||||
The configuration portal supports using a database (SQLite, MySQL, MsSQL, or Postgres), OAuth or LDAP
|
The configuration portal currently supports using SQLite and MySQL as a user source for authentication and profile data.
|
||||||
(Active Directory or OpenLDAP) as a user source for authentication and profile data.
|
It also supports LDAP (Active Directory or OpenLDAP) as authentication provider.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
* Self-hosted and web based
|
||||||
|
* Automatically select IP from the network pool assigned to client
|
||||||
|
* QR-Code for convenient mobile client configuration
|
||||||
|
* Sent email to client with QR-code and client config
|
||||||
|
* Enable / Disable clients seamlessly
|
||||||
|
* Generation of `wgX.conf` after any modification
|
||||||
|
* IPv6 ready
|
||||||
|
* User authentication (SQLite/MySQL and LDAP)
|
||||||
|
* Dockerized
|
||||||
|
* Responsive template
|
||||||
|
* One single binary
|
||||||
|
* Can be used with existing WireGuard setups
|
||||||
|
* Support for multiple WireGuard interfaces
|
||||||
|
* REST API for management and client deployment
|
||||||
|
* Peer Expiry Feature
|
||||||
|
|
||||||
* Self-hosted - the whole application is a single binary
|

|
||||||
* Responsive multi-language web UI written in Vue.js
|
|
||||||
* Automatically selects IP from the network pool assigned to the client
|
|
||||||
* QR-Code for convenient mobile client configuration
|
|
||||||
* Sends email to the client with QR-code and client config
|
|
||||||
* Enable / Disable clients seamlessly
|
|
||||||
* Generation of wg-quick configuration file (`wgX.conf`) if required
|
|
||||||
* User authentication (database, OAuth, or LDAP), Passkey support
|
|
||||||
* IPv6 ready
|
|
||||||
* Docker ready
|
|
||||||
* Can be used with existing WireGuard setups
|
|
||||||
* Support for multiple WireGuard interfaces
|
|
||||||
* Peer Expiry Feature
|
|
||||||
* Handles route and DNS settings like wg-quick does
|
|
||||||
* Exposes Prometheus metrics for monitoring and alerting
|
|
||||||
* REST API for management and client deployment
|
|
||||||
* Webhook for custom actions on peer, interface, or user updates
|
|
||||||
|
|
||||||
<!-- Text to this line # is included in docs/documentation/overview.md -->
|
## Setup
|
||||||

|
Make sure that your host system has at least one WireGuard interface (for example wg0) available.
|
||||||
|
If you did not start up a WireGuard interface yet, take a look at [wg-quick](https://manpages.debian.org/unstable/wireguard-tools/wg-quick.8.en.html) in order to get started.
|
||||||
|
|
||||||
## Documentation
|
### Docker
|
||||||
|
The easiest way to run WireGuard Portal is to use the [Docker image](https://hub.docker.com/r/wgportal/wg-portal) provided.
|
||||||
|
|
||||||
For the complete documentation visit [wgportal.org](https://wgportal.org).
|
Since the project was accepted by the Docker-Sponsored Open Source Program, the image is now available on Docker Hub:
|
||||||
|
https://hub.docker.com/r/wgportal/wg-portal
|
||||||
|
|
||||||
|
> :warning: **HINT**: the *latest* tag always refers to the master branch and might contain unstable or incompatible code!
|
||||||
|
> For production use a fixed version or use the *stable* tag.
|
||||||
|
|
||||||
|
Docker Compose snippet with some sample configuration values:
|
||||||
|
```
|
||||||
|
version: '3.6'
|
||||||
|
services:
|
||||||
|
wg-portal:
|
||||||
|
image: wgportal/wg-portal:v1
|
||||||
|
container_name: wg-portal
|
||||||
|
restart: unless-stopped
|
||||||
|
cap_add:
|
||||||
|
- NET_ADMIN
|
||||||
|
network_mode: "host"
|
||||||
|
volumes:
|
||||||
|
- /etc/wireguard:/etc/wireguard
|
||||||
|
- ./data:/app/data
|
||||||
|
ports:
|
||||||
|
- '8123:8123'
|
||||||
|
environment:
|
||||||
|
# WireGuard Settings
|
||||||
|
- WG_DEVICES=wg0
|
||||||
|
- WG_DEFAULT_DEVICE=wg0
|
||||||
|
- WG_CONFIG_PATH=/etc/wireguard
|
||||||
|
# Core Settings
|
||||||
|
- EXTERNAL_URL=https://vpn.company.com
|
||||||
|
- WEBSITE_TITLE=WireGuard VPN
|
||||||
|
- COMPANY_NAME=Your Company Name
|
||||||
|
- ADMIN_USER=admin@domain.com
|
||||||
|
- ADMIN_PASS=supersecret
|
||||||
|
# Mail Settings
|
||||||
|
- MAIL_FROM=WireGuard VPN <noreply+wireguard@company.com>
|
||||||
|
- EMAIL_HOST=10.10.10.10
|
||||||
|
- EMAIL_PORT=25
|
||||||
|
# LDAP Settings
|
||||||
|
- LDAP_ENABLED=true
|
||||||
|
- LDAP_URL=ldap://srv-ad01.company.local:389
|
||||||
|
- LDAP_BASEDN=DC=COMPANY,DC=LOCAL
|
||||||
|
- LDAP_USER=ldap_wireguard@company.local
|
||||||
|
- LDAP_PASSWORD=supersecretldappassword
|
||||||
|
- LDAP_ADMIN_GROUP=CN=WireGuardAdmins,OU=Users,DC=COMPANY,DC=LOCAL
|
||||||
|
```
|
||||||
|
Please note that mapping ```/etc/wireguard``` to ```/etc/wireguard``` inside the docker, will erase your host's current configuration.
|
||||||
|
If needed, please make sure to back up your files from ```/etc/wireguard```.
|
||||||
|
For a full list of configuration options take a look at the source file [internal/server/configuration.go](internal/server/configuration.go#L58).
|
||||||
|
|
||||||
|
### Standalone
|
||||||
|
For a standalone application, use the Makefile provided in the repository to build the application. Go version 1.18 or higher has to be installed to build WireGuard Portal.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
# show all possible make commands
|
||||||
|
make
|
||||||
|
|
||||||
|
# build wg-portal for current system architecture
|
||||||
|
make build
|
||||||
|
```
|
||||||
|
|
||||||
|
The compiled binary will be located in the dist folder.
|
||||||
|
A detailed description for using this software with a raspberry pi can be found in the [README-RASPBERRYPI.md](README-RASPBERRYPI.md).
|
||||||
|
|
||||||
|
To build the Docker image, Docker (> 20.x) with buildx is required. If you want to build cross-platform images, you need to install qemu.
|
||||||
|
On arch linux for example install: `docker-buildx qemu-user-static qemu-user-static-binfmt`.
|
||||||
|
|
||||||
|
Once the Docker setup is completed, create a new buildx builder:
|
||||||
|
```shell
|
||||||
|
docker buildx create --name wgportalbuilder --platform linux/arm/v7,linux/arm64,linux/amd64
|
||||||
|
docker buildx use wgportalbuilder
|
||||||
|
docker buildx inspect --bootstrap
|
||||||
|
```
|
||||||
|
Now you can compile the Docker image:
|
||||||
|
```shell
|
||||||
|
# multi platform build, can only be exported to tar archives
|
||||||
|
docker buildx build --platform linux/arm/v7,linux/arm64,linux/amd64 --output type=local,dest=docker_images \
|
||||||
|
--build-arg BUILD_IDENTIFIER=dev --build-arg BUILD_VERSION=0.1 -t h44z/wg-portal .
|
||||||
|
|
||||||
|
|
||||||
|
# image for current platform only (same as docker build)
|
||||||
|
docker buildx build --load \
|
||||||
|
--build-arg BUILD_IDENTIFIER=dev --build-arg BUILD_VERSION=0.1 -t h44z/wg-portal .
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
You can configure WireGuard Portal using either environment variables or a yaml configuration file.
|
||||||
|
The filepath of the yaml configuration file defaults to **config.yml** in the working directory of the executable.
|
||||||
|
It is possible to override the configuration filepath using the environment variable **CONFIG_FILE**.
|
||||||
|
For example: `CONFIG_FILE=/home/test/config.yml ./wg-portal-amd64`.
|
||||||
|
|
||||||
|
### Configuration Options
|
||||||
|
The following configuration options are available:
|
||||||
|
|
||||||
|
| environment | yaml | yaml_parent | default_value | description |
|
||||||
|
|----------------------------|-------------------------|-------------|-----------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
|
| LISTENING_ADDRESS | listeningAddress | core | :8123 | The address on which the web server is listening. Optional IP address and port, e.g.: 127.0.0.1:8080. |
|
||||||
|
| EXTERNAL_URL | externalUrl | core | http://localhost:8123 | The external URL where the web server is reachable. This link is used in emails that are created by the WireGuard Portal. |
|
||||||
|
| WEBSITE_TITLE | title | core | WireGuard VPN | The website title. |
|
||||||
|
| COMPANY_NAME | company | core | WireGuard Portal | The company name (for branding). |
|
||||||
|
| MAIL_FROM | mailFrom | core | WireGuard VPN <noreply@company.com> | The email address from which emails are sent. |
|
||||||
|
| LOGO_URL | logoUrl | core | /img/header-logo.png | The logo displayed in the page's header. |
|
||||||
|
| ADMIN_USER | adminUser | core | admin@wgportal.local | The administrator user. Must be a valid email address. |
|
||||||
|
| ADMIN_PASS | adminPass | core | wgportal | The administrator password. If unchanged, a random password will be set on first startup. |
|
||||||
|
| EDITABLE_KEYS | editableKeys | core | true | Allow to edit key-pairs in the UI. |
|
||||||
|
| CREATE_DEFAULT_PEER | createDefaultPeer | core | false | If an LDAP user logs in for the first time, a new WireGuard peer will be created on the WG_DEFAULT_DEVICE if this option is enabled. |
|
||||||
|
| SELF_PROVISIONING | selfProvisioning | core | false | Allow registered users to automatically create peers via the RESTful API. |
|
||||||
|
| WG_EXPORTER_FRIENDLY_NAMES | wgExporterFriendlyNames | core | false | Enable integration with [prometheus_wireguard_exporter friendly name](https://github.com/MindFlavor/prometheus_wireguard_exporter#friendly-tags). |
|
||||||
|
| LDAP_ENABLED | ldapEnabled | core | false | Enable or disable the LDAP backend. |
|
||||||
|
| SESSION_SECRET | sessionSecret | core | secret | Use a custom secret to encrypt session data. |
|
||||||
|
| BACKGROUND_TASK_INTERVAL | backgroundTaskInterval | core | 900 | The interval (in seconds) for the background tasks (like peer expiry check). |
|
||||||
|
| EXPIRY_REENABLE | expiryReEnable | core | false | Reactivate expired peers if the expiration date is in the future. |
|
||||||
|
| DATABASE_TYPE | typ | database | sqlite | Either mysql or sqlite. |
|
||||||
|
| DATABASE_HOST | host | database | | The mysql server address. |
|
||||||
|
| DATABASE_PORT | port | database | | The mysql server port. |
|
||||||
|
| DATABASE_NAME | database | database | data/wg_portal.db | For sqlite database: the database file-path, otherwise the database name. |
|
||||||
|
| DATABASE_USERNAME | user | database | | The mysql user. |
|
||||||
|
| DATABASE_PASSWORD | password | database | | The mysql password. |
|
||||||
|
| EMAIL_HOST | host | email | 127.0.0.1 | The email server address. |
|
||||||
|
| EMAIL_PORT | port | email | 25 | The email server port. |
|
||||||
|
| EMAIL_TLS | tls | email | false | Use STARTTLS. DEPRECATED: use EMAIL_ENCRYPTION instead. |
|
||||||
|
| EMAIL_ENCRYPTION | encryption | email | none | Either none, tls or starttls. |
|
||||||
|
| EMAIL_CERT_VALIDATION | certcheck | email | false | Validate the email server certificate. |
|
||||||
|
| EMAIL_USERNAME | user | email | | An optional username for SMTP authentication. |
|
||||||
|
| EMAIL_PASSWORD | pass | email | | An optional password for SMTP authentication. |
|
||||||
|
| EMAIL_AUTHTYPE | auth | email | plain | Either plain, login or crammd5. If username and password are empty, this value is ignored. |
|
||||||
|
| WG_DEVICES | devices | wg | wg0 | A comma separated list of WireGuard devices. |
|
||||||
|
| WG_DEFAULT_DEVICE | defaultDevice | wg | wg0 | This device is used for auto-created peers (if CREATE_DEFAULT_PEER is enabled). |
|
||||||
|
| WG_CONFIG_PATH | configDirectory | wg | /etc/wireguard | If set, interface configuration updates will be written to this path, filename: <devicename>.conf. |
|
||||||
|
| MANAGE_IPS | manageIPAddresses | wg | true | Handle IP address setup of interface, only available on linux. |
|
||||||
|
| USER_MANAGE_PEERS | userManagePeers | wg | false | Logged in user can create or update peers (partially). |
|
||||||
|
| LDAP_URL | url | ldap | ldap://srv-ad01.company.local:389 | The LDAP server url. |
|
||||||
|
| LDAP_STARTTLS | startTLS | ldap | true | Use STARTTLS. |
|
||||||
|
| LDAP_CERT_VALIDATION | certcheck | ldap | false | Validate the LDAP server certificate. |
|
||||||
|
| LDAP_BASEDN | dn | ldap | DC=COMPANY,DC=LOCAL | The base DN for searching users. |
|
||||||
|
| LDAP_USER | user | ldap | company\\\\ldap_wireguard | The bind user. |
|
||||||
|
| LDAP_PASSWORD | pass | ldap | SuperSecret | The bind password. |
|
||||||
|
| LDAP_LOGIN_FILTER | loginFilter | ldap | (&(objectClass=organizationalPerson)(mail={{login_identifier}})(!userAccountControl:1.2.840.113556.1.4.803:=2)) | {{login_identifier}} will be replaced with the login email address. |
|
||||||
|
| LDAP_SYNC_FILTER | syncFilter | ldap | (&(objectClass=organizationalPerson)(!userAccountControl:1.2.840.113556.1.4.803:=2)(mail=*)) | The filter string for the LDAP synchronization service. Users matching this filter will be synchronized with the WireGuard Portal database. |
|
||||||
|
| LDAP_SYNC_GROUP_FILTER | syncGroupFilter | ldap | | The filter string for the LDAP groups, for example: (objectClass=group). The groups are used to recursively check for admin group member ship of users. |
|
||||||
|
| LDAP_ADMIN_GROUP | adminGroup | ldap | CN=WireGuardAdmins,OU=_O_IT,DC=COMPANY,DC=LOCAL | Users in this group are marked as administrators. |
|
||||||
|
| LDAP_ATTR_EMAIL | attrEmail | ldap | mail | User email attribute. |
|
||||||
|
| LDAP_ATTR_FIRSTNAME | attrFirstname | ldap | givenName | User firstname attribute. |
|
||||||
|
| LDAP_ATTR_LASTNAME | attrLastname | ldap | sn | User lastname attribute. |
|
||||||
|
| LDAP_ATTR_PHONE | attrPhone | ldap | telephoneNumber | User phone number attribute. |
|
||||||
|
| LDAP_ATTR_GROUPS | attrGroups | ldap | memberOf | User groups attribute. |
|
||||||
|
| LDAP_CERT_CONN | ldapCertConn | ldap | false | Allow connection with certificate against LDAP server without user/password |
|
||||||
|
| LDAPTLS_CERT | ldapTlsCert | ldap | | The LDAP cert's path |
|
||||||
|
| LDAPTLS_KEY | ldapTlsKey | ldap | | The LDAP key's path |
|
||||||
|
| LOG_LEVEL | | | debug | Specify log level, one of: trace, debug, info, off. |
|
||||||
|
| LOG_JSON | | | false | Format log output as JSON. |
|
||||||
|
| LOG_COLOR | | | true | Colorize log output. |
|
||||||
|
| CONFIG_FILE | | | config.yml | The config file path. |
|
||||||
|
|
||||||
|
### Sample yaml configuration
|
||||||
|
config.yml:
|
||||||
|
```yaml
|
||||||
|
core:
|
||||||
|
listeningAddress: :8123
|
||||||
|
externalUrl: https://wg-test.test.com
|
||||||
|
adminUser: test@test.com
|
||||||
|
adminPass: test
|
||||||
|
editableKeys: true
|
||||||
|
createDefaultPeer: false
|
||||||
|
ldapEnabled: true
|
||||||
|
mailFrom: WireGuard VPN <noreply@test.com>
|
||||||
|
ldap:
|
||||||
|
url: ldap://10.10.10.10:389
|
||||||
|
dn: DC=test,DC=test
|
||||||
|
startTLS: false
|
||||||
|
user: wireguard@test.test
|
||||||
|
pass: test
|
||||||
|
adminGroup: CN=WireGuardAdmins,CN=Users,DC=test,DC=test
|
||||||
|
database:
|
||||||
|
typ: sqlite
|
||||||
|
database: data/wg_portal.db
|
||||||
|
email:
|
||||||
|
host: smtp.gmail.com
|
||||||
|
port: 587
|
||||||
|
tls: true
|
||||||
|
user: test@gmail.com
|
||||||
|
pass: topsecret
|
||||||
|
wg:
|
||||||
|
devices:
|
||||||
|
- wg0
|
||||||
|
- wg1
|
||||||
|
defaultDevice: wg0
|
||||||
|
configDirectory: /etc/wireguard
|
||||||
|
manageIPAddresses: true
|
||||||
|
```
|
||||||
|
|
||||||
|
### RESTful API
|
||||||
|
WireGuard Portal offers a RESTful API to interact with.
|
||||||
|
The API is documented using OpenAPI 2.0, the Swagger UI can be found
|
||||||
|
under the URL `http://<your wg-portal ip/domain>/swagger/index.html?displayOperationId=true`.
|
||||||
|
|
||||||
|
The [API's unittesting](tests/test_API.py) may serve as an example how to make use of the API with python3 & pyswagger.
|
||||||
|
|
||||||
## What is out of scope
|
## What is out of scope
|
||||||
|
* Creating or removing WireGuard (wgX) interfaces.
|
||||||
* Automatic generation or application of any `iptables` or `nftables` rules.
|
* Generation or application of any `iptables` or `nftables` rules.
|
||||||
* Support for operating systems other than linux.
|
* Setting up or changing IP-addresses of the WireGuard interface on operating systems other than linux.
|
||||||
* Automatic import of private keys of an existing WireGuard setup.
|
* Importing private keys of an existing WireGuard setup.
|
||||||
|
|
||||||
## Application stack
|
## Application stack
|
||||||
|
|
||||||
* [wgctrl-go](https://github.com/WireGuard/wgctrl-go) and [netlink](https://github.com/vishvananda/netlink) for interface handling
|
* [Gin, HTTP web framework written in Go](https://github.com/gin-gonic/gin)
|
||||||
* [Bootstrap](https://getbootstrap.com/), for the HTML templates
|
* [go-template, data-driven templates for generating textual output](https://golang.org/pkg/text/template/)
|
||||||
* [Vue.js](https://vuejs.org/), for the frontend
|
* [Bootstrap, for the HTML templates](https://getbootstrap.com/)
|
||||||
|
* [JQuery, for some nice JavaScript effects ;)](https://jquery.com/)
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
* MIT License. [MIT](LICENSE.txt) or <https://opensource.org/licenses/MIT>
|
* MIT License. [MIT](LICENSE.txt) or https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
|
|
||||||
> [!IMPORTANT]
|
This project was inspired by [wg-gen-web](https://github.com/vx3r/wg-gen-web).
|
||||||
> Since the project was accepted by the Docker-Sponsored Open Source Program, the Docker image location has moved to [wgportal/wg-portal](https://hub.docker.com/r/wgportal/wg-portal).
|
|
||||||
> Please update the Docker image from **h44z/wg-portal** to **wgportal/wg-portal**.
|
|
||||||
|
33
SECURITY.md
@@ -1,33 +0,0 @@
|
|||||||
# Security Policy
|
|
||||||
|
|
||||||
If you believe you've found a security issue in one of the supported versions of *WireGuard Portal*, please report it to us as described below.
|
|
||||||
|
|
||||||
## Supported Versions
|
|
||||||
|
|
||||||
| Version | Supported |
|
|
||||||
|---------|--------------------|
|
|
||||||
| v2.x | :white_check_mark: |
|
|
||||||
| v1.x | :white_check_mark: |
|
|
||||||
|
|
||||||
## Reporting a Vulnerability
|
|
||||||
|
|
||||||
Please do not report security vulnerabilities through public GitHub issues.
|
|
||||||
|
|
||||||
Instead, we encourage you to submit a report through GitHub [private vulnerability reporting](https://github.com/h44z/wg-portal/security).
|
|
||||||
If you prefer to submit a report without logging in to GitHub, please email *info (at) wgportal.org*.
|
|
||||||
We will respond as soon as possible, but as only two people currently maintain this project, we cannot guarantee specific response times.
|
|
||||||
|
|
||||||
We prefer all communications to be in English.
|
|
||||||
Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue:
|
|
||||||
|
|
||||||
- Type of issue (e.g. SQL injection, cross-site scripting, ...)
|
|
||||||
- Full paths of source file(s) related to the manifestation of the issue
|
|
||||||
- The location of the affected source code (tag/branch/commit or direct URL)
|
|
||||||
- Any special configuration required to reproduce the issue
|
|
||||||
- Step-by-step instructions to reproduce the issue
|
|
||||||
- Proof-of-concept or exploit code (if possible)
|
|
||||||
- Impact of the issue, including how an attacker might exploit the issue
|
|
||||||
|
|
||||||
This information will help us triage your report more quickly.
|
|
||||||
|
|
||||||
Thank you for helping keep *WireGuard Portal* and its users safe!
|
|
190
assets/css/_bootswatch.scss
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
// Lux 4.5.3
|
||||||
|
// Bootswatch
|
||||||
|
|
||||||
|
|
||||||
|
// Variables ===================================================================
|
||||||
|
|
||||||
|
$web-font-path: "https://fonts.googleapis.com/css2?family=Nunito+Sans:wght@400;600&display=swap" !default;
|
||||||
|
@import url($web-font-path);
|
||||||
|
|
||||||
|
// Navbar ======================================================================
|
||||||
|
|
||||||
|
.navbar {
|
||||||
|
font-size: $font-size-sm;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-weight: 600;
|
||||||
|
|
||||||
|
&-nav {
|
||||||
|
.nav-link {
|
||||||
|
padding-top: .715rem;
|
||||||
|
padding-bottom: .715rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-brand {
|
||||||
|
margin-right: 2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-primary {
|
||||||
|
background-color: theme-color("primary") !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-light {
|
||||||
|
border: 1px solid rgba(0, 0, 0, .1);
|
||||||
|
|
||||||
|
&.navbar-fixed-top {
|
||||||
|
border-width: 0 0 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.navbar-bottom-top {
|
||||||
|
border-width: 1px 0 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item {
|
||||||
|
margin-right: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Buttons =====================================================================
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
font-size: $font-size-sm;
|
||||||
|
text-transform: uppercase;
|
||||||
|
|
||||||
|
&-sm {
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-warning {
|
||||||
|
&,
|
||||||
|
&:hover,
|
||||||
|
&:not([disabled]):not(.disabled):active,
|
||||||
|
&:focus {
|
||||||
|
color: $white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-outline-secondary {
|
||||||
|
border-color: $gray-600;
|
||||||
|
color: $gray-600;
|
||||||
|
|
||||||
|
&:not([disabled]):not(.disabled):hover,
|
||||||
|
&:not([disabled]):not(.disabled):focus,
|
||||||
|
&:not([disabled]):not(.disabled):active {
|
||||||
|
background-color: $gray-400;
|
||||||
|
border-color: $gray-400;
|
||||||
|
color: $white;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not([disabled]):not(.disabled):focus {
|
||||||
|
box-shadow: 0 0 0 .2rem rgba($gray-400, .5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[class*="btn-outline-"] {
|
||||||
|
border-width: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.border-secondary {
|
||||||
|
border: 1px solid $gray-400 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Typography ==================================================================
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-weight: 200;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5,
|
||||||
|
h6 {
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-secondary {
|
||||||
|
color: $body-color !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tables ======================================================================
|
||||||
|
|
||||||
|
th {
|
||||||
|
font-size: $font-size-sm;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table {
|
||||||
|
th,
|
||||||
|
td {
|
||||||
|
padding: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-sm {
|
||||||
|
th,
|
||||||
|
td {
|
||||||
|
padding: .75rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Forms =======================================================================
|
||||||
|
|
||||||
|
.custom-switch {
|
||||||
|
.custom-control-label {
|
||||||
|
&::after {
|
||||||
|
top: add(.15625rem, 2px);
|
||||||
|
left: add(-2.25rem, 2px);
|
||||||
|
width: subtract(1rem, 4px);
|
||||||
|
height: subtract(1rem, 4px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Navs ========================================================================
|
||||||
|
|
||||||
|
.dropdown-menu {
|
||||||
|
font-size: $font-size-sm;
|
||||||
|
text-transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Indicators ==================================================================
|
||||||
|
|
||||||
|
.badge {
|
||||||
|
padding-top: .28rem;
|
||||||
|
|
||||||
|
&-pill {
|
||||||
|
border-radius: 10rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Containers ==================================================================
|
||||||
|
|
||||||
|
.list-group-item {
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5,
|
||||||
|
h6,
|
||||||
|
.h1,
|
||||||
|
.h2,
|
||||||
|
.h3,
|
||||||
|
.h4,
|
||||||
|
.h5,
|
||||||
|
.h6 {
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
&-title,
|
||||||
|
&-header {
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
}
|
106
assets/css/_variables.scss
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
// Lux 4.5.3
|
||||||
|
// Bootswatch
|
||||||
|
|
||||||
|
//
|
||||||
|
// Color system
|
||||||
|
//
|
||||||
|
|
||||||
|
$white: #fff !default;
|
||||||
|
$gray-100: #f8f9fa !default;
|
||||||
|
$gray-200: #f7f7f9 !default;
|
||||||
|
$gray-300: #eceeef !default;
|
||||||
|
$gray-400: #ced4da !default;
|
||||||
|
$gray-500: #adb5bd !default;
|
||||||
|
$gray-600: #919aa1 !default;
|
||||||
|
$gray-700: #55595c !default;
|
||||||
|
$gray-800: #343a40 !default;
|
||||||
|
$gray-900: #1a1a1a !default;
|
||||||
|
$black: #000 !default;
|
||||||
|
|
||||||
|
$blue: #007bff !default;
|
||||||
|
$indigo: #6610f2 !default;
|
||||||
|
$purple: #6f42c1 !default;
|
||||||
|
$pink: #e83e8c !default;
|
||||||
|
$red: #d9534f !default;
|
||||||
|
$orange: #fd7e14 !default;
|
||||||
|
$yellow: #f0ad4e !default;
|
||||||
|
$green: #4bbf73 !default;
|
||||||
|
$teal: #20c997 !default;
|
||||||
|
$cyan: #1f9bcf !default;
|
||||||
|
|
||||||
|
$primary: $gray-900 !default;
|
||||||
|
$secondary: $white !default;
|
||||||
|
$success: $green !default;
|
||||||
|
$info: $cyan !default;
|
||||||
|
$warning: $yellow !default;
|
||||||
|
$danger: $red !default;
|
||||||
|
$light: $white !default;
|
||||||
|
$dark: $gray-800 !default;
|
||||||
|
|
||||||
|
$yiq-contrasted-threshold: 185 !default;
|
||||||
|
|
||||||
|
// Options
|
||||||
|
|
||||||
|
$enable-rounded: false !default;
|
||||||
|
|
||||||
|
// Body
|
||||||
|
|
||||||
|
$body-color: $gray-700 !default;
|
||||||
|
|
||||||
|
// Fonts
|
||||||
|
|
||||||
|
// stylelint-disable-next-line value-keyword-case
|
||||||
|
$font-family-sans-serif: "Nunito Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol" !default;
|
||||||
|
$font-size-base: .875rem !default;
|
||||||
|
$h1-font-size: 2rem !default;
|
||||||
|
$h2-font-size: 1.75rem !default;
|
||||||
|
$h3-font-size: 1.5rem !default;
|
||||||
|
$h4-font-size: 1.25rem !default;
|
||||||
|
$h5-font-size: 1rem !default;
|
||||||
|
$h6-font-size: .75rem !default;
|
||||||
|
$headings-font-weight: 600 !default;
|
||||||
|
$headings-color: $gray-900 !default;
|
||||||
|
|
||||||
|
// Tables
|
||||||
|
|
||||||
|
$table-border-color: rgba(0, 0, 0, .05) !default;
|
||||||
|
|
||||||
|
// Buttons + Forms
|
||||||
|
|
||||||
|
$input-btn-border-width: 0 !default;
|
||||||
|
|
||||||
|
// Buttons
|
||||||
|
|
||||||
|
$btn-line-height: 1.5rem !default;
|
||||||
|
$input-btn-padding-y: .75rem !default;
|
||||||
|
$input-btn-padding-x: 1.5rem !default;
|
||||||
|
$input-btn-padding-y-sm: .5rem !default;
|
||||||
|
$input-btn-padding-x-sm: 1rem !default;
|
||||||
|
$input-btn-padding-y-lg: 2rem !default;
|
||||||
|
$input-btn-padding-x-lg: 2rem !default;
|
||||||
|
$btn-font-weight: 600 !default;
|
||||||
|
|
||||||
|
// Forms
|
||||||
|
|
||||||
|
$input-line-height: 1.5 !default;
|
||||||
|
$input-bg: $gray-200 !default;
|
||||||
|
$input-disabled-bg: $gray-300 !default;
|
||||||
|
$input-group-addon-bg: $gray-300 !default;
|
||||||
|
|
||||||
|
// Navbar
|
||||||
|
|
||||||
|
$navbar-padding-y: 1.5rem !default;
|
||||||
|
$navbar-dark-hover-color: $white !default;
|
||||||
|
$navbar-light-color: rgba($black, .3) !default;
|
||||||
|
$navbar-light-hover-color: $gray-900 !default;
|
||||||
|
$navbar-light-active-color: $gray-900 !default;
|
||||||
|
|
||||||
|
// Pagination
|
||||||
|
|
||||||
|
$pagination-border-color: transparent !default;
|
||||||
|
$pagination-hover-border-color: $pagination-border-color !default;
|
||||||
|
$pagination-disabled-border-color: $pagination-border-color !default;
|
||||||
|
|
||||||
|
// Breadcrumbs
|
||||||
|
|
||||||
|
$breadcrumb-bg: transparent !default;
|
5
assets/css/bootstrap-tokenfield.min.css
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
/*!
|
||||||
|
* bootstrap-tokenfield
|
||||||
|
* https://github.com/sliptree/bootstrap-tokenfield
|
||||||
|
* Copyright 2013-2014 Sliptree and other contributors; Licensed MIT
|
||||||
|
*/@-webkit-keyframes blink{0%{border-color:#ededed}100%{border-color:#b94a48}}@-moz-keyframes blink{0%{border-color:#ededed}100%{border-color:#b94a48}}@keyframes blink{0%{border-color:#ededed}100%{border-color:#b94a48}}.tokenfield{height:auto;min-height:34px;padding-bottom:0}.tokenfield.focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}.tokenfield .token{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;display:inline-block;border:1px solid #d9d9d9;background-color:#ededed;white-space:nowrap;margin:-1px 5px 5px 0;height:22px;vertical-align:top;cursor:default}.tokenfield .token:hover{border-color:#b9b9b9}.tokenfield .token.active{border-color:#52a8ec;border-color:rgba(82,168,236,.8)}.tokenfield .token.duplicate{border-color:#ebccd1;-webkit-animation-name:blink;animation-name:blink;-webkit-animation-duration:.1s;animation-duration:.1s;-webkit-animation-direction:normal;animation-direction:normal;-webkit-animation-timing-function:ease;animation-timing-function:ease;-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite}.tokenfield .token.invalid{background:0 0;border:1px solid transparent;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;border-bottom:1px dotted #d9534f}.tokenfield .token.invalid.active{background:#ededed;border:1px solid #ededed;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.tokenfield .token .token-label{display:inline-block;overflow:hidden;text-overflow:ellipsis;padding-left:4px;vertical-align:top}.tokenfield .token .close{font-family:Arial;display:inline-block;line-height:100%;font-size:1.1em;line-height:1.49em;margin-left:5px;float:none;height:100%;vertical-align:top;padding-right:4px}.tokenfield .token-input{background:0 0;width:60px;min-width:60px;border:0;height:20px;padding:0;margin-bottom:6px;-webkit-box-shadow:none;box-shadow:none}.tokenfield .token-input:focus{border-color:transparent;outline:0;-webkit-box-shadow:none;box-shadow:none}.tokenfield.disabled{cursor:not-allowed;background-color:#eee}.tokenfield.disabled .token-input{cursor:not-allowed}.tokenfield.disabled .token:hover{cursor:not-allowed;border-color:#d9d9d9}.tokenfield.disabled .token:hover .close{cursor:not-allowed;opacity:.2;filter:alpha(opacity=20)}.has-warning .tokenfield.focus{border-color:#66512c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b}.has-error .tokenfield.focus{border-color:#843534;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483}.has-success .tokenfield.focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168}.tokenfield.input-sm,.input-group-sm .tokenfield{min-height:30px;padding-bottom:0}.input-group-sm .token,.tokenfield.input-sm .token{height:20px;margin-bottom:4px}.input-group-sm .token-input,.tokenfield.input-sm .token-input{height:18px;margin-bottom:5px}.tokenfield.input-lg,.input-group-lg .tokenfield{height:auto;min-height:45px;padding-bottom:4px}.input-group-lg .token,.tokenfield.input-lg .token{height:25px}.input-group-lg .token-label,.tokenfield.input-lg .token-label{line-height:23px}.input-group-lg .token .close,.tokenfield.input-lg .token .close{line-height:1.3em}.input-group-lg .token-input,.tokenfield.input-lg .token-input{height:23px;line-height:23px;margin-bottom:6px;vertical-align:top}.tokenfield.rtl{direction:rtl;text-align:right}.tokenfield.rtl .token{margin:-1px 0 5px 5px}.tokenfield.rtl .token .token-label{padding-left:0;padding-right:4px}
|
118
assets/css/custom.css
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
/* THEME STYLE */
|
||||||
|
pre{background:#f7f7f9}iframe{overflow:hidden;border:none}@media (min-width: 768px){body>.navbar-transparent{box-shadow:none}body>.navbar-transparent .navbar-nav>.open>a{box-shadow:none}}#home,#help{font-size:.9rem}#home .navbar,#help .navbar{background:#349aed;background:linear-gradient(145deg, #349aed 50%, #34d8ed 100%);transition:box-shadow 200ms ease-in}#home .navbar-transparent,#help .navbar-transparent{background:none !important;box-shadow:none}#home .navbar-brand .nav-link,#help .navbar-brand .nav-link{display:inline-block;margin-right:-30px}#home .nav-link,#help .nav-link{text-transform:uppercase;font-weight:500;color:#fff}#home{padding-top:0}#home .btn{padding:.6rem .55rem .5rem;box-shadow:none;font-size:.7rem;font-weight:500}.bs-docs-section{margin-top:4em}.bs-docs-section .page-header h1{padding:2rem 0;font-size:3rem}.dropdown-menu.show[aria-labelledby="themes"]{display:-ms-flexbox;display:flex;width:420px;-ms-flex-wrap:wrap;flex-wrap:wrap}.dropdown-menu.show[aria-labelledby="themes"] .dropdown-item{width:33.333%}.dropdown-menu.show[aria-labelledby="themes"] .dropdown-item:first-child{width:100%}.bs-component{position:relative}.bs-component+.bs-component{margin-top:1rem}.bs-component .card{margin-bottom:1rem}.bs-component .modal{position:relative;top:auto;right:auto;left:auto;bottom:auto;z-index:1;display:block}.bs-component .modal-dialog{width:90%}.bs-component .popover{position:relative;display:inline-block;width:220px;margin:20px}.source-button{display:none;position:absolute;top:0;right:0;z-index:100;font-weight:700}.source-button:hover{cursor:pointer}.bs-component:hover .source-button{display:block}#source-modal pre{max-height:calc(100vh - 11rem);background-color:rgba(0,0,0,0.7);color:rgba(255,255,255,0.7)}.nav-tabs{margin-bottom:15px}.progress{margin-bottom:10px}#footer{margin:5em 0}#footer li{float:left;margin-right:1.5em;margin-bottom:1.5em}#footer p{clear:left;margin-bottom:0}.splash{padding:12em 0 6em;background:#349aed;background:linear-gradient(145deg, #349aed 50%, #34d8ed 100%);color:#fff;text-align:center}.splash .logo{width:160px}.splash h1{font-size:3em;color:#fff}.splash #social{margin:2em 0 3em}.splash .alert{margin:2em 0;border:none}.splash .sponsor a{color:#fff}.section-tout{padding:6em 0 1em;border-bottom:1px solid rgba(0,0,0,0.05);background-color:#eaf1f1;text-align:center}.section-tout .icon{display:-ms-flexbox;display:flex;-ms-flex-pack:center;justify-content:center;-ms-flex-align:center;align-items:center;width:80px;height:80px;margin:0 auto 1rem;background:#349aed;background:linear-gradient(145deg, #3b9cea 50%, #3db8eb 100%);border-radius:50%;font-size:2rem;color:rgba(0,0,0,0.5)}.section-tout p{margin-bottom:5em}.section-preview{padding:4em 0}.section-preview .preview{margin-bottom:4em;background-color:#eaf1f1}.section-preview .preview .image{position:relative}.section-preview .preview .image::before{box-shadow:inset 0 0 0 1px rgba(0,0,0,0.1);position:absolute;top:0;left:0;width:100%;height:100%;content:"";pointer-events:none}.section-preview .preview .options{padding:2em;border:1px solid rgba(0,0,0,0.05);border-top:none;text-align:center}.section-preview .preview .options p{margin-bottom:2em}.section-preview .dropdown-menu{text-align:left}.section-preview .lead{margin-bottom:2em}.sponsor #carbonads{max-width:240px;margin:0 auto}.sponsor .carbon-text{display:block;margin-top:1em;font-size:12px}.sponsor .carbon-poweredby{float:right;margin-top:1em;font-size:10px}@media (max-width: 767px){.splash{padding-top:8em}.splash .logo{width:100px}.splash h1{font-size:2em}#banner{margin-bottom:2em;text-align:center}}
|
||||||
|
|
||||||
|
/* CUSTOM STYLE */
|
||||||
|
|
||||||
|
/* Start collapsable table
|
||||||
|
-------------------------------------------------- */
|
||||||
|
|
||||||
|
.hiddenRow, .hiddenCell {
|
||||||
|
padding: 0px!important;
|
||||||
|
border-top: 0px!important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapsedRow .col-md-6{
|
||||||
|
display:inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapsedRow {
|
||||||
|
padding: 10px 0px;
|
||||||
|
border-top: 1px solid lightgray;
|
||||||
|
margin-left: 0px;
|
||||||
|
margin-right: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapse-indicator {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapse-indicator:after {
|
||||||
|
font-weight: 900;
|
||||||
|
font-family: "Font Awesome 5 Free";
|
||||||
|
content: "\f056";
|
||||||
|
}
|
||||||
|
.collapse-indicator.collapsed:after {
|
||||||
|
font-weight: 900;
|
||||||
|
font-family: "Font Awesome 5 Free";
|
||||||
|
content: "\f055";
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --------------------------------------------------
|
||||||
|
End collapsable table*/
|
||||||
|
|
||||||
|
.jumbotron-home {
|
||||||
|
padding: 1rem 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 576px) {
|
||||||
|
.jumbotron-home {
|
||||||
|
padding: 2rem 2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1440px) {
|
||||||
|
.container, .container-lg, .container-md, .container-sm, .container-xl {
|
||||||
|
max-width: 1400px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.device-status-table {
|
||||||
|
font-size: small;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar {
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-brand > img {
|
||||||
|
height: 2rem;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.disabled-peer {
|
||||||
|
color: #d03131;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expiring-peer {
|
||||||
|
color: #d09d12;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tokenfield .token {
|
||||||
|
border-radius: 0px;
|
||||||
|
border: 1px solid #1a1a1a;
|
||||||
|
color: #1a1a1a;
|
||||||
|
background-color: #f7f7f9;
|
||||||
|
margin: -4px 5px 5px 0;
|
||||||
|
height: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group.required label:after {
|
||||||
|
content:"*";
|
||||||
|
color:red;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.advanced-settings:before {
|
||||||
|
content: "Hide";
|
||||||
|
}
|
||||||
|
|
||||||
|
a.advanced-settings.collapsed:before {
|
||||||
|
content: "Show";
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group.global-config label:after, .custom-control.global-config label:after {
|
||||||
|
content: "g";
|
||||||
|
color: #0057bb;
|
||||||
|
font-size: xx-small;
|
||||||
|
top: -5px;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-blue {
|
||||||
|
color: #0057bb;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 992px) {
|
||||||
|
.pull-right-lg {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
}
|
7
assets/css/jquery-ui.min.css
vendored
Normal file
8
assets/css/signin.css
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
.navbar {
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-brand > img {
|
||||||
|
height: 2rem;
|
||||||
|
width: auto;
|
||||||
|
}
|
5
assets/css/tokenfield-typeahead.min.css
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
/*!
|
||||||
|
* bootstrap-tokenfield
|
||||||
|
* https://github.com/sliptree/bootstrap-tokenfield
|
||||||
|
* Copyright 2013-2014 Sliptree and other contributors; Licensed MIT
|
||||||
|
*/.twitter-typeahead{width:100%;position:relative;vertical-align:top}.twitter-typeahead .tt-input,.twitter-typeahead .tt-hint{margin:0;width:100%;vertical-align:middle;background-color:#fff}.twitter-typeahead .tt-hint{color:#999;z-index:1;border:1px solid transparent}.twitter-typeahead .tt-input{color:#555;z-index:2}.twitter-typeahead .tt-input,.twitter-typeahead .tt-hint{height:34px;padding:6px 12px;font-size:14px;line-height:1.428571429}.twitter-typeahead .input-sm.tt-input,.twitter-typeahead .hint-sm.tt-hint{border-radius:3px}.twitter-typeahead .input-lg.tt-input,.twitter-typeahead .hint-lg.tt-hint{border-radius:6px}.input-group .twitter-typeahead:first-child .tt-input,.input-group .twitter-typeahead:first-child .tt-hint{border-radius:4px 0 0 4px!important}.input-group .twitter-typeahead:last-child .tt-input,.input-group .twitter-typeahead:last-child .tt-hint{border-radius:0 4px 4px 0!important}.input-group.input-group-sm .twitter-typeahead:first-child .tt-input,.input-group.input-group-sm .twitter-typeahead:first-child .tt-hint{border-radius:3px 0 0 3px!important}.input-group.input-group-sm .twitter-typeahead:last-child .tt-input,.input-group.input-group-sm .twitter-typeahead:last-child .tt-hint{border-radius:0 3px 3px 0!important}.input-sm.tt-input,.hint-sm.tt-hint,.input-group.input-group-sm .tt-input,.input-group.input-group-sm .tt-hint{height:30px;padding:5px 10px;font-size:12px;line-height:1.5}.input-group.input-group-lg .twitter-typeahead:first-child .tt-input,.input-group.input-group-lg .twitter-typeahead:first-child .tt-hint{border-radius:6px 0 0 6px!important}.input-group.input-group-lg .twitter-typeahead:last-child .tt-input,.input-group.input-group-lg .twitter-typeahead:last-child .tt-hint{border-radius:0 6px 6px 0!important}.input-lg.tt-input,.hint-lg.tt-hint,.input-group.input-group-lg .tt-input,.input-group.input-group-lg .tt-hint{height:45px;padding:10px 16px;font-size:18px;line-height:1.33}.tt-dropdown-menu{width:100%;min-width:160px;margin-top:2px;padding:5px 0;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,.15);*border-right-width:2px;*border-bottom-width:2px;border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2);-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box}.tt-suggestion{display:block;padding:3px 20px}.tt-suggestion.tt-cursor{color:#262626;background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0)}.tt-suggestion.tt-cursor a{color:#fff}.tt-suggestion p{margin:0}.tokenfield .twitter-typeahead{width:auto}.tokenfield .twitter-typeahead .tt-hint{padding:0;height:20px}.tokenfield.input-sm .twitter-typeahead .tt-input,.tokenfield.input-sm .twitter-typeahead .tt-hint{height:18px;font-size:12px;line-height:1.5}.tokenfield.input-lg .twitter-typeahead .tt-input,.tokenfield.input-lg .twitter-typeahead .tt-hint{height:23px;font-size:18px;line-height:1.33}.tokenfield .twitter-typeahead .tt-suggestions{font-size:14px}
|
Before Width: | Height: | Size: 692 KiB After Width: | Height: | Size: 692 KiB |
Before Width: | Height: | Size: 141 KiB After Width: | Height: | Size: 141 KiB |
Before Width: | Height: | Size: 829 KiB After Width: | Height: | Size: 829 KiB |
4
assets/fonts/font-awesome.min.css
vendored
Normal file
Before Width: | Height: | Size: 434 KiB After Width: | Height: | Size: 434 KiB |
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 99 KiB |
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 6.1 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
13
assets/js/bootstrap-confirmation.min.js
vendored
Normal file
7
assets/js/bootstrap-tokenfield.min.js
vendored
Normal file
39
assets/js/custom.js
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
(function($) {
|
||||||
|
"use strict"; // Start of use strict
|
||||||
|
|
||||||
|
// Smooth scrolling using jQuery easing
|
||||||
|
$(document).on('click', 'a.scroll-to-top', function(e) {
|
||||||
|
var $anchor = $(this);
|
||||||
|
$('html, body').stop().animate({
|
||||||
|
scrollTop: ($($anchor.attr('href')).offset().top)
|
||||||
|
}, 1000, 'easeInOutExpo');
|
||||||
|
e.preventDefault();
|
||||||
|
});
|
||||||
|
|
||||||
|
$(function () {
|
||||||
|
$('[data-toggle="tooltip"]').tooltip()
|
||||||
|
});
|
||||||
|
|
||||||
|
$(".online-status").each(function(){
|
||||||
|
const onlineStatusID = "#" + $(this).attr('id');
|
||||||
|
$.get( "/user/status?pkey=" + encodeURIComponent($(this).attr('data-pkey')), function( data ) {
|
||||||
|
console.log(onlineStatusID + " " + data)
|
||||||
|
if(data === true) {
|
||||||
|
$(onlineStatusID).html('<i class="fas fa-link text-success"></i>');
|
||||||
|
} else {
|
||||||
|
$(onlineStatusID).html('<i class="fas fa-unlink"></i>');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
$(function() {
|
||||||
|
$('select.device-selector').change(function() {
|
||||||
|
this.form.submit();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
$('[data-toggle=confirmation]').confirmation({
|
||||||
|
rootSelector: '[data-toggle=confirmation]',
|
||||||
|
// other options
|
||||||
|
});
|
||||||
|
})(jQuery); // End of use strict
|
||||||
|
|
||||||
|
|
13
assets/js/jquery-ui.min.js
vendored
Normal file
73
assets/tpl/admin_create_clients.html
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
|
||||||
|
<title>{{ .Static.WebsiteTitle }} - Admin</title>
|
||||||
|
<meta name="description" content="{{ .Static.WebsiteTitle }}">
|
||||||
|
<link rel="stylesheet" href="/css/jquery-ui.min.css">
|
||||||
|
<link rel="stylesheet" href="/css/bootstrap.min.css">
|
||||||
|
<link rel="stylesheet" href="/fonts/fontawesome-all.min.css">
|
||||||
|
<link rel="stylesheet" href="/css/bootstrap-tokenfield.min.css">
|
||||||
|
<link rel="stylesheet" href="/css/tokenfield-typeahead.min.css">
|
||||||
|
<link rel="stylesheet" href="/css/custom.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body id="page-top" class="d-flex flex-column min-vh-100">
|
||||||
|
{{template "prt_nav.html" .}}
|
||||||
|
<div class="container mt-5">
|
||||||
|
<h1>Create new clients</h1>
|
||||||
|
<h2>Enter valid user email addresses to quickly create new accounts.</h2>
|
||||||
|
{{template "prt_flashes.html" .}}
|
||||||
|
<form method="post" enctype="multipart/form-data">
|
||||||
|
<input type="hidden" name="_csrf" value="{{.Csrf}}">
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group required col-md-12">
|
||||||
|
<label for="inputEmail">Email Addresses</label>
|
||||||
|
<input type="text" name="email" class="form-control" id="inputEmail" value="{{.FormData.Emails}}" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group required col-md-12">
|
||||||
|
<label for="inputIdentifier">Client Friendly Name (will be added as suffix to the name of the user)</label>
|
||||||
|
<input type="text" name="identifier" class="form-control" id="inputIdentifier" value="{{.FormData.Identifier}}" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit" class="btn btn-primary">Create</button>
|
||||||
|
<a href="/admin" class="btn btn-secondary">Cancel</a>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{{template "prt_footer.html" .}}
|
||||||
|
<script src="/js/jquery.min.js"></script>
|
||||||
|
<script src="/js/jquery.easing.js"></script>
|
||||||
|
<script src="/js/popper.min.js"></script>
|
||||||
|
<script src="/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<script src="/js/bootstrap-confirmation.min.js"></script>
|
||||||
|
<script src="/js/bootstrap-tokenfield.min.js"></script>
|
||||||
|
<script src="/js/custom.js"></script>
|
||||||
|
<script>$('#inputEmail').on('tokenfield:createdtoken', function (e) {
|
||||||
|
// Über-simplistic e-mail validation
|
||||||
|
var re = /\S+@\S+\.\S+/
|
||||||
|
var valid = re.test(e.attrs.value)
|
||||||
|
if (!valid) {
|
||||||
|
$(e.relatedTarget).addClass('invalid')
|
||||||
|
}
|
||||||
|
}).on('tokenfield:createtoken', function (e) {
|
||||||
|
var existingTokens = $(this).tokenfield('getTokens');
|
||||||
|
$.each(existingTokens, function(index, token) {
|
||||||
|
if (token.value === e.attrs.value)
|
||||||
|
e.preventDefault();
|
||||||
|
});
|
||||||
|
}).tokenfield({
|
||||||
|
autocomplete: {
|
||||||
|
source: [{{range $i, $u :=.Users}}{{if ne $i 0}},{{end}}'{{$u.Email}}'{{end}}],
|
||||||
|
delay: 100
|
||||||
|
},
|
||||||
|
inputType: 'email',
|
||||||
|
createTokensOnBlur: true,
|
||||||
|
showAutocompleteOnFocus: false
|
||||||
|
})</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
221
assets/tpl/admin_edit_client.html
Normal file
@@ -0,0 +1,221 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
|
||||||
|
<title>{{ .Static.WebsiteTitle }} - Admin</title>
|
||||||
|
<meta name="description" content="{{ .Static.WebsiteTitle }}">
|
||||||
|
<link rel="stylesheet" href="/css/bootstrap.min.css">
|
||||||
|
<link rel="stylesheet" href="/fonts/fontawesome-all.min.css">
|
||||||
|
<link rel="stylesheet" href="/css/custom.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body id="page-top" class="d-flex flex-column min-vh-100">
|
||||||
|
{{template "prt_nav.html" .}}
|
||||||
|
<div class="container mt-5">
|
||||||
|
{{template "prt_flashes.html" .}}
|
||||||
|
|
||||||
|
<!-- server mode -->
|
||||||
|
{{if eq .Device.Type "server"}}
|
||||||
|
{{if .Peer.IsNew}}
|
||||||
|
<h1>Create a new client</h1>
|
||||||
|
{{else}}
|
||||||
|
<h1>Edit client: <strong>{{.Peer.Identifier}}</strong></h1>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
<form method="post" enctype="multipart/form-data">
|
||||||
|
<input type="hidden" name="_csrf" value="{{.Csrf}}">
|
||||||
|
<input type="hidden" name="uid" value="{{.Peer.UID}}">
|
||||||
|
<input type="hidden" name="devicetype" value="{{.Device.Type}}">
|
||||||
|
<input type="hidden" name="device" value="{{.Device.DeviceName}}">
|
||||||
|
<input type="hidden" name="endpoint" value="{{.Peer.Endpoint}}">
|
||||||
|
{{if .EditableKeys}}
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group col-md-12">
|
||||||
|
<label for="server_PrivateKey">Private Key</label>
|
||||||
|
<input type="text" name="privkey" class="form-control" id="server_PrivateKey" value="{{.Peer.PrivateKey}}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group required col-md-12">
|
||||||
|
<label for="server_PublicKey">Public Key</label>
|
||||||
|
<input type="text" name="pubkey" class="form-control" id="server_PublicKey" value="{{.Peer.PublicKey}}" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group col-md-12">
|
||||||
|
<label for="server_PresharedKey">Preshared Key</label>
|
||||||
|
<input type="text" name="presharedkey" class="form-control" id="server_PresharedKey" value="{{.Peer.PresharedKey}}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{else}}
|
||||||
|
<input type="hidden" name="privkey" value="{{.Peer.PrivateKey}}">
|
||||||
|
<input type="hidden" name="presharedkey" value="{{.Peer.PresharedKey}}">
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group col-md-12">
|
||||||
|
<label for="server_ro_PublicKey">Public Key</label>
|
||||||
|
<input type="text" name="pubkey" readonly class="form-control" id="server_ro_PublicKey" value="{{.Peer.PublicKey}}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group required col-md-12">
|
||||||
|
<label for="server_Identifier">Client Friendly Name</label>
|
||||||
|
<input type="text" name="identifier" class="form-control" id="server_Identifier" value="{{.Peer.Identifier}}" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group required col-md-12">
|
||||||
|
<label for="server_Email">Client Email Address</label>
|
||||||
|
<input type="email" name="mail" class="form-control" id="server_Email" value="{{.Peer.Email}}" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group required col-md-12">
|
||||||
|
<label for="server_IP">Client IP Address</label>
|
||||||
|
<input type="text" name="ip" class="form-control" id="server_IP" value="{{.Peer.IPsStr}}" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group col-md-12 global-config">
|
||||||
|
<label for="server_AllowedIP">Allowed IPs</label>
|
||||||
|
<input type="text" name="allowedip" class="form-control" id="server_AllowedIP" value="{{.Peer.AllowedIPsStr}}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group col-md-12">
|
||||||
|
<label for="server_AllowedIPSrv">Extra Allowed IPs (Server sided)</label>
|
||||||
|
<input type="text" name="allowedipSrv" class="form-control" id="server_AllowedIPSrv" value="{{.Peer.AllowedIPsSrvStr}}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group col-md-12 global-config">
|
||||||
|
<label for="server_DNS">Client DNS Servers</label>
|
||||||
|
<input type="text" name="dns" class="form-control" id="server_DNS" value="{{.Peer.DNSStr}}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group col-md-6 global-config">
|
||||||
|
<label for="server_PersistentKeepalive">Persistent Keepalive (0 = off)</label>
|
||||||
|
<input type="number" name="keepalive" class="form-control" id="server_PersistentKeepalive" placeholder="16" value="{{.Peer.PersistentKeepalive}}">
|
||||||
|
</div>
|
||||||
|
<div class="form-group col-md-6 global-config">
|
||||||
|
<label for="server_MTU">Client MTU (0 = default)</label>
|
||||||
|
<input type="number" name="mtu" class="form-control" id="server_MTU" placeholder="" value="{{.Peer.Mtu}}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group col-md-6">
|
||||||
|
<div class="custom-control custom-switch">
|
||||||
|
<input class="custom-control-input" name="isdisabled" type="checkbox" value="true" id="server_Disabled" {{if .Peer.DeactivatedAt}}checked{{end}}>
|
||||||
|
<label class="custom-control-label" for="server_Disabled">
|
||||||
|
Disabled
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="custom-control custom-switch">
|
||||||
|
<input class="custom-control-input" name="ignoreglobalsettings" type="checkbox" value="true" id="server_IgnoreGlobalSettings" {{if .Peer.IgnoreGlobalSettings}}checked{{end}}>
|
||||||
|
<label class="custom-control-label" for="server_IgnoreGlobalSettings">
|
||||||
|
Ignore global settings (<span class="text-blue">g</span>)
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group col-md-6">
|
||||||
|
<label for="expires_at">Expires At</label>
|
||||||
|
<input type="date" name="expires_at" pattern="\d{4}-\d{2}-\d{2}" class="form-control" id="expires_at" placeholder="" value="{{formatDate .Peer.ExpiresAt}}" min="2022-01-01">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<button type="submit" class="btn btn-primary">Save</button>
|
||||||
|
<a href="/admin" class="btn btn-secondary">Cancel</a>
|
||||||
|
</form>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
<!-- client mode -->
|
||||||
|
{{if eq .Device.Type "client"}}
|
||||||
|
{{if .Peer.IsNew}}
|
||||||
|
<h1>Create a new remote endpoint</h1>
|
||||||
|
{{else}}
|
||||||
|
<h1>Edit remote endpoint: <strong>{{.Peer.Identifier}}</strong></h1>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
<form method="post" enctype="multipart/form-data">
|
||||||
|
<input type="hidden" name="_csrf" value="{{.Csrf}}">
|
||||||
|
<input type="hidden" name="uid" value="{{.Peer.UID}}">
|
||||||
|
<input type="hidden" name="mail" value="{{.AdminEmail}}">
|
||||||
|
<input type="hidden" name="devicetype" value="{{.Device.Type}}">
|
||||||
|
<input type="hidden" name="device" value="{{.Device.DeviceName}}">
|
||||||
|
<input type="hidden" name="privkey" value="{{.Peer.PrivateKey}}">
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group required col-md-12">
|
||||||
|
<label for="client_Identifier">Endpoint Friendly Name</label>
|
||||||
|
<input type="text" name="identifier" class="form-control" id="client_Identifier" value="{{.Peer.Identifier}}" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group required col-md-12">
|
||||||
|
<label for="client_Endpoint">Endpoint Address</label>
|
||||||
|
<input type="text" name="endpoint" class="form-control" id="client_Endpoint" value="{{.Peer.Endpoint}}" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group required col-md-12">
|
||||||
|
<label for="client_PublicKey">Endpoint Public Key</label>
|
||||||
|
<input type="text" name="pubkey" class="form-control" id="client_PublicKey" value="{{.Peer.PublicKey}}" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group col-md-12">
|
||||||
|
<label for="client_PresharedKey">Preshared Key</label>
|
||||||
|
<input type="text" name="presharedkey" class="form-control" id="client_PresharedKey" value="{{.Peer.PresharedKey}}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group col-md-12">
|
||||||
|
<label for="client_AllowedIP">Allowed IPs</label>
|
||||||
|
<input type="text" name="allowedip" class="form-control" id="client_AllowedIP" value="{{.Peer.AllowedIPsStr}}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group col-md-6">
|
||||||
|
<label for="client_PersistentKeepalive">Persistent Keepalive (0 = off)</label>
|
||||||
|
<input type="number" name="keepalive" class="form-control" id="client_PersistentKeepalive" placeholder="16" value="{{.Peer.PersistentKeepalive}}">
|
||||||
|
</div>
|
||||||
|
<div class="form-group col-md-6">
|
||||||
|
<label for="client_IP">Ping-Check IP Address</label>
|
||||||
|
<input type="text" name="ip" class="form-control" id="client_IP" value="{{.Peer.IPsStr}}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group col-md-6">
|
||||||
|
<div class="custom-control custom-switch">
|
||||||
|
<input class="custom-control-input" name="isdisabled" type="checkbox" value="true" id="client_Disabled" {{if .Peer.DeactivatedAt}}checked{{end}}>
|
||||||
|
<label class="custom-control-label" for="client_Disabled">
|
||||||
|
Disabled
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group col-md-6">
|
||||||
|
<label for="expires_at">Expires At</label>
|
||||||
|
<input type="date" name="expires_at" pattern="\d{4}-\d{2}-\d{2}" class="form-control" id="expires_at" placeholder="" value="{{formatDate .Peer.ExpiresAt}}" min="2022-01-01">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<button type="submit" class="btn btn-primary">Save</button>
|
||||||
|
<a href="/admin" class="btn btn-secondary">Cancel</a>
|
||||||
|
</form>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
{{template "prt_footer.html" .}}
|
||||||
|
<script src="/js/jquery.min.js"></script>
|
||||||
|
<script src="/js/jquery.easing.js"></script>
|
||||||
|
<script src="/js/popper.min.js"></script>
|
||||||
|
<script src="/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<script src="/js/bootstrap-confirmation.min.js"></script>
|
||||||
|
<script src="/js/custom.js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
263
assets/tpl/admin_edit_interface.html
Normal file
@@ -0,0 +1,263 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
|
||||||
|
<title>{{ .Static.WebsiteTitle }} - Admin</title>
|
||||||
|
<meta name="description" content="{{ .Static.WebsiteTitle }}">
|
||||||
|
<link rel="stylesheet" href="/css/bootstrap.min.css">
|
||||||
|
<link rel="stylesheet" href="/fonts/fontawesome-all.min.css">
|
||||||
|
<link rel="stylesheet" href="/css/custom.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body id="page-top" class="d-flex flex-column min-vh-100">
|
||||||
|
{{template "prt_nav.html" .}}
|
||||||
|
<div class="container mt-5 main-app">
|
||||||
|
<h1>Edit interface <strong>{{.Device.DeviceName}}</strong></h1>
|
||||||
|
{{template "prt_flashes.html" .}}
|
||||||
|
|
||||||
|
<ul class="nav nav-tabs">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link {{if eq .Device.Type "server"}}active{{end}}" data-toggle="tab" href="#server">Server Mode</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link {{if eq .Device.Type "client"}}active{{end}}" data-toggle="tab" href="#client">Client Mode</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div id="configContent" class="tab-content">
|
||||||
|
<!-- server mode -->
|
||||||
|
<div class="tab-pane fade {{if eq .Device.Type "server"}}active show{{end}}" id="server">
|
||||||
|
<form method="post" enctype="multipart/form-data" name="server">
|
||||||
|
<input type="hidden" name="_csrf" value="{{.Csrf}}">
|
||||||
|
<input type="hidden" name="device" value="{{.Device.DeviceName}}">
|
||||||
|
<input type="hidden" name="devicetype" value="server">
|
||||||
|
<h3>Server's interface configuration</h3>
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group col-md-12">
|
||||||
|
<label for="server_DisplayName">Display Name</label>
|
||||||
|
<input type="text" name="displayname" class="form-control" id="server_DisplayName" value="{{.Device.DisplayName}}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{if .EditableKeys}}
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group required col-md-12">
|
||||||
|
<label for="server_PrivateKey">Private Key</label>
|
||||||
|
<input type="text" name="privkey" class="form-control" id="server_PrivateKey" value="{{.Device.PrivateKey}}" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group required col-md-12">
|
||||||
|
<label for="server_PublicKey">Public Key</label>
|
||||||
|
<input type="text" name="pubkey" class="form-control" id="server_PublicKey" value="{{.Device.PublicKey}}" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{else}}
|
||||||
|
<input type="hidden" name="privkey" value="{{.Device.PrivateKey}}">
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group col-md-12">
|
||||||
|
<label for="server_ro_PublicKey">Public Key</label>
|
||||||
|
<input type="text" name="pubkey" readonly class="form-control" id="server_ro_PublicKey" value="{{.Device.PublicKey}}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group required col-md-6">
|
||||||
|
<label for="server_ListenPort">Listen port</label>
|
||||||
|
<input type="number" name="port" class="form-control" id="server_ListenPort" placeholder="51820" value="{{.Device.ListenPort}}" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group required col-md-6">
|
||||||
|
<label for="server_IPs">Server IP address</label>
|
||||||
|
<input type="text" name="ip" class="form-control" id="server_IPs" placeholder="10.6.6.1/24" value="{{.Device.IPsStr}}" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<h3>Client's global configuration (<span class="text-blue">g</span>)</h3>
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group required col-md-12">
|
||||||
|
<label for="server_PublicEndpoint">Public Endpoint for Clients</label>
|
||||||
|
<input type="text" name="endpoint" class="form-control" id="server_PublicEndpoint" placeholder="vpn.company.com:51820" value="{{.Device.DefaultEndpoint}}" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group col-md-6">
|
||||||
|
<label for="server_DNS">DNS Servers</label>
|
||||||
|
<input type="text" name="dns" class="form-control" id="server_DNS" placeholder="1.1.1.1" value="{{.Device.DNSStr}}">
|
||||||
|
</div>
|
||||||
|
<div class="form-group col-md-6">
|
||||||
|
<label for="server_AllowedIP">Default allowed IPs</label>
|
||||||
|
<input type="text" name="allowedip" class="form-control" id="server_AllowedIP" placeholder="10.6.6.0/24" value="{{.Device.DefaultAllowedIPsStr}}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group col-md-6">
|
||||||
|
<label for="server_MTU">MTU (also used for the server interface, 0 = default)</label>
|
||||||
|
<input type="number" name="mtu" class="form-control" id="server_MTU" placeholder="" value="{{.Device.Mtu}}">
|
||||||
|
</div>
|
||||||
|
<div class="form-group col-md-6">
|
||||||
|
<label for="server_PersistentKeepalive">Persistent Keepalive (0 = off)</label>
|
||||||
|
<input type="number" name="keepalive" class="form-control" id="server_PersistentKeepalive" placeholder="16" value="{{.Device.DefaultPersistentKeepalive}}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<h3>Interface configuration hooks</h3>
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group col-md-12">
|
||||||
|
<label for="server_PreUp">Pre Up</label>
|
||||||
|
<input type="text" name="preup" class="form-control" id="server_PreUp" value="{{.Device.PreUp}}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group col-md-12">
|
||||||
|
<label for="server_PostUp">Post Up</label>
|
||||||
|
<input type="text" name="postup" class="form-control" id="server_PostUp" value="{{.Device.PostUp}}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group col-md-12">
|
||||||
|
<label for="server_PreDown">Pre Down</label>
|
||||||
|
<input type="text" name="predown" class="form-control" id="server_PreDown" value="{{.Device.PreDown}}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group col-md-12">
|
||||||
|
<label for="server_PostDown">Post Down</label>
|
||||||
|
<input type="text" name="postdown" class="form-control" id="server_PostDown" value="{{.Device.PostDown}}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<a href="#" class="advanced-settings btn btn-link collapsed" data-toggle="collapse" data-target="#collapseAdvancedServer" aria-expanded="false" aria-controls="collapseAdvancedServer">
|
||||||
|
Advanced Settings
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="collapseAdvancedServer" class="collapse" aria-labelledby="collapseAdvancedServer">
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group col-md-6">
|
||||||
|
<label for="server_FirewallMark">Firewall Mark (0 = default or off)</label>
|
||||||
|
<input type="number" name="firewallmark" class="form-control" id="server_FirewallMark" placeholder="" value="{{.Device.FirewallMark}}">
|
||||||
|
</div>
|
||||||
|
<div class="form-group col-md-6">
|
||||||
|
<label for="server_RoutingTable">Routing Table (empty = default or auto)</label>
|
||||||
|
<input type="text" name="routingtable" class="form-control" id="server_RoutingTable" placeholder="auto" value="{{.Device.RoutingTable}}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group col-md-12">
|
||||||
|
<div class="custom-control custom-switch">
|
||||||
|
<input class="custom-control-input" name="saveconfig" type="checkbox" value="true" id="server_SaveConfig" {{if .Peer.SaveConfig}}checked{{end}}>
|
||||||
|
<label class="custom-control-label" for="server_SaveConfig">
|
||||||
|
Save Configuration (if interface was edited via WireGuard configuration tool)
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit" class="btn btn-primary">Save</button>
|
||||||
|
<a href="/admin" class="btn btn-secondary">Cancel</a>
|
||||||
|
<a href="/admin/device/applyglobals" class="btn btn-dark float-right">Apply Global Settings (<span class="text-blue">g</span>) to clients</a>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- client mode -->
|
||||||
|
<div class="tab-pane fade {{if eq .Device.Type "client"}}active show{{end}}" id="client">
|
||||||
|
<form method="post" enctype="multipart/form-data" name="client">
|
||||||
|
<input type="hidden" name="_csrf" value="{{.Csrf}}">
|
||||||
|
<input type="hidden" name="device" value="{{.Device.DeviceName}}">
|
||||||
|
<input type="hidden" name="devicetype" value="client">
|
||||||
|
<h3>Client's interface configuration</h3>
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group col-md-12">
|
||||||
|
<label for="client_DisplayName">Display Name</label>
|
||||||
|
<input type="text" name="displayname" class="form-control" id="client_DisplayName" value="{{.Device.DisplayName}}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{if .EditableKeys}}
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group required col-md-12">
|
||||||
|
<label for="client_PrivateKey">Private Key</label>
|
||||||
|
<input type="text" name="privkey" class="form-control" id="client_PrivateKey" value="{{.Device.PrivateKey}}" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group required col-md-12">
|
||||||
|
<label for="client_PublicKey">Public Key</label>
|
||||||
|
<input type="text" name="pubkey" class="form-control" id="client_PublicKey" value="{{.Device.PublicKey}}" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{else}}
|
||||||
|
<input type="hidden" name="privkey" value="{{.Device.PrivateKey}}">
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group col-md-12">
|
||||||
|
<label for="client_ro_PublicKey">Public Key</label>
|
||||||
|
<input type="text" name="pubkey" readonly class="form-control" id="client_ro_PublicKey" value="{{.Device.PublicKey}}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group required col-md-6">
|
||||||
|
<label for="client_IPs">Client IP address</label>
|
||||||
|
<input type="text" name="ip" class="form-control" id="client_IPs" placeholder="10.6.6.1/24" value="{{.Device.IPsStr}}" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group col-md-6">
|
||||||
|
<label for="client_DNS">DNS Servers</label>
|
||||||
|
<input type="text" name="dns" class="form-control" id="client_DNS" placeholder="1.1.1.1" value="{{.Device.DNSStr}}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group col-md-4">
|
||||||
|
<label for="client_MTU">MTU (0 = default)</label>
|
||||||
|
<input type="number" name="mtu" class="form-control" id="client_MTU" placeholder="" value="{{.Device.Mtu}}">
|
||||||
|
</div>
|
||||||
|
<div class="form-group col-md-4">
|
||||||
|
<label for="client_FirewallMark">Firewall Mark (0 = default or off)</label>
|
||||||
|
<input type="number" name="firewallmark" class="form-control" id="client_FirewallMark" placeholder="" value="{{.Device.FirewallMark}}">
|
||||||
|
</div>
|
||||||
|
<div class="form-group col-md-4">
|
||||||
|
<label for="client_RoutingTable">Routing Table (empty = default or auto)</label>
|
||||||
|
<input type="text" name="routingtable" class="form-control" id="client_RoutingTable" placeholder="auto" value="{{.Device.RoutingTable}}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<h3>Interface configuration hooks</h3>
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group col-md-12">
|
||||||
|
<label for="client_PreUp">Pre Up</label>
|
||||||
|
<input type="text" name="preup" class="form-control" id="client_PreUp" value="{{.Device.PreUp}}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group col-md-12">
|
||||||
|
<label for="client_PostUp">Post Up</label>
|
||||||
|
<input type="text" name="postup" class="form-control" id="client_PostUp" value="{{.Device.PostUp}}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group col-md-12">
|
||||||
|
<label for="client_PreDown">Pre Down</label>
|
||||||
|
<input type="text" name="predown" class="form-control" id="client_PreDown" value="{{.Device.PreDown}}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group col-md-12">
|
||||||
|
<label for="client_PostDown">Post Down</label>
|
||||||
|
<input type="text" name="postdown" class="form-control" id="client_PostDown" value="{{.Device.PostDown}}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit" class="btn btn-primary">Save</button>
|
||||||
|
<a href="/admin" class="btn btn-secondary">Cancel</a>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{template "prt_footer.html" .}}
|
||||||
|
<script src="/js/jquery.min.js"></script>
|
||||||
|
<script src="/js/jquery.easing.js"></script>
|
||||||
|
<script src="/js/popper.min.js"></script>
|
||||||
|
<script src="/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<script src="/js/bootstrap-confirmation.min.js"></script>
|
||||||
|
<script src="/js/custom.js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
95
assets/tpl/admin_edit_user.html
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
|
||||||
|
<title>{{ .Static.WebsiteTitle }} - Users</title>
|
||||||
|
<meta name="description" content="{{ .Static.WebsiteTitle }}">
|
||||||
|
<link rel="stylesheet" href="/css/bootstrap.min.css">
|
||||||
|
<link rel="stylesheet" href="/fonts/fontawesome-all.min.css">
|
||||||
|
<link rel="stylesheet" href="/css/custom.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body id="page-top" class="d-flex flex-column min-vh-100">
|
||||||
|
{{template "prt_nav.html" .}}
|
||||||
|
<div class="container mt-5">
|
||||||
|
{{if eq .User.CreatedAt .Epoch}}
|
||||||
|
<h1>Create a new user</h1>
|
||||||
|
{{else}}
|
||||||
|
<h1>Edit user <strong>{{.User.Email}}</strong></h1>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{template "prt_flashes.html" .}}
|
||||||
|
|
||||||
|
<form method="post" enctype="multipart/form-data">
|
||||||
|
<input type="hidden" name="_csrf" value="{{.Csrf}}">
|
||||||
|
{{if eq .User.CreatedAt .Epoch}}
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group required col-md-12">
|
||||||
|
<label for="inputEmail">Email</label>
|
||||||
|
<input type="text" name="email" class="form-control" id="inputEmail" value="{{.User.Email}}" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{else}}
|
||||||
|
<input type="hidden" name="email" value="{{.User.Email}}">
|
||||||
|
{{end}}
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group required col-md-12">
|
||||||
|
<label for="inputFirstname">Firstname</label>
|
||||||
|
<input type="text" name="firstname" class="form-control" id="inputFirstname" value="{{.User.Firstname}}" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group required col-md-12">
|
||||||
|
<label for="inputLastname">Lastname</label>
|
||||||
|
<input type="text" name="lastname" class="form-control" id="inputLastname" value="{{.User.Lastname}}" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group col-md-12">
|
||||||
|
<label for="inputPhone">Phone</label>
|
||||||
|
<input type="text" name="phone" class="form-control" id="inputPhone" value="{{.User.Phone}}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group col-md-12 {{if eq .User.CreatedAt .Epoch}}required{{end}}">
|
||||||
|
<label for="inputPassword">Password</label>
|
||||||
|
<input type="password" name="password" class="form-control" id="inputPassword" {{if eq .User.CreatedAt .Epoch}}required{{end}}>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group col-md-12">
|
||||||
|
<div class="custom-control custom-switch">
|
||||||
|
<input class="custom-control-input" name="isadmin" type="checkbox" value="true" id="inputAdmin" {{if .User.IsAdmin}}checked{{end}}>
|
||||||
|
<label class="custom-control-label" for="inputAdmin">
|
||||||
|
Administrator
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="custom-control custom-switch">
|
||||||
|
<input class="custom-control-input" name="isdisabled" type="checkbox" value="true" id="inputDisabled" {{if .User.DeletedAt.Valid}}checked{{end}}>
|
||||||
|
<label class="custom-control-label" for="inputDisabled">
|
||||||
|
Disabled
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit" class="btn btn-primary">Save</button>
|
||||||
|
<a href="/admin/users/" class="btn btn-secondary">Cancel</a>
|
||||||
|
{{if eq $.Session.IsAdmin true}}
|
||||||
|
{{if eq .User.Source "db"}}
|
||||||
|
<a href="/admin/users/delete?pkey={{.User.Email}}" data-toggle="confirmation" data-title="Really delete user and associated peers?" title="Delete user and associated peers" class="btn btn-danger float-right">Delete</a>
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{{template "prt_footer.html" .}}
|
||||||
|
<script src="/js/jquery.min.js"></script>
|
||||||
|
<script src="/js/jquery.easing.js"></script>
|
||||||
|
<script src="/js/popper.min.js"></script>
|
||||||
|
<script src="/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<script src="/js/bootstrap-confirmation.min.js"></script>
|
||||||
|
<script src="/js/custom.js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
278
assets/tpl/admin_index.html
Normal file
@@ -0,0 +1,278 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
|
||||||
|
<title>{{ .Static.WebsiteTitle }} - Admin</title>
|
||||||
|
<meta name="description" content="{{ .Static.WebsiteTitle }}">
|
||||||
|
<link rel="stylesheet" href="/css/bootstrap.min.css">
|
||||||
|
<link rel="stylesheet" href="/fonts/fontawesome-all.min.css">
|
||||||
|
<link rel="stylesheet" href="/css/custom.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body id="page-top" class="d-flex flex-column min-vh-100">
|
||||||
|
{{template "prt_nav.html" .}}
|
||||||
|
<div class="container mt-5">
|
||||||
|
<h1>WireGuard VPN Administration</h1>
|
||||||
|
{{template "prt_flashes.html" .}}
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<span class="mr-auto">Interface status for <strong>{{.Device.DeviceName}}</strong> {{if eq $.Device.Type "server"}}(server mode){{end}}{{if eq $.Device.Type "client"}}(client mode){{end}}</span>
|
||||||
|
<a href="/admin/device/write?dev={{.Device.DeviceName}}" title="Write interface configuration"><i class="fas fa-save"></i></a>
|
||||||
|
|
||||||
|
<a href="/admin/device/download?dev={{.Device.DeviceName}}" title="Download interface configuration"><i class="fas fa-download"></i></a>
|
||||||
|
|
||||||
|
<a href="/admin/device/edit?dev={{.Device.DeviceName}}" title="Edit interface settings"><i class="fas fa-cog"></i></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="row">
|
||||||
|
{{if eq $.Device.Type "server"}}
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<table class="table table-sm table-borderless device-status-table">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>Public Key:</td>
|
||||||
|
<td>{{.Device.PublicKey}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Public Endpoint:</td>
|
||||||
|
<td>{{.Device.DefaultEndpoint}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Listening Port:</td>
|
||||||
|
<td>{{.Device.ListenPort}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Enabled Peers:</td>
|
||||||
|
<td>{{len .Device.Interface.Peers}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Total Peers:</td>
|
||||||
|
<td>{{.TotalPeers}}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<table class="table table-sm table-borderless device-status-table">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>IP Address:</td>
|
||||||
|
<td>{{.Device.IPsStr}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Default allowed IP's:</td>
|
||||||
|
<td>{{.Device.DefaultAllowedIPsStr}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Default DNS servers:</td>
|
||||||
|
<td>{{.Device.DNSStr}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Default MTU:</td>
|
||||||
|
<td>{{.Device.Mtu}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Default Keepalive Interval:</td>
|
||||||
|
<td>{{.Device.DefaultPersistentKeepalive}}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
{{if eq $.Device.Type "client"}}
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<table class="table table-sm table-borderless device-status-table">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>Public Key:</td>
|
||||||
|
<td>{{.Device.PublicKey}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Enabled Endpoints:</td>
|
||||||
|
<td>{{len .Device.Interface.Peers}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Total Endpoints:</td>
|
||||||
|
<td>{{.TotalPeers}}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<table class="table table-sm table-borderless device-status-table">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>IP Address:</td>
|
||||||
|
<td>{{.Device.IPsStr}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>DNS servers:</td>
|
||||||
|
<td>{{.Device.DNSStr}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Default MTU:</td>
|
||||||
|
<td>{{.Device.Mtu}}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-4 row">
|
||||||
|
<div class="col-sm-8 col-12">
|
||||||
|
{{if eq $.Device.Type "server"}}
|
||||||
|
<h2 class="mt-2">Current VPN Peers</h2>
|
||||||
|
{{end}}
|
||||||
|
{{if eq $.Device.Type "client"}}
|
||||||
|
<h2 class="mt-2">Current VPN Endpoints</h2>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-4 col-12 text-right">
|
||||||
|
<a href="/admin/peer/emailall" data-toggle="confirmation" data-title="Send mail to all peers?" title="Send mail to all peers" class="btn btn-light"><i class="fa fa-fw fa-paper-plane"></i></a>
|
||||||
|
{{if eq $.Device.Type "server"}}
|
||||||
|
<a href="/admin/peer/createldap" title="Add multiple peers" class="btn btn-primary"><i class="fa fa-fw fa-plus"></i><i class="fa fa-fw fa-users"></i></a>
|
||||||
|
{{end}}
|
||||||
|
<a href="/admin/peer/create" title="Add a peer" class="btn btn-primary"><i class="fa fa-fw fa-plus"></i><i class="fa fa-fw fa-user"></i></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-2 table-responsive">
|
||||||
|
<table class="table table-sm" id="userTable">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col" class="list-image-cell"></th><!-- Status and expand -->
|
||||||
|
<th scope="col"><a href="?sort=id">Identifier <i class="fa fa-fw {{.Session.GetSortIcon "peers" "id"}}"></i></a></th>
|
||||||
|
<th scope="col"><a href="?sort=pubKey">Public Key <i class="fa fa-fw {{.Session.GetSortIcon "peers" "pubKey"}}"></i></a></th>
|
||||||
|
{{if eq $.Device.Type "server"}}
|
||||||
|
<th scope="col"><a href="?sort=mail">E-Mail <i class="fa fa-fw {{.Session.GetSortIcon "peers" "mail"}}"></i></a></th>
|
||||||
|
{{end}}
|
||||||
|
{{if eq $.Device.Type "server"}}
|
||||||
|
<th scope="col"><a href="?sort=ip">IP's <i class="fa fa-fw {{.Session.GetSortIcon "peers" "ip"}}"></i></a></th>
|
||||||
|
{{end}}
|
||||||
|
{{if eq $.Device.Type "client"}}
|
||||||
|
<th scope="col"><a href="?sort=endpoint">Endpoint <i class="fa fa-fw {{.Session.GetSortIcon "peers" "endpoint"}}"></i></a></th>
|
||||||
|
{{end}}
|
||||||
|
<th scope="col"><a href="?sort=handshake">Handshake <i class="fa fa-fw {{.Session.GetSortIcon "peers" "handshake"}}"></i></a></th>
|
||||||
|
<th scope="col"></th><!-- Actions -->
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{{range $i, $p :=.Peers}}
|
||||||
|
{{$peerUser:=(userForEmail $.Users $p.Email)}}
|
||||||
|
<tr id="user-pos-{{$i}}" {{if $p.DeactivatedAt}}class="disabled-peer"{{end}}>
|
||||||
|
<th scope="row" class="list-image-cell">
|
||||||
|
<a href="#{{$p.UID}}" data-toggle="collapse" class="collapse-indicator collapsed"></a>
|
||||||
|
<!-- online check -->
|
||||||
|
<span title="Online status" class="online-status" id="online-{{$p.UID}}" data-pkey="{{$p.PublicKey}}"><i class="fas fa-unlink"></i></span>
|
||||||
|
</th>
|
||||||
|
<td>{{$p.Identifier}}{{if $p.WillExpire}} <i class="fas fa-hourglass-end expiring-peer" data-toggle="tooltip" data-placement="right" title="" data-original-title="Expires at: {{formatDate $p.ExpiresAt}}"></i>{{end}}</td>
|
||||||
|
<td>{{$p.PublicKey}}</td>
|
||||||
|
{{if eq $.Device.Type "server"}}
|
||||||
|
<td>{{$p.Email}}</td>
|
||||||
|
{{end}}
|
||||||
|
{{if eq $.Device.Type "server"}}
|
||||||
|
<td>{{$p.IPsStr}}</td>
|
||||||
|
{{end}}
|
||||||
|
{{if eq $.Device.Type "client"}}
|
||||||
|
<td>{{$p.Endpoint}}</td>
|
||||||
|
{{end}}
|
||||||
|
<td><span data-toggle="tooltip" data-placement="left" title="" data-original-title="{{$p.LastHandshakeTime}}">{{$p.LastHandshake}}</span></td>
|
||||||
|
<td>
|
||||||
|
{{if eq $.Session.IsAdmin true}}
|
||||||
|
<a href="/admin/peer/edit?pkey={{$p.PublicKey}}" title="Edit peer"><i class="fas fa-cog"></i></a>
|
||||||
|
{{end}}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="hiddenRow">
|
||||||
|
<td colspan="7" class="hiddenCell" style="white-space:nowrap">
|
||||||
|
<div class="collapse" id="{{$p.UID}}" data-parent="#userTable">
|
||||||
|
<div class="row collapsedRow">
|
||||||
|
<div class="col-md-6 leftBorder">
|
||||||
|
<ul class="nav nav-tabs">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link active" data-toggle="tab" href="#t1{{$p.UID}}">Personal</a>
|
||||||
|
</li>
|
||||||
|
{{if eq $.Device.Type "server"}}
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" data-toggle="tab" href="#t2{{$p.UID}}">Configuration</a>
|
||||||
|
</li>
|
||||||
|
{{end}}
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" data-toggle="tab" href="#t3{{$p.UID}}">Danger Zone</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<div class="tab-content" id="tabContent{{$p.UID}}">
|
||||||
|
<div id="t1{{$p.UID}}" class="tab-pane fade active show">
|
||||||
|
<h4>User details</h4>
|
||||||
|
{{if not $peerUser}}
|
||||||
|
<p>No user information available...</p>
|
||||||
|
{{else}}
|
||||||
|
<ul>
|
||||||
|
<li>Firstname: {{$peerUser.Firstname}}</li>
|
||||||
|
<li>Lastname: {{$peerUser.Lastname}}</li>
|
||||||
|
<li>Phone: {{$peerUser.Phone}}</li>
|
||||||
|
<li>Mail: {{$peerUser.Email}}</li>
|
||||||
|
</ul>
|
||||||
|
{{end}}
|
||||||
|
<h4>Connection / Traffic</h4>
|
||||||
|
{{if not $p.Peer}}
|
||||||
|
<p>No Traffic data available...</p>
|
||||||
|
{{else}}
|
||||||
|
<p class="ml-4">{{if $p.DeactivatedAt}}-{{else}}<i class="fas fa-network-wired" title="Last Endpoint"></i> {{$p.Peer.Endpoint}}{{end}}</p>
|
||||||
|
<p class="ml-4">{{if $p.DeactivatedAt}}-{{else}}<i class="fas fa-long-arrow-alt-down" title="Download"></i> {{formatBytes $p.Peer.ReceiveBytes}} / <i class="fas fa-long-arrow-alt-up" title="Upload"></i> {{formatBytes $p.Peer.TransmitBytes}}{{end}}</p>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
{{if eq $.Device.Type "server"}}
|
||||||
|
<div id="t2{{$p.UID}}" class="tab-pane fade">
|
||||||
|
<pre>{{$p.Config}}</pre>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
<div id="t3{{$p.UID}}" class="tab-pane fade">
|
||||||
|
<a href="/admin/peer/delete?pkey={{$p.PublicKey}}" class="btn btn-danger" title="Delete peer">Delete</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
{{if eq $.Device.Type "server"}}
|
||||||
|
<img class="list-image-large" loading="lazy" alt="Configuration QR Code" src="/user/qrcode?pkey={{$p.PublicKey}}"/>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
{{if $p.DeactivatedAt}}
|
||||||
|
<div class="pull-right-lg mt-lg-5 disabled-peer">Peer is disabled! <i class="fas fa-comment-dots" data-toggle="tooltip" data-placement="left" title="" data-original-title="Reason: {{$p.DeactivatedReason}}"></i></div>
|
||||||
|
{{end}}
|
||||||
|
{{if $p.WillExpire}}
|
||||||
|
<div class="pull-right-lg mt-lg-5 expiring-peer"><i class="fas fa-exclamation-triangle"></i> Peer will expire on {{ formatDate $p.ExpiresAt}}</div>
|
||||||
|
{{end}}
|
||||||
|
{{if eq $.Device.Type "server"}}
|
||||||
|
<div class="pull-right-lg mt-lg-5 mt-md-3">
|
||||||
|
<a href="/admin/peer/download?pkey={{$p.PublicKey}}" class="btn btn-primary" title="Download configuration">Download</a>
|
||||||
|
<a href="/admin/peer/email?pkey={{$p.PublicKey}}" class="btn btn-primary" title="Send configuration via Email">Email</a>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{{end}}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<p>Currently listed peers: <strong>{{len .Peers}}</strong></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{template "prt_footer.html" .}}
|
||||||
|
<script src="/js/jquery.min.js"></script>
|
||||||
|
<script src="/js/jquery.easing.js"></script>
|
||||||
|
<script src="/js/popper.min.js"></script>
|
||||||
|
<script src="/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<script src="/js/bootstrap-confirmation.min.js"></script>
|
||||||
|
<script src="/js/custom.js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
69
assets/tpl/admin_user_index.html
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
|
||||||
|
<title>{{ .Static.WebsiteTitle }} - Users</title>
|
||||||
|
<meta name="description" content="{{ .Static.WebsiteTitle }}">
|
||||||
|
<link rel="stylesheet" href="/css/bootstrap.min.css">
|
||||||
|
<link rel="stylesheet" href="/fonts/fontawesome-all.min.css">
|
||||||
|
<link rel="stylesheet" href="/css/custom.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body id="page-top" class="d-flex flex-column min-vh-100">
|
||||||
|
{{template "prt_nav.html" .}}
|
||||||
|
<div class="container mt-5">
|
||||||
|
<h1>WireGuard VPN Users</h1>
|
||||||
|
{{template "prt_flashes.html" .}}
|
||||||
|
<div class="mt-4 row">
|
||||||
|
<div class="col-sm-10 col-12">
|
||||||
|
<h2 class="mt-2">All Users</h2>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-2 col-12 text-right">
|
||||||
|
<a href="/admin/users/create" title="Add a user" class="btn btn-primary"><i class="fa fa-fw fa-plus"></i>M</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-2 table-responsive">
|
||||||
|
<table class="table table-sm" id="userTable">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col"><a href="?sort=email">E-Mail <i class="fa fa-fw {{.Session.GetSortIcon "users" "email"}}"></i></a></th>
|
||||||
|
<th scope="col"><a href="?sort=lastname">Lastname <i class="fa fa-fw {{.Session.GetSortIcon "users" "lastname"}}"></i></a></th>
|
||||||
|
<th scope="col"><a href="?sort=firstname">Firstname <i class="fa fa-fw {{.Session.GetSortIcon "users" "firstname"}}"></i></a></th>
|
||||||
|
<th scope="col"><a href="?sort=source">Source <i class="fa fa-fw {{.Session.GetSortIcon "users" "source"}}"></i></a></th>
|
||||||
|
<th scope="col"><a href="?sort=admin">Is Admin <i class="fa fa-fw {{.Session.GetSortIcon "users" "admin"}}"></i></a></th>
|
||||||
|
<th scope="col"></th><!-- Actions -->
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{{range $i, $u :=.Users}}
|
||||||
|
<tr id="user-pos-{{$i}}" {{if $u.DeletedAt.Valid}}class="disabled-peer"{{end}}>
|
||||||
|
<td>{{$u.Email}}</td>
|
||||||
|
<td>{{$u.Lastname}}</td>
|
||||||
|
<td>{{$u.Firstname}}</td>
|
||||||
|
<td>{{$u.Source}}</td>
|
||||||
|
<td>{{if $u.IsAdmin}}True{{else}}False{{end}}</td>
|
||||||
|
<td>
|
||||||
|
{{if eq $.Session.IsAdmin true}}
|
||||||
|
{{if eq $u.Source "db"}}
|
||||||
|
<a href="/admin/users/edit?pkey={{$u.Email}}" title="Edit user"><i class="fas fa-cog"></i></a>
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{{end}}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<p>Currently listed users: <strong>{{len .Users}}</strong></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{template "prt_footer.html" .}}
|
||||||
|
<script src="/js/jquery.min.js"></script>
|
||||||
|
<script src="/js/jquery.easing.js"></script>
|
||||||
|
<script src="/js/popper.min.js"></script>
|
||||||
|
<script src="/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<script src="/js/bootstrap-confirmation.min.js"></script>
|
||||||
|
<script src="/js/custom.js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
@@ -19,7 +19,7 @@
|
|||||||
<!--[if !mso]><!-->
|
<!--[if !mso]><!-->
|
||||||
<link href="https://fonts.googleapis.com/css?family=Muli:400,400i,700,700i" rel="stylesheet" />
|
<link href="https://fonts.googleapis.com/css?family=Muli:400,400i,700,700i" rel="stylesheet" />
|
||||||
<!--<![endif]-->
|
<!--<![endif]-->
|
||||||
<title>{{$.PortalName}}</title>
|
<title>Email Template</title>
|
||||||
<!--[if gte mso 9]>
|
<!--[if gte mso 9]>
|
||||||
<style type="text/css" media="all">
|
<style type="text/css" media="all">
|
||||||
sup { font-size: 100% !important; }
|
sup { font-size: 100% !important; }
|
||||||
@@ -100,7 +100,7 @@
|
|||||||
<th class="column-top" width="280" style="font-size:0pt; line-height:0pt; padding:0; margin:0; font-weight:normal; vertical-align:top;">
|
<th class="column-top" width="280" style="font-size:0pt; line-height:0pt; padding:0; margin:0; font-weight:normal; vertical-align:top;">
|
||||||
<table width="100%" border="0" cellspacing="0" cellpadding="0">
|
<table width="100%" border="0" cellspacing="0" cellpadding="0">
|
||||||
<tr>
|
<tr>
|
||||||
{{if $.User.Firstname}}
|
{{if $.User}}
|
||||||
<td class="h4 pb20" style="color:#000000; font-family:'Muli', Arial,sans-serif; font-size:20px; line-height:28px; text-align:left; padding-bottom:20px;">Hello {{$.User.Firstname}} {{$.User.Lastname}}</td>
|
<td class="h4 pb20" style="color:#000000; font-family:'Muli', Arial,sans-serif; font-size:20px; line-height:28px; text-align:left; padding-bottom:20px;">Hello {{$.User.Firstname}} {{$.User.Lastname}}</td>
|
||||||
{{else}}
|
{{else}}
|
||||||
<td class="h4 pb20" style="color:#000000; font-family:'Muli', Arial,sans-serif; font-size:20px; line-height:28px; text-align:left; padding-bottom:20px;">Hello</td>
|
<td class="h4 pb20" style="color:#000000; font-family:'Muli', Arial,sans-serif; font-size:20px; line-height:28px; text-align:left; padding-bottom:20px;">Hello</td>
|
||||||
@@ -143,7 +143,7 @@
|
|||||||
<td align="left">
|
<td align="left">
|
||||||
<table border="0" cellspacing="0" cellpadding="0">
|
<table border="0" cellspacing="0" cellpadding="0">
|
||||||
<tr>
|
<tr>
|
||||||
<td class="blue-button text-button" style="background:#000000; color:#ffffff; font-family:'Muli', Arial,sans-serif; font-size:14px; line-height:18px; padding:12px 30px; text-align:center; border-radius:0px 22px 22px 22px; font-weight:bold;"><a href="https://www.wireguard.com/install" target="_blank" class="link-white" style="color:#ffffff; text-decoration:none;"><span class="link-white" style="color:#ffffff; text-decoration:none;">Download WireGuard VPN Client</span></a></td>
|
<td class="blue-button text-button" style="background:#000000; color:#c1cddc; font-family:'Muli', Arial,sans-serif; font-size:14px; line-height:18px; padding:12px 30px; text-align:center; border-radius:0px 22px 22px 22px; font-weight:bold;"><a href="https://www.wireguard.com/install" target="_blank" class="link-white" style="color:#ffffff; text-decoration:none;"><span class="link-white" style="color:#ffffff; text-decoration:none;">Download WireGuard VPN Client</span></a></td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</td>
|
</td>
|
||||||
@@ -167,10 +167,10 @@
|
|||||||
<td class="p30-15 bbrr" style="padding: 50px 30px; border-radius:0px 0px 26px 26px;" bgcolor="#ffffff">
|
<td class="p30-15 bbrr" style="padding: 50px 30px; border-radius:0px 0px 26px 26px;" bgcolor="#ffffff">
|
||||||
<table width="100%" border="0" cellspacing="0" cellpadding="0">
|
<table width="100%" border="0" cellspacing="0" cellpadding="0">
|
||||||
<tr>
|
<tr>
|
||||||
<td class="text-footer1 pb10" style="color:#000000; font-family:'Muli', Arial,sans-serif; font-size:16px; line-height:20px; text-align:center; padding-bottom:10px;">This mail was generated by {{$.PortalName}}.</td>
|
<td class="text-footer1 pb10" style="color:#000000; font-family:'Muli', Arial,sans-serif; font-size:16px; line-height:20px; text-align:center; padding-bottom:10px;">This mail was generated using WireGuard Portal.</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="text-footer2" style="color:#000000; font-family:'Muli', Arial,sans-serif; font-size:12px; line-height:26px; text-align:center;"><a href="{{$.PortalUrl}}" target="_blank" rel="noopener noreferrer" class="link" style="color:#000000; text-decoration:none;"><span class="link" style="color:#000000; text-decoration:none;">Visit {{$.PortalName}}</span></a></td>
|
<td class="text-footer2" style="color:#000000; font-family:'Muli', Arial,sans-serif; font-size:12px; line-height:26px; text-align:center;"><a href="{{$.PortalUrl}}" target="_blank" rel="noopener noreferrer" class="link" style="color:#000000; text-decoration:none;"><span class="link" style="color:#000000; text-decoration:none;">Visit WireGuard Portal</span></a></td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</td>
|
</td>
|
33
assets/tpl/error.html
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
|
||||||
|
<title>{{ .Static.WebsiteTitle }} - Error</title>
|
||||||
|
<meta name="description" content="{{ .Static.WebsiteTitle }}">
|
||||||
|
<link rel="stylesheet" href="/css/bootstrap.min.css">
|
||||||
|
<link rel="stylesheet" href="/fonts/fontawesome-all.min.css">
|
||||||
|
<link rel="stylesheet" href="/css/custom.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body id="page-top">
|
||||||
|
{{template "prt_nav.html" .}}
|
||||||
|
<div class="container">
|
||||||
|
<div class="text-center mt-5">
|
||||||
|
<div class="error mx-auto" data-text="{{.Data.Code}}">
|
||||||
|
<p class="m-0">{{.Data.Code}}</p>
|
||||||
|
</div>
|
||||||
|
<p class="text-dark mb-5 lead">{{.Data.Message}}</p>
|
||||||
|
<p class="text-black-50 mb-0">{{.Data.Details}}</p><a href="/">← Back to Dashboard</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{template "prt_footer.html" .}}
|
||||||
|
<script src="/js/jquery.min.js"></script>
|
||||||
|
<script src="/js/jquery.easing.js"></script>
|
||||||
|
<script src="/js/popper.min.js"></script>
|
||||||
|
<script src="/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<script src="/js/bootstrap-confirmation.min.js"></script>
|
||||||
|
<script src="/js/custom.js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
89
assets/tpl/index.html
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<!-- Theme: https://bootswatch.com/lux/ -->
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
|
||||||
|
<title>{{ .Static.WebsiteTitle }}</title>
|
||||||
|
<meta name="description" content="{{ .Static.WebsiteTitle }}">
|
||||||
|
<link rel="stylesheet" href="/css/bootstrap.min.css">
|
||||||
|
<link rel="stylesheet" href="/fonts/fontawesome-all.min.css">
|
||||||
|
<link rel="stylesheet" href="/css/custom.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body id="page-top" class="d-flex flex-column min-vh-100">
|
||||||
|
{{template "prt_nav.html" .}}
|
||||||
|
<div class="container mt-2">
|
||||||
|
<div class="page-header">
|
||||||
|
<h1>{{ .Static.WebsiteTitle }}</h1>
|
||||||
|
</div>
|
||||||
|
{{template "prt_flashes.html" .}}
|
||||||
|
<p class="lead">WireGuard® is an extremely simple yet fast and modern VPN that utilizes state-of-the-art cryptography. It aims to be faster, simpler, leaner, and more useful than IPsec, while avoiding the massive headache. It intends to be considerably more performant than OpenVPN. </p>
|
||||||
|
<h3 class="mt-3">More Information</h3>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-4">
|
||||||
|
<div class="card border-secondary mb-4" style="min-height: 15rem;">
|
||||||
|
<div class="card-header">WireGuard Installation</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<h4 class="card-title">Installation</h4>
|
||||||
|
<p class="card-text">Installation instructions for client software can be found on the official WireGuard website.</p>
|
||||||
|
<a href="https://www.wireguard.com/install/" title="WireGuard Installation" target="_blank" rel="noopener noreferrer" class="btn btn-primary btn-sm">Open Instructions</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-4">
|
||||||
|
<div class="card border-secondary mb-4" style="min-height: 15rem;">
|
||||||
|
<div class="card-header">About WireGuard</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<h4 class="card-title">About</h4>
|
||||||
|
<p class="card-text">WireGuard® is an extremely simple yet fast and modern VPN that utilizes state-of-the-art cryptography.</p>
|
||||||
|
<a href="https://www.wireguard.com/" title="WireGuard" target="_blank" rel="noopener noreferrer" class="btn btn-primary btn-sm">More details</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-4">
|
||||||
|
<div class="card border-secondary mb-4" style="min-height: 15rem;">
|
||||||
|
<div class="card-header">About WireGuard Portal</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<h4 class="card-title">WireGuard Portal</h4>
|
||||||
|
<p class="card-text">WireGuard Portal is a simple, web based configuration portal for WireGuard.</p>
|
||||||
|
<a href="https://github.com/h44z/wg-portal/" title="WireGuard Portal" target="_blank" rel="noopener noreferrer" class="btn btn-primary btn-sm">More details</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="jumbotron jumbotron-home">
|
||||||
|
<h2 class="display-5">VPN Profiles</h2>
|
||||||
|
<p class="lead">You can access and download your personal VPN configurations via your Userprofile.</p>
|
||||||
|
<hr class="my-4">
|
||||||
|
<p>To find all your configured profiles click on the button below.</p>
|
||||||
|
<p class="lead">
|
||||||
|
<a href="/user/profile" class="btn btn-primary btn-lg" title="User-Profile">Open My Profile</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{with eq $.Session.LoggedIn true}}{{with eq $.Session.IsAdmin true}}
|
||||||
|
<div class="jumbotron jumbotron-home">
|
||||||
|
<h2 class="display-5">Administration Area</h2>
|
||||||
|
<p class="lead">In the administration area you can manage WireGuard peers and the server interface as well as users that are allowed to log in to the WireGuard Portal.</p>
|
||||||
|
<hr class="my-4">
|
||||||
|
<p>To find all your configured profiles click on the button below.</p>
|
||||||
|
<p class="lead">
|
||||||
|
<a href="/admin/" class="btn btn-primary btn-lg" title="WireGuard Administration">Open WireGuard Administration</a>
|
||||||
|
<a href="/admin/users/" class="btn btn-primary btn-lg" title="User Administration">Open User Administration</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{{end}}{{end}}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
{{template "prt_footer.html" .}}
|
||||||
|
<script src="/js/jquery.min.js"></script>
|
||||||
|
<script src="/js/jquery.easing.js"></script>
|
||||||
|
<script src="/js/popper.min.js"></script>
|
||||||
|
<script src="/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<script src="/js/bootstrap-confirmation.min.js"></script>
|
||||||
|
<script src="/js/custom.js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
66
assets/tpl/login.html
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
|
||||||
|
<title>{{ .static.WebsiteTitle }} - Login</title>
|
||||||
|
<meta name="description" content="{{ .static.WebsiteTitle }}">
|
||||||
|
<link rel="stylesheet" href="/css/bootstrap.min.css">
|
||||||
|
<link rel="stylesheet" href="/fonts/fontawesome-all.min.css">
|
||||||
|
<link rel="stylesheet" href="/fonts/font-awesome.min.css">
|
||||||
|
<link rel="stylesheet" href="/fonts/fontawesome5-overrides.min.css">
|
||||||
|
<link rel="stylesheet" href="/css/signin.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body id="page-top" class="d-flex flex-column min-vh-100">
|
||||||
|
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
|
||||||
|
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#topNavbar" aria-controls="topNavbar" aria-expanded="false" aria-label="Toggle navigation">
|
||||||
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<a class="navbar-brand" href="/"><img src="{{$.static.WebsiteLogo}}" alt="{{$.static.CompanyName}}"/></a>
|
||||||
|
<div id="topNavbar" class="navbar-collapse collapse">
|
||||||
|
</div><!--/.navbar-collapse -->
|
||||||
|
</nav>
|
||||||
|
<div class="container mt-1">
|
||||||
|
<div class="card mt-5">
|
||||||
|
<div class="card-header">Please sign in</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<form class="form-signin" method="post" name="login">
|
||||||
|
<input type="hidden" name="_csrf" value="{{.Csrf}}">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="inputUsername">Username</label>
|
||||||
|
<input type="text" name="username" class="form-control" id="inputUsername" aria-describedby="usernameHelp" placeholder="Enter username or email">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="inputPassword">Password</label>
|
||||||
|
<input type="password" name="password" class="form-control" id="inputPassword" placeholder="Password">
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-lg btn-primary btn-block mt-5" type="submit">Sign in</button>
|
||||||
|
|
||||||
|
{{ if eq .error true }}
|
||||||
|
<div class="alert alert-danger mt-3" role="alert">
|
||||||
|
{{.message}}
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div class="card o-hidden border-0 my-5">
|
||||||
|
<div class="card-body p-0">
|
||||||
|
<a href="/" class="btn btn-white btn-block text-primary btn-user">Go Home</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{template "prt_flashes.html" .}}
|
||||||
|
</div>
|
||||||
|
<script src="/js/jquery.min.js"></script>
|
||||||
|
<script src="/js/jquery.easing.js"></script>
|
||||||
|
<script src="/js/popper.min.js"></script>
|
||||||
|
<script src="/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<script src="/js/bootstrap-confirmation.min.js"></script>
|
||||||
|
<script src="/js/custom.js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
5
assets/tpl/prt_flashes.html
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{{range $flash := $.Alerts}}
|
||||||
|
<div class="alert alert-{{$flash.Type}}" role="alert">
|
||||||
|
{{$flash.Message}}
|
||||||
|
</div>
|
||||||
|
{{end}}
|
5
assets/tpl/prt_footer.html
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<footer class="page-footer mt-auto">
|
||||||
|
<div class="container mt-3">
|
||||||
|
<p class="text-muted">Copyright © {{ $.Static.CompanyName }} {{$.Static.Year}}, version {{$.Static.Version}} <a class="float-right scroll-to-top" href="#page-top"><i class="fas fa-angle-up"></i></a></p>
|
||||||
|
</div>
|
||||||
|
</footer>
|
61
assets/tpl/prt_nav.html
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
|
||||||
|
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#topNavbar" aria-controls="topNavbar" aria-expanded="false" aria-label="Toggle navigation">
|
||||||
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<a class="navbar-brand" href="/"><img src="{{$.Static.WebsiteLogo}}" alt="{{$.Static.CompanyName}}"/></a>
|
||||||
|
<div id="topNavbar" class="navbar-collapse collapse">
|
||||||
|
<ul class="navbar-nav mr-auto mt-2 mt-lg-0">
|
||||||
|
<li class="nav-spacer"></li>
|
||||||
|
{{with eq $.Session.LoggedIn true}}{{with eq $.Session.IsAdmin true}}
|
||||||
|
{{with eq $.Route "/admin/"}}
|
||||||
|
<form class="form-inline my-2 my-lg-0" method="get">
|
||||||
|
<input class="form-control mr-sm-2" name="search" type="search" placeholder="Search" aria-label="Search" value="{{index $.Session.Search "peers"}}">
|
||||||
|
<button class="btn btn-outline-success my-2 my-sm-0" type="submit"><i class="fa fa-search"></i></button>
|
||||||
|
</form>
|
||||||
|
{{end}}
|
||||||
|
{{with eq $.Route "/admin/users/"}}
|
||||||
|
<form class="form-inline my-2 my-lg-0" method="get">
|
||||||
|
<input class="form-control mr-sm-2" name="search" type="search" placeholder="Search" aria-label="Search" value="{{index $.Session.Search "users"}}">
|
||||||
|
<button class="btn btn-outline-success my-2 my-sm-0" type="submit"><i class="fa fa-search"></i></button>
|
||||||
|
</form>
|
||||||
|
{{end}}
|
||||||
|
{{end}}{{end}}
|
||||||
|
</ul>
|
||||||
|
{{with eq $.Session.LoggedIn true}}{{with eq $.Session.IsAdmin true}}
|
||||||
|
{{with startsWith $.Route "/admin/"}}
|
||||||
|
<form class="form-inline my-2 my-lg-0" method="get">
|
||||||
|
<div class="form-group mr-sm-2">
|
||||||
|
<select name="device" id="inputDevice" class="form-control device-selector">
|
||||||
|
{{range $d, $dn := $.DeviceNames}}
|
||||||
|
<option value="{{$d}}" {{if eq $d $.Session.DeviceName}}selected{{end}}>{{$d}} {{if and (ne $dn "") (ne $d $dn)}}({{$dn}}){{end}}</option>
|
||||||
|
{{end}}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{{end}}
|
||||||
|
{{end}}{{end}}
|
||||||
|
{{if eq $.Session.LoggedIn true}}
|
||||||
|
<div class="nav-item dropdown">
|
||||||
|
<a href="#" class="navbar-text dropdown-toggle" data-toggle="dropdown">{{$.Session.Firstname}} {{$.Session.Lastname}} <span class="caret"></span></a>
|
||||||
|
<div class="dropdown-menu">
|
||||||
|
{{with eq $.Session.LoggedIn true}}{{with eq $.Session.IsAdmin true}}
|
||||||
|
<a class="dropdown-item" href="/admin/"><i class="fas fa-cogs"></i> Administration</a>
|
||||||
|
<a class="dropdown-item" href="/admin/users/"><i class="fas fa-users-cog"></i> User Management</a>
|
||||||
|
<div class="dropdown-divider"></div>
|
||||||
|
{{end}}{{end}}
|
||||||
|
<a class="dropdown-item" href="/user/profile"><i class="fas fa-user"></i> Profile</a>
|
||||||
|
<div class="dropdown-divider"></div>
|
||||||
|
<a class="dropdown-item" href="/auth/logout"><i class="fas fa-sign-out-alt"></i> Logout</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{else}}
|
||||||
|
<a href="/auth/login" class="navbar-text"><i class="fas fa-sign-in-alt fa-sm fa-fw mr-2 text-gray-400"></i> Login</a></li>
|
||||||
|
{{end}}
|
||||||
|
</div><!--/.navbar-collapse -->
|
||||||
|
</nav>
|
||||||
|
{{if not $.Device.IsValid}}
|
||||||
|
<div class="container">
|
||||||
|
<div class="alert alert-danger">Warning: WireGuard Interface {{$.Device.DeviceName}} is not fully configured! Configurations may be incomplete and non functional!</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
44
assets/tpl/user_create_client.html
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
|
||||||
|
<title>{{ .Static.WebsiteTitle }} - Admin</title>
|
||||||
|
<meta name="description" content="{{ .Static.WebsiteTitle }}">
|
||||||
|
<link rel="stylesheet" href="/css/bootstrap.min.css">
|
||||||
|
<link rel="stylesheet" href="/fonts/fontawesome-all.min.css">
|
||||||
|
<link rel="stylesheet" href="/css/custom.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body id="page-top" class="d-flex flex-column min-vh-100">
|
||||||
|
{{template "prt_nav.html" .}}
|
||||||
|
<div class="container mt-5">
|
||||||
|
{{template "prt_flashes.html" .}}
|
||||||
|
|
||||||
|
<!-- server mode -->
|
||||||
|
<h1>Create a new client</h1>
|
||||||
|
|
||||||
|
<form method="post" enctype="multipart/form-data">
|
||||||
|
<input type="hidden" name="_csrf" value="{{.Csrf}}">
|
||||||
|
<input type="hidden" name="uid" value="{{.Peer.UID}}">
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group required col-md-12">
|
||||||
|
<label for="server_PublicKey">Public Key</label>
|
||||||
|
<input type="text" name="pubkey" class="form-control" id="server_PublicKey" value="{{.Peer.PublicKey}}" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit" class="btn btn-primary">Save</button>
|
||||||
|
<a href="/user/profile" class="btn btn-secondary">Cancel</a>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{{template "prt_footer.html" .}}
|
||||||
|
<script src="/js/jquery.min.js"></script>
|
||||||
|
<script src="/js/jquery.easing.js"></script>
|
||||||
|
<script src="/js/popper.min.js"></script>
|
||||||
|
<script src="/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<script src="/js/bootstrap-confirmation.min.js"></script>
|
||||||
|
<script src="/js/custom.js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
54
assets/tpl/user_edit_client.html
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
|
||||||
|
<title>{{ .Static.WebsiteTitle }} - Admin</title>
|
||||||
|
<meta name="description" content="{{ .Static.WebsiteTitle }}">
|
||||||
|
<link rel="stylesheet" href="/css/bootstrap.min.css">
|
||||||
|
<link rel="stylesheet" href="/fonts/fontawesome-all.min.css">
|
||||||
|
<link rel="stylesheet" href="/css/custom.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body id="page-top" class="d-flex flex-column min-vh-100">
|
||||||
|
{{template "prt_nav.html" .}}
|
||||||
|
<div class="container mt-5">
|
||||||
|
{{template "prt_flashes.html" .}}
|
||||||
|
|
||||||
|
<!-- server mode -->
|
||||||
|
<h1>Edit client: <strong>{{.Peer.Identifier}}</strong></h1>
|
||||||
|
|
||||||
|
<form method="post" enctype="multipart/form-data">
|
||||||
|
<input type="hidden" name="_csrf" value="{{.Csrf}}">
|
||||||
|
<input type="hidden" name="uid" value="{{.Peer.UID}}">
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group required col-md-12">
|
||||||
|
<label for="server_PublicKey">Public Key</label>
|
||||||
|
<input type="text" name="pubkey" class="form-control" id="server_PublicKey" value="{{.Peer.PublicKey}}" required disabled="disabled">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group col-md-12">
|
||||||
|
<div class="custom-control custom-switch">
|
||||||
|
<input class="custom-control-input" name="isdisabled" type="checkbox" value="true" id="server_Disabled" {{if .Peer.DeactivatedAt}}disabled="disabled"{{end}} {{if .Peer.DeactivatedAt}}checked{{end}}>
|
||||||
|
<label class="custom-control-label" for="server_Disabled">
|
||||||
|
Disabled
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary">Save</button>
|
||||||
|
<a href="/user/profile" class="btn btn-secondary">Cancel</a>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{{template "prt_footer.html" .}}
|
||||||
|
<script src="/js/jquery.min.js"></script>
|
||||||
|
<script src="/js/jquery.easing.js"></script>
|
||||||
|
<script src="/js/popper.min.js"></script>
|
||||||
|
<script src="/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<script src="/js/bootstrap-confirmation.min.js"></script>
|
||||||
|
<script src="/js/custom.js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
137
assets/tpl/user_index.html
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
|
||||||
|
<title>{{ .Static.WebsiteTitle }} - Profile</title>
|
||||||
|
<meta name="description" content="{{ .Static.WebsiteTitle }}">
|
||||||
|
<link rel="stylesheet" href="/css/bootstrap.min.css">
|
||||||
|
<link rel="stylesheet" href="/fonts/fontawesome-all.min.css">
|
||||||
|
<link rel="stylesheet" href="/css/custom.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body id="page-top" class="d-flex flex-column min-vh-100">
|
||||||
|
{{template "prt_nav.html" .}}
|
||||||
|
<div class="container mt-5">
|
||||||
|
<h1>WireGuard VPN User-Portal</h1>
|
||||||
|
|
||||||
|
<div class="mt-4 row">
|
||||||
|
<div class="col-sm-8 col-12">
|
||||||
|
<h2 class="mt-2">Your VPN Profiles</h2>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-4 col-12 text-right">
|
||||||
|
{{if eq $.UserManagePeers true}}
|
||||||
|
<a href="/user/peer/create" title="Add a peer" class="btn btn-primary"><i class="fa fa-fw fa-plus"></i><i class="fa fa-fw fa-user"></i></a>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-2 table-responsive">
|
||||||
|
<table class="table table-sm" id="userTable">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col" class="list-image-cell"></th><!-- Status and expand -->
|
||||||
|
<th scope="col"><a href="?sort=id">Identifier <i class="fa fa-fw {{.Session.GetSortIcon "userpeers" "id"}}"></i></a></th>
|
||||||
|
<th scope="col"><a href="?sort=pubKey">Public Key <i class="fa fa-fw {{.Session.GetSortIcon "userpeers" "pubKey"}}"></i></a></th>
|
||||||
|
<th scope="col"><a href="?sort=mail">E-Mail <i class="fa fa-fw {{.Session.GetSortIcon "userpeers" "mail"}}"></i></a></th>
|
||||||
|
<th scope="col"><a href="?sort=ip">IP's <i class="fa fa-fw {{.Session.GetSortIcon "userpeers" "ip"}}"></i></a></th>
|
||||||
|
<th scope="col"><a href="?sort=device">Interface <i class="fa fa-fw {{.Session.GetSortIcon "userpeers" "device"}}"></i></a></th>
|
||||||
|
<th scope="col"><a href="?sort=handshake">Handshake <i class="fa fa-fw {{.Session.GetSortIcon "userpeers" "handshake"}}"></i></a></th>
|
||||||
|
{{if eq $.UserManagePeers true}}
|
||||||
|
<th scope="col"></th>
|
||||||
|
{{end}}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{{range $i, $p :=.Peers}}
|
||||||
|
{{$peerUser:=(userForEmail $.Users $p.Email)}}
|
||||||
|
<tr id="user-pos-{{$i}}" {{if $p.DeactivatedAt}}class="disabled-peer"{{end}}>
|
||||||
|
<th scope="row" class="list-image-cell">
|
||||||
|
<a href="#{{$p.UID}}" data-toggle="collapse" class="collapse-indicator collapsed"></a>
|
||||||
|
<!-- online check -->
|
||||||
|
<span class="online-status" id="online-{{$p.UID}}" data-pkey="{{$p.PublicKey}}"><i class="fas fa-unlink"></i></span>
|
||||||
|
</th>
|
||||||
|
<td>{{$p.Identifier}}{{if $p.WillExpire}} <i class="fas fa-hourglass-end expiring-peer" data-toggle="tooltip" data-placement="right" title="" data-original-title="Expires at: {{formatDate $p.ExpiresAt}}"></i>{{end}}</td>
|
||||||
|
<td>{{$p.PublicKey}}</td>
|
||||||
|
<td>{{$p.Email}}</td>
|
||||||
|
<td>{{$p.IPsStr}}</td>
|
||||||
|
<td>{{$p.DeviceName}}</td>
|
||||||
|
<td><span data-toggle="tooltip" data-placement="left" title="" data-original-title="{{$p.LastHandshakeTime}}">{{$p.LastHandshake}}</span></td>
|
||||||
|
{{if eq $.UserManagePeers true}}
|
||||||
|
<td>
|
||||||
|
<a href="/user/peer/edit?pkey={{$p.PublicKey}}" title="Edit peer"><i class="fas fa-cog"></i></a>
|
||||||
|
</td>
|
||||||
|
{{end}}
|
||||||
|
</tr>
|
||||||
|
<tr class="hiddenRow">
|
||||||
|
<td colspan="6" class="hiddenCell" style="white-space:nowrap">
|
||||||
|
<div class="collapse" id="{{$p.UID}}" data-parent="#userTable">
|
||||||
|
<div class="row collapsedRow">
|
||||||
|
<div class="col-md-6 leftBorder">
|
||||||
|
<ul class="nav nav-tabs">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link active" data-toggle="tab" href="#t1{{$p.UID}}">Personal</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" data-toggle="tab" href="#t2{{$p.UID}}">Configuration</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<div class="tab-content" id="tabContent{{$p.UID}}">
|
||||||
|
<div id="t1{{$p.UID}}" class="tab-pane fade active show">
|
||||||
|
<h4>User details</h4>
|
||||||
|
{{if not $peerUser}}
|
||||||
|
<p>No user information available...</p>
|
||||||
|
{{else}}
|
||||||
|
<ul>
|
||||||
|
<li>Firstname: {{$peerUser.Firstname}}</li>
|
||||||
|
<li>Lastname: {{$peerUser.Lastname}}</li>
|
||||||
|
<li>Phone: {{$peerUser.Phone}}</li>
|
||||||
|
<li>Mail: {{$peerUser.Email}}</li>
|
||||||
|
</ul>
|
||||||
|
{{end}}
|
||||||
|
<h4>Traffic</h4>
|
||||||
|
{{if not $p.Peer}}
|
||||||
|
<p>No Traffic data available...</p>
|
||||||
|
{{else}}
|
||||||
|
<p>{{if $p.DeactivatedAt}}-{{else}}<i class="fas fa-long-arrow-alt-down"></i></i> {{formatBytes $p.Peer.ReceiveBytes}} / <i class="fas fa-long-arrow-alt-up"></i> {{formatBytes $p.Peer.TransmitBytes}}{{end}}</p>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
<div id="t2{{$p.UID}}" class="tab-pane fade">
|
||||||
|
<pre>{{$p.Config}}</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
<img class="list-image-large" src="/user/qrcode?pkey={{$p.PublicKey}}"/>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
{{if $p.DeactivatedAt}}
|
||||||
|
<div class="pull-right-lg mt-lg-5 disabled-peer">Peer is disabled! <i class="fas fa-comment-dots" data-toggle="tooltip" data-placement="left" title="" data-original-title="Reason: {{$p.DeactivatedReason}}"></i></div>
|
||||||
|
{{end}}
|
||||||
|
{{if $p.WillExpire}}
|
||||||
|
<div class="pull-right-lg mt-lg-5 expiring-peer"><i class="fas fa-exclamation-triangle"></i> Profile expires on {{ formatDate $p.ExpiresAt}}</div>
|
||||||
|
{{end}}
|
||||||
|
<div class="pull-right-lg mt-lg-5 mt-md-3">
|
||||||
|
<a href="/user/download?pkey={{$p.PublicKey}}" class="btn btn-primary" title="Download configuration">Download</a>
|
||||||
|
<a href="/user/email?pkey={{$p.PublicKey}}" class="btn btn-primary" title="Send configuration via Email">Email</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{{end}}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<p>Currently listed peers: <strong>{{len .Peers}}</strong></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{template "prt_footer.html" .}}
|
||||||
|
<script src="/js/jquery.min.js"></script>
|
||||||
|
<script src="/js/jquery.easing.js"></script>
|
||||||
|
<script src="/js/popper.min.js"></script>
|
||||||
|
<script src="/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<script src="/js/bootstrap-confirmation.min.js"></script>
|
||||||
|
<script src="/js/custom.js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
@@ -1,117 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/swaggo/swag"
|
|
||||||
"github.com/swaggo/swag/gen"
|
|
||||||
"gopkg.in/yaml.v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
var apiRootPath = "/internal/app/api"
|
|
||||||
var apiDocPath = "core/assets/doc"
|
|
||||||
var apiMkDocPath = "/docs/documentation/rest-api"
|
|
||||||
|
|
||||||
// this replaces the call to: swag init --propertyStrategy pascalcase --parseDependency --parseInternal --generalInfo base.go
|
|
||||||
func main() {
|
|
||||||
wd, err := os.Getwd() // should be the project root
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
apiBasePath := filepath.Join(wd, apiRootPath)
|
|
||||||
apis := []string{"v0", "v1"}
|
|
||||||
|
|
||||||
for _, apiVersion := range apis {
|
|
||||||
apiPath := filepath.Join(apiBasePath, apiVersion, "handlers")
|
|
||||||
|
|
||||||
apiVersion = strings.TrimLeft(apiVersion, "api-")
|
|
||||||
log.Println("")
|
|
||||||
log.Println("Generate swagger docs for API", apiVersion)
|
|
||||||
log.Println("Api path:", apiPath)
|
|
||||||
|
|
||||||
err := generateApi(apiBasePath, apiPath, apiVersion)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("failed to generate API docs for %s: %v", apiVersion, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// copy the latest version of the API docs for mkdocs
|
|
||||||
if apiVersion == apis[len(apis)-1] {
|
|
||||||
if err = copyDocForMkdocs(wd, apiBasePath, apiVersion); err != nil {
|
|
||||||
log.Printf("failed to copy API docs for mkdocs: %v", err)
|
|
||||||
} else {
|
|
||||||
log.Println("Copied API docs " + apiVersion + " for mkdocs")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Println("Generated swagger docs for API", apiVersion)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func generateApi(basePath, apiPath, version string) error {
|
|
||||||
err := gen.New().Build(&gen.Config{
|
|
||||||
SearchDir: apiPath,
|
|
||||||
Excludes: "",
|
|
||||||
MainAPIFile: "base.go",
|
|
||||||
PropNamingStrategy: swag.PascalCase,
|
|
||||||
OutputDir: filepath.Join(basePath, apiDocPath),
|
|
||||||
OutputTypes: []string{"json", "yaml"},
|
|
||||||
ParseVendor: false,
|
|
||||||
ParseDependency: 3,
|
|
||||||
MarkdownFilesDir: "",
|
|
||||||
ParseInternal: true,
|
|
||||||
GeneratedTime: false,
|
|
||||||
CodeExampleFilesDir: "",
|
|
||||||
ParseDepth: 3,
|
|
||||||
InstanceName: version,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("swag failed: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func copyDocForMkdocs(workingDir, basePath, version string) error {
|
|
||||||
srcPath := filepath.Join(basePath, apiDocPath, fmt.Sprintf("%s_swagger.yaml", version))
|
|
||||||
dstPath := filepath.Join(workingDir, apiMkDocPath, "swagger.yaml")
|
|
||||||
|
|
||||||
// copy the file
|
|
||||||
input, err := os.ReadFile(srcPath)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error while reading swagger doc: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
output, err := removeAuthorizeButton(input)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error while removing authorize button: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = os.WriteFile(dstPath, output, 0644)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error while writing swagger doc: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func removeAuthorizeButton(input []byte) ([]byte, error) {
|
|
||||||
var swagger map[string]any
|
|
||||||
err := yaml.Unmarshal(input, &swagger)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error while unmarshalling swagger file: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
delete(swagger, "securityDefinitions")
|
|
||||||
|
|
||||||
output, err := yaml.Marshal(&swagger)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error while marshalling swagger file: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return output, nil
|
|
||||||
}
|
|
35
cmd/hc/main.go
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
// source taken from https://git.prolicht.digital/golib/healthcheck/-/blob/master/cmd/hc/main.go
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// main checks the given URL, if the response is not 200, it will return with exit code 1
|
||||||
|
// on success, exit code 0 will be returned
|
||||||
|
func main() {
|
||||||
|
os.Exit(checkWebEndpointFromArgs())
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkWebEndpointFromArgs() int {
|
||||||
|
if len(os.Args) < 2 {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
if status := checkWebEndpoint(os.Args[1]); !status {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkWebEndpoint(url string) bool {
|
||||||
|
client := &http.Client{
|
||||||
|
Timeout: time.Second * 2,
|
||||||
|
}
|
||||||
|
if resp, err := client.Get(url); err != nil || resp.StatusCode < 200 || resp.StatusCode > 299 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
@@ -2,193 +2,103 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"log/slog"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"runtime"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/go-playground/validator/v10"
|
"github.com/h44z/wg-portal/internal/common/healthcheck"
|
||||||
evbus "github.com/vardius/message-bus"
|
"github.com/h44z/wg-portal/internal/server"
|
||||||
"gorm.io/gorm/schema"
|
"github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"github.com/h44z/wg-portal/internal"
|
|
||||||
"github.com/h44z/wg-portal/internal/adapters"
|
|
||||||
"github.com/h44z/wg-portal/internal/app"
|
|
||||||
"github.com/h44z/wg-portal/internal/app/api/core"
|
|
||||||
backendV0 "github.com/h44z/wg-portal/internal/app/api/v0/backend"
|
|
||||||
handlersV0 "github.com/h44z/wg-portal/internal/app/api/v0/handlers"
|
|
||||||
backendV1 "github.com/h44z/wg-portal/internal/app/api/v1/backend"
|
|
||||||
handlersV1 "github.com/h44z/wg-portal/internal/app/api/v1/handlers"
|
|
||||||
"github.com/h44z/wg-portal/internal/app/audit"
|
|
||||||
"github.com/h44z/wg-portal/internal/app/auth"
|
|
||||||
"github.com/h44z/wg-portal/internal/app/configfile"
|
|
||||||
"github.com/h44z/wg-portal/internal/app/mail"
|
|
||||||
"github.com/h44z/wg-portal/internal/app/route"
|
|
||||||
"github.com/h44z/wg-portal/internal/app/users"
|
|
||||||
"github.com/h44z/wg-portal/internal/app/webhooks"
|
|
||||||
"github.com/h44z/wg-portal/internal/app/wireguard"
|
|
||||||
"github.com/h44z/wg-portal/internal/config"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// main entry point for WireGuard Portal
|
|
||||||
func main() {
|
func main() {
|
||||||
ctx := internal.SignalAwareContext(context.Background(), syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM)
|
_ = setupLogger(logrus.StandardLogger())
|
||||||
|
|
||||||
slog.Info("Starting WireGuard Portal V2...", "version", internal.Version)
|
c := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(c, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP)
|
||||||
|
|
||||||
cfg, err := config.GetConfig()
|
logrus.Infof("sysinfo: os=%s, arch=%s", runtime.GOOS, runtime.GOARCH)
|
||||||
internal.AssertNoError(err)
|
logrus.Infof("starting WireGuard Portal Server [%s]...", server.Version)
|
||||||
internal.SetupLogging(cfg.Advanced.LogLevel, cfg.Advanced.LogPretty, cfg.Advanced.LogJson)
|
|
||||||
|
|
||||||
cfg.LogStartupValues()
|
// Context for clean shutdown
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
dbEncryptedSerializer := app.NewGormEncryptedStringSerializer(cfg.Database.EncryptionPassphrase)
|
// start health check service on port 11223
|
||||||
schema.RegisterSerializer("encstr", dbEncryptedSerializer)
|
healthcheck.New(healthcheck.ListenOn("127.0.0.1:11223")).StartWithContext(ctx)
|
||||||
rawDb, err := adapters.NewDatabase(cfg.Database)
|
|
||||||
internal.AssertNoError(err)
|
|
||||||
|
|
||||||
database, err := adapters.NewSqlRepository(rawDb)
|
service := server.Server{}
|
||||||
internal.AssertNoError(err)
|
if err := service.Setup(ctx); err != nil {
|
||||||
|
logrus.Fatalf("setup failed: %v", err)
|
||||||
wireGuard := adapters.NewWireGuardRepository()
|
|
||||||
|
|
||||||
wgQuick := adapters.NewWgQuickRepo()
|
|
||||||
|
|
||||||
mailer := adapters.NewSmtpMailRepo(cfg.Mail)
|
|
||||||
|
|
||||||
metricsServer := adapters.NewMetricsServer(cfg)
|
|
||||||
|
|
||||||
cfgFileSystem, err := adapters.NewFileSystemRepository(cfg.Advanced.ConfigStoragePath)
|
|
||||||
internal.AssertNoError(err)
|
|
||||||
|
|
||||||
shouldExit, err := app.HandleProgramArgs(rawDb)
|
|
||||||
switch {
|
|
||||||
case shouldExit && err == nil:
|
|
||||||
return
|
|
||||||
case shouldExit:
|
|
||||||
slog.Error("Failed to process program args", "error", err)
|
|
||||||
os.Exit(1)
|
|
||||||
default:
|
|
||||||
internal.AssertNoError(err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
queueSize := 100
|
// Attach signal handlers to context
|
||||||
eventBus := evbus.New(queueSize)
|
go func() {
|
||||||
|
osCall := <-c
|
||||||
|
logrus.Tracef("received system call: %v", osCall)
|
||||||
|
cancel() // cancel the context
|
||||||
|
}()
|
||||||
|
|
||||||
auditManager := audit.NewManager(database)
|
// Start main process in background
|
||||||
|
go service.Run()
|
||||||
|
|
||||||
auditRecorder, err := audit.NewAuditRecorder(cfg, eventBus, database)
|
<-ctx.Done() // Wait until the context gets canceled
|
||||||
internal.AssertNoError(err)
|
|
||||||
auditRecorder.StartBackgroundJobs(ctx)
|
|
||||||
|
|
||||||
userManager, err := users.NewUserManager(cfg, eventBus, database, database)
|
// Give goroutines some time to stop gracefully
|
||||||
internal.AssertNoError(err)
|
logrus.Info("stopping WireGuard Portal Server...")
|
||||||
userManager.StartBackgroundJobs(ctx)
|
time.Sleep(2 * time.Second)
|
||||||
|
|
||||||
authenticator, err := auth.NewAuthenticator(&cfg.Auth, cfg.Web.ExternalUrl, eventBus, userManager)
|
logrus.Infof("stopped WireGuard Portal Server...")
|
||||||
internal.AssertNoError(err)
|
logrus.Exit(0)
|
||||||
authenticator.StartBackgroundJobs(ctx)
|
}
|
||||||
|
|
||||||
webAuthn, err := auth.NewWebAuthnAuthenticator(cfg, eventBus, userManager)
|
func setupLogger(logger *logrus.Logger) error {
|
||||||
internal.AssertNoError(err)
|
// Check environment variables for logrus settings
|
||||||
|
level, ok := os.LookupEnv("LOG_LEVEL")
|
||||||
wireGuardManager, err := wireguard.NewWireGuardManager(cfg, eventBus, wireGuard, wgQuick, database)
|
if !ok {
|
||||||
internal.AssertNoError(err)
|
level = "debug" // Default logrus level
|
||||||
wireGuardManager.StartBackgroundJobs(ctx)
|
}
|
||||||
|
|
||||||
statisticsCollector, err := wireguard.NewStatisticsCollector(cfg, eventBus, database, wireGuard, metricsServer)
|
useJSON, ok := os.LookupEnv("LOG_JSON")
|
||||||
internal.AssertNoError(err)
|
if !ok {
|
||||||
statisticsCollector.StartBackgroundJobs(ctx)
|
useJSON = "false" // Default use human readable logging
|
||||||
|
}
|
||||||
cfgFileManager, err := configfile.NewConfigFileManager(cfg, eventBus, database, database, cfgFileSystem)
|
|
||||||
internal.AssertNoError(err)
|
useColor, ok := os.LookupEnv("LOG_COLOR")
|
||||||
|
if !ok {
|
||||||
mailManager, err := mail.NewMailManager(cfg, mailer, cfgFileManager, database, database)
|
useColor = "true"
|
||||||
internal.AssertNoError(err)
|
}
|
||||||
|
|
||||||
routeManager, err := route.NewRouteManager(cfg, eventBus, database)
|
switch level {
|
||||||
internal.AssertNoError(err)
|
case "off":
|
||||||
routeManager.StartBackgroundJobs(ctx)
|
logger.SetOutput(io.Discard)
|
||||||
|
case "info":
|
||||||
webhookManager, err := webhooks.NewManager(cfg, eventBus)
|
logger.SetLevel(logrus.InfoLevel)
|
||||||
internal.AssertNoError(err)
|
case "debug":
|
||||||
webhookManager.StartBackgroundJobs(ctx)
|
logger.SetLevel(logrus.DebugLevel)
|
||||||
|
case "trace":
|
||||||
err = app.Initialize(cfg, wireGuardManager, userManager)
|
logger.SetLevel(logrus.TraceLevel)
|
||||||
internal.AssertNoError(err)
|
}
|
||||||
|
|
||||||
validatorManager := validator.New()
|
var formatter logrus.Formatter
|
||||||
|
if useJSON == "false" {
|
||||||
// region API v0 (SPA frontend)
|
f := new(logrus.TextFormatter)
|
||||||
|
f.TimestampFormat = "2006-01-02 15:04:05"
|
||||||
apiV0Session := handlersV0.NewSessionWrapper(cfg)
|
f.FullTimestamp = true
|
||||||
apiV0Auth := handlersV0.NewAuthenticationHandler(authenticator, apiV0Session)
|
if useColor == "true" {
|
||||||
|
f.ForceColors = true
|
||||||
apiV0BackendUsers := backendV0.NewUserService(cfg, userManager, wireGuardManager)
|
}
|
||||||
apiV0BackendInterfaces := backendV0.NewInterfaceService(cfg, wireGuardManager, cfgFileManager)
|
formatter = f
|
||||||
apiV0BackendPeers := backendV0.NewPeerService(cfg, wireGuardManager, cfgFileManager, mailManager)
|
} else {
|
||||||
|
f := new(logrus.JSONFormatter)
|
||||||
apiV0EndpointAuth := handlersV0.NewAuthEndpoint(cfg, apiV0Auth, apiV0Session, validatorManager, authenticator,
|
f.TimestampFormat = "2006-01-02 15:04:05"
|
||||||
webAuthn)
|
formatter = f
|
||||||
apiV0EndpointAudit := handlersV0.NewAuditEndpoint(cfg, apiV0Auth, auditManager)
|
}
|
||||||
apiV0EndpointUsers := handlersV0.NewUserEndpoint(cfg, apiV0Auth, validatorManager, apiV0BackendUsers)
|
|
||||||
apiV0EndpointInterfaces := handlersV0.NewInterfaceEndpoint(cfg, apiV0Auth, validatorManager, apiV0BackendInterfaces)
|
logger.SetFormatter(formatter)
|
||||||
apiV0EndpointPeers := handlersV0.NewPeerEndpoint(cfg, apiV0Auth, validatorManager, apiV0BackendPeers)
|
|
||||||
apiV0EndpointConfig := handlersV0.NewConfigEndpoint(cfg, apiV0Auth)
|
return nil
|
||||||
apiV0EndpointTest := handlersV0.NewTestEndpoint(apiV0Auth)
|
|
||||||
|
|
||||||
apiFrontend := handlersV0.NewRestApi(apiV0Session,
|
|
||||||
apiV0EndpointAuth,
|
|
||||||
apiV0EndpointAudit,
|
|
||||||
apiV0EndpointUsers,
|
|
||||||
apiV0EndpointInterfaces,
|
|
||||||
apiV0EndpointPeers,
|
|
||||||
apiV0EndpointConfig,
|
|
||||||
apiV0EndpointTest,
|
|
||||||
)
|
|
||||||
|
|
||||||
// endregion API v0 (SPA frontend)
|
|
||||||
|
|
||||||
// region API v1 (User REST API)
|
|
||||||
|
|
||||||
apiV1Auth := handlersV1.NewAuthenticationHandler(userManager)
|
|
||||||
apiV1BackendUsers := backendV1.NewUserService(cfg, userManager)
|
|
||||||
apiV1BackendPeers := backendV1.NewPeerService(cfg, wireGuardManager, userManager)
|
|
||||||
apiV1BackendInterfaces := backendV1.NewInterfaceService(cfg, wireGuardManager)
|
|
||||||
apiV1BackendProvisioning := backendV1.NewProvisioningService(cfg, userManager, wireGuardManager, cfgFileManager)
|
|
||||||
apiV1BackendMetrics := backendV1.NewMetricsService(cfg, database, userManager, wireGuardManager)
|
|
||||||
|
|
||||||
apiV1EndpointUsers := handlersV1.NewUserEndpoint(apiV1Auth, validatorManager, apiV1BackendUsers)
|
|
||||||
apiV1EndpointPeers := handlersV1.NewPeerEndpoint(apiV1Auth, validatorManager, apiV1BackendPeers)
|
|
||||||
apiV1EndpointInterfaces := handlersV1.NewInterfaceEndpoint(apiV1Auth, validatorManager, apiV1BackendInterfaces)
|
|
||||||
apiV1EndpointProvisioning := handlersV1.NewProvisioningEndpoint(apiV1Auth, validatorManager,
|
|
||||||
apiV1BackendProvisioning)
|
|
||||||
apiV1EndpointMetrics := handlersV1.NewMetricsEndpoint(apiV1Auth, validatorManager, apiV1BackendMetrics)
|
|
||||||
|
|
||||||
apiV1 := handlersV1.NewRestApi(
|
|
||||||
apiV1EndpointUsers,
|
|
||||||
apiV1EndpointPeers,
|
|
||||||
apiV1EndpointInterfaces,
|
|
||||||
apiV1EndpointProvisioning,
|
|
||||||
apiV1EndpointMetrics,
|
|
||||||
)
|
|
||||||
|
|
||||||
// endregion API v1 (User REST API)
|
|
||||||
|
|
||||||
webSrv, err := core.NewServer(cfg, apiFrontend, apiV1)
|
|
||||||
internal.AssertNoError(err)
|
|
||||||
|
|
||||||
go metricsServer.Run(ctx)
|
|
||||||
go webSrv.Run(ctx, cfg.Web.ListeningAddress)
|
|
||||||
|
|
||||||
slog.Info("Application startup complete")
|
|
||||||
|
|
||||||
// wait until context gets cancelled
|
|
||||||
<-ctx.Done()
|
|
||||||
|
|
||||||
slog.Info("Stopping WireGuard Portal")
|
|
||||||
|
|
||||||
time.Sleep(5 * time.Second) // wait for (most) goroutines to finish gracefully
|
|
||||||
|
|
||||||
slog.Info("Stopped WireGuard Portal")
|
|
||||||
}
|
}
|
||||||
|
@@ -1,96 +0,0 @@
|
|||||||
# More information about the configuration can be found in the documentation: https://wgportal.org/master/documentation/overview/
|
|
||||||
|
|
||||||
advanced:
|
|
||||||
log_level: trace
|
|
||||||
|
|
||||||
core:
|
|
||||||
admin_user: test@test.de
|
|
||||||
admin_password: secret
|
|
||||||
create_default_peer: true
|
|
||||||
create_default_peer_on_creation: false
|
|
||||||
|
|
||||||
web:
|
|
||||||
external_url: http://localhost:8888
|
|
||||||
request_logging: true
|
|
||||||
|
|
||||||
webhook:
|
|
||||||
url: ""
|
|
||||||
authentication: ""
|
|
||||||
timeout: 10s
|
|
||||||
|
|
||||||
auth:
|
|
||||||
ldap:
|
|
||||||
- id: ldap1
|
|
||||||
provider_name: company ldap
|
|
||||||
url: ldap://ldap.yourcompany.local:389
|
|
||||||
bind_user: ldap_wireguard@yourcompany.local
|
|
||||||
bind_pass: super_Secret_PASSWORD
|
|
||||||
base_dn: DC=YOURCOMPANY,DC=LOCAL
|
|
||||||
login_filter: (&(objectClass=organizationalPerson)(mail={{login_identifier}})(!userAccountControl:1.2.840.113556.1.4.803:=2))
|
|
||||||
admin_group: CN=WireGuardAdmins,OU=it,DC=YOURCOMPANY,DC=LOCAL
|
|
||||||
sync_interval: 0 # sync disabled
|
|
||||||
sync_filter: (&(objectClass=organizationalPerson)(!userAccountControl:1.2.840.113556.1.4.803:=2)(mail=*))
|
|
||||||
registration_enabled: true
|
|
||||||
oidc:
|
|
||||||
- id: oidc1
|
|
||||||
provider_name: google
|
|
||||||
display_name: Login with</br>Google
|
|
||||||
base_url: https://accounts.google.com
|
|
||||||
client_id: the-client-id-1234.apps.googleusercontent.com
|
|
||||||
client_secret: A_CLIENT_SECRET
|
|
||||||
extra_scopes:
|
|
||||||
- https://www.googleapis.com/auth/userinfo.email
|
|
||||||
- https://www.googleapis.com/auth/userinfo.profile
|
|
||||||
registration_enabled: true
|
|
||||||
- id: oidc2
|
|
||||||
provider_name: google2
|
|
||||||
display_name: Login with</br>Google2
|
|
||||||
base_url: https://accounts.google.com
|
|
||||||
client_id: another-client-id-1234.apps.googleusercontent.com
|
|
||||||
client_secret: A_CLIENT_SECRET
|
|
||||||
extra_scopes:
|
|
||||||
- https://www.googleapis.com/auth/userinfo.email
|
|
||||||
- https://www.googleapis.com/auth/userinfo.profile
|
|
||||||
registration_enabled: true
|
|
||||||
oauth:
|
|
||||||
- id: google_plain_oauth
|
|
||||||
provider_name: google3
|
|
||||||
display_name: Login with</br>Google3
|
|
||||||
client_id: another-client-id-1234.apps.googleusercontent.com
|
|
||||||
client_secret: A_CLIENT_SECRET
|
|
||||||
auth_url: https://accounts.google.com/o/oauth2/v2/auth
|
|
||||||
token_url: https://oauth2.googleapis.com/token
|
|
||||||
user_info_url: https://openidconnect.googleapis.com/v1/userinfo
|
|
||||||
scopes:
|
|
||||||
- openid
|
|
||||||
- email
|
|
||||||
- profile
|
|
||||||
field_map:
|
|
||||||
email: email
|
|
||||||
firstname: name
|
|
||||||
user_identifier: sub
|
|
||||||
is_admin: this-attribute-must-be-true
|
|
||||||
registration_enabled: true
|
|
||||||
- id: google_plain_oauth_with_groups
|
|
||||||
provider_name: google4
|
|
||||||
display_name: Login with</br>Google4
|
|
||||||
client_id: another-client-id-1234.apps.googleusercontent.com
|
|
||||||
client_secret: A_CLIENT_SECRET
|
|
||||||
auth_url: https://accounts.google.com/o/oauth2/v2/auth
|
|
||||||
token_url: https://oauth2.googleapis.com/token
|
|
||||||
user_info_url: https://openidconnect.googleapis.com/v1/userinfo
|
|
||||||
scopes:
|
|
||||||
- openid
|
|
||||||
- email
|
|
||||||
- profile
|
|
||||||
- i-want-some-groups
|
|
||||||
field_map:
|
|
||||||
email: email
|
|
||||||
firstname: name
|
|
||||||
user_identifier: sub
|
|
||||||
user_groups: groups
|
|
||||||
admin_mapping:
|
|
||||||
admin_value_regex: ^true$
|
|
||||||
admin_group_regex: ^admin-group-name$
|
|
||||||
registration_enabled: true
|
|
||||||
log_user_info: true
|
|
5
ct.yaml
@@ -1,5 +0,0 @@
|
|||||||
# See https://github.com/helm/chart-testing#configuration
|
|
||||||
remote: origin
|
|
||||||
chart-dirs: deploy
|
|
||||||
target-branch: master
|
|
||||||
validate-maintainers: false
|
|
@@ -1,23 +0,0 @@
|
|||||||
# Patterns to ignore when building packages.
|
|
||||||
# This supports shell glob matching, relative path matching, and
|
|
||||||
# negation (prefixed with !). Only one pattern per line.
|
|
||||||
.DS_Store
|
|
||||||
# Common VCS dirs
|
|
||||||
.git/
|
|
||||||
.gitignore
|
|
||||||
.bzr/
|
|
||||||
.bzrignore
|
|
||||||
.hg/
|
|
||||||
.hgignore
|
|
||||||
.svn/
|
|
||||||
# Common backup files
|
|
||||||
*.swp
|
|
||||||
*.bak
|
|
||||||
*.tmp
|
|
||||||
*.orig
|
|
||||||
*~
|
|
||||||
# Various IDEs
|
|
||||||
.project
|
|
||||||
.idea/
|
|
||||||
*.tmproj
|
|
||||||
.vscode/
|
|
@@ -1,25 +0,0 @@
|
|||||||
apiVersion: v2
|
|
||||||
name: wg-portal
|
|
||||||
description: WireGuard Configuration Portal with LDAP, OAuth, OIDC authentication
|
|
||||||
# Version is set to ensure compatibility with the chart's Ingress resource.
|
|
||||||
kubeVersion: ">=1.19.0"
|
|
||||||
type: application
|
|
||||||
home: https://wgportal.org
|
|
||||||
icon: https://wgportal.org/latest/assets/images/logo.svg
|
|
||||||
sources:
|
|
||||||
- https://github.com/h44z/wg-portal
|
|
||||||
|
|
||||||
annotations:
|
|
||||||
artifacthub.io/category: networking
|
|
||||||
artifacthub.io/changes: ""
|
|
||||||
|
|
||||||
# This is the chart version. This version number should be incremented each time you make changes
|
|
||||||
# to the chart and its templates, including the app version.
|
|
||||||
# Versions are expected to follow Semantic Versioning (https://semver.org/)
|
|
||||||
version: 0.7.1
|
|
||||||
|
|
||||||
# This is the version number of the application being deployed. This version number should be
|
|
||||||
# incremented each time you make changes to the application. Versions are not expected to
|
|
||||||
# follow Semantic Versioning. They should reflect the version the application is using.
|
|
||||||
# It is recommended to use it with quotes.
|
|
||||||
appVersion: "v2"
|
|
@@ -1,124 +0,0 @@
|
|||||||
# wg-portal
|
|
||||||
|
|
||||||
  
|
|
||||||
|
|
||||||
WireGuard Configuration Portal with LDAP, OAuth, OIDC authentication
|
|
||||||
|
|
||||||
**Homepage:** <https://wgportal.org>
|
|
||||||
|
|
||||||
## Source Code
|
|
||||||
|
|
||||||
* <https://github.com/h44z/wg-portal>
|
|
||||||
|
|
||||||
## Requirements
|
|
||||||
|
|
||||||
Kubernetes: `>=1.19.0`
|
|
||||||
|
|
||||||
## Installing the Chart
|
|
||||||
|
|
||||||
To install the chart with the release name `wg-portal`:
|
|
||||||
|
|
||||||
```console
|
|
||||||
helm install wg-portal oci://ghcr.io/h44z/charts/wg-portal
|
|
||||||
```
|
|
||||||
|
|
||||||
This command deploy wg-portal on the Kubernetes cluster in the default configuration.
|
|
||||||
The [Values](#values) section lists the parameters that can be configured during installation.
|
|
||||||
|
|
||||||
## Values
|
|
||||||
|
|
||||||
| Key | Type | Default | Description |
|
|
||||||
|-----|------|---------|-------------|
|
|
||||||
| nameOverride | string | `""` | Partially override resource names (adds suffix) |
|
|
||||||
| fullnameOverride | string | `""` | Fully override resource names |
|
|
||||||
| extraDeploy | list | `[]` | Array of extra objects to deploy with the release |
|
|
||||||
| config.advanced | tpl/object | `{}` | [Advanced configuration](https://wgportal.org/latest/documentation/configuration/overview/#advanced) options. |
|
|
||||||
| config.auth | tpl/object | `{}` | [Auth configuration](https://wgportal.org/latest/documentation/configuration/overview/#auth) options. |
|
|
||||||
| config.core | tpl/object | `{}` | [Core configuration](https://wgportal.org/latest/documentation/configuration/overview/#core) options.<br> If external admins in `auth` are defined and there are no `admin_user` and `admin_password` defined here, the default admin account will be disabled. |
|
|
||||||
| config.database | tpl/object | `{}` | [Database configuration](https://wgportal.org/latest/documentation/configuration/overview/#database) options |
|
|
||||||
| config.mail | tpl/object | `{}` | [Mail configuration](https://wgportal.org/latest/documentation/configuration/overview/#mail) options |
|
|
||||||
| config.statistics | tpl/object | `{}` | [Statistics configuration](https://wgportal.org/latest/documentation/configuration/overview/#statistics) options |
|
|
||||||
| config.web | tpl/object | `{}` | [Web configuration](https://wgportal.org/latest/documentation/configuration/overview/#web) options.<br> `listening_address` will be set automatically from `service.web.port`. `external_url` is required to enable ingress and certificate resources. |
|
|
||||||
| revisionHistoryLimit | string | `10` | The number of old ReplicaSets to retain to allow rollback. |
|
|
||||||
| workloadType | string | `"Deployment"` | Workload type - `Deployment` or `StatefulSet` |
|
|
||||||
| strategy | object | `{"type":"RollingUpdate"}` | Update strategy for the workload Valid values are: `RollingUpdate` or `Recreate` for Deployment, `RollingUpdate` or `OnDelete` for StatefulSet |
|
|
||||||
| image.repository | string | `"ghcr.io/h44z/wg-portal"` | Image repository |
|
|
||||||
| image.pullPolicy | string | `"IfNotPresent"` | Image pull policy |
|
|
||||||
| image.tag | string | `""` | Overrides the image tag whose default is the chart appVersion |
|
|
||||||
| imagePullSecrets | list | `[]` | Image pull secrets |
|
|
||||||
| podAnnotations | tpl/object | `{}` | Extra annotations to add to the pod |
|
|
||||||
| podLabels | object | `{}` | Extra labels to add to the pod |
|
|
||||||
| podSecurityContext | object | `{}` | Pod Security Context |
|
|
||||||
| securityContext.capabilities.add | list | `["NET_ADMIN"]` | Add capabilities to the container |
|
|
||||||
| initContainers | tpl/list | `[]` | Pod init containers |
|
|
||||||
| sidecarContainers | tpl/list | `[]` | Pod sidecar containers |
|
|
||||||
| dnsPolicy | string | `"ClusterFirst"` | Set DNS policy for the pod. Valid values are `ClusterFirstWithHostNet`, `ClusterFirst`, `Default` or `None`. |
|
|
||||||
| restartPolicy | string | `"Always"` | Restart policy for all containers within the pod. Valid values are `Always`, `OnFailure` or `Never`. |
|
|
||||||
| hostNetwork | string | `false`. | Use the host's network namespace. |
|
|
||||||
| resources | object | `{}` | Resources requests and limits |
|
|
||||||
| command | list | `[]` | Overwrite pod command |
|
|
||||||
| args | list | `[]` | Additional pod arguments |
|
|
||||||
| env | tpl/list | `[]` | Additional environment variables |
|
|
||||||
| envFrom | tpl/list | `[]` | Additional environment variables from a secret or configMap |
|
|
||||||
| livenessProbe | object | `{}` | Liveness probe configuration |
|
|
||||||
| readinessProbe | object | `{}` | Readiness probe configuration |
|
|
||||||
| startupProbe | object | `{}` | Startup probe configuration |
|
|
||||||
| volumes | tpl/list | `[]` | Additional volumes |
|
|
||||||
| volumeMounts | tpl/list | `[]` | Additional volumeMounts |
|
|
||||||
| nodeSelector | object | `{"kubernetes.io/os":"linux"}` | Node Selector configuration |
|
|
||||||
| tolerations | list | `[]` | Tolerations configuration |
|
|
||||||
| affinity | object | `{}` | Affinity configuration |
|
|
||||||
| service.mixed.enabled | bool | `false` | Whether to create a single service for the web and wireguard interfaces |
|
|
||||||
| service.mixed.type | string | `"LoadBalancer"` | Service type |
|
|
||||||
| service.web.annotations | object | `{}` | Annotations for the web service |
|
|
||||||
| service.web.type | string | `"ClusterIP"` | Web service type |
|
|
||||||
| service.web.port | int | `8888` | Web service port Used for the web interface listener |
|
|
||||||
| service.web.appProtocol | string | `"http"` | Web service appProtocol. Will be auto set to `https` if certificate is enabled. |
|
|
||||||
| service.wireguard.annotations | object | `{}` | Annotations for the WireGuard service |
|
|
||||||
| service.wireguard.type | string | `"LoadBalancer"` | Wireguard service type |
|
|
||||||
| service.wireguard.ports | list | `[51820]` | Wireguard service ports. Exposes the WireGuard ports for created interfaces. Lowerest port is selected as start port for the first interface. Increment next port by 1 for each additional interface. |
|
|
||||||
| service.metrics.port | int | `8787` | |
|
|
||||||
| ingress.enabled | bool | `false` | Specifies whether an ingress resource should be created |
|
|
||||||
| ingress.className | string | `""` | Ingress class name |
|
|
||||||
| ingress.annotations | object | `{}` | Ingress annotations |
|
|
||||||
| ingress.tls | bool | `false` | Ingress TLS configuration. Enable certificate resource or add ingress annotation to create required secret |
|
|
||||||
| certificate.enabled | bool | `false` | Specifies whether a certificate resource should be created. If enabled, certificate will be used for the web. |
|
|
||||||
| certificate.issuer.name | string | `""` | Certificate issuer name |
|
|
||||||
| certificate.issuer.kind | string | `""` | Certificate issuer kind (ClusterIssuer or Issuer) |
|
|
||||||
| certificate.issuer.group | string | `"cert-manager.io"` | Certificate issuer group |
|
|
||||||
| certificate.duration | string | `""` | Optional. [Documentation](https://cert-manager.io/docs/usage/certificate/#creating-certificate-resources) |
|
|
||||||
| certificate.renewBefore | string | `""` | Optional. [Documentation](https://cert-manager.io/docs/usage/certificate/#creating-certificate-resources) |
|
|
||||||
| certificate.commonName | string | `""` | Optional. [Documentation](https://cert-manager.io/docs/usage/certificate/#creating-certificate-resources) |
|
|
||||||
| certificate.emailAddresses | list | `[]` | Optional. [Documentation](https://cert-manager.io/docs/usage/certificate/#creating-certificate-resources) |
|
|
||||||
| certificate.ipAddresses | list | `[]` | Optional. [Documentation](https://cert-manager.io/docs/usage/certificate/#creating-certificate-resources) |
|
|
||||||
| certificate.keystores | object | `{}` | Optional. [Documentation](https://cert-manager.io/docs/usage/certificate/#creating-certificate-resources) |
|
|
||||||
| certificate.privateKey | object | `{}` | Optional. [Documentation](https://cert-manager.io/docs/usage/certificate/#creating-certificate-resources) |
|
|
||||||
| certificate.secretTemplate | object | `{}` | Optional. [Documentation](https://cert-manager.io/docs/usage/certificate/#creating-certificate-resources) |
|
|
||||||
| certificate.subject | object | `{}` | Optional. [Documentation](https://cert-manager.io/docs/usage/certificate/#creating-certificate-resources) |
|
|
||||||
| certificate.uris | list | `[]` | Optional. [Documentation](https://cert-manager.io/docs/usage/certificate/#creating-certificate-resources) |
|
|
||||||
| certificate.usages | list | `[]` | Optional. [Documentation](https://cert-manager.io/docs/usage/certificate/#creating-certificate-resources) |
|
|
||||||
| persistence.enabled | bool | `false` | Specifies whether an persistent volume should be created |
|
|
||||||
| persistence.annotations | object | `{}` | Persistent Volume Claim annotations |
|
|
||||||
| persistence.storageClass | string | `""` | Persistent Volume storage class. If undefined (the default) cluster's default provisioner will be used. |
|
|
||||||
| persistence.accessMode | string | `"ReadWriteOnce"` | Persistent Volume Access Mode |
|
|
||||||
| persistence.size | string | `"1Gi"` | Persistent Volume size |
|
|
||||||
| persistence.volumeName | string | `""` | Persistent Volume Name (optional) |
|
|
||||||
| serviceAccount.create | bool | `true` | Specifies whether a service account should be created |
|
|
||||||
| serviceAccount.annotations | object | `{}` | Service account annotations |
|
|
||||||
| serviceAccount.automount | bool | `false` | Automatically mount a ServiceAccount's API credentials |
|
|
||||||
| serviceAccount.name | string | `""` | The name of the service account to use. If not set and create is true, a name is generated using the fullname template |
|
|
||||||
| monitoring.enabled | bool | `false` | Enable Prometheus monitoring. |
|
|
||||||
| monitoring.apiVersion | string | `"monitoring.coreos.com/v1"` | API version of the Prometheus resource. Use `azmonitoring.coreos.com/v1` for Azure Managed Prometheus. |
|
|
||||||
| monitoring.kind | string | `"PodMonitor"` | Kind of the Prometheus resource. Could be `PodMonitor` or `ServiceMonitor`. |
|
|
||||||
| monitoring.labels | object | `{}` | Resource labels. |
|
|
||||||
| monitoring.annotations | object | `{}` | Resource annotations. |
|
|
||||||
| monitoring.interval | string | `1m` | Interval at which metrics should be scraped. If not specified `config.statistics.data_collection_interval` interval is used. |
|
|
||||||
| monitoring.metricRelabelings | list | `[]` | Relabelings to samples before ingestion. |
|
|
||||||
| monitoring.relabelings | list | `[]` | Relabelings to samples before scraping. |
|
|
||||||
| monitoring.scrapeTimeout | string | `""` | Timeout after which the scrape is ended If not specified, the Prometheus global scrape interval is used. |
|
|
||||||
| monitoring.jobLabel | string | `""` | The label to use to retrieve the job name from. |
|
|
||||||
| monitoring.podTargetLabels | object | `{}` | Transfers labels on the Kubernetes Pod onto the target. |
|
|
||||||
| monitoring.dashboard.enabled | bool | `false` | Enable Grafana dashboard. |
|
|
||||||
| monitoring.dashboard.annotations | object | `{}` | Annotations for the dashboard ConfigMap. |
|
|
||||||
| monitoring.dashboard.labels | object | `{}` | Additional labels for the dashboard ConfigMap. |
|
|
||||||
| monitoring.dashboard.namespace | string | `""` | Dashboard ConfigMap namespace Overrides the namespace for the dashboard ConfigMap. |
|
|
@@ -1,27 +0,0 @@
|
|||||||
{{ template "chart.header" . }}
|
|
||||||
{{ template "chart.deprecationWarning" . }}
|
|
||||||
|
|
||||||
{{ template "chart.badgesSection" . }}
|
|
||||||
|
|
||||||
{{ template "chart.description" . }}
|
|
||||||
|
|
||||||
{{ template "chart.homepageLine" . }}
|
|
||||||
|
|
||||||
{{ template "chart.maintainersSection" . }}
|
|
||||||
|
|
||||||
{{ template "chart.sourcesSection" . }}
|
|
||||||
|
|
||||||
{{ template "chart.requirementsSection" . }}
|
|
||||||
|
|
||||||
## Installing the Chart
|
|
||||||
|
|
||||||
To install the chart with the release name `wg-portal`:
|
|
||||||
|
|
||||||
```console
|
|
||||||
helm install wg-portal oci://ghcr.io/h44z/charts/wg-portal
|
|
||||||
```
|
|
||||||
|
|
||||||
This command deploy wg-portal on the Kubernetes cluster in the default configuration.
|
|
||||||
The [Values](#values) section lists the parameters that can be configured during installation.
|
|
||||||
|
|
||||||
{{ template "chart.valuesSection" . }}
|
|
@@ -1,917 +0,0 @@
|
|||||||
{
|
|
||||||
"annotations": {},
|
|
||||||
"description": "WireGuard Portal Dashboard",
|
|
||||||
"panels": [
|
|
||||||
{
|
|
||||||
"datasource": {
|
|
||||||
"default": false,
|
|
||||||
"type": "prometheus",
|
|
||||||
"uid": "${datasource}"
|
|
||||||
},
|
|
||||||
"description": "",
|
|
||||||
"fieldConfig": {
|
|
||||||
"defaults": {
|
|
||||||
"color": {
|
|
||||||
"mode": "palette-classic"
|
|
||||||
},
|
|
||||||
"custom": {
|
|
||||||
"axisBorderShow": false,
|
|
||||||
"axisCenteredZero": false,
|
|
||||||
"axisColorMode": "text",
|
|
||||||
"axisLabel": "",
|
|
||||||
"axisPlacement": "auto",
|
|
||||||
"barAlignment": 0,
|
|
||||||
"barWidthFactor": 0.6,
|
|
||||||
"drawStyle": "line",
|
|
||||||
"fillOpacity": 10,
|
|
||||||
"gradientMode": "opacity",
|
|
||||||
"hideFrom": {
|
|
||||||
"legend": false,
|
|
||||||
"tooltip": false,
|
|
||||||
"viz": false
|
|
||||||
},
|
|
||||||
"insertNulls": 3600000,
|
|
||||||
"lineInterpolation": "smooth",
|
|
||||||
"lineStyle": {
|
|
||||||
"fill": "solid"
|
|
||||||
},
|
|
||||||
"lineWidth": 1,
|
|
||||||
"pointSize": 5,
|
|
||||||
"scaleDistribution": {
|
|
||||||
"type": "linear"
|
|
||||||
},
|
|
||||||
"showPoints": "never",
|
|
||||||
"spanNulls": true,
|
|
||||||
"stacking": {
|
|
||||||
"group": "A",
|
|
||||||
"mode": "none"
|
|
||||||
},
|
|
||||||
"thresholdsStyle": {
|
|
||||||
"mode": "off"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"mappings": [],
|
|
||||||
"thresholds": {
|
|
||||||
"mode": "absolute",
|
|
||||||
"steps": [
|
|
||||||
{
|
|
||||||
"color": "green",
|
|
||||||
"value": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"color": "red",
|
|
||||||
"value": 80
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"unit": "bytes"
|
|
||||||
},
|
|
||||||
"overrides": []
|
|
||||||
},
|
|
||||||
"gridPos": {
|
|
||||||
"h": 9,
|
|
||||||
"w": 12,
|
|
||||||
"x": 0,
|
|
||||||
"y": 0
|
|
||||||
},
|
|
||||||
"id": 2,
|
|
||||||
"options": {
|
|
||||||
"legend": {
|
|
||||||
"calcs": [],
|
|
||||||
"displayMode": "list",
|
|
||||||
"placement": "right",
|
|
||||||
"showLegend": true
|
|
||||||
},
|
|
||||||
"tooltip": {
|
|
||||||
"mode": "multi",
|
|
||||||
"sort": "none"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"datasource": {
|
|
||||||
"type": "prometheus",
|
|
||||||
"uid": "${datasource}"
|
|
||||||
},
|
|
||||||
"disableTextWrap": false,
|
|
||||||
"editorMode": "code",
|
|
||||||
"exemplar": false,
|
|
||||||
"expr": "sum by (instance, interface) (wireguard_interface_received_bytes_total{instance=\"$instance\", interface=~\"$interface\"})",
|
|
||||||
"fullMetaSearch": false,
|
|
||||||
"hide": false,
|
|
||||||
"includeNullMetadata": true,
|
|
||||||
"instant": false,
|
|
||||||
"interval": "",
|
|
||||||
"legendFormat": "Received {{interface}}",
|
|
||||||
"range": true,
|
|
||||||
"refId": "A",
|
|
||||||
"useBackend": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"datasource": {
|
|
||||||
"type": "prometheus",
|
|
||||||
"uid": "${datasource}"
|
|
||||||
},
|
|
||||||
"editorMode": "code",
|
|
||||||
"expr": "sum by (instance, interface) (wireguard_interface_sent_bytes_total{instance=\"$instance\", interface=~\"$interface\"})",
|
|
||||||
"hide": false,
|
|
||||||
"instant": false,
|
|
||||||
"legendFormat": "Sent {{interface}}",
|
|
||||||
"range": true,
|
|
||||||
"refId": "B"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"title": "Interface Bytes Total",
|
|
||||||
"type": "timeseries"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"datasource": {
|
|
||||||
"default": false,
|
|
||||||
"type": "prometheus",
|
|
||||||
"uid": "${datasource}"
|
|
||||||
},
|
|
||||||
"description": "",
|
|
||||||
"fieldConfig": {
|
|
||||||
"defaults": {
|
|
||||||
"color": {
|
|
||||||
"mode": "palette-classic"
|
|
||||||
},
|
|
||||||
"custom": {
|
|
||||||
"axisBorderShow": false,
|
|
||||||
"axisCenteredZero": false,
|
|
||||||
"axisColorMode": "text",
|
|
||||||
"axisLabel": "",
|
|
||||||
"axisPlacement": "auto",
|
|
||||||
"barAlignment": 0,
|
|
||||||
"barWidthFactor": 0.6,
|
|
||||||
"drawStyle": "line",
|
|
||||||
"fillOpacity": 10,
|
|
||||||
"gradientMode": "opacity",
|
|
||||||
"hideFrom": {
|
|
||||||
"legend": false,
|
|
||||||
"tooltip": false,
|
|
||||||
"viz": false
|
|
||||||
},
|
|
||||||
"insertNulls": 3600000,
|
|
||||||
"lineInterpolation": "smooth",
|
|
||||||
"lineStyle": {
|
|
||||||
"fill": "solid"
|
|
||||||
},
|
|
||||||
"lineWidth": 1,
|
|
||||||
"pointSize": 5,
|
|
||||||
"scaleDistribution": {
|
|
||||||
"type": "linear"
|
|
||||||
},
|
|
||||||
"showPoints": "never",
|
|
||||||
"spanNulls": true,
|
|
||||||
"stacking": {
|
|
||||||
"group": "A",
|
|
||||||
"mode": "none"
|
|
||||||
},
|
|
||||||
"thresholdsStyle": {
|
|
||||||
"mode": "off"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"mappings": [],
|
|
||||||
"thresholds": {
|
|
||||||
"mode": "absolute",
|
|
||||||
"steps": [
|
|
||||||
{
|
|
||||||
"color": "green",
|
|
||||||
"value": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"color": "red",
|
|
||||||
"value": 80
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"unit": "bytes"
|
|
||||||
},
|
|
||||||
"overrides": []
|
|
||||||
},
|
|
||||||
"gridPos": {
|
|
||||||
"h": 9,
|
|
||||||
"w": 12,
|
|
||||||
"x": 12,
|
|
||||||
"y": 0
|
|
||||||
},
|
|
||||||
"id": 13,
|
|
||||||
"options": {
|
|
||||||
"legend": {
|
|
||||||
"calcs": [],
|
|
||||||
"displayMode": "list",
|
|
||||||
"placement": "right",
|
|
||||||
"showLegend": true
|
|
||||||
},
|
|
||||||
"tooltip": {
|
|
||||||
"mode": "multi",
|
|
||||||
"sort": "none"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"datasource": {
|
|
||||||
"type": "prometheus",
|
|
||||||
"uid": "${datasource}"
|
|
||||||
},
|
|
||||||
"editorMode": "code",
|
|
||||||
"expr": "sum by (instance, interface) (rate(wireguard_interface_received_bytes_total{instance=\"$instance\", interface=~\"$interface\"}[$__rate_interval]))",
|
|
||||||
"hide": false,
|
|
||||||
"instant": false,
|
|
||||||
"interval": "",
|
|
||||||
"legendFormat": "Received {{interface}}",
|
|
||||||
"range": true,
|
|
||||||
"refId": "A"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"datasource": {
|
|
||||||
"type": "prometheus",
|
|
||||||
"uid": "${datasource}"
|
|
||||||
},
|
|
||||||
"editorMode": "code",
|
|
||||||
"expr": "sum by (instance, interface) (rate(wireguard_interface_sent_bytes_total{instance=\"$instance\", interface=~\"$interface\"}[$__rate_interval]))",
|
|
||||||
"hide": false,
|
|
||||||
"instant": false,
|
|
||||||
"interval": "",
|
|
||||||
"legendFormat": "Sent {{interface}}",
|
|
||||||
"range": true,
|
|
||||||
"refId": "B"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"title": "Interface Bandwidth",
|
|
||||||
"type": "timeseries"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"datasource": {
|
|
||||||
"default": false,
|
|
||||||
"type": "prometheus",
|
|
||||||
"uid": "${datasource}"
|
|
||||||
},
|
|
||||||
"description": "",
|
|
||||||
"fieldConfig": {
|
|
||||||
"defaults": {
|
|
||||||
"color": {
|
|
||||||
"mode": "palette-classic"
|
|
||||||
},
|
|
||||||
"custom": {
|
|
||||||
"axisBorderShow": false,
|
|
||||||
"axisCenteredZero": false,
|
|
||||||
"axisColorMode": "text",
|
|
||||||
"axisLabel": "",
|
|
||||||
"axisPlacement": "auto",
|
|
||||||
"barAlignment": 0,
|
|
||||||
"barWidthFactor": 0.6,
|
|
||||||
"drawStyle": "line",
|
|
||||||
"fillOpacity": 10,
|
|
||||||
"gradientMode": "opacity",
|
|
||||||
"hideFrom": {
|
|
||||||
"legend": false,
|
|
||||||
"tooltip": false,
|
|
||||||
"viz": false
|
|
||||||
},
|
|
||||||
"insertNulls": 3600000,
|
|
||||||
"lineInterpolation": "smooth",
|
|
||||||
"lineStyle": {
|
|
||||||
"fill": "solid"
|
|
||||||
},
|
|
||||||
"lineWidth": 1,
|
|
||||||
"pointSize": 5,
|
|
||||||
"scaleDistribution": {
|
|
||||||
"type": "linear"
|
|
||||||
},
|
|
||||||
"showPoints": "never",
|
|
||||||
"spanNulls": true,
|
|
||||||
"stacking": {
|
|
||||||
"group": "A",
|
|
||||||
"mode": "none"
|
|
||||||
},
|
|
||||||
"thresholdsStyle": {
|
|
||||||
"mode": "off"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"mappings": [],
|
|
||||||
"thresholds": {
|
|
||||||
"mode": "absolute",
|
|
||||||
"steps": [
|
|
||||||
{
|
|
||||||
"color": "green",
|
|
||||||
"value": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"color": "red",
|
|
||||||
"value": 80
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"unit": "bytes"
|
|
||||||
},
|
|
||||||
"overrides": []
|
|
||||||
},
|
|
||||||
"gridPos": {
|
|
||||||
"h": 9,
|
|
||||||
"w": 12,
|
|
||||||
"x": 0,
|
|
||||||
"y": 9
|
|
||||||
},
|
|
||||||
"id": 16,
|
|
||||||
"options": {
|
|
||||||
"legend": {
|
|
||||||
"calcs": [],
|
|
||||||
"displayMode": "list",
|
|
||||||
"placement": "right",
|
|
||||||
"showLegend": true
|
|
||||||
},
|
|
||||||
"tooltip": {
|
|
||||||
"mode": "multi",
|
|
||||||
"sort": "none"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"datasource": {
|
|
||||||
"type": "prometheus",
|
|
||||||
"uid": "${datasource}"
|
|
||||||
},
|
|
||||||
"editorMode": "code",
|
|
||||||
"expr": "sum by (name, instance, interface) (rate(wireguard_peer_received_bytes_total{instance=\"$instance\", interface=~\"$interface\"}[$__rate_interval]))",
|
|
||||||
"hide": false,
|
|
||||||
"instant": false,
|
|
||||||
"interval": "$interval",
|
|
||||||
"legendFormat": "{{name}}",
|
|
||||||
"range": true,
|
|
||||||
"refId": "A"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"title": "Peer Receive Bandwidth",
|
|
||||||
"type": "timeseries"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"datasource": {
|
|
||||||
"default": false,
|
|
||||||
"type": "prometheus",
|
|
||||||
"uid": "${datasource}"
|
|
||||||
},
|
|
||||||
"description": "",
|
|
||||||
"fieldConfig": {
|
|
||||||
"defaults": {
|
|
||||||
"color": {
|
|
||||||
"mode": "palette-classic"
|
|
||||||
},
|
|
||||||
"custom": {
|
|
||||||
"axisBorderShow": false,
|
|
||||||
"axisCenteredZero": false,
|
|
||||||
"axisColorMode": "text",
|
|
||||||
"axisLabel": "",
|
|
||||||
"axisPlacement": "auto",
|
|
||||||
"barAlignment": 0,
|
|
||||||
"barWidthFactor": 0.6,
|
|
||||||
"drawStyle": "line",
|
|
||||||
"fillOpacity": 10,
|
|
||||||
"gradientMode": "opacity",
|
|
||||||
"hideFrom": {
|
|
||||||
"legend": false,
|
|
||||||
"tooltip": false,
|
|
||||||
"viz": false
|
|
||||||
},
|
|
||||||
"insertNulls": 3600000,
|
|
||||||
"lineInterpolation": "smooth",
|
|
||||||
"lineStyle": {
|
|
||||||
"fill": "solid"
|
|
||||||
},
|
|
||||||
"lineWidth": 1,
|
|
||||||
"pointSize": 5,
|
|
||||||
"scaleDistribution": {
|
|
||||||
"type": "linear"
|
|
||||||
},
|
|
||||||
"showPoints": "never",
|
|
||||||
"spanNulls": true,
|
|
||||||
"stacking": {
|
|
||||||
"group": "A",
|
|
||||||
"mode": "none"
|
|
||||||
},
|
|
||||||
"thresholdsStyle": {
|
|
||||||
"mode": "off"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"mappings": [],
|
|
||||||
"thresholds": {
|
|
||||||
"mode": "absolute",
|
|
||||||
"steps": [
|
|
||||||
{
|
|
||||||
"color": "green",
|
|
||||||
"value": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"color": "red",
|
|
||||||
"value": 80
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"unit": "bytes"
|
|
||||||
},
|
|
||||||
"overrides": []
|
|
||||||
},
|
|
||||||
"gridPos": {
|
|
||||||
"h": 9,
|
|
||||||
"w": 12,
|
|
||||||
"x": 12,
|
|
||||||
"y": 9
|
|
||||||
},
|
|
||||||
"id": 17,
|
|
||||||
"options": {
|
|
||||||
"legend": {
|
|
||||||
"calcs": [],
|
|
||||||
"displayMode": "list",
|
|
||||||
"placement": "right",
|
|
||||||
"showLegend": true
|
|
||||||
},
|
|
||||||
"tooltip": {
|
|
||||||
"mode": "multi",
|
|
||||||
"sort": "none"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"datasource": {
|
|
||||||
"type": "prometheus",
|
|
||||||
"uid": "${datasource}"
|
|
||||||
},
|
|
||||||
"editorMode": "code",
|
|
||||||
"expr": "sum by (instance, interface, name) (rate(wireguard_peer_sent_bytes_total{instance=\"$instance\", interface=~\"$interface\"}[$__rate_interval]))",
|
|
||||||
"hide": false,
|
|
||||||
"instant": false,
|
|
||||||
"interval": "$interval",
|
|
||||||
"legendFormat": "{{name}}",
|
|
||||||
"range": true,
|
|
||||||
"refId": "A"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"title": "Peer Transmit Bandwidth",
|
|
||||||
"type": "timeseries"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"datasource": {
|
|
||||||
"default": false,
|
|
||||||
"type": "prometheus",
|
|
||||||
"uid": "${datasource}"
|
|
||||||
},
|
|
||||||
"description": "",
|
|
||||||
"fieldConfig": {
|
|
||||||
"defaults": {
|
|
||||||
"color": {
|
|
||||||
"mode": "thresholds"
|
|
||||||
},
|
|
||||||
"custom": {
|
|
||||||
"fillOpacity": 60,
|
|
||||||
"hideFrom": {
|
|
||||||
"legend": false,
|
|
||||||
"tooltip": false,
|
|
||||||
"viz": false
|
|
||||||
},
|
|
||||||
"lineWidth": 1
|
|
||||||
},
|
|
||||||
"fieldMinMax": false,
|
|
||||||
"mappings": [],
|
|
||||||
"thresholds": {
|
|
||||||
"mode": "absolute",
|
|
||||||
"steps": [
|
|
||||||
{
|
|
||||||
"color": "red",
|
|
||||||
"value": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"color": "green",
|
|
||||||
"value": 1
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"unit": "bool_yes_no"
|
|
||||||
},
|
|
||||||
"overrides": []
|
|
||||||
},
|
|
||||||
"gridPos": {
|
|
||||||
"h": 11,
|
|
||||||
"w": 24,
|
|
||||||
"x": 0,
|
|
||||||
"y": 18
|
|
||||||
},
|
|
||||||
"id": 12,
|
|
||||||
"options": {
|
|
||||||
"colWidth": 0.85,
|
|
||||||
"legend": {
|
|
||||||
"displayMode": "list",
|
|
||||||
"placement": "bottom",
|
|
||||||
"showLegend": false
|
|
||||||
},
|
|
||||||
"rowHeight": 0.85,
|
|
||||||
"showValue": "never",
|
|
||||||
"tooltip": {
|
|
||||||
"mode": "single",
|
|
||||||
"sort": "none"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"datasource": {
|
|
||||||
"type": "prometheus",
|
|
||||||
"uid": "${datasource}"
|
|
||||||
},
|
|
||||||
"editorMode": "code",
|
|
||||||
"exemplar": false,
|
|
||||||
"expr": "sum by(name) (wireguard_peer_up{instance=\"$instance\", interface=~\"$interface\"})",
|
|
||||||
"instant": false,
|
|
||||||
"interval": "$interval",
|
|
||||||
"legendFormat": "{{name}}",
|
|
||||||
"range": true,
|
|
||||||
"refId": "A"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"title": "Peer Connection History",
|
|
||||||
"type": "status-history"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"datasource": {
|
|
||||||
"default": false,
|
|
||||||
"type": "prometheus",
|
|
||||||
"uid": "${datasource}"
|
|
||||||
},
|
|
||||||
"description": "",
|
|
||||||
"fieldConfig": {
|
|
||||||
"defaults": {
|
|
||||||
"color": {
|
|
||||||
"mode": "palette-classic-by-name"
|
|
||||||
},
|
|
||||||
"custom": {
|
|
||||||
"align": "auto",
|
|
||||||
"cellOptions": {
|
|
||||||
"type": "auto",
|
|
||||||
"wrapText": false
|
|
||||||
},
|
|
||||||
"filterable": false,
|
|
||||||
"inspect": false
|
|
||||||
},
|
|
||||||
"fieldMinMax": false,
|
|
||||||
"mappings": [],
|
|
||||||
"thresholds": {
|
|
||||||
"mode": "absolute",
|
|
||||||
"steps": [
|
|
||||||
{
|
|
||||||
"color": "dark-red",
|
|
||||||
"value": null
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"overrides": [
|
|
||||||
{
|
|
||||||
"matcher": {
|
|
||||||
"id": "byRegexp",
|
|
||||||
"options": "/(Time|instance|interface|name)\\s\\d*/"
|
|
||||||
},
|
|
||||||
"properties": [
|
|
||||||
{
|
|
||||||
"id": "custom.hidden",
|
|
||||||
"value": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"matcher": {
|
|
||||||
"id": "byRegexp",
|
|
||||||
"options": "/Received|Transmitted/"
|
|
||||||
},
|
|
||||||
"properties": [
|
|
||||||
{
|
|
||||||
"id": "unit",
|
|
||||||
"value": "bytes"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"matcher": {
|
|
||||||
"id": "byName",
|
|
||||||
"options": "Last Handshake"
|
|
||||||
},
|
|
||||||
"properties": [
|
|
||||||
{
|
|
||||||
"id": "unit",
|
|
||||||
"value": "s"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"matcher": {
|
|
||||||
"id": "byName",
|
|
||||||
"options": "Connected"
|
|
||||||
},
|
|
||||||
"properties": [
|
|
||||||
{
|
|
||||||
"id": "mappings",
|
|
||||||
"value": [
|
|
||||||
{
|
|
||||||
"options": {
|
|
||||||
"0": {
|
|
||||||
"color": "red",
|
|
||||||
"index": 0,
|
|
||||||
"text": "No"
|
|
||||||
},
|
|
||||||
"1": {
|
|
||||||
"color": "green",
|
|
||||||
"index": 1,
|
|
||||||
"text": "Yes"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"type": "value"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "custom.cellOptions",
|
|
||||||
"value": {
|
|
||||||
"type": "color-text"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"gridPos": {
|
|
||||||
"h": 14,
|
|
||||||
"w": 24,
|
|
||||||
"x": 0,
|
|
||||||
"y": 29
|
|
||||||
},
|
|
||||||
"id": 11,
|
|
||||||
"options": {
|
|
||||||
"cellHeight": "sm",
|
|
||||||
"footer": {
|
|
||||||
"countRows": false,
|
|
||||||
"enablePagination": false,
|
|
||||||
"fields": [],
|
|
||||||
"reducer": [
|
|
||||||
"sum"
|
|
||||||
],
|
|
||||||
"show": false
|
|
||||||
},
|
|
||||||
"showHeader": true,
|
|
||||||
"sortBy": [
|
|
||||||
{
|
|
||||||
"desc": true,
|
|
||||||
"displayName": "Sent"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"datasource": {
|
|
||||||
"type": "prometheus",
|
|
||||||
"uid": "${datasource}"
|
|
||||||
},
|
|
||||||
"disableTextWrap": false,
|
|
||||||
"editorMode": "code",
|
|
||||||
"exemplar": false,
|
|
||||||
"expr": "sum by(id, instance, interface, name, addresses) (increase(wireguard_peer_received_bytes_total{instance=\"$instance\", interface=~\"$interface\"}[$__range]))",
|
|
||||||
"format": "table",
|
|
||||||
"fullMetaSearch": false,
|
|
||||||
"hide": false,
|
|
||||||
"includeNullMetadata": true,
|
|
||||||
"instant": false,
|
|
||||||
"interval": "",
|
|
||||||
"legendFormat": "__auto",
|
|
||||||
"range": true,
|
|
||||||
"refId": "A",
|
|
||||||
"useBackend": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"datasource": {
|
|
||||||
"type": "prometheus",
|
|
||||||
"uid": "${datasource}"
|
|
||||||
},
|
|
||||||
"disableTextWrap": false,
|
|
||||||
"editorMode": "code",
|
|
||||||
"exemplar": false,
|
|
||||||
"expr": "sum by(id, instance, interface, name) (increase(wireguard_peer_sent_bytes_total{instance=\"$instance\", interface=~\"$interface\"}[$__range]))",
|
|
||||||
"format": "table",
|
|
||||||
"fullMetaSearch": false,
|
|
||||||
"includeNullMetadata": true,
|
|
||||||
"instant": false,
|
|
||||||
"interval": "",
|
|
||||||
"legendFormat": "__auto",
|
|
||||||
"range": true,
|
|
||||||
"refId": "B",
|
|
||||||
"useBackend": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"datasource": {
|
|
||||||
"type": "prometheus",
|
|
||||||
"uid": "${datasource}"
|
|
||||||
},
|
|
||||||
"editorMode": "code",
|
|
||||||
"exemplar": false,
|
|
||||||
"expr": "time()-sum(wireguard_peer_last_handshake_seconds{instance=\"$instance\", interface=~\"$interface\"}) by(id, instance, interface, name) ",
|
|
||||||
"format": "table",
|
|
||||||
"hide": false,
|
|
||||||
"instant": true,
|
|
||||||
"interval": "",
|
|
||||||
"legendFormat": "__auto",
|
|
||||||
"range": false,
|
|
||||||
"refId": "C"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"datasource": {
|
|
||||||
"type": "prometheus",
|
|
||||||
"uid": "${datasource}"
|
|
||||||
},
|
|
||||||
"editorMode": "code",
|
|
||||||
"exemplar": false,
|
|
||||||
"expr": "sum(wireguard_peer_up{instance=\"$instance\", interface=~\"$interface\"}) by(id, instance, interface, name) ",
|
|
||||||
"format": "table",
|
|
||||||
"hide": false,
|
|
||||||
"instant": true,
|
|
||||||
"interval": "",
|
|
||||||
"legendFormat": "__auto",
|
|
||||||
"range": false,
|
|
||||||
"refId": "D"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"title": "Peer Info",
|
|
||||||
"transformations": [
|
|
||||||
{
|
|
||||||
"id": "joinByField",
|
|
||||||
"options": {
|
|
||||||
"byField": "id",
|
|
||||||
"mode": "outer"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "organize",
|
|
||||||
"options": {
|
|
||||||
"excludeByName": {
|
|
||||||
"Time 1": false,
|
|
||||||
"Time 2": false,
|
|
||||||
"Time 3": false,
|
|
||||||
"Time 4": false
|
|
||||||
},
|
|
||||||
"includeByName": {},
|
|
||||||
"indexByName": {
|
|
||||||
"Time 1": 8,
|
|
||||||
"Time 2": 9,
|
|
||||||
"Time 3": 10,
|
|
||||||
"Time 4": 11,
|
|
||||||
"Value #A": 4,
|
|
||||||
"Value #B": 5,
|
|
||||||
"Value #C": 6,
|
|
||||||
"Value #D": 7,
|
|
||||||
"addresses": 2,
|
|
||||||
"id": 3,
|
|
||||||
"instance 1": 12,
|
|
||||||
"instance 2": 13,
|
|
||||||
"instance 3": 16,
|
|
||||||
"instance 4": 19,
|
|
||||||
"interface 1": 0,
|
|
||||||
"interface 2": 14,
|
|
||||||
"interface 3": 17,
|
|
||||||
"interface 4": 20,
|
|
||||||
"name 1": 1,
|
|
||||||
"name 2": 15,
|
|
||||||
"name 3": 18,
|
|
||||||
"name 4": 21
|
|
||||||
},
|
|
||||||
"renameByName": {
|
|
||||||
"Value #A": "Received",
|
|
||||||
"Value #B": "Transmitted",
|
|
||||||
"Value #C": "Last Handshake",
|
|
||||||
"Value #D": "Connected",
|
|
||||||
"addresses": "IP Addresses",
|
|
||||||
"id": "Public Key",
|
|
||||||
"interface": "Interface",
|
|
||||||
"interface 1": "Interface",
|
|
||||||
"name": "Name",
|
|
||||||
"name 1": "Name"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"type": "table"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"refresh": "1m",
|
|
||||||
"tags": [
|
|
||||||
"wireguard",
|
|
||||||
"vpn"
|
|
||||||
],
|
|
||||||
"templating": {
|
|
||||||
"list": [
|
|
||||||
{
|
|
||||||
"current": {},
|
|
||||||
"hide": 0,
|
|
||||||
"includeAll": false,
|
|
||||||
"label": "Prometheus",
|
|
||||||
"multi": false,
|
|
||||||
"name": "datasource",
|
|
||||||
"options": [],
|
|
||||||
"query": "prometheus",
|
|
||||||
"refresh": 1,
|
|
||||||
"regex": "",
|
|
||||||
"skipUrlSync": false,
|
|
||||||
"type": "datasource"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"current": {},
|
|
||||||
"datasource": {
|
|
||||||
"type": "prometheus",
|
|
||||||
"uid": "${datasource}"
|
|
||||||
},
|
|
||||||
"definition": "label_values(wireguard_interface_sent_bytes_total,instance)",
|
|
||||||
"hide": 0,
|
|
||||||
"includeAll": false,
|
|
||||||
"label": "Instance",
|
|
||||||
"multi": false,
|
|
||||||
"name": "instance",
|
|
||||||
"options": [],
|
|
||||||
"query": {
|
|
||||||
"qryType": 1,
|
|
||||||
"query": "label_values(wireguard_interface_sent_bytes_total,instance)",
|
|
||||||
"refId": "PrometheusVariableQueryEditor-VariableQuery"
|
|
||||||
},
|
|
||||||
"refresh": 1,
|
|
||||||
"regex": "",
|
|
||||||
"skipUrlSync": false,
|
|
||||||
"sort": 0,
|
|
||||||
"type": "query"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"current": {},
|
|
||||||
"datasource": {
|
|
||||||
"type": "prometheus",
|
|
||||||
"uid": "${datasource}"
|
|
||||||
},
|
|
||||||
"definition": "label_values(wireguard_interface_sent_bytes_total{instance=\"$instance\"},interface)",
|
|
||||||
"hide": 0,
|
|
||||||
"includeAll": true,
|
|
||||||
"label": "Interface",
|
|
||||||
"multi": true,
|
|
||||||
"name": "interface",
|
|
||||||
"options": [],
|
|
||||||
"query": {
|
|
||||||
"qryType": 1,
|
|
||||||
"query": "label_values(wireguard_interface_sent_bytes_total{instance=\"$instance\"},interface)",
|
|
||||||
"refId": "PrometheusVariableQueryEditor-VariableQuery"
|
|
||||||
},
|
|
||||||
"refresh": 1,
|
|
||||||
"regex": "",
|
|
||||||
"skipUrlSync": false,
|
|
||||||
"sort": 0,
|
|
||||||
"type": "query"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"current": {
|
|
||||||
"text": "2m",
|
|
||||||
"value": "2m"
|
|
||||||
},
|
|
||||||
"description": "",
|
|
||||||
"label": "Step Interval",
|
|
||||||
"name": "interval",
|
|
||||||
"options": [
|
|
||||||
{
|
|
||||||
"selected": false,
|
|
||||||
"text": "30s",
|
|
||||||
"value": "30s"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"selected": false,
|
|
||||||
"text": "1m",
|
|
||||||
"value": "1m"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"selected": true,
|
|
||||||
"text": "2m",
|
|
||||||
"value": "2m"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"selected": false,
|
|
||||||
"text": "5m",
|
|
||||||
"value": "5m"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"selected": false,
|
|
||||||
"text": "10m",
|
|
||||||
"value": "10m"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"query": "30s,1m,2m,5m,10m",
|
|
||||||
"type": "custom"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"time": {
|
|
||||||
"from": "now-12h",
|
|
||||||
"to": "now"
|
|
||||||
},
|
|
||||||
"timepicker": {},
|
|
||||||
"timezone": "",
|
|
||||||
"title": "WireGuard Portal",
|
|
||||||
"uid": "wireguard-portal",
|
|
||||||
"weekStart": ""
|
|
||||||
}
|
|
@@ -1,24 +0,0 @@
|
|||||||
{{- $serviceName := printf "%s-web" (include "wg-portal.fullname" .) -}}
|
|
||||||
{{- $servicePort := .Values.service.web.port }}
|
|
||||||
|
|
||||||
{{- if not .Values.ingress.enabled }}
|
|
||||||
Get the application URL by running these commands:
|
|
||||||
{{- if eq "ClusterIP" .Values.service.web.type }}
|
|
||||||
kubectl --namespace {{ .Release.Namespace }} port-forward svc/{{ $serviceName }} {{ $servicePort }}:{{ $servicePort }}
|
|
||||||
|
|
||||||
Visit http://127.0.0.1:{{ $servicePort }} to use your application
|
|
||||||
|
|
||||||
{{- else if eq "LoadBalancer" .Values.service.web.type }}
|
|
||||||
NOTE: It may take a few minutes for the LoadBalancer IP to be available.
|
|
||||||
You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ $serviceName }}'
|
|
||||||
export SERVICE_IP=$(kubectl get --namespace {{ .Release.Namespace }} svc {{ $serviceName }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}")
|
|
||||||
echo http://$SERVICE_IP:{{ $servicePort }}
|
|
||||||
|
|
||||||
{{- else if eq "NodePort" .Values.service.web.type }}
|
|
||||||
export NODE_IP=$(kubectl get --namespace {{ .Release.Namespace }} nodes -o jsonpath="{.items[0].status.addresses[0].address}")
|
|
||||||
export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} svc {{ $serviceName }} -o jsonpath="{.spec.ports[0].nodePort}" )
|
|
||||||
echo http://$NODE_IP:$NODE_PORT
|
|
||||||
{{- end }}
|
|
||||||
{{- else }}
|
|
||||||
Visit http{{ if .Values.ingress.tls }}s{{ end }}://{{ .Values.ingress.host }}{{ .Values.ingress.path }} to use your application
|
|
||||||
{{- end }}
|
|
@@ -1,132 +0,0 @@
|
|||||||
{{/*
|
|
||||||
Expand the name of the chart
|
|
||||||
*/}}
|
|
||||||
{{- define "wg-portal.name" -}}
|
|
||||||
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
|
|
||||||
{{- end }}
|
|
||||||
|
|
||||||
{{/*
|
|
||||||
Create a default fully qualified app name
|
|
||||||
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
|
|
||||||
If release name contains chart name it will be used as a full name.
|
|
||||||
*/}}
|
|
||||||
{{- define "wg-portal.fullname" -}}
|
|
||||||
{{- if .Values.fullnameOverride }}
|
|
||||||
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
|
|
||||||
{{- else }}
|
|
||||||
{{- $name := default .Chart.Name .Values.nameOverride }}
|
|
||||||
{{- if contains $name .Release.Name }}
|
|
||||||
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
|
|
||||||
{{- else }}
|
|
||||||
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
|
|
||||||
{{- end }}
|
|
||||||
{{- end }}
|
|
||||||
{{- end }}
|
|
||||||
|
|
||||||
{{/*
|
|
||||||
Create chart name and version as used by the chart label
|
|
||||||
*/}}
|
|
||||||
{{- define "wg-portal.chart" -}}
|
|
||||||
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
|
|
||||||
{{- end }}
|
|
||||||
|
|
||||||
{{/*
|
|
||||||
Common labels
|
|
||||||
*/}}
|
|
||||||
{{- define "wg-portal.labels" -}}
|
|
||||||
helm.sh/chart: {{ include "wg-portal.chart" . }}
|
|
||||||
{{ include "wg-portal.selectorLabels" . }}
|
|
||||||
{{- if .Chart.AppVersion }}
|
|
||||||
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
|
|
||||||
{{- end }}
|
|
||||||
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
|
||||||
{{- end }}
|
|
||||||
|
|
||||||
{{/*
|
|
||||||
Selector labels
|
|
||||||
*/}}
|
|
||||||
{{- define "wg-portal.selectorLabels" -}}
|
|
||||||
app.kubernetes.io/name: {{ include "wg-portal.name" . }}
|
|
||||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
|
||||||
{{- end }}
|
|
||||||
|
|
||||||
{{/*
|
|
||||||
Create the name of the service account to use
|
|
||||||
*/}}
|
|
||||||
{{- define "wg-portal.serviceAccountName" -}}
|
|
||||||
{{- if .Values.serviceAccount.create }}
|
|
||||||
{{- default (include "wg-portal.fullname" .) .Values.serviceAccount.name }}
|
|
||||||
{{- else }}
|
|
||||||
{{- default "default" .Values.serviceAccount.name }}
|
|
||||||
{{- end }}
|
|
||||||
{{- end }}
|
|
||||||
|
|
||||||
{{/*
|
|
||||||
Disables default admin credentials
|
|
||||||
If external auth is enabled and has admin group mappings,
|
|
||||||
the admin_user will be set to blank (disabled).
|
|
||||||
*/}}
|
|
||||||
{{- define "wg-portal.admin" -}}
|
|
||||||
{{- $externalAdmin := false -}}
|
|
||||||
{{- with .Values.config.auth -}}
|
|
||||||
{{- range (default list .ldap) -}}
|
|
||||||
{{- if hasKey . "admin_group" -}}
|
|
||||||
{{- $externalAdmin = true -}}
|
|
||||||
{{- end -}}
|
|
||||||
{{- end }}
|
|
||||||
{{- range (concat (default list .oidc) (default list .oauth)) -}}
|
|
||||||
{{- if hasKey .field_map "is_admin" -}}
|
|
||||||
{{- $externalAdmin = true -}}
|
|
||||||
{{- end -}}
|
|
||||||
{{- end -}}
|
|
||||||
{{- end -}}
|
|
||||||
{{- if $externalAdmin -}}
|
|
||||||
admin_user: ""
|
|
||||||
{{- end -}}
|
|
||||||
{{- end -}}
|
|
||||||
|
|
||||||
{{/*
|
|
||||||
Define PersistentVolumeClaim spec
|
|
||||||
*/}}
|
|
||||||
{{- define "wg-portal.pvc" -}}
|
|
||||||
accessModes:
|
|
||||||
- {{ .Values.persistence.accessMode }}
|
|
||||||
resources:
|
|
||||||
requests:
|
|
||||||
storage: {{ .Values.persistence.size | quote }}
|
|
||||||
{{- with .Values.persistence.storageClass }}
|
|
||||||
storageClassName: {{ . }}
|
|
||||||
{{- end }}
|
|
||||||
{{- with .Values.persistence.volumeName }}
|
|
||||||
volumeName: {{ . }}
|
|
||||||
{{- end }}
|
|
||||||
{{- end -}}
|
|
||||||
|
|
||||||
{{/*
|
|
||||||
Define hostname
|
|
||||||
*/}}
|
|
||||||
{{- define "wg-portal.hostname" -}}
|
|
||||||
{{- if .Values.config.web.external_url -}}
|
|
||||||
{{- (urlParse (tpl .Values.config.web.external_url .)).hostname -}}
|
|
||||||
{{- end -}}
|
|
||||||
{{- end -}}
|
|
||||||
|
|
||||||
|
|
||||||
{{/*
|
|
||||||
wg-portal.util.merge will merge two YAML templates or dict with template and output the result.
|
|
||||||
This takes an array of three values:
|
|
||||||
- the top context
|
|
||||||
- the template name or dict of the overrides (destination)
|
|
||||||
- the template name of the base (source)
|
|
||||||
{{- include "wg-portal.util.merge" (list $ .Values.podLabels "wg-portal.selectorLabels") }}
|
|
||||||
{{- include "wg-portal.util.merge" (list $ "wg-portal.destTemplate" "wg-portal.sourceTemplate") }}
|
|
||||||
*/}}
|
|
||||||
{{- define "wg-portal.util.merge" -}}
|
|
||||||
{{- $top := first . -}}
|
|
||||||
{{- $overrides := index . 1 -}}
|
|
||||||
{{- $base := fromYaml (include (index . 2) $top) | default (dict) -}}
|
|
||||||
{{- if kindIs "string" $overrides -}}
|
|
||||||
{{- $overrides = fromYaml (include $overrides $top) | default (dict) -}}
|
|
||||||
{{- end -}}
|
|
||||||
{{- toYaml (merge $overrides $base) -}}
|
|
||||||
{{- end -}}
|
|
@@ -1,119 +0,0 @@
|
|||||||
{{- define "wg-portal.podTemplate" -}}
|
|
||||||
metadata:
|
|
||||||
annotations:
|
|
||||||
checksum/config: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }}
|
|
||||||
kubectl.kubernetes.io/default-container: {{ .Chart.Name }}
|
|
||||||
{{- with .Values.podAnnotations }}
|
|
||||||
{{- tpl (toYaml .) $ | nindent 4 }}
|
|
||||||
{{- end }}
|
|
||||||
labels: {{- include "wg-portal.util.merge" (list $ .Values.podLabels "wg-portal.selectorLabels") | nindent 4 }}
|
|
||||||
spec:
|
|
||||||
{{- with .Values.affinity }}
|
|
||||||
affinity: {{- toYaml . | nindent 4 }}
|
|
||||||
{{- end }}
|
|
||||||
automountServiceAccountToken: {{ .Values.serviceAccount.automount }}
|
|
||||||
containers:
|
|
||||||
{{- with .Values.sidecarContainers }}
|
|
||||||
{{- tpl (toYaml .) $ | nindent 4 }}
|
|
||||||
{{- end }}
|
|
||||||
- name: {{ .Chart.Name }}
|
|
||||||
image: "{{ .Values.image.repository }}:{{ default .Chart.AppVersion .Values.image.tag}}"
|
|
||||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
|
||||||
{{- with .Values.command }}
|
|
||||||
command: {{ . }}
|
|
||||||
{{- end }}
|
|
||||||
{{- with .Values.args }}
|
|
||||||
args: {{ . }}
|
|
||||||
{{- end }}
|
|
||||||
{{- with .Values.env }}
|
|
||||||
env: {{- tpl (toYaml .) $ | nindent 8 }}
|
|
||||||
{{- end }}
|
|
||||||
{{- with .Values.envFrom }}
|
|
||||||
envFrom: {{- tpl (toYaml .) $ | nindent 8 }}
|
|
||||||
{{- end }}
|
|
||||||
ports:
|
|
||||||
- name: metrics
|
|
||||||
containerPort: {{ .Values.service.metrics.port}}
|
|
||||||
protocol: TCP
|
|
||||||
- name: web
|
|
||||||
containerPort: {{ .Values.service.web.port }}
|
|
||||||
protocol: TCP
|
|
||||||
{{- range $index, $port := .Values.service.wireguard.ports }}
|
|
||||||
- name: wg{{ $index }}
|
|
||||||
containerPort: {{ $port }}
|
|
||||||
protocol: UDP
|
|
||||||
{{- end }}
|
|
||||||
{{- with .Values.livenessProbe }}
|
|
||||||
livenessProbe: {{- toYaml . | nindent 8 }}
|
|
||||||
{{- end }}
|
|
||||||
{{- with .Values.readinessProbe }}
|
|
||||||
readinessProbe: {{- toYaml . | nindent 8 }}
|
|
||||||
{{- end }}
|
|
||||||
{{- with .Values.startupProbe }}
|
|
||||||
startupProbe: {{- toYaml . | nindent 8 }}
|
|
||||||
{{- end }}
|
|
||||||
{{- with .Values.securityContext }}
|
|
||||||
securityContext: {{- toYaml . | nindent 8 }}
|
|
||||||
{{- end }}
|
|
||||||
{{- with .Values.resources}}
|
|
||||||
resources: {{- toYaml . | nindent 8 }}
|
|
||||||
{{- end }}
|
|
||||||
volumeMounts:
|
|
||||||
- name: config
|
|
||||||
mountPath: /app/config
|
|
||||||
readOnly: true
|
|
||||||
- name: data
|
|
||||||
mountPath: /app/data
|
|
||||||
{{- if and .Values.certificate.enabled (include "wg-portal.hostname" .) }}
|
|
||||||
- name: certs
|
|
||||||
mountPath: /app/certs
|
|
||||||
{{- end }}
|
|
||||||
{{- with .Values.volumeMounts }}
|
|
||||||
{{- tpl (toYaml .) $ | nindent 8 }}
|
|
||||||
{{- end }}
|
|
||||||
{{- with .Values.dnsPolicy }}
|
|
||||||
dnsPolicy: {{ . }}
|
|
||||||
{{- end }}
|
|
||||||
{{- with .Values.hostNetwork }}
|
|
||||||
hostNetwork: {{ . }}
|
|
||||||
{{- end }}
|
|
||||||
{{- with .Values.imagePullSecrets }}
|
|
||||||
imagePullSecrets: {{- toYaml . | nindent 4 }}
|
|
||||||
{{- end }}
|
|
||||||
{{- with .Values.initContainers }}
|
|
||||||
initContainers: {{- tpl (toYaml .) $ | nindent 4 }}
|
|
||||||
{{- end }}
|
|
||||||
{{- with .Values.nodeSelector }}
|
|
||||||
nodeSelector: {{- toYaml . | nindent 4 }}
|
|
||||||
{{- end }}
|
|
||||||
{{- with .Values.restartPolicy }}
|
|
||||||
restartPolicy: {{ . }}
|
|
||||||
{{- end }}
|
|
||||||
serviceAccountName: {{ include "wg-portal.serviceAccountName" . }}
|
|
||||||
{{- with .Values.podSecurityContext }}
|
|
||||||
securityContext: {{- toYaml . | nindent 4 }}
|
|
||||||
{{- end }}
|
|
||||||
{{- with .Values.tolerations }}
|
|
||||||
tolerations: {{- toYaml . | nindent 4 }}
|
|
||||||
{{- end }}
|
|
||||||
volumes:
|
|
||||||
- name: config
|
|
||||||
secret:
|
|
||||||
secretName: {{ include "wg-portal.fullname" . }}
|
|
||||||
{{- if and .Values.certificate.enabled (include "wg-portal.hostname" .) }}
|
|
||||||
- name: certs
|
|
||||||
secret:
|
|
||||||
secretName: {{ include "wg-portal.fullname" . }}-tls
|
|
||||||
{{- end }}
|
|
||||||
{{- if not .Values.persistence.enabled }}
|
|
||||||
- name: data
|
|
||||||
emptyDir: {}
|
|
||||||
{{- else if eq .Values.workloadType "Deployment" }}
|
|
||||||
- name: data
|
|
||||||
persistentVolumeClaim:
|
|
||||||
claimName: {{ include "wg-portal.fullname" . }}
|
|
||||||
{{- end }}
|
|
||||||
{{- with .Values.volumes }}
|
|
||||||
{{- tpl (toYaml .) $ | nindent 4 }}
|
|
||||||
{{- end }}
|
|
||||||
{{- end -}}
|
|
@@ -1,66 +0,0 @@
|
|||||||
{{/*
|
|
||||||
Define the service template
|
|
||||||
{{- include "wg-portal.service" (dict "context" $ "scope" .Values.service.<name> "ports" list "name" "<name>") -}}
|
|
||||||
*/}}
|
|
||||||
{{- define "wg-portal.service.tpl" -}}
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Service
|
|
||||||
metadata:
|
|
||||||
{{- with .scope.annotations }}
|
|
||||||
annotations: {{- toYaml . | nindent 4 }}
|
|
||||||
{{- end }}
|
|
||||||
labels: {{- include "wg-portal.labels" .context | nindent 4 }}
|
|
||||||
name: {{ include "wg-portal.fullname" .context }}{{ ternary "" (printf "-%s" .name) (empty .name) }}
|
|
||||||
spec:
|
|
||||||
{{- with .scope.clusterIP }}
|
|
||||||
clusterIP: {{ . }}
|
|
||||||
{{- end }}
|
|
||||||
{{- with .scope.externalIPs }}
|
|
||||||
externalIPs: {{ toYaml . | nindent 4 }}
|
|
||||||
{{- end }}
|
|
||||||
{{- with .scope.externalName }}
|
|
||||||
externalName: {{ . }}
|
|
||||||
{{- end }}
|
|
||||||
{{- with .scope.externalTrafficPolicy }}
|
|
||||||
externalTrafficPolicy: {{ . }}
|
|
||||||
{{- end }}
|
|
||||||
{{- with .scope.healthCheckNodePort }}
|
|
||||||
healthCheckNodePort: {{ . }}
|
|
||||||
{{- end }}
|
|
||||||
{{- with .scope.loadBalancerIP }}
|
|
||||||
loadBalancerIP: {{ . }}
|
|
||||||
{{- end }}
|
|
||||||
{{- with .scope.loadBalancerSourceRanges }}
|
|
||||||
loadBalancerSourceRanges: {{ toYaml . | nindent 4 }}
|
|
||||||
{{- end }}
|
|
||||||
ports: {{- toYaml .ports | nindent 4 }}
|
|
||||||
{{- with .scope.publishNotReadyAddresses }}
|
|
||||||
publishNotReadyAddresses: {{ . }}
|
|
||||||
{{- end }}
|
|
||||||
{{- with .scope.sessionAffinity }}
|
|
||||||
sessionAffinity: {{ . }}
|
|
||||||
{{- end }}
|
|
||||||
{{- with .scope.sessionAffinityConfig }}
|
|
||||||
sessionAffinityConfig: {{ toYaml . | nindent 4 }}
|
|
||||||
{{- end }}
|
|
||||||
{{- with .scope.topologyKeys }}
|
|
||||||
topologyKeys: {{ toYaml . | nindent 4 }}
|
|
||||||
{{- end }}
|
|
||||||
{{- with .scope.type }}
|
|
||||||
type: {{ . }}
|
|
||||||
{{- end }}
|
|
||||||
selector: {{- include "wg-portal.selectorLabels" .context | nindent 4 }}
|
|
||||||
{{- end -}}
|
|
||||||
|
|
||||||
{{/*
|
|
||||||
Define the service port template for the web port
|
|
||||||
*/}}
|
|
||||||
{{- define "wg-portal.service.webPort" -}}
|
|
||||||
name: web
|
|
||||||
port: {{ .Values.service.web.port }}
|
|
||||||
protocol: TCP
|
|
||||||
targetPort: web
|
|
||||||
{{- if semverCompare ">=1.20-0" .Capabilities.KubeVersion.Version }}
|
|
||||||
appProtocol: {{ ternary "https" .Values.service.web.appProtocol .Values.certificate.enabled }}
|
|
||||||
{{- end -}}
|
|
||||||
{{- end -}}
|
|
@@ -1,54 +0,0 @@
|
|||||||
{{/* https://cert-manager.io/docs/usage/certificate/#creating-certificate-resources */}}
|
|
||||||
{{- if and .Values.certificate.enabled (include "wg-portal.hostname" .) -}}
|
|
||||||
apiVersion: cert-manager.io/v1
|
|
||||||
kind: Certificate
|
|
||||||
metadata:
|
|
||||||
name: {{ include "wg-portal.fullname" . }}
|
|
||||||
labels: {{- include "wg-portal.labels" . | nindent 4 }}
|
|
||||||
spec:
|
|
||||||
secretName: {{ include "wg-portal.fullname" . }}-tls
|
|
||||||
{{- with .Values.certificate.secretTemplate }}
|
|
||||||
secretTemplate: {{ toYaml . | nindent 4 }}
|
|
||||||
{{- end }}
|
|
||||||
{{- with .Values.certificate.privateKey }}
|
|
||||||
privateKey: {{- toYaml . | nindent 4 }}
|
|
||||||
{{- end }}
|
|
||||||
{{- with .Values.certificate.keystores }}
|
|
||||||
keystores: {{- toYaml . | nindent 4 }}
|
|
||||||
{{- end }}
|
|
||||||
{{- with .Values.certificate.duration }}
|
|
||||||
duration: {{ . }}
|
|
||||||
{{- end }}
|
|
||||||
{{- with .Values.certificate.renewBefore }}
|
|
||||||
renewBefore: {{ . }}
|
|
||||||
{{- end }}
|
|
||||||
{{- with .Values.certificate.usages }}
|
|
||||||
usages: {{- toYaml . | nindent 4 }}
|
|
||||||
{{- end }}
|
|
||||||
{{- with .Values.certificate.subject }}
|
|
||||||
subject: {{- toYaml . | nindent 4 }}
|
|
||||||
{{- end }}
|
|
||||||
{{- with .Values.certificate.commonName }}
|
|
||||||
commonName: {{ . }}
|
|
||||||
{{- end }}
|
|
||||||
dnsNames:
|
|
||||||
- {{ include "wg-portal.hostname" . }}
|
|
||||||
{{- with .Values.certificate.uris }}
|
|
||||||
uris: {{- toYaml . | nindent 4 }}
|
|
||||||
{{- end }}
|
|
||||||
{{- with .Values.certificate.emailAddresses }}
|
|
||||||
emailAddresses: {{- toYaml . | nindent 4 }}
|
|
||||||
{{- end }}
|
|
||||||
{{- with .Values.certificate.ipAddresses }}
|
|
||||||
ipAddresses: {{- toYaml . | nindent 4 }}
|
|
||||||
{{- end }}
|
|
||||||
{{- with .Values.certificate.otherNames }}
|
|
||||||
otherNames: {{- toYaml . | nindent 4 }}
|
|
||||||
{{- end }}
|
|
||||||
issuerRef:
|
|
||||||
{{- with .Values.certificate.issuer.group }}
|
|
||||||
group: {{ . }}
|
|
||||||
{{- end }}
|
|
||||||
kind: {{ .Values.certificate.issuer.kind }}
|
|
||||||
name: {{ .Values.certificate.issuer.name }}
|
|
||||||
{{- end -}}
|
|
@@ -1,14 +0,0 @@
|
|||||||
{{- with .Values.monitoring.dashboard -}}
|
|
||||||
{{- if .enabled }}
|
|
||||||
apiVersion: v1
|
|
||||||
kind: ConfigMap
|
|
||||||
metadata:
|
|
||||||
{{- with .annotations }}
|
|
||||||
annotations: {{- toYaml . | nindent 4 }}
|
|
||||||
{{- end }}
|
|
||||||
labels: {{- include "wg-portal.util.merge" (list $ .labels "wg-portal.labels") | nindent 4 }}
|
|
||||||
name: {{ printf "grafana-dashboards-%s" (include "wg-portal.fullname" $) }}
|
|
||||||
namespace: {{ default $.Release.Namespace .namespace }}
|
|
||||||
data: {{ ($.Files.Glob "files/dashboard.json").AsConfig | nindent 2 }}
|
|
||||||
{{- end -}}
|
|
||||||
{{- end -}}
|
|
@@ -1,17 +0,0 @@
|
|||||||
{{- if eq .Values.workloadType "Deployment" -}}
|
|
||||||
apiVersion: apps/v1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: {{ include "wg-portal.fullname" . }}
|
|
||||||
labels: {{- include "wg-portal.labels" . | nindent 4 }}
|
|
||||||
spec:
|
|
||||||
{{- with .Values.revisionHistoryLimit }}
|
|
||||||
revisionHistoryLimit: {{ . }}
|
|
||||||
{{- end }}
|
|
||||||
{{- with .Values.strategy }}
|
|
||||||
strategy: {{- toYaml . | nindent 4 }}
|
|
||||||
{{- end }}
|
|
||||||
selector:
|
|
||||||
matchLabels: {{- include "wg-portal.selectorLabels" . | nindent 6 }}
|
|
||||||
template: {{- include "wg-portal.podTemplate" . | nindent 4 }}
|
|
||||||
{{- end -}}
|
|
@@ -1,4 +0,0 @@
|
|||||||
{{- range .Values.extraDeploy -}}
|
|
||||||
{{- tpl (toYaml .) $ }}
|
|
||||||
---
|
|
||||||
{{- end -}}
|
|
@@ -1,30 +0,0 @@
|
|||||||
{{- $hostname := include "wg-portal.hostname" . -}}
|
|
||||||
{{- if and .Values.ingress.enabled $hostname -}}
|
|
||||||
apiVersion: networking.k8s.io/v1
|
|
||||||
kind: Ingress
|
|
||||||
metadata:
|
|
||||||
{{- with .Values.ingress.annotations }}
|
|
||||||
annotations: {{- toYaml . | nindent 4 }}
|
|
||||||
{{- end }}
|
|
||||||
name: {{ include "wg-portal.fullname" . }}
|
|
||||||
labels: {{- include "wg-portal.labels" . | nindent 4 }}
|
|
||||||
spec:
|
|
||||||
ingressClassName: {{ .Values.ingress.className }}
|
|
||||||
rules:
|
|
||||||
- host: {{ $hostname }}
|
|
||||||
http:
|
|
||||||
paths:
|
|
||||||
- path: {{ default "/" (urlParse (tpl .Values.config.web.external_url .)).path }}
|
|
||||||
pathType: {{ default "ImplementationSpecific" .pathType }}
|
|
||||||
backend:
|
|
||||||
service:
|
|
||||||
name: {{ include "wg-portal.fullname" . }}
|
|
||||||
port:
|
|
||||||
name: web
|
|
||||||
{{- if .Values.ingress.tls }}
|
|
||||||
tls:
|
|
||||||
- hosts:
|
|
||||||
- {{ $hostname | quote }}
|
|
||||||
secretName: {{ include "wg-portal.fullname" . }}-tls
|
|
||||||
{{- end }}
|
|
||||||
{{- end }}
|
|
@@ -1,44 +0,0 @@
|
|||||||
{{- with .Values.monitoring -}}
|
|
||||||
{{- if and .enabled ($.Capabilities.APIVersions.Has .apiVersion) -}}
|
|
||||||
{{- $endpointsKey := (eq .kind "PodMonitor") | ternary "podMetricsEndpoints" "endpoints" -}}
|
|
||||||
apiVersion: {{ .apiVersion }}
|
|
||||||
kind: {{ .kind }}
|
|
||||||
metadata:
|
|
||||||
{{- with .annotations }}
|
|
||||||
annotations: {{- toYaml . | nindent 4 }}
|
|
||||||
{{- end }}
|
|
||||||
labels: {{- include "wg-portal.util.merge" (list $ .labels "wg-portal.labels") | nindent 4 }}
|
|
||||||
name: {{ include "wg-portal.fullname" $ }}
|
|
||||||
spec:
|
|
||||||
namespaceSelector:
|
|
||||||
matchNames:
|
|
||||||
- {{ $.Release.Namespace }}
|
|
||||||
selector:
|
|
||||||
matchLabels:
|
|
||||||
{{- include "wg-portal.selectorLabels" $ | nindent 6 }}
|
|
||||||
{{ $endpointsKey }}:
|
|
||||||
- port: metrics
|
|
||||||
path: /metrics
|
|
||||||
interval: {{ coalesce .interval ($.Values.config.statistics).data_collection_interval "1m" }}
|
|
||||||
{{- with .metricRelabelings }}
|
|
||||||
metricRelabelings: {{- toYaml . | nindent 8 }}
|
|
||||||
{{- end }}
|
|
||||||
relabelings:
|
|
||||||
- action: replace
|
|
||||||
sourceLabels:
|
|
||||||
- __meta_kubernetes_pod_label_app_kubernetes_io_name
|
|
||||||
targetLabel: instance
|
|
||||||
{{- with .relabelings }}
|
|
||||||
{{- toYaml . | nindent 8 }}
|
|
||||||
{{- end }}
|
|
||||||
{{- with .scrapeTimeout }}
|
|
||||||
scrapeTimeout: {{ . }}
|
|
||||||
{{- end }}
|
|
||||||
{{- with .jobLabel }}
|
|
||||||
jobLabel: {{ . }}
|
|
||||||
{{- end }}
|
|
||||||
{{- with .podTargetLabels }}
|
|
||||||
podTargetLabels: {{- toYaml . | nindent 2 }}
|
|
||||||
{{- end }}
|
|
||||||
{{- end -}}
|
|
||||||
{{- end -}}
|
|