Compare commits
24 Commits
c8d3bf780d
...
1.2.3
Author | SHA1 | Date | |
---|---|---|---|
db61a764eb | |||
919eef79f6 | |||
a2dd447dbd | |||
8a76b46165 | |||
af3084ad36 | |||
285b8bdbe4 | |||
e2da4f09ee | |||
4ed99da689 | |||
bc94d31a8d | |||
0d3db75190 | |||
090f217f93 | |||
b98828fce3 | |||
5f8ae787f0 | |||
0c807a7de7 | |||
2dc60acaa6 | |||
29ea96a5e5 | |||
026331b397 | |||
bfe74ad1c5 | |||
7c8c2bef70 | |||
4ccb57e6a3 | |||
5de6710261 | |||
e99e272029 | |||
650f612a6b | |||
d2c37a0098 |
22
bot/src/bot/extension/clean_logs_extension.py
Normal file
22
bot/src/bot/extension/clean_logs_extension.py
Normal file
@@ -0,0 +1,22 @@
|
||||
import os
|
||||
import shutil
|
||||
from datetime import datetime
|
||||
|
||||
from cpl_core.application.application_extension_abc import ApplicationExtensionABC
|
||||
from cpl_core.configuration import ConfigurationABC
|
||||
from cpl_core.dependency_injection import ServiceProviderABC
|
||||
from cpl_query.extension import List
|
||||
|
||||
|
||||
class CleanLogsExtension(ApplicationExtensionABC):
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
async def run(self, config: ConfigurationABC, services: ServiceProviderABC):
|
||||
(
|
||||
List(str, os.listdir("logs/"))
|
||||
.where(lambda x: os.path.isdir(f"logs/{x}"))
|
||||
.order_by()
|
||||
.where(lambda x: (datetime.now() - datetime.strptime(x, "%Y-%m-%d")).days >= 7)
|
||||
.for_each(lambda x: shutil.rmtree(f"logs/{x}"))
|
||||
)
|
@@ -6,6 +6,7 @@ from cpl_core.application import ApplicationBuilder
|
||||
from cpl_core.console import Console
|
||||
|
||||
from bot.application import Application
|
||||
from bot.extension.clean_logs_extension import CleanLogsExtension
|
||||
from bot.extension.init_bot_extension import InitBotExtension
|
||||
from bot.startup import Startup
|
||||
from bot.startup_discord_extension import StartupDiscordExtension
|
||||
@@ -31,6 +32,7 @@ class Program:
|
||||
.use_extension(StartupDiscordExtension)
|
||||
.use_extension(StartupModuleExtension)
|
||||
.use_extension(StartupMigrationExtension)
|
||||
.use_extension(CleanLogsExtension)
|
||||
.use_extension(DatabaseExtension)
|
||||
.use_extension(ConfigExtension)
|
||||
.use_extension(InitBotExtension)
|
||||
|
@@ -14,7 +14,10 @@ from bot_api.model.reset_password_dto import ResetPasswordDTO
|
||||
from bot_api.model.token_dto import TokenDTO
|
||||
from bot_api.model.update_auth_user_dto import UpdateAuthUserDTO
|
||||
from bot_api.route.route import Route
|
||||
from bot_core.configuration.feature_flags_enum import FeatureFlagsEnum
|
||||
from bot_core.configuration.feature_flags_settings import FeatureFlagsSettings
|
||||
from bot_data.model.auth_role_enum import AuthRoleEnum
|
||||
from bot_data.model.technician_config import TechnicianConfig
|
||||
|
||||
|
||||
class AuthController:
|
||||
@@ -30,6 +33,7 @@ class AuthController:
|
||||
mail_settings: EMailClientSettings,
|
||||
mailer: EMailClientABC,
|
||||
auth_service: AuthServiceABC,
|
||||
technician_config: TechnicianConfig,
|
||||
):
|
||||
self._config = config
|
||||
self._env = env
|
||||
@@ -39,6 +43,7 @@ class AuthController:
|
||||
self._mail_settings = mail_settings
|
||||
self._mailer = mailer
|
||||
self._auth_service = auth_service
|
||||
self._technician_config = technician_config
|
||||
|
||||
@Route.get(f"{BasePath}/users")
|
||||
@Route.authorize(role=AuthRoleEnum.admin)
|
||||
@@ -70,17 +75,32 @@ class AuthController:
|
||||
|
||||
@Route.post(f"{BasePath}/register")
|
||||
async def register(self):
|
||||
if not FeatureFlagsSettings.get_flag_from_dict(
|
||||
self._technician_config.feature_flags, FeatureFlagsEnum.basic_registration
|
||||
):
|
||||
return
|
||||
|
||||
dto: AuthUserDTO = JSONProcessor.process(AuthUserDTO, request.get_json(force=True, silent=True))
|
||||
self._auth_service.add_auth_user(dto)
|
||||
return "", 200
|
||||
|
||||
@Route.post(f"{BasePath}/register-by-id/<id>")
|
||||
async def register_id(self, id: str):
|
||||
if not FeatureFlagsSettings.get_flag_from_dict(
|
||||
self._technician_config.feature_flags, FeatureFlagsEnum.basic_registration
|
||||
):
|
||||
return
|
||||
|
||||
result = await self._auth_service.confirm_email_async(id)
|
||||
return jsonify(result)
|
||||
|
||||
@Route.post(f"{BasePath}/login")
|
||||
async def login(self) -> Response:
|
||||
if not FeatureFlagsSettings.get_flag_from_dict(
|
||||
self._technician_config.feature_flags, FeatureFlagsEnum.basic_login
|
||||
):
|
||||
return jsonify({})
|
||||
|
||||
dto: AuthUserDTO = JSONProcessor.process(AuthUserDTO, request.get_json(force=True, silent=True))
|
||||
result = await self._auth_service.login_async(dto)
|
||||
return jsonify(result.to_dict())
|
||||
@@ -100,6 +120,11 @@ class AuthController:
|
||||
|
||||
@Route.post(f"{BasePath}/forgot-password/<email>")
|
||||
async def forgot_password(self, email: str):
|
||||
if not FeatureFlagsSettings.get_flag_from_dict(
|
||||
self._technician_config.feature_flags, FeatureFlagsEnum.basic_login
|
||||
):
|
||||
return "", 409
|
||||
|
||||
await self._auth_service.forgot_password_async(email)
|
||||
return "", 200
|
||||
|
||||
@@ -110,6 +135,11 @@ class AuthController:
|
||||
|
||||
@Route.post(f"{BasePath}/reset-password")
|
||||
async def reset_password(self):
|
||||
if not FeatureFlagsSettings.get_flag_from_dict(
|
||||
self._technician_config.feature_flags, FeatureFlagsEnum.basic_login
|
||||
):
|
||||
return "", 409
|
||||
|
||||
dto: ResetPasswordDTO = JSONProcessor.process(ResetPasswordDTO, request.get_json(force=True, silent=True))
|
||||
await self._auth_service.reset_password_async(dto)
|
||||
return "", 200
|
||||
|
@@ -12,6 +12,9 @@ from bot_api.logging.api_logger import ApiLogger
|
||||
from bot_api.model.settings_dto import SettingsDTO
|
||||
from bot_api.model.version_dto import VersionDTO
|
||||
from bot_api.route.route import Route
|
||||
from bot_core.configuration.feature_flags_enum import FeatureFlagsEnum
|
||||
from bot_core.configuration.feature_flags_settings import FeatureFlagsSettings
|
||||
from bot_data.model.technician_config import TechnicianConfig
|
||||
|
||||
|
||||
class GuiController:
|
||||
@@ -82,3 +85,11 @@ class GuiController:
|
||||
)
|
||||
self._mailer.send_mail(mail)
|
||||
return "", 200
|
||||
|
||||
@Route.get(f"{BasePath}/has-feature-flag/<flag>")
|
||||
async def has_feature_flag(self, flag: str):
|
||||
settings: TechnicianConfig = self._config.get_configuration(TechnicianConfig)
|
||||
return {
|
||||
"key": flag,
|
||||
"value": FeatureFlagsSettings.get_flag_from_dict(settings.feature_flags, FeatureFlagsEnum(flag)),
|
||||
}
|
||||
|
@@ -28,3 +28,5 @@ class FeatureFlagsEnum(Enum):
|
||||
technician_full_access = "TechnicianFullAccess"
|
||||
steam_special_offers = "SteamSpecialOffers"
|
||||
scheduled_events = "ScheduledEvents"
|
||||
basic_registration = "BasicRegistration"
|
||||
basic_login = "BasicLogin"
|
||||
|
@@ -30,6 +30,8 @@ class FeatureFlagsSettings(ConfigurationModelABC):
|
||||
FeatureFlagsEnum.technician_full_access.value: False, # 03.10.2023 #393
|
||||
FeatureFlagsEnum.steam_special_offers.value: False, # 11.10.2023 #188
|
||||
FeatureFlagsEnum.scheduled_events.value: False, # 14.11.2023 #410
|
||||
FeatureFlagsEnum.basic_registration.value: False, # 19.11.2023 #440
|
||||
FeatureFlagsEnum.basic_login.value: False, # 19.11.2023 #440
|
||||
}
|
||||
|
||||
def __init__(self, **kwargs: dict):
|
||||
|
@@ -50,4 +50,6 @@ type Query {
|
||||
technicianConfig: TechnicianConfig
|
||||
possibleFeatureFlags: [String]
|
||||
discord: Discord
|
||||
|
||||
hasFeatureFlag(flag: String): FeatureFlag
|
||||
}
|
@@ -45,7 +45,7 @@ class ScheduledEventMutation(QueryABC):
|
||||
input["channelId"] if "channelId" in input else None,
|
||||
datetime.strptime(input["startTime"], "%Y-%m-%dT%H:%M:%S.%fZ"),
|
||||
datetime.strptime(input["endTime"], "%Y-%m-%dT%H:%M:%S.%fZ") if "endTime" in input else None,
|
||||
input["entityType"],
|
||||
EntityType(int(input["entityType"])),
|
||||
input["location"] if "location" in input else None,
|
||||
server,
|
||||
)
|
||||
|
@@ -1,7 +1,9 @@
|
||||
from cpl_core.configuration import ConfigurationABC
|
||||
from cpl_discord.service import DiscordBotServiceABC
|
||||
from cpl_query.extension import List
|
||||
|
||||
from bot_core.configuration.feature_flags_enum import FeatureFlagsEnum
|
||||
from bot_core.configuration.feature_flags_settings import FeatureFlagsSettings
|
||||
from bot_data.abc.achievement_repository_abc import AchievementRepositoryABC
|
||||
from bot_data.abc.auto_role_repository_abc import AutoRoleRepositoryABC
|
||||
from bot_data.abc.client_repository_abc import ClientRepositoryABC
|
||||
@@ -22,6 +24,7 @@ from bot_data.abc.user_joined_voice_channel_repository_abc import (
|
||||
from bot_data.abc.user_repository_abc import UserRepositoryABC
|
||||
from bot_data.abc.user_warnings_repository_abc import UserWarningsRepositoryABC
|
||||
from bot_data.model.short_role_name_position_enum import ShortRoleNamePositionEnum
|
||||
from bot_data.model.technician_config import TechnicianConfig
|
||||
from bot_graphql.abc.query_abc import QueryABC
|
||||
from bot_graphql.filter.achievement_filter import AchievementFilter
|
||||
from bot_graphql.filter.auto_role_filter import AutoRoleFilter
|
||||
@@ -45,6 +48,7 @@ from modules.achievements.achievement_service import AchievementService
|
||||
class Query(QueryABC):
|
||||
def __init__(
|
||||
self,
|
||||
config: ConfigurationABC,
|
||||
bot: DiscordBotServiceABC,
|
||||
auto_roles: AutoRoleRepositoryABC,
|
||||
clients: ClientRepositoryABC,
|
||||
@@ -65,6 +69,8 @@ class Query(QueryABC):
|
||||
):
|
||||
QueryABC.__init__(self, "Query")
|
||||
|
||||
self._config = config
|
||||
|
||||
self.add_collection("autoRole", lambda *_: auto_roles.get_auto_roles(), AutoRoleFilter)
|
||||
self.add_collection(
|
||||
"autoRoleRule",
|
||||
@@ -120,3 +126,17 @@ class Query(QueryABC):
|
||||
|
||||
self.set_field("possibleFeatureFlags", lambda *_: [e.value for e in FeatureFlagsEnum])
|
||||
self.set_field("discord", lambda *_: Discord(bot.guilds, List(any).extend(bot.users)))
|
||||
|
||||
self.set_field(
|
||||
"hasFeatureFlag",
|
||||
lambda *_, **kwargs: self._resolve_has_feature_flag(*_, **kwargs),
|
||||
)
|
||||
|
||||
def _resolve_has_feature_flag(self, *_, **kwargs):
|
||||
settings: TechnicianConfig = self._config.get_configuration(TechnicianConfig)
|
||||
if "flag" not in kwargs:
|
||||
return False
|
||||
return {
|
||||
"key": kwargs["flag"],
|
||||
"value": FeatureFlagsSettings.get_flag_from_dict(settings.feature_flags, FeatureFlagsEnum(kwargs["flag"])),
|
||||
}
|
||||
|
@@ -7,6 +7,7 @@ from cpl_discord.service.discord_collection_abc import DiscordCollectionABC
|
||||
from bot_core.abc.module_abc import ModuleABC
|
||||
from bot_core.abc.task_abc import TaskABC
|
||||
from bot_core.configuration.feature_flags_enum import FeatureFlagsEnum
|
||||
from modules.base.command.scheduled_events_group import ScheduledEventsCommand
|
||||
from modules.base.tasks.birthday_watcher import BirthdayWatcher
|
||||
from modules.base.command.afk_command import AFKCommand
|
||||
from modules.base.command.game_server_group import GameServerGroup
|
||||
@@ -83,6 +84,7 @@ class BaseModule(ModuleABC):
|
||||
services.add_transient(RegisterGroup)
|
||||
services.add_transient(UnregisterGroup)
|
||||
services.add_transient(GameServerGroup)
|
||||
services.add_transient(ScheduledEventsCommand)
|
||||
# events
|
||||
services.add_transient(DiscordEventTypesEnum.on_command.value, BaseOnCommandEvent)
|
||||
services.add_transient(DiscordEventTypesEnum.on_command_error.value, BaseOnCommandErrorEvent)
|
||||
|
@@ -38,7 +38,7 @@ class MassMoveCommand(DiscordCommandABC):
|
||||
self,
|
||||
ctx: Context,
|
||||
channel_to: discord.VoiceChannel,
|
||||
channel_from: Transform[str, VoiceChannelTransformer] = None,
|
||||
channel_from: Transform[discord.VoiceChannel, VoiceChannelTransformer] = None,
|
||||
):
|
||||
self._logger.debug(__name__, f"Received command mass-move {ctx}")
|
||||
|
||||
|
37
bot/src/modules/base/command/scheduled_events_group.py
Normal file
37
bot/src/modules/base/command/scheduled_events_group.py
Normal file
@@ -0,0 +1,37 @@
|
||||
from cpl_discord.command import DiscordCommandABC
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Context
|
||||
|
||||
from bot_core.helper.command_checks import CommandChecks
|
||||
from bot_core.logging.command_logger import CommandLogger
|
||||
from modules.base.service.event_service import EventService
|
||||
|
||||
|
||||
class ScheduledEventsCommand(DiscordCommandABC):
|
||||
def __init__(
|
||||
self,
|
||||
logger: CommandLogger,
|
||||
events: EventService,
|
||||
):
|
||||
DiscordCommandABC.__init__(self)
|
||||
|
||||
self._logger = logger
|
||||
self._events = events
|
||||
|
||||
self._logger.trace(__name__, f"Loaded command service: {type(self).__name__}")
|
||||
|
||||
@commands.hybrid_group(name="scheduled-events")
|
||||
@commands.guild_only()
|
||||
async def scheduled_events(self, ctx: Context):
|
||||
pass
|
||||
|
||||
@scheduled_events.command()
|
||||
@commands.guild_only()
|
||||
@CommandChecks.check_is_ready()
|
||||
@CommandChecks.check_is_member_moderator()
|
||||
async def reload(self, ctx: Context):
|
||||
self._logger.debug(__name__, "Running scheduled-events reload")
|
||||
try:
|
||||
await self._events.check_and_create_scheduled_events(ctx.guild)
|
||||
except Exception as e:
|
||||
self._logger.error(__name__, f"Reloading scheduled events failed", e)
|
@@ -82,7 +82,7 @@ class BaseOnMemberJoinEvent(OnMemberJoinABC):
|
||||
return
|
||||
|
||||
self._logger.debug(__name__, f"Add user: {member.id}")
|
||||
self._users.add_user(User(member.id, 0, 0, 0, server))
|
||||
self._users.add_user(User(member.id, 0, 0, 0, None, server))
|
||||
self._db.save_changes()
|
||||
user = self._users.get_user_by_discord_id_and_server_id(member.id, server.id)
|
||||
self._user_joins.add_user_joined_server(UserJoinedServer(user, datetime.now()))
|
||||
|
@@ -42,4 +42,5 @@ class BaseOnScheduledEventUpdateEvent(OnScheduledEventUpdateABC):
|
||||
return
|
||||
self._events.remove_event(event)
|
||||
|
||||
await self._events.check_and_create_scheduled_events(before.guild)
|
||||
self._logger.debug(__name__, f"Module {type(self)} stopped")
|
||||
|
@@ -58,14 +58,22 @@ class BaseOnVoiceStateUpdateEvent(OnVoiceStateUpdateABC):
|
||||
return
|
||||
|
||||
try:
|
||||
settings: ServerConfig = self._config.get_configuration(f"ServerConfig_{server.discord_id}")
|
||||
|
||||
if joined:
|
||||
active_joins = self._user_joins_vc.find_active_user_joined_voice_channels_by_user_id(user.id)
|
||||
for join in active_joins:
|
||||
join.leaved_on = datetime.now()
|
||||
user.xp += round(join.time * settings.xp_per_ontime_hour)
|
||||
self._user_joins_vc.update_user_joined_voice_channel(join)
|
||||
self._users.update_user(user)
|
||||
self._db.save_changes()
|
||||
|
||||
join = UserJoinedVoiceChannel(user, channel_id, datetime.now())
|
||||
self._user_joins_vc.add_user_joined_voice_channel(join)
|
||||
self._db.save_changes()
|
||||
return
|
||||
|
||||
settings: ServerConfig = self._config.get_configuration(f"ServerConfig_{server.discord_id}")
|
||||
|
||||
join = self._user_joins_vc.get_active_user_joined_voice_channel_by_user_id(user.id)
|
||||
join.leaved_on = datetime.now()
|
||||
|
||||
|
@@ -25,7 +25,7 @@ class VoiceChannelTransformer(Transformer):
|
||||
return [
|
||||
app_commands.Choice(
|
||||
name=f"{vc.name}" if vc.category is None else f"{vc.name}: {vc.category.name}",
|
||||
value=vc.name,
|
||||
value=str(vc.id),
|
||||
)
|
||||
for vc in get_client_utils().get_auto_complete_list(voice_channels, current, lambda x: x.name)
|
||||
]
|
||||
|
@@ -1,13 +1,22 @@
|
||||
import calendar
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Optional
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
import discord
|
||||
from cpl_core.configuration import ConfigurationABC
|
||||
from cpl_core.database.context import DatabaseContextABC
|
||||
from cpl_core.logging import LoggerABC
|
||||
from cpl_discord.container import Guild
|
||||
from cpl_query.extension import List
|
||||
from discord import PrivacyLevel
|
||||
from discord.scheduled_event import ScheduledEvent as DiscordEvent
|
||||
|
||||
from bot_data.abc.scheduled_event_repository_abc import ScheduledEventRepositoryABC
|
||||
from bot_data.abc.server_repository_abc import ServerRepositoryABC
|
||||
from bot_data.abc.user_repository_abc import UserRepositoryABC
|
||||
from bot_data.model.scheduled_event import ScheduledEvent
|
||||
from bot_data.model.scheduled_event_interval_enum import ScheduledEventIntervalEnum
|
||||
from bot_data.model.server_config import ServerConfig
|
||||
from modules.base.model.active_event import ActiveEvent
|
||||
|
||||
@@ -20,12 +29,14 @@ class EventService:
|
||||
servers: ServerRepositoryABC,
|
||||
users: UserRepositoryABC,
|
||||
db: DatabaseContextABC,
|
||||
events: ScheduledEventRepositoryABC,
|
||||
):
|
||||
self._config = config
|
||||
self._logger = logger
|
||||
self._servers = servers
|
||||
self._users = users
|
||||
self._db = db
|
||||
self._events = events
|
||||
|
||||
self._active_events = List(ActiveEvent)
|
||||
|
||||
@@ -61,3 +72,72 @@ class EventService:
|
||||
self._users.update_user(user)
|
||||
self._db.save_changes()
|
||||
active_event.participants.append(user)
|
||||
|
||||
def _append_interval(self, interval: ScheduledEventIntervalEnum, ts: datetime) -> datetime:
|
||||
now = datetime.now().replace(tzinfo=ZoneInfo("Europe/Berlin"))
|
||||
if ts >= now:
|
||||
return ts
|
||||
|
||||
if interval == ScheduledEventIntervalEnum.daily:
|
||||
ts = ts + timedelta(days=1)
|
||||
|
||||
elif interval == ScheduledEventIntervalEnum.weekly:
|
||||
ts = ts + timedelta(weeks=1)
|
||||
|
||||
elif interval == ScheduledEventIntervalEnum.monthly:
|
||||
days_in_month = calendar.monthrange(ts.year, ts.month + 1)[1]
|
||||
ts = ts + timedelta(days=days_in_month)
|
||||
|
||||
elif interval == ScheduledEventIntervalEnum.yearly:
|
||||
ts = ts + timedelta(days=365)
|
||||
|
||||
while ts < now:
|
||||
ts = self._append_interval(interval, ts)
|
||||
|
||||
return ts
|
||||
|
||||
async def check_and_create_scheduled_events(self, guild: Guild):
|
||||
server = self._servers.get_server_by_discord_id(guild.id)
|
||||
scheduled_events_from_db = self._events.get_scheduled_events_by_server_id(server.id)
|
||||
for scheduled_event in scheduled_events_from_db:
|
||||
scheduled_event: ScheduledEvent = scheduled_event
|
||||
from_guild = List(DiscordEvent, guild.scheduled_events).where(
|
||||
lambda x: x.name == scheduled_event.name
|
||||
and x.description == scheduled_event.description
|
||||
and x.entity_type == scheduled_event.entity_type
|
||||
)
|
||||
if from_guild.count() != 0:
|
||||
continue
|
||||
|
||||
kwargs = {"name": scheduled_event.name, "description": scheduled_event.description}
|
||||
|
||||
if scheduled_event.channel_id is not None:
|
||||
kwargs["channel"] = guild.get_channel(scheduled_event.channel_id)
|
||||
|
||||
if scheduled_event.start_time is not None:
|
||||
start_time = self._append_interval(
|
||||
scheduled_event.interval, scheduled_event.start_time.replace(tzinfo=ZoneInfo("Europe/Berlin"))
|
||||
)
|
||||
kwargs["start_time"] = start_time
|
||||
scheduled_event.start_time = scheduled_event.start_time.replace(tzinfo=None)
|
||||
|
||||
if scheduled_event.end_time is not None:
|
||||
end_time = self._append_interval(
|
||||
scheduled_event.interval, scheduled_event.end_time.replace(tzinfo=ZoneInfo("Europe/Berlin"))
|
||||
)
|
||||
kwargs["end_time"] = end_time
|
||||
scheduled_event.end_time = scheduled_event.end_time.replace(tzinfo=None)
|
||||
|
||||
kwargs["entity_type"] = scheduled_event.entity_type
|
||||
if scheduled_event.location is not None:
|
||||
kwargs["location"] = scheduled_event.location
|
||||
|
||||
kwargs["privacy_level"] = PrivacyLevel.guild_only
|
||||
|
||||
try:
|
||||
self._logger.debug(__name__, f"Try to create scheduled event for guild {guild.name}")
|
||||
await guild.create_scheduled_event(**kwargs)
|
||||
self._events.update_scheduled_event(scheduled_event)
|
||||
self._db.save_changes()
|
||||
except Exception as e:
|
||||
self._logger.error(__name__, f"Watching scheduled events failed", e)
|
||||
|
@@ -1,23 +1,14 @@
|
||||
import calendar
|
||||
from datetime import datetime, timedelta
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
from cpl_core.configuration import ConfigurationABC
|
||||
from cpl_core.database.context import DatabaseContextABC
|
||||
from cpl_discord.service import DiscordBotServiceABC
|
||||
from cpl_query.extension import List
|
||||
from cpl_translation import TranslatePipe
|
||||
from discord import Guild, PrivacyLevel
|
||||
from discord.ext import tasks
|
||||
from discord.scheduled_event import ScheduledEvent as DiscordEvent
|
||||
|
||||
from bot_core.abc.task_abc import TaskABC
|
||||
from bot_core.logging.task_logger import TaskLogger
|
||||
from bot_core.service.message_service import MessageService
|
||||
from bot_data.abc.scheduled_event_repository_abc import ScheduledEventRepositoryABC
|
||||
from bot_data.abc.server_repository_abc import ServerRepositoryABC
|
||||
from bot_data.model.scheduled_event import ScheduledEvent
|
||||
from bot_data.model.scheduled_event_interval_enum import ScheduledEventIntervalEnum
|
||||
from modules.base.service.event_service import EventService
|
||||
|
||||
|
||||
class ScheduledEventsWatcher(TaskABC):
|
||||
@@ -28,7 +19,7 @@ class ScheduledEventsWatcher(TaskABC):
|
||||
bot: DiscordBotServiceABC,
|
||||
db: DatabaseContextABC,
|
||||
servers: ServerRepositoryABC,
|
||||
events: ScheduledEventRepositoryABC,
|
||||
events: EventService,
|
||||
message_service: MessageService,
|
||||
t: TranslatePipe,
|
||||
):
|
||||
@@ -46,78 +37,12 @@ class ScheduledEventsWatcher(TaskABC):
|
||||
if not self._is_maintenance():
|
||||
self.watch.start()
|
||||
|
||||
def _append_interval(self, interval: ScheduledEventIntervalEnum, ts: datetime) -> datetime:
|
||||
now = datetime.now()
|
||||
if ts >= now:
|
||||
return ts
|
||||
|
||||
if interval == ScheduledEventIntervalEnum.daily:
|
||||
ts = ts + timedelta(days=1)
|
||||
|
||||
elif interval == ScheduledEventIntervalEnum.weekly:
|
||||
ts = ts + timedelta(weeks=1)
|
||||
|
||||
elif interval == ScheduledEventIntervalEnum.monthly:
|
||||
days_in_month = calendar.monthrange(ts.year, ts.month + 1)[1]
|
||||
ts = ts + timedelta(days=days_in_month)
|
||||
|
||||
elif interval == ScheduledEventIntervalEnum.yearly:
|
||||
ts = ts + timedelta(days=365)
|
||||
|
||||
return ts
|
||||
|
||||
@tasks.loop(hours=24)
|
||||
@tasks.loop(hours=12)
|
||||
async def watch(self):
|
||||
self._logger.info(__name__, "Watching scheduled events")
|
||||
try:
|
||||
for guild in self._bot.guilds:
|
||||
guild: Guild = guild
|
||||
server = self._servers.get_server_by_discord_id(guild.id)
|
||||
scheduled_events_from_guild = self._events.get_scheduled_events_by_server_id(server.id)
|
||||
for scheduled_event in scheduled_events_from_guild:
|
||||
scheduled_event: ScheduledEvent = scheduled_event
|
||||
from_guild = List(DiscordEvent, guild.scheduled_events).where(
|
||||
lambda x: x.name == scheduled_event.name
|
||||
and x.description == scheduled_event.description
|
||||
and x.entity_type == scheduled_event.entity_type
|
||||
)
|
||||
if from_guild.count() != 0:
|
||||
continue
|
||||
|
||||
kwargs = {"name": scheduled_event.name, "description": scheduled_event.description}
|
||||
|
||||
if scheduled_event.channel_id is not None:
|
||||
kwargs["channel"] = guild.get_channel(scheduled_event.channel_id)
|
||||
|
||||
if scheduled_event.start_time is not None:
|
||||
scheduled_event.start_time = self._append_interval(
|
||||
scheduled_event.interval, scheduled_event.start_time
|
||||
)
|
||||
|
||||
start_time = scheduled_event.start_time.replace(tzinfo=ZoneInfo("Europe/Berlin"))
|
||||
|
||||
kwargs["start_time"] = start_time
|
||||
|
||||
if scheduled_event.end_time is not None:
|
||||
scheduled_event.end_time = self._append_interval(
|
||||
scheduled_event.interval, scheduled_event.end_time
|
||||
)
|
||||
end_time = scheduled_event.end_time.replace(tzinfo=ZoneInfo("Europe/Berlin"))
|
||||
kwargs["end_time"] = end_time
|
||||
|
||||
kwargs["entity_type"] = scheduled_event.entity_type
|
||||
if scheduled_event.location is not None:
|
||||
kwargs["location"] = scheduled_event.location
|
||||
|
||||
kwargs["privacy_level"] = PrivacyLevel.guild_only
|
||||
|
||||
try:
|
||||
self._logger.debug(__name__, f"Try to create scheduled event for guild {guild.name}")
|
||||
await guild.create_scheduled_event(**kwargs)
|
||||
self._events.update_scheduled_event(scheduled_event)
|
||||
self._db.save_changes()
|
||||
except Exception as e:
|
||||
self._logger.error(__name__, f"Watching scheduled events failed", e)
|
||||
await self._events.check_and_create_scheduled_events(guild)
|
||||
except Exception as e:
|
||||
self._logger.error(__name__, f"Watching scheduled events failed", e)
|
||||
|
||||
|
@@ -1,26 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
bot sh-edraft.de Discord bot
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Discord bot for customers of sh-edraft.de
|
||||
|
||||
:copyright: (c) 2022 - 2023 sh-edraft.de
|
||||
:license: MIT, see LICENSE for more details.
|
||||
|
||||
"""
|
||||
|
||||
__title__ = "modules.base.thread"
|
||||
__author__ = "Sven Heidemann"
|
||||
__license__ = "MIT"
|
||||
__copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de"
|
||||
__version__ = "1.2.2"
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
|
||||
# imports
|
||||
|
||||
VersionInfo = namedtuple("VersionInfo", "major minor micro")
|
||||
version_info = VersionInfo(major="1", minor="2", micro="2")
|
110
web/package.json
110
web/package.json
@@ -1,56 +1,56 @@
|
||||
{
|
||||
"name": "web",
|
||||
"version": "1.2.dev410",
|
||||
"scripts": {
|
||||
"ng": "ng",
|
||||
"update-version": "ts-node update-version.ts",
|
||||
"prestart": "npm run update-version",
|
||||
"start": "ng serve",
|
||||
"prebuild": "npm run update-version",
|
||||
"build": "ng build",
|
||||
"watch": "ng build --watch --configuration development",
|
||||
"test": "ng test",
|
||||
"gv": "echo $npm_package_version",
|
||||
"predocker-build": "npm run update-version",
|
||||
"docker-build": "export VERSION=$npm_package_version; ng build; docker build -t sh-edraft.de/sdb-web:$VERSION .",
|
||||
"docker-build-dev": "export VERSION=$npm_package_version; ng build --configuration development; docker build -t sh-edraft.de/sdb-web:$VERSION .",
|
||||
"docker-build-stage": "export VERSION=$npm_package_version; ng build --configuration staging; docker build -t sh-edraft.de/sdb-web:$VERSION ."
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "^15.1.4",
|
||||
"@angular/common": "^15.1.4",
|
||||
"@angular/compiler": "^15.1.4",
|
||||
"@angular/core": "^15.1.4",
|
||||
"@angular/forms": "^15.1.4",
|
||||
"@angular/platform-browser": "^15.1.4",
|
||||
"@angular/platform-browser-dynamic": "^15.1.4",
|
||||
"@angular/router": "^15.1.4",
|
||||
"@auth0/angular-jwt": "^5.1.0",
|
||||
"@microsoft/signalr": "^6.0.9",
|
||||
"@ngx-translate/core": "^14.0.0",
|
||||
"@ngx-translate/http-loader": "^7.0.0",
|
||||
"@types/socket.io-client": "^3.0.0",
|
||||
"moment": "^2.29.4",
|
||||
"primeicons": "^6.0.1",
|
||||
"primeng": "^15.2.0",
|
||||
"rxjs": "~7.5.0",
|
||||
"socket.io-client": "^4.5.3",
|
||||
"zone.js": "~0.11.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "^15.1.5",
|
||||
"@angular/cli": "~15.1.5",
|
||||
"@angular/compiler-cli": "^15.1.4",
|
||||
"@types/jasmine": "~4.0.0",
|
||||
"@types/node": "^18.11.9",
|
||||
"jasmine-core": "~4.1.0",
|
||||
"karma": "~6.3.0",
|
||||
"karma-chrome-launcher": "~3.1.0",
|
||||
"karma-coverage": "~2.2.0",
|
||||
"karma-jasmine": "~5.0.0",
|
||||
"karma-jasmine-html-reporter": "~1.7.0",
|
||||
"tslib": "^2.4.1",
|
||||
"typescript": "~4.9.5"
|
||||
}
|
||||
}
|
||||
"name": "web",
|
||||
"version": "1.2.2",
|
||||
"scripts": {
|
||||
"ng": "ng",
|
||||
"update-version": "ts-node update-version.ts",
|
||||
"prestart": "npm run update-version",
|
||||
"start": "ng serve",
|
||||
"prebuild": "npm run update-version",
|
||||
"build": "ng build",
|
||||
"watch": "ng build --watch --configuration development",
|
||||
"test": "ng test",
|
||||
"gv": "echo $npm_package_version",
|
||||
"predocker-build": "npm run update-version",
|
||||
"docker-build": "export VERSION=$npm_package_version; ng build; docker build -t sh-edraft.de/sdb-web:$VERSION .",
|
||||
"docker-build-dev": "export VERSION=$npm_package_version; ng build --configuration development; docker build -t sh-edraft.de/sdb-web:$VERSION .",
|
||||
"docker-build-stage": "export VERSION=$npm_package_version; ng build --configuration staging; docker build -t sh-edraft.de/sdb-web:$VERSION ."
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "^15.1.4",
|
||||
"@angular/common": "^15.1.4",
|
||||
"@angular/compiler": "^15.1.4",
|
||||
"@angular/core": "^15.1.4",
|
||||
"@angular/forms": "^15.1.4",
|
||||
"@angular/platform-browser": "^15.1.4",
|
||||
"@angular/platform-browser-dynamic": "^15.1.4",
|
||||
"@angular/router": "^15.1.4",
|
||||
"@auth0/angular-jwt": "^5.1.0",
|
||||
"@microsoft/signalr": "^6.0.9",
|
||||
"@ngx-translate/core": "^14.0.0",
|
||||
"@ngx-translate/http-loader": "^7.0.0",
|
||||
"@types/socket.io-client": "^3.0.0",
|
||||
"moment": "^2.29.4",
|
||||
"primeicons": "^6.0.1",
|
||||
"primeng": "^15.2.0",
|
||||
"rxjs": "~7.5.0",
|
||||
"socket.io-client": "^4.5.3",
|
||||
"zone.js": "~0.11.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "^15.1.5",
|
||||
"@angular/cli": "~15.1.5",
|
||||
"@angular/compiler-cli": "^15.1.4",
|
||||
"@types/jasmine": "~4.0.0",
|
||||
"@types/node": "^18.11.9",
|
||||
"jasmine-core": "~4.1.0",
|
||||
"karma": "~6.3.0",
|
||||
"karma-chrome-launcher": "~3.1.0",
|
||||
"karma-coverage": "~2.2.0",
|
||||
"karma-jasmine": "~5.0.0",
|
||||
"karma-jasmine-html-reporter": "~1.7.0",
|
||||
"tslib": "^2.4.1",
|
||||
"typescript": "~4.9.5"
|
||||
}
|
||||
}
|
||||
|
@@ -108,6 +108,15 @@ export class Queries {
|
||||
}
|
||||
`;
|
||||
|
||||
static hasFeatureFlag = `
|
||||
query HasFeatureFlag($flag: String) {
|
||||
hasFeatureFlag(flag: $flag) {
|
||||
key
|
||||
value
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
static hasServerFeatureFlag = `
|
||||
query HasServerFeatureFlag($filter: ServerFilter, $flag: String) {
|
||||
servers(filter: $filter) {
|
||||
|
@@ -4,7 +4,9 @@
|
||||
<form [formGroup]="loginForm">
|
||||
<h1>{{'auth.header' | translate}}</h1>
|
||||
<div class="input-field">
|
||||
<input type="email" pInputText formControlName="email" placeholder="{{'common.email' | translate}}" [ngClass]="{ 'invalid-feedback-input': submitted && (
|
||||
<input [disabled]="!basicLoginFeatureFlags" type="email" pInputText formControlName="email"
|
||||
placeholder="{{'common.email' | translate}}"
|
||||
[ngClass]="{ 'invalid-feedback-input': submitted && (
|
||||
(loginForm.controls.email.errors && loginForm.controls.email.errors['required'] || authUserAtrErrors.email.required) ||
|
||||
(authUserAtrErrors.email.wrongData) ||
|
||||
(authUserAtrErrors.email.notConfirmed)
|
||||
@@ -24,7 +26,8 @@
|
||||
styleClass="p-password p-component p-inputwrapper p-input-icon-right"
|
||||
Remove after update!
|
||||
-->
|
||||
<p-password formControlName="password" placeholder="{{'auth.login.password' | translate}}" [ngClass]="{ 'invalid-feedback-input': submitted && (
|
||||
<p-password [disabled]="!basicLoginFeatureFlags" formControlName="password"
|
||||
placeholder="{{'auth.login.password' | translate}}" [ngClass]="{ 'invalid-feedback-input': submitted && (
|
||||
(loginForm.controls.password.errors && loginForm.controls.password.errors['required'] || authUserAtrErrors.password.required) ||
|
||||
(authUserAtrErrors.password.wrongData)
|
||||
)}" [toggleMask]="true" [feedback]="false"
|
||||
@@ -39,17 +42,18 @@
|
||||
</div>
|
||||
<div class="login-form-submit">
|
||||
<button pButton label="{{'auth.login.login' | translate}}" class="btn login-form-submit-btn" (click)="login()"
|
||||
[disabled]="loginForm.invalid"></button>
|
||||
[disabled]="loginForm.invalid || !basicLoginFeatureFlags"></button>
|
||||
</div>
|
||||
<div class="login-form-sub-button-wrapper" *ngIf="!code && !state">
|
||||
<div class="login-form-sub-btn-wrapper">
|
||||
<button pButton label="{{'auth.login.login_with_discord' | translate}}" class="btn login-form-sub-btn" (click)="discordLogin()"></button>
|
||||
<button pButton label="{{'auth.login.login_with_discord' | translate}}" class="btn login-form-sub-btn"
|
||||
(click)="discordLogin()"></button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="login-form-sub-button-wrapper">
|
||||
<div class="login-form-sub-btn-wrapper">
|
||||
<button pButton label="{{'auth.login.register' | translate}}" class="btn login-form-sub-btn"
|
||||
(click)="register()"></button>
|
||||
(click)="register()" [disabled]="!basicRegistrationFeatureFlags"></button>
|
||||
</div>
|
||||
<div class="login-form-sub-btn-wrapper">
|
||||
<button pButton label="{{'auth.login.forgot_password' | translate}}"
|
||||
|
@@ -13,6 +13,7 @@ import { ThemeService } from "src/app/services/theme/theme.service";
|
||||
import { throwError } from "rxjs";
|
||||
import { TranslateService } from "@ngx-translate/core";
|
||||
import { ConfirmationDialogService } from "../../../../services/confirmation-dialog/confirmation-dialog.service";
|
||||
import { GuiService } from "../../../../services/gui/gui.service";
|
||||
|
||||
@Component({
|
||||
selector: "app-login",
|
||||
@@ -32,6 +33,8 @@ export class LoginComponent implements OnInit {
|
||||
state!: string;
|
||||
user!: AuthUserDTO;
|
||||
oAuthId!: string;
|
||||
public basicLoginFeatureFlags = false;
|
||||
public basicRegistrationFeatureFlags = false;
|
||||
|
||||
constructor(
|
||||
private authService: AuthService,
|
||||
@@ -41,11 +44,19 @@ export class LoginComponent implements OnInit {
|
||||
private themeService: ThemeService,
|
||||
private route: ActivatedRoute,
|
||||
private confirmDialog: ConfirmationDialogService,
|
||||
private translate: TranslateService
|
||||
private translate: TranslateService,
|
||||
private gui: GuiService
|
||||
) {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.gui.hasFeatureFlag("BasicLogin").subscribe(flag => {
|
||||
this.basicLoginFeatureFlags = flag.value;
|
||||
});
|
||||
this.gui.hasFeatureFlag("BasicRegistration").subscribe(flag => {
|
||||
this.basicRegistrationFeatureFlags = flag.value;
|
||||
});
|
||||
|
||||
this.initLoginForm();
|
||||
this.spinnerService.showSpinner();
|
||||
if (this.authService.isLoggedIn$.value) {
|
||||
@@ -107,8 +118,8 @@ export class LoginComponent implements OnInit {
|
||||
|
||||
initLoginForm(): void {
|
||||
this.loginForm = this.formBuilder.group({
|
||||
email: ["", [Validators.required, Validators.email]],
|
||||
password: ["", [Validators.required, Validators.minLength(8)]]
|
||||
email: [{ value: "", disabled: !this.basicLoginFeatureFlags }, [Validators.required, Validators.email]],
|
||||
password: [{ value: "", disabled: !this.basicLoginFeatureFlags }, [Validators.required, Validators.minLength(8)]]
|
||||
});
|
||||
}
|
||||
|
||||
|
@@ -2,9 +2,10 @@
|
||||
<div class="login-form-wrapper register-form-wrapper">
|
||||
<div class="login-form">
|
||||
<form [formGroup]="loginForm">
|
||||
<h1>sh-edraft.de</h1>
|
||||
<h1>{{'auth.header' | translate}}</h1>
|
||||
<div class="input-field">
|
||||
<input type="text" pInputText formControlName="firstName" placeholder="{{'auth.register.first_name' | translate}}"
|
||||
<input type="text" pInputText formControlName="firstName"
|
||||
placeholder="{{'auth.register.first_name' | translate}}"
|
||||
autocomplete="given-name">
|
||||
<div *ngIf="submitted" class="invalid-feedback">
|
||||
<div
|
||||
@@ -15,7 +16,8 @@
|
||||
</div>
|
||||
|
||||
<div class="input-field">
|
||||
<input type="text" pInputText formControlName="lastName" placeholder="{{'auth.register.last_name' | translate}}"
|
||||
<input type="text" pInputText formControlName="lastName"
|
||||
placeholder="{{'auth.register.last_name' | translate}}"
|
||||
autocomplete="family-name">
|
||||
<div *ngIf="submitted" class="invalid-feedback">
|
||||
<div
|
||||
@@ -74,12 +76,14 @@
|
||||
</div>
|
||||
|
||||
<div class="login-form-submit">
|
||||
<button pButton label="{{'auth.register.register' | translate}}" class="btn login-form-submit-btn" (click)="register()"
|
||||
<button pButton label="{{'auth.register.register' | translate}}" class="btn login-form-submit-btn"
|
||||
(click)="register()"
|
||||
[disabled]="loginForm.invalid"></button>
|
||||
</div>
|
||||
<div class="login-form-sub-button-wrapper">
|
||||
<div class="login-form-sub-btn-wrapper">
|
||||
<button pButton label="{{'auth.register.login' | translate}}" class="btn login-form-sub-btn" (click)="login()"></button>
|
||||
<button pButton label="{{'auth.register.login' | translate}}" class="btn login-form-sub-btn"
|
||||
(click)="login()"></button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
@@ -12,6 +12,7 @@ import { SpinnerService } from "src/app/services/spinner/spinner.service";
|
||||
import { Subject, throwError } from "rxjs";
|
||||
import { TranslateService } from "@ngx-translate/core";
|
||||
import { SettingsService } from "../../../../services/settings/settings.service";
|
||||
import { GuiService } from "../../../../services/gui/gui.service";
|
||||
|
||||
@Component({
|
||||
selector: "app-registration",
|
||||
@@ -46,7 +47,8 @@ export class RegistrationComponent implements OnInit, OnDestroy {
|
||||
private spinnerService: SpinnerService,
|
||||
private route: ActivatedRoute,
|
||||
private translate: TranslateService,
|
||||
private settings: SettingsService
|
||||
private settings: SettingsService,
|
||||
private gui: GuiService
|
||||
) {
|
||||
this.spinnerService.showSpinner();
|
||||
if (this.authService.isLoggedIn$.value) {
|
||||
@@ -56,6 +58,14 @@ export class RegistrationComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.gui.hasFeatureFlag("BasicRegistration").subscribe(flag => {
|
||||
if (flag.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.router.navigate(["/auth/login"]);
|
||||
});
|
||||
|
||||
this.translate.onLangChange.pipe(takeUntil(this.unsubscriber)).subscribe(lang => {
|
||||
this.confirmPrivacyString = this.translate.instant("auth.register.confirm_privacy", { url: this.settings.getPrivacyURL() });
|
||||
});
|
||||
|
@@ -31,7 +31,9 @@
|
||||
<div class="content-column">
|
||||
<div class="content-data-name">{{'view.server.profile.xp' | translate}}:</div>
|
||||
<div *ngIf="!isEditing" class="content-data-value">{{user.xp}}</div>
|
||||
<div *ngIf="isModerator && isEditing" class="content-data-value"><input class="table-edit-input" pInputText min="0" type="number" [(ngModel)]="user.xp"></div>
|
||||
<div *ngIf="isModerator && isEditing" class="content-data-value"><input class="table-edit-input" pInputText
|
||||
min="0" type="number"
|
||||
[(ngModel)]="user.xp"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -71,7 +73,8 @@
|
||||
<div class="content-data-name">{{'view.server.profile.level' | translate}}:</div>
|
||||
<div *ngIf="!isEditing" class="content-data-value">{{user.level?.name}}</div>
|
||||
<div *ngIf="isModerator && isEditing" class="content-data-value">
|
||||
<p-dropdown [options]="levels" [(ngModel)]="user.level" dataKey="id" placeholder="{{'common.level' | translate}}">
|
||||
<p-dropdown [options]="levels" [(ngModel)]="user.level" dataKey="id"
|
||||
placeholder="{{'common.level' | translate}}">
|
||||
</p-dropdown>
|
||||
</div>
|
||||
</div>
|
||||
@@ -100,7 +103,8 @@
|
||||
|
||||
<div>
|
||||
<div class="content-divider"></div>
|
||||
<p-table #dt [value]="(user.userWarnings ?? [])" [responsive]="true" responsiveLayout="stack" [breakpoint]="'720px'" dataKey="id" editMode="row">
|
||||
<p-table #dt [value]="(user.userWarnings ?? [])" [responsive]="true" responsiveLayout="stack"
|
||||
[breakpoint]="'720px'" dataKey="id" editMode="row">
|
||||
<ng-template pTemplate="caption">
|
||||
<div class="table-caption">
|
||||
<div class="table-caption-table-info">
|
||||
@@ -178,10 +182,13 @@
|
||||
</td>
|
||||
<td>
|
||||
<div class="btn-wrapper">
|
||||
<button *ngIf="!editing" pButton type="button" class="btn danger-icon-btn" icon="pi pi-trash" (click)="deleteUserWarning(ri)"></button>
|
||||
<button *ngIf="!editing" pButton type="button" class="btn danger-icon-btn" icon="pi pi-trash"
|
||||
(click)="deleteUserWarning(ri)"></button>
|
||||
|
||||
<button *ngIf="editing" pButton type="button" pSaveEditableRow class="btn icon-btn" icon="pi pi-check" (click)="editSaveUserWarning(value, ri)"></button>
|
||||
<button *ngIf="editing" pButton type="button" pCancelEditableRow class="btn danger-icon-btn" icon="pi pi-times"
|
||||
<button *ngIf="editing" pButton type="button" pSaveEditableRow class="btn icon-btn" icon="pi pi-check"
|
||||
(click)="editSaveUserWarning(value, ri)"></button>
|
||||
<button *ngIf="editing" pButton type="button" pCancelEditableRow class="btn danger-icon-btn"
|
||||
icon="pi pi-times"
|
||||
(click)="editCancelUserWarning(ri)"></button>
|
||||
</div>
|
||||
</td>
|
||||
@@ -215,16 +222,22 @@
|
||||
</div>
|
||||
</p-panel>
|
||||
|
||||
<p-panel header="{{'view.server.profile.joined_voice_channel.header' | translate}}" [toggleable]="true" [collapsed]="true"
|
||||
<p-panel header="{{'view.server.profile.joined_voice_channel.header' | translate}}" [toggleable]="true"
|
||||
[collapsed]="true"
|
||||
(onBeforeToggle)="onBeforeToggle($event.event, $event.collapsed)">
|
||||
<div *ngFor="let join of user.joinedVoiceChannels;">
|
||||
<div class="content-row">
|
||||
<div class="content-column">
|
||||
<div class="content-column" style="flex: 0.3;">
|
||||
<div class="content-data-name">{{'common.id' | translate}}:</div>
|
||||
<div class="content-data-value">{{join.id}}</div>
|
||||
</div>
|
||||
|
||||
<div class="content-column" style="flex: 0.75;">
|
||||
<div class="content-data-name">{{'view.server.profile.joined_voice_channel.time' | translate}}:</div>
|
||||
<div class="content-data-value">{{join.time}} {{'general.hours' | translate}}</div>
|
||||
</div>
|
||||
|
||||
<div class="content-column">
|
||||
<div class="content-column" style="flex: 2;">
|
||||
<div class="content-data-name">{{'view.server.profile.joined_voice_channel.channel' | translate}}:</div>
|
||||
<div class="content-data-value">{{join.channelName}}</div>
|
||||
</div>
|
||||
@@ -242,7 +255,8 @@
|
||||
</div>
|
||||
</p-panel>
|
||||
|
||||
<p-panel header="{{'view.server.profile.joined_game_server.header' | translate}}" [toggleable]="true" [collapsed]="true"
|
||||
<p-panel header="{{'view.server.profile.joined_game_server.header' | translate}}" [toggleable]="true"
|
||||
[collapsed]="true"
|
||||
(onBeforeToggle)="onBeforeToggle($event.event, $event.collapsed)">
|
||||
<div *ngFor="let join of user.userJoinedGameServers;">
|
||||
<div class="content-row">
|
||||
|
@@ -1,24 +1,25 @@
|
||||
import { HttpClient, HttpHeaders } from '@angular/common/http';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Observable, Subscribable } from 'rxjs';
|
||||
import { SettingsDTO } from 'src/app/models/config/settings.dto';
|
||||
import { SoftwareVersionDTO } from 'src/app/models/config/software-version.dto';
|
||||
import { SettingsService } from '../settings/settings.service';
|
||||
import { HttpClient, HttpHeaders } from "@angular/common/http";
|
||||
import { Injectable } from "@angular/core";
|
||||
import { Observable } from "rxjs";
|
||||
import { SettingsDTO } from "src/app/models/config/settings.dto";
|
||||
import { SoftwareVersionDTO } from "src/app/models/config/software-version.dto";
|
||||
import { SettingsService } from "../settings/settings.service";
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
providedIn: "root"
|
||||
})
|
||||
export class GuiService {
|
||||
|
||||
constructor(
|
||||
private appsettings: SettingsService,
|
||||
private http: HttpClient,
|
||||
) { }
|
||||
private http: HttpClient
|
||||
) {
|
||||
}
|
||||
|
||||
getApiVersion(): Observable<SoftwareVersionDTO> {
|
||||
return this.http.get<SoftwareVersionDTO>(`${this.appsettings.getApiURL()}/api/gui/api-version`, {
|
||||
headers: new HttpHeaders({
|
||||
'Content-Type': 'application/json'
|
||||
"Content-Type": "application/json"
|
||||
})
|
||||
});
|
||||
}
|
||||
@@ -26,7 +27,7 @@ export class GuiService {
|
||||
getSettings(): Observable<SettingsDTO> {
|
||||
return this.http.get<SettingsDTO>(`${this.appsettings.getApiURL()}/api/gui/settings`, {
|
||||
headers: new HttpHeaders({
|
||||
'Content-Type': 'application/json'
|
||||
"Content-Type": "application/json"
|
||||
})
|
||||
});
|
||||
}
|
||||
@@ -34,7 +35,18 @@ export class GuiService {
|
||||
sendTestMail(mail: string): Observable<unknown> {
|
||||
return this.http.post(`${this.appsettings.getApiURL()}/api/gui/send-test-mail/${mail}`, {
|
||||
headers: new HttpHeaders({
|
||||
'Content-Type': 'application/json'
|
||||
"Content-Type": "application/json"
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
hasFeatureFlag(flag: string): Observable<{ key: string, value: boolean }> {
|
||||
return this.http.get<{
|
||||
key: string,
|
||||
value: boolean
|
||||
}>(`${this.appsettings.getApiURL()}/api/gui/has-feature-flag/${flag}`, {
|
||||
headers: new HttpHeaders({
|
||||
"Content-Type": "application/json"
|
||||
})
|
||||
});
|
||||
}
|
||||
|
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"WebVersion": {
|
||||
"Major": "1",
|
||||
"Minor": "2",
|
||||
"Micro": "dev410"
|
||||
}
|
||||
}
|
||||
"WebVersion": {
|
||||
"Major": "1",
|
||||
"Minor": "2",
|
||||
"Micro": "2"
|
||||
}
|
||||
}
|
||||
|
@@ -176,15 +176,16 @@ header {
|
||||
flex-direction: row;
|
||||
flex: 1;
|
||||
margin: 5px 0;
|
||||
gap: 5px;
|
||||
|
||||
.content-column {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.content-data-name {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
|
||||
font-size: 18px;
|
||||
@@ -192,7 +193,6 @@ header {
|
||||
|
||||
.content-data-value {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
|
||||
font-size: 18px;
|
||||
|
Reference in New Issue
Block a user