Reviewed-on: sh-edraft.de/kd_discord_bot#73 Closes #72
This commit is contained in:
commit
f553779797
@ -10,7 +10,6 @@
|
|||||||
"DatabaseModule": true,
|
"DatabaseModule": true,
|
||||||
"ModeratorModule": true,
|
"ModeratorModule": true,
|
||||||
"PermissionModule": true,
|
"PermissionModule": true,
|
||||||
"PresenceModule": true,
|
"PresenceModule": true
|
||||||
"ApiOnly": true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
from cpl_query.extension import List
|
from cpl_query.extension import List
|
||||||
|
|
||||||
@ -23,6 +24,12 @@ class AuthServiceABC(ABC):
|
|||||||
@abstractmethod
|
@abstractmethod
|
||||||
def decode_token(self, token: str) -> dict: pass
|
def decode_token(self, token: str) -> dict: pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def get_decoded_token_from_request(self) -> dict: pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def find_decoded_token_from_request(self) -> Optional[dict]: pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
async def get_all_auth_users_async(self) -> List[AuthUserDTO]: pass
|
async def get_all_auth_users_async(self) -> List[AuthUserDTO]: pass
|
||||||
|
|
||||||
|
@ -11,10 +11,12 @@ from flask import Flask
|
|||||||
from bot_api.abc.auth_service_abc import AuthServiceABC
|
from bot_api.abc.auth_service_abc import AuthServiceABC
|
||||||
from bot_api.api import Api
|
from bot_api.api import Api
|
||||||
from bot_api.api_thread import ApiThread
|
from bot_api.api_thread import ApiThread
|
||||||
|
from bot_api.controller.discord.server_controller import ServerController
|
||||||
from bot_api.controller.gui_controller import GuiController
|
from bot_api.controller.gui_controller import GuiController
|
||||||
from bot_api.controller.auth_controller import AuthController
|
from bot_api.controller.auth_controller import AuthController
|
||||||
from bot_api.event.bot_api_on_ready_event import BotApiOnReadyEvent
|
from bot_api.event.bot_api_on_ready_event import BotApiOnReadyEvent
|
||||||
from bot_api.service.auth_service import AuthService
|
from bot_api.service.auth_service import AuthService
|
||||||
|
from bot_api.service.discord_service import DiscordService
|
||||||
from bot_core.abc.module_abc import ModuleABC
|
from bot_core.abc.module_abc import ModuleABC
|
||||||
from bot_core.configuration.feature_flags_enum import FeatureFlagsEnum
|
from bot_core.configuration.feature_flags_enum import FeatureFlagsEnum
|
||||||
|
|
||||||
@ -41,6 +43,8 @@ class ApiModule(ModuleABC):
|
|||||||
services.add_transient(AuthServiceABC, AuthService)
|
services.add_transient(AuthServiceABC, AuthService)
|
||||||
services.add_transient(AuthController)
|
services.add_transient(AuthController)
|
||||||
services.add_transient(GuiController)
|
services.add_transient(GuiController)
|
||||||
|
services.add_transient(DiscordService)
|
||||||
|
services.add_transient(ServerController)
|
||||||
|
|
||||||
# cpl-discord
|
# cpl-discord
|
||||||
self._dc.add_event(DiscordEventTypesEnum.on_ready.value, BotApiOnReadyEvent)
|
self._dc.add_event(DiscordEventTypesEnum.on_ready.value, BotApiOnReadyEvent)
|
||||||
|
@ -15,6 +15,7 @@ from bot_api.model.auth_user_dto import AuthUserDTO
|
|||||||
from bot_api.model.token_dto import TokenDTO
|
from bot_api.model.token_dto import TokenDTO
|
||||||
from bot_api.model.update_auth_user_dto import UpdateAuthUserDTO
|
from bot_api.model.update_auth_user_dto import UpdateAuthUserDTO
|
||||||
from bot_api.route.route import Route
|
from bot_api.route.route import Route
|
||||||
|
from bot_data.model.auth_role_enum import AuthRoleEnum
|
||||||
|
|
||||||
|
|
||||||
class AuthController:
|
class AuthController:
|
||||||
@ -41,13 +42,13 @@ class AuthController:
|
|||||||
self._auth_service = auth_service
|
self._auth_service = auth_service
|
||||||
|
|
||||||
@Route.get(f'{BasePath}/users')
|
@Route.get(f'{BasePath}/users')
|
||||||
@Route.authorize
|
@Route.authorize(role=AuthRoleEnum.admin)
|
||||||
async def get_all_users(self) -> Response:
|
async def get_all_users(self) -> Response:
|
||||||
result = await self._auth_service.get_all_auth_users_async()
|
result = await self._auth_service.get_all_auth_users_async()
|
||||||
return jsonify(result.select(lambda x: x.to_dict()))
|
return jsonify(result.select(lambda x: x.to_dict()))
|
||||||
|
|
||||||
@Route.post(f'{BasePath}/users/get/filtered')
|
@Route.post(f'{BasePath}/users/get/filtered')
|
||||||
@Route.authorize
|
@Route.authorize(role=AuthRoleEnum.admin)
|
||||||
async def get_filtered_users(self) -> Response:
|
async def get_filtered_users(self) -> Response:
|
||||||
dto: AuthUserSelectCriteria = JSONProcessor.process(AuthUserSelectCriteria, request.get_json(force=True, silent=True))
|
dto: AuthUserSelectCriteria = JSONProcessor.process(AuthUserSelectCriteria, request.get_json(force=True, silent=True))
|
||||||
result = await self._auth_service.get_filtered_auth_users_async(dto)
|
result = await self._auth_service.get_filtered_auth_users_async(dto)
|
||||||
@ -55,13 +56,13 @@ class AuthController:
|
|||||||
return jsonify(result.to_dict())
|
return jsonify(result.to_dict())
|
||||||
|
|
||||||
@Route.get(f'{BasePath}/users/get/<email>')
|
@Route.get(f'{BasePath}/users/get/<email>')
|
||||||
@Route.authorize
|
@Route.authorize(role=AuthRoleEnum.admin)
|
||||||
async def get_user_from_email(self, email: str) -> Response:
|
async def get_user_from_email(self, email: str) -> Response:
|
||||||
result = await self._auth_service.get_auth_user_by_email_async(email)
|
result = await self._auth_service.get_auth_user_by_email_async(email)
|
||||||
return jsonify(result.to_dict())
|
return jsonify(result.to_dict())
|
||||||
|
|
||||||
@Route.get(f'{BasePath}/users/find/<email>')
|
@Route.get(f'{BasePath}/users/find/<email>')
|
||||||
@Route.authorize
|
@Route.authorize(role=AuthRoleEnum.admin)
|
||||||
async def find_user_from_email(self, email: str) -> Response:
|
async def find_user_from_email(self, email: str) -> Response:
|
||||||
result = await self._auth_service.find_auth_user_by_email_async(email)
|
result = await self._auth_service.find_auth_user_by_email_async(email)
|
||||||
return jsonify(result.to_dict())
|
return jsonify(result.to_dict())
|
||||||
@ -109,7 +110,7 @@ class AuthController:
|
|||||||
return '', 200
|
return '', 200
|
||||||
|
|
||||||
@Route.post(f'{BasePath}/update-user-as-admin')
|
@Route.post(f'{BasePath}/update-user-as-admin')
|
||||||
@Route.authorize
|
@Route.authorize(role=AuthRoleEnum.admin)
|
||||||
async def update_user_as_admin(self):
|
async def update_user_as_admin(self):
|
||||||
dto: UpdateAuthUserDTO = JSONProcessor.process(UpdateAuthUserDTO, request.get_json(force=True, silent=True))
|
dto: UpdateAuthUserDTO = JSONProcessor.process(UpdateAuthUserDTO, request.get_json(force=True, silent=True))
|
||||||
await self._auth_service.update_user_as_admin_async(dto)
|
await self._auth_service.update_user_as_admin_async(dto)
|
||||||
@ -129,14 +130,14 @@ class AuthController:
|
|||||||
return '', 200
|
return '', 200
|
||||||
|
|
||||||
@Route.post(f'{BasePath}/delete-user')
|
@Route.post(f'{BasePath}/delete-user')
|
||||||
@Route.authorize
|
@Route.authorize(role=AuthRoleEnum.admin)
|
||||||
async def delete_user(self):
|
async def delete_user(self):
|
||||||
dto: AuthUserDTO = JSONProcessor.process(AuthUserDTO, request.get_json(force=True, silent=True))
|
dto: AuthUserDTO = JSONProcessor.process(AuthUserDTO, request.get_json(force=True, silent=True))
|
||||||
await self._auth_service.delete_auth_user_async(dto)
|
await self._auth_service.delete_auth_user_async(dto)
|
||||||
return '', 200
|
return '', 200
|
||||||
|
|
||||||
@Route.post(f'{BasePath}/delete-user-by-mail/<email>')
|
@Route.post(f'{BasePath}/delete-user-by-mail/<email>')
|
||||||
@Route.authorize
|
@Route.authorize(role=AuthRoleEnum.admin)
|
||||||
async def delete_user_by_mail(self, email: str):
|
async def delete_user_by_mail(self, email: str):
|
||||||
await self._auth_service.delete_auth_user_by_email_async(email)
|
await self._auth_service.delete_auth_user_by_email_async(email)
|
||||||
return '', 200
|
return '', 200
|
||||||
|
0
kdb-bot/src/bot_api/controller/discord/__init__.py
Normal file
0
kdb-bot/src/bot_api/controller/discord/__init__.py
Normal file
65
kdb-bot/src/bot_api/controller/discord/server_controller.py
Normal file
65
kdb-bot/src/bot_api/controller/discord/server_controller.py
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
from cpl_core.configuration import ConfigurationABC
|
||||||
|
from cpl_core.environment import ApplicationEnvironmentABC
|
||||||
|
from cpl_core.mailing import EMailClientABC, EMailClientSettings
|
||||||
|
from cpl_translation import TranslatePipe
|
||||||
|
from flask import Response, jsonify, request
|
||||||
|
|
||||||
|
from bot_api.api import Api
|
||||||
|
from bot_api.filter.discord.server_select_criteria import ServerSelectCriteria
|
||||||
|
from bot_api.json_processor import JSONProcessor
|
||||||
|
from bot_api.logging.api_logger import ApiLogger
|
||||||
|
from bot_api.route.route import Route
|
||||||
|
from bot_api.service.discord_service import DiscordService
|
||||||
|
from bot_data.model.auth_role_enum import AuthRoleEnum
|
||||||
|
|
||||||
|
|
||||||
|
class ServerController:
|
||||||
|
BasePath = f'/api/discord/server'
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
config: ConfigurationABC,
|
||||||
|
env: ApplicationEnvironmentABC,
|
||||||
|
logger: ApiLogger,
|
||||||
|
t: TranslatePipe,
|
||||||
|
api: Api,
|
||||||
|
mail_settings: EMailClientSettings,
|
||||||
|
mailer: EMailClientABC,
|
||||||
|
discord_service: DiscordService
|
||||||
|
):
|
||||||
|
self._config = config
|
||||||
|
self._env = env
|
||||||
|
self._logger = logger
|
||||||
|
self._t = t
|
||||||
|
self._api = api
|
||||||
|
self._mail_settings = mail_settings
|
||||||
|
self._mailer = mailer
|
||||||
|
self._discord_service = discord_service
|
||||||
|
|
||||||
|
@Route.get(f'{BasePath}/get/servers')
|
||||||
|
@Route.authorize(role=AuthRoleEnum.admin)
|
||||||
|
async def get_all_servers(self) -> Response:
|
||||||
|
result = await self._discord_service.get_all_servers()
|
||||||
|
result = result.select(lambda x: x.to_dict())
|
||||||
|
return jsonify(result)
|
||||||
|
|
||||||
|
@Route.get(f'{BasePath}/get/servers-by-user')
|
||||||
|
@Route.authorize
|
||||||
|
async def get_all_servers_by_user(self) -> Response:
|
||||||
|
result = await self._discord_service.get_all_servers_by_user()
|
||||||
|
result = result.select(lambda x: x.to_dict())
|
||||||
|
return jsonify(result)
|
||||||
|
|
||||||
|
@Route.post(f'{BasePath}/get/filtered')
|
||||||
|
@Route.authorize
|
||||||
|
async def get_filtered_servers(self) -> Response:
|
||||||
|
dto: ServerSelectCriteria = JSONProcessor.process(ServerSelectCriteria, request.get_json(force=True, silent=True))
|
||||||
|
result = await self._discord_service.get_filtered_servers_async(dto)
|
||||||
|
result.result = result.result.select(lambda x: x.to_dict())
|
||||||
|
return jsonify(result.to_dict())
|
||||||
|
|
||||||
|
@Route.get(f'{BasePath}/get/<id>')
|
||||||
|
@Route.authorize
|
||||||
|
async def get_server_by_id(self, id: int) -> Response:
|
||||||
|
result = await self._discord_service.get_server_by_id_async(id)
|
||||||
|
return jsonify(result.to_dict())
|
0
kdb-bot/src/bot_api/event/__init__.py
Normal file
0
kdb-bot/src/bot_api/event/__init__.py
Normal file
@ -21,3 +21,4 @@ class ServiceErrorCode(Enum):
|
|||||||
MailError = 10
|
MailError = 10
|
||||||
|
|
||||||
Unauthorized = 11
|
Unauthorized = 11
|
||||||
|
Forbidden = 12
|
||||||
|
0
kdb-bot/src/bot_api/filter/discord/__init__.py
Normal file
0
kdb-bot/src/bot_api/filter/discord/__init__.py
Normal file
17
kdb-bot/src/bot_api/filter/discord/server_select_criteria.py
Normal file
17
kdb-bot/src/bot_api/filter/discord/server_select_criteria.py
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
from bot_api.abc.select_criteria_abc import SelectCriteriaABC
|
||||||
|
|
||||||
|
|
||||||
|
class ServerSelectCriteria(SelectCriteriaABC):
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
page_index: int,
|
||||||
|
page_size: int,
|
||||||
|
sort_direction: str,
|
||||||
|
sort_column: str,
|
||||||
|
|
||||||
|
name: str,
|
||||||
|
):
|
||||||
|
SelectCriteriaABC.__init__(self, page_index, page_size, sort_direction, sort_column)
|
||||||
|
|
||||||
|
self.name = name
|
@ -15,6 +15,7 @@ class AuthUserDTO(DtoABC):
|
|||||||
password: str,
|
password: str,
|
||||||
confirmation_id: Optional[str],
|
confirmation_id: Optional[str],
|
||||||
auth_role: AuthRoleEnum,
|
auth_role: AuthRoleEnum,
|
||||||
|
user_id: Optional[int],
|
||||||
):
|
):
|
||||||
DtoABC.__init__(self)
|
DtoABC.__init__(self)
|
||||||
|
|
||||||
@ -25,6 +26,7 @@ class AuthUserDTO(DtoABC):
|
|||||||
self._password = password
|
self._password = password
|
||||||
self._is_confirmed = confirmation_id is None
|
self._is_confirmed = confirmation_id is None
|
||||||
self._auth_role = auth_role
|
self._auth_role = auth_role
|
||||||
|
self._user_id = user_id
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def id(self) -> int:
|
def id(self) -> int:
|
||||||
@ -78,6 +80,14 @@ class AuthUserDTO(DtoABC):
|
|||||||
def auth_role(self, value: AuthRoleEnum):
|
def auth_role(self, value: AuthRoleEnum):
|
||||||
self._auth_role = value
|
self._auth_role = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def user_id(self) -> Optional[int]:
|
||||||
|
return self._user_id
|
||||||
|
|
||||||
|
@user_id.setter
|
||||||
|
def user_id(self, value: Optional[int]):
|
||||||
|
self._user_id = value
|
||||||
|
|
||||||
def from_dict(self, values: dict):
|
def from_dict(self, values: dict):
|
||||||
self._id = values['id']
|
self._id = values['id']
|
||||||
self._first_name = values['firstName']
|
self._first_name = values['firstName']
|
||||||
@ -86,6 +96,7 @@ class AuthUserDTO(DtoABC):
|
|||||||
self._password = values['password']
|
self._password = values['password']
|
||||||
self._is_confirmed = values['isConfirmed']
|
self._is_confirmed = values['isConfirmed']
|
||||||
self._auth_role = values['authRole']
|
self._auth_role = values['authRole']
|
||||||
|
self._user_id = values['userId']
|
||||||
|
|
||||||
def to_dict(self) -> dict:
|
def to_dict(self) -> dict:
|
||||||
return {
|
return {
|
||||||
@ -96,4 +107,5 @@ class AuthUserDTO(DtoABC):
|
|||||||
'password': self._password,
|
'password': self._password,
|
||||||
'isConfirmed': self._is_confirmed,
|
'isConfirmed': self._is_confirmed,
|
||||||
'authRole': self._auth_role.value,
|
'authRole': self._auth_role.value,
|
||||||
|
'userId': self._user_id,
|
||||||
}
|
}
|
||||||
|
0
kdb-bot/src/bot_api/model/discord/__init__.py
Normal file
0
kdb-bot/src/bot_api/model/discord/__init__.py
Normal file
58
kdb-bot/src/bot_api/model/discord/server_dto.py
Normal file
58
kdb-bot/src/bot_api/model/discord/server_dto.py
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from bot_api.abc.dto_abc import DtoABC
|
||||||
|
|
||||||
|
|
||||||
|
class ServerDTO(DtoABC):
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
server_id: int,
|
||||||
|
discord_id: int,
|
||||||
|
name: str,
|
||||||
|
member_count: int,
|
||||||
|
icon_url: Optional[str]
|
||||||
|
|
||||||
|
):
|
||||||
|
DtoABC.__init__(self)
|
||||||
|
|
||||||
|
self._server_id = server_id
|
||||||
|
self._discord_id = discord_id
|
||||||
|
self._name = name
|
||||||
|
self._member_count = member_count
|
||||||
|
self._icon_url = icon_url
|
||||||
|
|
||||||
|
@property
|
||||||
|
def server_id(self) -> int:
|
||||||
|
return self._server_id
|
||||||
|
|
||||||
|
@property
|
||||||
|
def discord_id(self) -> int:
|
||||||
|
return self._discord_id
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self) -> str:
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def member_count(self) -> int:
|
||||||
|
return self._member_count
|
||||||
|
|
||||||
|
@property
|
||||||
|
def icon_url(self) -> Optional[str]:
|
||||||
|
return self._icon_url
|
||||||
|
|
||||||
|
def from_dict(self, values: dict):
|
||||||
|
self._server_id = int(values['serverId'])
|
||||||
|
self._discord_id = int(values['discordId'])
|
||||||
|
self._name = values['name']
|
||||||
|
self._icon_url = int(values['iconURL'])
|
||||||
|
|
||||||
|
def to_dict(self) -> dict:
|
||||||
|
return {
|
||||||
|
'serverId': self._server_id,
|
||||||
|
'discordId': self._discord_id,
|
||||||
|
'name': self._name,
|
||||||
|
'memberCount': self._member_count,
|
||||||
|
'iconURL': self._icon_url,
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
from cpl_query.extension import List
|
||||||
|
|
||||||
|
from bot_api.abc.dto_abc import DtoABC
|
||||||
|
from bot_data.filtered_result import FilteredResult
|
||||||
|
|
||||||
|
|
||||||
|
class ServerFilteredResultDTO(DtoABC, FilteredResult):
|
||||||
|
|
||||||
|
def __init__(self, result: List = None, total_count: int = 0):
|
||||||
|
DtoABC.__init__(self)
|
||||||
|
FilteredResult.__init__(self, result, total_count)
|
||||||
|
|
||||||
|
def from_dict(self, values: dict):
|
||||||
|
self._result = values['servers']
|
||||||
|
self._total_count = values['totalCount']
|
||||||
|
|
||||||
|
def to_dict(self) -> dict:
|
||||||
|
return {
|
||||||
|
'servers': self.result,
|
||||||
|
'totalCount': self.total_count
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
|
import functools
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
from typing import Optional
|
from typing import Optional, Callable
|
||||||
|
|
||||||
from flask import request, jsonify
|
from flask import request, jsonify
|
||||||
from flask_cors import cross_origin
|
from flask_cors import cross_origin
|
||||||
@ -9,6 +10,7 @@ from bot_api.exception.service_error_code_enum import ServiceErrorCode
|
|||||||
from bot_api.exception.service_exception import ServiceException
|
from bot_api.exception.service_exception import ServiceException
|
||||||
from bot_api.model.error_dto import ErrorDTO
|
from bot_api.model.error_dto import ErrorDTO
|
||||||
from bot_data.abc.auth_user_repository_abc import AuthUserRepositoryABC
|
from bot_data.abc.auth_user_repository_abc import AuthUserRepositoryABC
|
||||||
|
from bot_data.model.auth_role_enum import AuthRoleEnum
|
||||||
|
|
||||||
|
|
||||||
class Route:
|
class Route:
|
||||||
@ -23,7 +25,10 @@ class Route:
|
|||||||
cls._auth = auth
|
cls._auth = auth
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def authorize(cls, f):
|
def authorize(cls, f: Callable = None, role: AuthRoleEnum = None):
|
||||||
|
if f is None:
|
||||||
|
return functools.partial(cls.authorize, role=role)
|
||||||
|
|
||||||
@wraps(f)
|
@wraps(f)
|
||||||
async def decorator(*args, **kwargs):
|
async def decorator(*args, **kwargs):
|
||||||
token = None
|
token = None
|
||||||
@ -46,6 +51,23 @@ class Route:
|
|||||||
error = ErrorDTO(ex.error_code, ex.message)
|
error = ErrorDTO(ex.error_code, ex.message)
|
||||||
return jsonify(error.to_dict()), 401
|
return jsonify(error.to_dict()), 401
|
||||||
|
|
||||||
|
token = cls._auth.decode_token(token)
|
||||||
|
if token is None or 'email' not in token:
|
||||||
|
ex = ServiceException(ServiceErrorCode.Unauthorized, f'Token invalid')
|
||||||
|
error = ErrorDTO(ex.error_code, ex.message)
|
||||||
|
return jsonify(error.to_dict()), 401
|
||||||
|
|
||||||
|
user = cls._auth_users.get_auth_user_by_email(token['email'])
|
||||||
|
if user is None:
|
||||||
|
ex = ServiceException(ServiceErrorCode.Unauthorized, f'Token invalid')
|
||||||
|
error = ErrorDTO(ex.error_code, ex.message)
|
||||||
|
return jsonify(error.to_dict()), 401
|
||||||
|
|
||||||
|
if role is not None and user.auth_role.value < role.value:
|
||||||
|
ex = ServiceException(ServiceErrorCode.Unauthorized, f'Role {role} required')
|
||||||
|
error = ErrorDTO(ex.error_code, ex.message)
|
||||||
|
return jsonify(error.to_dict()), 403
|
||||||
|
|
||||||
return await f(*args, **kwargs)
|
return await f(*args, **kwargs)
|
||||||
|
|
||||||
return decorator
|
return decorator
|
||||||
|
@ -9,6 +9,7 @@ from cpl_core.database.context import DatabaseContextABC
|
|||||||
from cpl_core.mailing import EMailClientABC, EMail
|
from cpl_core.mailing import EMailClientABC, EMail
|
||||||
from cpl_query.extension import List
|
from cpl_query.extension import List
|
||||||
from cpl_translation import TranslatePipe
|
from cpl_translation import TranslatePipe
|
||||||
|
from flask import request
|
||||||
|
|
||||||
from bot_api.abc.auth_service_abc import AuthServiceABC
|
from bot_api.abc.auth_service_abc import AuthServiceABC
|
||||||
from bot_api.configuration.authentication_settings import AuthenticationSettings
|
from bot_api.configuration.authentication_settings import AuthenticationSettings
|
||||||
@ -27,6 +28,8 @@ from bot_api.transformer.auth_user_transformer import AuthUserTransformer as AUT
|
|||||||
from bot_data.abc.auth_user_repository_abc import AuthUserRepositoryABC
|
from bot_data.abc.auth_user_repository_abc import AuthUserRepositoryABC
|
||||||
from bot_data.model.auth_user import AuthUser
|
from bot_data.model.auth_user import AuthUser
|
||||||
|
|
||||||
|
_email_regex = r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b'
|
||||||
|
|
||||||
|
|
||||||
class AuthService(AuthServiceABC):
|
class AuthService(AuthServiceABC):
|
||||||
|
|
||||||
@ -65,7 +68,7 @@ class AuthService(AuthServiceABC):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _is_email_valid(email: str) -> bool:
|
def _is_email_valid(email: str) -> bool:
|
||||||
if re.match(re.compile(r'^[a-z0-9]+[\._]?[a-z0-9]+[@]\w+[.]\w{2,3}$'), email) is not None:
|
if re.fullmatch(_email_regex, email) is not None:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return False
|
return False
|
||||||
@ -94,6 +97,37 @@ class AuthService(AuthServiceABC):
|
|||||||
algorithms=['HS256']
|
algorithms=['HS256']
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def get_decoded_token_from_request(self) -> dict:
|
||||||
|
token = None
|
||||||
|
if 'Authorization' in request.headers:
|
||||||
|
bearer = request.headers.get('Authorization')
|
||||||
|
token = bearer.split()[1]
|
||||||
|
|
||||||
|
if token is None:
|
||||||
|
raise ServiceException(ServiceErrorCode.Unauthorized, f'Token not set')
|
||||||
|
|
||||||
|
return jwt.decode(
|
||||||
|
token,
|
||||||
|
key=self._auth_settings.secret_key,
|
||||||
|
issuer=self._auth_settings.issuer,
|
||||||
|
audience=self._auth_settings.audience,
|
||||||
|
algorithms=['HS256']
|
||||||
|
)
|
||||||
|
|
||||||
|
def find_decoded_token_from_request(self) -> Optional[dict]:
|
||||||
|
token = None
|
||||||
|
if 'Authorization' in request.headers:
|
||||||
|
bearer = request.headers.get('Authorization')
|
||||||
|
token = bearer.split()[1]
|
||||||
|
|
||||||
|
return jwt.decode(
|
||||||
|
token,
|
||||||
|
key=self._auth_settings.secret_key,
|
||||||
|
issuer=self._auth_settings.issuer,
|
||||||
|
audience=self._auth_settings.audience,
|
||||||
|
algorithms=['HS256']
|
||||||
|
) if token is not None else None
|
||||||
|
|
||||||
def _create_and_save_refresh_token(self, user: AuthUser) -> str:
|
def _create_and_save_refresh_token(self, user: AuthUser) -> str:
|
||||||
token = str(uuid.uuid4())
|
token = str(uuid.uuid4())
|
||||||
user.refresh_token = token
|
user.refresh_token = token
|
||||||
|
101
kdb-bot/src/bot_api/service/discord_service.py
Normal file
101
kdb-bot/src/bot_api/service/discord_service.py
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from cpl_discord.service import DiscordBotServiceABC
|
||||||
|
from cpl_query.extension import List
|
||||||
|
from flask import jsonify
|
||||||
|
|
||||||
|
from bot_api.abc.auth_service_abc import AuthServiceABC
|
||||||
|
from bot_api.exception.service_error_code_enum import ServiceErrorCode
|
||||||
|
from bot_api.exception.service_exception import ServiceException
|
||||||
|
from bot_api.filter.discord.server_select_criteria import ServerSelectCriteria
|
||||||
|
from bot_api.model.discord.server_dto import ServerDTO
|
||||||
|
from bot_api.model.discord.server_filtered_result_dto import ServerFilteredResultDTO
|
||||||
|
from bot_api.model.error_dto import ErrorDTO
|
||||||
|
from bot_api.transformer.server_transformer import ServerTransformer
|
||||||
|
from bot_data.abc.server_repository_abc import ServerRepositoryABC
|
||||||
|
from bot_data.abc.user_repository_abc import UserRepositoryABC
|
||||||
|
from bot_data.model.auth_role_enum import AuthRoleEnum
|
||||||
|
from bot_data.model.server import Server
|
||||||
|
|
||||||
|
|
||||||
|
class DiscordService:
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
bot: DiscordBotServiceABC,
|
||||||
|
servers: ServerRepositoryABC,
|
||||||
|
auth: AuthServiceABC,
|
||||||
|
users: UserRepositoryABC,
|
||||||
|
):
|
||||||
|
self._bot = bot
|
||||||
|
self._servers = servers
|
||||||
|
self._auth = auth
|
||||||
|
self._users = users
|
||||||
|
|
||||||
|
def _to_dto(self, x: Server) -> Optional[ServerDTO]:
|
||||||
|
guild = self._bot.get_guild(x.discord_server_id)
|
||||||
|
if guild is None:
|
||||||
|
return ServerTransformer.to_dto(
|
||||||
|
x,
|
||||||
|
'',
|
||||||
|
0,
|
||||||
|
None
|
||||||
|
)
|
||||||
|
|
||||||
|
return ServerTransformer.to_dto(
|
||||||
|
x,
|
||||||
|
guild.name,
|
||||||
|
guild.member_count,
|
||||||
|
guild.icon
|
||||||
|
)
|
||||||
|
|
||||||
|
async def get_all_servers(self) -> List[ServerDTO]:
|
||||||
|
servers = List(ServerDTO, self._servers.get_servers())
|
||||||
|
return servers.select(self._to_dto).where(lambda x: x.name != '')
|
||||||
|
|
||||||
|
async def get_all_servers_by_user(self) -> List[ServerDTO]:
|
||||||
|
token = self._auth.get_decoded_token_from_request()
|
||||||
|
if token is None or 'email' not in token or 'role' not in token:
|
||||||
|
raise ServiceException(ServiceErrorCode.InvalidData, 'Token invalid')
|
||||||
|
|
||||||
|
role = AuthRoleEnum(token['role'])
|
||||||
|
if role == AuthRoleEnum.admin:
|
||||||
|
servers = self._servers.get_servers()
|
||||||
|
else:
|
||||||
|
user = await self._auth.find_auth_user_by_email_async(token['email'])
|
||||||
|
user_from_db = self._users.find_user_by_id(0 if user.user_id is None else user.user_id)
|
||||||
|
servers = self._servers.get_servers().where(lambda x: user_from_db is not None and x.server_id == user_from_db.server.server_id)
|
||||||
|
|
||||||
|
servers = List(ServerDTO, servers)
|
||||||
|
return servers.select(self._to_dto).where(lambda x: x.name != '')
|
||||||
|
|
||||||
|
async def get_filtered_servers_async(self, criteria: ServerSelectCriteria) -> ServerFilteredResultDTO:
|
||||||
|
token = self._auth.get_decoded_token_from_request()
|
||||||
|
if token is None or 'email' not in token or 'role' not in token:
|
||||||
|
raise ServiceException(ServiceErrorCode.InvalidData, 'Token invalid')
|
||||||
|
|
||||||
|
role = AuthRoleEnum(token['role'])
|
||||||
|
filtered_result = self._servers.get_filtered_servers(criteria)
|
||||||
|
# filter out servers, where the user not exists
|
||||||
|
if role != AuthRoleEnum.admin:
|
||||||
|
user = await self._auth.find_auth_user_by_email_async(token['email'])
|
||||||
|
user_from_db = self._users.find_user_by_id(0 if user.user_id is None else user.user_id)
|
||||||
|
filtered_result.result = filtered_result.result.where(lambda x: user_from_db is not None and x.server_id == user_from_db.server.server_id)
|
||||||
|
|
||||||
|
servers: List = filtered_result.result.select(self._to_dto).where(lambda x: x.name != '')
|
||||||
|
result = List(ServerDTO, servers)
|
||||||
|
|
||||||
|
if criteria.name is not None and criteria.name != '':
|
||||||
|
result = result.where(lambda x: criteria.name.lower() in x.name.lower() or x.name.lower() == criteria.name.lower())
|
||||||
|
|
||||||
|
return ServerFilteredResultDTO(
|
||||||
|
List(ServerDTO, result),
|
||||||
|
servers.count()
|
||||||
|
)
|
||||||
|
|
||||||
|
async def get_server_by_id_async(self, id: int) -> ServerDTO:
|
||||||
|
server = self._servers.get_server_by_id(id)
|
||||||
|
guild = self._bot.get_guild(server.discord_server_id)
|
||||||
|
|
||||||
|
server_dto = ServerTransformer.to_dto(server, guild.name, guild.member_count, guild.icon)
|
||||||
|
return server_dto
|
@ -9,7 +9,7 @@ from bot_data.model.auth_user import AuthUser
|
|||||||
class AuthUserTransformer(TransformerABC):
|
class AuthUserTransformer(TransformerABC):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def to_db(dto: AuthUser) -> AuthUser:
|
def to_db(dto: AuthUserDTO) -> AuthUser:
|
||||||
return AuthUser(
|
return AuthUser(
|
||||||
dto.first_name,
|
dto.first_name,
|
||||||
dto.last_name,
|
dto.last_name,
|
||||||
@ -19,7 +19,8 @@ class AuthUserTransformer(TransformerABC):
|
|||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
datetime.now(tz=timezone.utc),
|
datetime.now(tz=timezone.utc),
|
||||||
AuthRoleEnum.normal if dto.auth_role is None else dto.auth_role,
|
AuthRoleEnum.normal if dto.auth_role is None else AuthRoleEnum(dto.auth_role),
|
||||||
|
dto.user_id,
|
||||||
id=0 if dto.id is None else dto.id
|
id=0 if dto.id is None else dto.id
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -32,5 +33,6 @@ class AuthUserTransformer(TransformerABC):
|
|||||||
db.email,
|
db.email,
|
||||||
db.password,
|
db.password,
|
||||||
db.confirmation_id,
|
db.confirmation_id,
|
||||||
db.auth_role
|
db.auth_role,
|
||||||
|
db.user_id
|
||||||
)
|
)
|
||||||
|
24
kdb-bot/src/bot_api/transformer/server_transformer.py
Normal file
24
kdb-bot/src/bot_api/transformer/server_transformer.py
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
import discord
|
||||||
|
|
||||||
|
from bot_api.abc.transformer_abc import TransformerABC
|
||||||
|
from bot_api.model.discord.server_dto import ServerDTO
|
||||||
|
from bot_data.model.server import Server
|
||||||
|
|
||||||
|
|
||||||
|
class ServerTransformer(TransformerABC):
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def to_db(dto: ServerDTO) -> Server:
|
||||||
|
return Server(dto.discord_id)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def to_dto(db: Server, name: str, member_count: int, icon_url: Optional[discord.Asset]) -> ServerDTO:
|
||||||
|
return ServerDTO(
|
||||||
|
db.server_id,
|
||||||
|
db.discord_server_id,
|
||||||
|
name,
|
||||||
|
member_count,
|
||||||
|
icon_url.url if icon_url is not None else None,
|
||||||
|
)
|
@ -3,6 +3,8 @@ from typing import Optional
|
|||||||
|
|
||||||
from cpl_query.extension import List
|
from cpl_query.extension import List
|
||||||
|
|
||||||
|
from bot_api.filter.discord.server_select_criteria import ServerSelectCriteria
|
||||||
|
from bot_data.filtered_result import FilteredResult
|
||||||
from bot_data.model.server import Server
|
from bot_data.model.server import Server
|
||||||
|
|
||||||
|
|
||||||
@ -13,6 +15,9 @@ class ServerRepositoryABC(ABC):
|
|||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def get_servers(self) -> List[Server]: pass
|
def get_servers(self) -> List[Server]: pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def get_filtered_servers(self, criteria: ServerSelectCriteria) -> FilteredResult: pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def get_server_by_id(self, id: int) -> Server: pass
|
def get_server_by_id(self, id: int) -> Server: pass
|
||||||
|
@ -16,6 +16,9 @@ class UserRepositoryABC(ABC):
|
|||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def get_user_by_id(self, id: int) -> User: pass
|
def get_user_by_id(self, id: int) -> User: pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def find_user_by_id(self, id: int) -> Optional[User]: pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def get_users_by_discord_id(self, discord_id: int) -> List[User]: pass
|
def get_users_by_discord_id(self, discord_id: int) -> List[User]: pass
|
||||||
|
@ -28,9 +28,11 @@ class ApiMigration(MigrationABC):
|
|||||||
`ForgotPasswordId` VARCHAR(255) DEFAULT NULL,
|
`ForgotPasswordId` VARCHAR(255) DEFAULT NULL,
|
||||||
`RefreshTokenExpiryTime` DATETIME(6) NOT NULL,
|
`RefreshTokenExpiryTime` DATETIME(6) NOT NULL,
|
||||||
`AuthRole` INT NOT NULL DEFAULT '0',
|
`AuthRole` INT NOT NULL DEFAULT '0',
|
||||||
|
`UserId` BIGINT NOT NULL DEFAULT '0',
|
||||||
`CreatedOn` DATETIME(6) NOT NULL,
|
`CreatedOn` DATETIME(6) NOT NULL,
|
||||||
`LastModifiedOn` DATETIME(6) NOT NULL,
|
`LastModifiedOn` DATETIME(6) NOT NULL,
|
||||||
PRIMARY KEY(`Id`)
|
PRIMARY KEY(`Id`),
|
||||||
|
FOREIGN KEY (`UserId`) REFERENCES `Users`(`UserId`)
|
||||||
)
|
)
|
||||||
""")
|
""")
|
||||||
)
|
)
|
||||||
|
@ -19,6 +19,7 @@ class AuthUser(TableABC):
|
|||||||
forgot_password_id: Optional[str],
|
forgot_password_id: Optional[str],
|
||||||
refresh_token_expire_time: datetime,
|
refresh_token_expire_time: datetime,
|
||||||
auth_role: AuthRoleEnum,
|
auth_role: AuthRoleEnum,
|
||||||
|
user_id: Optional[int],
|
||||||
created_at: datetime = None,
|
created_at: datetime = None,
|
||||||
modified_at: datetime = None,
|
modified_at: datetime = None,
|
||||||
id=0
|
id=0
|
||||||
@ -34,6 +35,7 @@ class AuthUser(TableABC):
|
|||||||
self._refresh_token_expire_time = refresh_token_expire_time
|
self._refresh_token_expire_time = refresh_token_expire_time
|
||||||
|
|
||||||
self._auth_role_id = auth_role
|
self._auth_role_id = auth_role
|
||||||
|
self._user_id = user_id
|
||||||
|
|
||||||
TableABC.__init__(self)
|
TableABC.__init__(self)
|
||||||
self._created_at = created_at if created_at is not None else self._created_at
|
self._created_at = created_at if created_at is not None else self._created_at
|
||||||
@ -115,6 +117,14 @@ class AuthUser(TableABC):
|
|||||||
def auth_role(self, value: AuthRoleEnum):
|
def auth_role(self, value: AuthRoleEnum):
|
||||||
self._auth_role_id = value
|
self._auth_role_id = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def user_id(self) -> Optional[int]:
|
||||||
|
return self._user_id
|
||||||
|
|
||||||
|
@user_id.setter
|
||||||
|
def user_id(self, value: Optional[int]):
|
||||||
|
self._user_id = value
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_select_all_string() -> str:
|
def get_select_all_string() -> str:
|
||||||
return str(f"""
|
return str(f"""
|
||||||
@ -163,6 +173,7 @@ class AuthUser(TableABC):
|
|||||||
`ForgotPasswordId`,
|
`ForgotPasswordId`,
|
||||||
`RefreshTokenExpiryTime`,
|
`RefreshTokenExpiryTime`,
|
||||||
`AuthRole`,
|
`AuthRole`,
|
||||||
|
`UserId`,
|
||||||
`CreatedOn`,
|
`CreatedOn`,
|
||||||
`LastModifiedOn`
|
`LastModifiedOn`
|
||||||
) VALUES (
|
) VALUES (
|
||||||
@ -176,6 +187,7 @@ class AuthUser(TableABC):
|
|||||||
'{"NULL" if self._forgot_password_id is None else self._forgot_password_id}',
|
'{"NULL" if self._forgot_password_id is None else self._forgot_password_id}',
|
||||||
'{self._refresh_token_expire_time}',
|
'{self._refresh_token_expire_time}',
|
||||||
{self._auth_role_id.value},
|
{self._auth_role_id.value},
|
||||||
|
{"NULL" if self._user_id is None else self._user_id}
|
||||||
'{self._created_at}',
|
'{self._created_at}',
|
||||||
'{self._modified_at}'
|
'{self._modified_at}'
|
||||||
)
|
)
|
||||||
@ -194,6 +206,7 @@ class AuthUser(TableABC):
|
|||||||
`ForgotPasswordId` = '{"NULL" if self._forgot_password_id is None else self._forgot_password_id}',
|
`ForgotPasswordId` = '{"NULL" if self._forgot_password_id is None else self._forgot_password_id}',
|
||||||
`RefreshTokenExpiryTime` = '{self._refresh_token_expire_time}',
|
`RefreshTokenExpiryTime` = '{self._refresh_token_expire_time}',
|
||||||
`AuthRole` = {self._auth_role_id.value},
|
`AuthRole` = {self._auth_role_id.value},
|
||||||
|
`UserId` = {"NULL" if self._user_id is None else self._user_id},
|
||||||
`LastModifiedOn` = '{self._modified_at}'
|
`LastModifiedOn` = '{self._modified_at}'
|
||||||
WHERE `AuthUsers`.`Id` = {self._auth_user_id};
|
WHERE `AuthUsers`.`Id` = {self._auth_user_id};
|
||||||
""")
|
""")
|
||||||
|
@ -37,6 +37,7 @@ class AuthUserRepositoryService(AuthUserRepositoryABC):
|
|||||||
self._get_value_from_result(result[7]),
|
self._get_value_from_result(result[7]),
|
||||||
self._get_value_from_result(result[8]),
|
self._get_value_from_result(result[8]),
|
||||||
AuthRoleEnum(self._get_value_from_result(result[9])),
|
AuthRoleEnum(self._get_value_from_result(result[9])),
|
||||||
|
self._get_value_from_result(result[10]),
|
||||||
id=self._get_value_from_result(result[0])
|
id=self._get_value_from_result(result[0])
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -3,8 +3,10 @@ from typing import Optional
|
|||||||
from cpl_core.database.context import DatabaseContextABC
|
from cpl_core.database.context import DatabaseContextABC
|
||||||
from cpl_query.extension import List
|
from cpl_query.extension import List
|
||||||
|
|
||||||
|
from bot_api.filter.discord.server_select_criteria import ServerSelectCriteria
|
||||||
from bot_core.logging.database_logger import DatabaseLogger
|
from bot_core.logging.database_logger import DatabaseLogger
|
||||||
from bot_data.abc.server_repository_abc import ServerRepositoryABC
|
from bot_data.abc.server_repository_abc import ServerRepositoryABC
|
||||||
|
from bot_data.filtered_result import FilteredResult
|
||||||
from bot_data.model.server import Server
|
from bot_data.model.server import Server
|
||||||
|
|
||||||
|
|
||||||
@ -28,6 +30,26 @@ class ServerRepositoryService(ServerRepositoryABC):
|
|||||||
|
|
||||||
return servers
|
return servers
|
||||||
|
|
||||||
|
def get_filtered_servers(self, criteria: ServerSelectCriteria) -> FilteredResult:
|
||||||
|
servers = self.get_servers()
|
||||||
|
self._logger.trace(__name__, f'Send SQL command: {Server.get_select_all_string()}')
|
||||||
|
query = servers
|
||||||
|
|
||||||
|
# sort
|
||||||
|
if criteria.sort_column is not None and criteria.sort_column != '' and criteria.sort_direction is not None and criteria.sort_direction:
|
||||||
|
crit_sort_direction = criteria.sort_direction.lower()
|
||||||
|
if crit_sort_direction == "desc" or crit_sort_direction == "descending":
|
||||||
|
query = query.order_by_descending(lambda x: getattr(x, criteria.sort_column))
|
||||||
|
else:
|
||||||
|
query = query.order_by(lambda x: getattr(x, criteria.sort_column))
|
||||||
|
|
||||||
|
result = FilteredResult()
|
||||||
|
result.total_count = query.count()
|
||||||
|
skip = criteria.page_size * criteria.page_index
|
||||||
|
result.result = query.skip(skip).take(criteria.page_size)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
def get_server_by_id(self, server_id: int) -> Server:
|
def get_server_by_id(self, server_id: int) -> Server:
|
||||||
self._logger.trace(__name__, f'Send SQL command: {Server.get_select_by_id_string(server_id)}')
|
self._logger.trace(__name__, f'Send SQL command: {Server.get_select_by_id_string(server_id)}')
|
||||||
result = self._context.select(Server.get_select_by_id_string(server_id))[0]
|
result = self._context.select(Server.get_select_by_id_string(server_id))[0]
|
||||||
|
@ -44,6 +44,21 @@ class UserRepositoryService(UserRepositoryABC):
|
|||||||
self._servers.get_server_by_id(result[3]),
|
self._servers.get_server_by_id(result[3]),
|
||||||
id=result[0]
|
id=result[0]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def find_user_by_id(self, id: int) -> Optional[User]:
|
||||||
|
self._logger.trace(__name__, f'Send SQL command: {User.get_select_by_id_string(id)}')
|
||||||
|
result = self._context.select(User.get_select_by_id_string(id))
|
||||||
|
if result is None or len(result) == 0:
|
||||||
|
return None
|
||||||
|
|
||||||
|
result = result[0]
|
||||||
|
|
||||||
|
return User(
|
||||||
|
result[1],
|
||||||
|
result[2],
|
||||||
|
self._servers.get_server_by_id(result[3]),
|
||||||
|
id=result[0]
|
||||||
|
)
|
||||||
|
|
||||||
def get_users_by_discord_id(self, discord_id: int) -> List[User]:
|
def get_users_by_discord_id(self, discord_id: int) -> List[User]:
|
||||||
users = List(User)
|
users = List(User)
|
||||||
|
4
kdb-web/.prettierrc
Normal file
4
kdb-web/.prettierrc
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"tabWidth": 4,
|
||||||
|
"useTabs": false
|
||||||
|
}
|
@ -7,6 +7,7 @@ import { AuthGuard } from './modules/shared/guards/auth/auth.guard';
|
|||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{ path: '', redirectTo: 'dashboard', pathMatch: 'full' },
|
{ path: '', redirectTo: 'dashboard', pathMatch: 'full' },
|
||||||
{ path: 'dashboard', loadChildren: () => import('./modules/view/dashboard/dashboard.module').then(m => m.DashboardModule), canActivate: [AuthGuard] },
|
{ path: 'dashboard', loadChildren: () => import('./modules/view/dashboard/dashboard.module').then(m => m.DashboardModule), canActivate: [AuthGuard] },
|
||||||
|
{ path: 'server', loadChildren: () => import('./modules/view/server/server.module').then(m => m.ServerModule), canActivate: [AuthGuard] },
|
||||||
{ path: 'change-password', loadChildren: () => import('./modules/view/change-password/change-password.module').then(m => m.ChangePasswordModule), canActivate: [AuthGuard] },
|
{ path: 'change-password', loadChildren: () => import('./modules/view/change-password/change-password.module').then(m => m.ChangePasswordModule), canActivate: [AuthGuard] },
|
||||||
{ path: 'user-settings', loadChildren: () => import('./modules/view/user-settings/user-settings.module').then(m => m.UserSettingsModule), canActivate: [AuthGuard] },
|
{ path: 'user-settings', loadChildren: () => import('./modules/view/user-settings/user-settings.module').then(m => m.UserSettingsModule), canActivate: [AuthGuard] },
|
||||||
{ path: 'auth', loadChildren: () => import('./modules/auth/auth.module').then(m => m.AuthModule) },
|
{ path: 'auth', loadChildren: () => import('./modules/auth/auth.module').then(m => m.AuthModule) },
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
<div class="menu">
|
<div class="menu">
|
||||||
<p-menu [model]="menuItems"></p-menu>
|
<p-panelMenu [model]="menuItems"></p-panelMenu>
|
||||||
</div>
|
</div>
|
@ -1,8 +1,10 @@
|
|||||||
import { Component, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core';
|
import { Component, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core';
|
||||||
|
import { ActivatedRoute } from '@angular/router';
|
||||||
import { LangChangeEvent, TranslateService } from '@ngx-translate/core';
|
import { LangChangeEvent, TranslateService } from '@ngx-translate/core';
|
||||||
import { MenuItem } from 'primeng/api';
|
import { MenuItem } from 'primeng/api';
|
||||||
import { AuthRoles } from 'src/app/models/auth/auth-roles.enum';
|
import { AuthRoles } from 'src/app/models/auth/auth-roles.enum';
|
||||||
import { AuthService } from 'src/app/services/auth/auth.service';
|
import { AuthService } from 'src/app/services/auth/auth.service';
|
||||||
|
import { ServerService } from 'src/app/services/data/server.service';
|
||||||
import { ThemeService } from 'src/app/services/theme/theme.service';
|
import { ThemeService } from 'src/app/services/theme/theme.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@ -13,13 +15,16 @@ import { ThemeService } from 'src/app/services/theme/theme.service';
|
|||||||
export class SidebarComponent implements OnInit {
|
export class SidebarComponent implements OnInit {
|
||||||
|
|
||||||
isSidebarOpen: boolean = true;
|
isSidebarOpen: boolean = true;
|
||||||
|
|
||||||
menuItems!: MenuItem[];
|
menuItems!: MenuItem[];
|
||||||
|
|
||||||
|
private serverId!: number;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private authService: AuthService,
|
private authService: AuthService,
|
||||||
private translateService: TranslateService,
|
private translateService: TranslateService,
|
||||||
private themeService: ThemeService
|
private themeService: ThemeService,
|
||||||
|
private route: ActivatedRoute,
|
||||||
|
private serverService: ServerService
|
||||||
) {
|
) {
|
||||||
this.themeService.isSidebarOpen$.subscribe(value => {
|
this.themeService.isSidebarOpen$.subscribe(value => {
|
||||||
this.isSidebarOpen = value;
|
this.isSidebarOpen = value;
|
||||||
@ -29,6 +34,15 @@ export class SidebarComponent implements OnInit {
|
|||||||
this.translateService.onLangChange.subscribe((event: LangChangeEvent) => {
|
this.translateService.onLangChange.subscribe((event: LangChangeEvent) => {
|
||||||
this.setMenu();
|
this.setMenu();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.serverService.server$.subscribe(server => {
|
||||||
|
if (!server) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.serverId = server.serverId;
|
||||||
|
this.setMenu();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
@ -39,20 +53,40 @@ export class SidebarComponent implements OnInit {
|
|||||||
this.authService.hasUserPermission(AuthRoles.Admin).then(hasPermission => {
|
this.authService.hasUserPermission(AuthRoles.Admin).then(hasPermission => {
|
||||||
this.menuItems = [];
|
this.menuItems = [];
|
||||||
this.menuItems = [
|
this.menuItems = [
|
||||||
{ label: this.isSidebarOpen ? this.translateService.instant('sidebar.dashboard') : '', icon: 'pi pi-th-large', routerLink: 'dashboard' },
|
{ label: this.isSidebarOpen ? this.translateService.instant('sidebar.dashboard') : '', icon: 'pi pi-th-large', routerLink: 'dashboard' }
|
||||||
];
|
];
|
||||||
|
|
||||||
if (!hasPermission) {
|
if (this.serverId) {
|
||||||
return;
|
this.addServerMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.menuItems.push(
|
if (hasPermission) {
|
||||||
{ separator: true },
|
this.addAdminMenu();
|
||||||
{ label: this.isSidebarOpen ? this.translateService.instant('sidebar.config') : '', icon: 'pi pi-cog', routerLink: '/admin/settings' },
|
}
|
||||||
{ label: this.isSidebarOpen ? this.translateService.instant('sidebar.auth_user_list') : '', icon: 'pi pi-user-edit', routerLink: '/admin/users' },
|
|
||||||
);
|
|
||||||
this.menuItems = this.menuItems.slice();
|
this.menuItems = this.menuItems.slice();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addServerMenu() {
|
||||||
|
this.menuItems.push(
|
||||||
|
{
|
||||||
|
label: this.isSidebarOpen ? this.translateService.instant('sidebar.server') : '', icon: 'pi pi-server', items: [
|
||||||
|
{ label: this.isSidebarOpen ? this.translateService.instant('sidebar.settings') : '', icon: 'pi pi-cog', routerLink: 'server/settings' },
|
||||||
|
{ label: this.isSidebarOpen ? this.translateService.instant('sidebar.members') : '', icon: 'pi pi-users', routerLink: 'server/members' },
|
||||||
|
]
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
addAdminMenu() {
|
||||||
|
this.menuItems.push(
|
||||||
|
{
|
||||||
|
label: this.isSidebarOpen ? this.translateService.instant('sidebar.administration') : '', icon: 'pi pi-cog', items: [
|
||||||
|
{ label: this.isSidebarOpen ? this.translateService.instant('sidebar.config') : '', icon: 'pi pi-cog', routerLink: '/admin/settings' },
|
||||||
|
{ label: this.isSidebarOpen ? this.translateService.instant('sidebar.auth_user_list') : '', icon: 'pi pi-user-edit', routerLink: '/admin/users' },
|
||||||
|
]
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
7
kdb-web/src/app/models/discord/server.dto.ts
Normal file
7
kdb-web/src/app/models/discord/server.dto.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export interface ServerDTO {
|
||||||
|
serverId: number;
|
||||||
|
discordId: number;
|
||||||
|
name: string;
|
||||||
|
memberCount: number;
|
||||||
|
iconURL: string | null;
|
||||||
|
}
|
@ -1,8 +0,0 @@
|
|||||||
import { SelectCriterion } from "../select-criterion.model";
|
|
||||||
|
|
||||||
export interface LoginSelectCriterion extends SelectCriterion {
|
|
||||||
timeFrom: string;
|
|
||||||
timeTo: string;
|
|
||||||
userName: string;
|
|
||||||
hostName: string;
|
|
||||||
}
|
|
@ -0,0 +1,7 @@
|
|||||||
|
import { AuthUserDTO } from "../../auth/auth-user.dto";
|
||||||
|
import { ServerDTO } from "../../discord/server.dto";
|
||||||
|
|
||||||
|
export interface GetFilteredServersResultDTO {
|
||||||
|
servers: ServerDTO[];
|
||||||
|
totalCount: number;
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
import { SelectCriterion } from "../select-criterion.model";
|
||||||
|
|
||||||
|
export interface ServerSelectCriterion extends SelectCriterion {
|
||||||
|
name: string | null;
|
||||||
|
}
|
@ -85,7 +85,7 @@ export class AuthUserComponent implements OnInit {
|
|||||||
};
|
};
|
||||||
|
|
||||||
this.setFilterForm();
|
this.setFilterForm();
|
||||||
// this.loadNextPage();
|
this.loadNextPage();
|
||||||
}
|
}
|
||||||
|
|
||||||
setFilterForm() {
|
setFilterForm() {
|
||||||
|
@ -18,6 +18,7 @@ import { ToastModule } from 'primeng/toast';
|
|||||||
import { AuthRolePipe } from './pipes/auth-role.pipe';
|
import { AuthRolePipe } from './pipes/auth-role.pipe';
|
||||||
import { IpAddressPipe } from './pipes/ip-address.pipe';
|
import { IpAddressPipe } from './pipes/ip-address.pipe';
|
||||||
import { BoolPipe } from './pipes/bool.pipe';
|
import { BoolPipe } from './pipes/bool.pipe';
|
||||||
|
import { PanelMenuModule } from 'primeng/panelmenu';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -44,7 +45,8 @@ import { BoolPipe } from './pipes/bool.pipe';
|
|||||||
CheckboxModule,
|
CheckboxModule,
|
||||||
DropdownModule,
|
DropdownModule,
|
||||||
TranslateModule,
|
TranslateModule,
|
||||||
DynamicDialogModule
|
DynamicDialogModule,
|
||||||
|
PanelMenuModule,
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
ButtonModule,
|
ButtonModule,
|
||||||
@ -63,6 +65,7 @@ import { BoolPipe } from './pipes/bool.pipe';
|
|||||||
DropdownModule,
|
DropdownModule,
|
||||||
TranslateModule,
|
TranslateModule,
|
||||||
DynamicDialogModule,
|
DynamicDialogModule,
|
||||||
|
PanelMenuModule,
|
||||||
AuthRolePipe,
|
AuthRolePipe,
|
||||||
IpAddressPipe,
|
IpAddressPipe,
|
||||||
BoolPipe,
|
BoolPipe,
|
||||||
|
@ -1,3 +1,50 @@
|
|||||||
<p>dashboard works!</p>
|
<h1>
|
||||||
|
{{'view.dashboard.header' | translate}}
|
||||||
|
</h1>
|
||||||
|
<div class="content-wrapper">
|
||||||
|
<div class="content-header">
|
||||||
|
<h2>
|
||||||
|
<i class="pi pi-server"></i>
|
||||||
|
{{'view.dashboard.server.header' | translate}}
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="content"></div>
|
<div class="content">
|
||||||
|
<div class="server-list-wrapper">
|
||||||
|
<div class="server-filter">
|
||||||
|
<form [formGroup]="filterForm">
|
||||||
|
<div class="input-field">
|
||||||
|
<input type="text" pInputText formControlName="name"
|
||||||
|
placeholder="{{'view.dashboard.filter.name' | translate}}" autocomplete="given-name">
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="server-count">
|
||||||
|
{{servers.length}} {{'view.dashboard.of' | translate}} {{totalRecords}} {{'view.dashboard.servers' | translate}}:
|
||||||
|
<hr>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="server-list">
|
||||||
|
<div class="server" *ngFor="let server of servers" (click)="selectServer(server)">
|
||||||
|
<div class="logo">
|
||||||
|
<img *ngIf="server.iconURL" [src]="server.iconURL">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="info">
|
||||||
|
<h3 class="name">
|
||||||
|
{{server.name}}
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<div class="data">
|
||||||
|
<i class="pi pi-users"></i>
|
||||||
|
{{server.memberCount}}
|
||||||
|
{{'view.dashboard.server.member_count' | translate}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -1,4 +1,16 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { FormBuilder, FormControl, FormGroup } from '@angular/forms';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
import { LazyLoadEvent } from 'primeng/api';
|
||||||
|
import { catchError, debounceTime, throwError } from 'rxjs';
|
||||||
|
import { ServerDTO } from 'src/app/models/discord/server.dto';
|
||||||
|
import { ServerSelectCriterion } from 'src/app/models/selection/server/server-select-criterion.dto';
|
||||||
|
import { ConfirmationDialogService } from 'src/app/services/confirmation-dialog/confirmation-dialog.service';
|
||||||
|
import { DataService } from 'src/app/services/data/data.service';
|
||||||
|
import { ServerService } from 'src/app/services/data/server.service';
|
||||||
|
import { SpinnerService } from 'src/app/services/spinner/spinner.service';
|
||||||
|
import { ToastService } from 'src/app/services/toast/toast.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-dashboard',
|
selector: 'app-dashboard',
|
||||||
@ -7,8 +19,97 @@ import { Component, OnInit } from '@angular/core';
|
|||||||
})
|
})
|
||||||
export class DashboardComponent implements OnInit {
|
export class DashboardComponent implements OnInit {
|
||||||
|
|
||||||
constructor() { }
|
servers: ServerDTO[] = [];
|
||||||
|
|
||||||
ngOnInit(): void {}
|
searchCriterions: ServerSelectCriterion = {
|
||||||
|
name: null,
|
||||||
|
pageIndex: 0,
|
||||||
|
pageSize: 10,
|
||||||
|
sortColumn: null,
|
||||||
|
sortDirection: null
|
||||||
|
};
|
||||||
|
totalRecords!: number;
|
||||||
|
|
||||||
|
|
||||||
|
filterForm!: FormGroup<{
|
||||||
|
name: FormControl<string | null>,
|
||||||
|
}>;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private data: DataService,
|
||||||
|
private spinnerService: SpinnerService,
|
||||||
|
private toastService: ToastService,
|
||||||
|
private confirmDialog: ConfirmationDialogService,
|
||||||
|
private fb: FormBuilder,
|
||||||
|
private translate: TranslateService,
|
||||||
|
private router: Router,
|
||||||
|
private serverService: ServerService
|
||||||
|
) { }
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.spinnerService.showSpinner();
|
||||||
|
this.setFilterForm();
|
||||||
|
this.loadNextPage();
|
||||||
|
}
|
||||||
|
|
||||||
|
setFilterForm() {
|
||||||
|
this.filterForm = this.fb.group({
|
||||||
|
name: [''],
|
||||||
|
});
|
||||||
|
|
||||||
|
this.filterForm.valueChanges.pipe(
|
||||||
|
debounceTime(600)
|
||||||
|
).subscribe(changes => {
|
||||||
|
if (changes.name) {
|
||||||
|
this.searchCriterions.name = changes.name;
|
||||||
|
} else {
|
||||||
|
this.searchCriterions.name = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.searchCriterions.pageSize)
|
||||||
|
this.searchCriterions.pageSize = 10;
|
||||||
|
|
||||||
|
if (this.searchCriterions.pageSize)
|
||||||
|
this.searchCriterions.pageIndex = 0;
|
||||||
|
|
||||||
|
this.loadNextPage();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
loadNextPage() {
|
||||||
|
this.spinnerService.showSpinner();
|
||||||
|
this.data.getFilteredServers(this.searchCriterions).pipe(catchError(err => {
|
||||||
|
this.spinnerService.hideSpinner();
|
||||||
|
return throwError(() => err);
|
||||||
|
})).subscribe(list => {
|
||||||
|
this.totalRecords = list.totalCount;
|
||||||
|
this.servers = list.servers;
|
||||||
|
this.spinnerService.hideSpinner();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
nextPage(event: LazyLoadEvent) {
|
||||||
|
this.searchCriterions.pageSize = event.rows ?? 0;
|
||||||
|
if (event.first != null && event.rows != null)
|
||||||
|
this.searchCriterions.pageIndex = event.first / event.rows;
|
||||||
|
this.searchCriterions.sortColumn = event.sortField ?? null;
|
||||||
|
this.searchCriterions.sortDirection = event.sortOrder === 1 ? 'asc' : event.sortOrder === -1 ? 'desc' : 'asc';
|
||||||
|
|
||||||
|
if (event.filters) {
|
||||||
|
// + "" => convert to string
|
||||||
|
this.searchCriterions.name = event.filters['name'] ? event.filters['name'] + "" : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.loadNextPage();
|
||||||
|
}
|
||||||
|
|
||||||
|
resetFilters() {
|
||||||
|
this.filterForm.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
selectServer(server: ServerDTO) {
|
||||||
|
this.serverService.server$.next(server);
|
||||||
|
this.router.navigate(['/server']);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ import { RouterModule, Routes } from '@angular/router';
|
|||||||
import { DashboardComponent } from './components/dashboard/dashboard.component';
|
import { DashboardComponent } from './components/dashboard/dashboard.component';
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{path: '', component: DashboardComponent}
|
{ path: '', component: DashboardComponent }
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { NgModule } from '@angular/core';
|
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
|
||||||
import { DashboardRoutingModule } from './dashboard-routing.module';
|
|
||||||
import { DashboardComponent } from './components/dashboard/dashboard.component';
|
|
||||||
import { SharedModule } from '../../shared/shared.module';
|
import { SharedModule } from '../../shared/shared.module';
|
||||||
|
import { DashboardComponent } from './components/dashboard/dashboard.component';
|
||||||
|
import { DashboardRoutingModule } from './dashboard-routing.module';
|
||||||
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
|
@ -0,0 +1,36 @@
|
|||||||
|
<h1>
|
||||||
|
{{'view.dashboard.header' | translate}}
|
||||||
|
</h1>
|
||||||
|
<div class="content-wrapper">
|
||||||
|
<div class="content-header">
|
||||||
|
<h2>
|
||||||
|
<i class="pi pi-server"></i>
|
||||||
|
{{'view.dashboard.server.header' | translate}}
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="content">
|
||||||
|
<div class="server-list-wrapper">
|
||||||
|
<div class="server-list">
|
||||||
|
<div class="server">
|
||||||
|
<div class="logo">
|
||||||
|
<img *ngIf="server.iconURL" [src]="server.iconURL">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="info">
|
||||||
|
<h3 class="name">
|
||||||
|
{{server.name}}
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<div class="data">
|
||||||
|
<i class="pi pi-users"></i>
|
||||||
|
{{server.memberCount}}
|
||||||
|
{{'view.dashboard.server.member_count' | translate}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { ServerDashboardComponent } from './server-dashboard.component';
|
||||||
|
|
||||||
|
describe('ServerDashboardComponent', () => {
|
||||||
|
let component: ServerDashboardComponent;
|
||||||
|
let fixture: ComponentFixture<ServerDashboardComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [ ServerDashboardComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(ServerDashboardComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,38 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
import { ServerDTO } from 'src/app/models/discord/server.dto';
|
||||||
|
import { DataService } from 'src/app/services/data/data.service';
|
||||||
|
import { ServerService } from 'src/app/services/data/server.service';
|
||||||
|
import { SpinnerService } from 'src/app/services/spinner/spinner.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-server-dashboard',
|
||||||
|
templateUrl: './server-dashboard.component.html',
|
||||||
|
styleUrls: ['./server-dashboard.component.scss']
|
||||||
|
})
|
||||||
|
export class ServerDashboardComponent implements OnInit {
|
||||||
|
|
||||||
|
id!: number;
|
||||||
|
server!: ServerDTO;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private route: ActivatedRoute,
|
||||||
|
private router: Router,
|
||||||
|
private data: DataService,
|
||||||
|
private spinner: SpinnerService,
|
||||||
|
private serverService: ServerService
|
||||||
|
) { }
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.spinner.showSpinner();
|
||||||
|
if (!this.serverService.server$.value) {
|
||||||
|
this.spinner.hideSpinner();
|
||||||
|
this.router.navigate(['/dashboard']);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.server = this.serverService.server$.value;
|
||||||
|
this.spinner.hideSpinner();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
13
kdb-web/src/app/modules/view/server/server-routing.module.ts
Normal file
13
kdb-web/src/app/modules/view/server/server-routing.module.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { RouterModule, Routes } from '@angular/router';
|
||||||
|
import { ServerDashboardComponent } from './server-dashboard/server-dashboard.component';
|
||||||
|
|
||||||
|
const routes: Routes = [
|
||||||
|
{ path: '', component: ServerDashboardComponent }
|
||||||
|
];
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [RouterModule.forChild(routes)],
|
||||||
|
exports: [RouterModule]
|
||||||
|
})
|
||||||
|
export class ServerRoutingModule { }
|
19
kdb-web/src/app/modules/view/server/server.module.ts
Normal file
19
kdb-web/src/app/modules/view/server/server.module.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { ServerDashboardComponent } from './server-dashboard/server-dashboard.component';
|
||||||
|
import { ServerRoutingModule } from './server-routing.module';
|
||||||
|
import { SharedModule } from '../../shared/shared.module';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [
|
||||||
|
ServerDashboardComponent
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
ServerRoutingModule,
|
||||||
|
SharedModule
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class ServerModule { }
|
@ -1,5 +1,9 @@
|
|||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient, HttpHeaders } from '@angular/common/http';
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { ServerDTO } from 'src/app/models/discord/server.dto';
|
||||||
|
import { GetFilteredServersResultDTO } from 'src/app/models/selection/server/get-filtered-servers-result.dto';
|
||||||
|
import { ServerSelectCriterion } from 'src/app/models/selection/server/server-select-criterion.dto';
|
||||||
import { SettingsService } from '../settings/settings.service';
|
import { SettingsService } from '../settings/settings.service';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
@ -11,4 +15,39 @@ export class DataService {
|
|||||||
private appsettings: SettingsService,
|
private appsettings: SettingsService,
|
||||||
private http: HttpClient,
|
private http: HttpClient,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* data requests */
|
||||||
|
getAllServers(): Observable<Array<ServerDTO>> {
|
||||||
|
return this.http.get<Array<ServerDTO>>(`${this.appsettings.getApiURL()}/api/discord/server/servers`, {
|
||||||
|
headers: new HttpHeaders({
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getAllServersByUser(): Observable<Array<ServerDTO>> {
|
||||||
|
return this.http.get<Array<ServerDTO>>(`${this.appsettings.getApiURL()}/api/discord/server/servers-by-user`, {
|
||||||
|
headers: new HttpHeaders({
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getFilteredServers(selectCriterions: ServerSelectCriterion): Observable<GetFilteredServersResultDTO> {
|
||||||
|
return this.http.post<GetFilteredServersResultDTO>(`${this.appsettings.getApiURL()}/api/discord/server/get/filtered`, selectCriterions, {
|
||||||
|
headers: new HttpHeaders({
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getServerByID(id: number): Observable<ServerDTO> {
|
||||||
|
return this.http.get<ServerDTO>(`${this.appsettings.getApiURL()}/api/discord/server/get/${id}`, {
|
||||||
|
headers: new HttpHeaders({
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
16
kdb-web/src/app/services/data/server.service.spec.ts
Normal file
16
kdb-web/src/app/services/data/server.service.spec.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { ServerService } from './server.service';
|
||||||
|
|
||||||
|
describe('ServerService', () => {
|
||||||
|
let service: ServerService;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({});
|
||||||
|
service = TestBed.inject(ServerService);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be created', () => {
|
||||||
|
expect(service).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
23
kdb-web/src/app/services/data/server.service.ts
Normal file
23
kdb-web/src/app/services/data/server.service.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { BehaviorSubject, Subject } from 'rxjs';
|
||||||
|
import { ServerDTO } from 'src/app/models/discord/server.dto';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class ServerService {
|
||||||
|
|
||||||
|
private server!: ServerDTO;
|
||||||
|
server$ = new BehaviorSubject<ServerDTO | null>(null);
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.server$.subscribe(server => {
|
||||||
|
if (!server) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.server = server;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -27,7 +27,7 @@ export class ThemeService {
|
|||||||
});
|
});
|
||||||
this.isSidebarOpen$.subscribe(isSidebarOpen => {
|
this.isSidebarOpen$.subscribe(isSidebarOpen => {
|
||||||
this.isSidebarOpen = isSidebarOpen;
|
this.isSidebarOpen = isSidebarOpen;
|
||||||
this.sidebarWidth$.next(isSidebarOpen ? '150px' : '50px');
|
this.sidebarWidth$.next(isSidebarOpen ? '175px' : '75px');
|
||||||
});
|
});
|
||||||
this.sidebarWidth$.subscribe(sidebarWidth => {
|
this.sidebarWidth$.subscribe(sidebarWidth => {
|
||||||
this.sidebarWidth = sidebarWidth;
|
this.sidebarWidth = sidebarWidth;
|
||||||
|
@ -7,10 +7,11 @@
|
|||||||
},
|
},
|
||||||
"sidebar": {
|
"sidebar": {
|
||||||
"dashboard": "Dashboard",
|
"dashboard": "Dashboard",
|
||||||
"domain_list": "Domänen",
|
"server": "Server",
|
||||||
"host_list": "Rechner",
|
"server_empty": "Kein Server ausgewählt",
|
||||||
"user_list": "Benutzer",
|
"settings": "Einstellungen",
|
||||||
"login_list": "Logins",
|
"members": "Mitglieder",
|
||||||
|
"administration": "Administration",
|
||||||
"config": "Konfiguration",
|
"config": "Konfiguration",
|
||||||
"auth_user_list": "Benutzer"
|
"auth_user_list": "Benutzer"
|
||||||
},
|
},
|
||||||
@ -106,7 +107,21 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"view": {
|
"view": {
|
||||||
"dashboard": {},
|
"dashboard": {
|
||||||
|
"header": "Dashboard",
|
||||||
|
"of": "von",
|
||||||
|
"servers": "Server",
|
||||||
|
"server": {
|
||||||
|
"header": "Server",
|
||||||
|
"member_count": "Mitglid(er)"
|
||||||
|
},
|
||||||
|
"filter": {
|
||||||
|
"name": "Name"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"server": {
|
||||||
|
"header": "Server"
|
||||||
|
},
|
||||||
"user-list": {},
|
"user-list": {},
|
||||||
"change-password": {
|
"change-password": {
|
||||||
"header": "Passwort ändern",
|
"header": "Passwort ändern",
|
||||||
@ -200,24 +215,8 @@
|
|||||||
"Freitag",
|
"Freitag",
|
||||||
"Samstag"
|
"Samstag"
|
||||||
],
|
],
|
||||||
"dayNamesShort": [
|
"dayNamesShort": ["Son", "Mon", "Die", "Mit", "Don", "Fre", "Sam"],
|
||||||
"Son",
|
"dayNamesMin": ["So", "Mo", "Di", "Mi", "Do", "Fr", "Sa"],
|
||||||
"Mon",
|
|
||||||
"Die",
|
|
||||||
"Mit",
|
|
||||||
"Don",
|
|
||||||
"Fre",
|
|
||||||
"Sam"
|
|
||||||
],
|
|
||||||
"dayNamesMin": [
|
|
||||||
"So",
|
|
||||||
"Mo",
|
|
||||||
"Di",
|
|
||||||
"Mi",
|
|
||||||
"Do",
|
|
||||||
"Fr",
|
|
||||||
"Sa"
|
|
||||||
],
|
|
||||||
"monthNames": [
|
"monthNames": [
|
||||||
"Januar",
|
"Januar",
|
||||||
"Februar",
|
"Februar",
|
||||||
@ -255,4 +254,4 @@
|
|||||||
"emptyMessage": "Keine Ergebnisse gefunden",
|
"emptyMessage": "Keine Ergebnisse gefunden",
|
||||||
"emptyFilterMessage": "Keine Ergebnisse gefunden"
|
"emptyFilterMessage": "Keine Ergebnisse gefunden"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,8 @@ body {
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
|
||||||
|
font-size: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
main {
|
main {
|
||||||
@ -18,15 +20,19 @@ main {
|
|||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1,
|
h1 {
|
||||||
h2 {
|
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 30px;
|
font-size: 1.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 25px;
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
header {
|
header {
|
||||||
@ -207,6 +213,59 @@ header {
|
|||||||
.table-header-small-dropdown {
|
.table-header-small-dropdown {
|
||||||
width: 150px;
|
width: 150px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.server-list-wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
|
||||||
|
.server-filter {
|
||||||
|
}
|
||||||
|
|
||||||
|
.server-count {
|
||||||
|
}
|
||||||
|
|
||||||
|
.server-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
gap: 15px;
|
||||||
|
|
||||||
|
.server {
|
||||||
|
display: flex;
|
||||||
|
gap: 15px;
|
||||||
|
|
||||||
|
padding: 20px;
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 4rem;
|
||||||
|
height: 4rem;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
gap: 10px;
|
||||||
|
|
||||||
|
.name {
|
||||||
|
margin: 0px;
|
||||||
|
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,14 +6,17 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.p-menu {
|
.p-menu,
|
||||||
|
.p-panelmenu {
|
||||||
background: none !important;
|
background: none !important;
|
||||||
border: none !important;
|
border: none !important;
|
||||||
width: auto !important;
|
width: auto !important;
|
||||||
border-radius: 0px !important;
|
border-radius: 0px !important;
|
||||||
padding: 0 !important;
|
padding: 0 !important;
|
||||||
|
|
||||||
.p-menuitem-link {
|
.p-menuitem-link,
|
||||||
|
.p-panelmenu-header > a,
|
||||||
|
.p-panelmenu-content .p-menuitem .p-menuitem-link {
|
||||||
$distance: 10px;
|
$distance: 10px;
|
||||||
padding: $distance 0px $distance $distance !important;
|
padding: $distance 0px $distance $distance !important;
|
||||||
margin: 4px 0px 4px 6px !important;
|
margin: 4px 0px 4px 6px !important;
|
||||||
@ -24,6 +27,31 @@
|
|||||||
top: $headerHeight !important;
|
top: $headerHeight !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.p-panelmenu {
|
||||||
|
.p-panelmenu-icon {
|
||||||
|
order: 1; // to be the first item on right side.
|
||||||
|
}
|
||||||
|
.p-menuitem-text {
|
||||||
|
flex-grow: 1; // to fill the whole space and push the icon to the end
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-panelmenu-header > a {
|
||||||
|
border: none !important;
|
||||||
|
border-radius: none !important;
|
||||||
|
font-weight: none !important;
|
||||||
|
transition: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-panelmenu-content {
|
||||||
|
border: none !important;
|
||||||
|
background: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-menuitem-text {
|
||||||
|
line-height: normal !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ui-menu .ui-menu-parent .ui-menu-child {
|
ui-menu .ui-menu-parent .ui-menu-child {
|
||||||
width: 400px; /* exagerated !! */
|
width: 400px; /* exagerated !! */
|
||||||
}
|
}
|
||||||
|
@ -15,16 +15,9 @@
|
|||||||
$primaryErrorColor: #b00020;
|
$primaryErrorColor: #b00020;
|
||||||
$secondaryErrorColor: #e94948;
|
$secondaryErrorColor: #e94948;
|
||||||
|
|
||||||
$default-border: 1px solid $secondaryBackgroundColor3;
|
$default-border: 2px solid $secondaryBackgroundColor3;
|
||||||
|
|
||||||
background-color: $primaryBackgroundColor !important;
|
background-color: $primaryBackgroundColor;
|
||||||
|
|
||||||
html,
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1,
|
h1,
|
||||||
h2 {
|
h2 {
|
||||||
@ -122,6 +115,35 @@
|
|||||||
.content-divider {
|
.content-divider {
|
||||||
border-bottom: $default-border;
|
border-bottom: $default-border;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.server-list-wrapper {
|
||||||
|
.server-filter {
|
||||||
|
}
|
||||||
|
|
||||||
|
.server-count {
|
||||||
|
}
|
||||||
|
|
||||||
|
.server-list {
|
||||||
|
.server {
|
||||||
|
border: $default-border;
|
||||||
|
border-radius: 15px;
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
img {
|
||||||
|
border-radius: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.name {
|
||||||
|
color: $primaryHeaderColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: $primaryHeaderColor !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -228,28 +250,43 @@
|
|||||||
stroke: $primaryHeaderColor !important;
|
stroke: $primaryHeaderColor !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.p-menu {
|
.p-menu,
|
||||||
|
.p-panelmenu {
|
||||||
color: $primaryTextColor !important;
|
color: $primaryTextColor !important;
|
||||||
|
|
||||||
.p-menuitem-link .p-menuitem-text,
|
.p-menuitem-link .p-menuitem-text,
|
||||||
.p-menuitem-link .p-menuitem-icon {
|
.p-menuitem-link .p-menuitem-icon,
|
||||||
|
.p-panelmenu-header > a {
|
||||||
color: $primaryTextColor !important;
|
color: $primaryTextColor !important;
|
||||||
|
background: transparent !important;
|
||||||
|
font-size: 1rem !important;
|
||||||
|
font-weight: normal !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.p-menuitem-link:focus {
|
.p-menuitem-link:focus,
|
||||||
|
.p-panelmenu-header > a:focus,
|
||||||
|
.p-panelmenu-content .p-menuitem .p-menuitem-link:focus {
|
||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.p-menuitem-link:hover {
|
.p-menuitem-link:hover,
|
||||||
|
.p-panelmenu-header > a:hover,
|
||||||
|
.p-panelmenu-content .p-menuitem .p-menuitem-link:hover {
|
||||||
background-color: $secondaryBackgroundColor !important;
|
background-color: $secondaryBackgroundColor !important;
|
||||||
$border-radius: 20px;
|
$border-radius: 20px;
|
||||||
border-radius: $border-radius 0px 0px $border-radius;
|
border-radius: $border-radius 0px 0px $border-radius;
|
||||||
|
|
||||||
.p-menuitem-text,
|
.p-menuitem-text,
|
||||||
.p-menuitem-icon {
|
.p-menuitem-icon,
|
||||||
|
.p-menuitem-text,
|
||||||
|
.p-panelmenu-icon {
|
||||||
color: $primaryHeaderColor !important;
|
color: $primaryHeaderColor !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.p-panelmenu-content {
|
||||||
|
margin: 5px 0px 5px 10px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.p-menu-overlay {
|
.p-menu-overlay {
|
||||||
@ -388,6 +425,9 @@
|
|||||||
|
|
||||||
input,
|
input,
|
||||||
.p-password {
|
.p-password {
|
||||||
|
border-radius: 10px;
|
||||||
|
border: $default-border;
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
}
|
}
|
||||||
@ -432,7 +472,7 @@
|
|||||||
color: $primaryTextColor !important;
|
color: $primaryTextColor !important;
|
||||||
border: 0 !important;
|
border: 0 !important;
|
||||||
padding: 0px !important;
|
padding: 0px !important;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: transparent !important;
|
background-color: transparent !important;
|
||||||
color: $primaryHeaderColor !important;
|
color: $primaryHeaderColor !important;
|
||||||
|
@ -15,14 +15,9 @@
|
|||||||
$primaryErrorColor: #b00020;
|
$primaryErrorColor: #b00020;
|
||||||
$secondaryErrorColor: #e94948;
|
$secondaryErrorColor: #e94948;
|
||||||
|
|
||||||
$default-border: 1px solid $secondaryBackgroundColor;
|
$default-border: 2px solid $secondaryBackgroundColor;
|
||||||
|
|
||||||
html,
|
background-color: $primaryBackgroundColor;
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1,
|
h1,
|
||||||
h2 {
|
h2 {
|
||||||
@ -120,6 +115,35 @@
|
|||||||
.content-divider {
|
.content-divider {
|
||||||
border-bottom: $default-border;
|
border-bottom: $default-border;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.server-list-wrapper {
|
||||||
|
.server-filter {
|
||||||
|
}
|
||||||
|
|
||||||
|
.server-count {
|
||||||
|
}
|
||||||
|
|
||||||
|
.server-list {
|
||||||
|
.server {
|
||||||
|
border: $default-border;
|
||||||
|
border-radius: 15px;
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
img {
|
||||||
|
border-radius: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.name {
|
||||||
|
color: $primaryHeaderColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: $primaryHeaderColor !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -226,28 +250,43 @@
|
|||||||
stroke: $primaryHeaderColor !important;
|
stroke: $primaryHeaderColor !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.p-menu {
|
.p-menu,
|
||||||
|
.p-panelmenu {
|
||||||
color: $primaryTextColor !important;
|
color: $primaryTextColor !important;
|
||||||
|
|
||||||
.p-menuitem-link .p-menuitem-text,
|
.p-menuitem-link .p-menuitem-text,
|
||||||
.p-menuitem-link .p-menuitem-icon {
|
.p-menuitem-link .p-menuitem-icon,
|
||||||
|
.p-panelmenu-header > a {
|
||||||
color: $primaryTextColor !important;
|
color: $primaryTextColor !important;
|
||||||
|
background: transparent !important;
|
||||||
|
font-size: 1rem !important;
|
||||||
|
font-weight: normal !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.p-menuitem-link:focus {
|
.p-menuitem-link:focus,
|
||||||
|
.p-panelmenu-header > a:focus,
|
||||||
|
.p-panelmenu-content .p-menuitem .p-menuitem-link:focus {
|
||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.p-menuitem-link:hover {
|
.p-menuitem-link:hover,
|
||||||
|
.p-panelmenu-header > a:hover,
|
||||||
|
.p-panelmenu-content .p-menuitem .p-menuitem-link:hover {
|
||||||
background-color: $secondaryBackgroundColor !important;
|
background-color: $secondaryBackgroundColor !important;
|
||||||
$border-radius: 20px;
|
$border-radius: 20px;
|
||||||
border-radius: $border-radius 0px 0px $border-radius;
|
border-radius: $border-radius 0px 0px $border-radius;
|
||||||
|
|
||||||
.p-menuitem-text,
|
.p-menuitem-text,
|
||||||
.p-menuitem-icon {
|
.p-menuitem-icon,
|
||||||
|
.p-menuitem-text,
|
||||||
|
.p-panelmenu-icon {
|
||||||
color: $primaryHeaderColor !important;
|
color: $primaryHeaderColor !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.p-panelmenu-content {
|
||||||
|
margin: 5px 0px 5px 10px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.p-menu-overlay {
|
.p-menu-overlay {
|
||||||
@ -386,6 +425,9 @@
|
|||||||
|
|
||||||
input,
|
input,
|
||||||
.p-password {
|
.p-password {
|
||||||
|
border-radius: 10px;
|
||||||
|
border: $default-border;
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
}
|
}
|
||||||
@ -430,7 +472,7 @@
|
|||||||
color: $primaryTextColor !important;
|
color: $primaryTextColor !important;
|
||||||
border: 0 !important;
|
border: 0 !important;
|
||||||
padding: 0px !important;
|
padding: 0px !important;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: transparent !important;
|
background-color: transparent !important;
|
||||||
color: $primaryHeaderColor !important;
|
color: $primaryHeaderColor !important;
|
||||||
|
@ -15,16 +15,9 @@
|
|||||||
$primaryErrorColor: #b00020;
|
$primaryErrorColor: #b00020;
|
||||||
$secondaryErrorColor: #e94948;
|
$secondaryErrorColor: #e94948;
|
||||||
|
|
||||||
$default-border: 1px solid $secondaryBackgroundColor3;
|
$default-border: 2px solid $secondaryBackgroundColor3;
|
||||||
|
|
||||||
background-color: $primaryBackgroundColor !important;
|
background-color: $primaryBackgroundColor;
|
||||||
|
|
||||||
html,
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1,
|
h1,
|
||||||
h2 {
|
h2 {
|
||||||
@ -122,6 +115,35 @@
|
|||||||
.content-divider {
|
.content-divider {
|
||||||
border-bottom: $default-border;
|
border-bottom: $default-border;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.server-list-wrapper {
|
||||||
|
.server-filter {
|
||||||
|
}
|
||||||
|
|
||||||
|
.server-count {
|
||||||
|
}
|
||||||
|
|
||||||
|
.server-list {
|
||||||
|
.server {
|
||||||
|
border: $default-border;
|
||||||
|
border-radius: 15px;
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
img {
|
||||||
|
border-radius: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.name {
|
||||||
|
color: $primaryHeaderColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: $primaryHeaderColor !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -230,28 +252,43 @@
|
|||||||
stroke: $primaryHeaderColor !important;
|
stroke: $primaryHeaderColor !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.p-menu {
|
.p-menu,
|
||||||
|
.p-panelmenu {
|
||||||
color: $primaryTextColor !important;
|
color: $primaryTextColor !important;
|
||||||
|
|
||||||
.p-menuitem-link .p-menuitem-text,
|
.p-menuitem-link .p-menuitem-text,
|
||||||
.p-menuitem-link .p-menuitem-icon {
|
.p-menuitem-link .p-menuitem-icon,
|
||||||
|
.p-panelmenu-header > a {
|
||||||
color: $primaryTextColor !important;
|
color: $primaryTextColor !important;
|
||||||
|
background: transparent !important;
|
||||||
|
font-size: 1rem !important;
|
||||||
|
font-weight: normal !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.p-menuitem-link:focus {
|
.p-menuitem-link:focus,
|
||||||
|
.p-panelmenu-header > a:focus,
|
||||||
|
.p-panelmenu-content .p-menuitem .p-menuitem-link:focus {
|
||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.p-menuitem-link:hover {
|
.p-menuitem-link:hover,
|
||||||
|
.p-panelmenu-header > a:hover,
|
||||||
|
.p-panelmenu-content .p-menuitem .p-menuitem-link:hover {
|
||||||
background-color: $secondaryBackgroundColor !important;
|
background-color: $secondaryBackgroundColor !important;
|
||||||
$border-radius: 20px;
|
$border-radius: 20px;
|
||||||
border-radius: $border-radius 0px 0px $border-radius;
|
border-radius: $border-radius 0px 0px $border-radius;
|
||||||
|
|
||||||
.p-menuitem-text,
|
.p-menuitem-text,
|
||||||
.p-menuitem-icon {
|
.p-menuitem-icon,
|
||||||
|
.p-menuitem-text,
|
||||||
|
.p-panelmenu-icon {
|
||||||
color: $primaryHeaderColor !important;
|
color: $primaryHeaderColor !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.p-panelmenu-content {
|
||||||
|
margin: 5px 0px 5px 10px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.p-menu-overlay {
|
.p-menu-overlay {
|
||||||
@ -390,6 +427,9 @@
|
|||||||
|
|
||||||
input,
|
input,
|
||||||
.p-password {
|
.p-password {
|
||||||
|
border-radius: 10px;
|
||||||
|
border: $default-border;
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
}
|
}
|
||||||
@ -434,7 +474,7 @@
|
|||||||
color: $primaryTextColor !important;
|
color: $primaryTextColor !important;
|
||||||
border: 0 !important;
|
border: 0 !important;
|
||||||
padding: 0px !important;
|
padding: 0px !important;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: transparent !important;
|
background-color: transparent !important;
|
||||||
color: $primaryHeaderColor !important;
|
color: $primaryHeaderColor !important;
|
||||||
|
@ -15,14 +15,9 @@
|
|||||||
$primaryErrorColor: #b00020;
|
$primaryErrorColor: #b00020;
|
||||||
$secondaryErrorColor: #e94948;
|
$secondaryErrorColor: #e94948;
|
||||||
|
|
||||||
$default-border: 1px solid $secondaryBackgroundColor;
|
$default-border: 2px solid $secondaryBackgroundColor;
|
||||||
|
|
||||||
html,
|
background-color: $primaryBackgroundColor;
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1,
|
h1,
|
||||||
h2 {
|
h2 {
|
||||||
@ -121,6 +116,35 @@
|
|||||||
border-bottom: $default-border;
|
border-bottom: $default-border;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.server-list-wrapper {
|
||||||
|
.server-filter {
|
||||||
|
}
|
||||||
|
|
||||||
|
.server-count {
|
||||||
|
}
|
||||||
|
|
||||||
|
.server-list {
|
||||||
|
.server {
|
||||||
|
border: $default-border;
|
||||||
|
border-radius: 15px;
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
img {
|
||||||
|
border-radius: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.name {
|
||||||
|
color: $primaryHeaderColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: $primaryHeaderColor !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -226,28 +250,43 @@
|
|||||||
stroke: $primaryHeaderColor !important;
|
stroke: $primaryHeaderColor !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.p-menu {
|
.p-menu,
|
||||||
|
.p-panelmenu {
|
||||||
color: $primaryTextColor !important;
|
color: $primaryTextColor !important;
|
||||||
|
|
||||||
.p-menuitem-link .p-menuitem-text,
|
.p-menuitem-link .p-menuitem-text,
|
||||||
.p-menuitem-link .p-menuitem-icon {
|
.p-menuitem-link .p-menuitem-icon,
|
||||||
|
.p-panelmenu-header > a {
|
||||||
color: $primaryTextColor !important;
|
color: $primaryTextColor !important;
|
||||||
|
background: transparent !important;
|
||||||
|
font-size: 1rem !important;
|
||||||
|
font-weight: normal !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.p-menuitem-link:focus {
|
.p-menuitem-link:focus,
|
||||||
|
.p-panelmenu-header > a:focus,
|
||||||
|
.p-panelmenu-content .p-menuitem .p-menuitem-link:focus {
|
||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.p-menuitem-link:hover {
|
.p-menuitem-link:hover,
|
||||||
|
.p-panelmenu-header > a:hover,
|
||||||
|
.p-panelmenu-content .p-menuitem .p-menuitem-link:hover {
|
||||||
background-color: $secondaryBackgroundColor !important;
|
background-color: $secondaryBackgroundColor !important;
|
||||||
$border-radius: 20px;
|
$border-radius: 20px;
|
||||||
border-radius: $border-radius 0px 0px $border-radius;
|
border-radius: $border-radius 0px 0px $border-radius;
|
||||||
|
|
||||||
.p-menuitem-text,
|
.p-menuitem-text,
|
||||||
.p-menuitem-icon {
|
.p-menuitem-icon,
|
||||||
|
.p-menuitem-text,
|
||||||
|
.p-panelmenu-icon {
|
||||||
color: $primaryHeaderColor !important;
|
color: $primaryHeaderColor !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.p-panelmenu-content {
|
||||||
|
margin: 5px 0px 5px 10px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.p-menu-overlay {
|
.p-menu-overlay {
|
||||||
@ -386,6 +425,9 @@
|
|||||||
|
|
||||||
input,
|
input,
|
||||||
.p-password {
|
.p-password {
|
||||||
|
border-radius: 10px;
|
||||||
|
border: $default-border;
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
}
|
}
|
||||||
@ -430,7 +472,7 @@
|
|||||||
color: $primaryTextColor !important;
|
color: $primaryTextColor !important;
|
||||||
border: 0 !important;
|
border: 0 !important;
|
||||||
padding: 0px !important;
|
padding: 0px !important;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: transparent !important;
|
background-color: transparent !important;
|
||||||
color: $primaryHeaderColor !important;
|
color: $primaryHeaderColor !important;
|
||||||
|
Loading…
Reference in New Issue
Block a user