| 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] | [Interface] | ||||
| Address = {{ data.address.replace("/32", "/24") }} | Address = {{ data.peer.address.replace("/32", "/24") }} | ||||
| PrivateKey = {{ data.private_key }} | PrivateKey = {{ data.peer.private_key }} | ||||
| DNS = {{ data.dns }} | DNS = {{ data.peer.dns }} | ||||
| 
 | 
 | ||||
| [Peer] | [Peer] | ||||
| PublicKey = {{ data.server_ref.public_key }} | PublicKey = {{ data.server.public_key }} | ||||
| AllowedIPs = {{ data.allowed_ips }} | AllowedIPs = {{ data.peer.allowed_ips }} | ||||
| Endpoint = {{ data.server_ref.endpoint }}:{{ data.server_ref.listen_port }} | Endpoint = {{ data.server.endpoint }}:{{ data.server.listen_port }} | ||||
| {% if data.preshared_key %} | {% if data.server.shared_key %} | ||||
|     PresharedKey = {{ data.server_ref.preshared_key }} | PresharedKey = {{ data.server.shared_key }} | ||||
| {% endif %} | {% 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 {Component, HostBinding} from '@angular/core'; | ||||
| import {AuthService} from "@services/*"; | 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({ | @Component({ | ||||
|   selector: 'app-root', |   selector: 'app-root', | ||||
|   template: `<router-outlet></router-outlet>`, |   template: `<router-outlet></router-outlet>`, | ||||
| }) | }) | ||||
| export class AppComponent { | 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 { | export interface User { | ||||
|   full_name: string; |   full_name: string; | ||||
|   email: string; |   email: string; | ||||
|   role: string; |   role: string; | ||||
|   username: string; |   username: string; | ||||
|   access_token: string, |   access_token: string; | ||||
|   token_type: string, |   token_type: string; | ||||
| 
 | 
 | ||||
| } | } | ||||
|  | |||||
| @ -1,29 +1,78 @@ | |||||
| import { Component, OnInit } from '@angular/core'; | import { Component, OnInit } from '@angular/core'; | ||||
| import {Observable} from "rxjs"; | import { Observable } from 'rxjs'; | ||||
| import {BreakpointObserver, Breakpoints} from "@angular/cdk/layout"; | import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout'; | ||||
| import {map, shareReplay} from "rxjs/operators"; | import { map, shareReplay } from 'rxjs/operators'; | ||||
| import {ConfigService} from "../../services/config.service"; | 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({ | @Component({ | ||||
|   selector: 'app-layout', |   selector: 'app-layout', | ||||
|   templateUrl: './layout.component.html', |   templateUrl: './layout.component.html', | ||||
|   styleUrls: ['./layout.component.scss'] |   styleUrls: ['./layout.component.scss'], | ||||
| }) | }) | ||||
| export class LayoutComponent implements OnInit { | export class LayoutComponent implements OnInit { | ||||
| 
 | 
 | ||||
|   isHandset$: Observable<boolean> = this.breakpointObserver.observe(Breakpoints.Handset) |   isHandset$: Observable<boolean> = this.breakpointObserver.observe(Breakpoints.Handset) | ||||
|     .pipe( |     .pipe( | ||||
|       map(result => result.matches), |       map(result => result.matches), | ||||
|       shareReplay() |       shareReplay(), | ||||
|     ); |     ); | ||||
| 
 | 
 | ||||
|   menu: Array<{link: Array<string>, icon: string, text: string}> = [ |   menu: {link: string[], icon: string, text: string}[] = [ | ||||
|     { link: ["/page/dashboard"], icon: "home", text: "Dashboard"} |     { 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 { |   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({ | @Component({ | ||||
|   selector: 'app-components', |   selector: 'app-components', | ||||
|   templateUrl: './components.component.html', |   template: '', | ||||
|   styleUrls: ['./components.component.scss'], |   styles: [''], | ||||
| }) | }) | ||||
| export class ComponentsComponent { | 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')"> | <div *ngIf="shown"    fxLayout="row" | ||||
|       <span aria-hidden="true">×</span> |      fxLayout.xs="column" | ||||
|     </button> |      fxFlexFill | ||||
|  |      fxLayoutAlign="center center"> | ||||
|  |   <mat-card  style="position: absolute; z-index: 10; left: 50%; top: 0; width: 50%;"> | ||||
|  |     <mat-card-header> | ||||
| 
 | 
 | ||||
|   </div> |       <mat-card-title class="card-container-left"> | ||||
|   <div class="modal-body"> |         {{title || "No 'title' defined" }} | ||||
|       <textarea *ngIf="area" readonly class=" |       </mat-card-title> | ||||
|                   mdl-textfield--full-width |       <mat-card-title class="card-container-right"> | ||||
|                   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> |         <mat-icon matTooltip="Close" (click)="cancel($event)" | ||||
|     <div style="text-align: center;" *ngIf="qrCode"> |                   class="app-material-icon-valign" style="cursor: pointer" | ||||
|       <qrcode [qrdata]="text" [width]="256" [errorCorrectionLevel]="'M'"></qrcode> |         >close</mat-icon> | ||||
|     </div> | 
 | ||||
|  | 
 | ||||
|  |       </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> |     </mat-card-content> | ||||
|   <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-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 | <button | ||||
|   class="mdl-button mdl-button--icon mdl-js-button mdl-js-ripple-effect" |   mat-icon-button | ||||
|   data-placement="bottom" |   color="primary" | ||||
|   [title]="hover" |   [matTooltip]="hover" | ||||
|   (click)="open($event, content)"> |   (click)="open($event)"> | ||||
|   <i class="material-icons">{{icon}}</i> |   <i class="material-icons">{{icon}}</i> | ||||
| </button> | </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"> |   <div fxFlex="65"> | ||||
|       <app-server [(server)]="servers[idx]" [(servers)]="servers" *ngFor="let server of servers; let idx = index"></app-server> |       <app-server [(server)]="servers[idx]" [(servers)]="servers" *ngFor="let server of servers; let idx = index"></app-server> | ||||
|   </div> |   </div> | ||||
| 
 | 
 | ||||
|   <div fxFlex="34"> |   <div fxFlex="35"> | ||||
|     <app-add-server [(servers)]="servers"></app-add-server> |     <app-add-server [(servers)]="servers"></app-add-server> | ||||
|   </div> |   </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 { NgModule } from '@angular/core'; | ||||
| import { Routes, RouterModule } from '@angular/router'; | import { Routes, RouterModule } from '@angular/router'; | ||||
| import {Dashboard2Component} from "./dashboard2/dashboard2.component"; | import { DashboardComponent } from './dashboard/dashboard.component'; | ||||
| import {LayoutComponent} from "../layout/layout/layout.component"; | import { LayoutComponent } from '../layout/layout/layout.component'; | ||||
| import {ErrorComponent} from "./error"; | import { ErrorComponent } from './error'; | ||||
| import {LoginComponent} from "./user/login/login.component"; | import { LoginComponent } from './user/login/login.component'; | ||||
| import {AuthGuard} from "@services/*"; | import { AuthGuard } from '@services/*'; | ||||
| 
 | import { EditComponent } from './user/edit/edit.component'; | ||||
| 
 |  | ||||
| 
 |  | ||||
| 
 | 
 | ||||
| const routes: Routes = [ | const routes: Routes = [ | ||||
|   { path: '', component: LayoutComponent, children: |   { path: '', component: LayoutComponent, children: | ||||
|       [ |   [ | ||||
|         //{ path: 'dashboard', component: DashboardComponent, pathMatch: 'full', canActivate: [AuthGuard]},
 |         { path: 'dashboard', component: DashboardComponent, pathMatch: 'full', canActivate: [AuthGuard] }, | ||||
|         { path: 'dashboard', component: Dashboard2Component, pathMatch: 'full', canActivate: [AuthGuard]}, |  | ||||
|         { path: '404', component: ErrorComponent, pathMatch: 'full' }, |         { path: '404', component: ErrorComponent, pathMatch: 'full' }, | ||||
|       ] |   ], | ||||
|   }, |   }, | ||||
|   { path: 'user', component: LayoutComponent, children: |   { path: 'user', component: LayoutComponent, children: | ||||
|       [ |   [ | ||||
|         //{ path: 'dashboard', component: DashboardComponent, pathMatch: 'full', canActivate: [AuthGuard]},
 |         { path: 'edit', component: EditComponent, pathMatch: 'full' }, | ||||
|         { path: 'login', component: LoginComponent, pathMatch: 'full'}, |         { path: 'login', component: LoginComponent, pathMatch: 'full' }, | ||||
|       ] |   ], | ||||
|   }, |   }, | ||||
| ]; | ]; | ||||
| 
 | 
 | ||||
| @NgModule({ | @NgModule({ | ||||
|   imports: [RouterModule.forChild(routes)], |   imports: [RouterModule.forChild(routes)], | ||||
|   exports: [RouterModule] |   exports: [RouterModule], | ||||
| }) | }) | ||||
| export class PageRoutingModule { } | 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-content> | ||||
|   <mat-card-title> |         <form [formGroup]="loginForm" (ngSubmit)="loginForm.valid && login()" class="form"> | ||||
|     Authenticate to Wireguard Management |  | ||||
|   </mat-card-title> |  | ||||
| 
 | 
 | ||||
|   <mat-card-content> |           <p> | ||||
|     <form [formGroup]="loginForm" (ngSubmit)="loginForm.valid && login()" class="form"> |             <mat-form-field class="full-width"> | ||||
|  |               <mat-label>Username</mat-label> | ||||
|  |               <input type="text" id="username" formControlName="username" matInput> | ||||
|  |             </mat-form-field> | ||||
|  |           </p> | ||||
| 
 | 
 | ||||
|       <p> |           <p> | ||||
|         <mat-form-field class="full-width"> |             <mat-form-field class="full-width"> | ||||
|           <mat-label>Username</mat-label> |               <mat-label>Password</mat-label> | ||||
|           <input type="text" id="username" formControlName="username" matInput> |               <input type="password" id="password" autocomplete="on" formControlName="password"  matInput> | ||||
|         </mat-form-field> |             </mat-form-field> | ||||
|       </p> |           </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"> |         </form> | ||||
|         SIGN IN |       </mat-card-content> | ||||
|       </button> |     </mat-card> | ||||
|  |   </div> | ||||
| 
 | 
 | ||||
| 
 | 
 | ||||
| 
 | 
 | ||||
| 
 | 
 | ||||
|  | </div> | ||||
| 
 | 
 | ||||
|     </form> |  | ||||
|   </mat-card-content> |  | ||||
| 
 |  | ||||
| 
 |  | ||||
| </mat-card> |  | ||||
| 
 | 
 | ||||
| 
 | 
 | ||||
|  | |||||
| @ -1,28 +1,26 @@ | |||||
| import {EventEmitter, Injectable} from '@angular/core'; | import { EventEmitter, Injectable } from '@angular/core'; | ||||
| import {Observable} from "rxjs"; | import { Observable } from 'rxjs'; | ||||
| 
 |  | ||||
| 
 | 
 | ||||
| @Injectable({ | @Injectable({ | ||||
|   providedIn: 'root' |   providedIn: 'root', | ||||
| }) | }) | ||||
| export class DataService { | export class DataService { | ||||
| 
 | 
 | ||||
|   _observables: any  = {}; |   _observables: any  = {}; | ||||
|   constructor() {} |   constructor() {} | ||||
| 
 | 
 | ||||
| 
 |   emit(event: string, value: any): void { | ||||
|   emit(event: string, value: any): void{ |     if (this._observables.hasOwnProperty(event)) { | ||||
|     if(this._observables.hasOwnProperty(event)) { |       this._observables[event].emit(value); | ||||
|       this._observables[event].emit(value) |  | ||||
|     } |     } | ||||
|   } |   } | ||||
| 
 | 
 | ||||
|   on(event: string): Observable<any> { |   on(event: string): Observable<any> { | ||||
|     if(!this._observables.hasOwnProperty(event)) { |     if (!this._observables.hasOwnProperty(event)) { | ||||
|       this._observables[event] = new EventEmitter<any>() |       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 { Injectable } from '@angular/core'; | ||||
| import {ConfigService} from "./config.service"; | import { ConfigService } from './config.service'; | ||||
| import {HttpClient} from "@angular/common/http"; | import { HttpClient } from '@angular/common/http'; | ||||
| 
 | 
 | ||||
| import {catchError} from "rxjs/operators"; | import { catchError } from 'rxjs/operators'; | ||||
| import {Server} from "../interfaces/server"; | import { Server } from '../interfaces/server'; | ||||
| import {Peer} from "../interfaces/peer"; | import { Peer } from '../interfaces/peer'; | ||||
| import {Observable, Subscribable} from "rxjs"; | import { Observable, Subscribable } from 'rxjs'; | ||||
| 
 | 
 | ||||
| @Injectable({ | @Injectable({ | ||||
|   providedIn: 'root' |   providedIn: 'root', | ||||
| }) | }) | ||||
| export class ServerService { | export class ServerService { | ||||
|   public_url_wg: string = "/api/wg"; |   public base = '/api/v1/'; | ||||
|   public url: string = this.public_url_wg + "/server"; |   public serverURL = this.base + "server"; | ||||
|   constructor(private config: ConfigService, private http: HttpClient) { |   public peerURL = this.base + "peer"; | ||||
|  |   public wgURL = this.base + "wg"; | ||||
| 
 | 
 | ||||
| 
 | 
 | ||||
|  |   constructor(private config: ConfigService, private http: HttpClient) { | ||||
| 
 | 
 | ||||
|   } |   } | ||||
| 
 | 
 | ||||
|   public deletePeer(peer: Peer): Subscribable<Peer>{ |   public deletePeer(peer: Peer): Subscribable<Peer> { | ||||
|     return this.http.post(this.url + "/peer/delete", peer) |     return this.http.post(this.peerURL + '/delete', peer); | ||||
|   } |   } | ||||
| 
 | 
 | ||||
| 
 |  | ||||
|   public serverPerformAction(action: string, item: any): Subscribable<Server> { |   public serverPerformAction(action: string, item: any): Subscribable<Server> { | ||||
|     return this.http.post(this.url + "/" + action, item) |     return this.http.post(this.serverURL + '/' + action, item) | ||||
|       .pipe(catchError(this.config.handleError.bind(this))) |       .pipe(catchError(this.config.handleError.bind(this))); | ||||
|   } |   } | ||||
| 
 | 
 | ||||
|   public addPeer(server: Server): Subscribable<Peer>{ |   public addPeer(server_interface: any): Subscribable<Peer> { | ||||
|     return this.http.post(this.url + "/peer/add", server) |     return this.http.post(this.peerURL + '/add', server_interface); | ||||
|   } |   } | ||||
| 
 | 
 | ||||
|   public editPeer(peer: Peer): Subscribable<Peer>{ |   public editPeer(peer: Peer): Subscribable<Peer> { | ||||
|     return this.http.post(this.url + "/peer/edit", peer) |     return this.http.post(this.peerURL + '/edit', peer); | ||||
|   } |   } | ||||
| 
 | 
 | ||||
|   public getServers(): Observable<Array<Server>>{ |   public getServers(): Observable<Server[]> { | ||||
|     return this.http.get<Array<Server>>(this.url + "/all") |     return this.http.get<Server[]>(this.serverURL + '/all') | ||||
|       .pipe(catchError(this.config.handleError.bind(this))) |       .pipe(catchError(this.config.handleError.bind(this))); | ||||
|   } |   } | ||||
| 
 | 
 | ||||
| 
 |  | ||||
|   public addServer(item: Server): Subscribable<Server> { |   public addServer(item: Server): Subscribable<Server> { | ||||
|     return this.http.post(this.url + "/add", item) |     return this.http.post(this.serverURL + '/add', item) | ||||
|       .pipe(catchError(this.config.handleError.bind(this))) |       .pipe(catchError(this.config.handleError.bind(this))); | ||||
|   } |   } | ||||
| 
 | 
 | ||||
|   public startServer(item: Server): Subscribable<Server> { |   public startServer(item: Server): Subscribable<Server> { | ||||
|     return this.serverPerformAction("start", item) |     return this.serverPerformAction('start', item); | ||||
|   } |   } | ||||
| 
 | 
 | ||||
|   public stopServer(item: Server): Subscribable<Server> { |   public stopServer(item: Server): Subscribable<Server> { | ||||
|     return this.serverPerformAction("stop", item) |     return this.serverPerformAction('stop', item); | ||||
|   } |   } | ||||
| 
 | 
 | ||||
|   public restartServer(item: Server): Subscribable<Server> { |   public restartServer(item: Server): Subscribable<Server> { | ||||
|     return this.serverPerformAction("restart", item) |     return this.serverPerformAction('restart', item); | ||||
|   } |   } | ||||
| 
 | 
 | ||||
|   public deleteServer(item: Server): Subscribable<Server> { |   public deleteServer(item: Server): Subscribable<Server> { | ||||
|     return this.serverPerformAction("delete", item) |     return this.serverPerformAction('delete', item); | ||||
|   } |   } | ||||
| 
 | 
 | ||||
|   public editServer(oldServer: Server, newServer: Server): Subscribable<Server> { |   public editServer(oldServer: Server, newServer: Server): Subscribable<Server> { | ||||
|     return this.serverPerformAction("edit", { |     return this.serverPerformAction('edit', { | ||||
|       "interface": oldServer.interface, |       interface: oldServer.interface, | ||||
|       "server": newServer |       server: newServer, | ||||
|     }) |     }); | ||||
|   } |   } | ||||
| 
 | 
 | ||||
|   public getKeyPair() { |   public getKeyPair() { | ||||
|     return this.http.get(this.public_url_wg + "/generate_keypair") |     return this.http.get(this.wgURL + '/generate_keypair'); | ||||
|   } |   } | ||||
| 
 | 
 | ||||
|   public getPSK() { |   public getPSK() { | ||||
|     return this.http.get(this.public_url_wg + "/generate_psk") |     return this.http.get(this.wgURL + '/generate_psk'); | ||||
|   } |   } | ||||
| 
 | 
 | ||||
|   public peerConfig(peer: Peer) { |   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) { |   public serverConfig(server: Server) { | ||||
|     return this.http.post(this.url + "/config", server) |     return this.http.post(this.serverURL + '/config', server); | ||||
|   } |   } | ||||
| 
 | 
 | ||||
|   public serverStats(server: Server) { |   public serverStats(server: Server) { | ||||
|     return this.http.post(this.url + "/stats", server) |     return this.http.post(this.serverURL + '/stats', server); | ||||
|   } |   } | ||||
| } | } | ||||
|  | |||||