[WIP] Added domain management
This commit is contained in:
parent
865e6465cf
commit
1001b6db5f
13
api/src/api_graphql/filter/domain_filter.py
Normal file
13
api/src/api_graphql/filter/domain_filter.py
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
from api_graphql.abc.db_model_filter_abc import DbModelFilterABC
|
||||||
|
from api_graphql.abc.filter.string_filter import StringFilter
|
||||||
|
|
||||||
|
|
||||||
|
class DomainFilter(DbModelFilterABC):
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
obj: dict,
|
||||||
|
):
|
||||||
|
DbModelFilterABC.__init__(self, obj)
|
||||||
|
|
||||||
|
self.add_field("name", StringFilter)
|
||||||
|
self.add_field("description", StringFilter)
|
53
api/src/api_graphql/graphql/domain.gql
Normal file
53
api/src/api_graphql/graphql/domain.gql
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
type DomainResult {
|
||||||
|
totalCount: Int
|
||||||
|
count: Int
|
||||||
|
nodes: [Domain]
|
||||||
|
}
|
||||||
|
|
||||||
|
type Domain implements DbModel {
|
||||||
|
id: ID
|
||||||
|
name: String
|
||||||
|
|
||||||
|
shortUrls: [ShortUrl]
|
||||||
|
|
||||||
|
deleted: Boolean
|
||||||
|
editor: User
|
||||||
|
createdUtc: String
|
||||||
|
updatedUtc: String
|
||||||
|
}
|
||||||
|
|
||||||
|
input DomainSort {
|
||||||
|
id: SortOrder
|
||||||
|
name: SortOrder
|
||||||
|
|
||||||
|
deleted: SortOrder
|
||||||
|
editorId: SortOrder
|
||||||
|
createdUtc: SortOrder
|
||||||
|
updatedUtc: SortOrder
|
||||||
|
}
|
||||||
|
|
||||||
|
input DomainFilter {
|
||||||
|
id: IntFilter
|
||||||
|
name: StringFilter
|
||||||
|
|
||||||
|
deleted: BooleanFilter
|
||||||
|
editor: IntFilter
|
||||||
|
createdUtc: DateFilter
|
||||||
|
updatedUtc: DateFilter
|
||||||
|
}
|
||||||
|
|
||||||
|
type DomainMutation {
|
||||||
|
create(input: DomainCreateInput!): Domain
|
||||||
|
update(input: DomainUpdateInput!): Domain
|
||||||
|
delete(id: ID!): Boolean
|
||||||
|
restore(id: ID!): Boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
input DomainCreateInput {
|
||||||
|
name: String!
|
||||||
|
}
|
||||||
|
|
||||||
|
input DomainUpdateInput {
|
||||||
|
id: ID!
|
||||||
|
name: String
|
||||||
|
}
|
@ -5,5 +5,6 @@ type Mutation {
|
|||||||
role: RoleMutation
|
role: RoleMutation
|
||||||
|
|
||||||
group: GroupMutation
|
group: GroupMutation
|
||||||
|
domain: DomainMutation
|
||||||
shortUrl: ShortUrlMutation
|
shortUrl: ShortUrlMutation
|
||||||
}
|
}
|
@ -11,6 +11,7 @@ type Query {
|
|||||||
userHasAnyPermission(permissions: [String]!): Boolean
|
userHasAnyPermission(permissions: [String]!): Boolean
|
||||||
notExistingUsersFromKeycloak: KeycloakUserResult
|
notExistingUsersFromKeycloak: KeycloakUserResult
|
||||||
|
|
||||||
|
domains(filter: [DomainFilter], sort: [DomainSort], skip: Int, take: Int): DomainResult
|
||||||
groups(filter: [GroupFilter], sort: [GroupSort], skip: Int, take: Int): GroupResult
|
groups(filter: [GroupFilter], sort: [GroupSort], skip: Int, take: Int): GroupResult
|
||||||
shortUrls(filter: [ShortUrlFilter], sort: [ShortUrlSort], skip: Int, take: Int): ShortUrlResult
|
shortUrls(filter: [ShortUrlFilter], sort: [ShortUrlSort], skip: Int, take: Int): ShortUrlResult
|
||||||
}
|
}
|
@ -11,6 +11,7 @@ type ShortUrl implements DbModel {
|
|||||||
description: String
|
description: String
|
||||||
visits: Int
|
visits: Int
|
||||||
group: Group
|
group: Group
|
||||||
|
domain: Domain
|
||||||
loadingScreen: Boolean
|
loadingScreen: Boolean
|
||||||
|
|
||||||
deleted: Boolean
|
deleted: Boolean
|
||||||
@ -55,6 +56,7 @@ input ShortUrlCreateInput {
|
|||||||
targetUrl: String!
|
targetUrl: String!
|
||||||
description: String
|
description: String
|
||||||
groupId: ID
|
groupId: ID
|
||||||
|
domainId: ID
|
||||||
loadingScreen: Boolean
|
loadingScreen: Boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,5 +66,6 @@ input ShortUrlUpdateInput {
|
|||||||
targetUrl: String
|
targetUrl: String
|
||||||
description: String
|
description: String
|
||||||
groupId: ID
|
groupId: ID
|
||||||
|
domainId: ID
|
||||||
loadingScreen: Boolean
|
loadingScreen: Boolean
|
||||||
}
|
}
|
||||||
|
13
api/src/api_graphql/input/domain_create_input.py
Normal file
13
api/src/api_graphql/input/domain_create_input.py
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
from api_graphql.abc.input_abc import InputABC
|
||||||
|
|
||||||
|
|
||||||
|
class DomainCreateInput(InputABC):
|
||||||
|
|
||||||
|
def __init__(self, src: dict):
|
||||||
|
InputABC.__init__(self, src)
|
||||||
|
|
||||||
|
self._name = self.option("name", str, required=True)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self) -> str:
|
||||||
|
return self._name
|
18
api/src/api_graphql/input/domain_update_input.py
Normal file
18
api/src/api_graphql/input/domain_update_input.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
from api_graphql.abc.input_abc import InputABC
|
||||||
|
|
||||||
|
|
||||||
|
class DomainUpdateInput(InputABC):
|
||||||
|
|
||||||
|
def __init__(self, src: dict):
|
||||||
|
InputABC.__init__(self, src)
|
||||||
|
|
||||||
|
self._id = self.option("id", int, required=True)
|
||||||
|
self._name = self.option("name", str)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def id(self) -> int:
|
||||||
|
return self._id
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self) -> str:
|
||||||
|
return self._name
|
@ -12,6 +12,7 @@ class ShortUrlCreateInput(InputABC):
|
|||||||
self._target_url = self.option("targetUrl", str, required=True)
|
self._target_url = self.option("targetUrl", str, required=True)
|
||||||
self._description = self.option("description", str)
|
self._description = self.option("description", str)
|
||||||
self._group_id = self.option("groupId", int)
|
self._group_id = self.option("groupId", int)
|
||||||
|
self._domain_id = self.option("domainId", int)
|
||||||
self._loading_screen = self.option("loadingScreen", bool)
|
self._loading_screen = self.option("loadingScreen", bool)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -30,6 +31,10 @@ class ShortUrlCreateInput(InputABC):
|
|||||||
def group_id(self) -> Optional[int]:
|
def group_id(self) -> Optional[int]:
|
||||||
return self._group_id
|
return self._group_id
|
||||||
|
|
||||||
|
@property
|
||||||
|
def domain_id(self) -> Optional[int]:
|
||||||
|
return self._domain_id
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def loading_screen(self) -> Optional[str]:
|
def loading_screen(self) -> Optional[str]:
|
||||||
return self._loading_screen
|
return self._loading_screen
|
||||||
|
@ -13,6 +13,7 @@ class ShortUrlUpdateInput(InputABC):
|
|||||||
self._target_url = self.option("targetUrl", str)
|
self._target_url = self.option("targetUrl", str)
|
||||||
self._description = self.option("description", str)
|
self._description = self.option("description", str)
|
||||||
self._group_id = self.option("groupId", int)
|
self._group_id = self.option("groupId", int)
|
||||||
|
self._domain_id = self.option("domainId", int)
|
||||||
self._loading_screen = self.option("loadingScreen", bool)
|
self._loading_screen = self.option("loadingScreen", bool)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -35,6 +36,10 @@ class ShortUrlUpdateInput(InputABC):
|
|||||||
def group_id(self) -> Optional[int]:
|
def group_id(self) -> Optional[int]:
|
||||||
return self._group_id
|
return self._group_id
|
||||||
|
|
||||||
|
@property
|
||||||
|
def domain_id(self) -> Optional[int]:
|
||||||
|
return self._domain_id
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def loading_screen(self) -> Optional[str]:
|
def loading_screen(self) -> Optional[str]:
|
||||||
return self._loading_screen
|
return self._loading_screen
|
||||||
|
@ -33,6 +33,16 @@ class Mutation(MutationABC):
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
self.add_mutation_type(
|
||||||
|
"domain",
|
||||||
|
"Domain",
|
||||||
|
require_any_permission=[
|
||||||
|
Permissions.domains_create,
|
||||||
|
Permissions.domains_update,
|
||||||
|
Permissions.domains_delete,
|
||||||
|
],
|
||||||
|
)
|
||||||
self.add_mutation_type(
|
self.add_mutation_type(
|
||||||
"group",
|
"group",
|
||||||
"Group",
|
"Group",
|
||||||
|
75
api/src/api_graphql/mutations/domain_mutation.py
Normal file
75
api/src/api_graphql/mutations/domain_mutation.py
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
from api_graphql.abc.mutation_abc import MutationABC
|
||||||
|
from api_graphql.input.domain_create_input import DomainCreateInput
|
||||||
|
from api_graphql.input.domain_update_input import DomainUpdateInput
|
||||||
|
from api_graphql.input.group_create_input import GroupCreateInput
|
||||||
|
from api_graphql.input.group_update_input import GroupUpdateInput
|
||||||
|
from core.logger import APILogger
|
||||||
|
from data.schemas.public.domain_dao import domainDao
|
||||||
|
from data.schemas.public.group import Group
|
||||||
|
from service.permission.permissions_enum import Permissions
|
||||||
|
|
||||||
|
logger = APILogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class DomainMutation(MutationABC):
|
||||||
|
def __init__(self):
|
||||||
|
MutationABC.__init__(self, "Domain")
|
||||||
|
|
||||||
|
self.mutation(
|
||||||
|
"create",
|
||||||
|
self.resolve_create,
|
||||||
|
DomainCreateInput,
|
||||||
|
require_any_permission=[Permissions.domains_create],
|
||||||
|
)
|
||||||
|
self.mutation(
|
||||||
|
"update",
|
||||||
|
self.resolve_update,
|
||||||
|
DomainUpdateInput,
|
||||||
|
require_any_permission=[Permissions.domains_update],
|
||||||
|
)
|
||||||
|
self.mutation(
|
||||||
|
"delete",
|
||||||
|
self.resolve_delete,
|
||||||
|
require_any_permission=[Permissions.domains_delete],
|
||||||
|
)
|
||||||
|
self.mutation(
|
||||||
|
"restore",
|
||||||
|
self.resolve_restore,
|
||||||
|
require_any_permission=[Permissions.domains_delete],
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def resolve_create(obj: GroupCreateInput, *_):
|
||||||
|
logger.debug(f"create domain: {obj.__dict__}")
|
||||||
|
|
||||||
|
domain = Group(
|
||||||
|
0,
|
||||||
|
obj.name,
|
||||||
|
)
|
||||||
|
nid = await domainDao.create(domain)
|
||||||
|
return await domainDao.get_by_id(nid)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def resolve_update(obj: GroupUpdateInput, *_):
|
||||||
|
logger.debug(f"update domain: {input}")
|
||||||
|
|
||||||
|
if obj.name is not None:
|
||||||
|
domain = await domainDao.get_by_id(obj.id)
|
||||||
|
domain.name = obj.name
|
||||||
|
await domainDao.update(domain)
|
||||||
|
|
||||||
|
return await domainDao.get_by_id(obj.id)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def resolve_delete(*_, id: str):
|
||||||
|
logger.debug(f"delete domain: {id}")
|
||||||
|
domain = await domainDao.get_by_id(id)
|
||||||
|
await domainDao.delete(domain)
|
||||||
|
return True
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def resolve_restore(*_, id: str):
|
||||||
|
logger.debug(f"restore domain: {id}")
|
||||||
|
domain = await domainDao.get_by_id(id)
|
||||||
|
await domainDao.restore(domain)
|
||||||
|
return True
|
@ -4,6 +4,7 @@ from api_graphql.abc.mutation_abc import MutationABC
|
|||||||
from api_graphql.input.short_url_create_input import ShortUrlCreateInput
|
from api_graphql.input.short_url_create_input import ShortUrlCreateInput
|
||||||
from api_graphql.input.short_url_update_input import ShortUrlUpdateInput
|
from api_graphql.input.short_url_update_input import ShortUrlUpdateInput
|
||||||
from core.logger import APILogger
|
from core.logger import APILogger
|
||||||
|
from data.schemas.public.domain_dao import domainDao
|
||||||
from data.schemas.public.group_dao import groupDao
|
from data.schemas.public.group_dao import groupDao
|
||||||
from data.schemas.public.short_url import ShortUrl
|
from data.schemas.public.short_url import ShortUrl
|
||||||
from data.schemas.public.short_url_dao import shortUrlDao
|
from data.schemas.public.short_url_dao import shortUrlDao
|
||||||
@ -49,6 +50,7 @@ class ShortUrlMutation(MutationABC):
|
|||||||
obj.target_url,
|
obj.target_url,
|
||||||
obj.description,
|
obj.description,
|
||||||
obj.group_id,
|
obj.group_id,
|
||||||
|
obj.domain_id,
|
||||||
obj.loading_screen,
|
obj.loading_screen,
|
||||||
)
|
)
|
||||||
nid = await shortUrlDao.create(short_url)
|
nid = await shortUrlDao.create(short_url)
|
||||||
@ -74,6 +76,16 @@ class ShortUrlMutation(MutationABC):
|
|||||||
if group_by_id is None:
|
if group_by_id is None:
|
||||||
raise NotFound(f"Group with id {obj.group_id} does not exist")
|
raise NotFound(f"Group with id {obj.group_id} does not exist")
|
||||||
short_url.group_id = obj.group_id
|
short_url.group_id = obj.group_id
|
||||||
|
else:
|
||||||
|
short_url.group_id = None
|
||||||
|
|
||||||
|
if obj.domain_id is not None:
|
||||||
|
domain_by_id = await domainDao.find_by_id(obj.domain_id)
|
||||||
|
if domain_by_id is None:
|
||||||
|
raise NotFound(f"Domain with id {obj.domain_id} does not exist")
|
||||||
|
short_url.domain_id = obj.domain_id
|
||||||
|
else:
|
||||||
|
short_url.domain_id = None
|
||||||
|
|
||||||
if obj.loading_screen is not None:
|
if obj.loading_screen is not None:
|
||||||
short_url.loading_screen = obj.loading_screen
|
short_url.loading_screen = obj.loading_screen
|
||||||
|
17
api/src/api_graphql/queries/domain_query.py
Normal file
17
api/src/api_graphql/queries/domain_query.py
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
from api_graphql.abc.db_model_query_abc import DbModelQueryABC
|
||||||
|
from data.schemas.public.domain import Domain
|
||||||
|
from data.schemas.public.group import Group
|
||||||
|
from data.schemas.public.short_url import ShortUrl
|
||||||
|
from data.schemas.public.short_url_dao import shortUrlDao
|
||||||
|
|
||||||
|
|
||||||
|
class DomainQuery(DbModelQueryABC):
|
||||||
|
def __init__(self):
|
||||||
|
DbModelQueryABC.__init__(self, "Domain")
|
||||||
|
|
||||||
|
self.set_field("name", lambda x, *_: x.name)
|
||||||
|
self.set_field("shortUrls", self._get_urls)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def _get_urls(domain: Domain, *_):
|
||||||
|
return await shortUrlDao.find_by({ShortUrl.domain_id: domain.id})
|
@ -9,5 +9,6 @@ class ShortUrlQuery(DbModelQueryABC):
|
|||||||
self.set_field("targetUrl", lambda x, *_: x.target_url)
|
self.set_field("targetUrl", lambda x, *_: x.target_url)
|
||||||
self.set_field("description", lambda x, *_: x.description)
|
self.set_field("description", lambda x, *_: x.description)
|
||||||
self.set_field("group", lambda x, *_: x.group)
|
self.set_field("group", lambda x, *_: x.group)
|
||||||
|
self.set_field("domain", lambda x, *_: x.domain)
|
||||||
self.set_field("visits", lambda x, *_: x.visit_count)
|
self.set_field("visits", lambda x, *_: x.visit_count)
|
||||||
self.set_field("loadingScreen", lambda x, *_: x.loading_screen)
|
self.set_field("loadingScreen", lambda x, *_: x.loading_screen)
|
||||||
|
@ -5,6 +5,7 @@ from api_graphql.abc.sort_abc import Sort
|
|||||||
from api_graphql.field.dao_field_builder import DaoFieldBuilder
|
from api_graphql.field.dao_field_builder import DaoFieldBuilder
|
||||||
from api_graphql.field.resolver_field_builder import ResolverFieldBuilder
|
from api_graphql.field.resolver_field_builder import ResolverFieldBuilder
|
||||||
from api_graphql.filter.api_key_filter import ApiKeyFilter
|
from api_graphql.filter.api_key_filter import ApiKeyFilter
|
||||||
|
from api_graphql.filter.domain_filter import DomainFilter
|
||||||
from api_graphql.filter.group_filter import GroupFilter
|
from api_graphql.filter.group_filter import GroupFilter
|
||||||
from api_graphql.filter.permission_filter import PermissionFilter
|
from api_graphql.filter.permission_filter import PermissionFilter
|
||||||
from api_graphql.filter.role_filter import RoleFilter
|
from api_graphql.filter.role_filter import RoleFilter
|
||||||
@ -18,6 +19,8 @@ from data.schemas.permission.permission import Permission
|
|||||||
from data.schemas.permission.permission_dao import permissionDao
|
from data.schemas.permission.permission_dao import permissionDao
|
||||||
from data.schemas.permission.role import Role
|
from data.schemas.permission.role import Role
|
||||||
from data.schemas.permission.role_dao import roleDao
|
from data.schemas.permission.role_dao import roleDao
|
||||||
|
from data.schemas.public.domain import Domain
|
||||||
|
from data.schemas.public.domain_dao import domainDao
|
||||||
from data.schemas.public.group import Group
|
from data.schemas.public.group import Group
|
||||||
from data.schemas.public.group_dao import groupDao
|
from data.schemas.public.group_dao import groupDao
|
||||||
from data.schemas.public.short_url import ShortUrl
|
from data.schemas.public.short_url import ShortUrl
|
||||||
@ -80,6 +83,20 @@ class Query(QueryABC):
|
|||||||
.with_require_any_permission([Permissions.users_create])
|
.with_require_any_permission([Permissions.users_create])
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
self.field(
|
||||||
|
DaoFieldBuilder("domains")
|
||||||
|
.with_dao(domainDao)
|
||||||
|
.with_filter(DomainFilter)
|
||||||
|
.with_sort(Sort[Domain])
|
||||||
|
.with_require_any_permission(
|
||||||
|
[
|
||||||
|
Permissions.domains,
|
||||||
|
Permissions.short_urls_create,
|
||||||
|
Permissions.short_urls_update,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
self.field(
|
self.field(
|
||||||
DaoFieldBuilder("groups")
|
DaoFieldBuilder("groups")
|
||||||
.with_dao(groupDao)
|
.with_dao(groupDao)
|
||||||
|
27
api/src/data/schemas/public/domain.py
Normal file
27
api/src/data/schemas/public/domain.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
from datetime import datetime
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from core.database.abc.db_model_abc import DbModelABC
|
||||||
|
from core.typing import SerialId
|
||||||
|
|
||||||
|
|
||||||
|
class Domain(DbModelABC):
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
id: SerialId,
|
||||||
|
name: str,
|
||||||
|
deleted: bool = False,
|
||||||
|
editor_id: Optional[SerialId] = None,
|
||||||
|
created: Optional[datetime] = None,
|
||||||
|
updated: Optional[datetime] = None,
|
||||||
|
):
|
||||||
|
DbModelABC.__init__(self, id, deleted, editor_id, created, updated)
|
||||||
|
self._name = name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self) -> str:
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
@name.setter
|
||||||
|
def name(self, value: str):
|
||||||
|
self._name = value
|
22
api/src/data/schemas/public/domain_dao.py
Normal file
22
api/src/data/schemas/public/domain_dao.py
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
from core.logger import DBLogger
|
||||||
|
from data.schemas.public.domain import Domain
|
||||||
|
from data.schemas.public.group import Group
|
||||||
|
|
||||||
|
logger = DBLogger(__name__)
|
||||||
|
|
||||||
|
from core.database.abc.db_model_dao_abc import DbModelDaoABC
|
||||||
|
|
||||||
|
|
||||||
|
class DomainDao(DbModelDaoABC[Group]):
|
||||||
|
def __init__(self):
|
||||||
|
DbModelDaoABC.__init__(self, __name__, Group, "public.domains")
|
||||||
|
self.attribute(Domain.name, str)
|
||||||
|
|
||||||
|
async def get_by_name(self, name: str) -> Group:
|
||||||
|
result = await self._db.select_map(
|
||||||
|
f"SELECT * FROM {self._table_name} WHERE Name = '{name}'"
|
||||||
|
)
|
||||||
|
return self.to_object(result[0])
|
||||||
|
|
||||||
|
|
||||||
|
domainDao = DomainDao()
|
@ -16,6 +16,7 @@ class ShortUrl(DbModelABC):
|
|||||||
target_url: str,
|
target_url: str,
|
||||||
description: Optional[str],
|
description: Optional[str],
|
||||||
group_id: Optional[SerialId],
|
group_id: Optional[SerialId],
|
||||||
|
domain_id: Optional[SerialId],
|
||||||
loading_screen: Optional[str] = None,
|
loading_screen: Optional[str] = None,
|
||||||
deleted: bool = False,
|
deleted: bool = False,
|
||||||
editor_id: Optional[SerialId] = None,
|
editor_id: Optional[SerialId] = None,
|
||||||
@ -27,6 +28,7 @@ class ShortUrl(DbModelABC):
|
|||||||
self._target_url = target_url
|
self._target_url = target_url
|
||||||
self._description = description
|
self._description = description
|
||||||
self._group_id = group_id
|
self._group_id = group_id
|
||||||
|
self._domain_id = domain_id
|
||||||
self._loading_screen = loading_screen
|
self._loading_screen = loading_screen
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -70,6 +72,23 @@ class ShortUrl(DbModelABC):
|
|||||||
|
|
||||||
return await groupDao.get_by_id(self._group_id)
|
return await groupDao.get_by_id(self._group_id)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def domain_id(self) -> SerialId:
|
||||||
|
return self._domain_id
|
||||||
|
|
||||||
|
@domain_id.setter
|
||||||
|
def domain_id(self, value: SerialId):
|
||||||
|
self._domain_id = value
|
||||||
|
|
||||||
|
@async_property
|
||||||
|
async def domain(self) -> Optional[Group]:
|
||||||
|
if self._domain_id is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
from data.schemas.public.domain_dao import domainDao
|
||||||
|
|
||||||
|
return await domainDao.get_by_id(self._domain_id)
|
||||||
|
|
||||||
@async_property
|
@async_property
|
||||||
async def visit_count(self) -> int:
|
async def visit_count(self) -> int:
|
||||||
from data.schemas.public.short_url_visit_dao import shortUrlVisitDao
|
from data.schemas.public.short_url_visit_dao import shortUrlVisitDao
|
||||||
|
@ -13,6 +13,7 @@ class ShortUrlDao(DbModelDaoABC[ShortUrl]):
|
|||||||
self.attribute(ShortUrl.target_url, str)
|
self.attribute(ShortUrl.target_url, str)
|
||||||
self.attribute(ShortUrl.description, str)
|
self.attribute(ShortUrl.description, str)
|
||||||
self.attribute(ShortUrl.group_id, int)
|
self.attribute(ShortUrl.group_id, int)
|
||||||
|
self.attribute(ShortUrl.domain_id, int)
|
||||||
self.attribute(ShortUrl.loading_screen, bool)
|
self.attribute(ShortUrl.loading_screen, bool)
|
||||||
|
|
||||||
|
|
||||||
|
32
api/src/data/scripts/2025-01-10-23-15-domains.sql
Normal file
32
api/src/data/scripts/2025-01-10-23-15-domains.sql
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
CREATE
|
||||||
|
SCHEMA IF NOT EXISTS public;
|
||||||
|
|
||||||
|
-- groups
|
||||||
|
CREATE TABLE IF NOT EXISTS public.domains
|
||||||
|
(
|
||||||
|
Id SERIAL PRIMARY KEY,
|
||||||
|
Name VARCHAR(255) NOT NULL,
|
||||||
|
-- for history
|
||||||
|
Deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
|
EditorId INT NULL REFERENCES administration.users (Id),
|
||||||
|
CreatedUtc timestamptz NOT NULL DEFAULT NOW(),
|
||||||
|
UpdatedUtc timestamptz NOT NULL DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS public.domains_history
|
||||||
|
(
|
||||||
|
LIKE public.domains
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TRIGGER domains_history_trigger
|
||||||
|
BEFORE INSERT OR UPDATE OR DELETE
|
||||||
|
ON public.domains
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE FUNCTION public.history_trigger_function();
|
||||||
|
|
||||||
|
ALTER TABLE public.short_urls
|
||||||
|
ADD COLUMN domainId INT NULL REFERENCES public.domains (Id);
|
||||||
|
|
||||||
|
ALTER TABLE public.short_urls_history
|
||||||
|
ADD COLUMN domainId INT NULL REFERENCES public.domains (Id);
|
||||||
|
|
@ -31,6 +31,12 @@ class Permissions(Enum):
|
|||||||
"""
|
"""
|
||||||
Public
|
Public
|
||||||
"""
|
"""
|
||||||
|
# domains
|
||||||
|
domains = "domains"
|
||||||
|
domains_create = "domains.create"
|
||||||
|
domains_update = "domains.update"
|
||||||
|
domains_delete = "domains.delete"
|
||||||
|
|
||||||
# groups
|
# groups
|
||||||
groups = "groups"
|
groups = "groups"
|
||||||
groups_create = "groups.create"
|
groups_create = "groups.create"
|
||||||
|
@ -1,30 +1,35 @@
|
|||||||
export enum PermissionsEnum {
|
export enum PermissionsEnum {
|
||||||
// Administration
|
// Administration
|
||||||
apiKeys = "api_keys",
|
apiKeys = 'api_keys',
|
||||||
apiKeysCreate = "api_keys.create",
|
apiKeysCreate = 'api_keys.create',
|
||||||
apiKeysUpdate = "api_keys.update",
|
apiKeysUpdate = 'api_keys.update',
|
||||||
apiKeysDelete = "api_keys.delete",
|
apiKeysDelete = 'api_keys.delete',
|
||||||
|
|
||||||
// Users
|
// Users
|
||||||
users = "users",
|
users = 'users',
|
||||||
usersCreate = "users.create",
|
usersCreate = 'users.create',
|
||||||
usersUpdate = "users.update",
|
usersUpdate = 'users.update',
|
||||||
usersDelete = "users.delete",
|
usersDelete = 'users.delete',
|
||||||
|
|
||||||
// Permissions
|
// Permissions
|
||||||
roles = "roles",
|
roles = 'roles',
|
||||||
rolesCreate = "roles.create",
|
rolesCreate = 'roles.create',
|
||||||
rolesUpdate = "roles.update",
|
rolesUpdate = 'roles.update',
|
||||||
rolesDelete = "roles.delete",
|
rolesDelete = 'roles.delete',
|
||||||
|
|
||||||
// Public
|
// Public
|
||||||
groups = "groups",
|
domains = 'domains',
|
||||||
groupsCreate = "groups.create",
|
domainsCreate = 'domains.create',
|
||||||
groupsUpdate = "groups.update",
|
domainsUpdate = 'domains.update',
|
||||||
groupsDelete = "groups.delete",
|
domainsDelete = 'domains.delete',
|
||||||
|
|
||||||
shortUrls = "short_urls",
|
groups = 'groups',
|
||||||
shortUrlsCreate = "short_urls.create",
|
groupsCreate = 'groups.create',
|
||||||
shortUrlsUpdate = "short_urls.update",
|
groupsUpdate = 'groups.update',
|
||||||
shortUrlsDelete = "short_urls.delete",
|
groupsDelete = 'groups.delete',
|
||||||
|
|
||||||
|
shortUrls = 'short_urls',
|
||||||
|
shortUrlsCreate = 'short_urls.create',
|
||||||
|
shortUrlsUpdate = 'short_urls.update',
|
||||||
|
shortUrlsDelete = 'short_urls.delete',
|
||||||
}
|
}
|
||||||
|
14
web/src/app/model/entities/domain.ts
Normal file
14
web/src/app/model/entities/domain.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { DbModel } from 'src/app/model/entities/db-model';
|
||||||
|
|
||||||
|
export interface Domain extends DbModel {
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DomainCreateInput {
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DomainUpdateInput {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
import { DbModel } from 'src/app/model/entities/db-model';
|
import { DbModel } from 'src/app/model/entities/db-model';
|
||||||
import { Group } from 'src/app/model/entities/group';
|
import { Group } from 'src/app/model/entities/group';
|
||||||
|
import { Domain } from 'src/app/model/entities/domain';
|
||||||
|
|
||||||
export interface ShortUrl extends DbModel {
|
export interface ShortUrl extends DbModel {
|
||||||
shortUrl: string;
|
shortUrl: string;
|
||||||
@ -8,6 +9,7 @@ export interface ShortUrl extends DbModel {
|
|||||||
loadingScreen: boolean;
|
loadingScreen: boolean;
|
||||||
visits: number;
|
visits: number;
|
||||||
group?: Group;
|
group?: Group;
|
||||||
|
domain?: Domain;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ShortUrlDto {
|
export interface ShortUrlDto {
|
||||||
@ -22,6 +24,7 @@ export interface ShortUrlCreateInput {
|
|||||||
description: string;
|
description: string;
|
||||||
loadingScreen: boolean;
|
loadingScreen: boolean;
|
||||||
groupId: number;
|
groupId: number;
|
||||||
|
domainId: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ShortUrlUpdateInput {
|
export interface ShortUrlUpdateInput {
|
||||||
@ -31,4 +34,5 @@ export interface ShortUrlUpdateInput {
|
|||||||
description: string;
|
description: string;
|
||||||
loadingScreen: boolean;
|
loadingScreen: boolean;
|
||||||
groupId: number;
|
groupId: number;
|
||||||
|
domainId: number;
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,15 @@ import { PermissionGuard } from 'src/app/core/guard/permission.guard';
|
|||||||
import { PermissionsEnum } from 'src/app/model/auth/permissionsEnum';
|
import { PermissionsEnum } from 'src/app/model/auth/permissionsEnum';
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
|
{
|
||||||
|
path: 'domains',
|
||||||
|
loadChildren: () =>
|
||||||
|
import('src/app/modules/admin/domains/domains.module').then(
|
||||||
|
m => m.DomainsModule
|
||||||
|
),
|
||||||
|
canActivate: [PermissionGuard],
|
||||||
|
data: { permissions: [PermissionsEnum.domains] },
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'groups',
|
path: 'groups',
|
||||||
loadChildren: () =>
|
loadChildren: () =>
|
||||||
|
35
web/src/app/modules/admin/domains/domains.columns.ts
Normal file
35
web/src/app/modules/admin/domains/domains.columns.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import { Injectable, Provider } from '@angular/core';
|
||||||
|
import {
|
||||||
|
DB_MODEL_COLUMNS,
|
||||||
|
ID_COLUMN,
|
||||||
|
PageColumns,
|
||||||
|
} from 'src/app/core/base/page.columns';
|
||||||
|
import { TableColumn } from 'src/app/modules/shared/components/table/table.model';
|
||||||
|
import { Domain } from 'src/app/model/entities/domain';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class DomainsColumns extends PageColumns<Domain> {
|
||||||
|
get(): TableColumn<Domain>[] {
|
||||||
|
return [
|
||||||
|
ID_COLUMN,
|
||||||
|
{
|
||||||
|
name: 'name',
|
||||||
|
label: 'common.name',
|
||||||
|
type: 'text',
|
||||||
|
filterable: true,
|
||||||
|
value: (row: Domain) => row.name,
|
||||||
|
},
|
||||||
|
...DB_MODEL_COLUMNS,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
static provide(): Provider[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
provide: PageColumns,
|
||||||
|
useClass: DomainsColumns,
|
||||||
|
},
|
||||||
|
DomainsColumns,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
232
web/src/app/modules/admin/domains/domains.data.service.ts
Normal file
232
web/src/app/modules/admin/domains/domains.data.service.ts
Normal file
@ -0,0 +1,232 @@
|
|||||||
|
import { Injectable, Provider } from '@angular/core';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import {
|
||||||
|
Create,
|
||||||
|
Delete,
|
||||||
|
PageDataService,
|
||||||
|
Restore,
|
||||||
|
Update,
|
||||||
|
} from 'src/app/core/base/page.data.service';
|
||||||
|
import { Filter } from 'src/app/model/graphql/filter/filter.model';
|
||||||
|
import { Sort } from 'src/app/model/graphql/filter/sort.model';
|
||||||
|
import { Apollo, gql } from 'apollo-angular';
|
||||||
|
import { QueryResult } from 'src/app/model/entities/query-result';
|
||||||
|
import { DB_MODEL_FRAGMENT } from 'src/app/model/graphql/db-model.query';
|
||||||
|
import { catchError, map } from 'rxjs/operators';
|
||||||
|
import { SpinnerService } from 'src/app/service/spinner.service';
|
||||||
|
import {
|
||||||
|
Domain,
|
||||||
|
DomainCreateInput,
|
||||||
|
DomainUpdateInput,
|
||||||
|
} from 'src/app/model/entities/domain';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class DomainsDataService
|
||||||
|
extends PageDataService<Domain>
|
||||||
|
implements
|
||||||
|
Create<Domain, DomainCreateInput>,
|
||||||
|
Update<Domain, DomainUpdateInput>,
|
||||||
|
Delete<Domain>,
|
||||||
|
Restore<Domain>
|
||||||
|
{
|
||||||
|
constructor(
|
||||||
|
private spinner: SpinnerService,
|
||||||
|
private apollo: Apollo
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
load(
|
||||||
|
filter?: Filter[] | undefined,
|
||||||
|
sort?: Sort[] | undefined,
|
||||||
|
skip?: number | undefined,
|
||||||
|
take?: number | undefined
|
||||||
|
): Observable<QueryResult<Domain>> {
|
||||||
|
return this.apollo
|
||||||
|
.query<{ domains: QueryResult<Domain> }>({
|
||||||
|
query: gql`
|
||||||
|
query getDomains(
|
||||||
|
$filter: [DomainFilter]
|
||||||
|
$sort: [DomainSort]
|
||||||
|
$skip: Int
|
||||||
|
$take: Int
|
||||||
|
) {
|
||||||
|
domains(filter: $filter, sort: $sort, skip: $skip, take: $take) {
|
||||||
|
count
|
||||||
|
totalCount
|
||||||
|
nodes {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
|
||||||
|
...DB_MODEL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
${DB_MODEL_FRAGMENT}
|
||||||
|
`,
|
||||||
|
variables: {
|
||||||
|
filter: filter,
|
||||||
|
sort: sort,
|
||||||
|
skip: skip,
|
||||||
|
take: take,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.pipe(
|
||||||
|
catchError(err => {
|
||||||
|
this.spinner.hide();
|
||||||
|
throw err;
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.pipe(map(result => result.data.domains));
|
||||||
|
}
|
||||||
|
|
||||||
|
loadById(id: number): Observable<Domain> {
|
||||||
|
return this.apollo
|
||||||
|
.query<{ domains: QueryResult<Domain> }>({
|
||||||
|
query: gql`
|
||||||
|
query getDomain($id: Int) {
|
||||||
|
domain(filter: { id: { equal: $id } }) {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
|
||||||
|
...DB_MODEL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
${DB_MODEL_FRAGMENT}
|
||||||
|
`,
|
||||||
|
variables: {
|
||||||
|
id: id,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.pipe(
|
||||||
|
catchError(err => {
|
||||||
|
this.spinner.hide();
|
||||||
|
throw err;
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.pipe(map(result => result.data.domains.nodes[0]));
|
||||||
|
}
|
||||||
|
|
||||||
|
create(object: DomainCreateInput): Observable<Domain | undefined> {
|
||||||
|
return this.apollo
|
||||||
|
.mutate<{ domain: { create: Domain } }>({
|
||||||
|
mutation: gql`
|
||||||
|
mutation createDomain($input: DomainCreateInput!) {
|
||||||
|
domain {
|
||||||
|
create(input: $input) {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
|
||||||
|
...DB_MODEL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
${DB_MODEL_FRAGMENT}
|
||||||
|
`,
|
||||||
|
variables: {
|
||||||
|
input: {
|
||||||
|
name: object.name,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.pipe(
|
||||||
|
catchError(err => {
|
||||||
|
this.spinner.hide();
|
||||||
|
throw err;
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.pipe(map(result => result.data?.domain.create));
|
||||||
|
}
|
||||||
|
|
||||||
|
update(object: DomainUpdateInput): Observable<Domain | undefined> {
|
||||||
|
return this.apollo
|
||||||
|
.mutate<{ domain: { update: Domain } }>({
|
||||||
|
mutation: gql`
|
||||||
|
mutation updateDomain($input: DomainUpdateInput!) {
|
||||||
|
domain {
|
||||||
|
update(input: $input) {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
|
||||||
|
...DB_MODEL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
${DB_MODEL_FRAGMENT}
|
||||||
|
`,
|
||||||
|
variables: {
|
||||||
|
input: {
|
||||||
|
id: object.id,
|
||||||
|
name: object.name,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.pipe(
|
||||||
|
catchError(err => {
|
||||||
|
this.spinner.hide();
|
||||||
|
throw err;
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.pipe(map(result => result.data?.domain.update));
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(object: Domain): Observable<boolean> {
|
||||||
|
return this.apollo
|
||||||
|
.mutate<{ domain: { delete: boolean } }>({
|
||||||
|
mutation: gql`
|
||||||
|
mutation deleteDomain($id: ID!) {
|
||||||
|
domain {
|
||||||
|
delete(id: $id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
variables: {
|
||||||
|
id: object.id,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.pipe(
|
||||||
|
catchError(err => {
|
||||||
|
this.spinner.hide();
|
||||||
|
throw err;
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.pipe(map(result => result.data?.domain.delete ?? false));
|
||||||
|
}
|
||||||
|
|
||||||
|
restore(object: Domain): Observable<boolean> {
|
||||||
|
return this.apollo
|
||||||
|
.mutate<{ domain: { restore: boolean } }>({
|
||||||
|
mutation: gql`
|
||||||
|
mutation restoreDomain($id: ID!) {
|
||||||
|
domain {
|
||||||
|
restore(id: $id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
variables: {
|
||||||
|
id: object.id,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.pipe(
|
||||||
|
catchError(err => {
|
||||||
|
this.spinner.hide();
|
||||||
|
throw err;
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.pipe(map(result => result.data?.domain.restore ?? false));
|
||||||
|
}
|
||||||
|
|
||||||
|
static provide(): Provider[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
provide: PageDataService,
|
||||||
|
useClass: DomainsDataService,
|
||||||
|
},
|
||||||
|
DomainsDataService,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
43
web/src/app/modules/admin/domains/domains.module.ts
Normal file
43
web/src/app/modules/admin/domains/domains.module.ts
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { SharedModule } from 'src/app/modules/shared/shared.module';
|
||||||
|
import { RouterModule, Routes } from '@angular/router';
|
||||||
|
import { PermissionGuard } from 'src/app/core/guard/permission.guard';
|
||||||
|
import { PermissionsEnum } from 'src/app/model/auth/permissionsEnum';
|
||||||
|
import { DomainsPage } from 'src/app/modules/admin/domains/domains.page';
|
||||||
|
import { DomainFormPageComponent } from 'src/app/modules/admin/domains/form-page/domain-form-page.component';
|
||||||
|
import { DomainsDataService } from 'src/app/modules/admin/domains/domains.data.service';
|
||||||
|
import { DomainsColumns } from 'src/app/modules/admin/domains/domains.columns';
|
||||||
|
|
||||||
|
const routes: Routes = [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
title: 'Domains',
|
||||||
|
component: DomainsPage,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: 'create',
|
||||||
|
component: DomainFormPageComponent,
|
||||||
|
canActivate: [PermissionGuard],
|
||||||
|
data: {
|
||||||
|
permissions: [PermissionsEnum.apiKeysCreate],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'edit/:id',
|
||||||
|
component: DomainFormPageComponent,
|
||||||
|
canActivate: [PermissionGuard],
|
||||||
|
data: {
|
||||||
|
permissions: [PermissionsEnum.apiKeysUpdate],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [DomainsPage, DomainFormPageComponent],
|
||||||
|
imports: [CommonModule, SharedModule, RouterModule.forChild(routes)],
|
||||||
|
providers: [DomainsDataService.provide(), DomainsColumns.provide()],
|
||||||
|
})
|
||||||
|
export class DomainsModule {}
|
19
web/src/app/modules/admin/domains/domains.page.html
Normal file
19
web/src/app/modules/admin/domains/domains.page.html
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<app-table
|
||||||
|
[rows]="result.nodes"
|
||||||
|
[columns]="columns"
|
||||||
|
[rowsPerPageOptions]="rowsPerPageOptions"
|
||||||
|
[totalCount]="result.totalCount"
|
||||||
|
[requireAnyPermissions]="requiredPermissions"
|
||||||
|
countHeaderTranslation="domain.count_header"
|
||||||
|
[loading]="loading"
|
||||||
|
[(filter)]="filter"
|
||||||
|
[(sort)]="sort"
|
||||||
|
[(skip)]="skip"
|
||||||
|
[(take)]="take"
|
||||||
|
(load)="load()"
|
||||||
|
[create]="true"
|
||||||
|
[update]="true"
|
||||||
|
(delete)="delete($event)"
|
||||||
|
(restore)="restore($event)"></app-table>
|
||||||
|
|
||||||
|
<router-outlet></router-outlet>
|
0
web/src/app/modules/admin/domains/domains.page.scss
Normal file
0
web/src/app/modules/admin/domains/domains.page.scss
Normal file
51
web/src/app/modules/admin/domains/domains.page.spec.ts
Normal file
51
web/src/app/modules/admin/domains/domains.page.spec.ts
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import { ComponentFixture, TestBed } from "@angular/core/testing";
|
||||||
|
import { ApiKeysPage } from "src/app/modules/admin/administration/api-keys/api-keys.page";
|
||||||
|
import { SharedModule } from "src/app/modules/shared/shared.module";
|
||||||
|
import { TranslateModule } from "@ngx-translate/core";
|
||||||
|
import { AuthService } from "src/app/service/auth.service";
|
||||||
|
import { KeycloakService } from "keycloak-angular";
|
||||||
|
import { ErrorHandlingService } from "src/app/service/error-handling.service";
|
||||||
|
import { ToastService } from "src/app/service/toast.service";
|
||||||
|
import { ConfirmationService, MessageService } from "primeng/api";
|
||||||
|
import { ActivatedRoute } from "@angular/router";
|
||||||
|
import { of } from "rxjs";
|
||||||
|
import { PageDataService } from "src/app/core/base/page.data.service";
|
||||||
|
import { ApiKeysDataService } from "src/app/modules/admin/administration/api-keys/api-keys.data.service";
|
||||||
|
|
||||||
|
describe("ApiKeysComponent", () => {
|
||||||
|
let component: ApiKeysPage;
|
||||||
|
let fixture: ComponentFixture<ApiKeysPage>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [ApiKeysPage],
|
||||||
|
imports: [SharedModule, TranslateModule.forRoot()],
|
||||||
|
providers: [
|
||||||
|
AuthService,
|
||||||
|
KeycloakService,
|
||||||
|
ErrorHandlingService,
|
||||||
|
ToastService,
|
||||||
|
MessageService,
|
||||||
|
ConfirmationService,
|
||||||
|
{
|
||||||
|
provide: ActivatedRoute,
|
||||||
|
useValue: {
|
||||||
|
snapshot: { params: of({}) },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: PageDataService,
|
||||||
|
useClass: ApiKeysDataService,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}).compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(ApiKeysPage);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should create", () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
72
web/src/app/modules/admin/domains/domains.page.ts
Normal file
72
web/src/app/modules/admin/domains/domains.page.ts
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { PageBase } from 'src/app/core/base/page-base';
|
||||||
|
import { ToastService } from 'src/app/service/toast.service';
|
||||||
|
import { ConfirmationDialogService } from 'src/app/service/confirmation-dialog.service';
|
||||||
|
import { PermissionsEnum } from 'src/app/model/auth/permissionsEnum';
|
||||||
|
import { Group } from 'src/app/model/entities/group';
|
||||||
|
import { DomainsDataService } from 'src/app/modules/admin/domains/domains.data.service';
|
||||||
|
import { DomainsColumns } from 'src/app/modules/admin/domains/domains.columns';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-domains',
|
||||||
|
templateUrl: './domains.page.html',
|
||||||
|
styleUrl: './domains.page.scss',
|
||||||
|
})
|
||||||
|
export class DomainsPage extends PageBase<
|
||||||
|
Group,
|
||||||
|
DomainsDataService,
|
||||||
|
DomainsColumns
|
||||||
|
> {
|
||||||
|
constructor(
|
||||||
|
private toast: ToastService,
|
||||||
|
private confirmation: ConfirmationDialogService
|
||||||
|
) {
|
||||||
|
super(true, {
|
||||||
|
read: [PermissionsEnum.domains],
|
||||||
|
create: [PermissionsEnum.domainsCreate],
|
||||||
|
update: [PermissionsEnum.domainsUpdate],
|
||||||
|
delete: [PermissionsEnum.domainsDelete],
|
||||||
|
restore: [PermissionsEnum.domainsDelete],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
load(): void {
|
||||||
|
this.loading = true;
|
||||||
|
this.dataService
|
||||||
|
.load(this.filter, this.sort, this.skip, this.take)
|
||||||
|
.subscribe(result => {
|
||||||
|
this.result = result;
|
||||||
|
this.loading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(group: Group): void {
|
||||||
|
this.confirmation.confirmDialog({
|
||||||
|
header: 'dialog.delete.header',
|
||||||
|
message: 'dialog.delete.message',
|
||||||
|
accept: () => {
|
||||||
|
this.loading = true;
|
||||||
|
this.dataService.delete(group).subscribe(() => {
|
||||||
|
this.toast.success('action.deleted');
|
||||||
|
this.load();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
messageParams: { entity: group.name },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
restore(group: Group): void {
|
||||||
|
this.confirmation.confirmDialog({
|
||||||
|
header: 'dialog.restore.header',
|
||||||
|
message: 'dialog.restore.message',
|
||||||
|
accept: () => {
|
||||||
|
this.loading = true;
|
||||||
|
this.dataService.restore(group).subscribe(() => {
|
||||||
|
this.toast.success('action.restored');
|
||||||
|
this.load();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
messageParams: { entity: group.name },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
<app-form-page
|
||||||
|
*ngIf="node"
|
||||||
|
[formGroup]="form"
|
||||||
|
[isUpdate]="isUpdate"
|
||||||
|
(onSave)="save()"
|
||||||
|
(onClose)="close()">
|
||||||
|
<ng-template formPageHeader let-isUpdate>
|
||||||
|
<h2>
|
||||||
|
{{ 'common.group' | translate }}
|
||||||
|
{{
|
||||||
|
(isUpdate ? 'sidebar.header.update' : 'sidebar.header.create')
|
||||||
|
| translate
|
||||||
|
}}
|
||||||
|
</h2>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<ng-template formPageContent>
|
||||||
|
<div class="form-page-input">
|
||||||
|
<p class="label">{{ 'common.id' | translate }}</p>
|
||||||
|
<input pInputText class="value" type="number" formControlName="id"/>
|
||||||
|
</div>
|
||||||
|
<div class="form-page-input">
|
||||||
|
<p class="label">{{ 'common.name' | translate }}</p>
|
||||||
|
<input
|
||||||
|
pInputText
|
||||||
|
class="value"
|
||||||
|
type="text"
|
||||||
|
formControlName="name"/>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
</app-form-page>
|
@ -0,0 +1,50 @@
|
|||||||
|
import { ComponentFixture, TestBed } from "@angular/core/testing";
|
||||||
|
import { RoleFormPageComponent } from "src/app/modules/admin/administration/roles/form-page/role-form-page.component";
|
||||||
|
import { SharedModule } from "src/app/modules/shared/shared.module";
|
||||||
|
import { TranslateModule } from "@ngx-translate/core";
|
||||||
|
import { AuthService } from "src/app/service/auth.service";
|
||||||
|
import { ErrorHandlingService } from "src/app/service/error-handling.service";
|
||||||
|
import { ToastService } from "src/app/service/toast.service";
|
||||||
|
import { ConfirmationService, MessageService } from "primeng/api";
|
||||||
|
import { ActivatedRoute } from "@angular/router";
|
||||||
|
import { of } from "rxjs";
|
||||||
|
import { ApiKeysDataService } from "src/app/modules/admin/administration/api-keys/api-keys.data.service";
|
||||||
|
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
|
||||||
|
|
||||||
|
describe("ApiKeyFormpageComponent", () => {
|
||||||
|
let component: RoleFormPageComponent;
|
||||||
|
let fixture: ComponentFixture<RoleFormPageComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [RoleFormPageComponent],
|
||||||
|
imports: [
|
||||||
|
BrowserAnimationsModule,
|
||||||
|
SharedModule,
|
||||||
|
TranslateModule.forRoot(),
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
AuthService,
|
||||||
|
ErrorHandlingService,
|
||||||
|
ToastService,
|
||||||
|
MessageService,
|
||||||
|
ConfirmationService,
|
||||||
|
{
|
||||||
|
provide: ActivatedRoute,
|
||||||
|
useValue: {
|
||||||
|
snapshot: { params: of({}) },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ApiKeysDataService,
|
||||||
|
],
|
||||||
|
}).compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(RoleFormPageComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should create", () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,93 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { FormControl, FormGroup, Validators } from '@angular/forms';
|
||||||
|
import { ToastService } from 'src/app/service/toast.service';
|
||||||
|
import { FormPageBase } from 'src/app/core/base/form-page-base';
|
||||||
|
import {
|
||||||
|
Domain,
|
||||||
|
DomainCreateInput,
|
||||||
|
DomainUpdateInput,
|
||||||
|
} from 'src/app/model/entities/domain';
|
||||||
|
import { DomainsDataService } from 'src/app/modules/admin/domains/domains.data.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-domain-form-page',
|
||||||
|
templateUrl: './domain-form-page.component.html',
|
||||||
|
styleUrl: './domain-form-page.component.scss',
|
||||||
|
})
|
||||||
|
export class DomainFormPageComponent extends FormPageBase<
|
||||||
|
Domain,
|
||||||
|
DomainCreateInput,
|
||||||
|
DomainUpdateInput,
|
||||||
|
DomainsDataService
|
||||||
|
> {
|
||||||
|
constructor(private toast: ToastService) {
|
||||||
|
super();
|
||||||
|
if (!this.nodeId) {
|
||||||
|
this.node = this.new();
|
||||||
|
this.setForm(this.node);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.dataService
|
||||||
|
.load([{ id: { equal: this.nodeId } }])
|
||||||
|
.subscribe(apiKey => {
|
||||||
|
this.node = apiKey.nodes[0];
|
||||||
|
this.setForm(this.node);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
new(): Domain {
|
||||||
|
return {} as Domain;
|
||||||
|
}
|
||||||
|
|
||||||
|
buildForm() {
|
||||||
|
this.form = new FormGroup({
|
||||||
|
id: new FormControl<number | undefined>(undefined),
|
||||||
|
name: new FormControl<string | undefined>(undefined, Validators.required),
|
||||||
|
});
|
||||||
|
this.form.controls['id'].disable();
|
||||||
|
}
|
||||||
|
|
||||||
|
setForm(node?: Domain) {
|
||||||
|
this.form.controls['id'].setValue(node?.id);
|
||||||
|
this.form.controls['name'].setValue(node?.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
getCreateInput(): DomainCreateInput {
|
||||||
|
return {
|
||||||
|
name: this.form.controls['name'].pristine
|
||||||
|
? undefined
|
||||||
|
: (this.form.controls['name'].value ?? undefined),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
getUpdateInput(): DomainUpdateInput {
|
||||||
|
if (!this.node?.id) {
|
||||||
|
throw new Error('Node id is missing');
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: this.form.controls['id'].value,
|
||||||
|
name: this.form.controls['name'].pristine
|
||||||
|
? undefined
|
||||||
|
: (this.form.controls['name'].value ?? undefined),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
create(apiKey: DomainCreateInput): void {
|
||||||
|
this.dataService.create(apiKey).subscribe(() => {
|
||||||
|
this.spinner.hide();
|
||||||
|
this.toast.success('action.created');
|
||||||
|
this.close();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
update(apiKey: DomainUpdateInput): void {
|
||||||
|
this.dataService.update(apiKey).subscribe(() => {
|
||||||
|
this.spinner.hide();
|
||||||
|
this.toast.success('action.created');
|
||||||
|
this.close();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -6,7 +6,7 @@
|
|||||||
(onClose)="close()">
|
(onClose)="close()">
|
||||||
<ng-template formPageHeader let-isUpdate>
|
<ng-template formPageHeader let-isUpdate>
|
||||||
<h2>
|
<h2>
|
||||||
{{ 'common.group' | translate }}
|
{{ 'common.short_url' | translate }}
|
||||||
{{
|
{{
|
||||||
(isUpdate ? 'sidebar.header.update' : 'sidebar.header.create')
|
(isUpdate ? 'sidebar.header.update' : 'sidebar.header.create')
|
||||||
| translate
|
| translate
|
||||||
@ -65,5 +65,20 @@
|
|||||||
></p-dropdown>
|
></p-dropdown>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-page-input">
|
||||||
|
<p class="label">{{ 'common.domain' | translate }}</p>
|
||||||
|
<div
|
||||||
|
class="value">
|
||||||
|
<p-dropdown
|
||||||
|
[options]="domains"
|
||||||
|
formControlName="domainId"
|
||||||
|
[showClear]="true"
|
||||||
|
[filter]="true"
|
||||||
|
filterBy="name"
|
||||||
|
optionLabel="name"
|
||||||
|
optionValue="id"
|
||||||
|
></p-dropdown>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</app-form-page>
|
</app-form-page>
|
||||||
|
@ -9,6 +9,7 @@ import {
|
|||||||
} from 'src/app/model/entities/short-url';
|
} from 'src/app/model/entities/short-url';
|
||||||
import { ShortUrlsDataService } from 'src/app/modules/admin/short-urls/short-urls.data.service';
|
import { ShortUrlsDataService } from 'src/app/modules/admin/short-urls/short-urls.data.service';
|
||||||
import { Group } from 'src/app/model/entities/group';
|
import { Group } from 'src/app/model/entities/group';
|
||||||
|
import { Domain } from 'src/app/model/entities/domain';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-short-url-form-page',
|
selector: 'app-short-url-form-page',
|
||||||
@ -22,12 +23,16 @@ export class ShortUrlFormPageComponent extends FormPageBase<
|
|||||||
ShortUrlsDataService
|
ShortUrlsDataService
|
||||||
> {
|
> {
|
||||||
groups: Group[] = [];
|
groups: Group[] = [];
|
||||||
|
domains: Domain[] = [];
|
||||||
|
|
||||||
constructor(private toast: ToastService) {
|
constructor(private toast: ToastService) {
|
||||||
super();
|
super();
|
||||||
this.dataService.getAllGroups().subscribe(groups => {
|
this.dataService.getAllGroups().subscribe(groups => {
|
||||||
this.groups = groups;
|
this.groups = groups;
|
||||||
});
|
});
|
||||||
|
this.dataService.getAllDomains().subscribe(domains => {
|
||||||
|
this.domains = domains;
|
||||||
|
});
|
||||||
|
|
||||||
if (!this.nodeId) {
|
if (!this.nodeId) {
|
||||||
this.node = this.new();
|
this.node = this.new();
|
||||||
@ -62,6 +67,7 @@ export class ShortUrlFormPageComponent extends FormPageBase<
|
|||||||
description: new FormControl<string | undefined>(undefined),
|
description: new FormControl<string | undefined>(undefined),
|
||||||
loadingScreen: new FormControl<boolean | undefined>(undefined),
|
loadingScreen: new FormControl<boolean | undefined>(undefined),
|
||||||
groupId: new FormControl<number | undefined>(undefined),
|
groupId: new FormControl<number | undefined>(undefined),
|
||||||
|
domainId: new FormControl<number | undefined>(undefined),
|
||||||
});
|
});
|
||||||
this.form.controls['id'].disable();
|
this.form.controls['id'].disable();
|
||||||
}
|
}
|
||||||
@ -86,6 +92,7 @@ export class ShortUrlFormPageComponent extends FormPageBase<
|
|||||||
this.form.controls['description'].setValue(node?.description);
|
this.form.controls['description'].setValue(node?.description);
|
||||||
this.form.controls['loadingScreen'].setValue(node?.loadingScreen);
|
this.form.controls['loadingScreen'].setValue(node?.loadingScreen);
|
||||||
this.form.controls['groupId'].setValue(node?.group?.id);
|
this.form.controls['groupId'].setValue(node?.group?.id);
|
||||||
|
this.form.controls['domainId'].setValue(node?.domain?.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
getCreateInput(): ShortUrlCreateInput {
|
getCreateInput(): ShortUrlCreateInput {
|
||||||
@ -103,6 +110,9 @@ export class ShortUrlFormPageComponent extends FormPageBase<
|
|||||||
groupId: this.form.controls['groupId'].pristine
|
groupId: this.form.controls['groupId'].pristine
|
||||||
? undefined
|
? undefined
|
||||||
: (this.form.controls['groupId'].value ?? undefined),
|
: (this.form.controls['groupId'].value ?? undefined),
|
||||||
|
domainId: this.form.controls['domainId'].pristine
|
||||||
|
? undefined
|
||||||
|
: (this.form.controls['domainId'].value ?? undefined),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,6 +138,9 @@ export class ShortUrlFormPageComponent extends FormPageBase<
|
|||||||
groupId: this.form.controls['groupId'].pristine
|
groupId: this.form.controls['groupId'].pristine
|
||||||
? undefined
|
? undefined
|
||||||
: (this.form.controls['groupId'].value ?? undefined),
|
: (this.form.controls['groupId'].value ?? undefined),
|
||||||
|
domainId: this.form.controls['domainId'].pristine
|
||||||
|
? undefined
|
||||||
|
: (this.form.controls['domainId'].value ?? undefined),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,6 +20,7 @@ import {
|
|||||||
ShortUrlUpdateInput,
|
ShortUrlUpdateInput,
|
||||||
} from 'src/app/model/entities/short-url';
|
} from 'src/app/model/entities/short-url';
|
||||||
import { Group } from 'src/app/model/entities/group';
|
import { Group } from 'src/app/model/entities/group';
|
||||||
|
import { Domain } from 'src/app/model/entities/domain';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ShortUrlsDataService
|
export class ShortUrlsDataService
|
||||||
@ -66,6 +67,10 @@ export class ShortUrlsDataService
|
|||||||
id
|
id
|
||||||
name
|
name
|
||||||
}
|
}
|
||||||
|
domain {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
}
|
||||||
|
|
||||||
...DB_MODEL
|
...DB_MODEL
|
||||||
}
|
}
|
||||||
@ -106,6 +111,10 @@ export class ShortUrlsDataService
|
|||||||
id
|
id
|
||||||
name
|
name
|
||||||
}
|
}
|
||||||
|
domain {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
}
|
||||||
|
|
||||||
...DB_MODEL
|
...DB_MODEL
|
||||||
}
|
}
|
||||||
@ -150,6 +159,7 @@ export class ShortUrlsDataService
|
|||||||
description: object.description,
|
description: object.description,
|
||||||
loadingScreen: object.loadingScreen,
|
loadingScreen: object.loadingScreen,
|
||||||
groupId: object.groupId,
|
groupId: object.groupId,
|
||||||
|
domainId: object.domainId,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@ -187,6 +197,7 @@ export class ShortUrlsDataService
|
|||||||
description: object.description,
|
description: object.description,
|
||||||
loadingScreen: object.loadingScreen,
|
loadingScreen: object.loadingScreen,
|
||||||
groupId: object.groupId,
|
groupId: object.groupId,
|
||||||
|
domainId: object.domainId,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@ -268,6 +279,29 @@ export class ShortUrlsDataService
|
|||||||
.pipe(map(result => result.data.groups.nodes));
|
.pipe(map(result => result.data.groups.nodes));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getAllDomains() {
|
||||||
|
return this.apollo
|
||||||
|
.query<{ domains: QueryResult<Domain> }>({
|
||||||
|
query: gql`
|
||||||
|
query getGroups {
|
||||||
|
domains {
|
||||||
|
nodes {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
})
|
||||||
|
.pipe(
|
||||||
|
catchError(err => {
|
||||||
|
this.spinner.hide();
|
||||||
|
throw err;
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.pipe(map(result => result.data.domains.nodes));
|
||||||
|
}
|
||||||
|
|
||||||
static provide(): Provider[] {
|
static provide(): Provider[] {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
|
@ -22,6 +22,10 @@
|
|||||||
<div class="pi pi-{{ url.loadingScreen ? 'check-circle' : 'times-circle' }}"></div>
|
<div class="pi pi-{{ url.loadingScreen ? 'check-circle' : 'times-circle' }}"></div>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="grid-container" *ngIf="url.domain">
|
||||||
|
<span class="grid-label font-bold">{{ 'common.domain' | translate }}:</span>
|
||||||
|
<span class="grid-value">{{ url.domain?.name }}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
|
@ -30,6 +30,14 @@ export class SidebarService {
|
|||||||
// trust me, you'll need this async
|
// trust me, you'll need this async
|
||||||
async setElements() {
|
async setElements() {
|
||||||
const elements: MenuElement[] = [
|
const elements: MenuElement[] = [
|
||||||
|
{
|
||||||
|
label: 'common.domains',
|
||||||
|
icon: 'pi pi-sitemap',
|
||||||
|
routerLink: ['/admin/domains'],
|
||||||
|
visible: await this.auth.hasAnyPermissionLazy([
|
||||||
|
PermissionsEnum.domains,
|
||||||
|
]),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: 'common.groups',
|
label: 'common.groups',
|
||||||
icon: 'pi pi-tags',
|
icon: 'pi pi-tags',
|
||||||
|
@ -25,6 +25,8 @@
|
|||||||
"created": "Erstellt",
|
"created": "Erstellt",
|
||||||
"deleted": "Gelöscht",
|
"deleted": "Gelöscht",
|
||||||
"description": "Beschreibung",
|
"description": "Beschreibung",
|
||||||
|
"domain": "Domain",
|
||||||
|
"domains": "Domains",
|
||||||
"download": "Herunterladen",
|
"download": "Herunterladen",
|
||||||
"edited_at": "Bearbeitet am",
|
"edited_at": "Bearbeitet am",
|
||||||
"editor": "Bearbeiter",
|
"editor": "Bearbeiter",
|
||||||
@ -58,6 +60,9 @@
|
|||||||
},
|
},
|
||||||
"save": "Speichern"
|
"save": "Speichern"
|
||||||
},
|
},
|
||||||
|
"domain": {
|
||||||
|
"count_header": "Domain(s)"
|
||||||
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"404": "404 - Nicht gefunden",
|
"404": "404 - Nicht gefunden",
|
||||||
"create_failed": "Erstellung fehlgeschlagen",
|
"create_failed": "Erstellung fehlgeschlagen",
|
||||||
|
@ -25,6 +25,8 @@
|
|||||||
"created": "Created",
|
"created": "Created",
|
||||||
"deleted": "Deleted",
|
"deleted": "Deleted",
|
||||||
"description": "Description",
|
"description": "Description",
|
||||||
|
"domain": "Domain",
|
||||||
|
"domains": "Domains",
|
||||||
"download": "Download",
|
"download": "Download",
|
||||||
"edited_at": "Edited at",
|
"edited_at": "Edited at",
|
||||||
"editor": "Editor",
|
"editor": "Editor",
|
||||||
@ -58,6 +60,9 @@
|
|||||||
},
|
},
|
||||||
"save": "Save"
|
"save": "Save"
|
||||||
},
|
},
|
||||||
|
"domain": {
|
||||||
|
"count_header": "Domain(s)"
|
||||||
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"404": "404 - Not found",
|
"404": "404 - Not found",
|
||||||
"create_failed": "Create failed",
|
"create_failed": "Create failed",
|
||||||
|
@ -18,7 +18,6 @@
|
|||||||
|
|
||||||
background-color: $backgroundColor2;
|
background-color: $backgroundColor2;
|
||||||
|
|
||||||
|
|
||||||
@layer utilities {
|
@layer utilities {
|
||||||
.highlight {
|
.highlight {
|
||||||
color: $textColorHighlight;
|
color: $textColorHighlight;
|
||||||
@ -59,6 +58,10 @@
|
|||||||
.deleted {
|
.deleted {
|
||||||
color: $accentColor !important;
|
color: $accentColor !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.divider {
|
||||||
|
border-bottom: 1px solid $accentColor;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
h1,
|
h1,
|
||||||
@ -67,6 +70,10 @@
|
|||||||
color: $headerColor;
|
color: $headerColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input, .p-checkbox-box, .p-dropdown {
|
||||||
|
border: 1px solid $accentColor;
|
||||||
|
}
|
||||||
|
|
||||||
.app {
|
.app {
|
||||||
.component {
|
.component {
|
||||||
background-color: $backgroundColor;
|
background-color: $backgroundColor;
|
||||||
|
@ -18,7 +18,6 @@
|
|||||||
|
|
||||||
background-color: $backgroundColor2;
|
background-color: $backgroundColor2;
|
||||||
|
|
||||||
|
|
||||||
@layer utilities {
|
@layer utilities {
|
||||||
.highlight {
|
.highlight {
|
||||||
color: $textColorHighlight;
|
color: $textColorHighlight;
|
||||||
@ -59,6 +58,10 @@
|
|||||||
.deleted {
|
.deleted {
|
||||||
color: $accentColor !important;
|
color: $accentColor !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.divider {
|
||||||
|
border-bottom: 1px solid $accentColor;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
h1,
|
h1,
|
||||||
@ -67,6 +70,10 @@
|
|||||||
color: $headerColor;
|
color: $headerColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input, .p-checkbox-box, .p-dropdown {
|
||||||
|
border: 1px solid $accentColor;
|
||||||
|
}
|
||||||
|
|
||||||
.app {
|
.app {
|
||||||
.component {
|
.component {
|
||||||
background-color: $backgroundColor;
|
background-color: $backgroundColor;
|
||||||
|
Loading…
Reference in New Issue
Block a user