After Width: | Height: | Size: 119 KiB |
Before Width: | Height: | Size: 190 KiB After Width: | Height: | Size: 94 KiB |
Before Width: | Height: | Size: 205 KiB After Width: | Height: | Size: 104 KiB |
Before Width: | Height: | Size: 260 KiB After Width: | Height: | Size: 74 KiB |
Before Width: | Height: | Size: 192 KiB After Width: | Height: | Size: 187 KiB |
After Width: | Height: | Size: 194 KiB |
After Width: | Height: | Size: 213 KiB |
After Width: | Height: | Size: 220 KiB |
After Width: | Height: | Size: 74 KiB |
@ -0,0 +1,25 @@ |
|||
{ |
|||
"requires": true, |
|||
"lockfileVersion": 1, |
|||
"dependencies": { |
|||
"@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" |
|||
} |
|||
}, |
|||
"@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==" |
|||
}, |
|||
"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 |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,13 @@ |
|||
import sqlalchemy |
|||
from sqlalchemy.ext.declarative import declarative_base |
|||
from sqlalchemy.orm import sessionmaker |
|||
import const |
|||
|
|||
engine = sqlalchemy.create_engine( |
|||
const.DATABASE_URL, connect_args={"check_same_thread": False} |
|||
) |
|||
|
|||
|
|||
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) |
|||
|
|||
Base = declarative_base() |
@ -0,0 +1,81 @@ |
|||
from datetime import timedelta, datetime |
|||
|
|||
import jwt |
|||
from fastapi import Depends, HTTPException |
|||
from fastapi.security import OAuth2PasswordBearer |
|||
from jwt import PyJWTError |
|||
from passlib.context import CryptContext |
|||
from sqlalchemy.orm import Session |
|||
from starlette import status |
|||
from starlette.requests import Request |
|||
from starlette.responses import Response |
|||
|
|||
import const |
|||
import schemas |
|||
from database import SessionLocal |
|||
import db.user |
|||
|
|||
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/login", auto_error=False) |
|||
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") |
|||
|
|||
|
|||
def get_password_hash(password): |
|||
return pwd_context.hash(password) |
|||
|
|||
|
|||
def verify_password(plain_password, hashed_password): |
|||
return pwd_context.verify(plain_password, hashed_password) |
|||
|
|||
|
|||
async def db_session_middleware(request: Request, call_next): |
|||
response = Response("Internal server error (Database error)", status_code=500) |
|||
try: |
|||
request.state.db = SessionLocal() |
|||
response = await call_next(request) |
|||
finally: |
|||
request.state.db.close() |
|||
return response |
|||
|
|||
|
|||
# NON MIDDLEWARE MIDDLEWARISH THING |
|||
|
|||
|
|||
# Dependency |
|||
def get_db(request: Request): |
|||
return request.state.db |
|||
|
|||
|
|||
def create_access_token(*, data: dict, expires_delta: timedelta = None): |
|||
to_encode = data.copy() |
|||
if expires_delta: |
|||
expire = datetime.utcnow() + expires_delta |
|||
else: |
|||
expire = datetime.utcnow() + timedelta(minutes=15) |
|||
to_encode.update({"exp": expire}) |
|||
encoded_jwt = jwt.encode(to_encode, const.SECRET_KEY, algorithm=const.ALGORITHM) |
|||
return encoded_jwt |
|||
|
|||
|
|||
def auth(token: str = Depends(oauth2_scheme), sess: Session = Depends(get_db)): |
|||
|
|||
credentials_exception = HTTPException( |
|||
status_code=status.HTTP_401_UNAUTHORIZED, |
|||
detail="Could not validate credentials", |
|||
headers={"WWW-Authenticate": "Bearer"}, |
|||
) |
|||
|
|||
try: |
|||
payload = jwt.decode(token, const.SECRET_KEY, algorithms=[const.ALGORITHM]) |
|||
username: str = payload.get("sub") |
|||
if username is None: |
|||
raise credentials_exception |
|||
|
|||
except PyJWTError: |
|||
raise credentials_exception |
|||
user = schemas.User.from_orm( |
|||
schemas.UserInDB(username=username, password="").from_db(sess) |
|||
) |
|||
if user is None: |
|||
raise credentials_exception |
|||
return user |
|||
|
@ -0,0 +1,95 @@ |
|||
import ipaddress |
|||
|
|||
from fastapi import APIRouter, Depends, HTTPException |
|||
from sqlalchemy.orm import Session |
|||
|
|||
import const |
|||
import models |
|||
import schemas |
|||
import middleware |
|||
import db.wireguard |
|||
import script.wireguard |
|||
|
|||
router = APIRouter() |
|||
|
|||
|
|||
@router.post("/add", response_model=schemas.WGPeer) |
|||
def add_peer( |
|||
peer_add: schemas.WGPeerAdd, |
|||
sess: Session = Depends(middleware.get_db) |
|||
): |
|||
server = schemas.WGServer(interface=peer_add.server_interface).from_db(sess) |
|||
peer = schemas.WGPeer(server_id=server.id) |
|||
|
|||
address_space = set(ipaddress.ip_network(server.address, strict=False).hosts()) |
|||
occupied_space = set() |
|||
for p in server.peers: |
|||
try: |
|||
occupied_space.add(ipaddress.ip_address(p.address.split("/")[0])) |
|||
except ValueError as e: |
|||
pass # Ignore invalid addresses. These are out of address_space |
|||
|
|||
address_space -= occupied_space |
|||
|
|||
# Select first available address |
|||
peer.address = str(list(sorted(address_space)).pop(0)) + "/32" |
|||
|
|||
# Private public key generation |
|||
keys = script.wireguard.generate_keys() |
|||
peer.private_key = keys["private_key"] |
|||
peer.public_key = keys["public_key"] |
|||
|
|||
# Set 0.0.0.0/0, ::/0 as default allowed ips |
|||
peer.allowed_ips = ', '.join(const.PEER_DEFAULT_ALLOWED_IPS) |
|||
|
|||
# Set unnamed |
|||
peer.name = "Unnamed" |
|||
|
|||
peer.dns = server.endpoint |
|||
|
|||
peer.configuration = script.wireguard.generate_config(dict( |
|||
peer=peer, |
|||
server=server |
|||
)) |
|||
|
|||
peer.sync(sess) |
|||
|
|||
# If server is running. Add peer |
|||
if script.wireguard.is_running(server): |
|||
script.wireguard.add_peer(server, peer) |
|||
|
|||
return peer |
|||
|
|||
|
|||
@router.post("/delete", response_model=schemas.WGPeer) |
|||
def delete_peer( |
|||
peer: schemas.WGPeer, |
|||
sess: Session = Depends(middleware.get_db) |
|||
): |
|||
peer.from_db(sess) # Sync full object |
|||
|
|||
if not db.wireguard.peer_remove(sess, peer): |
|||
raise HTTPException(400, detail="Were not able to delete peer %s (%s)" % (peer.name, peer.public_key)) |
|||
|
|||
server = schemas.WGServer(interface=peer.server_id) |
|||
if script.wireguard.is_running(server): |
|||
script.wireguard.remove_peer(server, peer) |
|||
|
|||
return peer |
|||
|
|||
|
|||
@router.post("/edit", response_model=schemas.WGPeer) |
|||
def edit_peer( |
|||
peer: schemas.WGPeer, |
|||
sess: Session = Depends(middleware.get_db) |
|||
): |
|||
server = schemas.WGServer(interface="")\ |
|||
.from_orm(sess.query(models.WGServer).filter_by(id=peer.server_id).one()) |
|||
|
|||
peer.configuration = script.wireguard.generate_config(dict( |
|||
peer=peer, |
|||
server=server |
|||
)) |
|||
peer.sync(sess) |
|||
|
|||
return peer |
@ -0,0 +1,138 @@ |
|||
import tempfile |
|||
from os.path import exists |
|||
|
|||
from fastapi import APIRouter, Depends, HTTPException |
|||
from sqlalchemy.orm import Session |
|||
from starlette.responses import JSONResponse |
|||
|
|||
import const |
|||
import schemas |
|||
import middleware |
|||
import db.wireguard |
|||
import script.wireguard |
|||
import typing |
|||
|
|||
router = APIRouter() |
|||
|
|||
|
|||
@router.get("/all", response_model=typing.List[schemas.WGServer]) |
|||
def servers_all( |
|||
sess: Session = Depends(middleware.get_db) |
|||
): |
|||
interfaces = db.wireguard.server_get_all(sess) |
|||
for iface in interfaces: |
|||
iface.is_running = script.wireguard.is_running(iface) |
|||
|
|||
return interfaces |
|||
|
|||
|
|||
@router.post("/add", response_model=schemas.WGServer) |
|||
def add_interface( |
|||
server: schemas.WGServerAdd, |
|||
sess: Session = Depends(middleware.get_db) |
|||
): |
|||
server.post_up = server.post_up if server.post_up != "" else const.DEFAULT_POST_UP |
|||
server.post_down = server.post_up if server.post_up != "" else const.DEFAULT_POST_DOWN |
|||
|
|||
# Public/Private key |
|||
try: |
|||
|
|||
if server.filter_query(sess).count() != 0: |
|||
raise HTTPException(status_code=400, detail="The server interface %s already exists in the database" % server.interface) |
|||
|
|||
keys = script.wireguard.generate_keys() |
|||
server.private_key = keys["private_key"] |
|||
server.public_key = keys["public_key"] |
|||
server.configuration = script.wireguard.generate_config(server) |
|||
|
|||
server.sync(sess) |
|||
except ValueError as e: |
|||
raise HTTPException(status_code=400, detail=str(e)) |
|||
|
|||
return server |
|||
|
|||
|
|||
@router.post("/stop", response_model=schemas.WGServer) |
|||
def start_server( |
|||
form_data: schemas.WGServer |
|||
): |
|||
script.wireguard.stop_interface(form_data) |
|||
form_data.is_running = script.wireguard.is_running(form_data) |
|||
return form_data |
|||
|
|||
|
|||
@router.post("/start", response_model=schemas.WGServer) |
|||
def start_server( |
|||
server: schemas.WGServer, |
|||
sess: Session = Depends(middleware.get_db) |
|||
): |
|||
script.wireguard.start_interface(server) |
|||
server.is_running = script.wireguard.is_running(server) |
|||
server.sync(sess) |
|||
return server |
|||
|
|||
|
|||
@router.post("/restart", response_model=schemas.WGServer) |
|||
def restart_server( |
|||
server: schemas.WGServer, |
|||
sess: Session = Depends(middleware.get_db) |
|||
): |
|||
script.wireguard.restart_interface(server) |
|||
server.is_running = script.wireguard.is_running(server) |
|||
server.sync(sess) |
|||
|
|||
return server |
|||
|
|||
|
|||
@router.post("/delete", response_model=schemas.WGServer) |
|||
def delete_server( |
|||
form_data: schemas.WGServer, |
|||
sess: Session = Depends(middleware.get_db) |
|||
): |
|||
# Stop if running |
|||
if script.wireguard.is_running(form_data): |
|||
script.wireguard.stop_interface(form_data) |
|||
|
|||
if not db.wireguard.server_remove(sess, form_data): |
|||
raise HTTPException(400, detail="Were not able to delete %s" % form_data.interface) |
|||
return form_data |
|||
|
|||
|
|||
@router.post("/stats", dependencies=[Depends(middleware.auth)]) |
|||
def stats_server(server: schemas.WGServer): |
|||
stats = script.wireguard.get_stats(server) |
|||
return JSONResponse(content=stats) |
|||
|
|||
|
|||
@router.post("/edit", response_model=schemas.WGServer) |
|||
def edit_server( |
|||
data: dict, sess: Session = Depends(middleware.get_db) |
|||
): |
|||
interface = data["interface"] |
|||
old = schemas.WGServer(interface=interface).from_db(sess) |
|||
|
|||
# Stop if running |
|||
if script.wireguard.is_running(old): |
|||
script.wireguard.stop_interface(old) |
|||
|
|||
# Update server |
|||
server = schemas.WGServer(**data["server"]) |
|||
server.configuration = script.wireguard.generate_config(server) |
|||
server = old.update(sess, new=server) |
|||
|
|||
# Update peers |
|||
for peer_data in server.peers: |
|||
peer = schemas.WGPeer(**peer_data) |
|||
peer.configuration = script.wireguard.generate_config(dict( |
|||
peer=peer, |
|||
server=server |
|||
)) |
|||
peer.sync(sess) |
|||
|
|||
script.wireguard.start_interface(server) |
|||
server.is_running = script.wireguard.is_running(server) |
|||
server.sync(sess) |
|||
server.from_db(sess) |
|||
|
|||
return server |
|||
|
@ -0,0 +1,86 @@ |
|||
from datetime import timedelta |
|||
|
|||
from fastapi import APIRouter, HTTPException, Depends, Form |
|||
from sqlalchemy.orm import Session |
|||
from starlette import status |
|||
|
|||
import const |
|||
import db.user |
|||
import middleware |
|||
import models |
|||
import schemas |
|||
|
|||
router = APIRouter() |
|||
|
|||
|
|||
@router.get("/logout") |
|||
def logout(user: schemas.User = Depends(middleware.auth)): |
|||
return dict(message="ok") |
|||
|
|||
|
|||
@router.post("/user/edit", response_model=schemas.User) |
|||
def edit(form_data: schemas.UserInDB, |
|||
user: schemas.UserInDB = Depends(middleware.auth), |
|||
sess: Session = Depends(middleware.get_db) |
|||
): |
|||
form_data.password = middleware.get_password_hash(form_data.password) |
|||
form_data.sync(sess) |
|||
return form_data |
|||
|
|||
|
|||
@router.post("/login", response_model=schemas.Token) |
|||
def login(*, username: str = Form(...), password: str = Form(...), sess: Session = Depends(middleware.get_db)): |
|||
user: schemas.UserInDB = schemas.UserInDB(username=username, password="").from_db(sess) |
|||
|
|||
# Verify password |
|||
if not user or not middleware.verify_password(password, user.password): |
|||
raise HTTPException( |
|||
status_code=status.HTTP_401_UNAUTHORIZED, |
|||
detail="Incorrect username or password", |
|||
headers={"WWW-Authenticate": "Bearer"}, |
|||
) |
|||
|
|||
# Create token |
|||
access_token_expires = timedelta(minutes=const.ACCESS_TOKEN_EXPIRE_MINUTES) |
|||
access_token = middleware.create_access_token( |
|||
data={"sub": user.username}, expires_delta=access_token_expires |
|||
) |
|||
|
|||
return schemas.Token( |
|||
access_token=access_token, |
|||
token_type="bearer", |
|||
user=schemas.User(**user.dict()) |
|||
) |
|||
|
|||
|
|||
@router.post("/users/create/") |
|||
def create_user( |
|||
form_data: schemas.UserInDB, |
|||
sess: Session = Depends(middleware.get_db), |
|||
user: schemas.User = Depends(middleware.auth) |
|||
): |
|||
user = db.user.get_user_by_name(sess, form_data.username) |
|||
|
|||
# User already exists |
|||
if user: |
|||
if not db.user.authenticate_user(sess, form_data.username, form_data.password): |
|||
raise HTTPException(status_code=401, detail="Incorrect password") |
|||
|
|||
else: |
|||
|
|||
# Create the user |
|||
if not db.user.create_user(sess, models.User( |
|||
username=form_data.username, |
|||
password=form_data.password, |
|||
full_name=form_data.full_name, |
|||
email=form_data.email, |
|||
role=form_data.role, |
|||
)): |
|||
raise HTTPException(status_code=400, detail="Could not create user") |
|||
|
|||
return login_for_access_token(OAuth2PasswordRequestForm( |
|||
username=form_data.username, |
|||
password=form_data.password, |
|||
scope="" |
|||
), sess) |
|||
|
@ -0,0 +1,25 @@ |
|||
from fastapi import APIRouter |
|||
|
|||
import middleware |
|||
import schemas |
|||
import script.wireguard |
|||
|
|||
router = APIRouter() |
|||
|
|||
|
|||
@router.get("/generate_psk", response_model=schemas.PSK) |
|||
def generate_psk(): |
|||
return schemas.PSK( |
|||
psk=script.wireguard.generate_psk() |
|||
) |
|||
|
|||
|
|||
@router.get("/generate_keypair", response_model=schemas.KeyPair) |
|||
def generate_key_pair(): |
|||
keys = script.wireguard.generate_keys() |
|||
private_key = keys["private_key"] |
|||
public_key = keys["public_key"] |
|||
return schemas.KeyPair( |
|||
private_key=private_key, |
|||
public_key=public_key |
|||
) |
@ -1,12 +1,12 @@ |
|||
[Interface] |
|||
Address = {{ data.address.replace("/32", "/24") }} |
|||
PrivateKey = {{ data.private_key }} |
|||
DNS = {{ data.dns }} |
|||
Address = {{ data.peer.address.replace("/32", "/24") }} |
|||
PrivateKey = {{ data.peer.private_key }} |
|||
DNS = {{ data.peer.dns }} |
|||
|
|||
[Peer] |
|||
PublicKey = {{ data.server_ref.public_key }} |
|||
AllowedIPs = {{ data.allowed_ips }} |
|||
Endpoint = {{ data.server_ref.endpoint }}:{{ data.server_ref.listen_port }} |
|||
{% if data.preshared_key %} |
|||
PresharedKey = {{ data.server_ref.preshared_key }} |
|||
PublicKey = {{ data.server.public_key }} |
|||
AllowedIPs = {{ data.peer.allowed_ips }} |
|||
Endpoint = {{ data.server.endpoint }}:{{ data.server.listen_port }} |
|||
{% if data.server.shared_key %} |
|||
PresharedKey = {{ data.server.shared_key }} |
|||
{% endif %} |
|||
|
@ -0,0 +1,80 @@ |
|||
import warnings |
|||
|
|||
import schemas |
|||
from database import SessionLocal |
|||
|
|||
with warnings.catch_warnings(): |
|||
warnings.filterwarnings("ignore",category=DeprecationWarning) |
|||
|
|||
|
|||
from main import app |
|||
from fastapi.testclient import TestClient |
|||
|
|||
|
|||
client = TestClient(app) |
|||
|
|||
sess = SessionLocal() |
|||
|
|||
username = "admin" |
|||
password = "admin" |
|||
token_headers = {} |
|||
|
|||
def test_logout_without_auth(): |
|||
response = client.get("/api/logout") |
|||
assert response.status_code == 401 |
|||
#assert response.json() == dict(message="ok") |
|||
|
|||
|
|||
def test_login_missing_username(): |
|||
response = client.post("/api/login", json=dict( |
|||
password=password |
|||
)) |
|||
assert response.status_code == 422 |
|||
|
|||
|
|||
def test_login_missing_password(): |
|||
|
|||
response = client.post("/api/login", json=dict( |
|||
password=password |
|||
)) |
|||
assert response.status_code == 422 |
|||
|
|||
|
|||
def test_login(): |
|||
|
|||
response = client.post("/api/login", json=dict( |
|||
username=username, |
|||
password=password |
|||
) |
|||
) |
|||
assert response.status_code == 200 # Must have status code 200 |
|||
assert "user" in response.json() |
|||
assert "token_type" in response.json() |
|||
assert "access_token" in response.json() |
|||
token_headers["Authorization"] = response.json()["token_type"] + " " + response.json()["access_token"] |
|||
return response |
|||
|
|||
|
|||
def test_logout_with_auth(): |
|||
response = client.get("/api/logout", headers=token_headers) |
|||
assert response.status_code == 200 |
|||
|
|||
|
|||
def test_user_edit(): |
|||
|
|||
user = schemas.UserInDB( |
|||
username="test", |
|||
password="test", |
|||
full_name="test", |
|||
email="test", |
|||
role="test" |
|||
) |
|||
|
|||
user.sync(sess=sess) |
|||
|
|||
db_user = user.from_db(sess) |
|||
#print(db_user.username) |
|||
|
|||
|
|||
|
|||
|
@ -1,12 +0,0 @@ |
|||
# This file is used by the build system to adjust CSS and JS output to support the specified browsers below. |
|||
# For additional information regarding the format and rule options, please see: |
|||
# https://github.com/browserslist/browserslist#queries |
|||
|
|||
# You can see what browsers were selected by your queries by running: |
|||
# npx browserslist |
|||
|
|||
> 0.5% |
|||
last 2 versions |
|||
Firefox ESR |
|||
not dead |
|||
not IE 9-11 # For IE 9-11 support, remove 'not'. |
@ -1,15 +1,68 @@ |
|||
import { Component } from '@angular/core'; |
|||
import {AuthService} from "@services/*"; |
|||
import {Component, HostBinding} from '@angular/core'; |
|||
import { AuthService } from '@services/*'; |
|||
import {OverlayContainer} from "@angular/cdk/overlay"; |
|||
import {DataService} from "./services/data.service"; |
|||
import {CookieService} from "ngx-cookie-service"; |
|||
|
|||
const THEME_DARKNESS_SUFFIX = `-dark`; |
|||
|
|||
@Component({ |
|||
selector: 'app-root', |
|||
template: `<router-outlet></router-outlet>`, |
|||
}) |
|||
export class AppComponent { |
|||
@HostBinding('class') activeThemeCssClass: string; |
|||
isThemeDark = false; |
|||
activeTheme: string; |
|||
|
|||
constructor( |
|||
private auth: |
|||
AuthService, |
|||
private overlayContainer: OverlayContainer, |
|||
private comm: DataService, |
|||
private cookieService: CookieService |
|||
) { |
|||
auth.init(); |
|||
|
|||
this.comm.on("changeTheme").subscribe( (data: { |
|||
theme: any, |
|||
darkMode: boolean |
|||
}) => { |
|||
this.setActiveTheme(data.theme.theme, /* darkness: */ data.darkMode) |
|||
}); |
|||
|
|||
if(this.cookieService.check("currentTheme")){ |
|||
this.setActiveTheme( |
|||
JSON.parse(this.cookieService.get("currentTheme")).theme, |
|||
(this.cookieService.get("darkMode") === 'true') |
|||
); |
|||
|
|||
} |
|||
|
|||
|
|||
|
|||
|
|||
|
|||
constructor(private auth: AuthService) { |
|||
auth.init() |
|||
} |
|||
|
|||
setActiveTheme(theme: string, darkness: boolean = null) { |
|||
if (darkness === null) |
|||
darkness = this.isThemeDark; |
|||
else if (this.isThemeDark === darkness) { |
|||
if (this.activeTheme === theme) return |
|||
} else |
|||
this.isThemeDark = darkness; |
|||
|
|||
this.activeTheme = theme; |
|||
|
|||
const cssClass = darkness === true ? theme + THEME_DARKNESS_SUFFIX : theme; |
|||
|
|||
const classList = this.overlayContainer.getContainerElement().classList; |
|||
if (classList.contains(this.activeThemeCssClass)) |
|||
classList.replace(this.activeThemeCssClass, cssClass); |
|||
else |
|||
classList.add(cssClass); |
|||
|
|||
this.activeThemeCssClass = cssClass |
|||
} |
|||
} |
|||
|
@ -1,80 +0,0 @@ |
|||
@import '~theme/helpers'; |
|||
|
|||
.blank-layout-card { |
|||
margin: auto; |
|||
|
|||
.mdl-button { |
|||
font-weight: 500; |
|||
} |
|||
|
|||
font-family: Roboto, Helvetica, sans-serif; |
|||
font-size: 1rem; |
|||
|
|||
.mdl-card__blank-layout-card.mdl-card { |
|||
max-width: 450px; |
|||
margin: auto; |
|||
|
|||
.mdl-card__supporting-text { |
|||
min-height: inherit; |
|||
width: 100%; |
|||
padding: 32px 24px; |
|||
box-sizing: border-box; |
|||
|
|||
.mdl-card__title-text { |
|||
font-size: 17px; |
|||
font-weight: bold; |
|||
} |
|||
|
|||
.blank-layout-card-name { |
|||
font-size: 24px; |
|||
display: block; |
|||
padding: 0 0 8px 0; |
|||
} |
|||
|
|||
.blank-layout-card-link { |
|||
padding: 12px 0; |
|||
} |
|||
|
|||
.blank-layout-card-link, |
|||
.blank-layout-card-link * { |
|||
display: inline-block; |
|||
font-size: 1rem; |
|||
font-weight: inherit; |
|||
color: $color-alto; |
|||
} |
|||
|
|||
.underlined { |
|||
display: inline-block; |
|||
border-bottom: 1px solid $color-light-blue; |
|||
} |
|||
|
|||
.checkbox--inline { |
|||
display: inline; |
|||
padding-top: 4px; |
|||
padding-left: 35px; |
|||
} |
|||
|
|||
.submit-cell { |
|||
display: flex; |
|||
} |
|||
|
|||
.text--huge { |
|||
font-size: 120px; |
|||
font-weight: bold; |
|||
display: inline-block; |
|||
padding: 100px 0 40px 0; |
|||
} |
|||
|
|||
.text--sorry { |
|||
font-size: 28px; |
|||
font-weight: 300; |
|||
} |
|||
|
|||
.alignment--bottom-right { |
|||
position: absolute; |
|||
bottom: 39px; |
|||
right: 46px; |
|||
} |
|||
} |
|||
} |
|||
} |
@ -1,10 +0,0 @@ |
|||
import { Component, HostBinding } from '@angular/core'; |
|||
|
|||
import { UpgradableComponent } from 'theme/components/upgradable'; |
|||
|
|||
@Component({ |
|||
template: '', |
|||
}) |
|||
export class BlankLayoutCardComponent extends UpgradableComponent { |
|||
@HostBinding('class.blank-layout-card') protected readonly blankLayoutCard = true; |
|||
} |
@ -1 +0,0 @@ |
|||
export { BlankLayoutCardComponent } from './blank-layout-card.component'; |
@ -1,2 +0,0 @@ |
|||
export { MessageMenuComponent } from './message-menu.component'; |
|||
export { MessageMenuService } from './message-menu.service'; |
@ -1,27 +0,0 @@ |
|||
<div class="material-icons mdl-badge mdl-badge--overlap mdl-button--icon message" id="inbox" [attr.data-badge]="messages.length"> |
|||
mail_outline |
|||
</div> |
|||
<ul |
|||
class="mdl-menu mdl-list mdl-js-menu mdl-js-ripple-effect mdl-menu--bottom-right mdl-shadow--2dp messages-dropdown" |
|||
for="inbox"> |
|||
<li class="mdl-list__item"> |
|||
You have {{ messages.length }} new messages! |
|||
</li> |
|||
<li *ngFor="let item of messages" class="mdl-menu__item mdl-list__item mdl-list__item--two-line list__item--border-top"> |
|||
<span class="mdl-list__item-primary-content"> |
|||
<span class="mdl-list__item-avatar" |
|||
ngClass="background-color--{{ item.color }}"> |
|||
<span class="text">{{ item.icon }}</span> |
|||
</span> |
|||
<span>{{ item.name }}</span> |
|||
<span |
|||
class="mdl-list__item-sub-title">{{ item.type }}</span> |
|||
</span> |
|||
<span class="mdl-list__item-secondary-content"> |
|||
<span class="label label--transparent">{{ item.time }}</span> |
|||
</span> |
|||
</li> |
|||
<li class="mdl-list__item list__item--border-top"> |
|||
<button href="#" class="mdl-button mdl-js-button mdl-js-ripple-effect">SHOW ALL MESSAGES</button> |
|||
</li> |
|||
</ul> |
@ -1,54 +0,0 @@ |
|||
@import '~theme/helpers'; |
|||
|
|||
.message-menu { |
|||
position: relative; |
|||
} |
|||
|
|||
.messages-dropdown { |
|||
&.mdl-menu { |
|||
width: 310px; |
|||
} |
|||
|
|||
.label { |
|||
color: $messages-dropdown-label-text-color; |
|||
} |
|||
|
|||
.mdl-list__item-primary-content { |
|||
font-weight: 400; |
|||
line-height: 18px; |
|||
|
|||
.mdl-list__item-avatar { |
|||
padding: ($list-avatar-size - $list-icon-size); |
|||
text-align: center; |
|||
|
|||
.material-icons { |
|||
vertical-align: top; |
|||
} |
|||
|
|||
.text { |
|||
font-size: 19px; |
|||
vertical-align: middle; |
|||
} |
|||
} |
|||
|
|||
.mdl-list__item-sub-title { |
|||
font-weight: 100; |
|||
font-size: 12px; |
|||
} |
|||
} |
|||
|
|||
&.mdl-list { |
|||
.mdl-list__item { |
|||
@include typo-dropdown-menu-li; |
|||
|
|||
&:first-child { |
|||
color: $dropdown-menu-header-font-color; |
|||
} |
|||
|
|||
&:last-child { |
|||
padding-top: $list-min-padding/2; |
|||
padding-bottom: 0; |
|||
} |
|||
} |
|||
} |
|||
} |
@ -1,19 +0,0 @@ |
|||
import { Component, HostBinding } from '@angular/core'; |
|||
|
|||
import { MessageMenuService } from './message-menu.service'; |
|||
|
|||
@Component({ |
|||
selector: 'app-message-menu', |
|||
styleUrls: ['./message-menu.component.scss'], |
|||
templateUrl: './message-menu.component.html', |
|||
providers: [MessageMenuService], |
|||
}) |
|||
export class MessageMenuComponent { |
|||
@HostBinding('class.message-menu') private readonly messageMenu = true; |
|||
|
|||
public messages: object[]; |
|||
|
|||
constructor(messageMenuService: MessageMenuService) { |
|||
this.messages = messageMenuService.getMessages(); |
|||
} |
|||
} |
@ -1,44 +0,0 @@ |
|||
import { Injectable } from '@angular/core'; |
|||
|
|||
@Injectable() |
|||
export class MessageMenuService { |
|||
public getMessages(): object[] { |
|||
return [ |
|||
{ |
|||
name: 'Alice', |
|||
type: 'Birthday Party', |
|||
time: 'just now', |
|||
icon: 'A', |
|||
color: 'primary', |
|||
}, |
|||
{ |
|||
name: 'Vladimir', |
|||
type: 'Deployment', |
|||
time: 'just now', |
|||
icon: 'V', |
|||
color: 'primary', |
|||
}, |
|||
{ |
|||
name: 'Mike', |
|||
type: 'No theme', |
|||
time: '5 min', |
|||
icon: 'M', |
|||
color: 'baby-blue', |
|||
}, |
|||
{ |
|||
name: 'Darth', |
|||
type: 'Suggestion', |
|||
time: '23 hours', |
|||
icon: 'D', |
|||
color: 'cerulean', |
|||
}, |
|||
{ |
|||
name: 'Don McDuket', |
|||
type: 'NEWS', |
|||
time: '30 Nov', |
|||
icon: 'D', |
|||
color: 'mint', |
|||
}, |
|||
]; |
|||
} |
|||
} |
@ -1,2 +0,0 @@ |
|||
export { NotificationMenuComponent } from './notification-menu.component'; |
|||
export { NotificationMenuService } from './notification-menu.service'; |
@ -1,27 +0,0 @@ |
|||
<div class="material-icons mdl-badge mdl-badge--overlap mdl-button--icon notification" id="notification" |
|||
[attr.data-badge]="notifications.length"> |
|||
notifications_none |
|||
</div> |
|||
<ul |
|||
class="mdl-menu mdl-list mdl-js-menu mdl-js-ripple-effect mdl-menu--bottom-right mdl-shadow--2dp notifications-dropdown" |
|||
for="notification"> |
|||
<li class="mdl-list__item"> |
|||
You have {{ notifications.length }} new notifications! |
|||
</li> |
|||
<li class="mdl-menu__item mdl-list__item list__item--border-top" |
|||
*ngFor="let item of notifications"> |
|||
<span class="mdl-list__item-primary-content"> |
|||
<span class="mdl-list__item-avatar" ngClass="background-color--{{ item.color }}"> |
|||
<i class="material-icons">{{ item.icon }}</i> |
|||
</span> |
|||
<span>{{ item.text }}</span> |
|||
</span> |
|||
<span class="mdl-list__item-secondary-content"> |
|||
<span class="label">{{ item.time }}</span> |
|||
</span> |
|||
</li> |
|||
|
|||
<li class="mdl-list__item list__item--border-top"> |
|||
<button href="#" class="mdl-button mdl-js-button mdl-js-ripple-effect">ALL NOTIFICATIONS</button> |
|||
</li> |
|||
</ul> |
@ -1,31 +0,0 @@ |
|||
@import '~theme/helpers'; |
|||
|
|||
.notification-menu { |
|||
position: relative; |
|||
} |
|||
|
|||
.notifications-dropdown.mdl-list { |
|||
width: 310px; |
|||
|
|||
.mdl-list__item { |
|||
@include typo-dropdown-menu-li; |
|||
|
|||
&:first-child { |
|||
color: $dropdown-menu-header-font-color; |
|||
} |
|||
|
|||
.mdl-list__item-avatar { |
|||
padding: ($list-avatar-size - $list-icon-size); |
|||
text-align: center; |
|||
|
|||
.material-icons { |
|||
vertical-align: top; |
|||
} |
|||
} |
|||
|
|||
&:last-child { |
|||
padding-top: $list-min-padding/2; |
|||
padding-bottom: 0; |
|||
} |
|||
} |
|||
} |
@ -1,19 +0,0 @@ |
|||
import { Component, HostBinding } from '@angular/core'; |
|||
|
|||
import { NotificationMenuService } from './notification-menu.service'; |
|||
|
|||
@Component({ |
|||
selector: 'app-notification-menu', |
|||
styleUrls: ['./notification-menu.component.scss'], |
|||
templateUrl: './notification-menu.component.html', |
|||
providers: [NotificationMenuService], |
|||
}) |
|||
export class NotificationMenuComponent { |
|||
@HostBinding('class.notification-menu') private readonly notificationMenu = true; |
|||
|
|||
public notifications: object[]; |
|||
|
|||
constructor(notificationMenuService: NotificationMenuService) { |
|||
this.notifications = notificationMenuService.getNotifications(); |
|||
} |
|||
} |
@ -1,30 +0,0 @@ |
|||
import { Injectable } from '@angular/core'; |
|||
|
|||
@Injectable() |
|||
export class NotificationMenuService { |
|||
public getNotifications(): object[] { |
|||
return [ |
|||
{ |
|||
text: 'You have 3 new orders.', |
|||
time: 'just now', |
|||
icon: 'plus_one', |
|||
color: 'primary', |
|||
}, { |
|||
text: 'Database error', |
|||
time: '1 min', |
|||
icon: 'error_outline', |
|||
color: 'secondary', |
|||
}, { |
|||
text: 'The Death Star is built!', |
|||
time: '2 hours', |
|||
icon: 'new_releases', |
|||
color: 'primary', |
|||
}, { |
|||
text: 'You have 4 new mails.', |
|||
time: '5 days', |
|||
icon: 'mail_outline', |
|||
color: 'primary', |
|||
}, |
|||
]; |
|||
} |
|||
} |
@ -1 +0,0 @@ |
|||
export { SidebarComponent } from './sidebar.component'; |
@ -1,3 +0,0 @@ |
|||
.mdl-navigation base-menu-item:nth-child(2) i.material-icons { |
|||
transform: rotate(180deg); |
|||
} |
@ -1,15 +0,0 @@ |
|||
import { Component, Input } from '@angular/core'; |
|||
|
|||
import { SidebarComponent as BaseSidebarComponent } from 'theme/components/sidebar'; |
|||
|
|||
@Component({ |
|||
selector: 'app-sidebar', |
|||
styleUrls: ['../../../theme/components/sidebar/sidebar.component.scss', './sidebar.component.scss'], |
|||
templateUrl: '../../../theme/components/sidebar/sidebar.component.html', |
|||
}) |
|||
export class SidebarComponent extends BaseSidebarComponent { |
|||
public title = 'Wireguard'; |
|||
public menu = [ |
|||
{ name: 'Dashboard', link: '/app/dashboard', icon: 'dashboard' }, |
|||
]; |
|||
} |
@ -1,11 +1,11 @@ |
|||
import {Peer} from "./peer"; |
|||
import { Peer } from './peer'; |
|||
|
|||
export interface User { |
|||
full_name: string; |
|||
email: string; |
|||
role: string; |
|||
username: string; |
|||
access_token: string, |
|||
token_type: string, |
|||
access_token: string; |
|||
token_type: string; |
|||
|
|||
} |
|||
|
@ -1,29 +1,78 @@ |
|||
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"; |
|||
import { Observable } from 'rxjs'; |
|||
import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout'; |
|||
import { map, shareReplay } from 'rxjs/operators'; |
|||
import { ConfigService } from '../../services/config.service'; |
|||
import { AuthService } from '@services/*'; |
|||
import {OverlayContainer} from "@angular/cdk/overlay"; |
|||
import {DataService} from "../../services/data.service"; |
|||
import {CookieService} from "ngx-cookie-service"; |
|||
|
|||
@Component({ |
|||
selector: 'app-layout', |
|||
templateUrl: './layout.component.html', |
|||
styleUrls: ['./layout.component.scss'] |
|||
styleUrls: ['./layout.component.scss'], |
|||
}) |
|||
export class LayoutComponent implements OnInit { |
|||
|
|||
isHandset$: Observable<boolean> = this.breakpointObserver.observe(Breakpoints.Handset) |
|||
.pipe( |
|||
map(result => result.matches), |
|||
shareReplay() |
|||
shareReplay(), |
|||
); |
|||
|
|||
menu: Array<{link: Array<string>, icon: string, text: string}> = [ |
|||
{ link: ["/page/dashboard"], icon: "home", text: "Dashboard"} |
|||
menu: {link: string[], icon: string, text: string}[] = [ |
|||
{ link: ['/page/dashboard'], icon: 'home', text: 'Dashboard' }, |
|||
]; |
|||
|
|||
constructor(private breakpointObserver: BreakpointObserver, public config: ConfigService) {} |
|||
themes = [ |
|||
{theme: "indigo-pink", name: "Blue"}, |
|||
{theme: "deeppurple-amber", name: "Purple"}, |
|||
{theme: "pink-bluegrey", name: "Pink"}, |
|||
{theme: "purple-green", name: "Purple-Green"}, |
|||
]; |
|||
currentTheme = null; |
|||
darkMode = false; |
|||
|
|||
constructor( |
|||
private breakpointObserver: BreakpointObserver, |
|||
public config: ConfigService, |
|||
public auth: AuthService, |
|||
private comm: DataService, |
|||
private cookieService: CookieService |
|||
) {} |
|||
ngOnInit(): void { |
|||
console.log("Layout") |
|||
console.log('Layout'); |
|||
|
|||
if(this.cookieService.check("currentTheme")){ |
|||
this.currentTheme = JSON.parse(this.cookieService.get("currentTheme")); |
|||
this.darkMode = (this.cookieService.get("darkMode") === 'true'); |
|||
}else { |
|||
this.currentTheme = { ... this.themes[0]} |
|||
} |
|||
|
|||
} |
|||
|
|||
toggleDarkMode($event){ |
|||
$event.stopPropagation(); |
|||
this.darkMode = !this.darkMode; |
|||
this.cookieService.set("darkMode", String(this.darkMode)); |
|||
this.sendData(); |
|||
} |
|||
|
|||
setCurrentTheme(theme){ |
|||
this.cookieService.set("currentTheme", JSON.stringify(theme)); |
|||
this.currentTheme = theme; |
|||
this.sendData(); |
|||
} |
|||
|
|||
sendData(){ |
|||
const send = { |
|||
theme: this.currentTheme, |
|||
darkMode: this.darkMode |
|||
}; |
|||
|
|||
this.comm.emit('changeTheme', send); |
|||
} |
|||
|
|||
} |
|||
|
@ -1,30 +1,10 @@ |
|||
import { Component, HostBinding } from '@angular/core'; |
|||
|
|||
import { Component } from '@angular/core'; |
|||
|
|||
@Component({ |
|||
selector: 'app-components', |
|||
templateUrl: './components.component.html', |
|||
styleUrls: ['./components.component.scss'], |
|||
template: '', |
|||
styles: [''], |
|||
}) |
|||
export class ComponentsComponent { |
|||
@HostBinding('class.mdl-grid') private readonly mdlGrid = true; |
|||
@HostBinding('class.ui-components') private readonly uiComponents = true; |
|||
|
|||
public data = [ |
|||
{ |
|||
name: 'Nathan Fillion', |
|||
description: 'Malcolm “Mal” Reynolds', |
|||
image: 'nathan-fillion.png', |
|||
}, |
|||
{ |
|||
name: 'Gina Torres', |
|||
description: 'Zoe Alleyne Washburne', |
|||
image: 'gina-torres.png', |
|||
}, |
|||
{ |
|||
name: 'Alan Tudyk', |
|||
description: 'Hoban “Wash” Washburne', |
|||
image: 'tudyk.png', |
|||
}, |
|||
]; |
|||
} |
|||
|
@ -1,36 +1,51 @@ |
|||
<ng-template #content let-modal> |
|||
<div class="modal-header"> |
|||
<h4 class="modal-title" id="modal-basic-title">{{title || "No 'title' defined" }}</h4> |
|||
|
|||
|
|||
<button type="button" class="close" aria-label="Close" (click)="modal.close('cancel')"> |
|||
<span aria-hidden="true">×</span> |
|||
</button> |
|||
<div *ngIf="shown" fxLayout="row" |
|||
fxLayout.xs="column" |
|||
fxFlexFill |
|||
fxLayoutAlign="center center"> |
|||
<mat-card style="position: absolute; z-index: 10; left: 50%; top: 0; width: 50%;"> |
|||
<mat-card-header> |
|||
|
|||
</div> |
|||
<div class="modal-body"> |
|||
<textarea *ngIf="area" readonly class=" |
|||
mdl-textfield--full-width |
|||
mdl-layout__header" style="min-height: 250px; height: 100%;">{{text || "No Text Defined" }}</textarea> |
|||
<div class="form-group" *ngIf="!area">{{text || "No Text Defined" }}</div> |
|||
<div style="text-align: center;" *ngIf="qrCode"> |
|||
<qrcode [qrdata]="text" [width]="256" [errorCorrectionLevel]="'M'"></qrcode> |
|||
</div> |
|||
<mat-card-title class="card-container-left"> |
|||
{{title || "No 'title' defined" }} |
|||
</mat-card-title> |
|||
<mat-card-title class="card-container-right"> |
|||
|
|||
<mat-icon matTooltip="Close" (click)="cancel($event)" |
|||
class="app-material-icon-valign" style="cursor: pointer" |
|||
>close</mat-icon> |
|||
|
|||
|
|||
</mat-card-title> |
|||
|
|||
</mat-card-header> |
|||
|
|||
<mat-card-content> |
|||
|
|||
<textarea *ngIf="area" readonly class="full-width" style="min-height: 250px; height: 100%;">{{text || "No Text Defined" }}</textarea> |
|||
<div class="form-group" *ngIf="!area">{{text || "No Text Defined" }}</div> |
|||
<div style="text-align: center;" *ngIf="qrCode"> |
|||
<hr> |
|||
<qrcode [qrdata]="text" [width]="256" [errorCorrectionLevel]="'M'"></qrcode> |
|||
</div> |
|||
|
|||
</div> |
|||
<div class="modal-footer"> |
|||
<button type="button" class="btn btn-dark" (click)="modal.close('confirm')">Confirm</button> |
|||
<button type="button" class="btn btn-dark" (click)="modal.close('cancel')">Cancel</button> |
|||
</div> |
|||
</ng-template> |
|||
</mat-card-content> |
|||
|
|||
<mat-card-actions align="end"> |
|||
<div class="button-row"> |
|||
<button mat-flat-button color="primary" type="button" (click)="confirm($event)">Confirm</button> |
|||
<button mat-flat-button color="warn" type="button" (click)="cancel($event)">Cancel</button> |
|||
</div> |
|||
</mat-card-actions> |
|||
|
|||
</mat-card> |
|||
</div> |
|||
|
|||
<button |
|||
class="mdl-button mdl-button--icon mdl-js-button mdl-js-ripple-effect" |
|||
data-placement="bottom" |
|||
[title]="hover" |
|||
(click)="open($event, content)"> |
|||
mat-icon-button |
|||
color="primary" |
|||
[matTooltip]="hover" |
|||
(click)="open($event)"> |
|||
<i class="material-icons">{{icon}}</i> |
|||
</button> |
|||
|
@ -0,0 +1 @@ |
|||
|
@ -1,9 +1,15 @@ |
|||
<div flex fxFill fxLayout="row" fxLayoutAlign="space-between" > |
|||
<div |
|||
fxFlexFill |
|||
fxLayout="row" |
|||
fxLayout.lt-lg="column" |
|||
|
|||
style="padding: 10px;" fxLayoutGap="20px"> |
|||
|
|||
<div fxFlex="65"> |
|||
<app-server [(server)]="servers[idx]" [(servers)]="servers" *ngFor="let server of servers; let idx = index"></app-server> |
|||
</div> |
|||
|
|||
<div fxFlex="34"> |
|||
<div fxFlex="35"> |
|||
<app-add-server [(servers)]="servers"></app-add-server> |
|||
</div> |
|||
|
@ -0,0 +1,38 @@ |
|||
import { Component, OnInit } from '@angular/core'; |
|||
import { BreakpointObserver } from '@angular/cdk/layout'; |
|||
import { Server } from '../../interfaces/server'; |
|||
import { ServerService } from '../../services/server.service'; |
|||
import { Peer } from '../../interfaces/peer'; |
|||
|
|||
@Component({ |
|||
selector: 'dashboard', |
|||
templateUrl: './dashboard.component.html', |
|||
styleUrls: ['./dashboard.component.css'], |
|||
}) |
|||
export class DashboardComponent implements OnInit { |
|||
servers: Server[] = []; |
|||
|
|||
constructor(private breakpointObserver: BreakpointObserver, private serverAPI: ServerService) { |
|||
|
|||
} |
|||
|
|||
ngOnInit(): void { |
|||
this.serverAPI.getServers() |
|||
.subscribe((servers: 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; |
|||
}); |
|||
|
|||
}); |
|||
|
|||
}); |
|||
|
|||
}); |
|||
} |
|||
|
|||
} |
@ -0,0 +1,49 @@ |
|||
import { NgModule } from '@angular/core'; |
|||
import { CommonModule } from '@angular/common'; |
|||
import { DashboardComponent } from './dashboard.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'; |
|||
import {MatTooltipModule} from "@angular/material/tooltip"; |
|||
|
|||
@NgModule({ |
|||
declarations: [ |
|||
DashboardComponent, |
|||
ServerComponent, |
|||
AddServerComponent, |
|||
PeerComponent, |
|||
], |
|||
imports: [ |
|||
CommonModule, |
|||
MatGridListModule, |
|||
MatCardModule, |
|||
MatMenuModule, |
|||
MatIconModule, |
|||
MatButtonModule, |
|||
MatExpansionModule, |
|||
MatFormFieldModule, |
|||
MatInputModule, |
|||
ReactiveFormsModule, |
|||
ComponentsModule, |
|||
FlexModule, |
|||
MatTableModule, |
|||
FormsModule, |
|||
QRCodeModule, |
|||
MatTooltipModule, |
|||
|
|||
], |
|||
}) |
|||
export class DashboardModule { } |
@ -0,0 +1,70 @@ |
|||
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>(); |
|||
|
|||
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(); |
|||
} |
|||
}); |
|||
|
|||
} |
|||
|
|||
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); |
|||
}); |
|||
} |
|||
|
|||
|
|||
|
|||
|
|||
} |
@ -0,0 +1,148 @@ |
|||
|
|||
<mat-card class="dashboard-card"> |
|||
|
|||
<mat-card-header> |
|||
<mat-card-title class="card-container-left"> |
|||
|
|||
<mat-icon |
|||
class="app-material-icon-valign" |
|||
[class]="{'green': server.is_running, 'red': !server.is_running}" |
|||
matTooltip="Indicates if the server is online or offline" |
|||
>check_circle</mat-icon> |
|||
{{server.interface}} |
|||
|
|||
</mat-card-title> |
|||
<mat-card-title class="card-container-right"> |
|||
|
|||
<app-modal-confirm |
|||
[qrCode]="true" |
|||
[noConfirm]="false" |
|||
area="true" |
|||
icon="settings" |
|||
title="Configuration" |
|||
[text]="server.configuration" |
|||
hover="Show config for {{server.interface}}"> |
|||
</app-modal-confirm> |
|||
|
|||
<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> |
|||
|
|||
</mat-card-title> |
|||
|
|||
<mat-card-subtitle style="margin-top: 2px;">Endpoint: <b>{{server.endpoint}}:{{server.listen_port}}</b> - Address Space: <b>{{server.address}}</b></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> |
|||
<i *ngIf="{ a: (peer._stats && peer._stats.handshake && (peer._stats.handshake.split(' ')[1] === 'seconds' || pInt(peer._stats.handshake.split(' ')[0]) < 3))}; let isRunning"class="material-icons table-icon app-material-icon-valign" [ngClass]="{'green': isRunning.a, 'red': !isRunning.a}">check_circle</i> |
|||
{{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}); selectedPeer=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> |
@ -0,0 +1,13 @@ |
|||
|
|||
table { |
|||
width: 100%; |
|||
} |
|||
|
|||
:host { |
|||
width: 100%; |
|||
} |
|||
|
|||
.table-icon{ |
|||
font-size: 20px; |
|||
} |
|||
|
@ -0,0 +1,75 @@ |
|||
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', |
|||
|
|||
styleUrls: ['./server.component.scss', '../dashboard.component.css'], |
|||
}) |
|||
export class ServerComponent implements OnInit { |
|||
@Input() server: Server; |
|||
@Input() servers: Server[]; |
|||
public editPeerEmitter: EventEmitter<any> = new EventEmitter<any>(); |
|||
|
|||
selectedPeer: Peer | null; |
|||
|
|||
constructor(private serverAPI: ServerService, private comm: DataService) { } |
|||
|
|||
ngOnInit(): void { |
|||
console.log('Server'); |
|||
} |
|||
|
|||
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({ |
|||
server_interface: this.server.interface |
|||
}).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 }); |
|||
} |
|||
pInt(string: string) { |
|||
return parseInt(string); |
|||
} |
|||
} |
@ -1,18 +0,0 @@ |
|||
.grid-container { |
|||
margin: 20px; |
|||
} |
|||
|
|||
.dashboard-card { |
|||
position: absolute; |
|||
top: 15px; |
|||
left: 15px; |
|||
right: 15px; |
|||
bottom: 15px; |
|||
} |
|||
|
|||
.more-button { |
|||
position: absolute; |
|||
top: 5px; |
|||
right: 10px; |
|||
} |
|||
|
@ -1,40 +0,0 @@ |
|||
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(); |
|||
}); |
|||
}); |
@ -1,44 +0,0 @@ |
|||
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 |
|||
}); |
|||
|
|||
|
|||
}); |
|||
|
|||
|
|||
}); |
|||
|
|||
|
|||
}) |
|||
} |
|||
|
|||
} |
@ -1,49 +0,0 @@ |
|||
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 { } |
@ -1,84 +0,0 @@ |
|||
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) |
|||
} |
|||
} |
@ -1,248 +0,0 @@ |
|||
|
|||
<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>--> |
@ -1,28 +0,0 @@ |
|||
|
|||
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; |
|||
} |
|||
|
|||
|
|||
|
@ -1,77 +0,0 @@ |
|||
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,32 +1,29 @@ |
|||
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/*"; |
|||
|
|||
|
|||
|
|||
import { DashboardComponent } from './dashboard/dashboard.component'; |
|||
import { LayoutComponent } from '../layout/layout/layout.component'; |
|||
import { ErrorComponent } from './error'; |
|||
import { LoginComponent } from './user/login/login.component'; |
|||
import { AuthGuard } from '@services/*'; |
|||
import { EditComponent } from './user/edit/edit.component'; |
|||
|
|||
const routes: Routes = [ |
|||
{ path: '', component: LayoutComponent, children: |
|||
[ |
|||
//{ path: 'dashboard', component: DashboardComponent, pathMatch: 'full', canActivate: [AuthGuard]},
|
|||
{ path: 'dashboard', component: Dashboard2Component, pathMatch: 'full', canActivate: [AuthGuard]}, |
|||
[ |
|||
{ path: 'dashboard', component: DashboardComponent, 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'}, |
|||
] |
|||
[ |
|||
{ path: 'edit', component: EditComponent, pathMatch: 'full' }, |
|||
{ path: 'login', component: LoginComponent, pathMatch: 'full' }, |
|||
], |
|||
}, |
|||
]; |
|||
|
|||
@NgModule({ |
|||
imports: [RouterModule.forChild(routes)], |
|||
exports: [RouterModule] |
|||
exports: [RouterModule], |
|||
}) |
|||
export class PageRoutingModule { } |
|||
|
@ -1,40 +1,42 @@ |
|||
<div flex fxFill fxLayout="row" fxLayoutAlign="center center" > |
|||
<div fxFlex="33"> |
|||
|
|||
<mat-card> |
|||
<mat-card-title> |
|||
Authenticate to Wireguard Management |
|||
</mat-card-title> |
|||
|
|||
<mat-card> |
|||
<mat-card-title> |
|||
Authenticate to Wireguard Management |
|||
</mat-card-title> |
|||
<mat-card-content> |
|||
<form [formGroup]="loginForm" (ngSubmit)="loginForm.valid && login()" class="form"> |
|||
|
|||
<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>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="password" id="password" autocomplete="on" formControlName="password" 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 mat-raised-button color="primary" [disabled]="!loginForm.valid" type="submit"> |
|||
SIGN IN |
|||
</button> |
|||
|
|||
<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> |
|||
</div> |
|||
|
|||
|
|||
|
|||
|
|||
</div> |
|||
|
|||
</form> |
|||
</mat-card-content> |
|||
|
|||
|
|||
</mat-card> |
|||
|
|||
|
|||
|
@ -1,28 +1,26 @@ |
|||
import {EventEmitter, Injectable} from '@angular/core'; |
|||
import {Observable} from "rxjs"; |
|||
|
|||
import { EventEmitter, Injectable } from '@angular/core'; |
|||
import { Observable } from 'rxjs'; |
|||
|
|||
@Injectable({ |
|||
providedIn: 'root' |
|||
providedIn: 'root', |
|||
}) |
|||
export class DataService { |
|||
|
|||
_observables: any = {}; |
|||
constructor() {} |
|||
|
|||
|
|||
emit(event: string, value: any): void{ |
|||
if(this._observables.hasOwnProperty(event)) { |
|||
this._observables[event].emit(value) |
|||
emit(event: string, value: any): void { |
|||
if (this._observables.hasOwnProperty(event)) { |
|||
this._observables[event].emit(value); |
|||
} |
|||
} |
|||
|
|||
on(event: string): Observable<any> { |
|||
if(!this._observables.hasOwnProperty(event)) { |
|||
this._observables[event] = new EventEmitter<any>() |
|||
if (!this._observables.hasOwnProperty(event)) { |
|||
this._observables[event] = new EventEmitter<any>(); |
|||
} |
|||
|
|||
return this._observables[event].asObservable() |
|||
return this._observables[event].asObservable(); |
|||
} |
|||
|
|||
} |
|||
|
@ -1,93 +1,93 @@ |
|||
import { Injectable } from '@angular/core'; |
|||
import {ConfigService} from "./config.service"; |
|||
import {HttpClient} from "@angular/common/http"; |
|||
import { ConfigService } from './config.service'; |
|||
import { HttpClient } from '@angular/common/http'; |
|||
|
|||
import {catchError} from "rxjs/operators"; |
|||
import {Server} from "../interfaces/server"; |
|||
import {Peer} from "../interfaces/peer"; |
|||
import {Observable, Subscribable} from "rxjs"; |
|||
import { catchError } from 'rxjs/operators'; |
|||
import { Server } from '../interfaces/server'; |
|||
import { Peer } from '../interfaces/peer'; |
|||
import { Observable, Subscribable } from 'rxjs'; |
|||
|
|||
@Injectable({ |
|||
providedIn: 'root' |
|||
providedIn: 'root', |
|||
}) |
|||
export class ServerService { |
|||
public_url_wg: string = "/api/wg"; |
|||
public url: string = this.public_url_wg + "/server"; |
|||
constructor(private config: ConfigService, private http: HttpClient) { |
|||
public base = '/api/v1/'; |
|||
public serverURL = this.base + "server"; |
|||
public peerURL = this.base + "peer"; |
|||
public wgURL = this.base + "wg"; |
|||
|
|||
|
|||
constructor(private config: ConfigService, private http: HttpClient) { |
|||
|
|||
} |
|||
|
|||
public deletePeer(peer: Peer): Subscribable<Peer>{ |
|||
return this.http.post(this.url + "/peer/delete", peer) |
|||
public deletePeer(peer: Peer): Subscribable<Peer> { |
|||
return this.http.post(this.peerURL + '/delete', peer); |
|||
} |
|||
|
|||
|
|||
public serverPerformAction(action: string, item: any): Subscribable<Server> { |
|||
return this.http.post(this.url + "/" + action, item) |
|||
.pipe(catchError(this.config.handleError.bind(this))) |
|||
return this.http.post(this.serverURL + '/' + action, item) |
|||
.pipe(catchError(this.config.handleError.bind(this))); |
|||
} |
|||
|
|||
public addPeer(server: Server): Subscribable<Peer>{ |
|||
return this.http.post(this.url + "/peer/add", server) |
|||
public addPeer(server_interface: any): Subscribable<Peer> { |
|||
return this.http.post(this.peerURL + '/add', server_interface); |
|||
} |
|||
|
|||
public editPeer(peer: Peer): Subscribable<Peer>{ |
|||
return this.http.post(this.url + "/peer/edit", peer) |
|||
public editPeer(peer: Peer): Subscribable<Peer> { |
|||
return this.http.post(this.peerURL + '/edit', peer); |
|||
} |
|||
|
|||
public getServers(): Observable<Array<Server>>{ |
|||
return this.http.get<Array<Server>>(this.url + "/all") |
|||
.pipe(catchError(this.config.handleError.bind(this))) |
|||
public getServers(): Observable<Server[]> { |
|||
return this.http.get<Server[]>(this.serverURL + '/all') |
|||
.pipe(catchError(this.config.handleError.bind(this))); |
|||
} |
|||
|
|||
|
|||
public addServer(item: Server): Subscribable<Server> { |
|||
return this.http.post(this.url + "/add", item) |
|||
.pipe(catchError(this.config.handleError.bind(this))) |
|||
return this.http.post(this.serverURL + '/add', item) |
|||
.pipe(catchError(this.config.handleError.bind(this))); |
|||
} |
|||
|
|||
public startServer(item: Server): Subscribable<Server> { |
|||
return this.serverPerformAction("start", item) |
|||
return this.serverPerformAction('start', item); |
|||
} |
|||
|
|||
public stopServer(item: Server): Subscribable<Server> { |
|||
return this.serverPerformAction("stop", item) |
|||
return this.serverPerformAction('stop', item); |
|||
} |
|||
|
|||
public restartServer(item: Server): Subscribable<Server> { |
|||
return this.serverPerformAction("restart", item) |
|||
return this.serverPerformAction('restart', item); |
|||
} |
|||
|
|||
public deleteServer(item: Server): Subscribable<Server> { |
|||
return this.serverPerformAction("delete", item) |
|||
return this.serverPerformAction('delete', item); |
|||
} |
|||
|
|||
public editServer(oldServer: Server, newServer: Server): Subscribable<Server> { |
|||
return this.serverPerformAction("edit", { |
|||
"interface": oldServer.interface, |
|||
"server": newServer |
|||
}) |
|||
return this.serverPerformAction('edit', { |
|||
interface: oldServer.interface, |
|||
server: newServer, |
|||
}); |
|||
} |
|||
|
|||
public getKeyPair() { |
|||
return this.http.get(this.public_url_wg + "/generate_keypair") |
|||
return this.http.get(this.wgURL + '/generate_keypair'); |
|||
} |
|||
|
|||
public getPSK() { |
|||
return this.http.get(this.public_url_wg + "/generate_psk") |
|||
return this.http.get(this.wgURL + '/generate_psk'); |
|||
} |
|||
|
|||
public peerConfig(peer: Peer) { |
|||
return this.http.post(this.public_url_wg + "/server/peer/config", peer) |
|||
return this.http.post(this.peerURL + '/config', peer); |
|||
} |
|||
|
|||
public serverConfig(server: Server) { |
|||
return this.http.post(this.url + "/config", server) |
|||
return this.http.post(this.serverURL + '/config', server); |
|||
} |
|||
|
|||
public serverStats(server: Server) { |
|||
return this.http.post(this.url + "/stats", server) |
|||
return this.http.post(this.serverURL + '/stats', server); |
|||
} |
|||
} |
|||
|