29 Commits

Author SHA1 Message Date
db61a764eb Merge pull request 'staging' (#447) from staging into master
All checks were successful
Deploy prod on push / on-push-deploy_sh-edraft (push) Successful in 3m45s
Reviewed-on: #447
2023-12-02 19:34:22 +01:00
919eef79f6 Merge branch 'master' into staging
All checks were successful
Deploy staging on push / on-push-deploy_sh-edraft (push) Successful in 3m58s
2023-12-02 19:33:58 +01:00
a2dd447dbd Always check scheduled_events after scheduled_event update #410
All checks were successful
Deploy staging on push / on-push-deploy_sh-edraft (push) Successful in 4m26s
Deploy dev on push / on-push-deploy_sh-edraft (push) Successful in 3m33s
2023-12-02 15:32:16 +01:00
8a76b46165 Improved event time loading #410
All checks were successful
Deploy dev on push / on-push-deploy_sh-edraft (push) Successful in 5m12s
Deploy staging on push / on-push-deploy_sh-edraft (push) Successful in 3m40s
2023-12-02 15:24:13 +01:00
af3084ad36 Fixed scheduled event end update #410
All checks were successful
Deploy staging on push / on-push-deploy_sh-edraft (push) Successful in 4m41s
Deploy dev on push / on-push-deploy_sh-edraft (push) Successful in 3m39s
2023-12-01 21:04:29 +01:00
285b8bdbe4 Fixed scheduled events #410
All checks were successful
Deploy staging on push / on-push-deploy_sh-edraft (push) Successful in 4m44s
2023-12-01 20:40:44 +01:00
e2da4f09ee Fixed command
All checks were successful
Deploy staging on push / on-push-deploy_sh-edraft (push) Successful in 4m50s
2023-12-01 20:02:42 +01:00
4ed99da689 Merge pull request 'dev' (#445) from dev into staging
All checks were successful
Deploy staging on push / on-push-deploy_sh-edraft (push) Successful in 3m32s
Reviewed-on: #445
2023-12-01 19:55:54 +01:00
bc94d31a8d Added logs cleanup
All checks were successful
Deploy dev on push / on-push-deploy_sh-edraft (push) Successful in 4m30s
2023-12-01 19:50:18 +01:00
0d3db75190 Improved scheduled_event handling #410
Some checks reported warnings
Deploy dev on push / on-push-deploy_sh-edraft (push) Has been cancelled
2023-12-01 19:06:11 +01:00
090f217f93 Fixed scheduled_events #410
All checks were successful
Deploy staging on push / on-push-deploy_sh-edraft (push) Successful in 3m37s
Deploy dev on push / on-push-deploy_sh-edraft (push) Successful in 3m39s
2023-11-30 17:57:43 +01:00
b98828fce3 Fixed mass move #444 2023-11-30 17:54:31 +01:00
5f8ae787f0 Improved profile voice states & fixed new vs handling
All checks were successful
Deploy staging on push / on-push-deploy_sh-edraft (push) Successful in 5m31s
2023-11-20 09:28:53 +01:00
0c807a7de7 Merge pull request 'dev into staging' (#442) from dev into staging
All checks were successful
Deploy staging on push / on-push-deploy_sh-edraft (push) Successful in 3m56s
Reviewed-on: #442
Reviewed-by: edraft-dev <dev.sven.heidemann@sh-edraft.de>
2023-11-19 14:49:59 +01:00
2dc60acaa6 Merge branch 'staging' into dev
All checks were successful
Deploy dev on push / on-push-deploy_sh-edraft (push) Successful in 4m45s
2023-11-19 14:42:42 +01:00
29ea96a5e5 Fixed new scheduled event
All checks were successful
Deploy dev on push / on-push-deploy_sh-edraft (push) Successful in 5m4s
2023-11-19 14:35:26 +01:00
026331b397 Merge pull request '#440' (#441) from #440 into dev
All checks were successful
Deploy dev on push / on-push-deploy_sh-edraft (push) Successful in 4m43s
Reviewed-on: #441
2023-11-19 14:18:54 +01:00
bfe74ad1c5 Fixed frontend version #440 2023-11-19 14:17:47 +01:00
7c8c2bef70 Added flag handling to auth controller #440 2023-11-19 14:11:28 +01:00
4ccb57e6a3 Added feature flag for basic auth stuff #440 2023-11-19 14:01:26 +01:00
c8d3bf780d Merge pull request '#410' (#439) from #410 into dev
All checks were successful
Deploy dev on push / on-push-deploy_sh-edraft (push) Successful in 4m49s
Reviewed-on: #439
2023-11-19 12:57:42 +01:00
8788b727c5 Some fixes #410 2023-11-19 12:56:55 +01:00
5e9280d972 Fixed loop timer #410 2023-11-19 12:37:20 +01:00
bd856d0143 Added scheduled_event handler #410 2023-11-19 12:32:42 +01:00
da57063b68 Completed frontend #410 2023-11-19 00:31:25 +01:00
5de6710261 Merge pull request 'staging' (#435) from staging into master
All checks were successful
Deploy prod on push / on-push-deploy_sh-edraft (push) Successful in 3m34s
Reviewed-on: #435
2023-11-07 20:37:13 +01:00
e99e272029 Merge branch 'master' into staging
All checks were successful
Deploy staging on push / on-push-deploy_sh-edraft (push) Successful in 3m12s
2023-11-07 20:37:03 +01:00
650f612a6b Fixed base on member join
All checks were successful
Deploy staging on push / on-push-deploy_sh-edraft (push) Successful in 4m19s
2023-11-07 20:36:29 +01:00
d2c37a0098 Merge pull request 'staging' (#434) from staging into master
All checks were successful
Deploy prod on push / on-push-deploy_sh-edraft (push) Successful in 3m15s
Reviewed-on: #434
2023-11-07 18:21:58 +01:00
41 changed files with 978 additions and 198 deletions

View 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}"))
)

View File

@@ -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)

View File

@@ -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

View File

@@ -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)),
}

View File

@@ -21,7 +21,7 @@ class TaskABC(commands.Cog):
@ServiceProviderABC.inject
async def _wait_until_ready(self, config: ConfigurationABC, logger: TaskLogger, bot: DiscordBotServiceABC):
logger.debug(__name__, f"Waiting before {type(self).__name__}")
logger.debug(__name__, f"Waiting before ready {type(self).__name__}")
await bot.wait_until_ready()
async def wait():

View File

@@ -28,3 +28,5 @@ class FeatureFlagsEnum(Enum):
technician_full_access = "TechnicianFullAccess"
steam_special_offers = "SteamSpecialOffers"
scheduled_events = "ScheduledEvents"
basic_registration = "BasicRegistration"
basic_login = "BasicLogin"

View File

@@ -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):

View File

@@ -154,7 +154,7 @@ class ScheduledEvent(TableABC):
{"NULL" if self._channel_id is None else f"'{self._channel_id}'"},
'{self._start_time}',
{"NULL" if self._end_time is None else f"'{self._end_time}'"},
'{self._entity_type}',
'{self._entity_type.value}',
{"NULL" if self._location is None else f"'{self._location}'"},
{self._server.id}
);
@@ -172,7 +172,7 @@ class ScheduledEvent(TableABC):
`ChannelId` = {"NULL" if self._channel_id is None else f"'{self._channel_id}'"},
`StartTime` = '{self._start_time}',
`EndTime` = {"NULL" if self._end_time is None else f"'{self._end_time}'"},
`EntityType` = '{self._entity_type}',
`EntityType` = '{self._entity_type.value}',
`Location` = {"NULL" if self._location is None else f"'{self._location}'"}
WHERE `Id` = {self._id};
"""

View File

@@ -2,6 +2,7 @@ from typing import Optional
from cpl_core.database.context import DatabaseContextABC
from cpl_query.extension import List
from discord import EntityType
from bot_core.logging.database_logger import DatabaseLogger
from bot_data.abc.server_repository_abc import ServerRepositoryABC
@@ -39,7 +40,7 @@ class ScheduledEventRepositoryService(ScheduledEventRepositoryABC):
int(self._get_value_from_result(sql_result[4])), # channel_id
self._get_value_from_result(sql_result[5]), # start_time
self._get_value_from_result(sql_result[6]), # end_time
self._get_value_from_result(sql_result[7]), # entity_type
EntityType(int(self._get_value_from_result(sql_result[7]))), # entity_type
self._get_value_from_result(sql_result[8]), # location
self._servers.get_server_by_id((sql_result[9])), # server
self._get_value_from_result(sql_result[10]), # created_at

View File

@@ -50,4 +50,6 @@ type Query {
technicianConfig: TechnicianConfig
possibleFeatureFlags: [String]
discord: Discord
hasFeatureFlag(flag: String): FeatureFlag
}

View File

@@ -2,6 +2,7 @@ from datetime import datetime
from cpl_core.database.context import DatabaseContextABC
from cpl_discord.service import DiscordBotServiceABC
from discord import EntityType
from bot_data.abc.server_repository_abc import ServerRepositoryABC
from bot_data.abc.scheduled_event_repository_abc import ScheduledEventRepositoryABC
@@ -44,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,
)
@@ -85,7 +86,9 @@ class ScheduledEventMutation(QueryABC):
if "endTime" in input
else scheduled_event.end_time
)
scheduled_event.entity_type = input["entityType"] if "entityType" in input else scheduled_event.entity_type
scheduled_event.entity_type = (
EntityType(int(input["entityType"])) if "entityType" in input else scheduled_event.entity_type
)
scheduled_event.location = input["location"] if "location" in input else scheduled_event.location
self._scheduled_events.update_scheduled_event(scheduled_event)

View File

@@ -18,6 +18,6 @@ class ScheduledEventQuery(DataQueryWithHistoryABC):
self.set_field("channelId", lambda x, *_: x.channel_id)
self.set_field("startTime", lambda x, *_: x.start_time)
self.set_field("endTime", lambda x, *_: x.end_time)
self.set_field("entityType", lambda x, *_: x.entity_type)
self.set_field("entityType", lambda x, *_: x.entity_type.value)
self.set_field("location", lambda x, *_: x.location)
self.set_field("server", lambda x, *_: x.server)

View File

@@ -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"])),
}

View File

@@ -7,7 +7,8 @@ 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.birthday_watcher import BirthdayWatcher
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
from modules.base.command.help_command import HelpCommand
@@ -45,6 +46,7 @@ from modules.base.events.base_on_voice_state_update_event_scheduled_event_bonus
from modules.base.forms.bug_report_form import BugReportForm
from modules.base.forms.complaint_form import ComplaintForm
from modules.base.helper.base_reaction_handler import BaseReactionHandler
from modules.base.tasks.scheduled_events_watcher import ScheduledEventsWatcher
from modules.base.service.event_service import EventService
from modules.base.service.user_warnings_service import UserWarningsService
@@ -61,6 +63,7 @@ class BaseModule(ModuleABC):
services.add_singleton(EventService)
services.add_transient(UserWarningsService)
services.add_singleton(TaskABC, BirthdayWatcher)
services.add_singleton(TaskABC, ScheduledEventsWatcher)
# forms
services.add_transient(BugReportForm)
@@ -81,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)

View File

@@ -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}")

View 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)

View File

@@ -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()))

View File

@@ -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")

View File

@@ -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()

View File

@@ -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)
]

View File

@@ -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)

View File

@@ -11,7 +11,7 @@ Discord bot for customers of sh-edraft.de
"""
__title__ = "modules.base.thread"
__title__ = "modules.base.tasks"
__author__ = "Sven Heidemann"
__license__ = "MIT"
__copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de"
@@ -20,7 +20,7 @@ __version__ = "1.2.2"
from collections import namedtuple
# imports
# imports:
VersionInfo = namedtuple("VersionInfo", "major minor micro")
version_info = VersionInfo(major="1", minor="2", micro="2")

View File

@@ -0,0 +1,51 @@
from cpl_core.configuration import ConfigurationABC
from cpl_core.database.context import DatabaseContextABC
from cpl_discord.service import DiscordBotServiceABC
from cpl_translation import TranslatePipe
from discord.ext import tasks
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.server_repository_abc import ServerRepositoryABC
from modules.base.service.event_service import EventService
class ScheduledEventsWatcher(TaskABC):
def __init__(
self,
config: ConfigurationABC,
logger: TaskLogger,
bot: DiscordBotServiceABC,
db: DatabaseContextABC,
servers: ServerRepositoryABC,
events: EventService,
message_service: MessageService,
t: TranslatePipe,
):
TaskABC.__init__(self)
self._config = config
self._logger = logger
self._bot = bot
self._db = db
self._servers = servers
self._events = events
self._message_service = message_service
self._t = t
if not self._is_maintenance():
self.watch.start()
@tasks.loop(hours=12)
async def watch(self):
self._logger.info(__name__, "Watching scheduled events")
try:
for guild in self._bot.guilds:
await self._events.check_and_create_scheduled_events(guild)
except Exception as e:
self._logger.error(__name__, f"Watching scheduled events failed", e)
@watch.before_loop
async def wait(self):
await self._wait_until_ready()

View File

@@ -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"
}
}

View File

@@ -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) {

View File

@@ -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}}"

View File

@@ -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)]]
});
}

View File

@@ -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>

View File

@@ -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() });
});

View File

@@ -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">

View File

@@ -58,7 +58,7 @@
<div style="display: flex; flex-direction: column; gap: 5px;">
{{'view.server.scheduled_events.edit_dialog.event_info.end_date_time' | translate}}:
<p-calendar formControlName="endTime" dateFormat="dd.mm.yy" [showIcon]="true"
[showTime]="true" [stepMinute]="15"></p-calendar>
[showTime]="true"></p-calendar>
</div>
<div style="display: flex; flex-direction: column; gap: 5px;">

View File

@@ -251,7 +251,7 @@ export class ScheduledEventsComponent extends ComponentWithTable implements OnIn
})).subscribe(result => {
this.isEditingNew = false;
this.spinner.hideSpinner();
this.toastService.success(this.translate.instant("view.server.ScheduledEvents.message.scheduled_event_create"), this.translate.instant("view.server.ScheduledEvents.message.scheduled_event_create_d", { name: result.scheduledEvent.createScheduledEvent?.name }));
this.toastService.success(this.translate.instant("view.server.scheduled_events.message.scheduled_event_create"), this.translate.instant("view.server.scheduled_events.message.scheduled_event_create_d", { name: result.scheduledEvent.createScheduledEvent?.name }));
this.loadNextPage();
});
return;
@@ -275,14 +275,14 @@ export class ScheduledEventsComponent extends ComponentWithTable implements OnIn
return throwError(err);
})).subscribe(_ => {
this.spinner.hideSpinner();
this.toastService.success(this.translate.instant("view.server.ScheduledEvents.message.scheduled_event_update"), this.translate.instant("view.server.ScheduledEvents.message.scheduled_event_update_d", { name: newScheduledEvent.name }));
this.toastService.success(this.translate.instant("view.server.scheduled_events.message.scheduled_event_update"), this.translate.instant("view.server.scheduled_events.message.scheduled_event_update_d", { name: newScheduledEvent.name }));
this.loadNextPage();
});
}
public deleteScheduledEvent(ScheduledEvent: ScheduledEvent): void {
this.confirmDialog.confirmDialog(
this.translate.instant("view.server.ScheduledEvents.message.scheduled_event_delete"), this.translate.instant("view.server.ScheduledEvents.message.scheduled_event_delete_q", { name: ScheduledEvent.name }),
this.translate.instant("view.server.scheduled_events.message.scheduled_event_delete"), this.translate.instant("view.server.scheduled_events.message.scheduled_event_delete_q", { name: ScheduledEvent.name }),
() => {
this.spinner.showSpinner();
this.data.mutation<ScheduledEventMutationResult>(Mutations.deleteScheduledEvent, {
@@ -293,7 +293,7 @@ export class ScheduledEventsComponent extends ComponentWithTable implements OnIn
return throwError(err);
})).subscribe(l => {
this.spinner.hideSpinner();
this.toastService.success(this.translate.instant("view.server.ScheduledEvents.message.scheduled_event_deleted"), this.translate.instant("view.server.ScheduledEvents.message.scheduled_event_deleted_d", { name: ScheduledEvent.name }));
this.toastService.success(this.translate.instant("view.server.scheduled_events.message.scheduled_event_deleted"), this.translate.instant("view.server.scheduled_events.message.scheduled_event_deleted_d", { name: ScheduledEvent.name }));
this.loadNextPage();
});
});
@@ -302,15 +302,5 @@ export class ScheduledEventsComponent extends ComponentWithTable implements OnIn
public addScheduledEvent(): void {
this.isEditingNew = true;
this.editableScheduledEvent = JSON.parse(JSON.stringify(this.newScheduledEventTemplate));
// const newScheduledEvent = JSON.parse(JSON.stringify(this.newScheduledEventTemplate));
//
// this.scheduledEvents = [newScheduledEvent, ...this.scheduledEvents];
//
// table.initRowEdit(newScheduledEvent);
//
// const index = this.scheduledEvents.findIndex(l => l.id == newScheduledEvent.id);
// this.onRowEditInit(table, newScheduledEvent, index);
//
// this.isEditingNew = true;
}
}

View File

@@ -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"
})
});
}

View File

@@ -555,11 +555,11 @@
"event_info": {
"description": "Beschreibung",
"description_input": "Erzähl den Leuten ein wenig mehr über dein Event. Markdown, neue Zeilen und Links werden unterstützt.",
"end_date_time": "Endzeitpunkt",
"event_topic": "Thema",
"event_topic_input": "Worum geht es bei deinem Event?",
"header": "Worum geht es bei deinem Event?",
"start_date_time": "Startzeitpunkt",
"end_date_time": "Endzeitpunkt",
"tab_name": "Eventinformationen"
},
"location": {
@@ -567,8 +567,8 @@
"interval": "Interval",
"intervals": {
"daily": "Täglich",
"weekly": "Wöchentlich",
"monthly": "Monatlich",
"weekly": "Wöchentlich",
"yearly": "Jährlich"
},
"somewhere_else": "Irgendwo anders",
@@ -581,6 +581,22 @@
}
},
"header": "Geplante Events",
"message": {
"scheduled_event_create": "Geplantes Event erstellt",
"scheduled_event_create_d": "Geplantes Event {{name}} erfolgreich erstellt",
"scheduled_event_create_failed": "Geplantes Event Erstellung fehlgeschlagen",
"scheduled_event_create_failed_d": "Die Erstellung der Geplantes Event ist fehlgeschlagen!",
"scheduled_event_delete": "Geplantes Event löschen",
"scheduled_event_delete_failed": "Geplantes Event Löschung fehlgeschlagen",
"scheduled_event_delete_failed_d": "Die Löschung der Geplantes Event {{name}} ist fehlgeschlagen!",
"scheduled_event_delete_q": "Sind Sie sich sicher, dass Sie das Geplantes Event {{name}} löschen möchten?",
"scheduled_event_deleted": "Geplantes Event gelöscht",
"scheduled_event_deleted_d": "Geplantes Event {{name}} erfolgreich gelöscht",
"scheduled_event_update": "Geplantes Event bearbeitet",
"scheduled_event_update_d": "Geplantes Event {{name}} erfolgreich bearbeitet",
"scheduled_event_update_failed": "Geplantes Event Bearbeitung fehlgeschlagen",
"scheduled_event_update_failed_d": "Die Bearbeitung der Geplantes Event ist fehlgeschlagen!"
},
"scheduled_events": "Geplante Events"
},
"short_role_names": {

View File

@@ -146,6 +146,7 @@
"edit": "Edit",
"email": "E-Mail",
"emoji": "Emoji",
"end_time": "End time",
"error": "Error",
"export": "Export",
"feature_flags": "Features",
@@ -179,11 +180,13 @@
},
"id": "Id",
"import": "Import",
"interval": "interval",
"joined_at": "Joined at",
"last_name": "Last name",
"leaved_at": "Leaved at",
"left_server": "Active",
"level": "Level",
"location": "Location",
"message_id": "Message Id",
"min_xp": "Min. XP",
"modified_at": "Modified at",
@@ -203,10 +206,12 @@
"role": "Role",
"rule_count": "Rules",
"save": "Save",
"start_time": "Start time",
"state": {
"off": "Off",
"on": "On"
},
"type": "Type",
"user_warnings": "User warnings",
"users": "User",
"value": "Value",
@@ -330,6 +335,7 @@
"config": "Configuration",
"dashboard": "Dashboard",
"members": "Members",
"scheduled_events": "Scheduled event",
"server": {
"achievements": "Achievements",
"auto_roles": "Auto role",
@@ -547,16 +553,24 @@
"add_header": "Add event",
"edit_header": "Edit event",
"event_info": {
"description": "Description",
"description_input": "Tell people a little more about your event. Markdown, new lines and links are supported.",
"end_date_time": "End date",
"event_topic": "Event topic",
"event_topic_input": "What's your event?",
"header": "What's your event about?",
"start_date": "Start date",
"start_time": "Start time",
"start_date_time": "Start date",
"tab_name": "Event info"
},
"location": {
"header": "Where is your event?",
"interval": "Interval",
"intervals": {
"daily": "Daily",
"monthly": "Monthly",
"weekly": "Weekly",
"yearly": "Yearly"
},
"somewhere_else": "Somewhere else",
"somewhere_else_input": "Enter a location",
"stage": "Stage channel",
@@ -567,6 +581,22 @@
}
},
"header": "Scheduled events",
"message": {
"scheduled_event_create": "Scheduled event created",
"scheduled_event_create_d": "Scheduled event {{name}} successfully created",
"scheduled_event_create_failed": "Scheduled event creation failed",
"scheduled_event_create_failed_d": "Creation of scheduled event failed!",
"scheduled_event_delete": "Delete scheduled event",
"scheduled_event_delete_failed": "Scheduled event deletion failed",
"scheduled_event_delete_failed_d": "Deletion of scheduled event {{name}} failed!",
"scheduled_event_delete_q": "Are you sure you want to delete the {{name}} scheduled_event?",
"scheduled_event_deleted": "Scheduled event deleted",
"scheduled_event_deleted_d": "Scheduled event {{name}} successfully deleted\t",
"scheduled_event_update": "Scheduled event edited",
"scheduled_event_update_d": "Scheduled event {{name}} edited successfully",
"scheduled_event_update_failed": "Scheduled event editing failed",
"scheduled_event_update_failed_d": "Scheduled event editing failed!"
},
"scheduled_events": "Scheduled events"
},
"short_role_names": {

View File

@@ -1,7 +1,7 @@
{
"WebVersion": {
"Major": "1",
"Minor": "2",
"Micro": "dev410"
}
}
"WebVersion": {
"Major": "1",
"Minor": "2",
"Micro": "2"
}
}

View File

@@ -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;

View File

@@ -187,12 +187,15 @@
}
}
.p-dialog-header {
background-color: $secondaryBackgroundColor !important;
background-color: $primaryBackgroundColor !important;
color: $primaryTextColor !important;
}
.p-dialog-content {
background-color: $secondaryBackgroundColor !important;
.content-data-name,
.content-data-value {
color: $primaryTextColor;
@@ -345,9 +348,11 @@
}
p-dropdown {
.p-dropdown {
background-color: $primaryBackgroundColor !important;
border-color: $primaryTextColor !important;
border: $default-border;
border-radius: 10px;
background-color: $secondaryBackgroundColor !important;
color: $primaryTextColor !important;
span {
@@ -411,8 +416,6 @@
}
}
}
}
.table-edit-input {
@@ -537,6 +540,10 @@
}
.icon-btn {
.p-button-label {
transition-duration: unset !important;
}
&:hover {
background-color: transparent !important;
color: $primaryHeaderColor !important;
@@ -544,6 +551,7 @@
}
}
.danger-btn,
.danger-icon-btn {
background-color: transparent !important;
color: $primaryTextColor !important;
@@ -560,25 +568,11 @@
}
}
.danger-btn {
background-color: $primaryErrorColor !important;
color: $primaryErrorColor !important;
border: 0 !important;
&:hover {
background-color: $primaryErrorColor !important;
color: $primaryTextColor !important;
border: 0;
}
.pi {
font-size: 1.275rem !important;
}
}
.p-datatable .p-sortable-column.p-highlight,
.p-datatable .p-sortable-column.p-highlight .p-sortable-column-icon {
.p-datatable .p-sortable-column.p-highlight .p-sortable-column-icon,
.p-datatable .p-sortable-column:not(.p-highlight):hover {
color: $primaryHeaderColor !important;
background-color: transparent !important;
}
.p-dropdown:not(.p-disabled):hover,
@@ -616,6 +610,27 @@
}
}
.p-selectbutton {
.p-highlight {
background-color: $primaryHeaderColor !important;
}
.p-button {
border: 1px solid $primaryHeaderColor !important;
&:hover {
background-color: $secondaryHeaderColor !important;
border: 1px solid $secondaryHeaderColor !important;
}
&:focus {
border-color: $primaryHeaderColor !important;
box-shadow: none !important;
}
}
}
.p-multiselect {
background-color: $primaryBackgroundColor !important;
color: $primaryTextColor !important;
@@ -661,4 +676,141 @@
}
}
}
.p-inputswitch.p-inputswitch-checked .p-inputswitch-slider {
background: $primaryHeaderColor !important;
}
.p-inputswitch.p-focus .p-inputswitch-slider {
box-shadow: none !important;
}
p-inputNumber {
background-color: $primaryBackgroundColor !important;
}
p-calendar > span > button {
background-color: $primaryHeaderColor !important;
border: 1px solid $primaryHeaderColor !important;
&:focus {
box-shadow: none !important;
}
}
.p-calendar {
.p-datepicker:not(.p-datepicker-inline) {
background-color: $secondaryBackgroundColor !important;
}
.p-datepicker {
color: $primaryTextColor !important;
.p-datepicker-header {
color: $primaryHeaderColor !important;
background-color: $primaryBackgroundColor !important;
.p-datepicker-title .p-datepicker-year,
.p-datepicker-title .p-datepicker-month,
.p-datepicker .p-datepicker-header .p-datepicker-title .p-datepicker-month {
color: $primaryTextColor !important;
&:hover {
color: $primaryHeaderColor !important;
}
&:focus {
box-shadow: none !important;
}
}
}
}
table td > span {
color: $primaryTextColor !important;
&:hover {
color: $primaryHeaderColor !important;
background-color: $primaryBackgroundColor !important;
}
&:focus {
box-shadow: none !important;
}
}
table td.p-datepicker-today > span {
color: $primaryHeaderColor !important;
background-color: $primaryBackgroundColor !important;
}
table td > span.p-highlight {
color: $primaryHeaderColor !important;
background-color: $primaryBackgroundColor !important;
}
.p-yearpicker .p-yearpicker-year,
.p-monthpicker .p-monthpicker-month:not(.p-disabled):not(.p-highlight) {
color: $primaryTextColor !important;
background-color: $secondaryBackgroundColor !important;
&:hover {
color: $primaryHeaderColor !important;
}
&:focus {
box-shadow: none !important;
}
}
}
.edit-dialog {
textarea {
background-color: $secondaryBackgroundColor;
color: $primaryTextColor;
&:hover {
border-color: $primaryHeaderColor;
}
&:focus {
border-color: $primaryHeaderColor;
}
}
.p-dialog-content {
.p-tabview {
.p-tabview-nav li .p-tabview-nav-link:not(.p-disabled):focus {
box-shadow: none !important;
}
.p-tabview-nav li.p-highlight .p-tabview-nav-link {
color: $primaryHeaderColor !important;
border-color: $primaryHeaderColor !important;
}
.p-tabview-nav,
.p-tabview-nav li .p-tabview-nav-link,
.p-tabview-panels {
background-color: $secondaryBackgroundColor !important;
color: $primaryTextColor !important;
}
}
}
}
.p-radiobutton {
.p-radiobutton-box.p-highlight {
border-color: $primaryHeaderColor !important;
background: $primaryHeaderColor !important;
}
.p-radiobutton-box:not(.p-disabled):not(.p-highlight):hover {
border-color: $primaryHeaderColor !important;
}
.p-radiobutton-box:not(.p-disabled).p-focus {
box-shadow: none !important;
}
}
}

View File

@@ -544,6 +544,7 @@
}
}
.danger-btn,
.danger-icon-btn {
background-color: transparent !important;
color: $primaryTextColor !important;
@@ -560,22 +561,6 @@
}
}
.danger-btn {
background-color: $primaryErrorColor !important;
color: $primaryErrorColor !important;
border: 0 !important;
&:hover {
background-color: $primaryErrorColor !important;
color: $primaryTextColor !important;
border: 0;
}
.pi {
font-size: 1.275rem !important;
}
}
.p-datatable .p-sortable-column.p-highlight,
.p-datatable .p-sortable-column.p-highlight .p-sortable-column-icon {
color: $primaryHeaderColor !important;
@@ -602,16 +587,23 @@
color: $primaryHeaderColor !important;
}
.input-number {
span {
.p-button {
background-color: $primaryHeaderColor !important;
border: 1px solid $primaryHeaderColor !important;
.p-selectbutton {
.p-highlight {
background-color: $primaryHeaderColor !important;
}
&:hover {
background-color: $secondaryHeaderColor !important;
border: 1px solid $secondaryHeaderColor !important;
}
.p-button {
border: 1px solid $primaryHeaderColor !important;
&:hover {
background-color: $secondaryHeaderColor !important;
border: 1px solid $secondaryHeaderColor !important;
}
&:focus {
border-color: $primaryHeaderColor !important;
box-shadow: none !important;
}
}
}
@@ -629,12 +621,12 @@
.p-multiselect-panel {
.p-multiselect-header {
background-color: $secondaryBackgroundColor !important;
background-color: $primaryBackgroundColor !important;
}
.p-multiselect-items,
.p-multiselect-item {
background-color: $primaryBackgroundColor !important;
background-color: $secondaryBackgroundColor !important;
}
.p-multiselect-item {
@@ -661,4 +653,141 @@
}
}
}
.p-inputswitch.p-inputswitch-checked .p-inputswitch-slider {
background: $primaryHeaderColor !important;
}
.p-inputswitch.p-focus .p-inputswitch-slider {
box-shadow: none !important;
}
p-inputNumber {
background-color: $primaryBackgroundColor !important;
}
p-calendar > span > button {
background-color: $primaryHeaderColor !important;
border: 1px solid $primaryHeaderColor !important;
&:focus {
box-shadow: none !important;
}
}
.p-calendar {
.p-datepicker:not(.p-datepicker-inline) {
background-color: $primaryBackgroundColor !important;
}
.p-datepicker {
color: $primaryTextColor !important;
.p-datepicker-header {
color: $primaryHeaderColor !important;
background-color: $primaryBackgroundColor !important;
.p-datepicker-title .p-datepicker-year,
.p-datepicker-title .p-datepicker-month,
.p-datepicker .p-datepicker-header .p-datepicker-title .p-datepicker-month {
color: $primaryTextColor !important;
&:hover {
color: $primaryHeaderColor !important;
}
&:focus {
box-shadow: none !important;
}
}
}
}
table td > span {
color: $primaryTextColor !important;
&:hover {
color: $primaryHeaderColor !important;
background-color: $primaryBackgroundColor !important;
}
&:focus {
box-shadow: none !important;
}
}
table td.p-datepicker-today > span {
color: $primaryHeaderColor !important;
background-color: $primaryBackgroundColor !important;
}
table td > span.p-highlight {
color: $primaryHeaderColor !important;
background-color: $primaryBackgroundColor !important;
}
.p-yearpicker .p-yearpicker-year,
.p-monthpicker .p-monthpicker-month:not(.p-disabled):not(.p-highlight) {
color: $primaryTextColor !important;
background-color: $secondaryBackgroundColor !important;
&:hover {
color: $primaryHeaderColor !important;
}
&:focus {
box-shadow: none !important;
}
}
}
.edit-dialog {
textarea {
background-color: $primaryBackgroundColor;
color: $primaryTextColor;
&:hover {
border-color: $primaryHeaderColor;
}
&:focus {
border-color: $primaryHeaderColor;
}
}
.p-dialog-content {
.p-tabview {
.p-tabview-nav li .p-tabview-nav-link:not(.p-disabled):focus {
box-shadow: none !important;
}
.p-tabview-nav li.p-highlight .p-tabview-nav-link {
color: $primaryHeaderColor !important;
border-color: $primaryHeaderColor !important;
}
.p-tabview-nav,
.p-tabview-nav li .p-tabview-nav-link,
.p-tabview-panels {
background-color: $primaryBackgroundColor !important;
color: $primaryTextColor !important;
}
}
}
}
.p-radiobutton {
.p-radiobutton-box.p-highlight {
border-color: $primaryHeaderColor !important;
background: $primaryHeaderColor !important;
}
.p-radiobutton-box:not(.p-disabled):not(.p-highlight):hover {
border-color: $primaryHeaderColor !important;
}
.p-radiobutton-box:not(.p-disabled).p-focus {
box-shadow: none !important;
}
}
}

View File

@@ -544,6 +544,7 @@
}
}
.danger-btn,
.danger-icon-btn {
background-color: transparent !important;
color: $primaryTextColor !important;
@@ -560,22 +561,6 @@
}
}
.danger-btn {
background-color: $primaryErrorColor !important;
color: $primaryErrorColor !important;
border: 0 !important;
&:hover {
background-color: $primaryErrorColor !important;
color: $primaryTextColor !important;
border: 0;
}
.pi {
font-size: 1.275rem !important;
}
}
.p-datatable .p-sortable-column.p-highlight,
.p-datatable .p-sortable-column.p-highlight .p-sortable-column-icon {
color: $primaryHeaderColor !important;
@@ -661,4 +646,142 @@
}
}
}
.p-inputswitch.p-inputswitch-checked .p-inputswitch-slider {
background: $primaryHeaderColor !important;
}
.p-inputswitch.p-focus .p-inputswitch-slider {
box-shadow: none !important;
}
p-inputNumber {
background-color: $primaryBackgroundColor !important;
}
p-calendar > span > button {
background-color: $primaryHeaderColor !important;
border: 1px solid $primaryHeaderColor !important;
&:focus {
box-shadow: none !important;
}
}
.p-calendar {
.p-datepicker:not(.p-datepicker-inline) {
background-color: $primaryBackgroundColor !important;
}
.p-datepicker {
color: $primaryTextColor !important;
.p-datepicker-header {
color: $primaryHeaderColor !important;
background-color: $primaryBackgroundColor !important;
.p-datepicker-title .p-datepicker-year,
.p-datepicker-title .p-datepicker-month,
.p-datepicker .p-datepicker-header .p-datepicker-title .p-datepicker-month {
color: $primaryTextColor !important;
&:hover {
color: $primaryHeaderColor !important;
}
&:focus {
box-shadow: none !important;
}
}
}
}
table td > span {
color: $primaryTextColor !important;
&:hover {
color: $primaryHeaderColor !important;
background-color: $primaryBackgroundColor !important;
}
&:focus {
box-shadow: none !important;
}
}
table td.p-datepicker-today > span {
color: $primaryHeaderColor !important;
background-color: $primaryBackgroundColor !important;
}
table td > span.p-highlight {
color: $primaryHeaderColor !important;
background-color: $primaryBackgroundColor !important;
}
.p-yearpicker .p-yearpicker-year,
.p-monthpicker .p-monthpicker-month:not(.p-disabled):not(.p-highlight) {
color: $primaryTextColor !important;
background-color: $secondaryBackgroundColor !important;
&:hover {
color: $primaryHeaderColor !important;
}
&:focus {
box-shadow: none !important;
}
}
}
.edit-dialog {
textarea {
background-color: $primaryBackgroundColor;
color: $primaryTextColor;
&:hover {
border-color: $primaryHeaderColor;
}
&:focus {
border-color: $primaryHeaderColor;
}
}
.p-dialog-content {
.p-tabview {
.p-tabview-nav li .p-tabview-nav-link:not(.p-disabled):focus {
box-shadow: none !important;
}
.p-tabview-nav li.p-highlight .p-tabview-nav-link {
color: $primaryHeaderColor !important;
border-color: $primaryHeaderColor !important;
}
.p-tabview-nav,
.p-tabview-nav li .p-tabview-nav-link,
.p-tabview-panels {
background-color: $primaryBackgroundColor !important;
color: $primaryTextColor !important;
}
}
}
}
.p-radiobutton {
.p-radiobutton-box.p-highlight {
border-color: $primaryHeaderColor !important;
background: $primaryHeaderColor !important;
}
.p-radiobutton-box:not(.p-disabled):not(.p-highlight):hover {
border-color: $primaryHeaderColor !important;
}
.p-radiobutton-box:not(.p-disabled).p-focus {
box-shadow: none !important;
}
}
}