Browse Source

Updated GUI and fixed alot of instabilities in front end.


			
			
				pull/2/head
			
			
		
Per-Arne 5 years ago
parent
commit
efa52e9fff
  1. 29
      .gitignore
  2. 28
      Dockerfile
  3. 39
      docker/gunicorn_config.py
  4. 32
      docker/start.py
  5. 4
      wg_dashboard_backend/const.py
  6. 38
      wg_dashboard_backend/main.py
  7. 1
      wg_dashboard_backend/requirements.txt
  8. 4
      wg_dashboard_backend/script/wireguard.py
  9. 1
      wg_dashboard_frontend/angular.json
  10. 31
      wg_dashboard_frontend/package-lock.json
  11. 4
      wg_dashboard_frontend/package.json
  12. 29
      wg_dashboard_frontend/src/app/app-routing.module.ts
  13. 36
      wg_dashboard_frontend/src/app/app.module.ts
  14. 2
      wg_dashboard_frontend/src/app/interfaces/server.ts
  15. 31
      wg_dashboard_frontend/src/app/layout/layout.module.ts
  16. 56
      wg_dashboard_frontend/src/app/layout/layout/layout.component.html
  17. 19
      wg_dashboard_frontend/src/app/layout/layout/layout.component.scss
  18. 25
      wg_dashboard_frontend/src/app/layout/layout/layout.component.spec.ts
  19. 29
      wg_dashboard_frontend/src/app/layout/layout/layout.component.ts
  20. 5
      wg_dashboard_frontend/src/app/layouts/blank-layout/blank-layout.component.html
  21. 11
      wg_dashboard_frontend/src/app/layouts/blank-layout/blank-layout.component.scss
  22. 18
      wg_dashboard_frontend/src/app/layouts/blank-layout/blank-layout.component.ts
  23. 1
      wg_dashboard_frontend/src/app/layouts/blank-layout/index.ts
  24. 89
      wg_dashboard_frontend/src/app/layouts/common-layout/common-layout.component.html
  25. 28
      wg_dashboard_frontend/src/app/layouts/common-layout/common-layout.component.ts
  26. 1
      wg_dashboard_frontend/src/app/layouts/common-layout/index.ts
  27. 1
      wg_dashboard_frontend/src/app/layouts/index.ts
  28. 32
      wg_dashboard_frontend/src/app/layouts/layouts.module.ts
  29. 0
      wg_dashboard_frontend/src/app/page/components/components.component.html
  30. 0
      wg_dashboard_frontend/src/app/page/components/components.component.scss
  31. 3
      wg_dashboard_frontend/src/app/page/components/components.component.ts
  32. 0
      wg_dashboard_frontend/src/app/page/components/components.module.ts
  33. 0
      wg_dashboard_frontend/src/app/page/components/index.ts
  34. 0
      wg_dashboard_frontend/src/app/page/components/modal-confirm/index.ts
  35. 0
      wg_dashboard_frontend/src/app/page/components/modal-confirm/modal-confirm.component.html
  36. 0
      wg_dashboard_frontend/src/app/page/components/modal-confirm/modal-confirm.component.scss
  37. 0
      wg_dashboard_frontend/src/app/page/components/modal-confirm/modal-confirm.component.ts
  38. 104
      wg_dashboard_frontend/src/app/page/dashboard2/add-server/add-server.component.html
  39. 18
      wg_dashboard_frontend/src/app/page/dashboard2/add-server/add-server.component.scss
  40. 85
      wg_dashboard_frontend/src/app/page/dashboard2/add-server/add-server.component.ts
  41. 18
      wg_dashboard_frontend/src/app/page/dashboard2/dashboard2.component.css
  42. 11
      wg_dashboard_frontend/src/app/page/dashboard2/dashboard2.component.html
  43. 40
      wg_dashboard_frontend/src/app/page/dashboard2/dashboard2.component.spec.ts
  44. 44
      wg_dashboard_frontend/src/app/page/dashboard2/dashboard2.component.ts
  45. 49
      wg_dashboard_frontend/src/app/page/dashboard2/dashboard2.module.ts
  46. 76
      wg_dashboard_frontend/src/app/page/dashboard2/peer/peer.component.html
  47. 17
      wg_dashboard_frontend/src/app/page/dashboard2/peer/peer.component.scss
  48. 84
      wg_dashboard_frontend/src/app/page/dashboard2/peer/peer.component.ts
  49. 248
      wg_dashboard_frontend/src/app/page/dashboard2/server/server.component.html
  50. 28
      wg_dashboard_frontend/src/app/page/dashboard2/server/server.component.scss
  51. 77
      wg_dashboard_frontend/src/app/page/dashboard2/server/server.component.ts
  52. 1
      wg_dashboard_frontend/src/app/page/error/error.component.css
  53. 9
      wg_dashboard_frontend/src/app/page/error/error.component.html
  54. 8
      wg_dashboard_frontend/src/app/page/error/error.component.ts
  55. 0
      wg_dashboard_frontend/src/app/page/error/index.ts
  56. 32
      wg_dashboard_frontend/src/app/page/page-routing.module.ts
  57. 24
      wg_dashboard_frontend/src/app/page/page.module.ts
  58. 48
      wg_dashboard_frontend/src/app/page/user/edit/edit.component.html
  59. 0
      wg_dashboard_frontend/src/app/page/user/edit/edit.component.scss
  60. 51
      wg_dashboard_frontend/src/app/page/user/edit/edit.component.ts
  61. 40
      wg_dashboard_frontend/src/app/page/user/login/login.component.html
  62. 0
      wg_dashboard_frontend/src/app/page/user/login/login.component.scss
  63. 25
      wg_dashboard_frontend/src/app/page/user/login/login.component.spec.ts
  64. 53
      wg_dashboard_frontend/src/app/page/user/login/login.component.ts
  65. 2
      wg_dashboard_frontend/src/app/pages/dashboard/dashboard.component.scss
  66. 20
      wg_dashboard_frontend/src/app/pages/pages/error/error.component.html
  67. 10
      wg_dashboard_frontend/src/app/pages/pages/error/error.component.ts
  68. 27
      wg_dashboard_frontend/src/app/pages/pages/forgot-password/forgot-password.component.html
  69. 10
      wg_dashboard_frontend/src/app/pages/pages/forgot-password/forgot-password.component.ts
  70. 1
      wg_dashboard_frontend/src/app/pages/pages/forgot-password/index.ts
  71. 1
      wg_dashboard_frontend/src/app/pages/pages/index.ts
  72. 30
      wg_dashboard_frontend/src/app/pages/pages/pages-routing.module.ts
  73. 30
      wg_dashboard_frontend/src/app/pages/pages/pages.module.ts
  74. 1
      wg_dashboard_frontend/src/app/pages/pages/sign-up/index.ts
  75. 73
      wg_dashboard_frontend/src/app/pages/pages/sign-up/sign-up.component.html
  76. 61
      wg_dashboard_frontend/src/app/pages/pages/sign-up/sign-up.component.ts
  77. 2
      wg_dashboard_frontend/src/app/services/auth/auth.guard.ts
  78. 2
      wg_dashboard_frontend/src/app/services/auth/auth.interceptor.ts
  79. 2
      wg_dashboard_frontend/src/app/services/config.service.ts
  80. 4
      wg_dashboard_frontend/src/index.html
  81. 1
      wg_dashboard_frontend/src/main.ts
  82. 6
      wg_dashboard_frontend/src/theme/_helpers.scss
  83. 14
      wg_dashboard_frontend/src/theme/components/card/card-actions.component.ts
  84. 18
      wg_dashboard_frontend/src/theme/components/card/card-body.component.ts
  85. 10
      wg_dashboard_frontend/src/theme/components/card/card-menu.component.ts
  86. 21
      wg_dashboard_frontend/src/theme/components/card/card-title.component.ts
  87. 20
      wg_dashboard_frontend/src/theme/components/card/card.component.scss
  88. 15
      wg_dashboard_frontend/src/theme/components/card/card.component.ts
  89. 29
      wg_dashboard_frontend/src/theme/components/card/card.module.ts
  90. 6
      wg_dashboard_frontend/src/theme/components/card/index.ts
  91. 23
      wg_dashboard_frontend/src/theme/components/checkbox/checkbox.component.ts
  92. 1
      wg_dashboard_frontend/src/theme/components/checkbox/index.ts
  93. 17
      wg_dashboard_frontend/src/theme/components/icon-toggle/icon-toggle.component.ts
  94. 1
      wg_dashboard_frontend/src/theme/components/icon-toggle/index.ts
  95. 1
      wg_dashboard_frontend/src/theme/components/page-top/index.ts
  96. 62
      wg_dashboard_frontend/src/theme/components/page-top/page-top.component.scss
  97. 10
      wg_dashboard_frontend/src/theme/components/page-top/page-top.component.ts
  98. 1
      wg_dashboard_frontend/src/theme/components/pagination/index.ts
  99. 13
      wg_dashboard_frontend/src/theme/components/pagination/pagination.component.html
  100. 36
      wg_dashboard_frontend/src/theme/components/pagination/pagination.component.scss

29
.gitignore

@ -4,9 +4,14 @@
/dist
/tmp
/out-tsc
/venv
venv
# dependencies
/node_modules
**node_modules
**build
**dist
**__pycache__
# IDEs and editors
/.idea
@ -40,3 +45,25 @@ testem.log
# System Files
.DS_Store
Thumbs.db
docker/__pycache__/
wg_dashboard_backend/__pycache__/
wg_dashboard_frontend/yarn.lock
wg_dashboard_backend/config/server/wg0/clients/4.conf
wg_dashboard_backend/config/server/wg0/clients/5.conf
wg_dashboard_backend/config/server/wg0/clients/6.conf
wg_dashboard_backend/config/server/wg0/clients/7.conf
wg_dashboard_backend/config/server/wg0/wg0.conf
wg-dashboard-py.iml
docker/wg0.conf
wg_dashboard_backend/database.db

28
Dockerfile

@ -6,16 +6,28 @@ RUN npm install && npm install -g @angular/cli
RUN ng build --configuration="production"
FROM alpine:latest
MAINTAINER per@sysx.no
ENV IS_DOCKER True
WORKDIR /app
# Install dependencies
RUN apk add --no-cache --update wireguard-tools py3-gunicorn python3 py3-pip
COPY wg_dashboard_backend /app
FROM tiangolo/uvicorn-gunicorn-fastapi:python3.7-alpine3.8
ENV IS_DOCKER True
RUN apk add --no-cache build-base libffi-dev
# Install dependencies
RUN apk add --no-cache build-base python3-dev libffi-dev && \
pip3 install uvicorn && \
pip3 install -r requirements.txt && \
apk del build-base python3-dev libffi-dev
# Copy startup scripts
COPY docker/ ./startup
RUN chmod 700 ./startup/start.py
RUN echo "http://dl-cdn.alpinelinux.org/alpine/v3.10/main" >> /etc/apk/repositories
RUN echo "http://dl-cdn.alpinelinux.org/alpine/v3.10/community" >> /etc/apk/repositories
RUN apk update && apk add --no-cache wireguard-tools
COPY ./wg_dashboard_backend /app
RUN pip install -r /app/requirements.txt
# Copy build files from previous step
COPY --from=0 /tmp/build/dist /app/build
ENTRYPOINT python3 startup/start.py

39
docker/gunicorn_config.py

@ -0,0 +1,39 @@
import json
import multiprocessing
import os
workers_per_core_str = os.getenv("WORKERS_PER_CORE", "1")
web_concurrency_str = os.getenv("WEB_CONCURRENCY", None)
host = os.getenv("HOST", "unix:/tmp/gunicorn.sock")
port = os.getenv("PORT", "80")
use_loglevel = os.getenv("LOG_LEVEL", "info")
use_bind = host if "unix" in host else f"{host}:{port}"
cores = multiprocessing.cpu_count()
workers_per_core = float(workers_per_core_str)
default_web_concurrency = workers_per_core * cores
if web_concurrency_str:
web_concurrency = int(web_concurrency_str)
assert web_concurrency > 0
else:
web_concurrency = max(int(default_web_concurrency), 2)
# Gunicorn config variables
loglevel = use_loglevel
workers = web_concurrency
bind = use_bind
keepalive = 120
errorlog = "-"
# For debugging and testing
log_data = {
"loglevel": loglevel,
"workers": workers,
"bind": bind,
"worker-tmp-dir": "/dev/shm",
# Additional, non-gunicorn variables
"workers_per_core": workers_per_core,
"host": host,
"port": port,
}
print(json.dumps(log_data))

32
docker/start.py

@ -0,0 +1,32 @@
import os
from os.path import isdir
DEFAULT_MODULE_LOCATIONS = [("app.main", "/app/app/main.py"), ("main", "/app/main.py")]
DEFAULT_GUNICORN_CONF = [(None, "/app/gunicorn_config.py"), (None, "/app/startup/gunicorn_config.py")]
def get_location(pot):
for i in pot:
if not isdir(i[1]):
continue
# Last record will be "defauilt"
return i[0] if i[0] else i[1]
if __name__ == "__main__":
MODULE_NAME = os.getenv("MODULE_LOCATION", get_location(DEFAULT_MODULE_LOCATIONS))
VARIABLE_NAME = os.getenv("VARIABLE_NAME", "app")
APP_MODULE = os.getenv("APP_MODULE", f"{MODULE_NAME}:{VARIABLE_NAME}")
GUNICORN_CONF = os.getenv("GUNICORN_CONF", get_location(DEFAULT_GUNICORN_CONF))
OPTIONS = [
"-k",
"uvicorn.workers.UvicornWorker",
"-c",
f"{GUNICORN_CONF} {APP_MODULE}"
]
# Set envs
os.putenv("VARIABLE_NAME", VARIABLE_NAME)
os.putenv("APP_MODULE", APP_MODULE)
os.putenv("GUNICORN_CONF", GUNICORN_CONF)
os.system(f"gunicorn -k uvicorn.workers.UvicornWorker -c {GUNICORN_CONF} {APP_MODULE}")

4
wg_dashboard_backend/const.py

@ -5,6 +5,10 @@ IS_DOCKER = os.getenv("IS_DOCKER", "False") == "True"
DATABASE_FILE = "/config/database.db" if IS_DOCKER else "./database.db"
DATABASE_URL = f"sqlite:///{DATABASE_FILE}"
os.makedirs("build", exist_ok=True)
DEFAULT_POST_UP = os.getenv("POST_UP", "iptables -A FORWARD -i %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE")
DEFAULT_POST_DOWN = os.getenv("POST_DOWN", "iptables -D FORWARD -i %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE")
SECRET_KEY = ''.join(random.choices(string.ascii_uppercase + string.digits, k=64))
ALGORITHM = "HS256"

38
wg_dashboard_backend/main.py

@ -39,20 +39,6 @@ engine = sqlalchemy.create_engine(
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/token")
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
if not database_exists(engine.url):
models.Base.metadata.create_all(engine)
# Create default user
_db: Session = SessionLocal()
_db.add(models.User(
username=os.getenv("ADMIN_USERNAME", "admin"),
password=db.user.get_password_hash(os.getenv("ADMIN_PASSWORD", "admin")),
full_name="Admin",
role="admin",
email=""
))
_db.commit()
_db.close()
app = FastAPI()
# Dependency
@ -157,6 +143,9 @@ def add_interface(
detail="Interface, Listen-Port and Address must be included in the schema.")
try:
form_data.post_up = form_data.post_up if form_data.post_up != "" else const.DEFAULT_POST_UP
form_data.post_down = form_data.post_up if form_data.post_up != "" else const.DEFAULT_POST_UP
wg_server = db.wireguard.server_add(sess, form_data)
# Public/Private key
@ -195,7 +184,9 @@ def edit_server(
@app.get("/api/wg/generate_keypair", response_model=schemas.KeyPair)
def generate_key_pair(user: schemas.User = Depends(auth)):
def generate_key_pair(
user: schemas.User = Depends(auth)
):
private_key, public_key = script.wireguard.generate_keys()
return schemas.KeyPair(
private_key=private_key,
@ -373,11 +364,26 @@ def create_user(
), sess)
@app.on_event("startup")
async def startup():
await database.connect()
# TODO - Fix
if not database_exists(engine.url):
models.Base.metadata.create_all(engine)
# Create default user
_db: Session = SessionLocal()
_db.add(models.User(
username=os.getenv("ADMIN_USERNAME", "admin"),
password=db.user.get_password_hash(os.getenv("ADMIN_PASSWORD", "admin")),
full_name="Admin",
role="admin",
email=""
))
_db.commit()
_db.close()
@app.on_event("shutdown")
async def shutdown():

1
wg_dashboard_backend/requirements.txt

@ -9,3 +9,4 @@ passlib
bcrypt
python-multipart
jinja2
sqlalchemy_utils

4
wg_dashboard_backend/script/wireguard.py

@ -114,6 +114,8 @@ def remove_peer(server: schemas.WGServer, peer: schemas.WGPeer):
def get_stats(server: schemas.WGServer):
try:
output = _run_wg(server, ["show", server.interface])
if not output:
return []
regex = r"peer:.*?^\n"
test_str = output.decode("utf-8") + "\n"
@ -155,4 +157,4 @@ def get_stats(server: schemas.WGServer):
return peers
except Exception as e:
_LOGGER.exception(e)
return False
return []

1
wg_dashboard_frontend/angular.json

@ -21,6 +21,7 @@
"src/assets"
],
"styles": [
"./node_modules/@angular/material/prebuilt-themes/indigo-pink.css",
"src/theme/styles.scss",
"node_modules/bootstrap/dist/css/bootstrap.min.css"
],

31
wg_dashboard_frontend/package-lock.json

@ -191,6 +191,22 @@
"resolved": "https://registry.npmjs.org/@angular/animations/-/animations-9.1.0.tgz",
"integrity": "sha512-o7X3HM+eocoryw3VrDUtG6Wci2KwtzyBFo3KBJXjQ16X6fwdkjTG+hLb7pp2CBFBEJW4tPYEy7cSBmEfMRTqag=="
},
"@angular/cdk": {
"version": "9.2.0",
"resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-9.2.0.tgz",
"integrity": "sha512-jeeznvNDpR9POuxzz8Y0zFvMynG9HCJo3ZPTqOjlOq8Lj8876+rLsHDvKEMeLdwlkdi1EweYJW1CLQzI+TwqDA==",
"requires": {
"parse5": "^5.0.0"
},
"dependencies": {
"parse5": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz",
"integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==",
"optional": true
}
}
},
"@angular/cli": {
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/@angular/cli/-/cli-9.1.0.tgz",
@ -451,6 +467,11 @@
"resolved": "https://registry.npmjs.org/@angular/core/-/core-9.1.0.tgz",
"integrity": "sha512-RVlyegdIAij0P1wLY5ObIdsBAzvmHkHfElnmfiNKhaDftP6U/3zRtaKDu0bq0jvn1WCQ8zXxFQ8AWyKZwyFS+w=="
},
"@angular/flex-layout": {
"version": "9.0.0-beta.29",
"resolved": "https://registry.npmjs.org/@angular/flex-layout/-/flex-layout-9.0.0-beta.29.tgz",
"integrity": "sha512-93sxR+kYfYMOdnlWL0Q77FZ428gg8XnBu0YZm6GsCdkw/vLggIT/G1ZAqHlCPIODt6pxmCJ5KXh4ShvniIYDsA=="
},
"@angular/forms": {
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/@angular/forms/-/forms-9.1.0.tgz",
@ -669,6 +690,11 @@
}
}
},
"@angular/material": {
"version": "9.2.0",
"resolved": "https://registry.npmjs.org/@angular/material/-/material-9.2.0.tgz",
"integrity": "sha512-KKzEIVh6/m56m+Ao8p4PK0SyEr0574l3VP2swj1qPag3u+FYgemmXCGTaChrKdDsez+zeTCPXImBGXzE6NQ80Q=="
},
"@angular/platform-browser": {
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-9.1.0.tgz",
@ -6134,6 +6160,11 @@
"integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==",
"dev": true
},
"hammerjs": {
"version": "2.0.8",
"resolved": "https://registry.npmjs.org/hammerjs/-/hammerjs-2.0.8.tgz",
"integrity": "sha1-BO93hiz/K7edMPdpIJWTAiK/YPE="
},
"handle-thing": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz",

4
wg_dashboard_frontend/package.json

@ -29,11 +29,14 @@
"homepage": "https://github.com/CreativeIT/material-angular-dashboard",
"dependencies": {
"@angular/animations": "9.1.0",
"@angular/cdk": "^9.2.0",
"@angular/common": "9.1.0",
"@angular/compiler": "9.1.0",
"@angular/core": "9.1.0",
"@angular/flex-layout": "^9.0.0-beta.29",
"@angular/forms": "9.1.0",
"@angular/localize": "9.1.0",
"@angular/material": "^9.2.0",
"@angular/platform-browser": "9.1.0",
"@angular/platform-browser-dynamic": "9.1.0",
"@angular/router": "9.1.0",
@ -44,6 +47,7 @@
"core-js": "3.6.4",
"d3": "5.15.1",
"goog-webfont-dl": "^1.0.3",
"hammerjs": "^2.0.8",
"ip-cidr": "^2.0.10",
"material-design-lite": "1.3.0",
"material-icons": "^0.3.1",

29
wg_dashboard_frontend/src/app/app-routing.module.ts

@ -1,13 +1,8 @@
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import {LayoutModule} from "./layout/layout.module";
import { LayoutsModule } from './layouts';
import { CommonLayoutComponent } from './layouts/common-layout';
import { DashboardComponent } from './pages/dashboard';
import {AuthGuard} from "@services/*";
import {EditComponent} from "./pages/user/edit/edit.component";
import {LoginComponent} from "./pages/user/login/login.component";
import {ErrorComponent} from "./page/error";
@ -16,28 +11,30 @@ import {LoginComponent} from "./pages/user/login/login.component";
RouterModule.forRoot(
[
{ path: '', redirectTo: 'app/dashboard', pathMatch: 'full' },
{ path: 'app', component: CommonLayoutComponent, children:
[
{ path: 'dashboard', component: DashboardComponent, pathMatch: 'full', canActivate: [AuthGuard]},
{ path: 'page', loadChildren: () => import('./page/page.module').then(m => m.PageModule) },
/*{ path: 'app', component: LayoutComponent, children:
[
//{ path: 'dashboard', component: DashboardComponent, pathMatch: 'full', canActivate: [AuthGuard]},
{ path: 'dashboard2', component: Dashboard2Component, pathMatch: 'full'},
{ path: '**', redirectTo: '/pages/404'},
]
},
},*/
{ path: 'user', component: CommonLayoutComponent, children:
/*{ path: 'user', component: LayoutComponent, children:
[
{ path: 'login', component: LoginComponent, pathMatch: 'full'},
{ path: 'edit', component: EditComponent, pathMatch: 'full', canActivate: [AuthGuard]},
]
},
},*/
{ path: '**', redirectTo: '/page/404'},
{ path: '**', redirectTo: '/pages/404' },
],
{ useHash: true },
),
LayoutsModule,
LayoutModule,
],
exports: [RouterModule],
})

36
wg_dashboard_frontend/src/app/app.module.ts

@ -1,29 +1,45 @@
import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http';
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AuthInterceptor, AuthService } from '@services/*';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { ComponentsModule } from './pages/components';
import { DashboardModule } from './pages/dashboard';
import { VarDirective } from './directives/var.directive';
import { QRCodeModule } from 'angularx-qrcode';
import {NgbModule} from "@ng-bootstrap/ng-bootstrap";
import {UserModule} from "./pages/user/user.module";
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { MatGridListModule } from '@angular/material/grid-list';
import { MatCardModule } from '@angular/material/card';
import { MatMenuModule } from '@angular/material/menu';
import { MatIconModule } from '@angular/material/icon';
import { MatButtonModule } from '@angular/material/button';
import { MatToolbarModule } from '@angular/material/toolbar';
import { MatSidenavModule } from '@angular/material/sidenav';
import { MatListModule } from '@angular/material/list';
import {FlexLayoutModule} from "@angular/flex-layout";
@NgModule({
declarations: [AppComponent, VarDirective],
declarations: [
AppComponent,
VarDirective,
],
imports: [
BrowserModule,
AppRoutingModule,
ComponentsModule,
DashboardModule,
UserModule,
HttpClientModule,
NgbModule,
QRCodeModule
QRCodeModule,
BrowserAnimationsModule,
MatGridListModule,
MatCardModule,
MatMenuModule,
MatIconModule,
MatButtonModule,
MatToolbarModule,
MatSidenavModule,
MatListModule,
FlexLayoutModule,
],
providers: [
AuthService,

2
wg_dashboard_frontend/src/app/interfaces/server.ts

@ -1,7 +1,7 @@
import {Peer} from "./peer";
export interface Server {
addresss: string;
address: string;
interface: string;
listen_port: string;
endpoint: string;

31
wg_dashboard_frontend/src/app/layout/layout.module.ts

@ -0,0 +1,31 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { LayoutComponent } from './layout/layout.component';
import {MatSidenavModule} from "@angular/material/sidenav";
import {MatToolbarModule} from "@angular/material/toolbar";
import {MatListModule} from "@angular/material/list";
import {MatIconModule} from "@angular/material/icon";
import {MatButtonModule} from "@angular/material/button";
import {FlexLayoutModule} from "@angular/flex-layout";
import {RouterModule} from "@angular/router";
@NgModule({
declarations: [LayoutComponent],
imports: [
CommonModule,
MatSidenavModule,
MatToolbarModule,
MatListModule,
MatIconModule,
MatButtonModule,
FlexLayoutModule,
RouterModule
],
exports: [
]
})
export class LayoutModule { }

56
wg_dashboard_frontend/src/app/layout/layout/layout.component.html

@ -0,0 +1,56 @@
<div style="height: 100vh;">
<mat-toolbar color="primary">
<mat-toolbar-row>
<button mat-icon-button (click)="sidenav.toggle()" fxShow="true" fxHide.gt-sm>
<mat-icon>menu</mat-icon>
</button>
<span>{{config.applicationName}}</span>
<span class="example-spacer"></span>
<div fxShow="true" fxHide.lt-md="true">
<!-- The following menu items will be hidden on both SM and XS screen sizes -->
<a *ngFor="let item of menu" [routerLink]="item.link" mat-button>
<mat-icon>{{item.icon}}</mat-icon>
{{item.text}}
</a>
</div>
</mat-toolbar-row>
</mat-toolbar>
<mat-sidenav-container fxFlexFill>
<mat-sidenav #sidenav>
<mat-nav-list>
<a href="#" mat-list-item>
<mat-icon>notifications</mat-icon>
Notifications
</a>
<a href="#" mat-list-item>
<mat-icon>message</mat-icon>
Messages</a>
<a href="#" mat-list-item>
<mat-icon>account_box</mat-icon>
My Account
</a>
<a href="#" mat-list-item>
<mat-icon>lock</mat-icon>
My Account
</a>
<a (click)="sidenav.toggle()" mat-list-item>
<mat-icon>close</mat-icon> Close
</a>
</mat-nav-list>
</mat-sidenav>
<mat-sidenav-content fxFlexFill>
<router-outlet></router-outlet>
</mat-sidenav-content>
</mat-sidenav-container>
</div>

19
wg_dashboard_frontend/src/app/layout/layout/layout.component.scss

@ -0,0 +1,19 @@
.sidenav-container {
height: 100%;
}
.sidenav {
width: 200px;
}
.sidenav .mat-toolbar {
background: inherit;
}
.mat-toolbar.mat-primary {
position: sticky;
top: 0;
z-index: 1;
}

25
wg_dashboard_frontend/src/app/layout/layout/layout.component.spec.ts

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { LayoutComponent } from './layout.component';
describe('LayoutComponent', () => {
let component: LayoutComponent;
let fixture: ComponentFixture<LayoutComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ LayoutComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(LayoutComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

29
wg_dashboard_frontend/src/app/layout/layout/layout.component.ts

@ -0,0 +1,29 @@
import { Component, OnInit } from '@angular/core';
import {Observable} from "rxjs";
import {BreakpointObserver, Breakpoints} from "@angular/cdk/layout";
import {map, shareReplay} from "rxjs/operators";
import {ConfigService} from "../../services/config.service";
@Component({
selector: 'app-layout',
templateUrl: './layout.component.html',
styleUrls: ['./layout.component.scss']
})
export class LayoutComponent implements OnInit {
isHandset$: Observable<boolean> = this.breakpointObserver.observe(Breakpoints.Handset)
.pipe(
map(result => result.matches),
shareReplay()
);
menu: Array<{link: Array<string>, icon: string, text: string}> = [
{ link: ["/page/dashboard"], icon: "home", text: "Dashboard"}
];
constructor(private breakpointObserver: BreakpointObserver, public config: ConfigService) {}
ngOnInit(): void {
console.log("Layout")
}
}

5
wg_dashboard_frontend/src/app/layouts/blank-layout/blank-layout.component.html

@ -1,5 +0,0 @@
<div class="mdl-layout mdl-js-layout">
<main class="mdl-layout__content">
<router-outlet></router-outlet>
</main>
</div>

11
wg_dashboard_frontend/src/app/layouts/blank-layout/blank-layout.component.scss

@ -1,11 +0,0 @@
@import '~theme/helpers';
app-blank-layout .mdl-layout .mdl-layout__content {
padding: 16px;
display: flex;
}
// FIXME: responsibility leak
.not-found .mdl-layout__content {
background-image: url('#{$image-path}/404.svg');
}

18
wg_dashboard_frontend/src/app/layouts/blank-layout/blank-layout.component.ts

@ -1,18 +0,0 @@
import { Component, HostBinding } from '@angular/core';
import { Router } from '@angular/router';
@Component({
selector: 'app-blank-layout',
styleUrls: ['./blank-layout.component.scss'],
templateUrl: './blank-layout.component.html',
})
export class BlankLayoutComponent {
// FIXME: responsibility leak
@HostBinding('class.not-found') private get notFound() {
return this.router.url === '/pages/404';
}
constructor(
private router: Router,
) { }
}

1
wg_dashboard_frontend/src/app/layouts/blank-layout/index.ts

@ -1 +0,0 @@
export { BlankLayoutComponent } from './blank-layout.component';

89
wg_dashboard_frontend/src/app/layouts/common-layout/common-layout.component.html

@ -1,89 +0,0 @@
<div class="mdl-layout mdl-js-layout mdl-layout--fixed-drawer mdl-layout--fixed-header has-drawer">
<div class="mdl-layout__header">
<base-page-top>
<span class="mdl-layout__title">{{title}}</span>
<nav class="mdl-navigation">
<base-menu-item *ngFor="let item of menu" [data]="item"></base-menu-item>
<div class="mdl-layout-spacer"></div>
</nav>
<div class="mdl-layout-spacer"></div>
<!--
<div class="mdl-textfield mdl-js-textfield mdl-textfield--expandable search">
<label class="mdl-button mdl-js-button mdl-button--icon" for="search">
<i class="material-icons">search</i>
</label>
<div class="mdl-textfield__expandable-holder">
<input class="mdl-textfield__input" type="text" id="search"/>
<label class="mdl-textfield__label" for="search">Enter your query...</label>
</div>
</div>-->
<div class="avatar-dropdown" id="icon" *ngIf="auth.user">
<span>Logged in as {{auth.user?.username}}</span>
<!--<img src="assets/images/Icon_header.png">-->
</div>
<ul
class="mdl-menu mdl-list mdl-menu--bottom-right mdl-js-menu mdl-js-ripple-effect mdl-shadow--2dp account-dropdown"
for="icon">
<li class="mdl-list__item mdl-list__item--two-line">
<span class="mdl-list__item-primary-content">
<span class="material-icons mdl-list__item-avatar"></span>
<span>{{ auth.user?.username }}</span>
<span class="mdl-list__item-sub-title">{{ auth.user?.email }}</span>
</span>
</li>
<li class="list__item--border-top"></li>
<li class="mdl-menu__item mdl-list__item">
<span class="mdl-list__item-primary-content" (click)="router.navigate(['/user/edit'])">
<i class="material-icons mdl-list__item-icon">account_circle</i>
My account
</span>
</li>
<!--<li class="mdl-menu__item mdl-list__item">
<span class="mdl-list__item-primary-content">
<i class="material-icons mdl-list__item-icon">check_box</i>
My tasks
</span>
<span class="mdl-list__item-secondary-content">
<span class="label background-color--primary">3 new</span>
</span>
</li>
<li class="mdl-menu__item mdl-list__item">
<span class="mdl-list__item-primary-content">
<i class="material-icons mdl-list__item-icon">perm_contact_calendar</i>
My events
</span>
</li>-->
<!--<li class="list__item--border-top"></li>
<li class="mdl-menu__item mdl-list__item">
<span class="mdl-list__item-primary-content">
<i class="material-icons mdl-list__item-icon">settings</i>
Settings
</span>
</li>-->
<li class="mdl-menu__item mdl-list__item">
<span class="mdl-list__item-primary-content" (click)="logout()">
<i class="material-icons mdl-list__item-icon text-color--secondary">exit_to_app</i>
Log out
</span>
</li>
</ul>
</base-page-top>
</div>
<!--<div class="mdl-layout__drawer">
<app-sidebar></app-sidebar>
</div>-->
<main class="mdl-layout__content">
<router-outlet></router-outlet>
</main>
</div>

28
wg_dashboard_frontend/src/app/layouts/common-layout/common-layout.component.ts

@ -1,28 +0,0 @@
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { AuthService } from '@services/*';
@Component({
selector: 'app-common-layout',
templateUrl: './common-layout.component.html',
})
export class CommonLayoutComponent implements OnInit {
public title = 'Wireguard Manager';
public menu = [
{ name: 'Dashboard', link: '/app/dashboard', icon: 'dashboard' },
];
constructor(public auth: AuthService,
public router: Router) {}
public ngOnInit() {
}
public logout() {
this.auth.logout()
.subscribe(res => this.router.navigate(['/user/login']));
}
}

1
wg_dashboard_frontend/src/app/layouts/common-layout/index.ts

@ -1 +0,0 @@
export { CommonLayoutComponent } from './common-layout.component';

1
wg_dashboard_frontend/src/app/layouts/index.ts

@ -1 +0,0 @@
export { LayoutsModule } from './layouts.module';

32
wg_dashboard_frontend/src/app/layouts/layouts.module.ts

@ -1,32 +0,0 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { BlankLayoutCardComponent } from 'app/components/blank-layout-card';
import { MessageMenuComponent } from 'app/components/message-menu';
import { NotificationMenuComponent } from 'app/components/notification-menu';
import { SidebarComponent } from 'app/components/sidebar';
import { ThemeModule } from 'theme';
import { BlankLayoutComponent } from './blank-layout';
import { CommonLayoutComponent } from './common-layout';
@NgModule({
imports: [
CommonModule,
ThemeModule,
RouterModule,
],
declarations: [
CommonLayoutComponent,
BlankLayoutComponent,
BlankLayoutCardComponent,
SidebarComponent,
MessageMenuComponent,
NotificationMenuComponent,
],
exports: [
CommonLayoutComponent,
BlankLayoutComponent,
],
})
export class LayoutsModule { }

0
wg_dashboard_frontend/src/app/pages/user/edit/edit.component.scss → wg_dashboard_frontend/src/app/page/components/components.component.html

0
wg_dashboard_frontend/src/app/pages/user/login/login.component.scss → wg_dashboard_frontend/src/app/page/components/components.component.scss

3
wg_dashboard_frontend/src/app/pages/components/components.component.ts → wg_dashboard_frontend/src/app/page/components/components.component.ts

@ -1,13 +1,12 @@
import { Component, HostBinding } from '@angular/core';
import { UpgradableComponent } from 'theme/components/upgradable';
@Component({
selector: 'app-components',
templateUrl: './components.component.html',
styleUrls: ['./components.component.scss'],
})
export class ComponentsComponent extends UpgradableComponent {
export class ComponentsComponent {
@HostBinding('class.mdl-grid') private readonly mdlGrid = true;
@HostBinding('class.ui-components') private readonly uiComponents = true;

0
wg_dashboard_frontend/src/app/pages/components/components.module.ts → wg_dashboard_frontend/src/app/page/components/components.module.ts

0
wg_dashboard_frontend/src/app/pages/components/index.ts → wg_dashboard_frontend/src/app/page/components/index.ts

0
wg_dashboard_frontend/src/app/pages/components/modal-confirm/index.ts → wg_dashboard_frontend/src/app/page/components/modal-confirm/index.ts

0
wg_dashboard_frontend/src/app/pages/components/modal-confirm/modal-confirm.component.html → wg_dashboard_frontend/src/app/page/components/modal-confirm/modal-confirm.component.html

0
wg_dashboard_frontend/src/app/pages/components/modal-confirm/modal-confirm.component.scss → wg_dashboard_frontend/src/app/page/components/modal-confirm/modal-confirm.component.scss

0
wg_dashboard_frontend/src/app/pages/components/modal-confirm/modal-confirm.component.ts → wg_dashboard_frontend/src/app/page/components/modal-confirm/modal-confirm.component.ts

104
wg_dashboard_frontend/src/app/page/dashboard2/add-server/add-server.component.html

@ -0,0 +1,104 @@
<mat-card class="dashboard-card">
<mat-card-content class="dashboard-card-content">
<form [formGroup]="serverForm" (ngSubmit)="serverForm.valid && add(serverForm.value)" class="add-server-form">
<p>Essentials</p>
<table class="add-server-full-width" cellspacing="0"><tr>
<td>
<mat-form-field class="add-server-full-width">
<mat-label>Interface</mat-label>
<input formControlName="interface" matInput placeholder="wg0">
</mat-form-field>
</td>
<td>
<mat-form-field class="add-server-full-width">
<mat-label>Address Scope</mat-label>
<input formControlName="address" matInput placeholder="10.0.200.1/24">
</mat-form-field>
</td>
</tr></table>
<table class="add-server-full-width" cellspacing="0"><tr>
<td>
<mat-form-field class="add-server-full-width">
<mat-label>Endpoint</mat-label>
<input formControlName="endpoint" matInput placeholder="my-address.com">
</mat-form-field>
</td>
<td>
<mat-form-field class="add-server-full-width">
<mat-label>Port</mat-label>
<input formControlName="listen_port" matInput placeholder="51820">
</mat-form-field>
</td>
</tr></table>
<p>Keys</p>
<p>
<mat-form-field class="add-server-full-width">
<mat-label>Private-Key</mat-label>
<input formControlName="private_key" matInput>
</mat-form-field>
</p>
<p>
<mat-form-field class="add-server-full-width">
<mat-label>Public-Key</mat-label>
<input formControlName="public_key" matInput>
</mat-form-field>
</p>
<p>
<mat-form-field class="add-server-full-width">
<mat-label>Shared-Key</mat-label>
<input formControlName="shared_key" matInput>
</mat-form-field>
</p>
<div class="add-server-button-group">
<button type="button" [hidden]="!isEdit" (click)="getKeyPair()" mat-raised-button color="primary" disabled>
<i class="material-icons">vpn_key</i>
Generate KeyPair
</button>
<button type="button" [hidden]="!isEdit" (click)="getPSK()" mat-raised-button color="primary">
<i class="material-icons">share</i>
Generate PSK
</button>
</div>
<p>Scripts</p>
<p>
<mat-form-field class="add-server-full-width">
<mat-label>Post-Up</mat-label>
<input formControlName="post_up" matInput>
</mat-form-field>
</p>
<p>
<mat-form-field class="add-server-full-width">
<mat-label>Post-Down</mat-label>
<input formControlName="post_down" matInput>
</mat-form-field>
</p>
<div class="add-server-button-group">
<button mat-raised-button color="primary" [disabled]="!serverForm.valid" type="submit">
<ng-container *ngIf="!isEdit">Add Server</ng-container>
<ng-container *ngIf="isEdit">Edit Server</ng-container>
</button>
<button mat-raised-button color="warn" (click)="isEdit = false; serverForm.reset()">
Reset
</button>
</div>
</form>
</mat-card-content>
</mat-card>

18
wg_dashboard_frontend/src/app/page/dashboard2/add-server/add-server.component.scss

@ -0,0 +1,18 @@
.add-server-form {
min-width: 150px;
//max-width: 500px;
width: 100%;
}
.add-server-full-width {
width: 100%;
}
td {
padding-right: 8px;
}
.add-server-button-group{
margin-right: 8px;
}

85
wg_dashboard_frontend/src/app/page/dashboard2/add-server/add-server.component.ts

@ -0,0 +1,85 @@
import {Component, Input, OnInit, ViewEncapsulation} from '@angular/core';
import {FormControl, FormGroup, Validators} from "@angular/forms";
import {IPValidator} from "../../../validators/ip-address.validator";
import {NumberValidator} from "../../../validators/number.validator";
import {Server} from "../../../interfaces/server";
import {ServerService} from "../../../services/server.service";
import {DataService} from "../../../services/data.service";
@Component({
selector: 'app-add-server',
templateUrl: './add-server.component.html',
encapsulation: ViewEncapsulation.None,
styleUrls: ['./add-server.component.scss', '../dashboard2.component.css']
})
export class AddServerComponent implements OnInit {
@Input() servers: Array<Server>;
serverForm = new FormGroup({
address: new FormControl('', [IPValidator.isIPAddress]),
interface: new FormControl('', [Validators.required, Validators.minLength(3)]),
listen_port: new FormControl('', [Validators.required, NumberValidator.stringIsNumber]),
endpoint: new FormControl('', Validators.required),
private_key: new FormControl('', [Validators.minLength(44), Validators.maxLength(44)]),
public_key: new FormControl('', [Validators.minLength(44), Validators.maxLength(44)]),
shared_key: new FormControl('', [Validators.minLength(44), Validators.maxLength(44)]),
post_up: new FormControl(''),
post_down: new FormControl(''),
// Unused on backend
is_running: new FormControl(false),
peers: new FormControl([]),
});
isEdit: boolean = false;
editServer: Server = null;
constructor(private serverAPI: ServerService, private comm: DataService) { }
ngOnInit(): void {
this.comm.on("server-edit").subscribe( (data: Server) => {
this.isEdit = true;
this.serverForm.setValue(data);
this.editServer = data;
})
}
add(form: Server) {
if(this.isEdit){
const idx = this.servers.indexOf(this.editServer);
this.serverAPI.editServer(this.editServer, form).subscribe((server: Server) => {
this.servers[idx] = server;
});
} else {
this.serverAPI.addServer(form).subscribe((server: Server) => {
this.servers.push(server);
});
}
this.isEdit = false;
this.serverForm.reset();
this.editServer = null;
}
getKeyPair() {
this.serverAPI.getKeyPair().subscribe((kp: any) => {
this.serverForm.patchValue({
private_key: kp.private_key,
public_key: kp.public_key
})
});
}
getPSK() {
this.serverAPI.getPSK().subscribe((psk: any) => {
this.serverForm.patchValue({
shared_key: psk.psk
})
});
}
}

18
wg_dashboard_frontend/src/app/page/dashboard2/dashboard2.component.css

@ -0,0 +1,18 @@
.grid-container {
margin: 20px;
}
.dashboard-card {
position: absolute;
top: 15px;
left: 15px;
right: 15px;
bottom: 15px;
}
.more-button {
position: absolute;
top: 5px;
right: 10px;
}

11
wg_dashboard_frontend/src/app/page/dashboard2/dashboard2.component.html

@ -0,0 +1,11 @@
<div flex fxFill fxLayout="row" fxLayoutAlign="space-between" >
<div fxFlex="65">
<app-server [(server)]="servers[idx]" [(servers)]="servers" *ngFor="let server of servers; let idx = index"></app-server>
</div>
<div fxFlex="34">
<app-add-server [(servers)]="servers"></app-add-server>
</div>
</div>

40
wg_dashboard_frontend/src/app/page/dashboard2/dashboard2.component.spec.ts

@ -0,0 +1,40 @@
import { LayoutModule } from '@angular/cdk/layout';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card';
import { MatGridListModule } from '@angular/material/grid-list';
import { MatIconModule } from '@angular/material/icon';
import { MatMenuModule } from '@angular/material/menu';
import { Dashboard2Component } from './dashboard2.component';
describe('Dashboard2Component', () => {
let component: Dashboard2Component;
let fixture: ComponentFixture<Dashboard2Component>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [Dashboard2Component],
imports: [
NoopAnimationsModule,
LayoutModule,
MatButtonModule,
MatCardModule,
MatGridListModule,
MatIconModule,
MatMenuModule,
]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(Dashboard2Component);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should compile', () => {
expect(component).toBeTruthy();
});
});

44
wg_dashboard_frontend/src/app/page/dashboard2/dashboard2.component.ts

@ -0,0 +1,44 @@
import {Component, OnInit} from '@angular/core';
import { map } from 'rxjs/operators';
import { Breakpoints, BreakpointObserver } from '@angular/cdk/layout';
import {Server} from "../../interfaces/server";
import {ServerService} from "../../services/server.service";
import {Peer} from "../../interfaces/peer";
@Component({
selector: 'dashboard2',
templateUrl: './dashboard2.component.html',
styleUrls: ['./dashboard2.component.css']
})
export class Dashboard2Component implements OnInit
{
servers: Array<Server> = [];
constructor(private breakpointObserver: BreakpointObserver, private serverAPI: ServerService) {
}
ngOnInit(): void {
this.serverAPI.getServers()
.subscribe( (servers: Array<Server>) => {
this.servers.push(...servers);
servers.forEach((server) => {
this.serverAPI.serverStats(server).subscribe((stats: Peer[]) => {
stats.forEach( item => {
const peer = server.peers.find(x => x.public_key == item.public_key);
peer._stats = item
});
});
});
})
}
}

49
wg_dashboard_frontend/src/app/page/dashboard2/dashboard2.module.ts

@ -0,0 +1,49 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import {Dashboard2Component} from "./dashboard2.component";
import {MatGridListModule} from "@angular/material/grid-list";
import {MatCardModule} from "@angular/material/card";
import {MatMenuModule} from "@angular/material/menu";
import {MatIconModule} from "@angular/material/icon";
import {MatButtonModule} from "@angular/material/button";
import {ServerComponent} from "./server/server.component";
import {MatExpansionModule} from "@angular/material/expansion";
import {AddServerComponent} from "./add-server/add-server.component";
import {MatFormFieldModule} from "@angular/material/form-field";
import {MatInputModule} from "@angular/material/input";
import {FormsModule, ReactiveFormsModule} from "@angular/forms";
import {ComponentsModule} from "../components";
import {FlexModule} from "@angular/flex-layout";
import {MatTableModule} from "@angular/material/table";
import {PeerComponent} from "./peer/peer.component";
import {QRCodeModule} from "angularx-qrcode";
@NgModule({
declarations: [
Dashboard2Component,
ServerComponent,
AddServerComponent,
PeerComponent
],
imports: [
CommonModule,
MatGridListModule,
MatCardModule,
MatMenuModule,
MatIconModule,
MatButtonModule,
MatExpansionModule,
MatFormFieldModule,
MatInputModule,
ReactiveFormsModule,
ComponentsModule,
FlexModule,
MatTableModule,
FormsModule,
QRCodeModule,
]
})
export class Dashboard2Module { }

76
wg_dashboard_frontend/src/app/page/dashboard2/peer/peer.component.html

@ -0,0 +1,76 @@
<div flex fxLayout="row" fxLayoutAlign="space-between">
<div fxFlex="50">
<form #peerForm="ngForm" class="peer-edit-form" (ngSubmit)="peerForm.valid && edit()" >
<b>Essentials</b>
<table class="full-width" cellspacing="0"><tr>
<td>
<mat-form-field class="full-width">
<mat-label>Name</mat-label>
<input [disabled]="!peer._edit" name="name" matInput [(ngModel)]="peer.name">
</mat-form-field>
</td>
<td>
<mat-form-field class="full-width">
<mat-label>Address</mat-label>
<input [disabled]="!peer._edit" name="address" matInput [(ngModel)]="peer.address">
</mat-form-field>
</td>
</tr></table>
<p>
<mat-form-field class="full-width">
<mat-label>DNS</mat-label>
<input [disabled]="!peer._edit" name="dns" [(ngModel)]="peer.dns" matInput>
</mat-form-field>
</p>
<p>
<mat-form-field class="full-width">
<mat-label>Allowed IPs</mat-label>
<input [disabled]="!peer._edit" name="allowed_ips" [(ngModel)]="peer.allowed_ips" matInput>
</mat-form-field>
</p>
<p>Keys</p>
<p>
<mat-form-field class="full-width">
<mat-label>Private-Key</mat-label>
<input [disabled]="!peer._edit" name="private_key" [(ngModel)]="peer.private_key" matInput>
</mat-form-field>
</p>
<p>
<mat-form-field class="full-width">
<mat-label>Public-Key</mat-label>
<input [disabled]="!peer._edit" name="public_key" [(ngModel)]="peer.public_key" matInput>
</mat-form-field>
</p>
<button
[hidden]="!peer._edit"
[disabled]="!peerForm.valid"
type="submit"
mat-raised-button color="primary">
Submit changes
</button>
</form>
</div>
<div fxFlex="33">
<textarea readonly class="mdl-textfield--full-width" style="min-height: 250px; height: 100%; background-color: #202020; color: #00bcd4;">{{config}}</textarea>
</div>
<div fxFlex="33">
<qrcode [qrdata]="config" [width]="256" [errorCorrectionLevel]="'M'"></qrcode>
</div>
</div>

17
wg_dashboard_frontend/src/app/page/dashboard2/peer/peer.component.scss

@ -0,0 +1,17 @@
.peer-edit-form {
min-width: 150px;
//max-width: 500px;
width: 100%;
text-align: left;
}
.full-width {
width: 100%;
}
td {
padding-left: 0 !important;
padding-right: 8px;
text-align: left !important;
}

84
wg_dashboard_frontend/src/app/page/dashboard2/peer/peer.component.ts

@ -0,0 +1,84 @@
import {Component, EventEmitter, Input, OnInit, ViewEncapsulation} from '@angular/core';
import {ServerService} from "../../../services/server.service";
import {Peer} from "../../../interfaces/peer";
import {Server} from "../../../interfaces/server";
import {FormControl, FormGroup} from "@angular/forms";
@Component({
selector: 'app-peer',
templateUrl: './peer.component.html',
encapsulation: ViewEncapsulation.None,
styleUrls: ['./peer.component.scss'],
})
export class PeerComponent implements OnInit {
@Input("peer") peer: Peer;
@Input("server") server: Server;
@Input("selectedPeer") selectedPeer: Peer;
@Input("onEvent") editPeerEmitter: EventEmitter<any> = new EventEmitter<any>();
config: string = "Loading...";
constructor(public serverAPI: ServerService) { }
ngOnInit(): void {
this.editPeerEmitter.subscribe( (msg) => {
if(msg.peer !== this.peer){
return;
}
if(msg.type === "edit"){
this.edit();
}else if(msg.type == "delete"){
this.delete();
}else if(msg.type == "open"){
this.fetchConfig();
}
})
}
edit(){
if(this.peer._edit) {
// Submit the edit (True -> False)
const idx = this.server.peers.indexOf(this.peer);
this.serverAPI.editPeer(this.peer).subscribe((newPeer) => {
Object.keys(newPeer).forEach(k => {
this.server.peers[idx][k] = newPeer[k];
});
});
} else if(!this.peer._edit) {
this.peer._expand = true;
// Open for edit. aka do nothing (False -> True
}
this.peer._edit = !this.peer._edit;
}
delete(){
const idx = this.server.peers.indexOf(this.peer);
this.serverAPI.deletePeer(this.peer).subscribe((apiServer) => {
this.server.peers.splice(idx, 1);
})
}
fetchConfig() {
this.serverAPI.peerConfig(this.peer).subscribe((config: any) => {
this.config = config.config
})
}
pInt(string: string) {
return parseInt(string)
}
}

248
wg_dashboard_frontend/src/app/page/dashboard2/server/server.component.html

@ -0,0 +1,248 @@
<mat-card class="dashboard-card">
<mat-card-header class="server-card-header">
<mat-card-title>
<span>{{server.interface}}</span>
<!-- This fills the remaining space of the current row -->
<span class="fill-remaining-space"></span>
<i class="material-icons" [ngClass]="{'text-success': server.is_running, 'text-danger': !server.is_running}">check_circle</i>
<app-modal-confirm
[qrCode]="true"
[noConfirm]="false"
area="true"
icon="settings"
title="Configuration"
[text]="serverConfig"
hover="Show config for {{server.interface}}">
</app-modal-confirm>
<span>
<app-modal-confirm
[noConfirm]="true"
(onConfirm)="addPeer()"
icon="person_add"
hover="Add peer to {{server.interface}}">
</app-modal-confirm>
<app-modal-confirm
*ngIf="!server.is_running"
[noConfirm]="true"
(onConfirm)="start()"
icon="play_arrow"
hover="Start {{server.interface}}">
</app-modal-confirm>
<app-modal-confirm
*ngIf="server.is_running"
[noConfirm]="false"
(onConfirm)="stop()"
title="Stop server {{server.interface}}?"
text="Are you sure you want to stop this server? This may cause you or your clients to lose connection to the server."
icon="stop"
hover="Stop {{server.interface}}">
</app-modal-confirm>
<app-modal-confirm
[noConfirm]="false"
(onConfirm)="restart()"
title="Restart server {{server.interface}}?"
text="Are you sure you want to restart this server? This may cause you or your clients to lose connection to the server."
icon="autorenew"
hover="Restart {{server.interface}}">
</app-modal-confirm>
<app-modal-confirm
[noConfirm]="true"
(onConfirm)="edit()"
icon="edit"
hover="Edit {{server.interface}}">
</app-modal-confirm>
<app-modal-confirm
(onConfirm)="delete()"
title="Delete {{server.interface}}"
text="Are you sure you want to delete {{server.interface}}"
icon="delete"
hover="Delete {{server.interface}}">
</app-modal-confirm>
</span>
</mat-card-title>
<mat-card-subtitle>{{server.address}} @ {{server.endpoint}}</mat-card-subtitle>
</mat-card-header>
<mat-card-content class="dashboard-card-content">
<table class="table">
<thead>
<tr>
<th>Name</th>
<th>Address</th>
<th>Public-Key</th>
<th>Total tx/rx</th>
<th>Handshake</th>
<th>Manage</th>
</tr>
</thead>
<tbody>
<ng-container *ngFor="let peer of server.peers; let idx = index;" (click)="selectedPeer = (selectedPeer != peer)? peer : null">
<tr (click)="openPeer(peer)">
<td>{{peer.name}}</td>
<td>{{peer.address}}</td>
<td>{{peer.public_key}}</td>
<td>{{peer._stats?.tx || '0'}}/{{peer._stats?.rx || '0'}}</td>
<td>{{peer._stats?.handshake || 'N/A'}}</td>
<td>
<!-- Edit buttons -->
<app-modal-confirm
[noConfirm]="true"
(onConfirm)="this.editPeerEmitter.emit({type: 'edit', peer: peer})"
icon="edit"
hover="Edit {{peer.name}}">
</app-modal-confirm>
<app-modal-confirm
[noConfirm]="false"
(onConfirm)="this.editPeerEmitter.emit({type: 'delete', peer: peer});"
text="Are you sure you want to delete {{peer.name}} ({{peer.public_key}})?"
title="Delete {{peer.name}}"
icon="delete"
hover="Delete {{peer.name}} ({{peer.public_key}})">
</app-modal-confirm>
</td>
</tr>
<tr [hidden]="peer !== selectedPeer">
<td colspan="6">
<app-peer [onEvent]="this.editPeerEmitter" [(peer)]="server.peers[idx]" [(server)]="server"></app-peer>
</td>
</tr>
</ng-container>
</tbody>
</table>
</mat-card-content>
<mat-card-actions>
</mat-card-actions>
</mat-card>
<!--
<mat-card class="dashboard-card">
<mat-card-content class="dashboard-card-content">
*Server*
<ng-container >
</ng-container>
</mat-card-content>
</mat-card>
-->
<!--
<div class=" mdl-card mdl-shadow--2dp">
<div class="mdl-card__title mdl-card--border">
<h2 class="mdl-card__title-text">{{server.interface}}</h2>
<span style="width:20px;"></span>
</div>
<div class="mdl-card__actions">
<div class="mdl-grid peer-item-header">
<div class="mdl-cell--2-col mdl-cell--12-col-phone">Name</div>
<div class="mdl-cell--2-col mdl-cell--12-col-phone">Address</div>
<div class="mdl-cell--3-col mdl-cell--12-col-phone">Public-Key</div>
<div class="mdl-cell--2-col mdl-cell--12-col-phone">Total tx/rx</div>
<div class="mdl-cell--2-col mdl-cell--12-col-phone">Handshake</div>
<div class="mdl-cell--2-col mdl-cell--12-col-phone">Manage</div>
</div>
<div style="cursor: pointer;" *ngFor="let peer of server.peers; let idx = index;" >
<app-peer [(peer)]="server.peers[idx]" [(server)]="server"></app-peer>
</div>
</div>
<div class="mdl-card__supporting-text">
</div>
<div class="mdl-card__menu">
<app-modal-confirm
[noConfirm]="true"
(onConfirm)="addPeer()"
icon="person_add"
hover="Add peer to {{server.interface}}">
</app-modal-confirm>
<app-modal-confirm
*ngIf="!server.is_running"
[noConfirm]="true"
(onConfirm)="start()"
icon="play_arrow"
hover="Start {{server.interface}}">
</app-modal-confirm>
<app-modal-confirm
*ngIf="server.is_running"
[noConfirm]="false"
(onConfirm)="stop()"
title="Stop server {{server.interface}}?"
text="Are you sure you want to stop this server? This may cause you or your clients to lose connection to the server."
icon="stop"
hover="Stop {{server.interface}}">
</app-modal-confirm>
<app-modal-confirm
[noConfirm]="false"
(onConfirm)="restart()"
title="Restart server {{server.interface}}?"
text="Are you sure you want to restart this server? This may cause you or your clients to lose connection to the server."
icon="autorenew"
hover="Restart {{server.interface}}">
</app-modal-confirm>
<app-modal-confirm
[noConfirm]="true"
(onConfirm)="edit()"
icon="edit"
hover="Edit {{server.interface}}">
</app-modal-confirm>
<app-modal-confirm
(onConfirm)="delete()"
title="Delete {{server.interface}}"
text="Are you sure you want to delete {{server.interface}}"
icon="delete"
hover="Delete {{server.interface}}">
</app-modal-confirm>
</div>
</div>-->

28
wg_dashboard_frontend/src/app/page/dashboard2/server/server.component.scss

@ -0,0 +1,28 @@
table {
width: 100%;
}
tr.example-detail-row {
height: 0 !important;
}
tr.example-element-row:not(.example-expanded-row):hover {
background: whitesmoke;
}
tr.example-element-row:not(.example-expanded-row):active {
background: #efefef;
}
.example-element-row td {
border-bottom-width: 0;
}
.example-element-detail {
overflow: hidden;
display: flex;
}

77
wg_dashboard_frontend/src/app/page/dashboard2/server/server.component.ts

@ -0,0 +1,77 @@
import {Component, EventEmitter, Input, OnInit, ViewEncapsulation} from '@angular/core';
import {Server} from "../../../interfaces/server";
import {ServerService} from "../../../services/server.service";
import {DataService} from "../../../services/data.service";
import {Peer} from "../../../interfaces/peer";
@Component({
selector: 'app-server',
templateUrl: './server.component.html',
encapsulation: ViewEncapsulation.None,
styleUrls: ['./server.component.scss', '../dashboard2.component.css'],
})
export class ServerComponent implements OnInit {
@Input() server: Server;
@Input() servers: Array<Server>;
public editPeerEmitter: EventEmitter<any> = new EventEmitter<any>();
serverConfig: string;
selectedPeer: Peer | null;
constructor(private serverAPI: ServerService, private comm: DataService) { }
ngOnInit(): void {
console.log("Server");
this.serverAPI.serverConfig(this.server).subscribe((x: any) => this.serverConfig = x.config)
}
edit(){
this.comm.emit('server-edit', this.server);
}
stop() {
this.serverAPI.stopServer(this.server).subscribe((apiServer) => {
this.server.is_running = apiServer.is_running
})
}
start() {
this.serverAPI.startServer(this.server).subscribe((apiServer) => {
this.server.is_running = apiServer.is_running
})
}
addPeer() {
this.serverAPI.addPeer(this.server).subscribe((peer) => {
this.server.peers.push(peer)
})
}
restart() {
this.serverAPI.restartServer(this.server).subscribe((apiServer) => {
this.server.is_running = apiServer.is_running
})
}
delete() {
const index = this.servers.indexOf(this.server);
this.serverAPI.deleteServer(this.server).subscribe((apiServer) => {
this.servers.splice(index, 1);
})
}
openPeer(peer: Peer) {
if(this.selectedPeer == peer){
this.selectedPeer = null;
return
}
this.selectedPeer = peer;
this.editPeerEmitter.emit({type: 'open', peer: peer});
}
}

1
wg_dashboard_frontend/src/app/page/error/error.component.css

@ -0,0 +1 @@
*{-webkit-box-sizing:border-box;box-sizing:border-box}body{padding:0;margin:0}#notfound{position:relative;height:100vh}#notfound .notfound{position:absolute;left:50%;top:50%;-webkit-transform:translate(-50%,-50%);-ms-transform:translate(-50%,-50%);transform:translate(-50%,-50%)}.notfound{max-width:520px;width:100%;line-height:1.4;text-align:center}.notfound .notfound-404{position:relative;height:240px}.notfound .notfound-404 h1{font-family:montserrat,sans-serif;position:absolute;left:50%;top:50%;-webkit-transform:translate(-50%,-50%);-ms-transform:translate(-50%,-50%);transform:translate(-50%,-50%);font-size:252px;font-weight:900;margin:0;color:#262626;text-transform:uppercase;letter-spacing:-40px;margin-left:-20px}.notfound .notfound-404 h1>span{text-shadow:-8px 0 0 #fff}.notfound .notfound-404 h3{font-family:cabin,sans-serif;position:relative;font-size:16px;font-weight:700;text-transform:uppercase;color:#262626;margin:0;letter-spacing:3px;padding-left:6px}.notfound h2{font-family:cabin,sans-serif;font-size:20px;font-weight:400;text-transform:uppercase;color:#000;margin-top:0;margin-bottom:25px}@media only screen and (max-width:767px){.notfound .notfound-404{height:200px}.notfound .notfound-404 h1{font-size:200px}}@media only screen and (max-width:480px){.notfound .notfound-404{height:162px}.notfound .notfound-404 h1{font-size:162px;height:150px;line-height:162px}.notfound h2{font-size:16px}}

9
wg_dashboard_frontend/src/app/page/error/error.component.html

@ -0,0 +1,9 @@
<div id="notfound">
<div class="notfound">
<div class="notfound-404">
<h3>Oops! Page not found</h3>
<h1><span>4</span><span>0</span><span>4</span></h1>
</div>
<h2>we are sorry, but the page you requested was not found</h2>
</div>
</div>

8
wg_dashboard_frontend/src/app/page/error/error.component.ts

@ -0,0 +1,8 @@
import { Component, HostBinding } from '@angular/core';
@Component({
selector: 'app-error',
styleUrls: ['error.component.css'],
templateUrl: './error.component.html',
})
export class ErrorComponent { }

0
wg_dashboard_frontend/src/app/pages/pages/error/index.ts → wg_dashboard_frontend/src/app/page/error/index.ts

32
wg_dashboard_frontend/src/app/page/page-routing.module.ts

@ -0,0 +1,32 @@
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import {Dashboard2Component} from "./dashboard2/dashboard2.component";
import {LayoutComponent} from "../layout/layout/layout.component";
import {ErrorComponent} from "./error";
import {LoginComponent} from "./user/login/login.component";
import {AuthGuard} from "@services/*";
const routes: Routes = [
{ path: '', component: LayoutComponent, children:
[
//{ path: 'dashboard', component: DashboardComponent, pathMatch: 'full', canActivate: [AuthGuard]},
{ path: 'dashboard', component: Dashboard2Component, pathMatch: 'full', canActivate: [AuthGuard]},
{ path: '404', component: ErrorComponent, pathMatch: 'full' },
]
},
{ path: 'user', component: LayoutComponent, children:
[
//{ path: 'dashboard', component: DashboardComponent, pathMatch: 'full', canActivate: [AuthGuard]},
{ path: 'login', component: LoginComponent, pathMatch: 'full'},
]
},
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class PageRoutingModule { }

24
wg_dashboard_frontend/src/app/page/page.module.ts

@ -0,0 +1,24 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import {PageRoutingModule} from "./page-routing.module";
import {Dashboard2Module} from "./dashboard2/dashboard2.module";
import {LoginComponent} from "./user/login/login.component";
import {MatCardModule} from "@angular/material/card";
import {FormsModule, ReactiveFormsModule} from "@angular/forms";
import {MatInputModule} from "@angular/material/input";
@NgModule({
declarations: [LoginComponent],
imports: [
CommonModule,
PageRoutingModule,
FormsModule,
Dashboard2Module,
MatCardModule,
ReactiveFormsModule,
MatInputModule,
],
})
export class PageModule { }

48
wg_dashboard_frontend/src/app/page/user/edit/edit.component.html

@ -0,0 +1,48 @@
<div class="container">
<base-card>
<base-card-title>
<h2 class="mdl-card__title-text">Edit User</h2>
</base-card-title>
<base-card-body>
<form [formGroup]="editForm" (ngSubmit)="editForm.valid && edit()" class="form">
<div class="mdl-grid">
<div class="mdl-cell mdl-cell--6-col mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
<input formControlName="full_name" class="mdl-textfield__input" type="text" id="full_name" value=""/>
<label class="mdl-textfield__label" for="full_name">Full Name</label>
</div>
<div class="mdl-cell mdl-cell--6-col mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
<input formControlName="username" class="mdl-textfield__input" type="text" id="username" />
<label class="mdl-textfield__label" for="username">Username</label>
</div>
<div class="mdl-cell mdl-cell--6-col mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
<input formControlName="password" class="mdl-textfield__input" type="text" id="password"/>
<label class="mdl-textfield__label" for="password">Password</label>
</div>
<div class="mdl-cell mdl-cell--6-col mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
<input formControlName="email" class="mdl-textfield__input" type="text" id="email"/>
<label class="mdl-textfield__label" for="email">Email</label>
</div>
</div>
<button [disabled]="!editForm.valid" type="submit" class="mdl-button mdl-js-button mdl-button--raised mdl-js-ripple-effect button--colored-light-blue">
Edit User
</button>
</form>
</base-card-body>
</base-card>
</div>

0
wg_dashboard_frontend/src/app/page/user/edit/edit.component.scss

51
wg_dashboard_frontend/src/app/page/user/edit/edit.component.ts

@ -0,0 +1,51 @@
import { Component, OnInit } from '@angular/core';
import {FormControl, FormGroup, Validators} from "@angular/forms";
import {AuthService} from "@services/*";
import {Router} from "@angular/router";
@Component({
selector: 'app-edit',
templateUrl: './edit.component.html',
styleUrls: ['./edit.component.scss']
})
export class EditComponent implements OnInit {
public editForm: FormGroup = new FormGroup({
full_name: new FormControl(''),
password: new FormControl('', Validators.required),
email: new FormControl('', [
Validators.required,
Validators.pattern('^([a-zA-Z0-9_\\-\\.]+)@([a-zA-Z0-9_\\-\\.]+)\\.([a-zA-Z]{2,5})$'),
Validators.maxLength(20),
]),
username: new FormControl('', [Validators.required, Validators.maxLength(20)]),
});
public user: any;
public error: string;
constructor(private authService: AuthService,
private router: Router) {
}
public ngOnInit() {
this.user = this.authService.user;
this.editForm.setValue({
full_name: this.user.full_name,
password: "",
email: this.user.email,
username: this.user.username
})
}
public edit() {
if (this.editForm.valid) {
this.authService.edit(this.editForm.getRawValue())
.subscribe(res => this.router.navigate(['/app/dashboard']),
error => this.error = error.message);
}
}
}

40
wg_dashboard_frontend/src/app/page/user/login/login.component.html

@ -0,0 +1,40 @@
<mat-card>
<mat-card-title>
Authenticate to Wireguard Management
</mat-card-title>
<mat-card-content>
<form [formGroup]="loginForm" (ngSubmit)="loginForm.valid && login()" class="form">
<p>
<mat-form-field class="full-width">
<mat-label>Username</mat-label>
<input type="text" id="username" formControlName="username" matInput>
</mat-form-field>
</p>
<p>
<mat-form-field class="full-width">
<mat-label>Password</mat-label>
<input type="text" id="password" formControlName="password" matInput>
</mat-form-field>
</p>
<button [disabled]="!loginForm.valid" type="submit" class="mdl-button mdl-js-button mdl-button--raised mdl-js-ripple-effect button--colored-light-blue">
SIGN IN
</button>
</form>
</mat-card-content>
</mat-card>

0
wg_dashboard_frontend/src/app/page/user/login/login.component.scss

25
wg_dashboard_frontend/src/app/page/user/login/login.component.spec.ts

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { LoginComponent } from './login.component';
describe('LoginComponent', () => {
let component: LoginComponent;
let fixture: ComponentFixture<LoginComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ LoginComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(LoginComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

53
wg_dashboard_frontend/src/app/page/user/login/login.component.ts

@ -0,0 +1,53 @@
import { Component, OnInit } from '@angular/core';
import {FormBuilder, FormControl, FormGroup, Validators} from "@angular/forms";
import {AuthService} from "@services/*";
import {Router} from "@angular/router";
@Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.scss']
})
export class LoginComponent implements OnInit {
public loginForm: FormGroup;
public username;
public password;
public error: string;
constructor(private authService: AuthService,
private fb: FormBuilder,
private router: Router) {
this.loginForm = this.fb.group({
password: new FormControl('', Validators.required),
username: new FormControl('', [
Validators.required,
]),
});
this.username = this.loginForm.get('username');
this.password = this.loginForm.get('password');
}
public ngOnInit() {
this.authService.logout();
this.loginForm.valueChanges.subscribe(() => {
this.error = null;
});
}
public login() {
this.error = null;
if (this.loginForm.valid) {
this.authService.login(this.loginForm.getRawValue())
.subscribe(res => this.router.navigate(['/page/dashboard']),
error => this.error = error.message);
}
}
public onInputChange(event) {
event.target.required = true;
}
}

2
wg_dashboard_frontend/src/app/pages/dashboard/dashboard.component.scss

@ -1,2 +0,0 @@

20
wg_dashboard_frontend/src/app/pages/pages/error/error.component.html

@ -1,20 +0,0 @@
<div class="mdl-card mdl-card__blank-layout-card mdl-shadow--2dp">
<div class="mdl-card__supporting-text color--dark-gray">
<div class="mdl-grid">
<div class="mdl-cell mdl-cell--12-col mdl-cell--4-col-phone">
<span class="mdl-card__title-text text-color--smooth-gray">DARKBOARD</span>
</div>
<div class="mdl-cell mdl-cell--12-col mdl-cell--4-col-phone">
<span class="text--huge color-text--light-blue">404</span>
</div>
<div class="mdl-cell mdl-cell--12-col mdl-cell--4-col-phone">
<span class="text--sorry text-color--white">Sorry, but there's nothing here</span>
</div>
<div class="mdl-cell mdl-cell--12-col mdl-cell--4-col-phone">
<a routerLink="/" class="mdl-button mdl-js-button color-text--light-blue pull-right">
Go Back
</a>
</div>
</div>
</div>
</div>

10
wg_dashboard_frontend/src/app/pages/pages/error/error.component.ts

@ -1,10 +0,0 @@
import { Component, HostBinding } from '@angular/core';
import { BlankLayoutCardComponent } from 'app/components/blank-layout-card';
@Component({
selector: 'app-error',
styleUrls: ['../../../components/blank-layout-card/blank-layout-card.component.scss'],
templateUrl: './error.component.html',
})
export class ErrorComponent extends BlankLayoutCardComponent { }

27
wg_dashboard_frontend/src/app/pages/pages/forgot-password/forgot-password.component.html

@ -1,27 +0,0 @@
<div class="mdl-card mdl-card__blank-layout-card mdl-shadow--2dp">
<div class="mdl-card__supporting-text color--dark-gray">
<div class="mdl-grid">
<div class="mdl-cell mdl-cell--12-col mdl-cell--4-col-phone">
<span class="mdl-card__title-text text-color--smooth-gray">DARKBOARD</span>
</div>
<div class="mdl-cell mdl-cell--12-col mdl-cell--4-col-phone">
<span class="blank-layout-card-name text-color--white">Forgot password?</span>
<span class="blank-layout-card-secondary-text text-color--smoke">Enter your email below to recieve your password</span>
</div>
<div class="mdl-cell mdl-cell--12-col mdl-cell--4-col-phone">
<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label full-size">
<input class="mdl-textfield__input" type="text" id="e-mail">
<label class="mdl-textfield__label" for="e-mail">Email</label>
</div>
</div>
<div class="mdl-cell mdl-cell--12-col mdl-cell--4-col-phone submit-cell">
<div class="mdl-layout-spacer"></div>
<a routerLink="/app/dashboard">
<button class="mdl-button mdl-js-button mdl-button--raised color--light-blue">
SEND PASSWORD
</button>
</a>
</div>
</div>
</div>
</div>

10
wg_dashboard_frontend/src/app/pages/pages/forgot-password/forgot-password.component.ts

@ -1,10 +0,0 @@
import { Component, HostBinding } from '@angular/core';
import { BlankLayoutCardComponent } from 'app/components/blank-layout-card';
@Component({
selector: 'app-forgot-password',
styleUrls: ['../../../components/blank-layout-card/blank-layout-card.component.scss'],
templateUrl: './forgot-password.component.html',
})
export class ForgotPasswordComponent extends BlankLayoutCardComponent { }

1
wg_dashboard_frontend/src/app/pages/pages/forgot-password/index.ts

@ -1 +0,0 @@
export { ForgotPasswordComponent } from './forgot-password.component';

1
wg_dashboard_frontend/src/app/pages/pages/index.ts

@ -1 +0,0 @@
export { PagesModule } from './pages.module';

30
wg_dashboard_frontend/src/app/pages/pages/pages-routing.module.ts

@ -1,30 +0,0 @@
import { ModuleWithProviders, NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { LayoutsModule } from 'app/layouts';
import { BlankLayoutComponent } from 'app/layouts/blank-layout';
import { ErrorComponent } from './error';
import { ForgotPasswordComponent } from './forgot-password';
import { LoginComponent } from './login';
import { SignUpComponent } from './sign-up';
@NgModule({
imports: [
RouterModule.forChild([
{
path: '',
component: BlankLayoutComponent,
children: [
{ path: '404', component: ErrorComponent, pathMatch: 'full' },
{ path: 'login', component: LoginComponent, pathMatch: 'full' },
{ path: 'sign-up', component: SignUpComponent, pathMatch: 'full' },
{ path: 'forgot-password', component: ForgotPasswordComponent, pathMatch: 'full' },
{ path: '**', redirectTo: '404' },
],
},
]),
LayoutsModule,
],
exports: [RouterModule],
})
export class PagesRoutingModule { }

30
wg_dashboard_frontend/src/app/pages/pages/pages.module.ts

@ -1,30 +0,0 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { ThemeModule } from 'theme';
import { TooltipModule } from '../../../theme/directives/tooltip/tooltip.module';
import { ErrorComponent } from './error';
import { ForgotPasswordComponent } from './forgot-password';
import { PagesRoutingModule } from './pages-routing.module';
import { SignUpComponent } from './sign-up';
@NgModule({
imports: [
CommonModule,
ThemeModule,
PagesRoutingModule,
FormsModule,
ReactiveFormsModule,
TooltipModule,
],
declarations: [
ErrorComponent,
SignUpComponent,
ForgotPasswordComponent,
],
})
export class PagesModule { }

1
wg_dashboard_frontend/src/app/pages/pages/sign-up/index.ts

@ -1 +0,0 @@
export { SignUpComponent } from './sign-up.component';

73
wg_dashboard_frontend/src/app/pages/pages/sign-up/sign-up.component.html

@ -1,73 +0,0 @@
<div class="mdl-card mdl-card__blank-layout-card mdl-shadow--2dp">
<div class="mdl-card__supporting-text color--dark-gray">
<div class="mdl-grid">
<div class="mdl-cell mdl-cell--12-col mdl-cell--4-col-phone">
<span class="mdl-card__title-text text-color--smooth-gray">DARKBOARD</span>
</div>
<div class="mdl-cell mdl-cell--12-col mdl-cell--4-col-phone">
<span class="blank-layout-card-name text-color--white">Sign up</span>
</div>
<form class="login-form" [formGroup]="signupForm" (submit)="login()" novalidate autocomplete="off">
<div class="mdl-cell mdl-cell--12-col mdl-cell--4-col-phone">
<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label full-size"
[class.is-invalid]="username.invalid && (username.dirty || username.touched)"
[class.is-valid]="username.valid && (username.dirty || username.touched)">
<input formControlName="username"
(change)="onInputChange($event)"
class="mdl-textfield__input" type="text" id="username">
<label class="mdl-textfield__label" for="username">Name</label>
<div *ngIf="username.invalid && (username.dirty || username.touched)">
<span *ngIf="username.errors.required" class="mdl-textfield__error">
Name is required. <span class="color-text--orange"> Please, write any name.</span>
</span>
</div>
</div>
<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label full-size"
[class.is-invalid]="password.invalid && (password.dirty || password.touched)"
[class.is-valid]="password.valid && (password.dirty || password.touched)" id="forRass">
<input formControlName="password"
(change)="onInputChange($event)"
class="mdl-textfield__input" type="password" id="password">
<label class="mdl-textfield__label" for="password">Password</label>
<div *ngIf="password.invalid && (password.dirty || password.touched)">
<span *ngIf="password.errors.required" class="mdl-textfield__error">
Password is required. <span class="color-text--orange"> Please, write any password.</span>
</span>
</div>
</div>
<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label full-size"
[class.is-invalid]="email.invalid && (email.dirty || email.touched)"
[class.is-valid]="email.valid && (email.dirty || email.touched)">
<input formControlName="email"
pattern="{{emailPattern}}"
(change)="onInputChange($event)"
class="mdl-textfield__input" type="text" id="email">
<label class="mdl-textfield__label" for="email">Email</label>
<div *ngIf="email.invalid && (email.dirty || email.touched)">
<span *ngIf="email.errors.required" class="mdl-textfield__error">
Email is required. <span class="color-text--orange"> Please, write any valid email.</span>
</span>
<span *ngIf="email.errors.pattern" class="mdl-textfield__error">
Email is invalid.
</span>
</div>
</div>
<label baseCheckbox color="light-blue" class="checkbox--inline" inline></label>
<span class="blank-layout-card-link">I agree all statements in <a href="#"
class="underlined">terms of service</a></span>
</div>
<div class="mdl-cell mdl-cell--12-col mdl-cell--4-col-phone submit-cell">
<a routerLink="/pages/login" class="blank-layout-card-link">I have already signed up</a>
<div class="mdl-layout-spacer"></div>
<button class="mdl-button mdl-js-button mdl-button--raised color--light-blue"
type="submit" [disabled]="signupForm.invalid">
SIGN UP
</button>
</div>
</form>
</div>
</div>
</div>

61
wg_dashboard_frontend/src/app/pages/pages/sign-up/sign-up.component.ts

@ -1,61 +0,0 @@
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms';
import { Router } from '@angular/router';
import { AuthService } from '@services/*';
import { BlankLayoutCardComponent } from 'app/components/blank-layout-card';
@Component({
selector: 'app-sign-up',
styleUrls: ['../../../components/blank-layout-card/blank-layout-card.component.scss'],
templateUrl: './sign-up.component.html',
})
export class SignUpComponent extends BlankLayoutCardComponent implements OnInit {
public signupForm: FormGroup;
public email;
public password;
public username;
public emailPattern = '^([a-zA-Z0-9_\\-\\.]+)@([a-zA-Z0-9_\\-\\.]+)\\.([a-zA-Z]{2,5})$';
public error: string;
constructor(private authService: AuthService,
private fb: FormBuilder,
private router: Router) {
super();
this.signupForm = this.fb.group({
password: new FormControl('', Validators.required),
email: new FormControl('', [
Validators.required,
Validators.pattern(this.emailPattern),
Validators.maxLength(20),
]),
username: new FormControl('', [Validators.required, Validators.maxLength(10)]),
});
this.email = this.signupForm.get('email');
this.password = this.signupForm.get('password');
this.username = this.signupForm.get('username');
}
public ngOnInit() {
this.authService.logout();
this.signupForm.valueChanges.subscribe(() => {
this.error = null;
});
}
public login() {
this.error = null;
if (this.signupForm.valid) {
this.authService.signup(this.signupForm.getRawValue())
.subscribe(res => this.router.navigate(['/app/dashboard']),
error => this.error = error.message);
}
}
public onInputChange(event) {
event.target.required = true;
}
}

2
wg_dashboard_frontend/src/app/services/auth/auth.guard.ts

@ -16,7 +16,7 @@ export class AuthGuard implements CanActivate {
return true;
}
// Navigate to the login page with extras
this.router.navigate(['/user/login']);
this.router.navigate(['page/user/login']);
return false;
}

2
wg_dashboard_frontend/src/app/services/auth/auth.interceptor.ts

@ -33,7 +33,7 @@ export class AuthInterceptor implements HttpInterceptor {
}
this.auth.clearData();
this.router.navigate(['user/login']);
this.router.navigate(['/page/user/login']);
}
}));
}

2
wg_dashboard_frontend/src/app/services/config.service.ts

@ -7,6 +7,8 @@ import {throwError} from "rxjs";
})
export class ConfigService {
public applicationName = "Wireguard Manager";
constructor() { }
public handleError(error: HttpErrorResponse) {

4
wg_dashboard_frontend/src/index.html

@ -8,8 +8,10 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/png" href="assets/icons/favicon-32x32.png" sizes="32x32">
<link rel="icon" type="image/png" href="assets/icons/favicon-16x16.png" sizes="16x16">
<!--<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">-->
</head>
<body>
<body class="mat-typography">
<app-root></app-root>
</body>
</html>

1
wg_dashboard_frontend/src/main.ts

@ -3,6 +3,7 @@ import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
import 'hammerjs';
if (environment.production) {
enableProdMode();

6
wg_dashboard_frontend/src/theme/_helpers.scss

@ -1,6 +0,0 @@
@import '~material-design-lite/src/functions';
@import '~material-design-lite/src/variables';
@import '~material-design-lite/src/mixins';
@import '~material-design-lite/src/color-definitions';
@import './scss/variables';
@import './scss/mixins';

14
wg_dashboard_frontend/src/theme/components/card/card-actions.component.ts

@ -1,14 +0,0 @@
import { Component, HostBinding, Input, ViewChild, ViewContainerRef } from '@angular/core';
@Component({
selector: 'base-card base-card-actions',
styleUrls: ['./card.component.scss'],
template: `<ng-content></ng-content>`,
})
export class CardActionsComponent {
@HostBinding('class.mdl-card__actions') private readonly mdlCardActions = true;
constructor(
private viewContainerRef: ViewContainerRef,
) { }
}

18
wg_dashboard_frontend/src/theme/components/card/card-body.component.ts

@ -1,18 +0,0 @@
import { Component, HostBinding, Input, ViewChild } from '@angular/core';
@Component({
selector: 'base-card base-card-body',
styleUrls: ['./card.component.scss'],
template: `<ng-content></ng-content>`,
})
export class CardBodyComponent {
@HostBinding('class.mdl-card__supporting-text') private readonly mdlCardSupportingText = true;
@HostBinding('class.mdl-card--expand') private isExpanded = false;
@Input() set expanded(value) {
if (value || value === '') {
this.isExpanded = true;
}
}
}

10
wg_dashboard_frontend/src/theme/components/card/card-menu.component.ts

@ -1,10 +0,0 @@
import { Component, HostBinding, Input, ViewChild, ViewContainerRef } from '@angular/core';
@Component({
selector: 'base-card base-card-menu',
styleUrls: ['./card.component.scss'],
template: `<ng-content></ng-content>`,
})
export class CardMenuComponent {
@HostBinding('class.mdl-card__menu') private readonly mdlCardMenu = true;
}

21
wg_dashboard_frontend/src/theme/components/card/card-title.component.ts

@ -1,21 +0,0 @@
import { Component, HostBinding, Input, ViewChild } from '@angular/core';
@Component({
selector: 'base-card base-card-title',
styleUrls: ['./card.component.scss'],
template: `<ng-content></ng-content>`,
})
export class CardTitleComponent {
@HostBinding('class.mdl-card__title') private readonly mdlCardTitle = true;
@HostBinding('class.mdl-card--border') private readonly mdlCardBorder = true;
@HostBinding('class.mdl-card--expand') private isExpanded = false;
@Input() set expanded(value) {
if (value || value === '') {
this.isExpanded = true;
}
}
}

20
wg_dashboard_frontend/src/theme/components/card/card.component.scss

@ -1,20 +0,0 @@
@import '~theme/helpers';
.mdl-card__title {
background-color: $card-title-background-color;
}
.mdl-card__supporting-text {
line-height: 22px;
width: calc(100% - #{$card-horizontal-padding*2});
overflow: visible;
}
.mdl-card__actions {
padding: 8px 16px;
}
.mdl-card {
height: 100%;
overflow: visible;
}

15
wg_dashboard_frontend/src/theme/components/card/card.component.ts

@ -1,15 +0,0 @@
import { Component, HostBinding, Input, ViewChild, ViewContainerRef } from '@angular/core';
@Component({
selector: 'base-card',
styleUrls: ['./card.component.scss'],
template: `<ng-content></ng-content>`,
})
export class CardComponent {
@HostBinding('class.mdl-card') private readonly mdlCard = true;
@HostBinding('class.mdl-shadow--2dp') private readonly mdlShadow2DP = true;
constructor(
private viewContainerRef: ViewContainerRef,
) { }
}

29
wg_dashboard_frontend/src/theme/components/card/card.module.ts

@ -1,29 +0,0 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { CardActionsComponent } from './card-actions.component';
import { CardBodyComponent } from './card-body.component';
import { CardMenuComponent } from './card-menu.component';
import { CardTitleComponent } from './card-title.component';
import { CardComponent } from './card.component';
@NgModule({
imports: [
CommonModule,
],
declarations: [
CardComponent,
CardTitleComponent,
CardMenuComponent,
CardBodyComponent,
CardActionsComponent,
],
exports: [
CardComponent,
CardTitleComponent,
CardMenuComponent,
CardBodyComponent,
CardActionsComponent,
],
})
export class CardModule { }

6
wg_dashboard_frontend/src/theme/components/card/index.ts

@ -1,6 +0,0 @@
export { CardActionsComponent } from './card-actions.component';
export { CardBodyComponent } from './card-body.component';
export { CardMenuComponent } from './card-menu.component';
export { CardTitleComponent } from './card-title.component';
export { CardComponent } from './card.component';
export { CardModule } from './card.module';

23
wg_dashboard_frontend/src/theme/components/checkbox/checkbox.component.ts

@ -1,23 +0,0 @@
import { Component, HostBinding, Input } from '@angular/core';
import { ToggleComponent } from 'theme/components/toggle/toggle.component';
@Component({
selector: 'label[baseCheckbox]',
styleUrls: ['../toggle/toggle.component.scss'],
template: `
<input type="checkbox" [id]="innerID" class="mdl-checkbox__input" [checked]="isChecked" (change)="isChecked = !isChecked">
<span class="mdl-checkbox__label"><ng-content></ng-content></span>
`,
})
export class CheckboxComponent extends ToggleComponent {
private isInline = false;
@Input() private set inline(value) {
if (value || value === '') {
this.isInline = true;
}
}
@HostBinding('class') private get className() {
return `mdl-checkbox mdl-js-checkbox mdl-js-ripple-effect checkbox--colored-${this.color} ${this.isInline && 'checkbox--inline'}`;
}
}

1
wg_dashboard_frontend/src/theme/components/checkbox/index.ts

@ -1 +0,0 @@
export { CheckboxComponent } from './checkbox.component';

17
wg_dashboard_frontend/src/theme/components/icon-toggle/icon-toggle.component.ts

@ -1,17 +0,0 @@
import { Component, HostBinding, Input } from '@angular/core';
import { ToggleComponent } from 'theme/components/toggle/toggle.component';
@Component({
selector: 'label[baseIconToggle]',
styleUrls: ['../toggle/toggle.component.scss'],
template: `
<input type="checkbox" [id]="innerID" class="mdl-icon-toggle__input" [checked]="isChecked" (change)="isChecked = !isChecked">
<i class="mdl-icon-toggle__label material-icons"><ng-content></ng-content></i>
`,
})
export class IconToggleComponent extends ToggleComponent {
@HostBinding('class') private get className() {
return `mdl-icon-toggle mdl-js-icon-toggle mdl-js-ripple-effect icon-toggle--colored-${this.color}`;
}
}

1
wg_dashboard_frontend/src/theme/components/icon-toggle/index.ts

@ -1 +0,0 @@
export { IconToggleComponent } from './icon-toggle.component';

1
wg_dashboard_frontend/src/theme/components/page-top/index.ts

@ -1 +0,0 @@
export { PageTopComponent } from './page-top.component';

62
wg_dashboard_frontend/src/theme/components/page-top/page-top.component.scss

@ -1,62 +0,0 @@
@import '~theme/helpers';
@media screen and (max-width: $layout-screen-size-threshold) {
.mdl-layout__header {
display: flex !important;
}
}
.account-dropdown {
&.mdl-menu {
width: 310px;
}
.mdl-list__item {
font-size: 1rem;
&:first-child {
font-size: 16px;
padding-top: $list-min-padding/2;
padding-bottom: $list-min-padding/2;
height: $account-dropdown-avatar-size + $list-min-padding;
.mdl-list__item-primary-content {
height: $account-dropdown-avatar-size;
line-height: 28px;
.mdl-list__item-avatar {
height: $account-dropdown-avatar-size;
width: $account-dropdown-avatar-size;
background: url("#{$image-path}/Icon.png");
background-size: cover;
}
.mdl-list__item-sub-title {
font-weight: 300;
}
}
}
&:hover .mdl-list__item-icon {
color: $list-icon-hover-color;
}
}
.list__item--border-top {
margin-top: 8px;
padding-top: 8px;
}
}
.settings-dropdown {
width: $settings_dropdown_width;
.mdl-menu__item,
a {
@include typo-dropdown-menu-li;
}
}
.search {
padding: 18px 0 !important;
}

10
wg_dashboard_frontend/src/theme/components/page-top/page-top.component.ts

@ -1,10 +0,0 @@
import { Component, HostBinding } from '@angular/core';
@Component({
selector: 'base-page-top',
styleUrls: ['./page-top.component.scss'],
template: `<ng-content></ng-content>`,
})
export class PageTopComponent {
@HostBinding('class.mdl-layout__header-row') protected readonly mdlLayoutHeaderRow = true;
}

1
wg_dashboard_frontend/src/theme/components/pagination/index.ts

@ -1 +0,0 @@
export { PaginationComponent } from './pagination.component';

13
wg_dashboard_frontend/src/theme/components/pagination/pagination.component.html

@ -1,13 +0,0 @@
<span (click)="onChangePage(-1)"><i class="material-icons">chevron_left</i></span>
<span> {{ currentPage }} of {{ numPage }} </span>
<span (click)="onChangePage(1)"><i class="material-icons">chevron_right</i></span>
<div class="goto">
<span> Go to </span>
<form (submit)="goToPage($event)">
<input [(ngModel)]="inputNumPage" name="inputPage"
class="mdl-textfield__input"
type="number"
min="1"
[max]="numPage">
</form>
</div>

36
wg_dashboard_frontend/src/theme/components/pagination/pagination.component.scss

@ -1,36 +0,0 @@
@import '~theme/helpers';
.pagination {
font-family: Roboto, Helvetica, sans-serif;
color: $color-smooth-gray;
font-size: 14px;
position: relative;
line-height: 16px;
user-select: none;
.material-icons {
cursor: pointer;
position: relative;
top: 0.5rem;
margin: 0 0.5rem;
}
.goto {
display: inline-block;
margin: 0 1rem 0 2rem;
form {
width: 30px;
display: inline-block;
input {
text-align: center;
font-family: Roboto, Helvetica, sans-serif;
&:focus {
outline: none;
}
}
}
}
}

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save