dev into staging #442
@ -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():
|
||||
|
@ -27,3 +27,4 @@ class FeatureFlagsEnum(Enum):
|
||||
short_role_name = "ShortRoleName"
|
||||
technician_full_access = "TechnicianFullAccess"
|
||||
steam_special_offers = "SteamSpecialOffers"
|
||||
scheduled_events = "ScheduledEvents"
|
||||
|
@ -29,6 +29,7 @@ class FeatureFlagsSettings(ConfigurationModelABC):
|
||||
FeatureFlagsEnum.short_role_name.value: False, # 28.09.2023 #378
|
||||
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
|
||||
}
|
||||
|
||||
def __init__(self, **kwargs: dict):
|
||||
|
35
bot/src/bot_data/abc/scheduled_event_repository_abc.py
Normal file
35
bot/src/bot_data/abc/scheduled_event_repository_abc.py
Normal file
@ -0,0 +1,35 @@
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
from cpl_query.extension import List
|
||||
|
||||
from bot_data.model.scheduled_event import ScheduledEvent
|
||||
|
||||
|
||||
class ScheduledEventRepositoryABC(ABC):
|
||||
@abstractmethod
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_scheduled_events(self) -> List[ScheduledEvent]:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_scheduled_event_by_id(self, id: int) -> ScheduledEvent:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_scheduled_events_by_server_id(self, id: int) -> List[ScheduledEvent]:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def add_scheduled_event(self, scheduled_event: ScheduledEvent):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def update_scheduled_event(self, scheduled_event: ScheduledEvent):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def delete_scheduled_event(self, scheduled_event: ScheduledEvent):
|
||||
pass
|
@ -14,6 +14,7 @@ from bot_data.abc.data_seeder_abc import DataSeederABC
|
||||
from bot_data.abc.game_server_repository_abc import GameServerRepositoryABC
|
||||
from bot_data.abc.known_user_repository_abc import KnownUserRepositoryABC
|
||||
from bot_data.abc.level_repository_abc import LevelRepositoryABC
|
||||
from bot_data.abc.scheduled_event_repository_abc import ScheduledEventRepositoryABC
|
||||
from bot_data.abc.server_config_repository_abc import ServerConfigRepositoryABC
|
||||
from bot_data.abc.server_repository_abc import ServerRepositoryABC
|
||||
from bot_data.abc.short_role_name_repository_abc import ShortRoleNameRepositoryABC
|
||||
@ -45,6 +46,7 @@ from bot_data.service.client_repository_service import ClientRepositoryService
|
||||
from bot_data.service.game_server_repository_service import GameServerRepositoryService
|
||||
from bot_data.service.known_user_repository_service import KnownUserRepositoryService
|
||||
from bot_data.service.level_repository_service import LevelRepositoryService
|
||||
from bot_data.service.scheduled_event_repository_service import ScheduledEventRepositoryService
|
||||
from bot_data.service.seeder_service import SeederService
|
||||
from bot_data.service.server_config_repository_service import (
|
||||
ServerConfigRepositoryService,
|
||||
@ -115,6 +117,7 @@ class DataModule(ModuleABC):
|
||||
services.add_transient(ServerConfigRepositoryABC, ServerConfigRepositoryService)
|
||||
services.add_transient(ShortRoleNameRepositoryABC, ShortRoleNameRepositoryService)
|
||||
services.add_transient(SteamSpecialOfferRepositoryABC, SteamSpecialOfferRepositoryService)
|
||||
services.add_transient(ScheduledEventRepositoryABC, ScheduledEventRepositoryService)
|
||||
|
||||
services.add_transient(SeederService)
|
||||
services.add_transient(DataSeederABC, TechnicianConfigSeeder)
|
||||
|
188
bot/src/bot_data/model/scheduled_event.py
Normal file
188
bot/src/bot_data/model/scheduled_event.py
Normal file
@ -0,0 +1,188 @@
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
import discord
|
||||
from cpl_core.database import TableABC
|
||||
|
||||
from bot_data.model.scheduled_event_interval_enum import ScheduledEventIntervalEnum
|
||||
from bot_data.model.server import Server
|
||||
|
||||
|
||||
class ScheduledEvent(TableABC):
|
||||
def __init__(
|
||||
self,
|
||||
interval: ScheduledEventIntervalEnum,
|
||||
name: str,
|
||||
description: str,
|
||||
channel_id: int,
|
||||
start_time: datetime,
|
||||
end_time: Optional[datetime],
|
||||
entity_type: discord.EntityType,
|
||||
location: Optional[str],
|
||||
server: Optional[Server],
|
||||
created_at: datetime = None,
|
||||
modified_at: datetime = None,
|
||||
id=0,
|
||||
):
|
||||
self._id = id
|
||||
self._interval = interval
|
||||
self._name = name
|
||||
self._description = description
|
||||
self._channel_id = channel_id
|
||||
self._start_time = start_time
|
||||
self._end_time = end_time
|
||||
self._entity_type = entity_type
|
||||
self._location = location
|
||||
self._server = server
|
||||
|
||||
TableABC.__init__(self)
|
||||
self._created_at = created_at if created_at is not None else self._created_at
|
||||
self._modified_at = modified_at if modified_at is not None else self._modified_at
|
||||
|
||||
@property
|
||||
def id(self) -> int:
|
||||
return self._id
|
||||
|
||||
@property
|
||||
def interval(self) -> ScheduledEventIntervalEnum:
|
||||
return self._interval
|
||||
|
||||
@interval.setter
|
||||
def interval(self, value: ScheduledEventIntervalEnum):
|
||||
self._interval = value
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return self._name
|
||||
|
||||
@name.setter
|
||||
def name(self, value: str):
|
||||
self._name = value
|
||||
|
||||
@property
|
||||
def description(self) -> str:
|
||||
return self._description
|
||||
|
||||
@description.setter
|
||||
def description(self, value: str):
|
||||
self._description = value
|
||||
|
||||
@property
|
||||
def channel_id(self) -> int:
|
||||
return self._channel_id
|
||||
|
||||
@channel_id.setter
|
||||
def channel_id(self, value: int):
|
||||
self._channel_id = value
|
||||
|
||||
@property
|
||||
def start_time(self) -> datetime:
|
||||
return self._start_time
|
||||
|
||||
@start_time.setter
|
||||
def start_time(self, value: datetime):
|
||||
self._start_time = value
|
||||
|
||||
@property
|
||||
def end_time(self) -> datetime:
|
||||
return self._end_time
|
||||
|
||||
@end_time.setter
|
||||
def end_time(self, value: datetime):
|
||||
self._end_time = value
|
||||
|
||||
@property
|
||||
def entity_type(self) -> discord.EntityType:
|
||||
return self._entity_type
|
||||
|
||||
@entity_type.setter
|
||||
def entity_type(self, value: discord.EntityType):
|
||||
self._entity_type = value
|
||||
|
||||
@property
|
||||
def location(self) -> str:
|
||||
return self._location
|
||||
|
||||
@location.setter
|
||||
def location(self, value: str):
|
||||
self._location = value
|
||||
|
||||
@property
|
||||
def server(self) -> Server:
|
||||
return self._server
|
||||
|
||||
@server.setter
|
||||
def server(self, value: Server):
|
||||
self._server = value
|
||||
|
||||
@staticmethod
|
||||
def get_select_all_string() -> str:
|
||||
return str(
|
||||
f"""
|
||||
SELECT * FROM `ScheduledEvents`;
|
||||
"""
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def get_select_by_id_string(id: int) -> str:
|
||||
return str(
|
||||
f"""
|
||||
SELECT * FROM `ScheduledEvents`
|
||||
WHERE `Id` = {id};
|
||||
"""
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def get_select_by_server_id_string(s_id: int) -> str:
|
||||
return str(
|
||||
f"""
|
||||
SELECT * FROM `ScheduledEvents`
|
||||
WHERE `ServerId` = {s_id};
|
||||
"""
|
||||
)
|
||||
|
||||
@property
|
||||
def insert_string(self) -> str:
|
||||
return str(
|
||||
f"""
|
||||
INSERT INTO `ScheduledEvents` (
|
||||
`Interval`, `Name`, `Description`, `ChannelId`, `StartTime`, `EndTime`, `EntityType`, `Location`, `ServerId`
|
||||
) VALUES (
|
||||
'{self._interval.value}',
|
||||
'{self._name}',
|
||||
{"NULL" if self._description is None else f"'{self._description}'"},
|
||||
{"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.value}',
|
||||
{"NULL" if self._location is None else f"'{self._location}'"},
|
||||
{self._server.id}
|
||||
);
|
||||
"""
|
||||
)
|
||||
|
||||
@property
|
||||
def udpate_string(self) -> str:
|
||||
return str(
|
||||
f"""
|
||||
UPDATE `ScheduledEvents`
|
||||
SET `Interval` = '{self._interval.value}',
|
||||
`Name` = '{self._name}',
|
||||
`Description` = {"NULL" if self._description is None else f"'{self._description}'"},
|
||||
`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.value}',
|
||||
`Location` = {"NULL" if self._location is None else f"'{self._location}'"}
|
||||
WHERE `Id` = {self._id};
|
||||
"""
|
||||
)
|
||||
|
||||
@property
|
||||
def delete_string(self) -> str:
|
||||
return str(
|
||||
f"""
|
||||
DELETE FROM `ScheduledEvents`
|
||||
WHERE `Id` = {self._id};
|
||||
"""
|
||||
)
|
118
bot/src/bot_data/model/scheduled_event_history.py
Normal file
118
bot/src/bot_data/model/scheduled_event_history.py
Normal file
@ -0,0 +1,118 @@
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
import discord
|
||||
from cpl_core.database import TableABC
|
||||
|
||||
from bot_data.abc.history_table_abc import HistoryTableABC
|
||||
from bot_data.model.server import Server
|
||||
|
||||
|
||||
class ScheduledEventHistory(HistoryTableABC):
|
||||
def __init__(
|
||||
self,
|
||||
interval: str,
|
||||
name: str,
|
||||
description: str,
|
||||
channel_id: int,
|
||||
start_time: datetime,
|
||||
end_time: Optional[datetime],
|
||||
entity_type: discord.EntityType,
|
||||
location: Optional[str],
|
||||
server: Optional[Server],
|
||||
deleted: bool,
|
||||
date_from: str,
|
||||
date_to: str,
|
||||
id=0,
|
||||
):
|
||||
HistoryTableABC.__init__(self)
|
||||
self._id = id
|
||||
self._interval = interval
|
||||
self._name = name
|
||||
self._description = description
|
||||
self._channel_id = channel_id
|
||||
self._start_time = start_time
|
||||
self._end_time = end_time
|
||||
self._entity_type = entity_type
|
||||
self._location = location
|
||||
self._server = server
|
||||
|
||||
self._deleted = deleted
|
||||
self._date_from = date_from
|
||||
self._date_to = date_to
|
||||
|
||||
@property
|
||||
def id(self) -> int:
|
||||
return self._id
|
||||
|
||||
@property
|
||||
def interval(self) -> str:
|
||||
return self._interval
|
||||
|
||||
@interval.setter
|
||||
def interval(self, value: str):
|
||||
self._interval = value
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return self._name
|
||||
|
||||
@name.setter
|
||||
def name(self, value: str):
|
||||
self._name = value
|
||||
|
||||
@property
|
||||
def description(self) -> str:
|
||||
return self._description
|
||||
|
||||
@description.setter
|
||||
def description(self, value: str):
|
||||
self._description = value
|
||||
|
||||
@property
|
||||
def channel_id(self) -> int:
|
||||
return self._channel_id
|
||||
|
||||
@channel_id.setter
|
||||
def channel_id(self, value: int):
|
||||
self._channel_id = value
|
||||
|
||||
@property
|
||||
def start_time(self) -> datetime:
|
||||
return self._start_time
|
||||
|
||||
@start_time.setter
|
||||
def start_time(self, value: datetime):
|
||||
self._start_time = value
|
||||
|
||||
@property
|
||||
def end_time(self) -> datetime:
|
||||
return self._end_time
|
||||
|
||||
@end_time.setter
|
||||
def end_time(self, value: datetime):
|
||||
self._end_time = value
|
||||
|
||||
@property
|
||||
def entity_type(self) -> discord.EntityType:
|
||||
return self._entity_type
|
||||
|
||||
@entity_type.setter
|
||||
def entity_type(self, value: discord.EntityType):
|
||||
self._entity_type = value
|
||||
|
||||
@property
|
||||
def location(self) -> str:
|
||||
return self._location
|
||||
|
||||
@location.setter
|
||||
def location(self, value: str):
|
||||
self._location = value
|
||||
|
||||
@property
|
||||
def server(self) -> Server:
|
||||
return self._server
|
||||
|
||||
@server.setter
|
||||
def server(self, value: Server):
|
||||
self._server = value
|
8
bot/src/bot_data/model/scheduled_event_interval_enum.py
Normal file
8
bot/src/bot_data/model/scheduled_event_interval_enum.py
Normal file
@ -0,0 +1,8 @@
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class ScheduledEventIntervalEnum(Enum):
|
||||
daily = "daily"
|
||||
weekly = "weekly"
|
||||
monthly = "monthly"
|
||||
yearly = "yearly"
|
@ -0,0 +1,3 @@
|
||||
DROP TABLE `ShortRoleNames`;
|
||||
|
||||
DROP TABLE `ShortRoleNamesHistory`;
|
83
bot/src/bot_data/scripts/1.2.2/1_ScheduledEvents_up.sql
Normal file
83
bot/src/bot_data/scripts/1.2.2/1_ScheduledEvents_up.sql
Normal file
@ -0,0 +1,83 @@
|
||||
CREATE TABLE IF NOT EXISTS `ScheduledEvents`
|
||||
(
|
||||
`Id` BIGINT NOT NULL AUTO_INCREMENT,
|
||||
`Interval` ENUM ('daily', 'weekly', 'monthly', 'yearly') NOT NULL,
|
||||
`Name` VARCHAR(255) NOT NULL,
|
||||
`Description` VARCHAR(255) NULL,
|
||||
`ChannelId` BIGINT NULL,
|
||||
`StartTime` DATETIME(6) NOT NULL,
|
||||
`EndTime` DATETIME(6) NULL,
|
||||
`EntityType` ENUM ('1','2','3') NOT NULL,
|
||||
`Location` VARCHAR(255) NULL,
|
||||
`ServerId` BIGINT,
|
||||
`CreatedAt` DATETIME(6) NULL DEFAULT CURRENT_TIMESTAMP(6),
|
||||
`LastModifiedAt` DATETIME(6) NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
|
||||
PRIMARY KEY (`Id`),
|
||||
FOREIGN KEY (`ServerId`) REFERENCES `Servers` (`ServerId`)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `ScheduledEventsHistory`
|
||||
(
|
||||
`Id` BIGINT(20) NOT NULL,
|
||||
`Interval` ENUM ('daily', 'weekly', 'monthly', 'yearly') NOT NULL,
|
||||
`Name` VARCHAR(255) NOT NULL,
|
||||
`Description` VARCHAR(255) NULL,
|
||||
`ChannelId` BIGINT NULL,
|
||||
`StartTime` DATETIME(6) NOT NULL,
|
||||
`EndTime` DATETIME(6) NULL,
|
||||
`EntityType` ENUM ('1','2','3') NOT NULL,
|
||||
`Location` VARCHAR(255) NULL,
|
||||
`ServerId` BIGINT(20) DEFAULT NULL,
|
||||
`Deleted` BOOL DEFAULT FALSE,
|
||||
`DateFrom` DATETIME(6) NOT NULL,
|
||||
`DateTo` DATETIME(6) NOT NULL
|
||||
);
|
||||
|
||||
DROP TRIGGER IF EXISTS `TR_ScheduledEventsUpdate`;
|
||||
|
||||
CREATE TRIGGER `TR_ScheduledEventsUpdate`
|
||||
AFTER UPDATE
|
||||
ON `ScheduledEvents`
|
||||
FOR EACH ROW
|
||||
BEGIN
|
||||
INSERT INTO `ScheduledEventsHistory` (`Id`,
|
||||
`Interval`,
|
||||
`Name`,
|
||||
`Description`,
|
||||
`ChannelId`,
|
||||
`StartTime`,
|
||||
`EndTime`,
|
||||
`EntityType`,
|
||||
`Location`,
|
||||
`ServerId`,
|
||||
`DateFrom`,
|
||||
`DateTo`)
|
||||
VALUES (OLD.Id, OLD.Interval, OLD.Name, OLD.Description, OLD.ChannelId, OLD.StartTime, OLD.EndTime, OLD.EntityType,
|
||||
OLD.Location, OLD.ServerId, OLD.LastModifiedAt,
|
||||
CURRENT_TIMESTAMP(6));
|
||||
END;
|
||||
|
||||
DROP TRIGGER IF EXISTS `TR_ScheduledEventsDelete`;
|
||||
|
||||
CREATE TRIGGER `TR_ScheduledEventsDelete`
|
||||
AFTER DELETE
|
||||
ON `ScheduledEvents`
|
||||
FOR EACH ROW
|
||||
BEGIN
|
||||
INSERT INTO `ScheduledEventsHistory` (`Id`,
|
||||
`Interval`,
|
||||
`Name`,
|
||||
`Description`,
|
||||
`ChannelId`,
|
||||
`StartTime`,
|
||||
`EndTime`,
|
||||
`EntityType`,
|
||||
`Location`,
|
||||
`ServerId`,
|
||||
`Deleted`,
|
||||
`DateFrom`,
|
||||
`DateTo`)
|
||||
VALUES (OLD.Id, OLD.Interval, OLD.Name, OLD.Description, OLD.ChannelId, OLD.StartTime, OLD.EndTime, OLD.EntityType,
|
||||
OLD.Location, OLD.ServerId, TRUE, OLD.LastModifiedAt,
|
||||
CURRENT_TIMESTAMP(6));
|
||||
END;
|
@ -0,0 +1,91 @@
|
||||
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
|
||||
from bot_data.abc.scheduled_event_repository_abc import ScheduledEventRepositoryABC
|
||||
from bot_data.model.scheduled_event import ScheduledEvent
|
||||
from bot_data.model.scheduled_event_interval_enum import ScheduledEventIntervalEnum
|
||||
|
||||
|
||||
class ScheduledEventRepositoryService(ScheduledEventRepositoryABC):
|
||||
def __init__(
|
||||
self,
|
||||
logger: DatabaseLogger,
|
||||
db_context: DatabaseContextABC,
|
||||
servers: ServerRepositoryABC,
|
||||
):
|
||||
self._logger = logger
|
||||
self._context = db_context
|
||||
|
||||
self._servers = servers
|
||||
|
||||
ScheduledEventRepositoryABC.__init__(self)
|
||||
|
||||
@staticmethod
|
||||
def _get_value_from_result(value: any) -> Optional[any]:
|
||||
if isinstance(value, str) and "NULL" in value:
|
||||
return None
|
||||
|
||||
return value
|
||||
|
||||
def _scheduled_event_from_result(self, sql_result: tuple) -> ScheduledEvent:
|
||||
return ScheduledEvent(
|
||||
self._get_value_from_result(ScheduledEventIntervalEnum(sql_result[1])), # interval
|
||||
self._get_value_from_result(sql_result[2]), # name
|
||||
self._get_value_from_result(sql_result[3]), # description
|
||||
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
|
||||
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
|
||||
self._get_value_from_result(sql_result[11]), # modified_at
|
||||
id=self._get_value_from_result(sql_result[0]),
|
||||
)
|
||||
|
||||
def get_scheduled_events(self) -> List[ScheduledEvent]:
|
||||
scheduled_events = List(ScheduledEvent)
|
||||
self._logger.trace(__name__, f"Send SQL command: {ScheduledEvent.get_select_all_string()}")
|
||||
results = self._context.select(ScheduledEvent.get_select_all_string())
|
||||
for result in results:
|
||||
self._logger.trace(__name__, f"Get scheduled_event with id {result[0]}")
|
||||
scheduled_events.append(self._scheduled_event_from_result(result))
|
||||
|
||||
return scheduled_events
|
||||
|
||||
def get_scheduled_event_by_id(self, id: int) -> ScheduledEvent:
|
||||
self._logger.trace(__name__, f"Send SQL command: {ScheduledEvent.get_select_by_id_string(id)}")
|
||||
result = self._context.select(ScheduledEvent.get_select_by_id_string(id))[0]
|
||||
|
||||
return self._scheduled_event_from_result(result)
|
||||
|
||||
def get_scheduled_events_by_server_id(self, server_id: int) -> List[ScheduledEvent]:
|
||||
scheduled_events = List(ScheduledEvent)
|
||||
self._logger.trace(
|
||||
__name__,
|
||||
f"Send SQL command: {ScheduledEvent.get_select_by_server_id_string(server_id)}",
|
||||
)
|
||||
results = self._context.select(ScheduledEvent.get_select_by_server_id_string(server_id))
|
||||
|
||||
for result in results:
|
||||
self._logger.trace(__name__, f"Get scheduled_event with id {result[0]}")
|
||||
scheduled_events.append(self._scheduled_event_from_result(result))
|
||||
|
||||
return scheduled_events
|
||||
|
||||
def add_scheduled_event(self, scheduled_event: ScheduledEvent):
|
||||
self._logger.trace(__name__, f"Send SQL command: {scheduled_event.insert_string}")
|
||||
self._context.cursor.execute(scheduled_event.insert_string)
|
||||
|
||||
def update_scheduled_event(self, scheduled_event: ScheduledEvent):
|
||||
self._logger.trace(__name__, f"Send SQL command: {scheduled_event.udpate_string}")
|
||||
self._context.cursor.execute(scheduled_event.udpate_string)
|
||||
|
||||
def delete_scheduled_event(self, scheduled_event: ScheduledEvent):
|
||||
self._logger.trace(__name__, f"Send SQL command: {scheduled_event.delete_string}")
|
||||
self._context.cursor.execute(scheduled_event.delete_string)
|
@ -1,8 +1,10 @@
|
||||
import functools
|
||||
from abc import ABC, abstractmethod
|
||||
from inspect import signature, Parameter
|
||||
from typing import Callable
|
||||
|
||||
from cpl_core.dependency_injection import ServiceProviderABC
|
||||
from cpl_core.type import R, T
|
||||
from cpl_query.extension import List
|
||||
|
||||
|
||||
@ -52,3 +54,18 @@ class FilterABC(ABC):
|
||||
return f(*args, **kwargs)
|
||||
|
||||
return decorator
|
||||
|
||||
def _get_attributes_from_dict(self, attrs: list[tuple[str, type]], values: dict):
|
||||
for key_pair in attrs:
|
||||
attr = key_pair[0]
|
||||
attr_type = key_pair[1]
|
||||
if attr in values:
|
||||
self.__setattr__(f"_{attr}", attr_type(values[attr]))
|
||||
|
||||
@staticmethod
|
||||
def _filter_by_attributes(attrs: list[dict], values: List[T]) -> List[R]:
|
||||
for attr in attrs:
|
||||
if attr["attr"] is None:
|
||||
continue
|
||||
values = values.where(attr["func"])
|
||||
return values
|
||||
|
@ -20,6 +20,7 @@ from bot_data.model.auto_role_rule import AutoRoleRule
|
||||
from bot_data.model.client import Client
|
||||
from bot_data.model.known_user import KnownUser
|
||||
from bot_data.model.level import Level
|
||||
from bot_data.model.scheduled_event import ScheduledEvent
|
||||
from bot_data.model.server import Server
|
||||
from bot_data.model.server_config import ServerConfig
|
||||
from bot_data.model.short_role_name import ShortRoleName
|
||||
@ -213,6 +214,16 @@ class QueryABC(ObjectType):
|
||||
access = True
|
||||
break
|
||||
|
||||
elif type(element) == ScheduledEvent:
|
||||
element: ScheduledEvent = element
|
||||
for u in user.users:
|
||||
u: User = u
|
||||
guild = bot.get_guild(u.server.discord_id)
|
||||
member = guild.get_member(u.discord_id)
|
||||
if permissions.is_member_moderator(member) and u.server.id == element.server.id:
|
||||
access = True
|
||||
break
|
||||
|
||||
elif type(element) == dict and "key" in element and element["key"] in [e.value for e in FeatureFlagsEnum]:
|
||||
for u in user.users:
|
||||
u: User = u
|
||||
|
71
bot/src/bot_graphql/filter/scheduled_event_filter.py
Normal file
71
bot/src/bot_graphql/filter/scheduled_event_filter.py
Normal file
@ -0,0 +1,71 @@
|
||||
from cpl_core.dependency_injection import ServiceProviderABC
|
||||
from cpl_discord.service import DiscordBotServiceABC
|
||||
from cpl_query.extension import List
|
||||
|
||||
from bot_data.model.scheduled_event import ScheduledEvent
|
||||
from bot_graphql.abc.filter_abc import FilterABC
|
||||
|
||||
|
||||
class ScheduledEventFilter(FilterABC):
|
||||
def __init__(self, bot: DiscordBotServiceABC, services: ServiceProviderABC):
|
||||
FilterABC.__init__(self)
|
||||
self._bot = bot
|
||||
self._services = services
|
||||
|
||||
self._id = None
|
||||
self._interval = None
|
||||
self._name = None
|
||||
self._description = None
|
||||
self._channel_id = None
|
||||
self._start_time = None
|
||||
self._end_time = None
|
||||
self._entity_type = None
|
||||
self._location = None
|
||||
self._server = None
|
||||
|
||||
def from_dict(self, values: dict):
|
||||
self._get_attributes_from_dict(
|
||||
[
|
||||
("id", int),
|
||||
("interval", str),
|
||||
("name", str),
|
||||
("description", str),
|
||||
("channel_id", str),
|
||||
("start_time", str),
|
||||
("end_time", str),
|
||||
("entity_type", str),
|
||||
("location", str),
|
||||
],
|
||||
values,
|
||||
)
|
||||
|
||||
if "server" in values:
|
||||
from bot_graphql.filter.server_filter import ServerFilter
|
||||
|
||||
self._server: ServerFilter = self._services.get_service(ServerFilter)
|
||||
self._server.from_dict(values["server"])
|
||||
|
||||
def filter(self, query: List[ScheduledEvent]) -> List[ScheduledEvent]:
|
||||
if self._id is not None:
|
||||
query = query.where(lambda x: x.id == self._id)
|
||||
|
||||
query = self._filter_by_attributes(
|
||||
[
|
||||
{"attr": self._id, "func": lambda x: x.id == self._id},
|
||||
{"attr": self._interval, "func": lambda x: x.interval == self._interval},
|
||||
{"attr": self._name, "func": lambda x: x.name == self._name},
|
||||
{"attr": self._description, "func": lambda x: x.description == self._description},
|
||||
{"attr": self._channel_id, "func": lambda x: x.channel_id == self._channel_id},
|
||||
{"attr": self._start_time, "func": lambda x: x.start_time == self._start_time},
|
||||
{"attr": self._end_time, "func": lambda x: x.end_time == self._end_time},
|
||||
{"attr": self._entity_type, "func": lambda x: x.entity_type == self._entity_type},
|
||||
{"attr": self._location, "func": lambda x: x.location == self._location},
|
||||
],
|
||||
query,
|
||||
)
|
||||
|
||||
if self._server is not None:
|
||||
servers = self._server.filter(query.select(lambda x: x.server)).select(lambda x: x.id)
|
||||
query = query.where(lambda x: x.server.id in servers)
|
||||
|
||||
return query
|
@ -1,3 +1,4 @@
|
||||
from cpl_core.dependency_injection import ServiceProviderABC
|
||||
from cpl_discord.service import DiscordBotServiceABC
|
||||
from cpl_query.extension import List
|
||||
|
||||
@ -8,9 +9,10 @@ from bot_graphql.abc.filter_abc import FilterABC
|
||||
|
||||
|
||||
class ShortRoleNameFilter(FilterABC):
|
||||
def __init__(self, bot: DiscordBotServiceABC):
|
||||
def __init__(self, bot: DiscordBotServiceABC, services: ServiceProviderABC):
|
||||
FilterABC.__init__(self)
|
||||
self._bot = bot
|
||||
self._services = services
|
||||
|
||||
self._id = None
|
||||
self._short_name = None
|
||||
|
@ -6,6 +6,7 @@ type Mutation {
|
||||
userJoinedGameServer: UserJoinedGameServerMutation
|
||||
achievement: AchievementMutation
|
||||
shortRoleName: ShortRoleNameMutation
|
||||
scheduledEvent: ScheduledEventMutation
|
||||
technicianConfig: TechnicianConfigMutation
|
||||
serverConfig: ServerConfigMutation
|
||||
}
|
@ -41,6 +41,9 @@ type Query {
|
||||
shortRoleNames(filter: ShortRoleNameFilter, page: Page, sort: Sort): [ShortRoleName]
|
||||
shortRoleNamePositions: [String]
|
||||
|
||||
scheduledEventCount: Int
|
||||
scheduledEvents(filter: ScheduledEventFilter, page: Page, sort: Sort): [ScheduledEvent]
|
||||
|
||||
userWarningCount: Int
|
||||
userWarnings(filter: UserWarningFilter, page: Page, sort: Sort): [UserWarning]
|
||||
|
||||
|
68
bot/src/bot_graphql/graphql/scheduledEvent.gql
Normal file
68
bot/src/bot_graphql/graphql/scheduledEvent.gql
Normal file
@ -0,0 +1,68 @@
|
||||
type ScheduledEvent implements TableWithHistoryQuery {
|
||||
id: ID
|
||||
interval: String
|
||||
name: String
|
||||
description: String
|
||||
channelId: String
|
||||
startTime: String
|
||||
endTime: String
|
||||
entityType: Int
|
||||
location: String
|
||||
|
||||
server: Server
|
||||
|
||||
createdAt: String
|
||||
modifiedAt: String
|
||||
|
||||
history: [ScheduledEventHistory]
|
||||
}
|
||||
|
||||
type ScheduledEventHistory implements HistoryTableQuery {
|
||||
id: ID
|
||||
interval: String
|
||||
name: String
|
||||
description: String
|
||||
channelId: String
|
||||
startTime: String
|
||||
endTime: String
|
||||
entityType: Int
|
||||
location: String
|
||||
|
||||
server: ID
|
||||
|
||||
deleted: Boolean
|
||||
dateFrom: String
|
||||
dateTo: String
|
||||
}
|
||||
|
||||
input ScheduledEventFilter {
|
||||
id: ID
|
||||
interval: String
|
||||
name: String
|
||||
description: String
|
||||
channelId: String
|
||||
startTime: String
|
||||
endTime: String
|
||||
entityType: Int
|
||||
location: String
|
||||
server: ServerFilter
|
||||
}
|
||||
|
||||
type ScheduledEventMutation {
|
||||
createScheduledEvent(input: ScheduledEventInput!): ScheduledEvent
|
||||
updateScheduledEvent(input: ScheduledEventInput!): ScheduledEvent
|
||||
deleteScheduledEvent(id: ID): ScheduledEvent
|
||||
}
|
||||
|
||||
input ScheduledEventInput {
|
||||
id: ID
|
||||
interval: String
|
||||
name: String
|
||||
description: String
|
||||
channelId: String
|
||||
startTime: String
|
||||
endTime: String
|
||||
entityType: Int
|
||||
location: String
|
||||
serverId: ID
|
||||
}
|
@ -35,6 +35,9 @@ type Server implements TableWithHistoryQuery {
|
||||
shortRoleNameCount: Int
|
||||
shortRoleNames(filter: ShortRoleNameFilter, page: Page, sort: Sort): [ShortRoleName]
|
||||
|
||||
scheduledEventCount: Int
|
||||
scheduledEvents(filter: ScheduledEventFilter, page: Page, sort: Sort): [ScheduledEvent]
|
||||
|
||||
config: ServerConfig
|
||||
hasFeatureFlag(flag: String): FeatureFlag
|
||||
|
||||
|
@ -12,6 +12,7 @@ from bot_graphql.filter.auto_role_filter import AutoRoleFilter
|
||||
from bot_graphql.filter.auto_role_rule_filter import AutoRoleRuleFilter
|
||||
from bot_graphql.filter.client_filter import ClientFilter
|
||||
from bot_graphql.filter.level_filter import LevelFilter
|
||||
from bot_graphql.filter.scheduled_event_filter import ScheduledEventFilter
|
||||
from bot_graphql.filter.server_filter import ServerFilter
|
||||
from bot_graphql.filter.short_role_name_filter import ShortRoleNameFilter
|
||||
from bot_graphql.filter.user_filter import UserFilter
|
||||
@ -27,6 +28,7 @@ from bot_graphql.mutations.achievement_mutation import AchievementMutation
|
||||
from bot_graphql.mutations.auto_role_mutation import AutoRoleMutation
|
||||
from bot_graphql.mutations.auto_role_rule_mutation import AutoRoleRuleMutation
|
||||
from bot_graphql.mutations.level_mutation import LevelMutation
|
||||
from bot_graphql.mutations.scheduled_event_mutation import ScheduledEventMutation
|
||||
from bot_graphql.mutations.server_config_mutation import ServerConfigMutation
|
||||
from bot_graphql.mutations.short_role_name_mutation import ShortRoleNameMutation
|
||||
from bot_graphql.mutations.technician_config_mutation import TechnicianConfigMutation
|
||||
@ -54,6 +56,7 @@ from bot_graphql.queries.known_user_history_query import KnownUserHistoryQuery
|
||||
from bot_graphql.queries.known_user_query import KnownUserQuery
|
||||
from bot_graphql.queries.level_history_query import LevelHistoryQuery
|
||||
from bot_graphql.queries.level_query import LevelQuery
|
||||
from bot_graphql.queries.scheduled_event_query import ScheduledEventQuery
|
||||
from bot_graphql.queries.server_config_query import ServerConfigQuery
|
||||
from bot_graphql.queries.server_history_query import ServerHistoryQuery
|
||||
from bot_graphql.queries.server_query import ServerQuery
|
||||
@ -140,6 +143,7 @@ class GraphQLModule(ModuleABC):
|
||||
services.add_transient(QueryABC, UserWarningHistoryQuery)
|
||||
services.add_transient(QueryABC, UserWarningQuery)
|
||||
services.add_transient(QueryABC, ServerStatisticQuery)
|
||||
services.add_transient(QueryABC, ScheduledEventQuery)
|
||||
|
||||
services.add_transient(QueryABC, DiscordQuery)
|
||||
services.add_transient(QueryABC, GuildQuery)
|
||||
@ -161,6 +165,7 @@ class GraphQLModule(ModuleABC):
|
||||
services.add_transient(FilterABC, UserJoinedGameServerFilter)
|
||||
services.add_transient(FilterABC, ShortRoleNameFilter)
|
||||
services.add_transient(FilterABC, UserWarningFilter)
|
||||
services.add_transient(FilterABC, ScheduledEventFilter)
|
||||
|
||||
# mutations
|
||||
services.add_transient(QueryABC, AutoRoleMutation)
|
||||
@ -172,3 +177,4 @@ class GraphQLModule(ModuleABC):
|
||||
services.add_transient(QueryABC, UserJoinedGameServerMutation)
|
||||
services.add_transient(QueryABC, TechnicianConfigMutation)
|
||||
services.add_transient(QueryABC, ServerConfigMutation)
|
||||
services.add_transient(QueryABC, ScheduledEventMutation)
|
||||
|
@ -4,6 +4,7 @@ from bot_graphql.mutations.achievement_mutation import AchievementMutation
|
||||
from bot_graphql.mutations.auto_role_mutation import AutoRoleMutation
|
||||
from bot_graphql.mutations.auto_role_rule_mutation import AutoRoleRuleMutation
|
||||
from bot_graphql.mutations.level_mutation import LevelMutation
|
||||
from bot_graphql.mutations.scheduled_event_mutation import ScheduledEventMutation
|
||||
from bot_graphql.mutations.server_config_mutation import ServerConfigMutation
|
||||
from bot_graphql.mutations.short_role_name_mutation import ShortRoleNameMutation
|
||||
from bot_graphql.mutations.technician_config_mutation import TechnicianConfigMutation
|
||||
@ -23,6 +24,7 @@ class Mutation(MutationType):
|
||||
achievement_mutation: AchievementMutation,
|
||||
user_joined_game_server: UserJoinedGameServerMutation,
|
||||
technician_config: TechnicianConfigMutation,
|
||||
scheduled_event: ScheduledEventMutation,
|
||||
server_config: ServerConfigMutation,
|
||||
short_role_name_mutation: ShortRoleNameMutation,
|
||||
):
|
||||
@ -36,4 +38,5 @@ class Mutation(MutationType):
|
||||
self.set_field("userJoinedGameServer", lambda *_: user_joined_game_server)
|
||||
self.set_field("shortRoleName", lambda *_: short_role_name_mutation)
|
||||
self.set_field("technicianConfig", lambda *_: technician_config)
|
||||
self.set_field("scheduledEvent", lambda *_: scheduled_event)
|
||||
self.set_field("serverConfig", lambda *_: server_config)
|
||||
|
107
bot/src/bot_graphql/mutations/scheduled_event_mutation.py
Normal file
107
bot/src/bot_graphql/mutations/scheduled_event_mutation.py
Normal file
@ -0,0 +1,107 @@
|
||||
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
|
||||
from bot_data.model.scheduled_event import ScheduledEvent
|
||||
from bot_data.model.scheduled_event_interval_enum import ScheduledEventIntervalEnum
|
||||
from bot_data.model.user_role_enum import UserRoleEnum
|
||||
from bot_graphql.abc.query_abc import QueryABC
|
||||
from modules.permission.service.permission_service import PermissionService
|
||||
|
||||
|
||||
class ScheduledEventMutation(QueryABC):
|
||||
def __init__(
|
||||
self,
|
||||
servers: ServerRepositoryABC,
|
||||
scheduled_events: ScheduledEventRepositoryABC,
|
||||
bot: DiscordBotServiceABC,
|
||||
db: DatabaseContextABC,
|
||||
permissions: PermissionService,
|
||||
):
|
||||
QueryABC.__init__(self, "ScheduledEventMutation")
|
||||
|
||||
self._servers = servers
|
||||
self._scheduled_events = scheduled_events
|
||||
self._bot = bot
|
||||
self._db = db
|
||||
self._permissions = permissions
|
||||
|
||||
self.set_field("createScheduledEvent", self.resolve_create_scheduled_event)
|
||||
self.set_field("updateScheduledEvent", self.resolve_update_scheduled_event)
|
||||
self.set_field("deleteScheduledEvent", self.resolve_delete_scheduled_event)
|
||||
|
||||
def resolve_create_scheduled_event(self, *_, input: dict):
|
||||
server = self._servers.get_server_by_id(input["serverId"])
|
||||
self._can_user_mutate_data(server, UserRoleEnum.moderator)
|
||||
|
||||
scheduled_event = ScheduledEvent(
|
||||
ScheduledEventIntervalEnum(input["interval"]),
|
||||
input["name"],
|
||||
input["description"] if "description" in input else None,
|
||||
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"],
|
||||
input["location"] if "location" in input else None,
|
||||
server,
|
||||
)
|
||||
|
||||
self._scheduled_events.add_scheduled_event(scheduled_event)
|
||||
self._db.save_changes()
|
||||
|
||||
def get_new_scheduled_event(srn: ScheduledEvent):
|
||||
return (
|
||||
srn.interval == scheduled_event.interval
|
||||
and srn.name == scheduled_event.name
|
||||
and srn.description == scheduled_event.description
|
||||
)
|
||||
|
||||
return (
|
||||
self._scheduled_events.get_scheduled_events_by_server_id(scheduled_event.server.id)
|
||||
.where(get_new_scheduled_event)
|
||||
.last()
|
||||
)
|
||||
|
||||
def resolve_update_scheduled_event(self, *_, input: dict):
|
||||
scheduled_event = self._scheduled_events.get_scheduled_event_by_id(input["id"])
|
||||
self._can_user_mutate_data(scheduled_event.server, UserRoleEnum.moderator)
|
||||
|
||||
scheduled_event.interval = (
|
||||
ScheduledEventIntervalEnum(input["interval"]) if "interval" in input else scheduled_event.interval
|
||||
)
|
||||
scheduled_event.name = input["name"] if "name" in input else scheduled_event.name
|
||||
scheduled_event.description = input["description"] if "description" in input else scheduled_event.description
|
||||
scheduled_event.channel_id = input["channelId"] if "channelId" in input else scheduled_event.channel_id
|
||||
scheduled_event.start_time = (
|
||||
datetime.strptime(input["startTime"], "%Y-%m-%dT%H:%M:%S.%fZ")
|
||||
if "startTime" in input
|
||||
else scheduled_event.start_time
|
||||
)
|
||||
scheduled_event.end_time = (
|
||||
datetime.strptime(input["endTime"], "%Y-%m-%dT%H:%M:%S.%fZ")
|
||||
if "endTime" in input
|
||||
else scheduled_event.end_time
|
||||
)
|
||||
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)
|
||||
self._db.save_changes()
|
||||
|
||||
scheduled_event = self._scheduled_events.get_scheduled_event_by_id(input["id"])
|
||||
return scheduled_event
|
||||
|
||||
def resolve_delete_scheduled_event(self, *_, id: int):
|
||||
scheduled_event = self._scheduled_events.get_scheduled_event_by_id(id)
|
||||
self._can_user_mutate_data(scheduled_event.server, UserRoleEnum.admin)
|
||||
|
||||
self._scheduled_events.delete_scheduled_event(scheduled_event)
|
||||
self._db.save_changes()
|
||||
|
||||
return scheduled_event
|
24
bot/src/bot_graphql/queries/scheduled_event_history_query.py
Normal file
24
bot/src/bot_graphql/queries/scheduled_event_history_query.py
Normal file
@ -0,0 +1,24 @@
|
||||
from cpl_core.database.context import DatabaseContextABC
|
||||
|
||||
from bot_data.model.scheduled_event_history import ScheduledEventHistory
|
||||
from bot_graphql.abc.data_query_with_history_abc import DataQueryWithHistoryABC
|
||||
|
||||
|
||||
class ScheduledEventHistoryQuery(DataQueryWithHistoryABC):
|
||||
def __init__(
|
||||
self,
|
||||
db: DatabaseContextABC,
|
||||
):
|
||||
DataQueryWithHistoryABC.__init__(self, "ScheduledEvent", "ScheduledEventsHistory", ScheduledEventHistory, db)
|
||||
|
||||
self.set_field("id", lambda x, *_: x.id)
|
||||
self.set_field("id", lambda x, *_: x.id)
|
||||
self.set_field("interval", lambda x, *_: x.interval.value)
|
||||
self.set_field("name", lambda x, *_: x.name)
|
||||
self.set_field("description", lambda x, *_: x.description)
|
||||
self.set_field("channel_id", lambda x, *_: x.channel_id)
|
||||
self.set_field("start_time", lambda x, *_: x.start_time)
|
||||
self.set_field("end_time", lambda x, *_: x.end_time)
|
||||
self.set_field("entity_type", lambda x, *_: x.entity_type)
|
||||
self.set_field("location", lambda x, *_: x.location)
|
||||
self.set_field("server", lambda x, *_: x.server)
|
23
bot/src/bot_graphql/queries/scheduled_event_query.py
Normal file
23
bot/src/bot_graphql/queries/scheduled_event_query.py
Normal file
@ -0,0 +1,23 @@
|
||||
from cpl_core.database.context import DatabaseContextABC
|
||||
|
||||
from bot_data.model.scheduled_event_history import ScheduledEventHistory
|
||||
from bot_graphql.abc.data_query_with_history_abc import DataQueryWithHistoryABC
|
||||
|
||||
|
||||
class ScheduledEventQuery(DataQueryWithHistoryABC):
|
||||
def __init__(
|
||||
self,
|
||||
db: DatabaseContextABC,
|
||||
):
|
||||
DataQueryWithHistoryABC.__init__(self, "ScheduledEvent", "ScheduledEventsHistory", ScheduledEventHistory, db)
|
||||
|
||||
self.set_field("id", lambda x, *_: x.id)
|
||||
self.set_field("interval", lambda x, *_: x.interval.value)
|
||||
self.set_field("name", lambda x, *_: x.name)
|
||||
self.set_field("description", lambda x, *_: x.description)
|
||||
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.value)
|
||||
self.set_field("location", lambda x, *_: x.location)
|
||||
self.set_field("server", lambda x, *_: x.server)
|
@ -9,6 +9,7 @@ from bot_data.abc.auto_role_repository_abc import AutoRoleRepositoryABC
|
||||
from bot_data.abc.client_repository_abc import ClientRepositoryABC
|
||||
from bot_data.abc.game_server_repository_abc import GameServerRepositoryABC
|
||||
from bot_data.abc.level_repository_abc import LevelRepositoryABC
|
||||
from bot_data.abc.scheduled_event_repository_abc import ScheduledEventRepositoryABC
|
||||
from bot_data.abc.server_config_repository_abc import ServerConfigRepositoryABC
|
||||
from bot_data.abc.short_role_name_repository_abc import ShortRoleNameRepositoryABC
|
||||
from bot_data.abc.user_joined_server_repository_abc import UserJoinedServerRepositoryABC
|
||||
@ -24,6 +25,7 @@ from bot_graphql.filter.achievement_filter import AchievementFilter
|
||||
from bot_graphql.filter.auto_role_filter import AutoRoleFilter
|
||||
from bot_graphql.filter.client_filter import ClientFilter
|
||||
from bot_graphql.filter.level_filter import LevelFilter
|
||||
from bot_graphql.filter.scheduled_event_filter import ScheduledEventFilter
|
||||
from bot_graphql.filter.short_role_name_filter import ShortRoleNameFilter
|
||||
from bot_graphql.filter.user_filter import UserFilter
|
||||
from bot_graphql.model.server_statistics import ServerStatistics
|
||||
@ -44,6 +46,7 @@ class ServerQuery(DataQueryWithHistoryABC):
|
||||
ujvs: UserJoinedVoiceChannelRepositoryABC,
|
||||
achievements: AchievementRepositoryABC,
|
||||
short_role_names: ShortRoleNameRepositoryABC,
|
||||
scheduled_events: ScheduledEventRepositoryABC,
|
||||
server_configs: ServerConfigRepositoryABC,
|
||||
):
|
||||
DataQueryWithHistoryABC.__init__(self, "Server", "ServersHistory", ServerHistory, db)
|
||||
@ -100,6 +103,11 @@ class ServerQuery(DataQueryWithHistoryABC):
|
||||
lambda server, *_: short_role_names.get_short_role_names_by_server_id(server.id),
|
||||
ShortRoleNameFilter,
|
||||
)
|
||||
self.add_collection(
|
||||
"scheduledEvent",
|
||||
lambda server, *_: scheduled_events.get_scheduled_events_by_server_id(server.id),
|
||||
ScheduledEventFilter,
|
||||
)
|
||||
self.set_field(
|
||||
"config",
|
||||
lambda server, *_: server_configs.get_server_config_by_server(server.id),
|
||||
|
@ -8,6 +8,7 @@ from bot_data.abc.client_repository_abc import ClientRepositoryABC
|
||||
from bot_data.abc.game_server_repository_abc import GameServerRepositoryABC
|
||||
from bot_data.abc.known_user_repository_abc import KnownUserRepositoryABC
|
||||
from bot_data.abc.level_repository_abc import LevelRepositoryABC
|
||||
from bot_data.abc.scheduled_event_repository_abc import ScheduledEventRepositoryABC
|
||||
from bot_data.abc.server_repository_abc import ServerRepositoryABC
|
||||
from bot_data.abc.short_role_name_repository_abc import ShortRoleNameRepositoryABC
|
||||
from bot_data.abc.technician_config_repository_abc import TechnicianConfigRepositoryABC
|
||||
@ -27,6 +28,7 @@ from bot_graphql.filter.auto_role_filter import AutoRoleFilter
|
||||
from bot_graphql.filter.auto_role_rule_filter import AutoRoleRuleFilter
|
||||
from bot_graphql.filter.client_filter import ClientFilter
|
||||
from bot_graphql.filter.level_filter import LevelFilter
|
||||
from bot_graphql.filter.scheduled_event_filter import ScheduledEventFilter
|
||||
from bot_graphql.filter.server_filter import ServerFilter
|
||||
from bot_graphql.filter.short_role_name_filter import ShortRoleNameFilter
|
||||
from bot_graphql.filter.user_filter import UserFilter
|
||||
@ -59,6 +61,7 @@ class Query(QueryABC):
|
||||
user_warnings: UserWarningsRepositoryABC,
|
||||
achievement_service: AchievementService,
|
||||
technician_config: TechnicianConfigRepositoryABC,
|
||||
scheduled_events: ScheduledEventRepositoryABC,
|
||||
):
|
||||
QueryABC.__init__(self, "Query")
|
||||
|
||||
@ -95,6 +98,11 @@ class Query(QueryABC):
|
||||
lambda *_: short_role_names.get_short_role_names(),
|
||||
ShortRoleNameFilter,
|
||||
)
|
||||
self.add_collection(
|
||||
"scheduledEvent",
|
||||
lambda server, *_: scheduled_events.get_scheduled_events_by_server_id(server.id),
|
||||
ScheduledEventFilter,
|
||||
)
|
||||
self.add_collection(
|
||||
"userWarning",
|
||||
lambda *_: user_warnings.get_user_warnings(),
|
||||
|
@ -16,10 +16,10 @@
|
||||
"LicenseName": "MIT",
|
||||
"LicenseDescription": "MIT, see LICENSE for more details.",
|
||||
"Dependencies": [
|
||||
"cpl-core>=1.2.1"
|
||||
"cpl-core>=1.2.dev410"
|
||||
],
|
||||
"DevDependencies": [
|
||||
"cpl-cli>=1.2.1"
|
||||
"cpl-cli>=1.2.dev410"
|
||||
],
|
||||
"PythonVersion": ">=3.10.4",
|
||||
"PythonPath": {},
|
||||
|
@ -7,7 +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.birthday_watcher import BirthdayWatcher
|
||||
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 +45,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 +62,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)
|
||||
|
26
bot/src/modules/base/tasks/__init__.py
Normal file
26
bot/src/modules/base/tasks/__init__.py
Normal file
@ -0,0 +1,26 @@
|
||||
# -*- 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.tasks"
|
||||
__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")
|
126
bot/src/modules/base/tasks/scheduled_events_watcher.py
Normal file
126
bot/src/modules/base/tasks/scheduled_events_watcher.py
Normal file
@ -0,0 +1,126 @@
|
||||
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
|
||||
|
||||
|
||||
class ScheduledEventsWatcher(TaskABC):
|
||||
def __init__(
|
||||
self,
|
||||
config: ConfigurationABC,
|
||||
logger: TaskLogger,
|
||||
bot: DiscordBotServiceABC,
|
||||
db: DatabaseContextABC,
|
||||
servers: ServerRepositoryABC,
|
||||
events: ScheduledEventRepositoryABC,
|
||||
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()
|
||||
|
||||
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)
|
||||
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)
|
||||
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()
|
@ -16,10 +16,10 @@
|
||||
"LicenseName": "",
|
||||
"LicenseDescription": "",
|
||||
"Dependencies": [
|
||||
"cpl-core>=1.2.1"
|
||||
"cpl-core>=1.2.dev410"
|
||||
],
|
||||
"DevDependencies": [
|
||||
"cpl-cli>=1.2.1"
|
||||
"cpl-cli>=1.2.dev410"
|
||||
],
|
||||
"PythonVersion": ">=3.10.4",
|
||||
"PythonPath": {
|
||||
|
@ -16,10 +16,10 @@
|
||||
"LicenseName": "",
|
||||
"LicenseDescription": "",
|
||||
"Dependencies": [
|
||||
"cpl-core>=1.2.1"
|
||||
"cpl-core>=1.2.dev410"
|
||||
],
|
||||
"DevDependencies": [
|
||||
"cpl-cli>=1.2.1"
|
||||
"cpl-cli>=1.2.dev410"
|
||||
],
|
||||
"PythonVersion": ">=3.10.4",
|
||||
"PythonPath": {
|
||||
|
@ -16,10 +16,10 @@
|
||||
"LicenseName": "",
|
||||
"LicenseDescription": "",
|
||||
"Dependencies": [
|
||||
"cpl-core>=1.2.1"
|
||||
"cpl-core>=1.2.dev410"
|
||||
],
|
||||
"DevDependencies": [
|
||||
"cpl-cli>=1.2.1"
|
||||
"cpl-cli>=1.2.dev410"
|
||||
],
|
||||
"PythonVersion": ">=3.10.4",
|
||||
"PythonPath": {
|
||||
|
@ -2,9 +2,9 @@
|
||||
"ProjectSettings": {
|
||||
"Name": "migration-to-sql",
|
||||
"Version": {
|
||||
"Major": "0",
|
||||
"Minor": "0",
|
||||
"Micro": "0"
|
||||
"Major": "1",
|
||||
"Minor": "2",
|
||||
"Micro": "2"
|
||||
},
|
||||
"Author": "",
|
||||
"AuthorEmail": "",
|
||||
@ -16,10 +16,10 @@
|
||||
"LicenseName": "",
|
||||
"LicenseDescription": "",
|
||||
"Dependencies": [
|
||||
"cpl-core>=2023.10.0"
|
||||
"cpl-core>=1.2.dev410"
|
||||
],
|
||||
"DevDependencies": [
|
||||
"cpl-cli>=2023.4.0.post3"
|
||||
"cpl-cli>=1.2.dev410"
|
||||
],
|
||||
"PythonVersion": ">=3.10.4",
|
||||
"PythonPath": {
|
||||
|
110
web/package.json
110
web/package.json
@ -1,56 +1,56 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
"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"
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
export interface Discord {
|
||||
export interface Discord {
|
||||
guilds?: Guild[];
|
||||
users?: DiscordUser[];
|
||||
}
|
||||
@ -21,7 +21,8 @@ export interface Channel {
|
||||
export enum ChannelType {
|
||||
category = "CategoryChannel",
|
||||
text = "TextChannel",
|
||||
voice = "VoiceChannel"
|
||||
voice = "VoiceChannel",
|
||||
stage = "StageChannel"
|
||||
}
|
||||
|
||||
export interface Role {
|
||||
|
43
web/src/app/models/data/scheduled_events.model.ts
Normal file
43
web/src/app/models/data/scheduled_events.model.ts
Normal file
@ -0,0 +1,43 @@
|
||||
import { DataWithHistory } from "./data.model";
|
||||
import { Server, ServerFilter } from "./server.model";
|
||||
|
||||
export enum EventType {
|
||||
stageInstance = 1,
|
||||
voice = 2,
|
||||
external = 3,
|
||||
}
|
||||
|
||||
export interface ScheduledEvent extends DataWithHistory {
|
||||
id?: number;
|
||||
interval?: ScheduledEventInterval;
|
||||
name?: string;
|
||||
description?: string;
|
||||
channelId?: string;
|
||||
startTime?: Date;
|
||||
endTime?: Date;
|
||||
entityType?: EventType;
|
||||
location?: string;
|
||||
server?: Server;
|
||||
}
|
||||
|
||||
export interface ScheduledEventFilter {
|
||||
id?: number;
|
||||
interval?: string;
|
||||
name?: string;
|
||||
description?: string;
|
||||
channelId?: string;
|
||||
startTime?: string;
|
||||
endTime?: string;
|
||||
entityType?: number;
|
||||
location?: string;
|
||||
server?: ServerFilter;
|
||||
}
|
||||
|
||||
// export enum ScheduledEventInterval {
|
||||
// daily = "daily",
|
||||
// weekly = "weekly",
|
||||
// monthly = "monthly",
|
||||
// yearly = "yearly"
|
||||
// }
|
||||
|
||||
export type ScheduledEventInterval = "daily" | "weekly" | "monthly" | "month"
|
@ -173,6 +173,78 @@ export class Mutations {
|
||||
}
|
||||
`;
|
||||
|
||||
static createScheduledEvent = `
|
||||
mutation createScheduledEvent($interval: String,$name: String,$description: String,$channelId: String,$startTime: String, $endTime: String,$entityType: Int,$location: String, $serverId: ID) {
|
||||
scheduledEvent {
|
||||
createScheduledEvent(input: {
|
||||
interval: $interval,
|
||||
name: $name,
|
||||
description: $description,
|
||||
channelId: $channelId,
|
||||
startTime: $startTime,
|
||||
endTime: $endTime,
|
||||
entityType: $entityType,
|
||||
location: $location,
|
||||
serverId: $serverId
|
||||
}) {
|
||||
interval
|
||||
name
|
||||
description
|
||||
channelId
|
||||
startTime
|
||||
endTime
|
||||
entityType
|
||||
location
|
||||
server {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
static updateScheduledEvent = `
|
||||
mutation updateScheduledEvent($id: ID, $interval: String,$name: String,$description: String,$channelId: String,$startTime: String, $endTime: String,$entityType: Int,$location: String, $serverId: ID) {
|
||||
scheduledEvent {
|
||||
updateScheduledEvent(input: {
|
||||
id: $id,
|
||||
interval: $interval,
|
||||
name: $name,
|
||||
description: $description,
|
||||
channelId: $channelId,
|
||||
startTime: $startTime,
|
||||
endTime: $endTime,
|
||||
entityType: $entityType,
|
||||
location: $location,
|
||||
serverId: $serverId}
|
||||
) {
|
||||
interval
|
||||
name
|
||||
description
|
||||
channelId
|
||||
startTime
|
||||
endTime
|
||||
entityType
|
||||
location
|
||||
server {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
static deleteScheduledEvent = `
|
||||
mutation deleteScheduledEvent($id: ID) {
|
||||
scheduledEvent {
|
||||
deleteScheduledEvent(id: $id) {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
static createShortRoleName = `
|
||||
mutation createShortRoleName($shortName: String, $roleId: String, $position: String, $serverId: ID) {
|
||||
shortRoleName {
|
||||
@ -333,7 +405,6 @@ export class Mutations {
|
||||
`;
|
||||
|
||||
|
||||
|
||||
static createUserWarning = `
|
||||
mutation createUserWarning($name: String, $description: String, $attribute: String, $operator: String, $value: String, $serverId: ID) {
|
||||
userWarning {
|
||||
|
@ -229,6 +229,58 @@ export class Queries {
|
||||
}
|
||||
`;
|
||||
|
||||
static scheduledEventQuery = `
|
||||
query ScheduledEventList($serverId: ID, $filter: ScheduledEventFilter, $page: Page, $sort: Sort) {
|
||||
servers(filter: {id: $serverId}) {
|
||||
scheduledEventCount
|
||||
scheduledEvents(filter: $filter, page: $page, sort: $sort) {
|
||||
id
|
||||
interval
|
||||
name
|
||||
description
|
||||
channelId
|
||||
startTime
|
||||
endTime
|
||||
entityType
|
||||
location
|
||||
server {
|
||||
id
|
||||
name
|
||||
}
|
||||
createdAt
|
||||
modifiedAt
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
static scheduledEventWithHistoryQuery = `
|
||||
query ScheduledEventHistory($serverId: ID, $id: ID) {
|
||||
servers(filter: {id: $serverId}) {
|
||||
scheduledEventCount
|
||||
scheduledEvents(filter: {id: $id}) {
|
||||
id
|
||||
|
||||
history {
|
||||
id
|
||||
interval
|
||||
name
|
||||
description
|
||||
channelId
|
||||
startTime
|
||||
endTime
|
||||
entityType
|
||||
location
|
||||
server
|
||||
deleted
|
||||
dateFrom
|
||||
dateTo
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
|
||||
static shortRoleNamePositionsQuery = `
|
||||
query {
|
||||
|
@ -9,6 +9,7 @@ import { ServerConfig } from "../config/server-config.model";
|
||||
import { ShortRoleName } from "../data/short_role_name.model";
|
||||
import { FeatureFlag } from "../config/feature-flags.model";
|
||||
import { UserWarning } from "../data/user_warning.model";
|
||||
import { ScheduledEvent } from "../data/scheduled_events.model";
|
||||
|
||||
export interface Query {
|
||||
serverCount: number;
|
||||
@ -57,6 +58,11 @@ export interface AchievementListQuery {
|
||||
achievements: Achievement[];
|
||||
}
|
||||
|
||||
export interface ScheduledEventListQuery {
|
||||
scheduledEventCount: number;
|
||||
scheduledEvents: ScheduledEvent[];
|
||||
}
|
||||
|
||||
export interface AutoRoleQuery {
|
||||
autoRoleCount: number;
|
||||
autoRoles: AutoRole[];
|
||||
|
@ -7,6 +7,7 @@ import { TechnicianConfig } from "../config/technician-config.model";
|
||||
import { ServerConfig } from "../config/server-config.model";
|
||||
import { ShortRoleName } from "../data/short_role_name.model";
|
||||
import { UserWarning } from "../data/user_warning.model";
|
||||
import { ScheduledEvent } from "../data/scheduled_events.model";
|
||||
|
||||
export interface GraphQLResult {
|
||||
data: {
|
||||
@ -71,6 +72,14 @@ export interface AchievementMutationResult {
|
||||
};
|
||||
}
|
||||
|
||||
export interface ScheduledEventMutationResult {
|
||||
scheduledEvent: {
|
||||
createScheduledEvent?: ScheduledEvent
|
||||
updateScheduledEvent?: ScheduledEvent
|
||||
deleteScheduledEvent?: ScheduledEvent
|
||||
};
|
||||
}
|
||||
|
||||
export interface ShortRoleNameMutationResult {
|
||||
shortRoleName: {
|
||||
createShortRoleName?: ShortRoleName
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { CommonModule, DatePipe } from "@angular/common";
|
||||
import { HttpClientModule } from "@angular/common/http";
|
||||
import { NgModule } from "@angular/core";
|
||||
import { FormsModule, ReactiveFormsModule } from "@angular/forms";
|
||||
@ -13,7 +13,7 @@ import { InputTextModule } from "primeng/inputtext";
|
||||
import { MenuModule } from "primeng/menu";
|
||||
import { PasswordModule } from "primeng/password";
|
||||
import { ProgressSpinnerModule } from "primeng/progressspinner";
|
||||
import { SortableColumn, TableModule } from "primeng/table";
|
||||
import { TableModule } from "primeng/table";
|
||||
import { ToastModule } from "primeng/toast";
|
||||
import { AuthRolePipe } from "./pipes/auth-role.pipe";
|
||||
import { IpAddressPipe } from "./pipes/ip-address.pipe";
|
||||
@ -24,18 +24,22 @@ import { InputNumberModule } from "primeng/inputnumber";
|
||||
import { ImageModule } from "primeng/image";
|
||||
import { SidebarModule } from "primeng/sidebar";
|
||||
import { HistoryBtnComponent } from "./components/history-btn/history-btn.component";
|
||||
import { DataViewModule, DataViewLayoutOptions } from "primeng/dataview";
|
||||
import { DataViewLayoutOptions, DataViewModule } from "primeng/dataview";
|
||||
import { ConfigListComponent } from "./components/config-list/config-list.component";
|
||||
import { MultiSelectModule } from "primeng/multiselect";
|
||||
import { HideableColumnComponent } from './components/hideable-column/hideable-column.component';
|
||||
import { HideableHeaderComponent } from './components/hideable-header/hideable-header.component';
|
||||
import { MultiSelectColumnsComponent } from './base/multi-select-columns/multi-select-columns.component';
|
||||
import { FeatureFlagListComponent } from './components/feature-flag-list/feature-flag-list.component';
|
||||
import { HideableColumnComponent } from "./components/hideable-column/hideable-column.component";
|
||||
import { HideableHeaderComponent } from "./components/hideable-header/hideable-header.component";
|
||||
import { MultiSelectColumnsComponent } from "./base/multi-select-columns/multi-select-columns.component";
|
||||
import { FeatureFlagListComponent } from "./components/feature-flag-list/feature-flag-list.component";
|
||||
import { InputSwitchModule } from "primeng/inputswitch";
|
||||
import { CalendarModule } from "primeng/calendar";
|
||||
import { DataImportAndExportComponent } from './components/data-import-and-export/data-import-and-export.component';
|
||||
import { DataImportAndExportComponent } from "./components/data-import-and-export/data-import-and-export.component";
|
||||
import { FileUploadModule } from "primeng/fileupload";
|
||||
import { SelectButtonModule } from "primeng/selectbutton";
|
||||
import { TabViewModule } from "primeng/tabview";
|
||||
import { RadioButtonModule } from "primeng/radiobutton";
|
||||
import { InputTextareaModule } from "primeng/inputtextarea";
|
||||
import { InputMaskModule } from "primeng/inputmask";
|
||||
|
||||
|
||||
const PrimeNGModules = [
|
||||
@ -66,7 +70,11 @@ const PrimeNGModules = [
|
||||
CalendarModule,
|
||||
FileUploadModule,
|
||||
SelectButtonModule,
|
||||
]
|
||||
TabViewModule,
|
||||
RadioButtonModule,
|
||||
InputTextareaModule,
|
||||
InputMaskModule
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
@ -79,7 +87,7 @@ const PrimeNGModules = [
|
||||
HideableHeaderComponent,
|
||||
MultiSelectColumnsComponent,
|
||||
FeatureFlagListComponent,
|
||||
DataImportAndExportComponent,
|
||||
DataImportAndExportComponent
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
@ -98,6 +106,9 @@ const PrimeNGModules = [
|
||||
MultiSelectColumnsComponent,
|
||||
FeatureFlagListComponent,
|
||||
DataImportAndExportComponent
|
||||
],
|
||||
providers: [
|
||||
DatePipe
|
||||
]
|
||||
})
|
||||
export class SharedModule {
|
||||
|
@ -0,0 +1,92 @@
|
||||
<div class="edit-dialog">
|
||||
<p-dialog [contentStyle]="{'overflow':'visible'}" [(visible)]="visible" [header]="header" [modal]="true"
|
||||
[draggable]="false" [resizable]="false"
|
||||
[style]="{ width: '500px', height: '575px' }">
|
||||
<form [formGroup]="inputForm">
|
||||
<p-tabView [(activeIndex)]="activeIndex">
|
||||
<p-tabPanel header="{{'view.server.scheduled_events.edit_dialog.location.tab_name' | translate}}">
|
||||
<div class="form">
|
||||
<div style="display: flex; flex-direction: column; gap: 5px;">
|
||||
{{'view.server.scheduled_events.edit_dialog.location.interval' | translate}}:
|
||||
<p-dropdown [options]="interval"
|
||||
formControlName="interval"
|
||||
placeholder="{{'view.server.scheduled_events.edit_dialog.location.interval' | translate}}"></p-dropdown>
|
||||
</div>
|
||||
|
||||
<div class="type">
|
||||
<div *ngFor="let enum of EventType | keyvalue" class="field-checkbox">
|
||||
<p-radioButton [inputId]="enum.key" [value]="enum.value" formControlName="entityType"></p-radioButton>
|
||||
<label [for]="enum.key"
|
||||
class="ml-2">{{'view.server.scheduled_events.edit_dialog.location.' + enum.key | translate}}</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="input">
|
||||
<p-dropdown *ngIf="inputForm.controls.entityType.value == 1"
|
||||
[options]="stageChannels"
|
||||
optionLabel="name"
|
||||
optionValue="id"
|
||||
formControlName="channelId"
|
||||
placeholder="{{'view.server.scheduled_events.edit_dialog.location.stage_input' | translate}}"></p-dropdown>
|
||||
<p-dropdown *ngIf="inputForm.controls.entityType.value == 2"
|
||||
[options]="voiceChannels"
|
||||
optionLabel="name"
|
||||
optionValue="id"
|
||||
formControlName="channelId"
|
||||
placeholder="{{'view.server.scheduled_events.edit_dialog.location.voice_input' | translate}}"></p-dropdown>
|
||||
<input *ngIf="inputForm.controls.entityType.value == 3" pInputText class="table-edit-input"
|
||||
formControlName="location"
|
||||
placeholder="{{'view.server.scheduled_events.edit_dialog.location.somewhere_else' | translate}}">
|
||||
</div>
|
||||
</div>
|
||||
</p-tabPanel>
|
||||
<p-tabPanel header="{{'view.server.scheduled_events.edit_dialog.event_info.tab_name' | translate}}">
|
||||
<div class="form">
|
||||
<div style="display: flex; flex-direction: column; gap: 5px;">
|
||||
{{'view.server.scheduled_events.edit_dialog.event_info.event_topic' | translate}}:
|
||||
<input pInputText class="table-edit-input"
|
||||
formControlName="name"
|
||||
placeholder="{{'view.server.scheduled_events.edit_dialog.event_info.event_topic_input' | translate}}">
|
||||
</div>
|
||||
|
||||
<div style="display: flex; flex-direction: column; gap: 5px;">
|
||||
{{'view.server.scheduled_events.edit_dialog.event_info.start_date_time' | translate}}:
|
||||
<p-calendar formControlName="startTime" dateFormat="dd.mm.yy" [showIcon]="true"
|
||||
[showTime]="true" [stepMinute]="15"></p-calendar>
|
||||
</div>
|
||||
|
||||
<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"></p-calendar>
|
||||
</div>
|
||||
|
||||
<div style="display: flex; flex-direction: column; gap: 5px;">
|
||||
{{'view.server.scheduled_events.edit_dialog.event_info.description' | translate}}:
|
||||
<textarea rows="5" cols="30" pInputTextarea [autoResize]="true"
|
||||
class="table-edit-input"
|
||||
formControlName="description"
|
||||
placeholder="{{'view.server.scheduled_events.edit_dialog.event_info.description_input' | translate}}"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</p-tabPanel>
|
||||
</p-tabView>
|
||||
</form>
|
||||
<ng-template pTemplate="footer">
|
||||
<div style="display: flex;">
|
||||
<div class="btn-wrapper" style="flex: 1;">
|
||||
<button pButton label="{{'common.back' | translate}}" class="text-btn"
|
||||
(click)="back()" [disabled]="activeIndex == 0"></button>
|
||||
</div>
|
||||
<div class="btn-wrapper">
|
||||
<button pButton label="{{'common.abort' | translate}}" class="btn danger-btn"
|
||||
(click)="event = undefined"></button>
|
||||
<button *ngIf="activeIndex < 1" pButton label="{{'common.continue' | translate}}" class="btn"
|
||||
(click)="next()" [disabled]="!inputForm"></button>
|
||||
<button *ngIf="activeIndex == 1" pButton label="{{'common.save' | translate}}" class="btn"
|
||||
(click)="saveEvent()" [disabled]="!inputForm.valid"></button>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
</p-dialog>
|
||||
</div>
|
@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { EditScheduledEventDialogComponent } from './edit-scheduled-event-dialog.component';
|
||||
|
||||
describe('EditScheduledEventDialogComponent', () => {
|
||||
let component: EditScheduledEventDialogComponent;
|
||||
let fixture: ComponentFixture<EditScheduledEventDialogComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ EditScheduledEventDialogComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(EditScheduledEventDialogComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,200 @@
|
||||
import { Component, EventEmitter, Input, OnChanges, OnInit, Output } from "@angular/core";
|
||||
import {
|
||||
EventType,
|
||||
ScheduledEvent,
|
||||
ScheduledEventInterval
|
||||
} from "../../../../../../models/data/scheduled_events.model";
|
||||
import { TranslateService } from "@ngx-translate/core";
|
||||
import { FormBuilder, FormControl, FormGroup, Validators } from "@angular/forms";
|
||||
import { SingleDiscordQuery } from "../../../../../../models/graphql/query.model";
|
||||
import { Queries } from "../../../../../../models/graphql/queries.model";
|
||||
import { Channel, ChannelType, Guild } from "../../../../../../models/data/discord.model";
|
||||
import { DataService } from "../../../../../../services/data/data.service";
|
||||
import { SpinnerService } from "../../../../../../services/spinner/spinner.service";
|
||||
import { ActivatedRoute } from "@angular/router";
|
||||
import { Server } from "../../../../../../models/data/server.model";
|
||||
import { DatePipe } from "@angular/common";
|
||||
|
||||
@Component({
|
||||
selector: "app-edit-scheduled-event-dialog",
|
||||
templateUrl: "./edit-scheduled-event-dialog.component.html",
|
||||
styleUrls: ["./edit-scheduled-event-dialog.component.scss"]
|
||||
})
|
||||
export class EditScheduledEventDialogComponent implements OnInit, OnChanges {
|
||||
public _event?: ScheduledEvent;
|
||||
|
||||
@Input()
|
||||
set event(event: ScheduledEvent | undefined) {
|
||||
this._event = event;
|
||||
this.eventChange.emit(event);
|
||||
}
|
||||
|
||||
get event(): ScheduledEvent | undefined {
|
||||
return this._event;
|
||||
}
|
||||
|
||||
@Output() eventChange: EventEmitter<ScheduledEvent> = new EventEmitter<ScheduledEvent>();
|
||||
|
||||
get header() {
|
||||
if (this.event && this.event.createdAt === "" && this.event.modifiedAt === "") {
|
||||
return this.translate.instant("view.server.scheduled_events.edit_dialog.add_header");
|
||||
} else {
|
||||
return this.translate.instant("view.server.scheduled_events.edit_dialog.edit_header");
|
||||
}
|
||||
}
|
||||
|
||||
get visible() {
|
||||
return this.event != undefined;
|
||||
}
|
||||
|
||||
set visible(val: boolean) {
|
||||
if (!val) {
|
||||
this.event = undefined;
|
||||
this.inputForm.reset();
|
||||
this.activeIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public activeIndex: number = 0;
|
||||
public inputForm!: FormGroup<{
|
||||
entityType: FormControl<number | undefined | null>;
|
||||
name: FormControl<string | undefined | null>;
|
||||
description: FormControl<string | undefined | null>;
|
||||
interval: FormControl<ScheduledEventInterval | undefined | null>;
|
||||
location: FormControl<string | undefined | null>;
|
||||
startTime: FormControl<Date | undefined | null>;
|
||||
id: FormControl<number | undefined | null>;
|
||||
endTime: FormControl<Date | undefined | null>;
|
||||
channelId: FormControl<string | undefined | null>
|
||||
}>;
|
||||
server: Server = {};
|
||||
public voiceChannels: Channel[] = [];
|
||||
public stageChannels: Channel[] = [];
|
||||
public guild: Guild = { channels: [], emojis: [], roles: [] };
|
||||
public times: string[] = [];
|
||||
public now = new Date();
|
||||
public interval = [
|
||||
{
|
||||
label: this.translate.instant("view.server.scheduled_events.edit_dialog.location.intervals.daily"),
|
||||
value: "daily"
|
||||
},
|
||||
{
|
||||
label: this.translate.instant("view.server.scheduled_events.edit_dialog.location.intervals.weekly"),
|
||||
value: "weekly"
|
||||
},
|
||||
{
|
||||
label: this.translate.instant("view.server.scheduled_events.edit_dialog.location.intervals.monthly"),
|
||||
value: "monthly"
|
||||
},
|
||||
{
|
||||
label: this.translate.instant("view.server.scheduled_events.edit_dialog.location.intervals.yearly"),
|
||||
value: "yearly"
|
||||
}
|
||||
];
|
||||
|
||||
constructor(
|
||||
private translate: TranslateService,
|
||||
private fb: FormBuilder,
|
||||
private data: DataService,
|
||||
private spinner: SpinnerService,
|
||||
private route: ActivatedRoute,
|
||||
private datePipe: DatePipe
|
||||
) {
|
||||
for (let i = 0; i < 25; i++) {
|
||||
let time = "";
|
||||
if (i < 10) {
|
||||
time = `0${i}`;
|
||||
} else {
|
||||
time = `${i}`;
|
||||
}
|
||||
this.times.push(`${time}:00`);
|
||||
this.times.push(`${time}:15`);
|
||||
this.times.push(`${time}:30`);
|
||||
this.times.push(`${time}:45`);
|
||||
}
|
||||
this.setInputForm();
|
||||
}
|
||||
|
||||
public ngOnChanges() {
|
||||
this.setInputForm();
|
||||
}
|
||||
|
||||
public ngOnInit() {
|
||||
this.data.getServerFromRoute(this.route).then(server => {
|
||||
this.server = server;
|
||||
this.spinner.showSpinner();
|
||||
|
||||
this.data.query<SingleDiscordQuery>(Queries.guildsQuery, {
|
||||
id: server?.discordId
|
||||
}
|
||||
).subscribe(data => {
|
||||
if (data.discord.guilds) {
|
||||
this.guild = data.discord.guilds[0];
|
||||
}
|
||||
|
||||
this.voiceChannels = this.guild.channels.filter(x => x.type === ChannelType.voice);
|
||||
this.stageChannels = this.guild.channels.filter(x => x.type === ChannelType.stage);
|
||||
this.setInputForm();
|
||||
this.spinner.hideSpinner();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public setInputForm() {
|
||||
if (this.now.getMinutes() % 15 != 0) {
|
||||
if (this.now.getMinutes() < 15) {
|
||||
this.now.setMinutes(15);
|
||||
} else if (this.now.getMinutes() < 30) {
|
||||
this.now.setMinutes(30);
|
||||
} else if (this.now.getMinutes() < 45) {
|
||||
this.now.setMinutes(45);
|
||||
} else if (this.now.getMinutes() > 45) {
|
||||
this.now.setMinutes(0);
|
||||
this.now.setHours(this.now.getHours() + 1);
|
||||
}
|
||||
}
|
||||
this.now.setSeconds(0);
|
||||
|
||||
this.inputForm = this.fb.group({
|
||||
id: new FormControl<number | undefined>(this.event?.id),
|
||||
interval: new FormControl<ScheduledEventInterval | undefined>(this.event?.interval, [Validators.required]),
|
||||
entityType: new FormControl<number | undefined>(this.event?.entityType, [Validators.required]),
|
||||
channelId: new FormControl<string | undefined>(this.event?.channelId, this.event?.entityType == EventType.voice || this.event?.entityType == EventType.stageInstance ? [Validators.required] : []),
|
||||
location: new FormControl<string | undefined>(this.event?.location, this.event?.entityType == EventType.external ? [Validators.required] : []),
|
||||
name: new FormControl<string | undefined>(this.event?.name, [Validators.required]),
|
||||
startTime: new FormControl<Date | undefined>(this.event?.startTime ? new Date(this.event.startTime) : this.now, [Validators.required]),
|
||||
endTime: new FormControl<Date | undefined>(this.event?.endTime ? new Date(this.event.endTime) : undefined),
|
||||
description: new FormControl<string | undefined>(this.event?.description)
|
||||
});
|
||||
}
|
||||
|
||||
public saveEvent() {
|
||||
this.event = {
|
||||
id: this.inputForm.controls.id.value ?? undefined,
|
||||
interval: this.inputForm.controls.interval.value ?? undefined,
|
||||
name: this.inputForm.controls.name.value ?? undefined,
|
||||
description: this.inputForm.controls.description.value ?? undefined,
|
||||
channelId: this.inputForm.controls.channelId.value ?? undefined,
|
||||
startTime: this.inputForm.controls.startTime.value ?? undefined,
|
||||
endTime: this.inputForm.controls.endTime.value ?? undefined,
|
||||
entityType: this.inputForm.controls.entityType.value ?? undefined,
|
||||
location: this.inputForm.controls.location.value ?? undefined,
|
||||
server: this.server
|
||||
};
|
||||
this.visible = false;
|
||||
}
|
||||
|
||||
public next() {
|
||||
this.activeIndex++;
|
||||
}
|
||||
|
||||
public back() {
|
||||
this.activeIndex--;
|
||||
}
|
||||
|
||||
protected readonly EventType = {
|
||||
stage: EventType.stageInstance,
|
||||
voice: EventType.voice,
|
||||
somewhere_else: EventType.external
|
||||
};
|
||||
}
|
@ -0,0 +1,244 @@
|
||||
<div *ngIf="editableScheduledEvent">
|
||||
<app-edit-scheduled-event-dialog [(event)]="editableScheduledEvent"></app-edit-scheduled-event-dialog>
|
||||
</div>
|
||||
|
||||
<h1>
|
||||
{{'view.server.scheduled_events.header' | translate}}
|
||||
</h1>
|
||||
<div class="content-wrapper">
|
||||
<div class="content">
|
||||
<p-table #dt [value]="scheduledEvents" [responsive]="true" responsiveLayout="stack" [breakpoint]="'720px'"
|
||||
dataKey="id" [rowHover]="true" [rows]="10"
|
||||
[rowsPerPageOptions]="[10,25,50]" [paginator]="true" [loading]="loading" [totalRecords]="totalRecords"
|
||||
[lazy]="true" (onLazyLoad)="nextPage($event)">
|
||||
|
||||
<ng-template pTemplate="caption">
|
||||
<div class="table-caption">
|
||||
<div class="table-caption-table-info">
|
||||
<div class="table-caption-text">
|
||||
<ng-container *ngIf="!loading">{{scheduledEvents.length}} {{'common.of' | translate}}
|
||||
{{dt.totalRecords}}
|
||||
</ng-container>
|
||||
{{'view.server.scheduled_events.scheduled_events' | translate}}
|
||||
</div>
|
||||
|
||||
<app-multi-select-columns [table]="name" [columns]="columns"
|
||||
[(hiddenColumns)]="hiddenColumns"></app-multi-select-columns>
|
||||
</div>
|
||||
|
||||
<div class="table-caption-btn-wrapper btn-wrapper">
|
||||
<button pButton label="{{'common.add' | translate}}" class="icon-btn btn"
|
||||
icon="pi pi-plus" (click)="addScheduledEvent()"
|
||||
[disabled]="isEditingNew || !user?.isModerator && !user?.isAdmin">
|
||||
</button>
|
||||
<button pButton label="{{'common.reset_filters' | translate}}" icon="pi pi-undo"
|
||||
class="icon-btn btn" (click)="resetFilters()">
|
||||
</button>
|
||||
<app-data-import-and-export name="scheduledEvent" [(data)]="scheduledEvents"
|
||||
[callback]="callback" [validator]="validator"></app-data-import-and-export>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template pTemplate="header">
|
||||
<tr>
|
||||
<th hideable-th="id" [parent]="this" [sortable]="true">
|
||||
<div class="table-header-label">
|
||||
<div class="table-header-text">{{'common.id' | translate}}</div>
|
||||
<p-sortIcon field="id" class="table-header-icon"></p-sortIcon>
|
||||
</div>
|
||||
</th>
|
||||
|
||||
<th hideable-th="interval" [parent]="this" [sortable]="true">
|
||||
<div class="table-header-label">
|
||||
<div class="table-header-text">{{'common.interval' | translate}}</div>
|
||||
<p-sortIcon field="interval" class="table-header-icon"></p-sortIcon>
|
||||
</div>
|
||||
</th>
|
||||
<th hideable-th="name" [parent]="this" [sortable]="true">
|
||||
<div class="table-header-label">
|
||||
<div class="table-header-text">{{'common.name' | translate}}</div>
|
||||
<p-sortIcon field="name" class="table-header-icon"></p-sortIcon>
|
||||
</div>
|
||||
</th>
|
||||
<th hideable-th="description" [parent]="this" [sortable]="true">
|
||||
<div class="table-header-label">
|
||||
<div class="table-header-text">{{'common.description' | translate}}</div>
|
||||
<p-sortIcon field="description" class="table-header-icon"></p-sortIcon>
|
||||
</div>
|
||||
</th>
|
||||
<th hideable-th="channel_id" [parent]="this" [sortable]="true">
|
||||
<div class="table-header-label">
|
||||
<div class="table-header-text">{{'common.channel_id' | translate}}</div>
|
||||
<p-sortIcon field="channelId" class="table-header-icon"></p-sortIcon>
|
||||
</div>
|
||||
</th>
|
||||
<th hideable-th="start_time" [parent]="this" [sortable]="true">
|
||||
<div class="table-header-label">
|
||||
<div class="table-header-text">{{'common.start_time' | translate}}</div>
|
||||
<p-sortIcon field="startTime" class="table-header-icon"></p-sortIcon>
|
||||
</div>
|
||||
</th>
|
||||
<th hideable-th="end_time" [parent]="this" [sortable]="true">
|
||||
<div class="table-header-label">
|
||||
<div class="table-header-text">{{'common.end_time' | translate}}</div>
|
||||
<p-sortIcon field="endTime" class="table-header-icon"></p-sortIcon>
|
||||
</div>
|
||||
</th>
|
||||
<th hideable-th="type" [parent]="this" [sortable]="true">
|
||||
<div class="table-header-label">
|
||||
<div class="table-header-text">{{'common.type' | translate}}</div>
|
||||
<p-sortIcon field="entityType" class="table-header-icon"></p-sortIcon>
|
||||
</div>
|
||||
</th>
|
||||
<th hideable-th="location" [parent]="this" [sortable]="true">
|
||||
<div class="table-header-label">
|
||||
<div class="table-header-text">{{'common.location' | translate}}</div>
|
||||
<p-sortIcon field="location" class="table-header-icon"></p-sortIcon>
|
||||
</div>
|
||||
</th>
|
||||
|
||||
<th>
|
||||
<div class="table-header-label">
|
||||
<div class="table-header-text">{{'common.created_at' | translate}}</div>
|
||||
</div>
|
||||
</th>
|
||||
|
||||
<th>
|
||||
<div class="table-header-label">
|
||||
<div class="table-header-text">{{'common.modified_at' | translate}}</div>
|
||||
</div>
|
||||
</th>
|
||||
|
||||
<th>
|
||||
<div class="table-header-label">
|
||||
<div class="table-header-text">{{'common.actions' | translate}}</div>
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th hideable-th="id" [parent]="this" class="table-header-small">
|
||||
<form [formGroup]="filterForm">
|
||||
<input type="text" pInputText formControlName="id"
|
||||
placeholder="{{'common.id' | translate}}">
|
||||
</form>
|
||||
</th>
|
||||
|
||||
<th hideable-th="interval" [parent]="this"></th>
|
||||
|
||||
<th hideable-th="name" [parent]="this">
|
||||
<form [formGroup]="filterForm">
|
||||
<input type="text" pInputText formControlName="name"
|
||||
placeholder="{{'common.name' | translate}}">
|
||||
</form>
|
||||
</th>
|
||||
|
||||
<th hideable-th="description" [parent]="this">
|
||||
<form [formGroup]="filterForm">
|
||||
<input type="text" pInputText formControlName="description"
|
||||
placeholder="{{'common.description' | translate}}">
|
||||
</form>
|
||||
</th>
|
||||
|
||||
<th hideable-th="channel_id" [parent]="this">
|
||||
<form [formGroup]="filterForm">
|
||||
<input type="number" pInputText formControlName="channelId"
|
||||
placeholder="{{'common.channel_id' | translate}}">
|
||||
</form>
|
||||
</th>
|
||||
|
||||
<th hideable-th="start_time" [parent]="this"></th>
|
||||
<th hideable-th="end_time" [parent]="this"></th>
|
||||
<th hideable-th="type" [parent]="this"></th>
|
||||
<th hideable-th="location" [parent]="this"></th>
|
||||
<th class="table-header-small-dropdown"></th>
|
||||
<th class="table-header-small-dropdown"></th>
|
||||
<th class="table-header-actions"></th>
|
||||
</tr>
|
||||
</ng-template>
|
||||
|
||||
<ng-template pTemplate="body" let-scheduledEvent let-editing="editing" let-ri="rowIndex">
|
||||
<tr [pEditableRow]="scheduledEvent">
|
||||
<td hideable-td="id" [parent]="this">
|
||||
<span class="p-column-title">{{'common.id' | translate}}:</span>
|
||||
{{scheduledEvent.id}}
|
||||
</td>
|
||||
|
||||
<td hideable-td="interval" [parent]="this">
|
||||
<span class="p-column-title">{{'common.interval' | translate}}:</span>
|
||||
{{scheduledEvent.interval}}
|
||||
</td>
|
||||
|
||||
<td hideable-td="name" [parent]="this">
|
||||
<span class="p-column-title">{{'common.name' | translate}}:</span>
|
||||
{{scheduledEvent.name}}
|
||||
</td>
|
||||
|
||||
<td hideable-td="description" [parent]="this">
|
||||
<span class="p-column-title">{{'common.description' | translate}}:</span>
|
||||
{{scheduledEvent.description}}
|
||||
</td>
|
||||
|
||||
<td hideable-td="channel_id" [parent]="this">
|
||||
<span class="p-column-title">{{'common.channel_id' | translate}}:</span>
|
||||
{{scheduledEvent.channelId}}
|
||||
</td>
|
||||
|
||||
<td hideable-td="start_time" [parent]="this">
|
||||
<span class="p-column-title">{{'common.start_time' | translate}}:</span>
|
||||
{{scheduledEvent.startTime}}
|
||||
</td>
|
||||
|
||||
<td hideable-td="end_time" [parent]="this">
|
||||
<span class="p-column-title">{{'common.end_time' | translate}}:</span>
|
||||
{{scheduledEvent.endTime}}
|
||||
</td>
|
||||
|
||||
<td hideable-td="type" [parent]="this">
|
||||
<span class="p-column-title">{{'common.type' | translate}}:</span>
|
||||
{{scheduledEvent.entityType}}
|
||||
</td>
|
||||
|
||||
<td hideable-td="location" [parent]="this">
|
||||
<span class="p-column-title">{{'common.location' | translate}}:</span>
|
||||
{{scheduledEvent.location}}
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<span class="p-column-title">{{'common.created_at' | translate}}:</span>
|
||||
{{scheduledEvent.createdAt | date:'dd.MM.yy HH:mm'}}
|
||||
</td>
|
||||
<td>
|
||||
<span class="p-column-title">{{'common.modified_at' | translate}}:</span>
|
||||
{{scheduledEvent.modifiedAt | date:'dd.MM.yy HH:mm'}}
|
||||
</td>
|
||||
<td>
|
||||
<div class="btn-wrapper">
|
||||
<app-history-btn *ngIf="!isEditingNew" [id]="scheduledEvent.id" [query]="query"
|
||||
translationKey="view.server.scheduledEvent.header"></app-history-btn>
|
||||
<button *ngIf="!editing" pButton class="btn icon-btn" icon="pi pi-pencil"
|
||||
(click)="onRowEditInit(dt, scheduledEvent, ri)"
|
||||
[disabled]="!user || !user.isModerator && !user.isAdmin"></button>
|
||||
<button *ngIf="!editing" pButton class="btn icon-btn danger-icon-btn" icon="pi pi-trash"
|
||||
(click)="deleteScheduledEvent(scheduledEvent)"
|
||||
[disabled]="!user || !user.isModerator && !user.isAdmin"></button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</ng-template>
|
||||
|
||||
<ng-template pTemplate="emptymessage">
|
||||
<tr></tr>
|
||||
<tr>
|
||||
<td colspan="14">{{'common.no_entries_found' | translate}}</td>
|
||||
</tr>
|
||||
<tr></tr>
|
||||
</ng-template>
|
||||
|
||||
<ng-template pTemplate="paginatorleft">
|
||||
</ng-template>
|
||||
</p-table>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ScheduledEventsComponent } from './scheduled-events.component';
|
||||
|
||||
describe('ScheduledEventsComponent', () => {
|
||||
let component: ScheduledEventsComponent;
|
||||
let fixture: ComponentFixture<ScheduledEventsComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ ScheduledEventsComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(ScheduledEventsComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,306 @@
|
||||
import { Component, OnDestroy, OnInit } from "@angular/core";
|
||||
import { FormBuilder, FormControl, FormGroup } from "@angular/forms";
|
||||
import { Page } from "../../../../../../models/graphql/filter/page.model";
|
||||
import { Sort, SortDirection } from "../../../../../../models/graphql/filter/sort.model";
|
||||
import { Subject, throwError } from "rxjs";
|
||||
import { Server } from "../../../../../../models/data/server.model";
|
||||
import { UserDTO } from "../../../../../../models/auth/auth-user.dto";
|
||||
import { LazyLoadEvent } from "primeng/api";
|
||||
import { Queries } from "../../../../../../models/graphql/queries.model";
|
||||
import { AuthService } from "../../../../../../services/auth/auth.service";
|
||||
import { SpinnerService } from "../../../../../../services/spinner/spinner.service";
|
||||
import { ToastService } from "../../../../../../services/toast/toast.service";
|
||||
import { ConfirmationDialogService } from "../../../../../../services/confirmation-dialog/confirmation-dialog.service";
|
||||
import { TranslateService } from "@ngx-translate/core";
|
||||
import { DataService } from "../../../../../../services/data/data.service";
|
||||
import { SidebarService } from "../../../../../../services/sidebar/sidebar.service";
|
||||
import { ActivatedRoute } from "@angular/router";
|
||||
import { Query, ScheduledEventListQuery } from "../../../../../../models/graphql/query.model";
|
||||
import { catchError, debounceTime, takeUntil } from "rxjs/operators";
|
||||
import { Table } from "primeng/table";
|
||||
import { ScheduledEventMutationResult } from "../../../../../../models/graphql/result.model";
|
||||
import { Mutations } from "../../../../../../models/graphql/mutations.model";
|
||||
import { ComponentWithTable } from "../../../../../../base/component-with-table";
|
||||
import { EventType, ScheduledEvent, ScheduledEventFilter } from "../../../../../../models/data/scheduled_events.model";
|
||||
|
||||
@Component({
|
||||
selector: "app-scheduled-events",
|
||||
templateUrl: "./scheduled-events.component.html",
|
||||
styleUrls: ["./scheduled-events.component.scss"]
|
||||
})
|
||||
export class ScheduledEventsComponent extends ComponentWithTable implements OnInit, OnDestroy {
|
||||
public scheduledEvents: ScheduledEvent[] = [];
|
||||
public loading = true;
|
||||
|
||||
public filterForm!: FormGroup<{
|
||||
id: FormControl<number | null>,
|
||||
interval: FormControl<string | null>,
|
||||
name: FormControl<string | null>,
|
||||
description: FormControl<string | null>,
|
||||
channelId: FormControl<string | null>,
|
||||
startTime: FormControl<string | null>,
|
||||
endTime: FormControl<string | null>,
|
||||
entityType: FormControl<EventType | null>,
|
||||
location: FormControl<string | null>,
|
||||
}>;
|
||||
|
||||
public filter: ScheduledEventFilter = {};
|
||||
public page: Page = {
|
||||
pageSize: undefined,
|
||||
pageIndex: undefined
|
||||
};
|
||||
public sort: Sort = {
|
||||
sortColumn: undefined,
|
||||
sortDirection: undefined
|
||||
};
|
||||
|
||||
public totalRecords: number = 0;
|
||||
|
||||
private unsubscriber = new Subject<void>();
|
||||
private server: Server = {};
|
||||
public user: UserDTO | null = null;
|
||||
public query: string = Queries.scheduledEventWithHistoryQuery;
|
||||
public _editableScheduledEvent?: ScheduledEvent = undefined;
|
||||
|
||||
set editableScheduledEvent(event: ScheduledEvent | undefined) {
|
||||
if (!event) {
|
||||
this.isEditingNew = false;
|
||||
} else if (this._editableScheduledEvent && event && this._editableScheduledEvent !== event) {
|
||||
this.onRowEditSave(event);
|
||||
}
|
||||
this._editableScheduledEvent = event;
|
||||
}
|
||||
|
||||
get editableScheduledEvent(): ScheduledEvent | undefined {
|
||||
return this._editableScheduledEvent;
|
||||
}
|
||||
|
||||
public constructor(
|
||||
private authService: AuthService,
|
||||
private spinner: SpinnerService,
|
||||
private toastService: ToastService,
|
||||
private confirmDialog: ConfirmationDialogService,
|
||||
private fb: FormBuilder,
|
||||
private translate: TranslateService,
|
||||
private data: DataService,
|
||||
private sidebar: SidebarService,
|
||||
private route: ActivatedRoute) {
|
||||
super("ScheduledEvent", ["id", "interval", "name", "description", "channel_id", "start_time", "end_time", "type", "location"],
|
||||
(oldElement: ScheduledEvent, newElement: ScheduledEvent) => {
|
||||
return oldElement.name === newElement.name;
|
||||
});
|
||||
}
|
||||
|
||||
public ngOnInit(): void {
|
||||
this.loading = true;
|
||||
this.setFilterForm();
|
||||
this.data.getServerFromRoute(this.route).then(async server => {
|
||||
this.server = server;
|
||||
let authUser = await this.authService.getLoggedInUser();
|
||||
this.user = authUser?.users?.find(u => u.server == this.server.id) ?? null;
|
||||
});
|
||||
}
|
||||
|
||||
public ngOnDestroy(): void {
|
||||
this.unsubscriber.next();
|
||||
this.unsubscriber.complete();
|
||||
}
|
||||
|
||||
public loadNextPage(): void {
|
||||
this.data.query<ScheduledEventListQuery>(Queries.scheduledEventQuery, {
|
||||
serverId: this.server.id, filter: this.filter, page: this.page, sort: this.sort
|
||||
},
|
||||
(data: Query) => {
|
||||
return data.servers[0];
|
||||
}
|
||||
).subscribe(data => {
|
||||
this.totalRecords = data.scheduledEventCount;
|
||||
this.scheduledEvents = data.scheduledEvents;
|
||||
this.spinner.hideSpinner();
|
||||
this.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
public setFilterForm(): void {
|
||||
this.filterForm = this.fb.group({
|
||||
id: new FormControl<number | null>(null),
|
||||
interval: new FormControl<string | null>(null),
|
||||
name: new FormControl<string | null>(null),
|
||||
description: new FormControl<string | null>(null),
|
||||
channelId: new FormControl<string | null>(null),
|
||||
startTime: new FormControl<string | null>(null),
|
||||
endTime: new FormControl<string | null>(null),
|
||||
entityType: new FormControl<EventType | null>(null),
|
||||
location: new FormControl<string | null>(null)
|
||||
});
|
||||
|
||||
this.filterForm.valueChanges.pipe(
|
||||
takeUntil(this.unsubscriber),
|
||||
debounceTime(600)
|
||||
).subscribe(changes => {
|
||||
if (changes.id) {
|
||||
this.filter.id = changes.id;
|
||||
} else {
|
||||
this.filter.id = undefined;
|
||||
}
|
||||
|
||||
if (changes.interval) {
|
||||
this.filter.interval = changes.interval;
|
||||
} else {
|
||||
this.filter.interval = undefined;
|
||||
}
|
||||
|
||||
if (changes.name) {
|
||||
this.filter.name = changes.name;
|
||||
} else {
|
||||
this.filter.name = undefined;
|
||||
}
|
||||
|
||||
if (changes.description) {
|
||||
this.filter.description = changes.description;
|
||||
} else {
|
||||
this.filter.description = undefined;
|
||||
}
|
||||
|
||||
if (changes.channelId) {
|
||||
this.filter.channelId = changes.channelId;
|
||||
} else {
|
||||
this.filter.channelId = undefined;
|
||||
}
|
||||
|
||||
if (changes.startTime) {
|
||||
this.filter.startTime = changes.startTime;
|
||||
} else {
|
||||
this.filter.startTime = undefined;
|
||||
}
|
||||
|
||||
if (changes.endTime) {
|
||||
this.filter.endTime = changes.endTime;
|
||||
} else {
|
||||
this.filter.endTime = undefined;
|
||||
}
|
||||
|
||||
if (changes.entityType) {
|
||||
this.filter.entityType = changes.entityType;
|
||||
} else {
|
||||
this.filter.entityType = undefined;
|
||||
}
|
||||
|
||||
if (changes.location) {
|
||||
this.filter.location = changes.location;
|
||||
} else {
|
||||
this.filter.location = undefined;
|
||||
}
|
||||
|
||||
if (this.page.pageSize)
|
||||
this.page.pageSize = 10;
|
||||
|
||||
if (this.page.pageIndex)
|
||||
this.page.pageIndex = 0;
|
||||
|
||||
this.loadNextPage();
|
||||
});
|
||||
}
|
||||
|
||||
public newScheduledEventTemplate: ScheduledEvent = {
|
||||
createdAt: "",
|
||||
modifiedAt: ""
|
||||
};
|
||||
|
||||
public nextPage(event: LazyLoadEvent): void {
|
||||
this.page.pageSize = event.rows ?? 0;
|
||||
if (event.first != null && event.rows != null)
|
||||
this.page.pageIndex = event.first / event.rows;
|
||||
this.sort.sortColumn = event.sortField ?? undefined;
|
||||
this.sort.sortDirection = event.sortOrder === 1 ? SortDirection.ASC : event.sortOrder === -1 ? SortDirection.DESC : SortDirection.ASC;
|
||||
|
||||
this.loadNextPage();
|
||||
}
|
||||
|
||||
public resetFilters(): void {
|
||||
this.filterForm.reset();
|
||||
}
|
||||
|
||||
public onRowEditInit(table: Table, event: ScheduledEvent, index: number): void {
|
||||
this.editableScheduledEvent = event;
|
||||
}
|
||||
|
||||
public override onRowEditSave(newScheduledEvent: ScheduledEvent): void {
|
||||
if (this.isEditingNew && JSON.stringify(newScheduledEvent) === JSON.stringify(this.newScheduledEventTemplate)) {
|
||||
this.isEditingNew = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.isEditingNew) {
|
||||
this.spinner.showSpinner();
|
||||
this.data.mutation<ScheduledEventMutationResult>(Mutations.createScheduledEvent, {
|
||||
interval: newScheduledEvent.interval,
|
||||
name: newScheduledEvent.name,
|
||||
description: newScheduledEvent.description,
|
||||
channelId: newScheduledEvent.channelId,
|
||||
startTime: newScheduledEvent.startTime,
|
||||
endTime: newScheduledEvent.endTime,
|
||||
entityType: newScheduledEvent.entityType,
|
||||
location: newScheduledEvent.location,
|
||||
serverId: this.server.id
|
||||
}
|
||||
).pipe(catchError(err => {
|
||||
this.isEditingNew = false;
|
||||
this.spinner.hideSpinner();
|
||||
return throwError(err);
|
||||
})).subscribe(result => {
|
||||
this.isEditingNew = false;
|
||||
this.spinner.hideSpinner();
|
||||
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;
|
||||
}
|
||||
|
||||
this.spinner.showSpinner();
|
||||
this.data.mutation<ScheduledEventMutationResult>(Mutations.updateScheduledEvent, {
|
||||
id: newScheduledEvent.id,
|
||||
interval: newScheduledEvent.interval,
|
||||
name: newScheduledEvent.name,
|
||||
description: newScheduledEvent.description,
|
||||
channelId: newScheduledEvent.channelId,
|
||||
startTime: newScheduledEvent.startTime,
|
||||
endTime: newScheduledEvent.endTime,
|
||||
entityType: newScheduledEvent.entityType,
|
||||
location: newScheduledEvent.location,
|
||||
serverId: this.server.id
|
||||
}
|
||||
).pipe(catchError(err => {
|
||||
this.spinner.hideSpinner();
|
||||
return throwError(err);
|
||||
})).subscribe(_ => {
|
||||
this.spinner.hideSpinner();
|
||||
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.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, {
|
||||
id: ScheduledEvent.id
|
||||
}
|
||||
).pipe(catchError(err => {
|
||||
this.spinner.hideSpinner();
|
||||
return throwError(err);
|
||||
})).subscribe(l => {
|
||||
this.spinner.hideSpinner();
|
||||
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();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public addScheduledEvent(): void {
|
||||
this.isEditingNew = true;
|
||||
this.editableScheduledEvent = JSON.parse(JSON.stringify(this.newScheduledEventTemplate));
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
import { RouterModule, Routes } from "@angular/router";
|
||||
import { ScheduledEventsComponent } from "./components/scheduled-events/scheduled-events.component";
|
||||
|
||||
const routes: Routes = [
|
||||
{ path: "", component: ScheduledEventsComponent }
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class ScheduledEventsRoutingModule {
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
import { CommonModule } from "@angular/common";
|
||||
|
||||
import { ScheduledEventsRoutingModule } from "./scheduled-events-routing.module";
|
||||
import { ScheduledEventsComponent } from "./components/scheduled-events/scheduled-events.component";
|
||||
import { SharedModule } from "../../../shared/shared.module";
|
||||
import {
|
||||
EditScheduledEventDialogComponent
|
||||
} from "./components/edit-scheduled-event-dialog/edit-scheduled-event-dialog.component";
|
||||
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
ScheduledEventsComponent,
|
||||
EditScheduledEventDialogComponent
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
SharedModule,
|
||||
ScheduledEventsRoutingModule
|
||||
],
|
||||
exports: []
|
||||
})
|
||||
export class ScheduledEventsModule {
|
||||
}
|
@ -19,6 +19,7 @@ const routes: Routes = [
|
||||
{ path: "levels", loadChildren: () => import("./levels/levels.module").then(m => m.LevelsModule), canActivate: [AuthGuard], data: { memberRole: MemberRoles.Moderator } },
|
||||
{ path: "achievements", loadChildren: () => import("./achievements/achievements.module").then(m => m.AchievementsModule), canActivate: [AuthGuard], data: { memberRole: MemberRoles.Moderator } },
|
||||
{ path: "short-role-names", loadChildren: () => import("./short-role-name/short-role-name.module").then(m => m.ShortRoleNameModule), canActivate: [AuthGuard], data: { memberRole: MemberRoles.Moderator } },
|
||||
{ path: "scheduled-events", loadChildren: () => import("./scheduled-events/scheduled-events.module").then(m => m.ScheduledEventsModule), canActivate: [AuthGuard], data: { memberRole: MemberRoles.Moderator } },
|
||||
{ path: "config", loadChildren: () => import("./config/config.module").then(m => m.ConfigModule), canActivate: [AuthGuard], data: { memberRole: MemberRoles.Admin } }
|
||||
];
|
||||
|
||||
|
@ -1,14 +1,8 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { ShortRoleNamesComponent } from './components/short-role-names/short-role-names.component';
|
||||
import { NgModule } from "@angular/core";
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { ShortRoleNamesComponent } from "./components/short-role-names/short-role-names.component";
|
||||
import { ShortRoleNameRoutingModule } from "./short-role-name-routing.module";
|
||||
import { ButtonModule } from "primeng/button";
|
||||
import { InputTextModule } from "primeng/inputtext";
|
||||
import { ReactiveFormsModule } from "@angular/forms";
|
||||
import { SharedModule } from "../../../shared/shared.module";
|
||||
import { TableModule } from "primeng/table";
|
||||
import { TranslateModule } from "@ngx-translate/core";
|
||||
|
||||
|
||||
|
||||
@NgModule({
|
||||
@ -18,13 +12,7 @@ import { TranslateModule } from "@ngx-translate/core";
|
||||
imports: [
|
||||
CommonModule,
|
||||
ShortRoleNameRoutingModule,
|
||||
ButtonModule,
|
||||
InputTextModule,
|
||||
ReactiveFormsModule,
|
||||
SharedModule,
|
||||
SharedModule,
|
||||
TableModule,
|
||||
TranslateModule
|
||||
]
|
||||
})
|
||||
export class ShortRoleNameModule { }
|
||||
|
@ -4,7 +4,7 @@ import { BehaviorSubject, forkJoin, Observable } from "rxjs";
|
||||
import { AuthRoles } from "../../models/auth/auth-roles.enum";
|
||||
import { AuthService } from "../auth/auth.service";
|
||||
import { TranslateService } from "@ngx-translate/core";
|
||||
import { NavigationEnd, Router } from "@angular/router";
|
||||
import { Router } from "@angular/router";
|
||||
import { ThemeService } from "../theme/theme.service";
|
||||
import { Server } from "../../models/data/server.model";
|
||||
import { UserDTO } from "../../models/auth/auth-user.dto";
|
||||
@ -30,6 +30,7 @@ export class SidebarService {
|
||||
serverAutoRoles: MenuItem = {};
|
||||
serverLevels: MenuItem = {};
|
||||
serverAchievements: MenuItem = {};
|
||||
serverScheduledEvents: MenuItem = {};
|
||||
serverShortRoleNames: MenuItem = {};
|
||||
serverConfig: MenuItem = {};
|
||||
serverMenu: MenuItem = {};
|
||||
@ -110,6 +111,13 @@ export class SidebarService {
|
||||
routerLink: `server/${this.server?.id}/achievements`
|
||||
};
|
||||
|
||||
this.serverScheduledEvents = {
|
||||
label: this.isSidebarOpen ? this.translateService.instant("sidebar.server.scheduled_events") : "",
|
||||
icon: "pi pi-calendar",
|
||||
visible: true,
|
||||
routerLink: `server/${this.server?.id}/scheduled-events`
|
||||
};
|
||||
|
||||
this.serverShortRoleNames = {
|
||||
label: this.isSidebarOpen ? this.translateService.instant("sidebar.server.short_role_names") : "",
|
||||
icon: "pi pi-list",
|
||||
@ -129,7 +137,7 @@ export class SidebarService {
|
||||
icon: "pi pi-server",
|
||||
visible: false,
|
||||
expanded: true,
|
||||
items: [this.serverDashboard, this.serverProfile, this.serverMembers, this.serverAutoRoles, this.serverLevels, this.serverAchievements, this.serverShortRoleNames, this.serverConfig]
|
||||
items: [this.serverDashboard, this.serverProfile, this.serverMembers, this.serverAutoRoles, this.serverLevels, this.serverAchievements, this.serverScheduledEvents, this.serverShortRoleNames, this.serverConfig]
|
||||
};
|
||||
this.adminConfig = {
|
||||
label: this.isSidebarOpen ? this.translateService.instant("sidebar.config") : "",
|
||||
@ -205,6 +213,7 @@ export class SidebarService {
|
||||
this.serverAutoRoles.visible = isTechnicianAndFullAccessActive || this.hasFeature("AutoRoleModule") && user?.isModerator;
|
||||
this.serverLevels.visible = isTechnicianAndFullAccessActive || this.hasFeature("LevelModule") && user?.isModerator;
|
||||
this.serverAchievements.visible = isTechnicianAndFullAccessActive || this.hasFeature("AchievementsModule") && user?.isModerator;
|
||||
this.serverScheduledEvents.visible = isTechnicianAndFullAccessActive || this.hasFeature("ScheduledEvents") && user?.isModerator;
|
||||
this.serverShortRoleNames.visible = isTechnicianAndFullAccessActive || this.hasFeature("ShortRoleName") && user?.isAdmin;
|
||||
|
||||
this.serverConfig.visible = isTechnicianAndFullAccessActive || user?.isAdmin;
|
||||
|
@ -124,12 +124,14 @@
|
||||
},
|
||||
"common": {
|
||||
"404": "404 - Der Eintrag konnte nicht gefunden werden",
|
||||
"abort": "Abbrechen",
|
||||
"actions": "Aktionen",
|
||||
"active": "Aktiv",
|
||||
"add": "Hinzufügen",
|
||||
"attribute": "Attribut",
|
||||
"auth_role": "Rolle",
|
||||
"author": "Autor",
|
||||
"back": "Zurück",
|
||||
"bool_as_string": {
|
||||
"false": "Nein",
|
||||
"true": "Ja"
|
||||
@ -137,12 +139,14 @@
|
||||
"channel_id": "Kanal Id",
|
||||
"channel_name": "Kanal",
|
||||
"color": "Farbe",
|
||||
"continue": "Weiter",
|
||||
"created_at": "Erstellt am",
|
||||
"description": "Beschreibung",
|
||||
"discord_id": "Discord Id",
|
||||
"edit": "Bearbeiten",
|
||||
"email": "E-Mail",
|
||||
"emoji": "Emoji",
|
||||
"end_time": "Endzeit",
|
||||
"error": "Fehler",
|
||||
"export": "Exportieren",
|
||||
"feature_flags": "Funktionen",
|
||||
@ -176,11 +180,13 @@
|
||||
},
|
||||
"id": "Id",
|
||||
"import": "Importieren",
|
||||
"interval": "Interval",
|
||||
"joined_at": "Beigetreten am",
|
||||
"last_name": "Nachname",
|
||||
"leaved_at": "Verlassen am",
|
||||
"left_server": "Aktiv",
|
||||
"level": "Level",
|
||||
"location": "Ort",
|
||||
"message_id": "Nachricht Id",
|
||||
"min_xp": "Min. XP",
|
||||
"modified_at": "Bearbeitet am",
|
||||
@ -200,10 +206,12 @@
|
||||
"role": "Rolle",
|
||||
"rule_count": "Regeln",
|
||||
"save": "Speichern",
|
||||
"start_time": "Startzeit",
|
||||
"state": {
|
||||
"off": "Aus",
|
||||
"on": "Ein"
|
||||
},
|
||||
"type": "Typ",
|
||||
"user_warnings": "Verwarnungen",
|
||||
"users": "Benutzer",
|
||||
"value": "Wert",
|
||||
@ -327,6 +335,7 @@
|
||||
"config": "Konfiguration",
|
||||
"dashboard": "Dashboard",
|
||||
"members": "Mitglieder",
|
||||
"scheduled_events": "Geplante Events",
|
||||
"server": {
|
||||
"achievements": "Errungenschaften",
|
||||
"auto_roles": "Auto Rollen",
|
||||
@ -335,6 +344,7 @@
|
||||
"levels": "Level",
|
||||
"members": "Mitglieder",
|
||||
"profile": "Dein Profil",
|
||||
"scheduled_events": "Geplante Events",
|
||||
"short_role_names": "Rollen Kürzel"
|
||||
},
|
||||
"server_empty": "Kein Server ausgewählt",
|
||||
@ -538,6 +548,57 @@
|
||||
"reaction_count": "Anzahl Reaktionen",
|
||||
"xp": "XP"
|
||||
},
|
||||
"scheduled_events": {
|
||||
"edit_dialog": {
|
||||
"add_header": "Event hinzufügen",
|
||||
"edit_header": "Event bearbeiten",
|
||||
"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",
|
||||
"tab_name": "Eventinformationen"
|
||||
},
|
||||
"location": {
|
||||
"header": "Wo ist dein Event?",
|
||||
"interval": "Interval",
|
||||
"intervals": {
|
||||
"daily": "Täglich",
|
||||
"monthly": "Monatlich",
|
||||
"weekly": "Wöchentlich",
|
||||
"yearly": "Jährlich"
|
||||
},
|
||||
"somewhere_else": "Irgendwo anders",
|
||||
"somewhere_else_input": "Ort eingeben",
|
||||
"stage": "Stage-Kanal",
|
||||
"stage_input": "Stage-Kanal auswählen",
|
||||
"tab_name": "Verzeichnis",
|
||||
"voice": "Sprachkanal",
|
||||
"voice_input": "Sprachkanal auswählen"
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
"header": "Rollen Kürzel",
|
||||
"message": {
|
||||
|
@ -124,12 +124,14 @@
|
||||
},
|
||||
"common": {
|
||||
"404": "404 - Entry not found!",
|
||||
"abort": "Abort",
|
||||
"actions": "Actions",
|
||||
"active": "Active",
|
||||
"add": "Add",
|
||||
"attribute": "Attribute",
|
||||
"auth_role": "Role",
|
||||
"author": "Author",
|
||||
"back": "Back",
|
||||
"bool_as_string": {
|
||||
"false": "No",
|
||||
"true": "Yes"
|
||||
@ -137,12 +139,14 @@
|
||||
"channel_id": "Channel Id",
|
||||
"channel_name": "Channel",
|
||||
"color": "Color",
|
||||
"continue": "Continue",
|
||||
"created_at": "Created at",
|
||||
"description": "Description",
|
||||
"discord_id": "Discord Id",
|
||||
"edit": "Edit",
|
||||
"email": "E-Mail",
|
||||
"emoji": "Emoji",
|
||||
"end_time": "End time",
|
||||
"error": "Error",
|
||||
"export": "Export",
|
||||
"feature_flags": "Features",
|
||||
@ -176,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",
|
||||
@ -200,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",
|
||||
@ -327,6 +335,7 @@
|
||||
"config": "Configuration",
|
||||
"dashboard": "Dashboard",
|
||||
"members": "Members",
|
||||
"scheduled_events": "Scheduled event",
|
||||
"server": {
|
||||
"achievements": "Achievements",
|
||||
"auto_roles": "Auto role",
|
||||
@ -335,6 +344,7 @@
|
||||
"levels": "Level",
|
||||
"members": "Members",
|
||||
"profile": "Your profile",
|
||||
"scheduled_events": "Scheduled events",
|
||||
"short_role_names": "Short role names"
|
||||
},
|
||||
"server_empty": "No server selected",
|
||||
@ -538,6 +548,57 @@
|
||||
"reaction_count": "Reaction count",
|
||||
"xp": "XP"
|
||||
},
|
||||
"scheduled_events": {
|
||||
"edit_dialog": {
|
||||
"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_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",
|
||||
"stage_input": "Select a stage channel",
|
||||
"tab_name": "Location",
|
||||
"voice": "Voice channel",
|
||||
"voice_input": "Select a voice channel"
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
"header": "Level",
|
||||
"message": {
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"WebVersion": {
|
||||
"Major": "1",
|
||||
"Minor": "2",
|
||||
"Micro": "2"
|
||||
}
|
||||
}
|
||||
"WebVersion": {
|
||||
"Major": "1",
|
||||
"Minor": "2",
|
||||
"Micro": "dev410"
|
||||
}
|
||||
}
|
@ -635,6 +635,18 @@ footer {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.btn,
|
||||
.icon-btn,
|
||||
.text-btn,
|
||||
.danger-btn,
|
||||
.danger-icon-btn {
|
||||
span {
|
||||
transition-duration: unset !important;
|
||||
}
|
||||
|
||||
transition: none !important;
|
||||
}
|
||||
|
||||
.spinner-component-wrapper {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
@ -661,6 +673,61 @@ p-inputNumber {
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
.edit-dialog {
|
||||
.p-dialog-content {
|
||||
padding: 0 !important;
|
||||
|
||||
.p-tabview-nav {
|
||||
justify-content: space-between;
|
||||
|
||||
li {
|
||||
width: 100%;
|
||||
|
||||
a {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
|
||||
textarea {
|
||||
min-height: 101px !important;
|
||||
|
||||
&:focus {
|
||||
box-shadow: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
.type {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
|
||||
.field-checkbox {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
input,
|
||||
.p-dropdown {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
p-calendar {
|
||||
.p-calendar {
|
||||
width: 100% !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
footer {
|
||||
.left,
|
||||
|
@ -113,3 +113,4 @@ p-table {
|
||||
.pi-sort-amount-down:before {
|
||||
content: "\e913" !important;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -552,6 +552,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
.danger-btn,
|
||||
.danger-icon-btn {
|
||||
background-color: transparent !important;
|
||||
color: $primaryTextColor !important;
|
||||
@ -568,22 +569,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,
|
||||
.p-datatable .p-sortable-column:not(.p-highlight):hover {
|
||||
@ -720,6 +705,8 @@
|
||||
}
|
||||
|
||||
.p-datepicker {
|
||||
color: $primaryTextColor !important;
|
||||
|
||||
.p-datepicker-header {
|
||||
color: $primaryHeaderColor !important;
|
||||
background-color: $primaryBackgroundColor !important;
|
||||
@ -777,4 +764,54 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user