mirror of
https://github.com/h44z/wg-portal.git
synced 2025-10-05 16:06:17 +00:00
Compare commits
8 Commits
v2.0.0-rc.
...
v2.0.0-rc.
Author | SHA1 | Date | |
---|---|---|---|
|
dddf0c475b | ||
|
fe60a5ab9b | ||
|
e176e07f7d | ||
|
b06c03ef8e | ||
|
6b0b78d749 | ||
|
62f3c8d4a1 | ||
|
fbcb22198c | ||
|
2c443a4a9b |
8
.github/workflows/docker-publish.yml
vendored
8
.github/workflows/docker-publish.yml
vendored
@@ -64,10 +64,10 @@ jobs:
|
||||
# major and major.minor tags are not available for alpha or beta releases
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=semver,pattern={{major}}
|
||||
# add v{{major}} tag, even for beta releases
|
||||
type=match,pattern=(v\d),group=1,enable=${{ contains(github.ref, 'beta') }}
|
||||
# add {{major}} tag, even for beta releases
|
||||
type=match,pattern=v(\d),group=1,enable=${{ contains(github.ref, 'beta') }}
|
||||
# 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') }}
|
||||
# set latest tag for default branch
|
||||
type=raw,value=latest,enable={{is_default_branch}}
|
||||
|
||||
|
@@ -52,7 +52,7 @@ COPY --from=builder /build/dist/wg-portal /
|
||||
######
|
||||
FROM alpine:3.19
|
||||
# Install OS-level dependencies
|
||||
RUN apk add --no-cache bash curl iptables nftables openresolv
|
||||
RUN apk add --no-cache bash curl iptables nftables openresolv wireguard-tools
|
||||
# Setup timezone
|
||||
ENV TZ=UTC
|
||||
# Copy binaries
|
||||
|
@@ -31,4 +31,4 @@ sudo install wg-portal /opt/wg-portal/
|
||||
## Unreleased
|
||||
|
||||
Unreleased versions could be downloaded from
|
||||
[GitHub Workflow](https://github.com/h44z/wg-portal/actions/workflows/docker-publish.yml?query=branch%3Amaster) artifacs also.
|
||||
[GitHub Workflow](https://github.com/h44z/wg-portal/actions/workflows/docker-publish.yml?query=branch%3Amaster) artifacts also.
|
||||
|
@@ -1,8 +1,13 @@
|
||||
## Image Usage
|
||||
|
||||
The preferred way to start WireGuard Portal as Docker container is to use Docker Compose.
|
||||
The WireGuard Portal Docker image is available on both [Docker Hub](https://hub.docker.com/r/wgportal/wg-portal) and [GitHub Container Registry](https://github.com/h44z/wg-portal/pkgs/container/wg-portal).
|
||||
It is built on the official Alpine Linux base image and comes pre-packaged with all necessary WireGuard dependencies.
|
||||
|
||||
A sample docker-compose.yml:
|
||||
This container allows you to establish WireGuard VPN connections without relying on a host system that supports WireGuard or using the `linuxserver/wireguard` Docker image.
|
||||
|
||||
The recommended method for deploying WireGuard Portal is via Docker Compose for ease of configuration and management.
|
||||
|
||||
A sample docker-compose.yml (managing WireGuard interfaces directly on the host) is provided below:
|
||||
|
||||
```yaml
|
||||
--8<-- "docker-compose.yml::17"
|
||||
@@ -12,14 +17,98 @@ By default, the webserver is listening on port **8888**.
|
||||
|
||||
Volumes for `/app/data` and `/app/config` should be used ensure data persistence across container restarts.
|
||||
|
||||
## WireGuard Interface Handling
|
||||
|
||||
WireGuard Portal supports managing WireGuard interfaces through three distinct deployment methods, providing flexibility based on your system architecture and operational preferences:
|
||||
|
||||
- **Directly on the host system**:
|
||||
WireGuard Portal can control WireGuard interfaces natively on the host, without using containers.
|
||||
This setup is ideal for environments where direct access to system networking is preferred.
|
||||
To use this method, you need to set the network mode to `host` in your docker-compose.yml file.
|
||||
```yaml
|
||||
services:
|
||||
wg-portal:
|
||||
...
|
||||
network_mode: "host"
|
||||
...
|
||||
```
|
||||
|
||||
- **Within the WireGuard Portal Docker container**:
|
||||
WireGuard interfaces can be managed directly from within the WireGuard Portal container itself.
|
||||
This is the recommended approach when running WireGuard Portal via Docker, as it encapsulates all functionality in a single, portable container without requiring a separate WireGuard host or image.
|
||||
```yaml
|
||||
services:
|
||||
wg-portal:
|
||||
image: wgportal/wg-portal:latest
|
||||
container_name: wg-portal
|
||||
...
|
||||
cap_add:
|
||||
- NET_ADMIN
|
||||
ports:
|
||||
# WireGuard port, needs to match the port in wg-portal interface config (add one port mapping for each interface)
|
||||
- "51820:51820/udp"
|
||||
# Web UI port
|
||||
- "8888:8888/tcp"
|
||||
sysctls:
|
||||
- net.ipv4.conf.all.src_valid_mark=1
|
||||
volumes:
|
||||
- ./wg/data:/app/data
|
||||
- ./wg/config:/app/config
|
||||
```
|
||||
|
||||
- **Via a separate Docker container**:
|
||||
WireGuard Portal can interface with and control WireGuard running in another Docker container, such as the [linuxserver/wireguard](https://docs.linuxserver.io/images/docker-wireguard/) image.
|
||||
This method is useful in setups that already use `linuxserver/wireguard` or where you want to isolate the VPN backend from the portal frontend.
|
||||
For this, you need to set the network mode to `service:wireguard` in your docker-compose.yml file, `wireguard` is the service name of your WireGuard container.
|
||||
```yaml
|
||||
services:
|
||||
wg-portal:
|
||||
image: wgportal/wg-portal:latest
|
||||
container_name: wg-portal
|
||||
...
|
||||
cap_add:
|
||||
- NET_ADMIN
|
||||
network_mode: "service:wireguard" # So we ensure to stay on the same network as the wireguard container.
|
||||
volumes:
|
||||
- ./wg/etc:/etc/wireguard
|
||||
- ./wg/data:/app/data
|
||||
- ./wg/config:/app/config
|
||||
|
||||
wireguard:
|
||||
image: lscr.io/linuxserver/wireguard:latest
|
||||
container_name: wireguard
|
||||
restart: unless-stopped
|
||||
cap_add:
|
||||
- NET_ADMIN
|
||||
ports:
|
||||
- "51820:51820/udp" # WireGuard port, needs to match the port in wg-portal interface config
|
||||
- "8888:8888/tcp" # Noticed that the port of the web UI is exposed in the wireguard container.
|
||||
volumes:
|
||||
- ./wg/etc:/config/wg_confs # We share the configuration (wgx.conf) between wg-portal and wireguard
|
||||
sysctls:
|
||||
- net.ipv4.conf.all.src_valid_mark=1
|
||||
```
|
||||
As the `linuxserver/wireguard` image uses _wg-quick_ to manage the interfaces, you need to have at least the following configuration set for WireGuard Portal:
|
||||
```yaml
|
||||
core:
|
||||
# The WireGuard container uses wg-quick to manage the WireGuard interfaces - this conflicts with WireGuard Portal during startup.
|
||||
# To avoid this, we need to set the restore_state option to false so that wg-quick can create the interfaces.
|
||||
restore_state: false
|
||||
# Usually, there are no existing interfaces in the WireGuard container, so we can set this to false.
|
||||
import_existing: false
|
||||
advanced:
|
||||
# WireGuard Portal needs to export the WireGuard configuration as wg-quick config files so that the WireGuard container can use them.
|
||||
config_storage_path: /etc/wireguard/
|
||||
```
|
||||
|
||||
## Image Versioning
|
||||
|
||||
All images are hosted on Docker Hub at [https://hub.docker.com/r/wgportal/wg-portal](https://hub.docker.com/r/wgportal/wg-portal).
|
||||
All images are hosted on Docker Hub at [https://hub.docker.com/r/wgportal/wg-portal](https://hub.docker.com/r/wgportal/wg-portal) or in the [GitHub Container Registry](https://github.com/h44z/wg-portal/pkgs/container/wg-portal).
|
||||
There are three types of tags in the repository:
|
||||
|
||||
#### Semantic versioned tags
|
||||
|
||||
For example, `1.0.19`.
|
||||
For example, `2.0.0-rc.1` or `v2.0.0-rc.1`.
|
||||
|
||||
These are official releases of WireGuard Portal. They correspond to the GitHub tags that we make, and you can see the release notes for them here: [https://github.com/h44z/wg-portal/releases](https://github.com/h44z/wg-portal/releases).
|
||||
|
||||
@@ -43,15 +132,22 @@ For each commit in the master and the stable branch, a corresponding Docker imag
|
||||
|
||||
## Configuration
|
||||
|
||||
You can configure WireGuard Portal using a yaml configuration file.
|
||||
The filepath of the yaml configuration file defaults to `/app/config/config.yml`.
|
||||
You can configure WireGuard Portal using a YAML configuration file.
|
||||
The filepath of the YAML configuration file defaults to `/app/config/config.yml`.
|
||||
It is possible to override the configuration filepath using the environment variable **WG_PORTAL_CONFIG**.
|
||||
|
||||
By default, WireGuard Portal uses a SQLite database. The database is stored in `/app/data/sqlite.db`.
|
||||
By default, WireGuard Portal uses an SQLite database. The database is stored in `/app/data/sqlite.db`.
|
||||
|
||||
You should mount those directories as a volume:
|
||||
|
||||
- /app/data
|
||||
- /app/config
|
||||
- `/app/data`
|
||||
- `/app/config`
|
||||
|
||||
A detailed description of the configuration options can be found [here](../configuration/overview.md).
|
||||
|
||||
If you want to access configuration files in wg-quick format, you can mount the `/etc/wireguard` directory to a location of your choice.
|
||||
Also enable the `config_storage_path` option in the configuration file:
|
||||
```yaml
|
||||
advanced:
|
||||
config_storage_path: /etc/wireguard
|
||||
```
|
||||
|
@@ -21,4 +21,5 @@ make build
|
||||
|
||||
## Install
|
||||
|
||||
Compiled binary will be available in `./dist` directory.
|
||||
Compiled binary will be available in `./dist` directory.
|
||||
For installation instructions, check the [Binaries](./binaries.md) section.
|
||||
|
@@ -1,5 +1,5 @@
|
||||
For production deployments of WireGuard Portal, we strongly recommend using version 1.
|
||||
If you want to use version 2, please be aware that it is still in beta and not feature complete.
|
||||
If you want to use version 2, please be aware that it is still a release candidate and not yet fully stable.
|
||||
|
||||
## Upgrade from v1 to v2
|
||||
|
||||
|
@@ -48,8 +48,11 @@ const languageFlag = computed(() => {
|
||||
}
|
||||
const langMap = {
|
||||
en: "us",
|
||||
pt: "pt",
|
||||
uk: "ua",
|
||||
zh: "cn",
|
||||
ko: "kr",
|
||||
|
||||
};
|
||||
return "fi-" + (langMap[lang] || lang);
|
||||
})
|
||||
@@ -121,10 +124,13 @@ const currentYear = ref(new Date().getFullYear())
|
||||
<a class="dropdown-item" href="#" @click.prevent="switchLanguage('de')"><span class="fi fi-de"></span> Deutsch</a>
|
||||
<a class="dropdown-item" href="#" @click.prevent="switchLanguage('en')"><span class="fi fi-us"></span> English</a>
|
||||
<a class="dropdown-item" href="#" @click.prevent="switchLanguage('fr')"><span class="fi fi-fr"></span> Français</a>
|
||||
<a class="dropdown-item" href="#" @click.prevent="switchLanguage('ko')"><span class="fi fi-kr"></span> 한국어</a>
|
||||
<a class="dropdown-item" href="#" @click.prevent="switchLanguage('pt')"><span class="fi fi-pt"></span> Português</a>
|
||||
<a class="dropdown-item" href="#" @click.prevent="switchLanguage('ru')"><span class="fi fi-ru"></span> Русский</a>
|
||||
<a class="dropdown-item" href="#" @click.prevent="switchLanguage('uk')"><span class="fi fi-ua"></span> Українська</a>
|
||||
<a class="dropdown-item" href="#" @click.prevent="switchLanguage('vi')"><span class="fi fi-vi"></span> Tiếng Việt</a>
|
||||
<a class="dropdown-item" href="#" @click.prevent="switchLanguage('zh')"><span class="fi fi-cn"></span> 中文</a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -2,10 +2,13 @@
|
||||
import de from './translations/de.json';
|
||||
import en from './translations/en.json';
|
||||
import fr from './translations/fr.json';
|
||||
import ko from './translations/ko.json';
|
||||
import pt from './translations/pt.json';
|
||||
import ru from './translations/ru.json';
|
||||
import uk from './translations/uk.json';
|
||||
import vi from './translations/vi.json';
|
||||
import zh from './translations/zh.json';
|
||||
|
||||
import {createI18n} from "vue-i18n";
|
||||
|
||||
// Create i18n instance with options
|
||||
@@ -23,6 +26,8 @@ const i18n = createI18n({
|
||||
"de": de,
|
||||
"en": en,
|
||||
"fr": fr,
|
||||
"ko": ko,
|
||||
"pt": pt,
|
||||
"ru": ru,
|
||||
"uk": uk,
|
||||
"vi": vi,
|
||||
|
532
frontend/src/lang/translations/ko.json
Normal file
532
frontend/src/lang/translations/ko.json
Normal file
@@ -0,0 +1,532 @@
|
||||
{
|
||||
"languages": {
|
||||
"ko": "한국어"
|
||||
},
|
||||
"general": {
|
||||
"pagination": {
|
||||
"size": "항목 수",
|
||||
"all": "전체 (느림)"
|
||||
},
|
||||
"search": {
|
||||
"placeholder": "검색...",
|
||||
"button": "검색"
|
||||
},
|
||||
"select-all": "모두 선택",
|
||||
"yes": "예",
|
||||
"no": "아니오",
|
||||
"cancel": "취소",
|
||||
"close": "닫기",
|
||||
"save": "저장",
|
||||
"delete": "삭제"
|
||||
},
|
||||
"login": {
|
||||
"headline": "로그인하세요",
|
||||
"username": {
|
||||
"label": "사용자 이름",
|
||||
"placeholder": "사용자 이름을 입력하세요"
|
||||
},
|
||||
"password": {
|
||||
"label": "비밀번호",
|
||||
"placeholder": "비밀번호를 입력하세요"
|
||||
},
|
||||
"button": "로그인"
|
||||
},
|
||||
"menu": {
|
||||
"home": "홈",
|
||||
"interfaces": "인터페이스",
|
||||
"users": "사용자",
|
||||
"lang": "언어 변경",
|
||||
"profile": "내 프로필",
|
||||
"settings": "설정",
|
||||
"audit": "감사 로그",
|
||||
"login": "로그인",
|
||||
"logout": "로그아웃"
|
||||
},
|
||||
"home": {
|
||||
"headline": "WireGuard® VPN 포털",
|
||||
"info-headline": "추가 정보",
|
||||
"abstract": "WireGuard®는 암호화 기술을 활용하는 매우 간단하면서도 빠르고 현대적인 VPN입니다. IPsec보다 빠르고, 간단하며, 가볍고, 더 유용하면서도 엄청난 골칫거리를 피하는 것을 목표로 합니다. OpenVPN보다 훨씬 더 성능이 뛰어날 것으로 예상됩니다.",
|
||||
"installation": {
|
||||
"box-header": "WireGuard 설치",
|
||||
"headline": "설치",
|
||||
"content": "클라이언트 소프트웨어 설치 지침은 공식 WireGuard 웹사이트에서 찾을 수 있습니다.",
|
||||
"button": "지침 열기"
|
||||
},
|
||||
"about-wg": {
|
||||
"box-header": "WireGuard 정보",
|
||||
"headline": "정보",
|
||||
"content": "WireGuard®는 암호화 기술을 활용하는 매우 간단하면서도 빠르고 현대적인 VPN입니다.",
|
||||
"button": "더 보기"
|
||||
},
|
||||
"about-portal": {
|
||||
"box-header": "WireGuard 포털 정보",
|
||||
"headline": "WireGuard 포털",
|
||||
"content": "WireGuard 포털은 WireGuard를 위한 간단한 웹 기반 구성 포털입니다.",
|
||||
"button": "더 보기"
|
||||
},
|
||||
"profiles": {
|
||||
"headline": "VPN 프로필",
|
||||
"abstract": "사용자 프로필을 통해 개인 VPN 구성에 액세스하고 다운로드할 수 있습니다.",
|
||||
"content": "구성된 모든 프로필을 찾으려면 아래 버튼을 클릭하세요.",
|
||||
"button": "내 프로필 열기"
|
||||
},
|
||||
"admin": {
|
||||
"headline": "관리 영역",
|
||||
"abstract": "관리 영역에서는 WireGuard 피어 및 서버 인터페이스뿐만 아니라 WireGuard 포털에 로그인할 수 있는 사용자도 관리할 수 있습니다.",
|
||||
"content": "",
|
||||
"button-admin": "서버 관리 열기",
|
||||
"button-user": "사용자 관리 열기"
|
||||
}
|
||||
},
|
||||
"interfaces": {
|
||||
"headline": "인터페이스 관리",
|
||||
"headline-peers": "현재 VPN 피어",
|
||||
"headline-endpoints": "현재 엔드포인트",
|
||||
"no-interface": {
|
||||
"default-selection": "사용 가능한 인터페이스 없음",
|
||||
"headline": "인터페이스를 찾을 수 없습니다...",
|
||||
"abstract": "새 WireGuard 인터페이스를 만들려면 위의 플러스 버튼을 클릭하세요."
|
||||
},
|
||||
"no-peer": {
|
||||
"headline": "사용 가능한 피어 없음",
|
||||
"abstract": "현재 선택한 WireGuard 인터페이스에 사용 가능한 피어가 없습니다."
|
||||
},
|
||||
"table-heading": {
|
||||
"name": "이름",
|
||||
"user": "사용자",
|
||||
"ip": "IP 주소",
|
||||
"endpoint": "엔드포인트",
|
||||
"status": "상태"
|
||||
},
|
||||
"interface": {
|
||||
"headline": "인터페이스 상태:",
|
||||
"mode": "모드",
|
||||
"key": "공개 키",
|
||||
"endpoint": "공개 엔드포인트",
|
||||
"port": "수신 포트",
|
||||
"peers": "활성화된 피어",
|
||||
"total-peers": "총 피어 수",
|
||||
"endpoints": "활성화된 엔드포인트",
|
||||
"total-endpoints": "총 엔드포인트 수",
|
||||
"ip": "IP 주소",
|
||||
"default-allowed-ip": "기본 허용 IP",
|
||||
"dns": "DNS 서버",
|
||||
"mtu": "MTU",
|
||||
"default-keep-alive": "기본 Keepalive 간격",
|
||||
"button-show-config": "구성 보기",
|
||||
"button-download-config": "구성 다운로드",
|
||||
"button-store-config": "wg-quick용 구성 저장",
|
||||
"button-edit": "인터페이스 편집"
|
||||
},
|
||||
"button-add-interface": "인터페이스 추가",
|
||||
"button-add-peer": "피어 추가",
|
||||
"button-add-peers": "여러 피어 추가",
|
||||
"button-show-peer": "피어 보기",
|
||||
"button-edit-peer": "피어 편집",
|
||||
"peer-disabled": "피어가 비활성화됨, 이유:",
|
||||
"peer-expiring": "피어 만료 예정:",
|
||||
"peer-connected": "연결됨",
|
||||
"peer-not-connected": "연결되지 않음",
|
||||
"peer-handshake": "마지막 핸드셰이크:"
|
||||
},
|
||||
"users": {
|
||||
"headline": "사용자 관리",
|
||||
"table-heading": {
|
||||
"id": "ID",
|
||||
"email": "이메일",
|
||||
"firstname": "이름",
|
||||
"lastname": "성",
|
||||
"source": "소스",
|
||||
"peers": "피어",
|
||||
"admin": "관리자"
|
||||
},
|
||||
"no-user": {
|
||||
"headline": "사용 가능한 사용자 없음",
|
||||
"abstract": "현재 WireGuard 포털에 등록된 사용자가 없습니다."
|
||||
},
|
||||
"button-add-user": "사용자 추가",
|
||||
"button-show-user": "사용자 보기",
|
||||
"button-edit-user": "사용자 편집",
|
||||
"user-disabled": "사용자가 비활성화됨, 이유:",
|
||||
"user-locked": "계정이 잠김, 이유:",
|
||||
"admin": "사용자에게 관리자 권한이 있습니다",
|
||||
"no-admin": "사용자에게 관리자 권한이 없습니다"
|
||||
},
|
||||
"profile": {
|
||||
"headline": "내 VPN 피어",
|
||||
"table-heading": {
|
||||
"name": "이름",
|
||||
"ip": "IP 주소",
|
||||
"stats": "상태",
|
||||
"interface": "서버 인터페이스"
|
||||
},
|
||||
"no-peer": {
|
||||
"headline": "사용 가능한 피어 없음",
|
||||
"abstract": "현재 사용자 프로필과 연결된 피어가 없습니다."
|
||||
},
|
||||
"peer-connected": "연결됨",
|
||||
"button-add-peer": "피어 추가",
|
||||
"button-show-peer": "피어 보기",
|
||||
"button-edit-peer": "피어 편집"
|
||||
},
|
||||
"settings": {
|
||||
"headline": "설정",
|
||||
"abstract": "여기에서 개인 설정을 변경할 수 있습니다.",
|
||||
"api": {
|
||||
"headline": "API 설정",
|
||||
"abstract": "여기에서 RESTful API 설정을 구성할 수 있습니다.",
|
||||
"active-description": "현재 사용자 계정에 대해 API가 활성화되어 있습니다. 모든 API 요청은 기본 인증(Basic Auth)으로 인증됩니다. 인증에 다음 자격 증명을 사용하세요.",
|
||||
"inactive-description": "현재 API가 비활성화되어 있습니다. 활성화하려면 아래 버튼을 누르세요.",
|
||||
"user-label": "API 사용자 이름:",
|
||||
"user-placeholder": "API 사용자",
|
||||
"token-label": "API 비밀번호:",
|
||||
"token-placeholder": "API 토큰",
|
||||
"token-created-label": "API 액세스 권한 부여 시각: ",
|
||||
"button-disable-title": "API를 비활성화합니다. 현재 토큰이 무효화됩니다.",
|
||||
"button-disable-text": "API 비활성화",
|
||||
"button-enable-title": "API를 활성화합니다. 새 토큰이 생성됩니다.",
|
||||
"button-enable-text": "API 활성화",
|
||||
"api-link": "API 문서"
|
||||
}
|
||||
},
|
||||
"audit": {
|
||||
"headline": "감사 로그",
|
||||
"abstract": "여기에서 WireGuard 포털에서 수행된 모든 작업의 감사 로그를 찾을 수 있습니다.",
|
||||
"no-entries": {
|
||||
"headline": "로그 항목 없음",
|
||||
"abstract": "현재 기록된 감사 로그가 없습니다."
|
||||
},
|
||||
"entries-headline": "로그 항목",
|
||||
"table-heading": {
|
||||
"id": "#",
|
||||
"time": "시간",
|
||||
"user": "사용자",
|
||||
"severity": "심각도",
|
||||
"origin": "출처",
|
||||
"message": "메시지"
|
||||
}
|
||||
},
|
||||
"modals": {
|
||||
"user-view": {
|
||||
"headline": "사용자 계정:",
|
||||
"tab-user": "정보",
|
||||
"tab-peers": "피어",
|
||||
"headline-info": "사용자 정보:",
|
||||
"headline-notes": "메모:",
|
||||
"email": "이메일",
|
||||
"firstname": "이름",
|
||||
"lastname": "성",
|
||||
"phone": "전화번호",
|
||||
"department": "부서",
|
||||
"api-enabled": "API 액세스",
|
||||
"disabled": "계정 비활성화됨",
|
||||
"locked": "계정 잠김",
|
||||
"no-peers": "사용자에게 연결된 피어가 없습니다.",
|
||||
"peers": {
|
||||
"name": "이름",
|
||||
"interface": "인터페이스",
|
||||
"ip": "IP 주소"
|
||||
}
|
||||
},
|
||||
"user-edit": {
|
||||
"headline-edit": "사용자 편집:",
|
||||
"headline-new": "새 사용자",
|
||||
"header-general": "일반",
|
||||
"header-personal": "사용자 정보",
|
||||
"header-notes": "메모",
|
||||
"header-state": "상태",
|
||||
"identifier": {
|
||||
"label": "식별자",
|
||||
"placeholder": "고유한 사용자 식별자"
|
||||
},
|
||||
"source": {
|
||||
"label": "소스",
|
||||
"placeholder": "사용자 소스"
|
||||
},
|
||||
"password": {
|
||||
"label": "비밀번호",
|
||||
"placeholder": "매우 비밀스러운 비밀번호",
|
||||
"description": "현재 비밀번호를 유지하려면 이 필드를 비워 두세요."
|
||||
},
|
||||
"email": {
|
||||
"label": "이메일",
|
||||
"placeholder": "이메일 주소"
|
||||
},
|
||||
"phone": {
|
||||
"label": "전화번호",
|
||||
"placeholder": "전화번호"
|
||||
},
|
||||
"department": {
|
||||
"label": "부서",
|
||||
"placeholder": "부서"
|
||||
},
|
||||
"firstname": {
|
||||
"label": "이름",
|
||||
"placeholder": "이름"
|
||||
},
|
||||
"lastname": {
|
||||
"label": "성",
|
||||
"placeholder": "성"
|
||||
},
|
||||
"notes": {
|
||||
"label": "메모",
|
||||
"placeholder": ""
|
||||
},
|
||||
"disabled": {
|
||||
"label": "비활성화됨 (WireGuard 연결 및 로그인 불가)"
|
||||
},
|
||||
"locked": {
|
||||
"label": "잠김 (로그인 불가, WireGuard 연결은 계속 작동)"
|
||||
},
|
||||
"admin": {
|
||||
"label": "관리자 여부"
|
||||
}
|
||||
},
|
||||
"interface-view": {
|
||||
"headline": "인터페이스 구성:"
|
||||
},
|
||||
"interface-edit": {
|
||||
"headline-edit": "인터페이스 편집:",
|
||||
"headline-new": "새 인터페이스",
|
||||
"tab-interface": "인터페이스",
|
||||
"tab-peerdef": "피어 기본값",
|
||||
"header-general": "일반",
|
||||
"header-network": "네트워크",
|
||||
"header-crypto": "암호화",
|
||||
"header-hooks": "인터페이스 후크",
|
||||
"header-peer-hooks": "후크",
|
||||
"header-state": "상태",
|
||||
"identifier": {
|
||||
"label": "식별자",
|
||||
"placeholder": "고유한 인터페이스 식별자"
|
||||
},
|
||||
"mode": {
|
||||
"label": "인터페이스 모드",
|
||||
"server": "서버 모드",
|
||||
"client": "클라이언트 모드",
|
||||
"any": "알 수 없는 모드"
|
||||
},
|
||||
"display-name": {
|
||||
"label": "표시 이름",
|
||||
"placeholder": "인터페이스에 대한 설명적인 이름"
|
||||
},
|
||||
"private-key": {
|
||||
"label": "개인 키",
|
||||
"placeholder": "개인 키"
|
||||
},
|
||||
"public-key": {
|
||||
"label": "공개 키",
|
||||
"placeholder": "공개 키"
|
||||
},
|
||||
"ip": {
|
||||
"label": "IP 주소",
|
||||
"placeholder": "IP 주소 (CIDR 형식)"
|
||||
},
|
||||
"listen-port": {
|
||||
"label": "수신 포트",
|
||||
"placeholder": "수신 포트"
|
||||
},
|
||||
"dns": {
|
||||
"label": "DNS 서버",
|
||||
"placeholder": "사용해야 하는 DNS 서버"
|
||||
},
|
||||
"dns-search": {
|
||||
"label": "DNS 검색 도메인",
|
||||
"placeholder": "DNS 검색 접두사"
|
||||
},
|
||||
"mtu": {
|
||||
"label": "MTU",
|
||||
"placeholder": "인터페이스 MTU (0 = 기본값 유지)"
|
||||
},
|
||||
"firewall-mark": {
|
||||
"label": "방화벽 표시",
|
||||
"placeholder": "나가는 트래픽에 적용되는 방화벽 표시. (0 = 자동)"
|
||||
},
|
||||
"routing-table": {
|
||||
"label": "라우팅 테이블",
|
||||
"placeholder": "라우팅 테이블 ID",
|
||||
"description": "특수 사례: off = 경로 관리 안 함, 0 = 자동"
|
||||
},
|
||||
"pre-up": {
|
||||
"label": "Pre-Up",
|
||||
"placeholder": "하나 이상의 bash 명령 (;으로 구분)"
|
||||
},
|
||||
"post-up": {
|
||||
"label": "Post-Up",
|
||||
"placeholder": "하나 이상의 bash 명령 (;으로 구분)"
|
||||
},
|
||||
"pre-down": {
|
||||
"label": "Pre-Down",
|
||||
"placeholder": "하나 이상의 bash 명령 (;으로 구분)"
|
||||
},
|
||||
"post-down": {
|
||||
"label": "Post-Down",
|
||||
"placeholder": "하나 이상의 bash 명령 (;으로 구분)"
|
||||
},
|
||||
"disabled": {
|
||||
"label": "인터페이스 비활성화됨"
|
||||
},
|
||||
"save-config": {
|
||||
"label": "wg-quick 구성 자동 저장"
|
||||
},
|
||||
"defaults": {
|
||||
"endpoint": {
|
||||
"label": "엔드포인트 주소",
|
||||
"placeholder": "엔드포인트 주소",
|
||||
"description": "피어가 연결할 엔드포인트 주소. (예: wg.example.com 또는 wg.example.com:51820)"
|
||||
},
|
||||
"networks": {
|
||||
"label": "IP 네트워크",
|
||||
"placeholder": "네트워크 주소",
|
||||
"description": "피어는 해당 서브넷에서 IP 주소를 받습니다."
|
||||
},
|
||||
"allowed-ip": {
|
||||
"label": "허용된 IP 주소",
|
||||
"placeholder": "기본 허용 IP 주소"
|
||||
},
|
||||
"mtu": {
|
||||
"label": "MTU",
|
||||
"placeholder": "클라이언트 MTU (0 = 기본값 유지)"
|
||||
},
|
||||
"keep-alive": {
|
||||
"label": "Keep Alive 간격",
|
||||
"placeholder": "영구 Keepalive (0 = 기본값)"
|
||||
}
|
||||
},
|
||||
"button-apply-defaults": "피어 기본값 적용"
|
||||
},
|
||||
"peer-view": {
|
||||
"headline-peer": "피어:",
|
||||
"headline-endpoint": "엔드포인트:",
|
||||
"section-info": "피어 정보",
|
||||
"section-status": "현재 상태",
|
||||
"section-config": "구성",
|
||||
"identifier": "식별자",
|
||||
"ip": "IP 주소",
|
||||
"user": "연결된 사용자",
|
||||
"notes": "메모",
|
||||
"expiry-status": "만료 시각",
|
||||
"disabled-status": "비활성화 시각",
|
||||
"traffic": "트래픽",
|
||||
"connection-status": "연결 통계",
|
||||
"upload": "업로드된 바이트 (서버에서 피어로)",
|
||||
"download": "다운로드된 바이트 (피어에서 서버로)",
|
||||
"pingable": "핑 가능 여부",
|
||||
"handshake": "마지막 핸드셰이크",
|
||||
"connected-since": "연결 시작 시각",
|
||||
"endpoint": "엔드포인트",
|
||||
"button-download": "구성 다운로드",
|
||||
"button-email": "이메일로 구성 보내기"
|
||||
},
|
||||
"peer-edit": {
|
||||
"headline-edit-peer": "피어 편집:",
|
||||
"headline-edit-endpoint": "엔드포인트 편집:",
|
||||
"headline-new-peer": "피어 생성",
|
||||
"headline-new-endpoint": "엔드포인트 생성",
|
||||
"header-general": "일반",
|
||||
"header-network": "네트워크",
|
||||
"header-crypto": "암호화",
|
||||
"header-hooks": "후크 (피어에서 실행됨)",
|
||||
"header-state": "상태",
|
||||
"display-name": {
|
||||
"label": "표시 이름",
|
||||
"placeholder": "피어에 대한 설명적인 이름"
|
||||
},
|
||||
"linked-user": {
|
||||
"label": "연결된 사용자",
|
||||
"placeholder": "이 피어를 소유한 사용자 계정"
|
||||
},
|
||||
"private-key": {
|
||||
"label": "개인 키",
|
||||
"placeholder": "개인 키"
|
||||
},
|
||||
"public-key": {
|
||||
"label": "공개 키",
|
||||
"placeholder": "공개 키"
|
||||
},
|
||||
"preshared-key": {
|
||||
"label": "사전 공유 키",
|
||||
"placeholder": "선택적 사전 공유 키"
|
||||
},
|
||||
"endpoint-public-key": {
|
||||
"label": "엔드포인트 공개 키",
|
||||
"placeholder": "원격 엔드포인트의 공개 키"
|
||||
},
|
||||
"endpoint": {
|
||||
"label": "엔드포인트 주소",
|
||||
"placeholder": "원격 엔드포인트의 주소"
|
||||
},
|
||||
"ip": {
|
||||
"label": "IP 주소",
|
||||
"placeholder": "IP 주소 (CIDR 형식)"
|
||||
},
|
||||
"allowed-ip": {
|
||||
"label": "허용된 IP 주소",
|
||||
"placeholder": "허용된 IP 주소 (CIDR 형식)"
|
||||
},
|
||||
"extra-allowed-ip": {
|
||||
"label": "추가 허용 IP 주소",
|
||||
"placeholder": "추가 허용 IP (서버 측)",
|
||||
"description": "이 IP 주소는 원격 WireGuard 인터페이스에 허용된 IP로 추가됩니다."
|
||||
},
|
||||
"dns": {
|
||||
"label": "DNS 서버",
|
||||
"placeholder": "사용해야 하는 DNS 서버"
|
||||
},
|
||||
"dns-search": {
|
||||
"label": "DNS 검색 도메인",
|
||||
"placeholder": "DNS 검색 접두사"
|
||||
},
|
||||
"keep-alive": {
|
||||
"label": "Keep Alive 간격",
|
||||
"placeholder": "영구 Keepalive (0 = 기본값)"
|
||||
},
|
||||
"mtu": {
|
||||
"label": "MTU",
|
||||
"placeholder": "클라이언트 MTU (0 = 기본값 유지)"
|
||||
},
|
||||
"pre-up": {
|
||||
"label": "Pre-Up",
|
||||
"placeholder": "하나 이상의 bash 명령 (;으로 구분)"
|
||||
},
|
||||
"post-up": {
|
||||
"label": "Post-Up",
|
||||
"placeholder": "하나 이상의 bash 명령 (;으로 구분)"
|
||||
},
|
||||
"pre-down": {
|
||||
"label": "Pre-Down",
|
||||
"placeholder": "하나 이상의 bash 명령 (;으로 구분)"
|
||||
},
|
||||
"post-down": {
|
||||
"label": "Post-Down",
|
||||
"placeholder": "하나 이상의 bash 명령 (;으로 구분)"
|
||||
},
|
||||
"disabled": {
|
||||
"label": "피어 비활성화됨"
|
||||
},
|
||||
"ignore-global": {
|
||||
"label": "전역 설정 무시"
|
||||
},
|
||||
"expires-at": {
|
||||
"label": "만료 날짜"
|
||||
}
|
||||
},
|
||||
"peer-multi-create": {
|
||||
"headline-peer": "여러 피어 생성",
|
||||
"headline-endpoint": "여러 엔드포인트 생성",
|
||||
"identifiers": {
|
||||
"label": "사용자 식별자",
|
||||
"placeholder": "사용자 식별자",
|
||||
"description": "피어를 생성할 사용자 식별자 (사용자 이름)."
|
||||
},
|
||||
"prefix": {
|
||||
"headline-peer": "피어:",
|
||||
"headline-endpoint": "엔드포인트:",
|
||||
"label": "표시 이름 접두사",
|
||||
"placeholder": "접두사",
|
||||
"description": "피어 표시 이름에 추가되는 접두사."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
182
frontend/src/lang/translations/pt.json
Normal file
182
frontend/src/lang/translations/pt.json
Normal file
@@ -0,0 +1,182 @@
|
||||
{
|
||||
"languages": {
|
||||
"pt": "Português"
|
||||
},
|
||||
"general": {
|
||||
"pagination": {
|
||||
"size": "Número de Elementos",
|
||||
"all": "Todos (lento)"
|
||||
},
|
||||
"search": {
|
||||
"placeholder": "Pesquisar...",
|
||||
"button": "Pesquisar"
|
||||
},
|
||||
"select-all": "Selecionar tudo",
|
||||
"yes": "Sim",
|
||||
"no": "Não",
|
||||
"cancel": "Cancelar",
|
||||
"close": "Fechar",
|
||||
"save": "Guardar",
|
||||
"delete": "Eliminar"
|
||||
},
|
||||
"login": {
|
||||
"headline": "Por favor, inicie sessão",
|
||||
"username": {
|
||||
"label": "Nome de utilizador",
|
||||
"placeholder": "Introduza o seu nome de utilizador"
|
||||
},
|
||||
"password": {
|
||||
"label": "Palavra-passe",
|
||||
"placeholder": "Introduza a sua palavra-passe"
|
||||
},
|
||||
"button": "Iniciar sessão"
|
||||
},
|
||||
"menu": {
|
||||
"home": "Início",
|
||||
"interfaces": "Interfaces",
|
||||
"users": "Utilizadores",
|
||||
"lang": "Alterar idioma",
|
||||
"profile": "O Meu Perfil",
|
||||
"settings": "Definições",
|
||||
"audit": "Registo de Auditoria",
|
||||
"login": "Iniciar Sessão",
|
||||
"logout": "Terminar Sessão"
|
||||
},
|
||||
"home": {
|
||||
"title": "Início",
|
||||
"card": {
|
||||
"interfaces": "Interfaces",
|
||||
"users": "Utilizadores"
|
||||
}
|
||||
},
|
||||
"interfaces": {
|
||||
"title": "Interfaces",
|
||||
"create": "Criar Interface",
|
||||
"name": "Nome",
|
||||
"address": "Endereço",
|
||||
"listen-port": "Porta de Escuta",
|
||||
"public-key": "Chave Pública",
|
||||
"private-key": "Chave Privada",
|
||||
"actions": "Ações",
|
||||
"delete-dialog": {
|
||||
"title": "Eliminar Interface",
|
||||
"text": "Tem a certeza de que deseja eliminar a interface '{{name}}'?"
|
||||
},
|
||||
"form": {
|
||||
"name": {
|
||||
"label": "Nome",
|
||||
"placeholder": "Introduza um nome exclusivo para a interface"
|
||||
},
|
||||
"address": {
|
||||
"label": "Endereço",
|
||||
"placeholder": "Introduza um endereço válido (ex: 10.0.0.1/24)"
|
||||
},
|
||||
"listen-port": {
|
||||
"label": "Porta de Escuta",
|
||||
"placeholder": "Introduza a porta onde o WireGuard irá escutar (ex: 51820)"
|
||||
},
|
||||
"private-key": {
|
||||
"label": "Chave Privada",
|
||||
"placeholder": "Será gerada automaticamente se não for fornecida"
|
||||
}
|
||||
}
|
||||
},
|
||||
"users": {
|
||||
"title": "Utilizadores",
|
||||
"create": "Criar Utilizador",
|
||||
"name": "Nome",
|
||||
"email": "Email",
|
||||
"enabled": "Ativo",
|
||||
"is-admin": "Administrador",
|
||||
"actions": "Ações",
|
||||
"edit": "Editar",
|
||||
"delete-dialog": {
|
||||
"title": "Eliminar Utilizador",
|
||||
"text": "Tem a certeza de que deseja eliminar o utilizador '{{nome}}'?"
|
||||
},
|
||||
"form": {
|
||||
"name": {
|
||||
"label": "Nome",
|
||||
"placeholder": "Introduza o nome do utilizador"
|
||||
},
|
||||
"email": {
|
||||
"label": "Email",
|
||||
"placeholder": "Introduza o email do utilizador"
|
||||
},
|
||||
"password": {
|
||||
"label": "Palavra-passe",
|
||||
"placeholder": "Deixe em branco para manter a atual"
|
||||
},
|
||||
"is-admin": {
|
||||
"label": "Administrador"
|
||||
},
|
||||
"enabled": {
|
||||
"label": "Ativo"
|
||||
}
|
||||
}
|
||||
},
|
||||
"peers": {
|
||||
"title": "Peers",
|
||||
"create": "Criar Peer",
|
||||
"public-key": "Chave Pública",
|
||||
"preshared-key": "Chave Pré-partilhada",
|
||||
"endpoint": "Endpoint",
|
||||
"allowed-ips": "IPs Permitidos",
|
||||
"latest-handshake": "Último Handshake",
|
||||
"transfer-rx": "Recebido",
|
||||
"transfer-tx": "Enviado",
|
||||
"persistent-keepalive": "Keepalive Persistente",
|
||||
"actions": "Ações",
|
||||
"edit": "Editar",
|
||||
"delete-dialog": {
|
||||
"title": "Eliminar Peer",
|
||||
"text": "Tem a certeza de que deseja eliminar este peer?"
|
||||
},
|
||||
"form": {
|
||||
"public-key": {
|
||||
"label": "Chave Pública",
|
||||
"placeholder": "Introduza a chave pública do peer"
|
||||
},
|
||||
"preshared-key": {
|
||||
"label": "Chave Pré-partilhada",
|
||||
"placeholder": "Opcional: Chave partilhada adicional para maior segurança"
|
||||
},
|
||||
"endpoint": {
|
||||
"label": "Endpoint",
|
||||
"placeholder": "Endereço público do peer (ex: 1.2.3.4:51820)"
|
||||
},
|
||||
"allowed-ips": {
|
||||
"label": "IPs Permitidos",
|
||||
"placeholder": "Lista de IPs (ex: 10.0.0.2/32, 192.168.1.0/24)"
|
||||
},
|
||||
"persistent-keepalive": {
|
||||
"label": "Keepalive Persistente",
|
||||
"placeholder": "Ex: 25 (em segundos)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"title": "Definições",
|
||||
"password": {
|
||||
"label": "Nova Palavra-passe",
|
||||
"placeholder": "Deixe em branco para manter a atual"
|
||||
},
|
||||
"save": "Guardar Alterações"
|
||||
},
|
||||
"audit": {
|
||||
"title": "Registo de Auditoria",
|
||||
"username": "Utilizador",
|
||||
"ip": "Endereço IP",
|
||||
"method": "Método",
|
||||
"path": "Caminho",
|
||||
"status": "Estado",
|
||||
"timestamp": "Data/Hora"
|
||||
},
|
||||
"errors": {
|
||||
"required": "Este campo é obrigatório",
|
||||
"invalid-email": "Endereço de email inválido",
|
||||
"invalid-address": "Endereço inválido",
|
||||
"invalid-endpoint": "Endpoint inválido",
|
||||
"invalid-allowed-ips": "Formato de IPs Permitidos inválido"
|
||||
}
|
||||
}
|
@@ -393,8 +393,14 @@ func (r *WgRepo) getOrCreatePeer(deviceId domain.InterfaceIdentifier, id domain.
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("peer create error for %s: %w", id.ToPublicKey(), err)
|
||||
}
|
||||
|
||||
peer, err = r.getPeer(deviceId, id)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("peer error after create: %w", err)
|
||||
}
|
||||
return peer, nil
|
||||
}
|
||||
|
||||
|
@@ -190,7 +190,7 @@ func (m Manager) CreatePeer(ctx context.Context, peer *domain.Peer) (*domain.Pee
|
||||
return nil, fmt.Errorf("failed to prepare peer for interface %s: %w", peer.InterfaceIdentifier, err)
|
||||
}
|
||||
|
||||
preparedPeer.OverwriteUserEditableFields(peer)
|
||||
preparedPeer.OverwriteUserEditableFields(peer, m.cfg)
|
||||
|
||||
peer = preparedPeer
|
||||
}
|
||||
@@ -278,7 +278,7 @@ func (m Manager) UpdatePeer(ctx context.Context, peer *domain.Peer) (*domain.Pee
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to load existing peer %s: %w", peer.Identifier, err)
|
||||
}
|
||||
originalPeer.OverwriteUserEditableFields(peer)
|
||||
originalPeer.OverwriteUserEditableFields(peer, m.cfg)
|
||||
|
||||
peer = originalPeer
|
||||
}
|
||||
|
@@ -7,6 +7,7 @@ import (
|
||||
"time"
|
||||
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
"github.com/h44z/wg-portal/internal/config"
|
||||
|
||||
"github.com/h44z/wg-portal/internal"
|
||||
)
|
||||
@@ -129,16 +130,18 @@ func (p *Peer) GenerateDisplayName(prefix string) {
|
||||
}
|
||||
|
||||
// OverwriteUserEditableFields overwrites the user editable fields of the peer with the values from the userPeer
|
||||
func (p *Peer) OverwriteUserEditableFields(userPeer *Peer) {
|
||||
func (p *Peer) OverwriteUserEditableFields(userPeer *Peer, cfg *config.Config) {
|
||||
p.DisplayName = userPeer.DisplayName
|
||||
p.Interface.PublicKey = userPeer.Interface.PublicKey
|
||||
p.Interface.PrivateKey = userPeer.Interface.PrivateKey
|
||||
if cfg.Core.EditableKeys {
|
||||
p.Interface.PublicKey = userPeer.Interface.PublicKey
|
||||
p.Interface.PrivateKey = userPeer.Interface.PrivateKey
|
||||
p.PresharedKey = userPeer.PresharedKey
|
||||
}
|
||||
p.Interface.Mtu = userPeer.Interface.Mtu
|
||||
p.PersistentKeepalive = userPeer.PersistentKeepalive
|
||||
p.ExpiresAt = userPeer.ExpiresAt
|
||||
p.Disabled = userPeer.Disabled
|
||||
p.DisabledReason = userPeer.DisabledReason
|
||||
p.PresharedKey = userPeer.PresharedKey
|
||||
}
|
||||
|
||||
type PeerInterfaceConfig struct {
|
||||
|
@@ -5,6 +5,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/h44z/wg-portal/internal/config"
|
||||
)
|
||||
|
||||
func TestPeer_IsDisabled(t *testing.T) {
|
||||
@@ -98,7 +99,7 @@ func TestPeer_OverwriteUserEditableFields(t *testing.T) {
|
||||
DisplayName: "New DisplayName",
|
||||
}
|
||||
|
||||
peer.OverwriteUserEditableFields(userPeer)
|
||||
peer.OverwriteUserEditableFields(userPeer, &config.Config{})
|
||||
assert.Equal(t, "New DisplayName", peer.DisplayName)
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user