@cryptotaxi247 / CoPilot / commits / bfd68510

Graylog pages (#101)

* add logging to db table * updated template files * logging modular * return validation exception message * logging for exception failures * skip logging of OPTIONS * add user id to all logging * return connection verified and logging * updated logout flow * log routes * retrieve logs by event type * log wazuh-indexer monitoring errors and add auth to routes * wazuh-manager rules logging * auth to agents routes * pydantic failure response messages * delete agent complete * Auto stash before merge of "graylog-pages" and "origin/graylog-pages" * moduler delete agent * graylog connector HTTPexceptions and logging * added store settings * graylog events HTTPException * updated auth store * refactored refresh token flow * updated graylog api * implemented date format * updated profile settings * added management page * graylog HTTP exceptions and logging * exception handling for soc-alerts and alerts * cases security and exceptions * analyzers * timeout to velo API and auth to velo routes * added alerts component * graylog post fix * added event definitions component * updated messages view * updated alerts view * updated alerts view * updated alerts view * updated events view * added streams view * updated streams view * graylog post fix for 204 status code * updated streams view * updated input api / types * convert agents sync to a background task * added inputs view * updated inputs view * updated graylog/metrics api * added graylog/pipelines apis/types * graylog route to get pipeline rule by pipeline id * added graylog metric page * graylog pipeline full endpoint * helper functions * updated graylog metric page * added graylog pipelines page * precommit fixes --------- Co-authored-by: Davide Di Modica <webmaster.ddm@gmail.com>

taylor_socfortress committed Nov 1, 2023 at 13:19 UTC bfd68510c61644678919a175d32e4e1f78f6a4da
363 files changed +16848 -7935
.env.example
+1
@@ -1 +1,2 @@
1 VITE_API_URL=http://127.0.0.1:5000
2 +VITE_TOKEN_DEBOUNCE_TIME=10
.env.production
+1
@@ -1 +1,2 @@
1 VITE_API_URL=http://127.0.0.1:5000
2 +VITE_TOKEN_DEBOUNCE_TIME=10
.gitignore
+2
@@ -42,3 +42,5 @@ wheels/
42 *.sqbpro
43 site/
44 backend/file-store/api.config.yaml
45 +unplugin.components.d.ts
46 +package-lock.json
backend/app/agents/routes/agents.py
+116 -37
@@ -1,5 +1,7 @@
1 from fastapi import APIRouter
2 +from fastapi import BackgroundTasks
3 from fastapi import HTTPException
4 +from fastapi import Security
5 from loguru import logger
6 from starlette.status import HTTP_401_UNAUTHORIZED
7
@@ -16,8 +18,12 @@ from app.agents.services.status import get_outdated_agents_wazuh
18 from app.agents.services.sync import sync_agents
19 from app.agents.velociraptor.services.agents import delete_agent_velociraptor
20 from app.agents.wazuh.schema.agents import WazuhAgentVulnerabilitiesResponse
21 +from app.agents.wazuh.services.agents import delete_agent_wazuh
22 from app.agents.wazuh.services.vulnerabilities import collect_agent_vulnerabilities
23
24 +# App specific imports
25 +from app.auth.routes.auth import AuthHandler
26 +
27 # App specific imports
28 from app.db.db_session import session
29 from app.db.universal_models import Agents
@@ -25,61 +31,125 @@ from app.db.universal_models import Agents
31 agents_router = APIRouter()
32
33
28 -def verify_admin(user):
29 - if not user.is_admin:
30 - raise HTTPException(status_code=HTTP_401_UNAUTHORIZED, detail="Unauthorized")
34 +def fetch_velociraptor_id(agent_id: str) -> str:
35 + try:
36 + return session.query(Agents).filter(Agents.agent_id == agent_id).first().velociraptor_id
37 + except Exception as e:
38 + logger.error(f"Failed to fetch agent {agent_id} from database: {e}")
39 + raise HTTPException(status_code=500, detail=f"Failed to fetch agent {agent_id} from database: {e}")
40 +
41
42 +def delete_agent_from_database(agent_id: str):
43 + try:
44 + session.query(Agents).filter(Agents.agent_id == agent_id).delete()
45 + session.commit()
46 + except Exception as e:
47 + logger.error(f"Failed to delete agent {agent_id} from database: {e}")
48 + raise HTTPException(status_code=500, detail=f"Failed to delete agent {agent_id} from database: {e}")
49
33 -@agents_router.get("", response_model=AgentsResponse, description="Get all disabled rules")
50 +
51 +@agents_router.get(
52 + "",
53 + response_model=AgentsResponse,
54 + description="Get all disabled rules",
55 + dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))],
56 +)
57 async def get_agents() -> AgentsResponse:
58 logger.info("Fetching all agents")
36 - agents = session.query(Agents).all()
37 - return AgentsResponse(agents=agents, success=True, message="Agents fetched successfully")
59 + try:
60 + agents = session.query(Agents).all()
61 + return AgentsResponse(agents=agents, success=True, message="Agents fetched successfully")
62 + except Exception as e:
63 + logger.error(f"Failed to fetch agents: {e}")
64 + raise HTTPException(status_code=500, detail=f"Failed to fetch agents: {e}")
65
66
40 -@agents_router.get("/{agent_id}", response_model=AgentsResponse, description="Get agent by agent_id")
67 +@agents_router.get(
68 + "/{agent_id}",
69 + response_model=AgentsResponse,
70 + description="Get agent by agent_id",
71 + dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))],
72 +)
73 async def get_agent(agent_id: str) -> AgentsResponse:
74 logger.info(f"Fetching agent with agent_id: {agent_id}")
43 - agent = session.query(Agents).filter(Agents.agent_id == agent_id).first()
44 - if not agent:
45 - raise HTTPException(status_code=404, detail=f"Agent with agent_id {agent_id} not found")
46 - return AgentsResponse(agents=[agent], success=True, message="Agent fetched successfully")
75 + try:
76 + agent = session.query(Agents).filter(Agents.agent_id == agent_id).first()
77 + return AgentsResponse(agents=[agent], success=True, message="Agent fetched successfully")
78 + except Exception as e:
79 + if not agent:
80 + raise HTTPException(status_code=404, detail=f"Agent with agent_id {agent_id} not found")
81 + raise HTTPException(status_code=500, detail=f"Failed to fetch agent: {e}")
82
83
49 -@agents_router.get("/hostname/{hostname}", response_model=AgentsResponse, description="Get agent by hostname")
84 +@agents_router.get(
85 + "/hostname/{hostname}",
86 + response_model=AgentsResponse,
87 + description="Get agent by hostname",
88 + dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))],
89 +)
90 async def get_agent_by_hostname(hostname: str) -> AgentsResponse:
91 logger.info(f"Fetching agent with hostname: {hostname}")
52 - agent = session.query(Agents).filter(Agents.hostname == hostname).first()
53 - if not agent:
54 - raise HTTPException(status_code=404, detail=f"Agent with hostname {hostname} not found")
55 - return AgentsResponse(agents=[agent], success=True, message="Agent fetched successfully")
56 -
57 -
58 -@agents_router.post("/sync", response_model=SyncedAgentsResponse, description="Sync agents from Wazuh Manager")
59 -async def sync_all_agents() -> SyncedAgentsResponse:
92 + try:
93 + agent = session.query(Agents).filter(Agents.hostname == hostname).first()
94 + return AgentsResponse(agents=[agent], success=True, message="Agent fetched successfully")
95 + except Exception as e:
96 + if not agent:
97 + raise HTTPException(status_code=404, detail=f"Agent with hostname {hostname} not found")
98 + raise HTTPException(status_code=500, detail=f"Failed to fetch agent: {e}")
99 +
100 +
101 +@agents_router.post(
102 + "/sync",
103 + response_model=SyncedAgentsResponse,
104 + description="Sync agents from Wazuh Manager",
105 + dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))],
106 +)
107 +async def sync_all_agents(backgroud_tasks: BackgroundTasks) -> SyncedAgentsResponse:
108 logger.info("Syncing agents from Wazuh Manager")
61 - return sync_agents()
109 + backgroud_tasks.add_task(sync_agents)
110 + # return sync_agents()
111 + return SyncedAgentsResponse(success=True, message="Agents synced started successfully")
112
113
64 -@agents_router.post("/{agent_id}/critical", response_model=AgentModifyResponse, description="Mark agent as critical")
114 +@agents_router.post(
115 + "/{agent_id}/critical",
116 + response_model=AgentModifyResponse,
117 + description="Mark agent as critical",
118 + dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))],
119 +)
120 async def mark_agent_as_critical(agent_id: str) -> AgentModifyResponse:
121 logger.info(f"Marking agent {agent_id} as critical")
122 return mark_agent_criticality(agent_id, True)
123
124
70 -@agents_router.post("/{agent_id}/noncritical", response_model=AgentModifyResponse, description="Mark agent as not critical")
125 +@agents_router.post(
126 + "/{agent_id}/noncritical",
127 + response_model=AgentModifyResponse,
128 + description="Mark agent as not critical",
129 + dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))],
130 +)
131 async def mark_agent_as_not_critical(agent_id: str) -> AgentModifyResponse:
132 logger.info(f"Marking agent {agent_id} as not critical")
133 return mark_agent_criticality(agent_id, False)
134
135
76 -@agents_router.get("/{agent_id}/vulnerabilities", response_model=WazuhAgentVulnerabilitiesResponse, description="Get agent vulnerabilities")
136 +@agents_router.get(
137 + "/{agent_id}/vulnerabilities",
138 + response_model=WazuhAgentVulnerabilitiesResponse,
139 + description="Get agent vulnerabilities",
140 + dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))],
141 +)
142 async def get_agent_vulnerabilities(agent_id: str) -> WazuhAgentVulnerabilitiesResponse:
143 logger.info(f"Fetching agent {agent_id} vulnerabilities")
144 return collect_agent_vulnerabilities(agent_id)
145
146
82 -@agents_router.get("/wazuh/outdated", response_model=OutdatedWazuhAgentsResponse, description="Get all outdated Wazuh agents")
147 +@agents_router.get(
148 + "/wazuh/outdated",
149 + response_model=OutdatedWazuhAgentsResponse,
150 + description="Get all outdated Wazuh agents",
151 + dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))],
152 +)
153 async def get_outdated_wazuh_agents() -> OutdatedWazuhAgentsResponse:
154 logger.info("Fetching all outdated Wazuh agents")
155 return get_outdated_agents_wazuh()
@@ -89,32 +159,41 @@ async def get_outdated_wazuh_agents() -> OutdatedWazuhAgentsResponse:
159 "/velociraptor/outdated",
160 response_model=OutdatedVelociraptorAgentsResponse,
161 description="Get all outdated Velociraptor agents",
162 + dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))],
163 )
164 async def get_outdated_velociraptor_agents() -> OutdatedVelociraptorAgentsResponse:
165 logger.info("Fetching all outdated Velociraptor agents")
166 return get_outdated_agents_velociraptor()
167
168
98 -@agents_router.delete("/{agent_id}/delete", response_model=AgentModifyResponse, description="Delete agent")
169 +@agents_router.delete(
170 + "/{agent_id}/delete",
171 + response_model=AgentModifyResponse,
172 + description="Delete agent",
173 + dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))],
174 +)
175 async def delete_agent(agent_id: str) -> AgentModifyResponse:
176 logger.info(f"Deleting agent {agent_id}")
101 - # delete_agent_db(agent_id)
102 - # delete_agent_wazuh(agent_id)
103 - client_id = session.query(Agents).filter(Agents.agent_id == agent_id).first().velociraptor_id
177 + delete_agent_wazuh(agent_id)
178 + client_id = fetch_velociraptor_id(agent_id)
179 delete_agent_velociraptor(client_id)
105 - return {"success": True, "message": f"Agent {agent_id} deleted from database and Wazuh"}
180 + delete_agent_from_database(agent_id)
181 + return {"success": True, "message": f"Agent {agent_id} deleted from database, Wazuh, and Velociraptor"}
182
183
184 @agents_router.put(
185 "/{agent_id}/update-customer-code",
186 response_model=AgentUpdateCustomerCodeResponse,
111 - description="Update agent customer code",
187 + description="Update `agent` customer code",
188 + dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))],
189 )
190 async def update_agent_customer_code(agent_id: str, body: AgentUpdateCustomerCodeBody) -> AgentUpdateCustomerCodeResponse:
191 logger.info(f"Updating agent {agent_id} customer code to {body.customer_code}")
115 - agent = session.query(Agents).filter(Agents.agent_id == agent_id).first()
116 - if not agent:
117 - raise HTTPException(status_code=404, detail=f"Agent with agent_id {agent_id} not found")
118 - agent.customer_code = body.customer_code
119 - session.commit()
120 - return {"success": True, "message": f"Agent {agent_id} customer code updated to {body.customer_code}"}
192 + try:
193 + agent = session.query(Agents).filter(Agents.agent_id == agent_id).first()
194 + agent.customer_code = body.customer_code
195 + session.commit()
196 + return {"success": True, "message": f"Agent {agent_id} customer code updated to {body.customer_code}"}
197 + except Exception as e:
198 + if not agent:
199 + raise HTTPException(status_code=404, detail=f"Agent with agent_id {agent_id} not found")
backend/app/agents/schema/agents.py
+1 -1
@@ -19,7 +19,7 @@ class SyncedAgent(WazuhAgent, VelociraptorAgent):
19
20
21 class SyncedAgentsResponse(BaseModel):
22 - agents_added: List[SyncedAgent]
22 + # agents_added: List[SyncedAgent]
23 success: bool
24 message: str
25
backend/app/agents/services/modify.py
+9 -6
@@ -7,12 +7,15 @@ from app.db.universal_models import Agents
7
8 def mark_agent_criticality(agent_id: str, critical: bool):
9 """Mark agent as critical or not critical."""
10 - agent = session.query(Agents).filter(Agents.agent_id == agent_id).first()
11 - if not agent:
12 - raise HTTPException(status_code=404, detail=f"Agent with agent_id {agent_id} not found")
13 - agent.critical_asset = critical
14 - session.commit()
15 - return {"success": True, "message": f"Agent {agent_id} marked as critical: {critical}"}
10 + try:
11 + agent = session.query(Agents).filter(Agents.agent_id == agent_id).first()
12 + agent.critical_asset = critical
13 + session.commit()
14 + return {"success": True, "message": f"Agent {agent_id} marked as critical: {critical}"}
15 + except Exception as e:
16 + if not agent:
17 + raise HTTPException(status_code=404, detail=f"Agent with agent_id {agent_id} not found")
18 + raise HTTPException(status_code=500, detail=f"Failed to mark agent as critical: {e}")
19
20
21 def delete_agent_db(agent_id: str):
backend/app/agents/services/status.py
+29 -16
@@ -1,5 +1,6 @@
1 from typing import List
2
3 +from fastapi import HTTPException
4 from loguru import logger
5
6 from app.agents.schema.agents import OutdatedVelociraptorAgentsResponse
@@ -19,7 +20,11 @@ def get_agent(agent_id: str) -> List[Agents]:
20 Returns:
21 AgentMetadata: The agent object if found, otherwise None.
22 """
22 - return session.query(Agents).filter(Agents.agent_id == agent_id).first()
23 + try:
24 + return session.query(Agents).filter(Agents.agent_id == agent_id).first()
25 + except Exception as e:
26 + logger.error(f"Failed to fetch agent with agent_id {agent_id}: {e}")
27 + raise HTTPException(status_code=500, detail=f"Failed to fetch agent with agent_id {agent_id}: {e}")
28
29
30 def get_outdated_agents_wazuh() -> OutdatedWazuhAgentsResponse:
@@ -32,12 +37,14 @@ def get_outdated_agents_wazuh() -> OutdatedWazuhAgentsResponse:
37 wazuh_manager = get_agent("000")
38 if wazuh_manager is None:
39 logger.error("Wazuh Manager with agent_id '000' not found.")
35 - return {"message": "Wazuh Manager with agent_id '000' not found.", "success": False}
36 -
37 - outdated_wazuh_agents = (
38 - session.query(Agents).filter(Agents.agent_id != "000", Agents.wazuh_agent_version != wazuh_manager.wazuh_agent_version).all()
39 - )
40 - return {"message": "Outdated Wazuh agents fetched successfully.", "success": True, "outdated_wazuh_agents": outdated_wazuh_agents}
40 + raise HTTPException(status_code=404, detail="Wazuh Manager with agent_id '000' not found.")
41 + try:
42 + outdated_wazuh_agents = (
43 + session.query(Agents).filter(Agents.agent_id != "000", Agents.wazuh_agent_version != wazuh_manager.wazuh_agent_version).all()
44 + )
45 + return {"message": "Outdated Wazuh agents fetched successfully.", "success": True, "outdated_wazuh_agents": outdated_wazuh_agents}
46 + except Exception as e:
47 + raise HTTPException(status_code=500, detail=f"Failed to fetch outdated Wazuh agents: {e}")
48
49
50 def get_outdated_agents_velociraptor() -> OutdatedVelociraptorAgentsResponse:
@@ -50,12 +57,18 @@ def get_outdated_agents_velociraptor() -> OutdatedVelociraptorAgentsResponse:
57 outdated_velociraptor_agents = []
58 vql_server_version = "select * from config"
59 server_version = UniversalService()._get_server_version(vql_server_version)
53 - agents = session.query(Agents).all()
54 - for agent in agents:
55 - if agent.velociraptor_agent_version != server_version:
56 - outdated_velociraptor_agents.append(agent)
57 - return {
58 - "message": "Outdated Velociraptor agents fetched successfully.",
59 - "success": True,
60 - "outdated_velociraptor_agents": outdated_velociraptor_agents,
61 - }
60 + try:
61 + agents = session.query(Agents).all()
62 + except Exception as e:
63 + raise HTTPException(status_code=500, detail=f"Failed to fetch agents: {e}")
64 + try:
65 + for agent in agents:
66 + if agent.velociraptor_agent_version != server_version:
67 + outdated_velociraptor_agents.append(agent)
68 + return {
69 + "message": "Outdated Velociraptor agents fetched successfully.",
70 + "success": True,
71 + "outdated_velociraptor_agents": outdated_velociraptor_agents,
72 + }
73 + except Exception as e:
74 + raise HTTPException(status_code=500, detail=f"Failed to fetch outdated Velociraptor agents: {e}")
backend/app/agents/velociraptor/services/agents.py
+81 -15
@@ -1,13 +1,26 @@
1 from datetime import datetime
2
3 +from fastapi import HTTPException
4 from loguru import logger
5
5 -from app.agents.schema.agents import AgentsResponse
6 +from app.agents.schema.agents import AgentModifyResponse
7 from app.agents.velociraptor.schema.agents import VelociraptorAgent
7 -from app.connectors.velociraptor.services.artifacts import ArtifactsService
8 from app.connectors.velociraptor.utils.universal import UniversalService
9
10
11 +def create_query(query: str) -> str:
12 + """
13 + Create a query string.
14 +
15 + Args:
16 + query (str): The query to be executed.
17 +
18 + Returns:
19 + str: The created query string.
20 + """
21 + return query
22 +
23 +
24 def collect_velociraptor_agent(agent_name: str) -> VelociraptorAgent:
25 """
26 Retrieves the client ID, last_seen_at and client version based on the agent name from Velociraptor.
@@ -48,20 +61,73 @@ def collect_velociraptor_agent(agent_name: str) -> VelociraptorAgent:
61 return VelociraptorAgent(client_id=client_id, client_last_seen=client_last_seen, client_version=client_version)
62
63
51 -def delete_agent_velociraptor(client_id: str) -> AgentsResponse:
52 - """
53 - Deletes an agent from Velociraptor.
64 +def execute_query(universal_service, query: str) -> dict:
65 + flow = universal_service.execute_query(query)
66 + logger.info(f"Successfully ran artifact collection on {flow}")
67 + return flow
68
55 - Args:
56 - client_id (str): The client ID of the agent to delete.
69
58 - Returns:
59 - AgentsResponse: The response object.
60 - """
61 - logger.info(f"Deleting agent {client_id} from Velociraptor")
70 +def check_flow_success(flow: dict, client_id: str) -> dict:
71 + if flow["success"]:
72 + logger.info(f"Successfully deleted velociraptor client {client_id}")
73 + return {"message": f"Successfully deleted velociraptor client {client_id}", "success": True}
74 + else:
75 + logger.error(f"Failed to delete velociraptor client {client_id}")
76 + return handle_exception(e="Failed to delete velociraptor client", client_id=client_id)
77 +
78 +
79 +def check_client_in_results(results: dict, client_id: str) -> dict:
80 + if results["results"] == []:
81 + logger.info(f"Successfully deleted velociraptor client {client_id}")
82 + return {"message": f"Successfully deleted velociraptor client {client_id}", "success": True}
83 +
84 + for result in results["results"]:
85 + if result["client_id"] == client_id:
86 + logger.error(f"Failed to delete velociraptor client {client_id}")
87 + return handle_exception(e="Failed to delete velociraptor client", client_id=client_id)
88 +
89 +
90 +def handle_exception(e: Exception, client_id: str) -> dict:
91 + logger.error(f"Failed to delete client {client_id}: {e}")
92 + raise HTTPException(
93 + status_code=500,
94 + detail=f"Failed to delete Velociraptor client {client_id}: {e}",
95 + )
96 +
97 +
98 +def delete_agent_velociraptor(client_id: str) -> AgentModifyResponse:
99 + try:
100 + delete_client(client_id=client_id)
101 + ensure_client_deleted(client_id=client_id)
102 + return AgentModifyResponse(success=True, message="Agent deleted successfully")
103 + except Exception as e:
104 + return handle_exception(e, client_id)
105 +
106 +
107 +def delete_client(client_id: str) -> dict:
108 + universal_service = UniversalService()
109 try:
63 - ArtifactsService().delete_client(client_id=client_id)
64 - return AgentsResponse(success=True, message="Agent deleted successfully")
110 + query = create_query(
111 + f"SELECT collect_client(client_id='server', artifacts=['Server.Utils.DeleteClient'], env=dict(ClientIdList='{client_id}',ReallyDoIt='Y')) FROM scope()",
112 + )
113 + flow = execute_query(universal_service, query)
114 + return check_flow_success(flow, client_id)
115 + except Exception as e:
116 + return handle_exception(e, client_id)
117 +
118 +
119 +def ensure_client_deleted(client_id: str) -> dict:
120 + universal_service = UniversalService()
121 + try:
122 + query = create_query("SELECT collect_client(client_id='server', artifacts=['Server.Information.Clients'], env=dict()) FROM scope()")
123 + flow = execute_query(universal_service, query)
124 + flow_id = (
125 + flow.get("results")[0]
126 + .get("collect_client(client_id='server', artifacts=['Server.Information.Clients'], env=dict())")
127 + .get("flow_id")
128 + )
129 +
130 + results = universal_service.read_collection_results(client_id=client_id, flow_id=flow_id, artifact="Server.Information.Clients")
131 + return check_client_in_results(results, client_id)
132 except Exception as e:
66 - logger.error(f"Failed to delete agent {client_id}. Error: {e}")
67 - return AgentsResponse(success=False, message="Failed to delete agent")
133 + return handle_exception(e, client_id)
backend/app/agents/wazuh/services/agents.py
+67 -28
@@ -1,3 +1,4 @@
1 +from fastapi import HTTPException
2 from loguru import logger
3
4 from app.agents.schema.agents import AgentModifyResponse
@@ -10,42 +11,80 @@ from app.connectors.wazuh_manager.utils.universal import send_get_request
11 def collect_wazuh_agents() -> WazuhAgentsList:
12 logger.info("Collecting all agents from Wazuh Manager")
13 agents_collected = send_get_request(endpoint="/agents", params={"limit": 1000})
13 - logger.info(f"Agents collected: {agents_collected}")
14 - if agents_collected["success"]:
15 - wazuh_agents_list = []
16 - for agent in agents_collected["data"]["data"]["affected_items"]:
17 - os_name = agent.get("os", {}).get("name", "Unknown")
18 - last_keep_alive = agent.get("lastKeepAlive", "Unknown")
19 - agent_group_list = agent.get("group", [])
20 - agent_group = agent_group_list[0] if agent_group_list else "Unknown"
21 -
22 - wazuh_agent = WazuhAgent(
23 - agent_id=agent["id"],
24 - agent_name=agent["name"],
25 - agent_ip=agent["ip"],
26 - agent_os=os_name,
27 - agent_label=agent_group,
28 - agent_last_seen=last_keep_alive,
29 - wazuh_agent_version=agent["version"] if "version" in agent else "n/a",
30 - )
31 - wazuh_agents_list.append(wazuh_agent)
32 -
33 - return WazuhAgentsList(agents=wazuh_agents_list, success=True, message="Agents collected successfully")
14 +
15 + if agents_collected.get("success") == False:
16 + raise HTTPException(
17 + status_code=500,
18 + detail=agents_collected.get("message", "Unknown error"),
19 + )
20 + try:
21 + if agents_collected.get("success"):
22 + wazuh_agents_list = []
23 + for agent in agents_collected.get("data", {}).get("data", {}).get("affected_items", []):
24 + os_name = agent.get("os", {}).get("name", "Unknown")
25 + last_keep_alive = agent.get("lastKeepAlive", "Unknown")
26 + agent_group_list = agent.get("group", [])
27 + agent_group = agent_group_list[0] if agent_group_list else "Unknown"
28 +
29 + wazuh_agent = WazuhAgent(
30 + agent_id=agent.get("id", "Unknown"),
31 + agent_name=agent.get("name", "Unknown"),
32 + agent_ip=agent.get("ip", "Unknown"),
33 + agent_os=os_name,
34 + agent_label=agent_group,
35 + agent_last_seen=last_keep_alive,
36 + wazuh_agent_version=agent.get("version", "n/a"),
37 + )
38 + wazuh_agents_list.append(wazuh_agent)
39 +
40 + return WazuhAgentsList(agents=wazuh_agents_list, success=True, message="Agents collected successfully")
41 +
42 + except (KeyError, IndexError, HTTPException) as e:
43 + # Handle or log the error as needed
44 + logger.error(f"An error occurred: {e}")
45 + raise HTTPException(
46 + status_code=500,
47 + detail=f"Failed to collect agents: {e}",
48 + )
49 +
50 + except Exception as e:
51 + # Catch-all for other exceptions
52 + logger.error(f"An unexpected error occurred: {e}")
53 + raise HTTPException(
54 + status_code=500,
55 + detail=f"Failed to collect agents: {e}",
56 + )
57 +
58 +
59 +def handle_agent_deletion_response(agent_deleted: dict, agent_id: str):
60 + if agent_deleted["success"]:
61 + return AgentModifyResponse(success=True, message="Agent deleted successfully")
62 else:
35 - return WazuhAgentsList(agents=[], success=False, message="Failed to collect agents")
63 + raise HTTPException(
64 + status_code=400,
65 + detail=f"Failed to delete agent {agent_id} from Wazuh Manager: {agent_deleted.get('message', 'Unknown error')}",
66 + )
67
68
38 -def delete_agent(agent_id: str) -> AgentModifyResponse:
69 +def delete_agent_wazuh(agent_id: str) -> AgentModifyResponse:
70 """Delete agent from Wazuh Manager."""
71 logger.info(f"Deleting agent {agent_id} from Wazuh Manager")
72 +
73 params = {
74 "purge": True,
75 "agents_list": [agent_id],
76 "status": "all",
77 "older_than": "0s",
78 }
47 - agent_deleted = send_delete_request(endpoint="/agents", params=params)
48 - if agent_deleted["success"]:
49 - return AgentModifyResponse(success=True, message="Agent deleted successfully")
50 - else:
51 - return AgentModifyResponse(success=False, message="Failed to delete agent")
79 +
80 + try:
81 + agent_deleted = send_delete_request(endpoint="/agents", params=params)
82 + return handle_agent_deletion_response(agent_deleted, agent_id)
83 +
84 + except HTTPException as http_e:
85 + # * Catch any HTTPException and re-raise it
86 + raise http_e
87 +
88 + except Exception as e:
89 + # * Catch-all for other exceptions
90 + raise HTTPException(status_code=500, detail=f"Failed to delete agent {agent_id} from Wazuh Manager: {e}")
backend/app/agents/wazuh/services/vulnerabilities.py
+15 -9
@@ -1,5 +1,6 @@
1 from typing import List
2
3 +from fastapi import HTTPException
4 from loguru import logger
5
6 from app.agents.wazuh.schema.agents import WazuhAgentVulnerabilities
@@ -11,15 +12,20 @@ def collect_agent_vulnerabilities(agent_id: str):
12 """Collect agent vulnerabilities from Wazuh Manager."""
13 logger.info(f"Collecting agent {agent_id} vulnerabilities from Wazuh Manager")
14 agent_vulnerabilities = send_get_request(endpoint=f"/vulnerability/{agent_id}")
14 - if agent_vulnerabilities["success"]:
15 - processed_vulnerabilities = process_agent_vulnerabilities(agent_vulnerabilities["data"])
16 - return WazuhAgentVulnerabilitiesResponse(
17 - vulnerabilities=processed_vulnerabilities,
18 - success=True,
19 - message="Vulnerabilities collected successfully",
20 - )
15 + if agent_vulnerabilities["success"] is False:
16 + raise HTTPException(status_code=500, detail=agent_vulnerabilities["message"])
17 +
18 + processed_vulnerabilities = process_agent_vulnerabilities(agent_vulnerabilities["data"])
19 + return WazuhAgentVulnerabilitiesResponse(
20 + vulnerabilities=processed_vulnerabilities,
21 + success=True,
22 + message="Vulnerabilities collected successfully",
23 + )
24
25
26 def process_agent_vulnerabilities(agent_vulnerabilities: dict) -> List[WazuhAgentVulnerabilities]:
24 - vulnerabilities = agent_vulnerabilities.get("data", {}).get("affected_items", [])
25 - return [WazuhAgentVulnerabilities(**vuln) for vuln in vulnerabilities]
27 + try:
28 + vulnerabilities = agent_vulnerabilities.get("data", {}).get("affected_items", [])
29 + return [WazuhAgentVulnerabilities(**vuln) for vuln in vulnerabilities]
30 + except Exception as e:
31 + raise HTTPException(status_code=500, detail=f"Failed to process agent vulnerabilities: {e}")
backend/app/auth/models/users.py
+2 -2
@@ -40,8 +40,8 @@ class UserInput(SQLModel):
40 password: str = Field(
41 max_length=256,
42 min_length=8,
43 - regex="^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d]{8,}$",
44 - description="Password must be at least 8 characters long and contain at least one uppercase letter, one lowercase letter, and one number",
43 + regex="^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[@$!%*?&#])[A-Za-z\\d@$!%*?&#]{8,}$",
44 + description="Password must be at least 8 characters long and contain at least one uppercase letter, one lowercase letter, one number, and one special character",
45 )
46 email: EmailStr
47 role_id: RoleEnum = Field(RoleEnum.analyst, description="Role ID 1: admin, 2: analyst", foreign_key="role.id")
backend/app/auth/routes/auth.py
+3
@@ -5,6 +5,9 @@ from fastapi import Depends
5 from fastapi import HTTPException
6 from fastapi import status
7 from fastapi.security import OAuth2PasswordRequestForm
8 +from loguru import logger
9 +from sqlmodel import Session
10 +from sqlmodel import engine
11
12 from app.auth.models.users import User
13 from app.auth.models.users import UserInput
backend/app/connectors/cortex/routes/analyzers.py
+14 -2
@@ -3,8 +3,10 @@ from typing import List
3 from fastapi import APIRouter
4 from fastapi import Depends
5 from fastapi import HTTPException
6 +from fastapi import Security
7 from loguru import logger
8
9 +from app.auth.utils import AuthHandler
10 from app.connectors.cortex.schema.analyzers import AnalyzersResponse
11 from app.connectors.cortex.schema.analyzers import RunAnalyzerBody
12 from app.connectors.cortex.schema.analyzers import RunAnalyzerResponse
@@ -28,13 +30,23 @@ def verify_analyzer_exists(run_analyzer_body: RunAnalyzerBody) -> RunAnalyzerBod
30 return run_analyzer_body
31
32
31 -@cortex_analyzer_router.get("", response_model=AnalyzersResponse, description="Get all analyzers")
33 +@cortex_analyzer_router.get(
34 + "",
35 + response_model=AnalyzersResponse,
36 + description="Get all analyzers",
37 + dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))],
38 +)
39 async def get_all_analyzers() -> AnalyzersResponse:
40 logger.info("Fetching all analyzers")
41 return get_analyzers()
42
43
37 -@cortex_analyzer_router.post("/run", response_model=RunAnalyzerResponse, description="Run an analyzer")
44 +@cortex_analyzer_router.post(
45 + "/run",
46 + response_model=RunAnalyzerResponse,
47 + description="Run an analyzer",
48 + dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))],
49 +)
50 async def run_analyzer_route(run_analyzer_body: RunAnalyzerBody = Depends(verify_analyzer_exists)) -> RunAnalyzerResponse:
51 is_valid, data_type = RunAnalyzerBody.is_valid_datatype(run_analyzer_body.analyzer_data)
52 if not is_valid:
backend/app/connectors/cortex/services/analyzers.py
+5 -1
@@ -21,7 +21,11 @@ from app.connectors.cortex.utils.universal import (
21
22
23 def fetch_analyzers(api: Api) -> List[Dict]:
24 - return api.analyzers.find_all({}, range="all")
24 + try:
25 + return api.analyzers.find_all({}, range="all")
26 + except Exception as e:
27 + logger.error(f"Error fetching analyzers: {e}")
28 + raise HTTPException(status_code=500, detail=f"Error fetching analyzers: {e}")
29
30
31 def extract_analyzer_names(analyzers: List[Dict]) -> List[str]:
backend/app/connectors/cortex/utils/universal.py
+2 -5
@@ -4,6 +4,7 @@ from typing import Any
4 from typing import Dict
5
6 from cortex4py.api import Api
7 +from fastapi import HTTPException
8 from loguru import logger
9
10 from app.connectors.cortex.schema.analyzers import AnalyzerJobData
@@ -70,11 +71,7 @@ def run_and_wait_for_analyzer(analyzer_name: str, job_data: AnalyzerJobData) ->
71 job = api.analyzers.run_by_name(analyzer_name, job_data.dict(), force=1)
72 return monitor_analyzer_job(api, job)
73 except Exception as e:
73 - logger.error(f"Error running analyzer {analyzer_name}: {e}")
74 - logger.debug(f"job_data dict: {job_data.dict()}")
75 - logger.debug(f"Exception details: {traceback.format_exc()}")
76 - logger.debug(f"Error running analyzer {analyzer_name}: {e}", exc_info=True)
77 - return {"success": False, "message": f"Error running analyzer {analyzer_name}: {e}"}
74 + raise HTTPException(status_code=500, detail=f"Error running analyzer {analyzer_name}: {e}")
75
76
77 def monitor_analyzer_job(api: Api, job: Any) -> Dict[str, Any]:
backend/app/connectors/dfir_iris/routes/alerts.py
+26 -4
@@ -1,8 +1,10 @@
1 from fastapi import APIRouter
2 from fastapi import Depends
3 from fastapi import HTTPException
4 +from fastapi import Security
5 from loguru import logger
6
7 +from app.auth.utils import AuthHandler
8 from app.connectors.dfir_iris.schema.alerts import AlertResponse
9 from app.connectors.dfir_iris.schema.alerts import AlertsResponse
10 from app.connectors.dfir_iris.schema.alerts import BookmarkedAlertsResponse
@@ -23,25 +25,45 @@ def verify_alert_exists(alert_id: str) -> str:
25 dfir_iris_alerts_router = APIRouter()
26
27
26 -@dfir_iris_alerts_router.get("", response_model=AlertsResponse, description="Get all alerts")
28 +@dfir_iris_alerts_router.get(
29 + "",
30 + response_model=AlertsResponse,
31 + description="Get all alerts",
32 + dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))],
33 +)
34 async def get_all_alerts() -> AlertsResponse:
35 logger.info("Fetching all alerts")
36 return get_alerts()
37
38
32 -@dfir_iris_alerts_router.get("/bookmark", response_model=BookmarkedAlertsResponse, description="Get all bookmarked alerts")
39 +@dfir_iris_alerts_router.get(
40 + "/bookmark",
41 + response_model=BookmarkedAlertsResponse,
42 + description="Get all bookmarked alerts",
43 + dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))],
44 +)
45 async def get_all_bookmarked_alerts() -> BookmarkedAlertsResponse:
46 logger.info("Fetching all bookmarked alerts")
47 return get_bookmarked_alerts()
48
49
38 -@dfir_iris_alerts_router.post("/bookmark/{alert_id}", response_model=AlertResponse, description="Bookmark an alert")
50 +@dfir_iris_alerts_router.post(
51 + "/bookmark/{alert_id}",
52 + response_model=AlertResponse,
53 + description="Bookmark an alert",
54 + dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))],
55 +)
56 async def bookmark_alert_route(alert_id: str = Depends(verify_alert_exists)) -> AlertResponse:
57 logger.info(f"Bookmarking alert {alert_id}")
58 return bookmark_alert(alert_id, bookmarked=True)
59
60
44 -@dfir_iris_alerts_router.delete("/bookmark/{alert_id}", response_model=AlertResponse, description="Unbookmark an alert")
61 +@dfir_iris_alerts_router.delete(
62 + "/bookmark/{alert_id}",
63 + response_model=AlertResponse,
64 + description="Unbookmark an alert",
65 + dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))],
66 +)
67 async def unbookmark_alert_route(alert_id: str = Depends(verify_alert_exists)) -> AlertResponse:
68 logger.info(f"Unbookmarking alert {alert_id}")
69 return bookmark_alert(alert_id, bookmarked=False)
backend/app/connectors/dfir_iris/routes/assets.py
+8 -1
@@ -1,8 +1,10 @@
1 from fastapi import APIRouter
2 from fastapi import Depends
3 from fastapi import HTTPException
4 +from fastapi import Security
5 from loguru import logger
6
7 +from app.auth.utils import AuthHandler
8 from app.connectors.dfir_iris.schema.assets import AssetResponse
9 from app.connectors.dfir_iris.services.assets import get_case_assets
10 from app.connectors.dfir_iris.utils.universal import check_case_exists
@@ -19,7 +21,12 @@ def verify_case_exists(case_id: int) -> int:
21 assets_router = APIRouter()
22
23
22 -@assets_router.get("/{case_id}", response_model=AssetResponse, description="Get all assets for a case")
24 +@assets_router.get(
25 + "/{case_id}",
26 + response_model=AssetResponse,
27 + description="Get all assets for a case",
28 + dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))],
29 +)
30 async def get_case_assets_route(case_id: int = Depends(verify_case_exists)) -> AssetResponse:
31 logger.info(f"Fetching assets for case {case_id}")
32 return get_case_assets(case_id)
backend/app/connectors/dfir_iris/routes/cases.py
+20 -3
@@ -3,8 +3,10 @@ from datetime import timedelta
3 from fastapi import APIRouter
4 from fastapi import Depends
5 from fastapi import HTTPException
6 +from fastapi import Security
7 from loguru import logger
8
9 +from app.auth.utils import AuthHandler
10 from app.connectors.dfir_iris.schema.cases import CaseOlderThanBody
11 from app.connectors.dfir_iris.schema.cases import CaseResponse
12 from app.connectors.dfir_iris.schema.cases import CasesBreachedResponse
@@ -37,19 +39,34 @@ def get_timedelta(older_than: int, time_unit: TimeUnit) -> CaseOlderThanBody:
39 return CaseOlderThanBody(older_than=delta, time_unit=time_unit)
40
41
40 -@cases_router.get("", response_model=CaseResponse, description="Get all cases")
42 +@cases_router.get(
43 + "",
44 + response_model=CaseResponse,
45 + description="Get all cases",
46 + dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))],
47 +)
48 async def get_cases_route() -> CaseResponse:
49 logger.info("Fetching all cases")
50 return get_all_cases()
51
52
46 -@cases_router.post("/older_than", response_model=CasesBreachedResponse, description="Get all cases older than a specified date")
53 +@cases_router.post(
54 + "/older_than",
55 + response_model=CasesBreachedResponse,
56 + description="Get all cases older than a specified date",
57 + dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))],
58 +)
59 async def get_cases_older_than_route(case_older_than_body: CaseOlderThanBody = Depends(get_timedelta)) -> CaseResponse:
60 logger.info(f"Fetching all cases older than {case_older_than_body.older_than} ({case_older_than_body.time_unit.value})")
61 return get_cases_older_than(case_older_than_body)
62
63
52 -@cases_router.get("/{case_id}", response_model=SingleCaseResponse, description="Get a single case")
64 +@cases_router.get(
65 + "/{case_id}",
66 + response_model=SingleCaseResponse,
67 + description="Get a single case",
68 + dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))],
69 +)
70 async def get_single_case_route(case_id: int = Depends(verify_case_exists)) -> SingleCaseResponse:
71 logger.info(f"Fetching case {case_id}")
72 single_case_body = SingleCaseBody(case_id=case_id)
backend/app/connectors/dfir_iris/routes/notes.py
+14 -2
@@ -3,8 +3,10 @@ from typing import Optional
3 from fastapi import APIRouter
4 from fastapi import Depends
5 from fastapi import HTTPException
6 +from fastapi import Security
7 from loguru import logger
8
9 +from app.auth.utils import AuthHandler
10 from app.connectors.dfir_iris.schema.notes import NoteCreationBody
11 from app.connectors.dfir_iris.schema.notes import NoteCreationResponse
12 from app.connectors.dfir_iris.schema.notes import NotesResponse
@@ -22,13 +24,23 @@ def verify_case_exists(case_id: int) -> int:
24 notes_router = APIRouter()
25
26
25 -@notes_router.get("/{case_id}", response_model=NotesResponse, description="Get all notes for a case")
27 +@notes_router.get(
28 + "/{case_id}",
29 + response_model=NotesResponse,
30 + description="Get all notes for a case",
31 + dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))],
32 +)
33 async def get_case_notes_route(case_id: int = Depends(verify_case_exists), search_term: Optional[str] = "%") -> NotesResponse:
34 logger.info(f"Fetching notes for case {case_id}")
35 return get_case_notes(case_id, search_term)
36
37
31 -@notes_router.post("/{case_id}", response_model=NoteCreationResponse, description="Create a note for a case")
38 +@notes_router.post(
39 + "/{case_id}",
40 + response_model=NoteCreationResponse,
41 + description="Create a note for a case",
42 + dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))],
43 +)
44 async def create_case_note_route(case_id: int, note_creation_body: NoteCreationBody) -> NoteCreationResponse:
45 verify_case_exists(case_id)
46 logger.info(f"Creating a note for case {case_id}")
backend/app/connectors/dfir_iris/routes/users.py
+14 -2
@@ -1,8 +1,10 @@
1 from fastapi import APIRouter
2 from fastapi import Depends
3 from fastapi import HTTPException
4 +from fastapi import Security
5 from loguru import logger
6
7 +from app.auth.utils import AuthHandler
8 from app.connectors.dfir_iris.schema.alerts import AlertResponse
9 from app.connectors.dfir_iris.schema.users import User
10 from app.connectors.dfir_iris.schema.users import UsersResponse
@@ -27,13 +29,23 @@ def verify_alert_exists(alert_id: str) -> str:
29 dfir_iris_users_router = APIRouter()
30
31
30 -@dfir_iris_users_router.get("", response_model=UsersResponse, description="Get all users")
32 +@dfir_iris_users_router.get(
33 + "",
34 + response_model=UsersResponse,
35 + description="Get all users",
36 + dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))],
37 +)
38 async def get_all_users() -> UsersResponse:
39 logger.info("Fetching all users")
40 return get_users()
41
42
36 -@dfir_iris_users_router.post("/assign/{alert_id}/{user_id}", response_model=AlertResponse, description="Assign a user to an alert")
43 +@dfir_iris_users_router.post(
44 + "/assign/{alert_id}/{user_id}",
45 + response_model=AlertResponse,
46 + description="Assign a user to an alert",
47 + dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))],
48 +)
49 async def assign_user_to_alert_route(alert_id: str = Depends(verify_alert_exists), user_id: int = Depends(verify_user_exists)) -> User:
50 logger.info(f"Assigning user {user_id} to alert {alert_id}")
51 return assign_user_to_alert(alert_id, user_id)
backend/app/connectors/dfir_iris/services/alerts.py
+2
@@ -1,3 +1,5 @@
1 +from fastapi import HTTPException
2 +
3 from app.connectors.dfir_iris.schema.alerts import AlertResponse
4 from app.connectors.dfir_iris.schema.alerts import AlertsResponse
5 from app.connectors.dfir_iris.schema.alerts import BookmarkedAlertsResponse
backend/app/connectors/dfir_iris/services/assets.py
+10 -3
@@ -1,3 +1,5 @@
1 +from fastapi import HTTPException
2 +
3 from app.connectors.dfir_iris.schema.assets import Asset
4 from app.connectors.dfir_iris.schema.assets import AssetResponse
5 from app.connectors.dfir_iris.schema.assets import AssetState
@@ -8,9 +10,14 @@ from app.connectors.dfir_iris.utils.universal import initialize_client_and_case
10 def get_case_assets(case_id: int) -> AssetResponse:
11 client, case = initialize_client_and_case("DFIR-IRIS")
12 result = fetch_and_validate_data(client, case.list_assets, case_id)
11 -
12 - asset_list = result["data"]["assets"]
13 - state_data = result["data"]["state"]
13 + try:
14 + asset_list = result["data"]["assets"]
15 + state_data = result["data"]["state"]
16 + except KeyError as e:
17 + raise HTTPException(
18 + status_code=500,
19 + detail=f"Failed to fetch assets for case {case_id}: {e}",
20 + )
21
22 return AssetResponse(
23 success=True,
backend/app/connectors/dfir_iris/services/cases.py
+8 -4
@@ -69,10 +69,14 @@ def filter_cases_older_than(cases: List[Dict], older_than: datetime) -> List[Dic
69
70 def get_all_cases() -> CaseResponse:
71 result = get_client_and_cases()
72 - if not result["success"]:
73 - logger.error(f"Failed to get all cases: {result['message']}")
74 - return HTTPException(status_code=500, detail=f"Failed to get all cases: {result['message']}")
75 - return CaseResponse(success=True, message="Successfully fetched all cases", cases=result["data"])
72 + try:
73 + if not result["success"]:
74 + logger.error(f"Failed to get all cases: {result['message']}")
75 + raise HTTPException(status_code=500, detail=f"Failed to get all cases: {result['message']}")
76 + return CaseResponse(success=True, message="Successfully fetched all cases", cases=result["data"])
77 + except Exception as err:
78 + logger.error(f"Failed to get all cases: {err}")
79 + raise HTTPException(status_code=500, detail=f"Failed to get all cases: {err}")
80
81
82 def get_cases_older_than(case_older_than_body: CaseOlderThanBody) -> CasesBreachedResponse:
backend/app/connectors/dfir_iris/utils/universal.py
+9 -2
@@ -86,7 +86,7 @@ def create_dfir_iris_client(connector_name: str) -> ClientSession:
86 )
87 except Exception as e:
88 logger.error(f"Error creating session with DFIR-IRIS: {e}")
89 - return HTTPException(status_code=500, detail=f"Error creating session with DFIR-IRIS: {e}")
89 + raise HTTPException(status_code=500, detail=f"Error creating session with DFIR-IRIS: {e}")
90
91
92 def fetch_and_parse_data(session: ClientSession, action: Callable, *args) -> Dict[str, Union[bool, Optional[Dict]]]:
@@ -163,8 +163,15 @@ def check_case_exists(case_id: int) -> bool:
163
164 def check_alert_exists(alert_id: str) -> bool:
165 try:
166 - logger.info(f"Checking if alert {alert_id} exists")
166 dfir_iris_client = create_dfir_iris_client("DFIR-IRIS")
167 + except Exception as e:
168 + raise HTTPException(
169 + status_code=500,
170 + detail=f"Failed to create DFIR-IRIS client. Make sure the DFIR-IRIS connector is configured correctly.",
171 + )
172 + try:
173 + logger.info(f"Checking if alert {alert_id} exists")
174 + # dfir_iris_client = create_dfir_iris_client("DFIR-IRIS")
175 alert = Alert(session=dfir_iris_client)
176 data = alert.get_alert(alert_id)
177 assert_api_resp(data, soft_fail=False)
backend/app/connectors/graylog/routes/collector.py
+26 -4
@@ -1,6 +1,8 @@
1 from fastapi import APIRouter
2 +from fastapi import Security
3 from loguru import logger
4
5 +from app.auth.utils import AuthHandler
6 from app.connectors.graylog.schema.collector import ConfiguredInputsResponse
7 from app.connectors.graylog.schema.collector import GraylogIndicesResponse
8 from app.connectors.graylog.schema.collector import GraylogInputsResponse
@@ -16,25 +18,45 @@ from app.connectors.graylog.services.collector import get_inputs_running
18 graylog_collector_router = APIRouter()
19
20
19 -@graylog_collector_router.get("/indices", response_model=GraylogIndicesResponse, description="Get all indices")
21 +@graylog_collector_router.get(
22 + "/indices",
23 + response_model=GraylogIndicesResponse,
24 + description="Get all indices",
25 + dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))],
26 +)
27 async def get_all_indices() -> GraylogIndicesResponse:
28 logger.info("Fetching all graylog indices")
29 return get_indices_full()
30
31
25 -@graylog_collector_router.get("/inputs", response_model=GraylogInputsResponse, description="Get all inputs")
32 +@graylog_collector_router.get(
33 + "/inputs",
34 + response_model=GraylogInputsResponse,
35 + description="Get all inputs",
36 + dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))],
37 +)
38 async def get_all_inputs() -> GraylogInputsResponse:
39 logger.info("Fetching all graylog inputs")
40 return get_inputs()
41
42
31 -@graylog_collector_router.get("/inputs/running", response_model=RunningInputsResponse, description="Get all running inputs")
43 +@graylog_collector_router.get(
44 + "/inputs/running",
45 + response_model=RunningInputsResponse,
46 + description="Get all running inputs",
47 + dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))],
48 +)
49 async def get_all_running_inputs() -> RunningInputsResponse:
50 logger.info("Fetching all graylog running inputs")
51 return get_inputs_running()
52
53
37 -@graylog_collector_router.get("/inputs/configured", response_model=ConfiguredInputsResponse, description="Get all configured inputs")
54 +@graylog_collector_router.get(
55 + "/inputs/configured",
56 + response_model=ConfiguredInputsResponse,
57 + description="Get all configured inputs",
58 + dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))],
59 +)
60 async def get_all_configured_inputs() -> ConfiguredInputsResponse:
61 logger.info("Fetching all graylog configured inputs")
62 return get_inputs_configured()
backend/app/connectors/graylog/routes/events.py
+14 -2
@@ -1,6 +1,8 @@
1 from fastapi import APIRouter
2 +from fastapi import Security
3 from loguru import logger
4
5 +from app.auth.utils import AuthHandler
6 from app.connectors.graylog.schema.events import AlertQuery
7 from app.connectors.graylog.schema.events import GraylogAlertsResponse
8 from app.connectors.graylog.schema.events import GraylogEventDefinitionsResponse
@@ -13,13 +15,23 @@ from app.connectors.graylog.services.events import get_event_definitions
15 graylog_events_router = APIRouter()
16
17
16 -@graylog_events_router.get("/event/definitions", response_model=GraylogEventDefinitionsResponse, description="Get all event definitions")
18 +@graylog_events_router.get(
19 + "/event/definitions",
20 + response_model=GraylogEventDefinitionsResponse,
21 + description="Get all event definitions",
22 + dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))],
23 +)
24 async def get_all_event_definitions() -> GraylogEventDefinitionsResponse:
25 logger.info("Fetching all graylog event definitions")
26 return get_event_definitions()
27
28
22 -@graylog_events_router.post("/event/alerts", response_model=GraylogAlertsResponse, description="Get all alerts")
29 +@graylog_events_router.post(
30 + "/event/alerts",
31 + response_model=GraylogAlertsResponse,
32 + description="Get all alerts",
33 + dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))],
34 +)
35 async def get_all_alerts(alert_query: AlertQuery) -> GraylogAlertsResponse:
36 logger.info("Fetching all graylog alerts")
37 return get_alerts(alert_query)
backend/app/connectors/graylog/routes/management.py
+32 -5
@@ -3,8 +3,10 @@ from typing import List
3 from fastapi import APIRouter
4 from fastapi import Depends
5 from fastapi import HTTPException
6 +from fastapi import Security
7 from loguru import logger
8
9 +from app.auth.utils import AuthHandler
10 from app.connectors.graylog.schema.management import DeletedIndexBody
11 from app.connectors.graylog.schema.management import DeletedIndexResponse
12 from app.connectors.graylog.schema.management import StartInputBody
@@ -75,35 +77,60 @@ def verify_stream_id(stop_stream_body: StopStreamBody) -> StopStreamBody:
77 return stop_stream_body
78
79
78 -@graylog_management_router.delete("/index", response_model=DeletedIndexResponse, description="Delete index")
80 +@graylog_management_router.delete(
81 + "/index",
82 + response_model=DeletedIndexResponse,
83 + description="Delete index",
84 + dependencies=[Security(AuthHandler().get_current_user, scopes=["admin"])],
85 +)
86 async def delete_index_route(deleted_index_body: DeletedIndexBody = Depends(verify_index_name)) -> DeletedIndexResponse:
87 logger.info(f"Deleting index {deleted_index_body.index_name}")
88
89 return delete_index(deleted_index_body.index_name)
90
91
85 -@graylog_management_router.post("/input/stop", response_model=StopInputResponse, description="Stop input")
92 +@graylog_management_router.post(
93 + "/input/stop",
94 + response_model=StopInputResponse,
95 + description="Stop input",
96 + dependencies=[Security(AuthHandler().get_current_user, scopes=["admin"])],
97 +)
98 async def stop_input_route(stop_input_body: StopInputBody = Depends(verify_input_id)) -> StopInputResponse:
99 logger.info(f"Stopping input {stop_input_body.input_id}")
100
101 return stop_input(stop_input_body.input_id)
102
103
92 -@graylog_management_router.post("/input/start", response_model=StartInputResponse, description="Start input")
104 +@graylog_management_router.post(
105 + "/input/start",
106 + response_model=StartInputResponse,
107 + description="Start input",
108 + dependencies=[Security(AuthHandler().get_current_user, scopes=["admin"])],
109 +)
110 async def start_input_route(start_input_body: StartInputBody = Depends(verify_input_id)) -> StartInputResponse:
111 logger.info(f"Starting input {start_input_body.input_id}")
112
113 return start_input(start_input_body.input_id)
114
115
99 -@graylog_management_router.post("/stream/stop", response_model=StopStreamResponse, description="Stop stream")
116 +@graylog_management_router.post(
117 + "/stream/stop",
118 + response_model=StopStreamResponse,
119 + description="Stop stream",
120 + dependencies=[Security(AuthHandler().get_current_user, scopes=["admin"])],
121 +)
122 async def stop_stream_route(stop_stream_body: StopStreamBody = Depends(verify_stream_id)) -> StopStreamResponse:
123 logger.info(f"Stopping stream {stop_stream_body.stream_id}")
124
125 return stop_stream(stop_stream_body.stream_id)
126
127
106 -@graylog_management_router.post("/stream/start", response_model=StartStreamResponse, description="Start stream")
128 +@graylog_management_router.post(
129 + "/stream/start",
130 + response_model=StartStreamResponse,
131 + description="Start stream",
132 + dependencies=[Security(AuthHandler().get_current_user, scopes=["admin"])],
133 +)
134 async def start_stream_route(start_stream_body: StartStreamBody = Depends(verify_stream_id)) -> StartStreamResponse:
135 logger.info(f"Starting stream {start_stream_body.stream_id}")
136
backend/app/connectors/graylog/routes/monitoring.py
+14 -2
@@ -1,6 +1,8 @@
1 from fastapi import APIRouter
2 +from fastapi import Security
3 from loguru import logger
4
5 +from app.auth.utils import AuthHandler
6 from app.connectors.graylog.schema.monitoring import GraylogMessagesResponse
7 from app.connectors.graylog.schema.monitoring import GraylogMetricsResponse
8 from app.connectors.graylog.services.monitoring import get_messages
@@ -12,14 +14,24 @@ from app.connectors.graylog.services.monitoring import get_metrics
14 graylog_monitoring_router = APIRouter()
15
16
15 -@graylog_monitoring_router.get("/messages", response_model=GraylogMessagesResponse, description="Get all messages")
17 +@graylog_monitoring_router.get(
18 + "/messages",
19 + response_model=GraylogMessagesResponse,
20 + description="Get all messages",
21 + dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))],
22 +)
23 async def get_all_messages(page_number: int = 1) -> GraylogMessagesResponse:
24 logger.info("Fetching all graylog messages")
25 logger.info(f"Page number: {page_number}")
26 return get_messages(page_number)
27
28
22 -@graylog_monitoring_router.get("/metrics", response_model=GraylogMetricsResponse, description="Get all metrics")
29 +@graylog_monitoring_router.get(
30 + "/metrics",
31 + response_model=GraylogMetricsResponse,
32 + description="Get all metrics",
33 + dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))],
34 +)
35 async def get_all_metrics() -> GraylogMetricsResponse:
36 logger.info("Fetching all graylog metrics")
37 return get_metrics()
backend/app/connectors/graylog/routes/pipelines.py
+79 -2
@@ -1,8 +1,20 @@
1 +from typing import Dict
2 +from typing import List
3 +
4 from fastapi import APIRouter
5 +from fastapi import Security
6 from loguru import logger
7
8 +from app.auth.utils import AuthHandler
9 from app.connectors.graylog.schema.pipelines import GraylogPipelinesResponse
10 +from app.connectors.graylog.schema.pipelines import GraylogPipelinesResponseWithRuleID
11 +from app.connectors.graylog.schema.pipelines import Pipeline
12 +from app.connectors.graylog.schema.pipelines import PipelineRule
13 from app.connectors.graylog.schema.pipelines import PipelineRulesResponse
14 +from app.connectors.graylog.schema.pipelines import PipelineWithRuleID
15 +from app.connectors.graylog.schema.pipelines import Stage
16 +from app.connectors.graylog.schema.pipelines import StageWithRuleID
17 +from app.connectors.graylog.services.pipelines import get_pipeline_rule_by_id
18 from app.connectors.graylog.services.pipelines import get_pipeline_rules
19 from app.connectors.graylog.services.pipelines import get_pipelines
20
@@ -12,13 +24,78 @@ from app.connectors.graylog.services.pipelines import get_pipelines
24 graylog_pipelines_router = APIRouter()
25
26
15 -@graylog_pipelines_router.get("/pipelines", response_model=GraylogPipelinesResponse, description="Get all pipelines")
27 +def create_rule_title_to_id_dict(pipeline_rules: List[PipelineRule]) -> Dict[str, str]:
28 + rule_title_to_id = {}
29 + for rule in pipeline_rules:
30 + rule_title_to_id[rule.title] = rule.id
31 + return rule_title_to_id
32 +
33 +
34 +def transform_stages_with_rule_ids(stages: List[Stage], rule_title_to_id: Dict[str, str]) -> List[StageWithRuleID]:
35 + new_stages = []
36 + for stage in stages:
37 + rule_ids = [rule_title_to_id.get(rule_title, None) for rule_title in stage.rules]
38 + new_stage = StageWithRuleID(**stage.dict(), rule_ids=rule_ids)
39 + new_stages.append(new_stage)
40 + return new_stages
41 +
42 +
43 +def transform_pipeline_with_rule_ids(pipeline: Pipeline, rule_title_to_id: Dict[str, str]) -> PipelineWithRuleID:
44 + new_stages = transform_stages_with_rule_ids(pipeline.stages, rule_title_to_id)
45 + pipeline_dict = pipeline.dict()
46 + pipeline_dict["stages"] = new_stages
47 + return PipelineWithRuleID(**pipeline_dict)
48 +
49 +
50 +@graylog_pipelines_router.get(
51 + "/pipelines",
52 + response_model=GraylogPipelinesResponse,
53 + description="Get all pipelines",
54 + dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))],
55 +)
56 async def get_all_pipelines() -> GraylogPipelinesResponse:
57 logger.info("Fetching all graylog pipelines")
58 return get_pipelines()
59
60
21 -@graylog_pipelines_router.get("/pipeline/rules", response_model=PipelineRulesResponse, description="Get all pipeline rules")
61 +@graylog_pipelines_router.get(
62 + "/pipeline/full",
63 + response_model=GraylogPipelinesResponseWithRuleID,
64 + description="Get all pipelines with rule IDs",
65 + dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))],
66 +)
67 +async def get_all_pipelines_with_rule_ids() -> GraylogPipelinesResponseWithRuleID:
68 + pipelines_response = get_pipelines()
69 + pipeline_rules_response = get_pipeline_rules()
70 +
71 + rule_title_to_id = create_rule_title_to_id_dict(pipeline_rules_response.pipeline_rules)
72 +
73 + new_pipelines = [transform_pipeline_with_rule_ids(pipeline, rule_title_to_id) for pipeline in pipelines_response.pipelines]
74 +
75 + return GraylogPipelinesResponseWithRuleID(
76 + pipelines=new_pipelines,
77 + success=pipelines_response.success,
78 + message=pipelines_response.message,
79 + )
80 +
81 +
82 +@graylog_pipelines_router.get(
83 + "/pipeline/rules",
84 + response_model=PipelineRulesResponse,
85 + description="Get all pipeline rules",
86 + dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))],
87 +)
88 async def get_all_pipeline_rules() -> PipelineRulesResponse:
89 logger.info("Fetching all graylog pipeline rules")
90 return get_pipeline_rules()
91 +
92 +
93 +@graylog_pipelines_router.get(
94 + "/pipeline/rules/{pipeline_id}",
95 + response_model=PipelineRulesResponse,
96 + description="Get all pipeline rules for a pipeline",
97 + dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))],
98 +)
99 +async def get_pipeline_rules_for_pipeline(pipeline_id: str) -> PipelineRulesResponse:
100 + logger.info(f"Fetching all graylog pipeline rules for pipeline {pipeline_id}")
101 + return get_pipeline_rule_by_id(pipeline_id)
backend/app/connectors/graylog/routes/streams.py
+8 -1
@@ -1,6 +1,8 @@
1 from fastapi import APIRouter
2 +from fastapi import Security
3 from loguru import logger
4
5 +from app.auth.utils import AuthHandler
6 from app.connectors.graylog.schema.streams import GraylogStreamsResponse
7 from app.connectors.graylog.services.streams import get_streams
8
@@ -10,7 +12,12 @@ from app.connectors.graylog.services.streams import get_streams
12 graylog_streams_router = APIRouter()
13
14
13 -@graylog_streams_router.get("/streams", response_model=GraylogStreamsResponse, description="Get all streams")
15 +@graylog_streams_router.get(
16 + "/streams",
17 + response_model=GraylogStreamsResponse,
18 + description="Get all streams",
19 + dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))],
20 +)
21 async def get_all_streams() -> GraylogStreamsResponse:
22 logger.info("Fetching all graylog streams")
23 return get_streams()
backend/app/connectors/graylog/schema/pipelines.py
+14
@@ -10,6 +10,10 @@ class Stage(BaseModel):
10 stage: int
11
12
13 +class StageWithRuleID(Stage):
14 + rule_ids: List[Optional[str]] # Add a new field to store rule IDs
15 +
16 +
17 class Pipeline(BaseModel):
18 created_at: str
19 description: str
@@ -42,3 +46,13 @@ class PipelineRulesResponse(BaseModel):
46 message: str
47 pipeline_rules: List[PipelineRule]
48 success: bool
49 +
50 +
51 +class PipelineWithRuleID(Pipeline):
52 + stages: List[StageWithRuleID] # Override the `stages` field with the new class
53 +
54 +
55 +class GraylogPipelinesResponseWithRuleID(BaseModel):
56 + message: str
57 + pipelines: List[PipelineWithRuleID]
58 + success: bool
backend/app/connectors/graylog/services/collector.py
+5 -1
@@ -1,6 +1,7 @@
1 from typing import List
2 from typing import Tuple
3
4 +from fastapi import HTTPException
5 from loguru import logger
6
7 from app.connectors.graylog.schema.collector import ConfiguredInput
@@ -18,7 +19,10 @@ def get_indices_full() -> GraylogIndicesResponse:
19 logger.info("Getting indices from Graylog")
20 indices_collected = send_get_request(endpoint="/api/system/indexer/indices")
21 if indices_collected["success"]:
21 - indices_data = indices_collected["data"]["all"]["indices"]
22 + try:
23 + indices_data = indices_collected["data"]["all"]["indices"]
24 + except KeyError:
25 + raise HTTPException(status_code=500, detail="Failed to collect indices key")
26
27 # Convert the dictionary to a list of GraylogIndexItem
28 indices_list = [GraylogIndexItem(index_name=name, index_info=info) for name, info in indices_data.items()]
backend/app/connectors/graylog/services/events.py
+9 -2
@@ -1,3 +1,4 @@
1 +from fastapi import HTTPException
2 from loguru import logger
3
4 from app.connectors.graylog.schema.events import AlertEvent
@@ -17,7 +18,10 @@ def get_event_definitions() -> GraylogEventDefinitionsResponse:
18 logger.info("Getting event definitions from Graylog")
19 event_definitions_collected = send_get_request(endpoint="/api/events/definitions")
20 if event_definitions_collected["success"]:
20 - event_definitions_data = event_definitions_collected["data"]["event_definitions"]
21 + try:
22 + event_definitions_data = event_definitions_collected["data"]["event_definitions"]
23 + except KeyError:
24 + raise HTTPException(status_code=500, detail="Failed to collect event definitions key")
25
26 # Convert the dictionary to a list of GraylogIndexItem
27 event_definitions_list = [EventDefinition(**event_definition_data) for event_definition_data in event_definitions_data]
@@ -36,7 +40,10 @@ def get_alerts(alert_query: AlertQuery) -> GraylogAlertsResponse:
40 response = send_post_request(endpoint="/api/events/search", data=alert_query.dict())
41
42 if response["success"]:
39 - raw_alerts_data = response["data"]
43 + try:
44 + raw_alerts_data = response["data"]
45 + except KeyError:
46 + raise HTTPException(status_code=500, detail="Failed to collect data key")
47 # Convert raw event data to Event objects
48 event_objects = [AlertEvent(**event_data) for event_data in raw_alerts_data["events"]]
49
backend/app/connectors/graylog/services/monitoring.py
+50 -38
@@ -1,3 +1,4 @@
1 +from fastapi import HTTPException
2 from loguru import logger
3
4 from app.connectors.graylog.schema.monitoring import GraylogMessages
@@ -14,24 +15,31 @@ def get_messages(page_number: int) -> GraylogMessagesResponse:
15 logger.info("Getting messages from Graylog")
16 params = {"page": page_number}
17 messages_collected = send_get_request(endpoint="/api/system/messages", params=params)
17 - if messages_collected["success"]:
18 - graylog_messages_list = []
19 - for message in messages_collected["data"]["messages"]:
20 - graylog_message = GraylogMessages(
21 - caller=message["caller"],
22 - content=message["content"],
23 - node_id=message["node_id"],
24 - timestamp=message["timestamp"],
18 + try:
19 + if messages_collected["success"]:
20 + graylog_messages_list = []
21 + for message in messages_collected["data"]["messages"]:
22 + graylog_message = GraylogMessages(
23 + caller=message["caller"],
24 + content=message["content"],
25 + node_id=message["node_id"],
26 + timestamp=message["timestamp"],
27 + )
28 + graylog_messages_list.append(graylog_message)
29 + return GraylogMessagesResponse(
30 + graylog_messages=graylog_messages_list,
31 + success=True,
32 + message="Messages collected successfully",
33 + total_messages=messages_collected["data"]["total"],
34 )
26 - graylog_messages_list.append(graylog_message)
27 - return GraylogMessagesResponse(
28 - graylog_messages=graylog_messages_list,
29 - success=True,
30 - message="Messages collected successfully",
31 - total_messages=messages_collected["data"]["total"],
32 - )
33 - else:
34 - return GraylogMessagesResponse(graylog_messages=[], success=False, message="Failed to collect messages")
35 +
36 + except KeyError as e:
37 + logger.error(f"Failed to collect messages key: {e}")
38 + raise HTTPException(status_code=500, detail=f"Failed to collect messages key: {e}")
39 + except Exception as e:
40 + logger.error(f"Failed to collect messages: {e}")
41 + raise HTTPException(status_code=500, detail=f"Failed to collect messages: {e}")
42 + return GraylogMessagesResponse(graylog_messages=[], success=False, message="Failed to collect messages")
43
44
45 def fetch_metrics_from_graylog() -> dict:
@@ -62,25 +70,29 @@ def get_metrics() -> GraylogMetricsResponse:
70 logger.info("Getting metrics from Graylog")
71 throughput_metrics_collected = fetch_metrics_from_graylog()
72 uncommitted_journal_entries_collected = fetch_uncommitted_journal_entries()
73 + try:
74 + if throughput_metrics_collected["success"] and uncommitted_journal_entries_collected["success"]:
75 + merged_metrics = merge_metrics_data(throughput_metrics_collected)
76 + throughput_metrics_list = filter_and_create_throughput_metrics(merged_metrics)
77 +
78 + uncommitted_journal_entries = GraylogUncommittedJournalEntries(
79 + uncommitted_journal_entries=uncommitted_journal_entries_collected["data"]["uncommitted_journal_entries"],
80 + )
81
66 - if throughput_metrics_collected["success"] and uncommitted_journal_entries_collected["success"]:
67 - merged_metrics = merge_metrics_data(throughput_metrics_collected)
68 - throughput_metrics_list = filter_and_create_throughput_metrics(merged_metrics)
69 -
70 - uncommitted_journal_entries = GraylogUncommittedJournalEntries(
71 - uncommitted_journal_entries=uncommitted_journal_entries_collected["data"]["uncommitted_journal_entries"],
72 - )
73 -
74 - return GraylogMetricsResponse(
75 - throughput_metrics=throughput_metrics_list,
76 - uncommitted_journal_entries=uncommitted_journal_entries.uncommitted_journal_entries,
77 - success=True,
78 - message="Metrics collected successfully",
79 - )
80 - else:
81 - return GraylogMetricsResponse(
82 - throughput_metrics=[],
83 - uncommitted_journal_entries=0,
84 - success=False,
85 - message="Failed to collect metrics",
86 - )
82 + return GraylogMetricsResponse(
83 + throughput_metrics=throughput_metrics_list,
84 + uncommitted_journal_entries=uncommitted_journal_entries.uncommitted_journal_entries,
85 + success=True,
86 + message="Metrics collected successfully",
87 + )
88 + except KeyError as e:
89 + raise HTTPException(status_code=500, detail=f"Failed to collect metrics key: {e}")
90 + except Exception as e:
91 + raise HTTPException(status_code=500, detail=f"Failed to collect metrics: {e}")
92 +
93 + return GraylogMetricsResponse(
94 + throughput_metrics=[],
95 + uncommitted_journal_entries=0,
96 + success=False,
97 + message="Failed to collect metrics",
98 + )
backend/app/connectors/graylog/services/pipelines.py
+38 -10
@@ -1,3 +1,4 @@
1 +from fastapi import HTTPException
2 from loguru import logger
3
4 from app.connectors.graylog.schema.pipelines import GraylogPipelinesResponse
@@ -11,19 +12,46 @@ def get_pipelines() -> GraylogPipelinesResponse:
12 """Get pipelines from Graylog."""
13 logger.info("Getting pipelines from Graylog")
14 pipelines_collected = send_get_request(endpoint="/api/system/pipelines/pipeline")
14 - if pipelines_collected["success"]:
15 - pipelines_list = [Pipeline(**pipeline_data) for pipeline_data in pipelines_collected["data"]]
16 - return GraylogPipelinesResponse(pipelines=pipelines_list, success=True, message="Pipelines collected successfully")
17 - else:
18 - return GraylogPipelinesResponse(pipelines=[], success=False, message="Failed to collect pipelines")
15 + try:
16 + if pipelines_collected["success"]:
17 + pipelines_list = [Pipeline(**pipeline_data) for pipeline_data in pipelines_collected["data"]]
18 + return GraylogPipelinesResponse(pipelines=pipelines_list, success=True, message="Pipelines collected successfully")
19 + except KeyError as e:
20 + logger.error(f"Failed to collect pipelines key: {e}")
21 + raise HTTPException(status_code=500, detail=f"Failed to collect pipelines key: {e}")
22 + except Exception as e:
23 + logger.error(f"Failed to collect pipelines: {e}")
24 + raise HTTPException(status_code=500, detail=f"Failed to collect pipelines: {e}")
25
26
27 def get_pipeline_rules() -> PipelineRulesResponse:
28 """Get pipeline rules from Graylog."""
29 logger.info("Getting pipeline rules from Graylog")
30 pipeline_rules_collected = send_get_request(endpoint="/api/system/pipelines/rule")
25 - if pipeline_rules_collected["success"]:
26 - pipeline_rules_list = [PipelineRule(**pipeline_rule_data) for pipeline_rule_data in pipeline_rules_collected["data"]]
27 - return PipelineRulesResponse(pipeline_rules=pipeline_rules_list, success=True, message="Pipeline rules collected successfully")
28 - else:
29 - return PipelineRulesResponse(pipeline_rules=[], success=False, message="Failed to collect pipeline rules")
31 + try:
32 + if pipeline_rules_collected["success"]:
33 + pipeline_rules_list = [PipelineRule(**pipeline_rule_data) for pipeline_rule_data in pipeline_rules_collected["data"]]
34 + return PipelineRulesResponse(pipeline_rules=pipeline_rules_list, success=True, message="Pipeline rules collected successfully")
35 + except KeyError as e:
36 + logger.error(f"Failed to collect pipeline rules key: {e}")
37 + raise HTTPException(status_code=500, detail=f"Failed to collect pipeline rules key: {e}")
38 + except Exception as e:
39 + logger.error(f"Failed to collect pipeline rules: {e}")
40 + raise HTTPException(status_code=500, detail=f"Failed to collect pipeline rules: {e}")
41 +
42 +
43 +def get_pipeline_rule_by_id(pipeline_id) -> PipelineRulesResponse:
44 + """Get pipeline rules from Graylog."""
45 + logger.info(f"Getting pipeline rules from Graylog for pipeline {pipeline_id}")
46 + pipeline_rules_collected = send_get_request(endpoint=f"/api/system/pipelines/rule/{pipeline_id}")
47 + logger.info(pipeline_rules_collected)
48 + try:
49 + if pipeline_rules_collected["success"]:
50 + pipeline_rule = PipelineRule(**pipeline_rules_collected["data"])
51 + return PipelineRulesResponse(pipeline_rules=[pipeline_rule], success=True, message="Pipeline rules collected successfully")
52 + except KeyError as e:
53 + logger.error(f"Failed to collect pipeline rules key: {e}")
54 + raise HTTPException(status_code=500, detail=f"Failed to collect pipeline rules key: {e}")
55 + except Exception as e:
56 + logger.error(f"Failed to collect pipeline rules: {e}")
57 + raise HTTPException(status_code=500, detail=f"Failed to collect pipeline rules: {e}")
backend/app/connectors/graylog/services/streams.py
+29 -14
@@ -1,5 +1,6 @@
1 from typing import List
2
3 +from fastapi import HTTPException
4 from loguru import logger
5
6 from app.connectors.graylog.schema.streams import GraylogStreamsResponse
@@ -11,23 +12,37 @@ def get_streams() -> GraylogStreamsResponse:
12 """Get streams from Graylog."""
13 logger.info("Getting streams from Graylog")
14 streams_collected = send_get_request(endpoint="/api/streams")
14 - if streams_collected["success"]:
15 - streams_list = [Stream(**stream_data) for stream_data in streams_collected["data"]["streams"]]
16 - return GraylogStreamsResponse(
17 - streams=streams_list,
18 - success=True,
19 - message="Streams collected successfully",
20 - total=streams_collected["data"]["total"],
21 - )
22 - else:
23 - return GraylogStreamsResponse(streams=[], success=False, message="Failed to collect streams", total=0)
15 + try:
16 + if streams_collected["success"]:
17 + streams_list = [Stream(**stream_data) for stream_data in streams_collected["data"]["streams"]]
18 + return GraylogStreamsResponse(
19 + streams=streams_list,
20 + success=True,
21 + message="Streams collected successfully",
22 + total=streams_collected["data"]["total"],
23 + )
24 + else:
25 + return GraylogStreamsResponse(streams=[], success=False, message="Failed to collect streams", total=0)
26 + except KeyError as e:
27 + logger.error(f"Failed to collect streams key: {e}")
28 + raise HTTPException(status_code=500, detail=f"Failed to collect streams key: {e}")
29 + except Exception as e:
30 + logger.error(f"Failed to collect streams: {e}")
31 + raise HTTPException(status_code=500, detail=f"Failed to collect streams: {e}")
32
33
34 def get_stream_ids() -> List[str]:
35 """Get stream IDs from Graylog."""
36 logger.info("Getting stream IDs from Graylog")
37 streams_collected = send_get_request(endpoint="/api/streams")
30 - if streams_collected["success"]:
31 - return [stream_data["id"] for stream_data in streams_collected["data"]["streams"]]
32 - else:
33 - return []
38 + try:
39 + if streams_collected["success"]:
40 + return [stream_data["id"] for stream_data in streams_collected["data"]["streams"]]
41 + else:
42 + return []
43 + except KeyError as e:
44 + logger.error(f"Failed to collect streams key: {e}")
45 + raise HTTPException(status_code=500, detail=f"Failed to collect streams key: {e}")
46 + except Exception as e:
47 + logger.error(f"Failed to collect streams: {e}")
48 + raise HTTPException(status_code=500, detail=f"Failed to collect streams: {e}")
backend/app/connectors/graylog/utils/universal.py
+34 -12
@@ -3,6 +3,7 @@ from typing import Dict
3 from typing import Optional
4
5 import requests
6 +from fastapi import HTTPException
7 from loguru import logger
8
9 from app.connectors.utils import get_connector_info_from_db
@@ -89,10 +90,16 @@ def send_get_request(endpoint: str, params: Optional[Dict[str, Any]] = None, con
90 params=params,
91 verify=False,
92 )
93 + if response.status_code == 404:
94 + raise HTTPException(
95 + status_code=404,
96 + detail=f"Failed to send GET request to {endpoint} with error: {response.json()['message']}",
97 + )
98 return {"data": response.json(), "success": True, "message": "Successfully retrieved data"}
99 + except HTTPException as e:
100 + raise e
101 except Exception as e:
94 - logger.error(f"Failed to send GET request to {endpoint} with error: {e}")
95 - return {"success": False, "message": f"Failed to send GET request to {endpoint} with error: {e}"}
102 + raise HTTPException(status_code=500, detail=f"Failed to send GET request to {endpoint} with error: {e}")
103
104
105 def send_post_request(endpoint: str, data: Dict[str, Any] = None, connector_name: str = "Graylog") -> Dict[str, Any]:
@@ -125,18 +132,19 @@ def send_post_request(endpoint: str, data: Dict[str, Any] = None, connector_name
132 verify=False,
133 )
134
128 - if response.status_code == 204:
135 + if response.status_code == 200:
136 + return {"data": response.json(), "success": True, "message": "Successfully completed request"}
137 + elif response.status_code == 204:
138 return {"data": None, "success": True, "message": "Successfully completed request with no content"}
139 else:
131 - return {
132 - "data": response.json(),
133 - "success": False if response.status_code >= 400 else True,
134 - "message": "Successfully retrieved data" if response.status_code < 400 else "Failed to retrieve data",
135 - }
140 + raise HTTPException(
141 + status_code=500,
142 + detail=f"Failed to send POST request to {endpoint} with error: {response.json()['message']}",
143 + )
144 + except HTTPException as e:
145 + raise e
146 except Exception as e:
137 - logger.debug(f"Response: {response}")
138 - logger.error(f"Failed to send POST request to {endpoint} with error: {e}")
139 - return {"success": False, "message": f"Failed to send POST request to {endpoint} with error: {e}"}
147 + raise HTTPException(status_code=500, detail=f"Failed to send POST request to {endpoint} with error: {e}")
148
149
150 def send_delete_request(endpoint: str, params: Optional[Dict[str, Any]] = None, connector_name: str = "Graylog") -> Dict[str, Any]:
@@ -167,7 +175,14 @@ def send_delete_request(endpoint: str, params: Optional[Dict[str, Any]] = None,
175 params=params,
176 verify=False,
177 )
170 - return {"data": response.json(), "success": True, "message": "Successfully retrieved data"}
178 + if response.status_code != 200 and response.status_code != 204:
179 + raise HTTPException(
180 + status_code=404,
181 + detail=f"Failed to send DELETE request to {endpoint} with error: {response.json()['message']}",
182 + )
183 + return {"data": "No content returned", "success": True, "message": "Successfully deleted data"}
184 + except HTTPException as e:
185 + raise e
186 except Exception as e:
187 logger.error(f"Failed to send DELETE request to {endpoint} with error: {e}")
188 return {"success": False, "message": f"Failed to send DELETE request to {endpoint} with error: {e}"}
@@ -201,7 +216,14 @@ def send_put_request(endpoint: str, data: Optional[Dict[str, Any]] = None, conne
216 json=data,
217 verify=False,
218 )
219 + if response.status_code != 200:
220 + raise HTTPException(
221 + status_code=404,
222 + detail=f"Failed to send PUT request to {endpoint} with error: {response.json()['message']}",
223 + )
224 return {"data": response.json(), "success": True, "message": "Successfully retrieved data"}
225 + except HTTPException as e:
226 + raise e
227 except Exception as e:
228 logger.error(f"Failed to send PUT request to {endpoint} with error: {e}")
229 return {"success": False, "message": f"Failed to send PUT request to {endpoint} with error: {e}"}
backend/app/connectors/routes.py
+17 -7
@@ -50,7 +50,7 @@ async def get_connectors() -> ConnectorListResponse:
50 "/{connector_id}",
51 response_model=ConnectorListResponse,
52 description="Fetch a specific connector",
53 - dependencies=[Security(AuthHandler().require_any_scope("admin", "test"))],
53 + dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))],
54 )
55 async def get_connector(connector_id: int) -> Union[ConnectorResponse, HTTPException]:
56 """
@@ -78,6 +78,7 @@ async def get_connector(connector_id: int) -> Union[ConnectorResponse, HTTPExcep
78 "/verify/{connector_id}",
79 response_model=VerifyConnectorResponse,
80 description="Verify a connector. Makes an API call to the connector to verify it is working.",
81 + dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))],
82 )
83 async def verify_connector(connector_id: int) -> Union[VerifyConnectorResponse, HTTPException]:
84 """
@@ -95,14 +96,19 @@ async def verify_connector(connector_id: int) -> Union[VerifyConnectorResponse,
96 HTTPException: An exception with a 404 status code is raised if the connector is not found.
97 """
98 connector = ConnectorServices.verify_connector_by_id(connector_id)
98 - if connector is not None:
99 - logger.info(f"Connector verified successfully: {connector}")
100 - return connector
101 - else:
99 + if connector is None:
100 raise HTTPException(status_code=404, detail=f"No connector found for ID: {connector_id}".format(connector_id=connector_id))
101 + if connector["connectionSuccessful"] is False:
102 + raise HTTPException(status_code=500, detail=f"Failed to verify connector: {connector['message']}")
103 + return connector
104
105
105 -@connector_router.put("/{connector_id}", response_model=ConnectorListResponse, description="Update a connector")
106 +@connector_router.put(
107 + "/{connector_id}",
108 + response_model=ConnectorListResponse,
109 + description="Update a connector",
110 + dependencies=[Security(AuthHandler().get_current_user, scopes=["admin"])],
111 +)
112 async def update_connector(connector_id: int, connector: UpdateConnector) -> ConnectorListResponse:
113 """
114 Update a connector by its ID.
@@ -126,7 +132,11 @@ async def update_connector(connector_id: int, connector: UpdateConnector) -> Con
132 raise HTTPException(status_code=404, detail=f"No connector found for ID: {connector_id}".format(connector_id=connector_id))
133
134
129 -@connector_router.post("/upload/{connector_id}", description="Upload a YAML file for a specific connector")
135 +@connector_router.post(
136 + "/upload/{connector_id}",
137 + description="Upload a YAML file for a specific connector",
138 + dependencies=[Security(AuthHandler().get_current_user, scopes=["admin"])],
139 +)
140 async def upload_yaml_file(connector_id: int, file: UploadFile = File(...)) -> dict:
141 """
142 Upload a YAML file for a specific connector ID.
backend/app/connectors/services.py
+1 -1
@@ -256,7 +256,7 @@ class ConnectorServices:
256 return connector_response
257 except Exception as e:
258 logger.exception(f"Failed to update connector: {e}")
259 - return None
259 + return Exception(f"Failed to update connector: {e}")
260
261 @staticmethod
262 def allowed_file(filename):
backend/app/connectors/velociraptor/routes/artifacts.py
+28 -4
@@ -3,8 +3,10 @@ from typing import List
3 from fastapi import APIRouter
4 from fastapi import Depends
5 from fastapi import HTTPException
6 +from fastapi import Security
7 from loguru import logger
8
9 +from app.auth.utils import AuthHandler
10 from app.connectors.velociraptor.schema.artifacts import ArtifactsResponse
11 from app.connectors.velociraptor.schema.artifacts import CollectArtifactBody
12 from app.connectors.velociraptor.schema.artifacts import CollectArtifactResponse
@@ -67,7 +69,12 @@ def get_velociraptor_id(hostname: str) -> str:
69 return velociraptor_id
70
71
70 -@velociraptor_artifacts_router.get("", response_model=ArtifactsResponse, description="Get all artifacts")
72 +@velociraptor_artifacts_router.get(
73 + "",
74 + response_model=ArtifactsResponse,
75 + description="Get all artifacts",
76 + dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))],
77 +)
78 async def get_all_artifacts() -> ArtifactsResponse:
79 logger.info("Fetching all artifacts")
80 return get_artifacts()
@@ -77,6 +84,7 @@ async def get_all_artifacts() -> ArtifactsResponse:
84 "/{os_prefix}",
85 response_model=ArtifactsResponse,
86 description="Get all artifacts for a specific OS prefix",
87 + dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))],
88 )
89 async def get_all_artifacts_for_os_prefix(os_prefix: str = Depends(verify_os_prefix_exists)) -> ArtifactsResponse:
90 logger.info(f"Fetching all artifacts for OS prefix {os_prefix}")
@@ -90,6 +98,7 @@ async def get_all_artifacts_for_os_prefix(os_prefix: str = Depends(verify_os_pre
98 "/hostname/{hostname}",
99 response_model=ArtifactsResponse,
100 description="Get all artifacts for a specific host's OS prefix",
101 + dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))],
102 )
103 async def get_all_artifacts_for_hostname(hostname: str) -> ArtifactsResponse:
104 logger.info(f"Fetching all artifacts for hostname {hostname}")
@@ -107,7 +116,12 @@ async def get_all_artifacts_for_hostname(hostname: str) -> ArtifactsResponse:
116 )
117
118
110 -@velociraptor_artifacts_router.post("/collect", response_model=CollectArtifactResponse, description="Run an analyzer")
119 +@velociraptor_artifacts_router.post(
120 + "/collect",
121 + response_model=CollectArtifactResponse,
122 + description="Run an analyzer",
123 + dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))],
124 +)
125 async def collect_artifact(collect_artifact_body: CollectArtifactBody) -> CollectArtifactResponse:
126 logger.info(f"Received request to collect artifact {collect_artifact_body}")
127 # Check that provided artifact name applies for the provided hostname and use the `get_all_artifacts_for_hostname` function to get the list of artifacts
@@ -124,7 +138,12 @@ async def collect_artifact(collect_artifact_body: CollectArtifactBody) -> Collec
138 return run_artifact_collection(collect_artifact_body)
139
140
127 -@velociraptor_artifacts_router.post("/command", response_model=RunCommandResponse, description="Run a remote command")
141 +@velociraptor_artifacts_router.post(
142 + "/command",
143 + response_model=RunCommandResponse,
144 + description="Run a remote command",
145 + dependencies=[Security(AuthHandler().get_current_user, scopes=["admin"])],
146 +)
147 async def run_command(run_command_body: RunCommandBody) -> RunCommandResponse:
148 logger.info(f"Received request to run command {run_command_body}")
149 result = await get_all_artifacts_for_hostname(run_command_body.hostname)
@@ -140,7 +159,12 @@ async def run_command(run_command_body: RunCommandBody) -> RunCommandResponse:
159 return run_remote_command(run_command_body)
160
161
143 -@velociraptor_artifacts_router.post("/quarantine", response_model=QuarantineResponse, description="Quarantine a host")
162 +@velociraptor_artifacts_router.post(
163 + "/quarantine",
164 + response_model=QuarantineResponse,
165 + description="Quarantine a host",
166 + dependencies=[Security(AuthHandler().get_current_user, scopes=["admin"])],
167 +)
168 async def quarantine(quarantine_body: QuarantineBody) -> QuarantineResponse:
169 logger.info(f"Received request to quarantine host {quarantine_body}")
170 result = await get_all_artifacts_for_hostname(quarantine_body.hostname)
backend/app/connectors/velociraptor/services/artifacts.py
+12 -45
@@ -63,11 +63,15 @@ def get_artifacts() -> ArtifactsResponse:
63 logger.info("Fetching artifacts from Velociraptor")
64 query = create_query("SELECT name,description FROM artifact_definitions()")
65 all_artifacts = universal_service.execute_query(query)
66 - if all_artifacts["success"]:
67 - artifacts = [Artifacts(**artifact) for artifact in all_artifacts["results"]]
68 - return ArtifactsResponse(success=True, message="All artifacts retrieved", artifacts=artifacts)
69 - else:
70 - raise HTTPException(status_code=500, detail=f"Failed to get all artifacts: {all_artifacts['message']}")
66 + try:
67 + if all_artifacts["success"]:
68 + artifacts = [Artifacts(**artifact) for artifact in all_artifacts["results"]]
69 + return ArtifactsResponse(success=True, message="All artifacts retrieved", artifacts=artifacts)
70 + else:
71 + raise HTTPException(status_code=500, detail=f"Failed to get all artifacts: {all_artifacts['message']}")
72 + except Exception as err:
73 + logger.error(f"Failed to get all artifacts: {err}")
74 + raise HTTPException(status_code=500, detail=f"Failed to get all artifacts: {err}")
75
76
77 def run_artifact_collection(collect_artifact_body: CollectArtifactBody) -> CollectArtifactResponse:
@@ -104,6 +108,9 @@ def run_artifact_collection(collect_artifact_body: CollectArtifactBody) -> Colle
108 logger.info(f"Successfully read collection results on {results}")
109
110 return CollectArtifactResponse(success=results["success"], message=results["message"], results=results["results"])
111 + except HTTPException as he: # Catch HTTPException separately to propagate the original message
112 + logger.error(f"HTTPException while running artifact collection on {collect_artifact_body}: {he.detail}")
113 + raise he
114 except Exception as err:
115 logger.error(f"Failed to run artifact collection on {collect_artifact_body}: {err}")
116 raise HTTPException(status_code=500, detail=f"Failed to run artifact collection on {collect_artifact_body}: {err}")
@@ -195,43 +202,3 @@ def quarantine_host(quarantine_body: QuarantineBody) -> QuarantineResponse:
202 except Exception as err:
203 logger.error(f"Failed to run artifact collection on {quarantine_body}: {err}")
204 raise HTTPException(status_code=500, detail=f"Failed to run artifact collection on {quarantine_body}: {err}")
198 -
199 -
200 -######################## KEEP
201 -class ArtifactsService:
202 - def delete_client(self, client_id: str) -> dict:
203 - """
204 - Delete a client from Velociraptor.
205 -
206 - Args:
207 - client_id (str): The ID of the client.
208 -
209 - Returns:
210 - dict: A dictionary with the success status and a message.
211 - """
212 - try:
213 - query = self._create_query(
214 - f"SELECT collect_client(client_id='server', artifacts=['Server.Utils.DeleteClient'], env=dict(ClientIdList='{client_id}',ReallyDoIt='Y')) "
215 - "FROM scope()",
216 - )
217 -
218 - flow = self.universal_service.execute_query(query)
219 - logger.info(f"Successfully ran artifact collection on {flow}")
220 -
221 - # artifact_key = f"collect_client(client_id='server', artifacts=['Server.Utils.DeleteClient'], env=dict(ClientIdList='{client_id}',ReallyDoIt='Y'))"
222 - flow_id = flow["results"][0][query]["flow_id"]
223 - logger.info(f"Extracted flow_id: {flow_id}")
224 -
225 - completed = self.universal_service.watch_flow_completion(flow_id)
226 - logger.info(f"Successfully watched flow completion on {completed}")
227 -
228 - return {
229 - "message": f"Successfully deleted client {client_id}",
230 - "success": True,
231 - }
232 - except Exception as err:
233 - logger.error(f"Failed to delete client {client_id}: {err}")
234 - return {
235 - "message": f"Failed to delete client {client_id}",
236 - "success": False,
237 - }
backend/app/connectors/velociraptor/utils/universal.py
+21 -6
@@ -1,10 +1,14 @@
1 +import asyncio
2 import json
3 +from concurrent.futures import ThreadPoolExecutor
4 +from concurrent.futures import TimeoutError
5 from datetime import datetime
6 from typing import Any
7 from typing import Dict
8
9 import grpc
10 import pyvelociraptor
11 +from fastapi import HTTPException
12 from loguru import logger
13 from pyvelociraptor import api_pb2
14 from pyvelociraptor import api_pb2_grpc
@@ -151,7 +155,7 @@ class UniversalService:
155 client_request = self.create_vql_request(vql)
156 try:
157 results = []
154 - for response in self.stub.Query(client_request):
158 + for response in self.stub.Query(client_request, timeout=30):
159 if response.Response:
160 results += json.loads(response.Response)
161 return {
@@ -159,12 +163,19 @@ class UniversalService:
163 "message": "Successfully executed query",
164 "results": results,
165 }
166 + except grpc.RpcError as e: # Catch gRPC-specific errors
167 + if e.code() == grpc.StatusCode.DEADLINE_EXCEEDED:
168 + logger.error("Failed to execute query due to timeout.")
169 + raise HTTPException(
170 + status_code=500,
171 + detail="Failed to execute query due to timeout. Make sure the Velocraptor server has stopped this artifact collection.",
172 + )
173 + else:
174 + logger.error(f"Failed to execute query: {e}")
175 + raise HTTPException(status_code=500, detail=f"Failed to execute query: {e.details()}")
176 except Exception as e:
177 logger.error(f"Failed to execute query: {e}")
164 - return {
165 - "success": False,
166 - "message": f"Failed to execute query: {e}",
167 - }
178 + raise HTTPException(status_code=500, detail=f"Failed to execute query: {e}")
179
180 def watch_flow_completion(self, flow_id: str):
181 """
@@ -177,6 +188,7 @@ class UniversalService:
188 dict: A dictionary with the success status and a message.
189 """
190 vql = f"SELECT * FROM watch_monitoring(artifact='System.Flow.Completion') WHERE FlowId='{flow_id}' LIMIT 1"
191 + logger.info(f"Watching flow {flow_id} for completion")
192 return self.execute_query(vql)
193
194 def read_collection_results(
@@ -268,7 +280,10 @@ class UniversalService:
280 Returns:
281 str: The server version.
282 """
271 - return self.execute_query(vql)["results"][0]["version"]["version"]
283 + try:
284 + return self.execute_query(vql)["results"][0]["version"]["version"]
285 + except IndexError as e:
286 + raise HTTPException(status_code=500, detail=f"Failed to get server version: {e}")
287
288 def _is_offline(self, last_seen_at: float):
289 """
backend/app/connectors/wazuh_indexer/routes/alerts.py
+34 -5
@@ -1,10 +1,13 @@
1 from typing import List
2
3 from fastapi import APIRouter
4 +from fastapi import BackgroundTasks
5 from fastapi import Depends
6 from fastapi import HTTPException
7 +from fastapi import Security
8 from loguru import logger
9
10 +from app.auth.utils import AuthHandler
11 from app.connectors.wazuh_indexer.schema.alerts import AlertsByHostResponse
12 from app.connectors.wazuh_indexer.schema.alerts import AlertsByRulePerHostResponse
13 from app.connectors.wazuh_indexer.schema.alerts import AlertsByRuleResponse
@@ -46,19 +49,34 @@ def verify_index_name(index_alerts_search_body: IndexAlertsSearchBody) -> IndexA
49 return index_alerts_search_body
50
51
49 -@wazuh_indexer_alerts_router.post("", response_model=AlertsSearchResponse, description="Get all alerts")
52 +@wazuh_indexer_alerts_router.post(
53 + "",
54 + response_model=AlertsSearchResponse,
55 + description="Get all alerts",
56 + dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))],
57 +)
58 async def get_all_alerts(alerts_search_body: AlertsSearchBody) -> AlertsSearchResponse:
59 logger.info("Fetching all alerts")
60 return get_alerts(alerts_search_body)
61
62
55 -@wazuh_indexer_alerts_router.post("/host", response_model=HostAlertsSearchResponse, description="Get all alerts for a host")
63 +@wazuh_indexer_alerts_router.post(
64 + "/host",
65 + response_model=HostAlertsSearchResponse,
66 + description="Get all alerts for a host",
67 + dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))],
68 +)
69 async def get_all_alerts_for_host(host_alerts_search_body: HostAlertsSearchBody) -> HostAlertsSearchResponse:
70 logger.info(f"Fetching all alerts for host {host_alerts_search_body.agent_name}")
71 return get_host_alerts(host_alerts_search_body)
72
73
61 -@wazuh_indexer_alerts_router.post("/index", response_model=IndexAlertsSearchResponse, description="Get all alerts for an index")
74 +@wazuh_indexer_alerts_router.post(
75 + "/index",
76 + response_model=IndexAlertsSearchResponse,
77 + description="Get all alerts for an index",
78 + dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))],
79 +)
80 async def get_all_alerts_for_index(
81 index_alerts_search_body: IndexAlertsSearchBody = Depends(verify_index_name),
82 ) -> IndexAlertsSearchResponse:
@@ -66,13 +84,23 @@ async def get_all_alerts_for_index(
84 return get_index_alerts(index_alerts_search_body)
85
86
69 -@wazuh_indexer_alerts_router.post("/hosts/all", response_model=AlertsByHostResponse, description="Get number of all alerts for all hosts")
87 +@wazuh_indexer_alerts_router.post(
88 + "/hosts/all",
89 + response_model=AlertsByHostResponse,
90 + description="Get number of all alerts for all hosts",
91 + dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))],
92 +)
93 async def get_all_alerts_by_host(alerts_search_body: AlertsSearchBody) -> AlertsByHostResponse:
94 logger.info("Fetching number of all alerts for all hosts")
95 return get_alerts_by_host(alerts_search_body)
96
97
75 -@wazuh_indexer_alerts_router.post("/rules/all", response_model=AlertsByRuleResponse, description="Get number of all alerts for all rules")
98 +@wazuh_indexer_alerts_router.post(
99 + "/rules/all",
100 + response_model=AlertsByRuleResponse,
101 + description="Get number of all alerts for all rules",
102 + dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))],
103 +)
104 async def get_all_alerts_by_rule(alerts_search_body: AlertsSearchBody) -> AlertsByRuleResponse:
105 logger.info("Fetching number of all alerts for all rules")
106 return get_alerts_by_rule(alerts_search_body)
@@ -82,6 +110,7 @@ async def get_all_alerts_by_rule(alerts_search_body: AlertsSearchBody) -> Alerts
110 "/rules/hosts/all",
111 response_model=AlertsByRulePerHostResponse,
112 description="Get number of all alerts for all rules per host",
113 + dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))],
114 )
115 async def get_all_alerts_by_rule_per_host(alerts_search_body: AlertsSearchBody) -> AlertsByRulePerHostResponse:
116 """
backend/app/connectors/wazuh_indexer/routes/monitoring.py
+27 -5
@@ -2,7 +2,9 @@ from typing import Union
2
3 from fastapi import APIRouter
4 from fastapi import HTTPException
5 +from fastapi import Security
6
7 +from app.auth.utils import AuthHandler
8 from app.connectors.wazuh_indexer.schema.monitoring import ClusterHealthResponse
9 from app.connectors.wazuh_indexer.schema.monitoring import IndicesStatsResponse
10 from app.connectors.wazuh_indexer.schema.monitoring import NodeAllocationResponse
@@ -17,7 +19,12 @@ from app.connectors.wazuh_indexer.services.monitoring import shards
19 wazuh_indexer_router = APIRouter()
20
21
20 -@wazuh_indexer_router.get("/health", response_model=ClusterHealthResponse, description="Fetch Wazuh Indexer cluster health")
22 +@wazuh_indexer_router.get(
23 + "/health",
24 + response_model=ClusterHealthResponse,
25 + description="Fetch Wazuh Indexer cluster health",
26 + dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))],
27 +)
28 async def get_cluster_health() -> Union[ClusterHealthResponse, HTTPException]:
29 """
30 Fetch Wazuh Indexer cluster health.
@@ -34,10 +41,15 @@ async def get_cluster_health() -> Union[ClusterHealthResponse, HTTPException]:
41 if cluster_health is not None:
42 return cluster_health
43 else:
37 - raise HTTPException(status_code=500, detail="Failed to retrieve cluster health.")
44 + raise Exception("Failed to retrieve cluster health.")
45
46
40 -@wazuh_indexer_router.get("/allocation", response_model=NodeAllocationResponse, description="Fetch Wazuh Indexer node allocation")
47 +@wazuh_indexer_router.get(
48 + "/allocation",
49 + response_model=NodeAllocationResponse,
50 + description="Fetch Wazuh Indexer node allocation",
51 + dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))],
52 +)
53 async def get_node_allocation() -> Union[NodeAllocationResponse, HTTPException]:
54 """
55 Fetch Wazuh Indexer node allocation.
@@ -57,7 +69,12 @@ async def get_node_allocation() -> Union[NodeAllocationResponse, HTTPException]:
69 raise HTTPException(status_code=500, detail="Failed to retrieve node allocation.")
70
71
60 -@wazuh_indexer_router.get("/indices", response_model=IndicesStatsResponse, description="Fetch Wazuh Indexer indices stats")
72 +@wazuh_indexer_router.get(
73 + "/indices",
74 + response_model=IndicesStatsResponse,
75 + description="Fetch Wazuh Indexer indices stats",
76 + dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))],
77 +)
78 async def get_indices_stats() -> Union[IndicesStatsResponse, HTTPException]:
79 """
80 Fetch Wazuh Indexer indices stats.
@@ -77,7 +94,12 @@ async def get_indices_stats() -> Union[IndicesStatsResponse, HTTPException]:
94 raise HTTPException(status_code=500, detail="Failed to retrieve indices stats.")
95
96
80 -@wazuh_indexer_router.get("/shards", response_model=ShardsResponse, description="Fetch Wazuh Indexer shards")
97 +@wazuh_indexer_router.get(
98 + "/shards",
99 + response_model=ShardsResponse,
100 + description="Fetch Wazuh Indexer shards",
101 + dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))],
102 +)
103 async def get_shards() -> Union[ShardsResponse, HTTPException]:
104 """
105 Fetch Wazuh Indexer shards.
backend/app/connectors/wazuh_indexer/schema/alerts.py
+8
@@ -1,3 +1,4 @@
1 +from enum import Enum
2 from typing import Any
3 from typing import Dict
4 from typing import List
@@ -97,3 +98,10 @@ class AlertsByRulePerHostResponse(BaseModel):
98 alerts_by_rule_per_host: List[AlertsByRulePerHost]
99 success: bool
100 message: str
101 +
102 +
103 +############# ! PASSABLE MESSAGES FROM ES CLIENT ! #############
104 +class SkippableWazuhIndexerClientErrors(Enum):
105 + NO_MAPPING_FOR_TIMESTAMP = "No mapping found for [timestamp_utc] in order to sort on"
106 + # Add other error messages here, for example:
107 + # ANOTHER_ERROR = "Another specific error message"
backend/app/connectors/wazuh_indexer/services/alerts.py
+17 -20
@@ -19,26 +19,11 @@ from app.connectors.wazuh_indexer.schema.alerts import HostAlertsSearchBody
19 from app.connectors.wazuh_indexer.schema.alerts import HostAlertsSearchResponse
20 from app.connectors.wazuh_indexer.schema.alerts import IndexAlertsSearchBody
21 from app.connectors.wazuh_indexer.schema.alerts import IndexAlertsSearchResponse
22 +from app.connectors.wazuh_indexer.schema.alerts import SkippableWazuhIndexerClientErrors
23 from app.connectors.wazuh_indexer.utils.universal import AlertsQueryBuilder
24 from app.connectors.wazuh_indexer.utils.universal import collect_indices
25 from app.connectors.wazuh_indexer.utils.universal import create_wazuh_indexer_client
26
26 -# def collect_and_aggregate_alerts(field_name: str, search_body: AlertsSearchBody) -> Dict[str, int]:
27 -# indices = collect_indices()
28 -# aggregated_alerts_dict = {}
29 -
30 -# for index_name in indices.indices_list:
31 -# try:
32 -# alerts_response = collect_alerts_generic(index_name, body=search_body)
33 -# if alerts_response.success:
34 -# for alert in alerts_response.alerts:
35 -# field_value = alert["_source"][field_name]
36 -# aggregated_alerts_dict[field_value] = aggregated_alerts_dict.get(field_value, 0) + 1
37 -# except HTTPException as e:
38 -# logger.warning(f"An error occurred while processing index {index_name}: {e.detail}")
39 -
40 -# return aggregated_alerts_dict
41 -
27
28 def collect_and_aggregate_alerts(field_names: List[str], search_body: AlertsSearchBody) -> Dict[str, int]:
29 indices = collect_indices()
@@ -52,7 +37,13 @@ def collect_and_aggregate_alerts(field_names: List[str], search_body: AlertsSear
37 composite_key = tuple(alert["_source"][field] for field in field_names)
38 aggregated_alerts_dict[composite_key] = aggregated_alerts_dict.get(composite_key, 0) + 1
39 except HTTPException as e:
55 - logger.warning(f"An error occurred while processing index {index_name}: {e.detail}")
40 + detail_str = str(e.detail) # Convert to string to make sure it's comparable
41 + if any(err.value in detail_str for err in SkippableWazuhIndexerClientErrors):
42 + logger.warning(f"Skipping index {index_name} due to specific error: {e.detail}")
43 + continue # Skip this index and continue with the next one
44 + else:
45 + logger.warning(f"An error occurred while processing index {index_name}: {e.detail}")
46 + raise HTTPException(status_code=500, detail=f"An error occurred while processing index {index_name}: {e.detail}")
47
48 return aggregated_alerts_dict
49
@@ -76,8 +67,8 @@ def collect_alerts_generic(index_name: str, body: AlertsSearchBody, is_host_spec
67 logger.info(f"Alerts collected: {alerts_list}")
68 return CollectAlertsResponse(alerts=alerts_list, success=True, message="Alerts collected successfully")
69 except Exception as e:
79 - logger.debug(f"Failed to collect alerts: {e}")
80 - return CollectAlertsResponse(alerts=[], success=False, message=f"Failed to collect alerts: {e}")
70 + logger.warning(f"An error occurred while collecting alerts: {e}")
71 + raise HTTPException(status_code=500, detail=f"An error occurred while collecting alerts: {e}")
72
73
74 def get_alerts_generic(search_body: Type[AlertsSearchBody], is_host_specific: bool = False, index_name: Optional[str] = None):
@@ -98,7 +89,13 @@ def get_alerts_generic(search_body: Type[AlertsSearchBody], is_host_specific: bo
89 },
90 )
91 except HTTPException as e:
101 - logger.warning(f"An error occurred while processing index {index_name}: {e.detail}")
92 + detail_str = str(e.detail) # Convert to string to make sure it's comparable
93 + if any(err.value in detail_str for err in SkippableWazuhIndexerClientErrors):
94 + logger.warning(f"Skipping index {index_name} due to specific error: {e.detail}")
95 + continue # Skip this index and continue with the next one
96 + else:
97 + logger.warning(f"An error occurred while processing index {index_name}: {e.detail}")
98 + raise HTTPException(status_code=500, detail=f"An error occurred while processing index {index_name}: {e.detail}")
99
100 if len(alerts_summary) == 0:
101 message = "No alerts found"
backend/app/connectors/wazuh_indexer/services/monitoring.py
+9 -7
@@ -1,6 +1,7 @@
1 from typing import Dict
2 from typing import Union
3
4 +from fastapi import HTTPException
5 from loguru import logger
6
7 from app.connectors.wazuh_indexer.schema.monitoring import ClusterHealth
@@ -38,8 +39,8 @@ def cluster_healthcheck() -> Union[ClusterHealthResponse, Dict[str, str]]:
39 message="Successfully collected Wazuh Indexer cluster health",
40 )
41 except Exception as e:
41 - logger.error(f"Cluster health check failed with error: {e}")
42 - return {"success": False, "message": f"Cluster health check failed with error: {e}"}
42 + e = f"Cluster health check failed with error: {e}"
43 + raise Exception(str(e))
44
45
46 def node_allocation() -> Union[NodeAllocationResponse, Dict[str, bool]]:
@@ -68,8 +69,8 @@ def node_allocation() -> Union[NodeAllocationResponse, Dict[str, bool]]:
69 message="Successfully collected Wazuh Indexer node allocation",
70 )
71 except Exception as e:
71 - logger.error(f"Node allocation check failed with error: {e}")
72 - return {"success": False, "message": f"Node allocation check failed with error: {e}"}
72 + e = f"Node allocation check failed with error: {e}"
73 + raise Exception(str(e))
74
75
76 def indices_stats() -> Union[IndicesStatsResponse, Dict[str, str]]:
@@ -97,8 +98,8 @@ def indices_stats() -> Union[IndicesStatsResponse, Dict[str, str]]:
98 message="Successfully collected Wazuh Indexer indices stats",
99 )
100 except Exception as e:
100 - logger.error(f"Indices stats check failed with error: {e}")
101 - return {"success": False, "message": f"Indices stats check failed with error: {e}"}
101 + e = f"Indices stats check failed with error: {e}"
102 + raise Exception(str(e))
103
104
105 def shards() -> Union[ShardsResponse, Dict[str, str]]:
@@ -123,4 +124,5 @@ def shards() -> Union[ShardsResponse, Dict[str, str]]:
124 return ShardsResponse(shards=shard_models, success=True, message="Successfully collected Wazuh Indexer shards")
125 except Exception as e:
126 logger.error(f"Shards check failed with error: {e}")
126 - return {"success": False, "message": f"Shards check failed with error: {e}"}
127 + e = f"Shards check failed with error: {e}"
128 + raise Exception(str(e))
backend/app/connectors/wazuh_indexer/utils/universal.py
+14 -11
@@ -6,6 +6,7 @@ from typing import Iterable
6 from typing import Tuple
7
8 from elasticsearch7 import Elasticsearch
9 +from fastapi import HTTPException
10 from loguru import logger
11
12 from app.connectors.utils import get_connector_info_from_db
@@ -62,16 +63,18 @@ def create_wazuh_indexer_client(connector_name: str) -> Elasticsearch:
63 """
64 attributes = get_connector_info_from_db(connector_name)
65 if attributes is None:
65 - logger.error("No Wazuh Indexer connector found in the database")
66 - return None
67 - return Elasticsearch(
68 - [attributes["connector_url"]],
69 - http_auth=(attributes["connector_username"], attributes["connector_password"]),
70 - verify_certs=False,
71 - timeout=15,
72 - max_retries=10,
73 - retry_on_timeout=False,
74 - )
66 + raise HTTPException(status_code=500, detail=f"No {connector_name} connector found in the database")
67 + try:
68 + return Elasticsearch(
69 + [attributes["connector_url"]],
70 + http_auth=(attributes["connector_username"], attributes["connector_password"]),
71 + verify_certs=False,
72 + timeout=15,
73 + max_retries=10,
74 + retry_on_timeout=False,
75 + )
76 + except Exception as e:
77 + raise HTTPException(status_code=500, detail=f"Failed to create Elasticsearch client: {e}")
78
79
80 def format_node_allocation(node_allocation):
@@ -161,7 +164,7 @@ def collect_indices() -> Indices:
164 return Indices(indices_list=indices_list, success=True, message="Indices collected successfully")
165 except Exception as e:
166 logger.error(f"Failed to collect indices: {e}")
164 - return Indices(message="Failed to collect indices", success=False)
167 + raise HTTPException(status_code=500, detail=f"Failed to collect indices: {e}")
168
169
170 class AlertsQueryBuilder:
backend/app/connectors/wazuh_manager/routes/rules.py
+13 -12
@@ -46,7 +46,7 @@ async def get_disabled_rules() -> AllDisabledRuleResponse:
46 )
47 async def disable_wazuh_rule(rule: RuleDisable, username: str = Depends(auth_handler.get_current_user)) -> RuleDisableResponse:
48 if query_disabled_rule(rule.rule_id):
49 - raise HTTPException(status_code=404, detail="Rule is already disabled")
49 + raise HTTPException(status_code=500, detail="Rule is already disabled")
50
51 rule_disabled = disable_rule(rule)
52 if rule_disabled:
@@ -87,14 +87,15 @@ async def enable_wazuh_rule(rule: RuleEnable) -> RuleEnableResponse:
87 raise HTTPException(status_code=404, detail="Was not able to enable rule")
88
89
90 -@wazuh_manager_router.post(
91 - "/rule/exclude",
92 - response_model=RuleExcludeResponse,
93 - description="Retrieve recommended exclusion for a Wazuh Rule",
94 -)
95 -async def exclude_wazuh_rule(rule: RuleExclude) -> RuleExcludeResponse:
96 - recommended_exclusion = exclude_rule(rule)
97 - if recommended_exclusion:
98 - return recommended_exclusion
99 - else:
100 - raise HTTPException(status_code=404, detail="Was not able to exclude rule")
90 +# ! TODO: Implement this endpoint
91 +# @wazuh_manager_router.post(
92 +# "/rule/exclude",
93 +# response_model=RuleExcludeResponse,
94 +# description="Retrieve recommended exclusion for a Wazuh Rule",
95 +# )
96 +# async def exclude_wazuh_rule(rule: RuleExclude) -> RuleExcludeResponse:
97 +# recommended_exclusion = exclude_rule(rule)
98 +# if recommended_exclusion:
99 +# return recommended_exclusion
100 +# else:
101 +# raise HTTPException(status_code=404, detail="Was not able to exclude rule")
backend/app/connectors/wazuh_manager/services/rules.py
+35 -23
@@ -6,6 +6,7 @@ from typing import Union
6
7 import pcre2
8 import xmltodict
9 +from fastapi import HTTPException
10 from loguru import logger
11
12 from app.connectors.wazuh_manager.schema.rules import RuleDisable
@@ -23,43 +24,52 @@ def fetch_filename(rule_id: str) -> str:
24 endpoint = "rules"
25 params = {"rule_ids": rule_id}
26 filename_data = send_get_request(endpoint=endpoint, params=params)
26 - if not filename_data["success"]:
27 - raise ValueError(filename_data["message"])
27 + if filename_data["data"]["data"]["total_affected_items"] == 0:
28 + raise HTTPException(status_code=404, detail=f"Rule {rule_id} not found. Make sure the rule ID is correct within the Wazuh Manager.")
29 return filename_data["data"]["data"]["affected_items"][0]["filename"]
30
31
32 def fetch_file_content(filename: str) -> str:
33 endpoint = f"rules/files/{filename}"
34 file_content_data = send_get_request(endpoint=endpoint)
34 - if not file_content_data["success"]:
35 - raise ValueError(file_content_data["message"])
35 + if file_content_data["data"]["data"]["total_affected_items"] == 0:
36 + raise HTTPException(
37 + status_code=404,
38 + detail=f"File {filename} not found. Make sure the file name is correct within the Wazuh Manager.",
39 + )
40 return file_content_data["data"]["data"]["affected_items"][0]["group"]
41
42
43 def set_rule_level(file_content: Any, rule_id: str, new_level: str) -> Tuple[str, Any]:
44 previous_level = None
41 - if isinstance(file_content, dict):
42 - file_content = [file_content]
43 - for group_block in file_content:
44 - rule_block = group_block.get("rule", None)
45 - if rule_block:
46 - if isinstance(rule_block, dict):
47 - rule_block = [rule_block]
48 - for rule in rule_block:
49 - if rule["@id"] == rule_id:
50 - previous_level = rule["@level"]
51 - rule["@level"] = new_level
52 - break
45 + try:
46 + if isinstance(file_content, dict):
47 + file_content = [file_content]
48 + for group_block in file_content:
49 + rule_block = group_block.get("rule", None)
50 + if rule_block:
51 + if isinstance(rule_block, dict):
52 + rule_block = [rule_block]
53 + for rule in rule_block:
54 + if rule["@id"] == rule_id:
55 + previous_level = rule["@level"]
56 + rule["@level"] = new_level
57 + break
58 + except (KeyError, TypeError) as e:
59 + raise HTTPException(status_code=500, detail=f"Failed to set rule level: {e}")
60 return previous_level, file_content
61
62
63 def convert_to_xml(updated_file_content: Union[Dict[str, str], List[Dict[str, str]]]) -> str:
64 xml_content_list = []
58 - for group in updated_file_content:
59 - xml_dict = {"group": group}
60 - xml_content = xmltodict.unparse(xml_dict, pretty=True)
61 - xml_content = xml_content.replace('<?xml version="1.0" encoding="utf-8"?>', "")
62 - xml_content_list.append(xml_content)
65 + try:
66 + for group in updated_file_content:
67 + xml_dict = {"group": group}
68 + xml_content = xmltodict.unparse(xml_dict, pretty=True)
69 + xml_content = xml_content.replace('<?xml version="1.0" encoding="utf-8"?>', "")
70 + xml_content_list.append(xml_content)
71 + except Exception as e:
72 + raise HTTPException(status_code=500, detail=f"Failed to convert to XML: {e}")
73 xml_content = "\n".join(xml_content_list)
74 xml_content = xml_content.strip()
75 return xml_content
@@ -71,8 +81,10 @@ def upload_updated_rule(filename: str, xml_content: str):
81 data=xml_content,
82 params={"overwrite": "true"},
83 )
74 - if not response["success"]:
75 - raise ValueError(response["message"])
84 + logger.info(response)
85 + if response["data"]["data"]["total_affected_items"] == 0:
86 + raise HTTPException(status_code=500, detail=f"Failed to upload updated rule to Wazuh Manager.")
87 + return response
88
89
90 def process_rule(rule, rule_action_func, ResponseModel):
backend/app/db/all_models.py
+1
@@ -6,3 +6,4 @@ from app.connectors.wazuh_manager.models.rules import DisabledRule
6 from app.db.universal_models import Agents
7 from app.db.universal_models import Customers
8 from app.db.universal_models import CustomersMeta
9 +from app.db.universal_models import LogEntry
backend/app/db/db_session.py
+1 -1
@@ -3,5 +3,5 @@ from sqlmodel import create_engine
3
4 from settings import SQLALCHEMY_DATABASE_URI
5
6 -engine = create_engine(SQLALCHEMY_DATABASE_URI)
6 +engine = create_engine(SQLALCHEMY_DATABASE_URI, connect_args={"check_same_thread": False})
7 session = Session(bind=engine)
backend/app/db/universal_models.py
+13
@@ -120,3 +120,16 @@ class Agents(SQLModel, table=True):
120 self.velociraptor_last_seen = velociraptor_agent.client_last_seen_as_datetime
121 self.velociraptor_agent_version = velociraptor_agent.client_version
122 self.customer_code = customer_code
123 +
124 +
125 +class LogEntry(SQLModel, table=True):
126 + __tablename__ = "log_entries"
127 + id: Optional[int] = Field(primary_key=True)
128 + timestamp: datetime = Field(default=datetime.utcnow())
129 + event_type: str
130 + user_id: int = Field(default=None, nullable=True)
131 + route: str
132 + method: str
133 + status_code: int
134 + message: str
135 + additional_info: str = Field(default=None, nullable=True)
backend/app/integrations/alert_escalation/routes/general_alert.py
+8 -1
@@ -1,6 +1,8 @@
1 from fastapi import APIRouter
2 +from fastapi import Security
3 from loguru import logger
4
5 +from app.auth.utils import AuthHandler
6 from app.integrations.alert_escalation.schema.general_alert import CreateAlertRequest
7 from app.integrations.alert_escalation.schema.general_alert import CreateAlertResponse
8 from app.integrations.alert_escalation.services.general_alert import create_alert
@@ -8,7 +10,12 @@ from app.integrations.alert_escalation.services.general_alert import create_aler
10 integration_general_alerts_router = APIRouter()
11
12
11 -@integration_general_alerts_router.post("/create", response_model=CreateAlertResponse, description="Create an alert in IRIS")
13 +@integration_general_alerts_router.post(
14 + "/create",
15 + response_model=CreateAlertResponse,
16 + description="Create an alert in IRIS",
17 + dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))],
18 +)
19 async def create_alert_route(create_alert_request: CreateAlertRequest) -> CreateAlertResponse:
20 logger.info(f"Creating alert {create_alert_request.alert_id} in IRIS")
21 return create_alert(create_alert_request)
backend/app/integrations/alert_escalation/services/general_alert.py
+6 -2
@@ -119,5 +119,9 @@ def create_alert(alert: CreateAlertRequest) -> CreateAlertResponse:
119 iris_alert_payload = build_alert_payload(alert_details, agent_data, ioc_payload)
120 client, alert = initialize_client_and_alert("DFIR-IRIS")
121 result = fetch_and_validate_data(client, alert.add_alert, iris_alert_payload.to_dict())
122 - alert_id = result["data"]["alert_id"]
123 - return CreateAlertResponse(alert_id=alert_id, success=True, message=f"Alert {alert_id} created successfully")
122 + try:
123 + alert_id = result["data"]["alert_id"]
124 + return CreateAlertResponse(alert_id=alert_id, success=True, message=f"Alert {alert_id} created successfully")
125 + except Exception as e:
126 + logger.error(f"Failed to create alert {alert.alert_id}: {e}")
127 + raise HTTPException(status_code=500, detail=f"Failed to create alert for ID {alert.alert_id}: {e}")
backend/app/utils.py
+444
@@ -1,3 +1,447 @@
1 +from datetime import datetime
2 +from datetime import timedelta
3 +from enum import Enum
4 +from typing import List
5 +from typing import Optional
6 +from typing import Union
7 +
8 +from fastapi import APIRouter
9 +from fastapi import HTTPException
10 +from fastapi import Request
11 +from fastapi import Security
12 +from fastapi.exceptions import RequestValidationError
13 +from loguru import logger
14 +from pydantic import BaseModel
15 +from pydantic import Field
16 +from pydantic import validator
17 +
18 +from app.auth.services.universal import find_user
19 +from app.auth.utils import AuthHandler
20 +from app.db.db_session import Session
21 +from app.db.db_session import engine
22 +from app.db.universal_models import LogEntry
23 +
24 +
25 +################## ! 422 VALIDATION ERROR TYPES FOR PYDANTIC VALUE ERROR RESPONSE ! ##################
26 +class ErrorType(str, Enum):
27 + PASSWORD_REGEX = "value_error.str.regex"
28 + TIME_RANGE = "value_error.time_range"
29 + JSON_INVALID = "json_invalid"
30 + MIN_LENGTH = "value_error.any_str.min_length"
31 + MAX_LENGTH = "value_error.any_str.max_length"
32 + NOT_A_NUMBER = "value_error.number.not_a_number"
33 + TOO_SMALL = "value_error.number.too_small"
34 + TOO_LARGE = "value_error.number.too_large"
35 + INVALID_DATETIME = "value_error.datetime"
36 + INVALID_DATE = "value_error.date"
37 + MIN_ITEMS = "value_error.list.min_items"
38 + MAX_ITEMS = "value_error.list.max_items"
39 + UNIQUE = "value_error.list.unique"
40 + NONE_NOT_ALLOWED = "value_error.none.not_allowed"
41 + MISSING = "value_error.missing"
42 + GENERAL = "value_error"
43 + # Add other types as needed
44 +
45 +
46 +class ValidationErrorItem(BaseModel):
47 + field: str
48 + error_type: ErrorType
49 + message: str = None # Initialize as None or some default
50 +
51 + @validator("message", pre=True, always=True)
52 + def set_message(cls, value, values):
53 + error_type = values.get("error_type")
54 + logger.info(error_type)
55 +
56 + error_messages = {
57 + ErrorType.PASSWORD_REGEX: "Password must be at least 8 characters long and contain at least one uppercase letter, one lowercase letter, one number, and one special character.",
58 + ErrorType.TIME_RANGE: "Invalid time range. Use 'h' for hours, 'd' for days, and 'w' for weeks.",
59 + ErrorType.JSON_INVALID: "Invalid JSON. Please check your JSON syntax and try again.",
60 + ErrorType.MIN_LENGTH: "Value is shorter than minimum length.",
61 + ErrorType.MAX_LENGTH: "Value is longer than maximum length.",
62 + ErrorType.NOT_A_NUMBER: "Input is not a number.",
63 + ErrorType.TOO_SMALL: "Value is too small.",
64 + ErrorType.TOO_LARGE: "Value is too large.",
65 + ErrorType.INVALID_DATETIME: "Invalid datetime format.",
66 + ErrorType.INVALID_DATE: "Invalid date format.",
67 + ErrorType.MIN_ITEMS: "Number of items is less than minimum.",
68 + ErrorType.MAX_ITEMS: "Number of items is more than maximum.",
69 + ErrorType.UNIQUE: "Items are not unique.",
70 + ErrorType.NONE_NOT_ALLOWED: "None is not an allowed value.",
71 + ErrorType.MISSING: "Missing data for required field.",
72 + ErrorType.GENERAL: "Invalid value.",
73 + }
74 +
75 + return error_messages.get(error_type, value)
76 +
77 +
78 +class ValidationErrorResponse(BaseModel):
79 + message: str
80 + details: List[ValidationErrorItem]
81 +
82 +
83 +################## ! LOGGING TO `log_entry` table ! ##################
84 +########! MODELS !########
85 +class LogEntryModel(BaseModel):
86 + event_type: str = Field(..., example="Info", description="Event type")
87 + user_id: Optional[int] = Field(None, example=1, description="User ID")
88 + route: str = Field(..., example="/wazuh_indexer/health", description="Route")
89 + method: str = Field(..., example="GET", description="Method")
90 + status_code: int = Field(..., example=200, description="Status code")
91 + message: str = Field(..., example="Route accessed", description="Message")
92 + additional_info: Optional[str] = Field(None, example="Additional details here", description="Additional info")
93 +
94 +
95 +class LogRetrieveModel(LogEntryModel):
96 + timestamp: datetime = Field(..., example=datetime.now(), description="Timestamp")
97 +
98 +
99 +class LogsResponse(BaseModel):
100 + logs: List[LogRetrieveModel]
101 + success: bool
102 + message: str
103 +
104 +
105 +class EventType(str, Enum):
106 + INFO = "Info"
107 + ERROR = "Error"
108 + # Add other event types as needed
109 +
110 +
111 +class TimeRangeModel(BaseModel):
112 + time_range: Union[str, int] = Field("1d", description="Time range to fetch logs for, e.g., 1, 1h, 1d, 1w")
113 +
114 + @validator("time_range")
115 + def validate_time_range(cls, value):
116 + try:
117 + if isinstance(value, int):
118 + if value < 1 or value > 7:
119 + raise RequestValidationError(
120 + [{"loc": ("time_range",), "msg": "The integer part should be between 1 and 7.", "type": "value_error.time_range"}],
121 + )
122 + return f"{value}d" # convert integer to day representation
123 +
124 + elif isinstance(value, str):
125 + unit = value[-1]
126 + int_part = int(value[:-1])
127 +
128 + if unit not in ["h", "d", "w"]:
129 + raise RequestValidationError(
130 + [
131 + {
132 + "loc": ("time_range",),
133 + "msg": "Invalid unit. Use 'h' for hours, 'd' for days, and 'w' for weeks.",
134 + "type": "value_error.time_range",
135 + },
136 + ],
137 + )
138 +
139 + if int_part <= 0:
140 + raise RequestValidationError(
141 + [{"loc": ("time_range",), "msg": "The integer part should be greater than 0.", "type": "value_error.time_range"}],
142 + )
143 +
144 + if unit == "w" and int_part > 1:
145 + raise RequestValidationError(
146 + [{"loc": ("time_range",), "msg": "The maximum allowed time range is 1 week.", "type": "value_error.time_range"}],
147 + )
148 + return value
149 +
150 + else:
151 + raise RequestValidationError(
152 + [
153 + {
154 + "loc": ("time_range",),
155 + "msg": "Invalid type. Time range should be either an integer or a string.",
156 + "type": "value_error.time_range",
157 + },
158 + ],
159 + )
160 +
161 + except ValueError:
162 + raise RequestValidationError(
163 + [
164 + {
165 + "loc": ("time_range",),
166 + "msg": "Invalid format. Time range should be an integer followed by a unit (h, d, w).",
167 + "type": "value_error.time_range",
168 + },
169 + ],
170 + )
171 +
172 +
173 +#########! LOGGER CLASS !#########
174 +class Logger:
175 + def __init__(self, session, auth_handler: AuthHandler):
176 + self.session = session
177 + self.auth_handler = auth_handler
178 +
179 + async def get_user_id_from_request(self, request: Request):
180 + auth_header = request.headers.get("Authorization")
181 + if auth_header:
182 + token = auth_header.replace("Bearer ", "")
183 + username, _ = self.auth_handler.decode_token(token)
184 + user = find_user(username)
185 + if user:
186 + return user.id
187 + return None
188 +
189 + def insert_log_entry(self, log_entry_model: LogEntryModel):
190 + log_entry = LogEntry(**log_entry_model.dict())
191 + self.session.add(log_entry)
192 + self.session.commit()
193 +
194 + async def log_route_access(self, user_id, request: Request, response):
195 + log_entry_model = LogEntryModel(
196 + event_type="Info",
197 + user_id=user_id,
198 + route=str(request.url),
199 + method=request.method,
200 + status_code=response.status_code,
201 + message="Route accessed",
202 + )
203 + self.insert_log_entry(log_entry_model)
204 +
205 + async def log_error(self, user_id, request: Request, exception: Exception, additional_info: Optional[str] = None):
206 + log_entry_model = LogEntryModel(
207 + event_type="Error",
208 + user_id=user_id,
209 + route=str(request.url),
210 + method=request.method,
211 + status_code=500, # Internal Server Error
212 + message=str(exception),
213 + additional_info=additional_info,
214 + )
215 + self.insert_log_entry(log_entry_model)
216 +
217 + async def log_and_raise_http_error(self, user_id, request: Request, exception: Exception):
218 + await self.log_error(user_id, request, exception)
219 + raise HTTPException(status_code=500, detail="Internal Server Error")
220 +
221 + def fetch_all_logs(self):
222 + logs = self.session.query(LogEntry).all() # Replace LogEntry with your actual LogEntry model
223 + return logs
224 +
225 +
226 +################## ! RETRIEVE LOGS ROUTES ! ##################
227 +logs_router = APIRouter()
228 +
229 +
230 +@logs_router.get(
231 + "",
232 + response_model=LogsResponse,
233 + description="Fetch all logs",
234 + dependencies=[Security(AuthHandler().get_current_user, scopes=["admin"])],
235 +)
236 +async def get_logs() -> LogsResponse: # Update this line to use the new model
237 + """
238 + Fetch all logs from the database.
239 +
240 + This endpoint retrieves all the logs stored in the database and returns them
241 + along with a success status and message.
242 +
243 + Returns:
244 + LogsResponse: A Pydantic model containing a list of logs and additional metadata.
245 +
246 + Raises:
247 + HTTPException: An exception with a 404 status code is raised if no logs are found.
248 + """
249 + with Session(engine) as session:
250 + auth_handler_instance = AuthHandler() # Replace with your actual AuthHandler initialization
251 + logger_instance = Logger(session, auth_handler_instance)
252 +
253 + logs = logger_instance.fetch_all_logs()
254 + if logs:
255 + return LogsResponse(logs=logs, success=True, message="Logs fetched successfully")
256 + else:
257 + raise HTTPException(status_code=404, detail="No logs found")
258 +
259 +
260 +@logs_router.get(
261 + "/{user_id}",
262 + response_model=LogsResponse,
263 + description="Fetch logs by user ID",
264 + dependencies=[Security(AuthHandler().get_current_user, scopes=["admin"])],
265 +)
266 +async def get_logs_by_user_id(user_id: int) -> LogsResponse: # Update this line to use the new model
267 + """
268 + Fetch all logs from the database where the user_id matches the provided user_id.
269 +
270 + This endpoint retrieves all the logs stored in the database where the user_id matches the provided user_id
271 + and returns them along with a success status and message.
272 +
273 + Args:
274 + user_id (int): The user_id to filter logs by.
275 +
276 + Returns:
277 + LogsResponse: A Pydantic model containing a list of logs and additional metadata.
278 +
279 + Raises:
280 + HTTPException: An exception with a 404 status code is raised if no logs are found.
281 + """
282 + with Session(engine) as session:
283 + auth_handler_instance = AuthHandler()
284 + logger_instance = Logger(session, auth_handler_instance)
285 + logs = logger_instance.fetch_all_logs()
286 + if logs:
287 + logs = [log for log in logs if log.user_id == user_id]
288 + if logs != []:
289 + return LogsResponse(logs=logs, success=True, message="Logs fetched successfully")
290 + else:
291 + raise HTTPException(status_code=404, detail=f"No logs found for user ID: {user_id}".format(user_id=user_id))
292 + else:
293 + raise HTTPException(status_code=404, detail="No logs found")
294 +
295 +
296 +@logs_router.post(
297 + "/timerange",
298 + response_model=LogsResponse,
299 + description="Fetch logs by time range",
300 + dependencies=[Security(AuthHandler().get_current_user, scopes=["admin"])],
301 +)
302 +async def get_logs_by_time_range(time_range: TimeRangeModel) -> LogsResponse:
303 + """
304 + Fetch all logs from the database where the timestamp is within the provided time range.
305 +
306 + This endpoint retrieves all the logs stored in the database where the timestamp is within the provided time range
307 + and returns them along with a success status and message.
308 +
309 + Args:
310 + time_range (TimeRangeModel): The time range to filter logs by.
311 +
312 + Returns:
313 + LogsResponse: A Pydantic model containing a list of logs and additional metadata.
314 +
315 + Raises:
316 + HTTPException: An exception with a 404 status code is raised if no logs are found.
317 + """
318 + with Session(engine) as session:
319 + auth_handler_instance = AuthHandler()
320 + logger_instance = Logger(session, auth_handler_instance)
321 + logs = logger_instance.fetch_all_logs()
322 + if logs:
323 + logs = [log for log in logs if log.timestamp >= datetime.now() - timedelta(days=int(time_range.time_range[:-1]))]
324 + if logs != []:
325 + return LogsResponse(logs=logs, success=True, message="Logs fetched successfully")
326 + else:
327 + raise HTTPException(
328 + status_code=404,
329 + detail=f"No logs found for time range: {time_range.time_range}".format(time_range=time_range.time_range),
330 + )
331 + else:
332 + raise HTTPException(status_code=404, detail="No logs found")
333 +
334 +
335 +@logs_router.post(
336 + "/{event_type}",
337 + response_model=LogsResponse,
338 + description="Fetch logs by event type",
339 + dependencies=[Security(AuthHandler().get_current_user, scopes=["admin"])],
340 +)
341 +async def get_logs_by_event_type(event_type: EventType) -> LogsResponse: # Update this line to use the new model
342 + """
343 + Fetch all logs from the database where the event_type matches the provided event_type.
344 +
345 + This endpoint retrieves all the logs stored in the database where the event_type matches the provided event_type
346 + and returns them along with a success status and message.
347 +
348 + Args:
349 + event_type (EventType): The event_type to filter logs by.
350 +
351 + Returns:
352 + LogsResponse: A Pydantic model containing a list of logs and additional metadata.
353 +
354 + Raises:
355 + HTTPException: An exception with a 404 status code is raised if no logs are found.
356 + """
357 + with Session(engine) as session:
358 + auth_handler_instance = AuthHandler()
359 + logger_instance = Logger(session, auth_handler_instance)
360 + logs = logger_instance.fetch_all_logs()
361 + if logs:
362 + logs = [log for log in logs if log.event_type == event_type]
363 + if logs != []:
364 + return LogsResponse(logs=logs, success=True, message="Logs fetched successfully")
365 + else:
366 + raise HTTPException(status_code=404, detail=f"No logs found for event type: {event_type}".format(event_type=event_type))
367 + else:
368 + raise HTTPException(status_code=404, detail="No logs found")
369 +
370 +
371 +@logs_router.delete(
372 + "",
373 + response_model=LogsResponse,
374 + description="Purge all logs",
375 + dependencies=[Security(AuthHandler().get_current_user, scopes=["admin"])],
376 +)
377 +async def purge_logs() -> LogsResponse: # Update this line to use the new model
378 + """
379 + Purge all logs from the database.
380 +
381 + This endpoint purges all the logs stored in the database and returns a success status and message.
382 +
383 + Returns:
384 + LogsResponse: A Pydantic model containing a list of logs and additional metadata.
385 +
386 + Raises:
387 + HTTPException: An exception with a 404 status code is raised if no logs are found.
388 + """
389 + with Session(engine) as session:
390 + auth_handler_instance = AuthHandler()
391 + logger_instance = Logger(session, auth_handler_instance)
392 + logs = logger_instance.fetch_all_logs()
393 + if logs:
394 + for log in logs:
395 + session.delete(log)
396 + session.commit()
397 + return LogsResponse(logs=[], success=True, message="Logs purged successfully")
398 + else:
399 + raise HTTPException(status_code=404, detail="No logs found")
400 +
401 +
402 +@logs_router.delete(
403 + "/timerange",
404 + response_model=LogsResponse,
405 + description="Purge logs by time range",
406 + dependencies=[Security(AuthHandler().get_current_user, scopes=["admin"])],
407 +)
408 +async def purge_logs_by_time_range(time_range: TimeRangeModel) -> LogsResponse:
409 + """
410 + Purge all logs from the database where the timestamp is within the provided time range.
411 +
412 + This endpoint purges all the logs stored in the database where the timestamp is within the provided time range
413 + and returns a success status and message.
414 +
415 + Args:
416 + time_range (TimeRangeModel): The time range to filter logs by.
417 +
418 + Returns:
419 + LogsResponse: A Pydantic model containing a list of logs and additional metadata.
420 +
421 + Raises:
422 + HTTPException: An exception with a 404 status code is raised if no logs are found.
423 + """
424 + with Session(engine) as session:
425 + auth_handler_instance = AuthHandler()
426 + logger_instance = Logger(session, auth_handler_instance)
427 + logs = logger_instance.fetch_all_logs()
428 + if logs:
429 + logs = [log for log in logs if log.timestamp >= datetime.now() - timedelta(days=int(time_range.time_range[:-1]))]
430 + if logs != []:
431 + for log in logs:
432 + session.delete(log)
433 + session.commit()
434 + return LogsResponse(logs=[], success=True, message="Logs purged successfully")
435 + else:
436 + raise HTTPException(
437 + status_code=404,
438 + detail=f"No logs found for time range: {time_range.time_range}".format(time_range=time_range.time_range),
439 + )
440 + else:
441 + raise HTTPException(status_code=404, detail="No logs found")
442 +
443 +
444 +################## ! ALLOWED FILES ! ##################
445 def allowed_file(filename):
446 ALLOWED_EXTENSIONS = {"yaml", "txt"}
447 return "." in filename and filename.rsplit(".", 1)[1].lower() in ALLOWED_EXTENSIONS
backend/copilot.py
+107
@@ -1,12 +1,18 @@
1 import uvicorn
2 +from dotenv import load_dotenv
3 from fastapi import FastAPI
4 from fastapi import HTTPException
5 from fastapi import Request
6 +from fastapi.exceptions import RequestValidationError
7 from fastapi.middleware.cors import CORSMiddleware
8 from fastapi.responses import JSONResponse
9 +from loguru import logger
10 +from pydantic import BaseSettings
11 +from sqlmodel import Session
12
13 from app.agents.routes.agents import agents_router
14 from app.auth.routes.auth import user_router
15 +from app.auth.utils import AuthHandler
16 from app.connectors.cortex.routes.analyzers import cortex_analyzer_router
17 from app.connectors.dfir_iris.routes.alerts import dfir_iris_alerts_router
18 from app.connectors.dfir_iris.routes.assets import assets_router
@@ -37,6 +43,14 @@ from app.integrations.alert_escalation.routes.general_alert import (
43 )
44 from app.integrations.dnstwist.routes.analyze import dnstwist_router
45 from app.smtp.routes.configure import smtp_router
46 +from app.utils import ErrorType
47 +from app.utils import Logger
48 +from app.utils import ValidationErrorItem
49 +from app.utils import ValidationErrorResponse
50 +from app.utils import logs_router
51 +
52 +auth_handler = AuthHandler()
53 +
54
55 app = FastAPI(description="CoPilot API", version="0.1.0", title="CoPilot API")
56
@@ -50,8 +64,75 @@ app.add_middleware(
64 )
65
66
67 +################## ! Middleware LOGGING TO `log_entry` table ! ##################
68 +# Constants
69 +EXCLUDED_PATHS = ["/auth/token", "/auth/register"]
70 +INTERNAL_SERVER_ERROR = 500
71 +
72 +
73 +async def process_request(request: Request, call_next, session, logger_instance):
74 + response = await call_next(request)
75 + user_id = await logger_instance.get_user_id_from_request(request)
76 + return response, user_id
77 +
78 +
79 +def is_excluded_path(path: str) -> bool:
80 + """Check if the request path is in the list of excluded paths."""
81 + return path in EXCLUDED_PATHS
82 +
83 +
84 +async def handle_exception(e, user_id, request, logger_instance):
85 + """
86 + Handle exceptions that occur during request processing.
87 + """
88 + user_id = await logger_instance.get_user_id_from_request(request) if user_id is None else user_id
89 + await logger_instance.log_error(user_id, request, e)
90 + if isinstance(e, HTTPException):
91 + status_code = e.status_code
92 + else:
93 + status_code = INTERNAL_SERVER_ERROR
94 + return JSONResponse(status_code=status_code, content={"message": str(e), "success": False})
95 +
96 +
97 +@app.middleware("http")
98 +async def log_requests(request: Request, call_next):
99 + """
100 + Middleware for logging requests.
101 + """
102 + # Skip logging for OPTIONS requests
103 + if request.method == "OPTIONS":
104 + return await call_next(request)
105 +
106 + with Session(engine) as session:
107 + logger_instance = Logger(session, auth_handler)
108 + user_id = None
109 +
110 + try:
111 + if not is_excluded_path(request.url.path):
112 + response, user_id = await process_request(request, call_next, session, logger_instance)
113 + else:
114 + response = await call_next(request)
115 + except Exception as e:
116 + return await handle_exception(e, user_id, request, logger_instance)
117 +
118 + await logger_instance.log_route_access(user_id, request, response)
119 +
120 + return response if response else await call_next(request)
121 +
122 +
123 +################## ! Exception Handlers ! ##################
124 +# Utility function to get user_id from request
125 +async def get_user_id_from_request(request: Request, session, logger_instance):
126 + return await logger_instance.get_user_id_from_request(request)
127 +
128 +
129 @app.exception_handler(HTTPException)
130 async def custom_http_exception_handler(request: Request, exc: HTTPException):
131 + with Session(engine) as session:
132 + logger_instance = Logger(session, auth_handler)
133 + user_id = await get_user_id_from_request(request, session, logger_instance)
134 + await logger_instance.log_error(user_id, request, exc.detail)
135 +
136 return JSONResponse(
137 status_code=exc.status_code,
138 content={
@@ -61,6 +142,31 @@ async def custom_http_exception_handler(request: Request, exc: HTTPException):
142 )
143
144
145 +@app.exception_handler(RequestValidationError)
146 +async def validation_exception_handler(request: Request, exc: RequestValidationError):
147 + errors = exc.errors()
148 + details = []
149 +
150 + for error in errors:
151 + field = error["loc"][-1]
152 + error_type = ErrorType(error["type"])
153 + details.append(ValidationErrorItem(field=field, error_type=error_type))
154 +
155 + # Extract the first message from details for use in ValidationErrorResponse
156 + main_message = details[0].message if details else "Validation Error"
157 +
158 + with Session(engine) as session:
159 + logger_instance = Logger(session, auth_handler)
160 + user_id = await get_user_id_from_request(request, session, logger_instance)
161 + await logger_instance.log_error(user_id, request, main_message)
162 +
163 + return JSONResponse(
164 + status_code=422,
165 + content=ValidationErrorResponse(message=main_message, details=details).dict(),
166 + )
167 +
168 +
169 +################## ! INCLUDE ROUTES ! ##################
170 app.include_router(connector_router, prefix="/connectors", tags=["connectors"])
171 app.include_router(wazuh_indexer_router, prefix="/wazuh_indexer", tags=["wazuh-indexer"])
172 app.include_router(user_router, prefix="/auth", tags=["auth"])
@@ -87,6 +193,7 @@ app.include_router(healtcheck_agents_router, prefix="/healthcheck", tags=["healt
193 app.include_router(smtp_router, prefix="/smtp", tags=["smtp"])
194 app.include_router(dnstwist_router, prefix="/dnstwist", tags=["dnstwist"])
195 app.include_router(integration_general_alerts_router, prefix="/alerts", tags=["alerts"])
196 +app.include_router(logs_router, prefix="/logs", tags=["logs"])
197
198
199 @app.on_event("startup")
figma-tokens.json
+581 -39
@@ -32,6 +32,14 @@
32 "value": "'JetBrains Mono', SFMono-Regular, Menlo, Consolas, Courier, monospace",
33 "type": "fontFamilies"
34 },
35 + "color-light-sidebar-background": {
36 + "value": "#ffffff",
37 + "type": "color"
38 + },
39 + "color-light-body-background": {
40 + "value": "#f5f7f9",
41 + "type": "color"
42 + },
43 "color-light-text": {
44 "value": "#000000",
45 "type": "color"
@@ -44,16 +52,44 @@
52 "value": "#ffffff",
53 "type": "color"
54 },
47 - "color-light-sidebar-background": {
48 - "value": "#F6F7F9",
55 + "color-light-background-secondary": {
56 + "value": "#fafbfc",
57 "type": "color"
58 },
51 - "color-light-body-background": {
52 - "value": "#E8EDF2",
59 + "color-light-primary": {
60 + "value": "rgb(0, 178, 123)",
61 "type": "color"
62 },
55 - "color-light-primary": {
56 - "value": "#00B27B",
63 + "color-light-primary-005": {
64 + "value": "rgba(0, 178, 123, 0.05)",
65 + "type": "color"
66 + },
67 + "color-light-primary-010": {
68 + "value": "rgba(0, 178, 123, 0.1)",
69 + "type": "color"
70 + },
71 + "color-light-primary-015": {
72 + "value": "rgba(0, 178, 123, 0.15)",
73 + "type": "color"
74 + },
75 + "color-light-primary-020": {
76 + "value": "rgba(0, 178, 123, 0.2)",
77 + "type": "color"
78 + },
79 + "color-light-primary-030": {
80 + "value": "rgba(0, 178, 123, 0.3)",
81 + "type": "color"
82 + },
83 + "color-light-primary-040": {
84 + "value": "rgba(0, 178, 123, 0.4)",
85 + "type": "color"
86 + },
87 + "color-light-primary-050": {
88 + "value": "rgba(0, 178, 123, 0.5)",
89 + "type": "color"
90 + },
91 + "color-light-primary-060": {
92 + "value": "rgba(0, 178, 123, 0.6)",
93 "type": "color"
94 },
95 "color-light-info": {
@@ -73,23 +109,115 @@
109 "type": "color"
110 },
111 "color-light-secondary-1": {
76 - "value": "#6267FF",
112 + "value": "rgb(98, 103, 255)",
113 + "type": "color"
114 + },
115 + "color-light-secondary-1-opacity-005": {
116 + "value": "rgba(98, 103, 255, 0.05)",
117 + "type": "color"
118 + },
119 + "color-light-secondary-1-opacity-010": {
120 + "value": "rgba(98, 103, 255, 0.1)",
121 + "type": "color"
122 + },
123 + "color-light-secondary-1-opacity-020": {
124 + "value": "rgba(98, 103, 255, 0.2)",
125 + "type": "color"
126 + },
127 + "color-light-secondary-1-opacity-030": {
128 + "value": "rgba(98, 103, 255, 0.3)",
129 "type": "color"
130 },
131 "color-light-secondary-2": {
80 - "value": "#FF61C9",
132 + "value": "rgb(255, 97, 201)",
133 + "type": "color"
134 + },
135 + "color-light-secondary-2-opacity-005": {
136 + "value": "rgba(255, 97, 201, 0.05)",
137 + "type": "color"
138 + },
139 + "color-light-secondary-2-opacity-010": {
140 + "value": "rgba(255, 97, 201, 0.1)",
141 + "type": "color"
142 + },
143 + "color-light-secondary-2-opacity-020": {
144 + "value": "rgba(255, 97, 201, 0.2)",
145 + "type": "color"
146 + },
147 + "color-light-secondary-2-opacity-030": {
148 + "value": "rgba(255, 97, 201, 0.3)",
149 "type": "color"
150 },
151 "color-light-secondary-3": {
84 - "value": "#FFB600",
152 + "value": "rgb(255, 182, 0)",
153 + "type": "color"
154 + },
155 + "color-light-secondary-3-opacity-005": {
156 + "value": "rgba(255, 182, 0, 0.05)",
157 + "type": "color"
158 + },
159 + "color-light-secondary-3-opacity-010": {
160 + "value": "rgba(255, 182, 0, 0.1)",
161 + "type": "color"
162 + },
163 + "color-light-secondary-3-opacity-020": {
164 + "value": "rgba(255, 182, 0, 0.2)",
165 + "type": "color"
166 + },
167 + "color-light-secondary-3-opacity-030": {
168 + "value": "rgba(255, 182, 0, 0.3)",
169 "type": "color"
170 },
171 "color-light-secondary-4": {
88 - "value": "#FF0156",
172 + "value": "rgb(255, 1, 86)",
173 "type": "color"
174 },
91 - "color-light-shade-1": {
92 - "value": "#ffffff",
175 + "color-light-secondary-4-opacity-005": {
176 + "value": "rgba(255, 1, 86, 0.05)",
177 + "type": "color"
178 + },
179 + "color-light-secondary-4-opacity-010": {
180 + "value": "rgba(255, 1, 86, 0.1)",
181 + "type": "color"
182 + },
183 + "color-light-secondary-4-opacity-020": {
184 + "value": "rgba(255, 1, 86, 0.2)",
185 + "type": "color"
186 + },
187 + "color-light-secondary-4-opacity-030": {
188 + "value": "rgba(255, 1, 86, 0.3)",
189 + "type": "color"
190 + },
191 + "color-light-divider-005": {
192 + "value": "rgba(0, 0, 0, 0.05)",
193 + "type": "color"
194 + },
195 + "color-light-divider-010": {
196 + "value": "rgba(0, 0, 0, 0.1)",
197 + "type": "color"
198 + },
199 + "color-light-divider-020": {
200 + "value": "rgba(0, 0, 0, 0.2)",
201 + "type": "color"
202 + },
203 + "color-light-hover-005": {
204 + "value": "rgba(0, 0, 0, 0.05)",
205 + "type": "color"
206 + },
207 + "color-light-hover-010": {
208 + "value": "rgba(0, 0, 0, 0.1)",
209 + "type": "color"
210 + },
211 + "color-light-hover-050": {
212 + "value": "rgba(0, 0, 0, 0.5)",
213 + "type": "color"
214 + },
215 + "color-dark-sidebar-background": {
216 + "value": "#1D1F25",
217 + "type": "color"
218 + },
219 + "color-dark-body-background": {
220 + "value": "#14161A",
221 "type": "color"
222 },
223 "color-dark-text": {
@@ -104,16 +232,44 @@
232 "value": "#26282d",
233 "type": "color"
234 },
107 - "color-dark-sidebar-background": {
235 + "color-dark-background-secondary": {
236 "value": "#1D1F25",
237 "type": "color"
238 },
111 - "color-dark-body-background": {
112 - "value": "#14161A",
239 + "color-dark-primary": {
240 + "value": "rgb(0, 225, 155)",
241 "type": "color"
242 },
115 - "color-dark-primary": {
116 - "value": "#00E19B",
243 + "color-dark-primary-005": {
244 + "value": "rgba(0, 225, 155, 0.05)",
245 + "type": "color"
246 + },
247 + "color-dark-primary-010": {
248 + "value": "rgba(0, 225, 155, 0.1)",
249 + "type": "color"
250 + },
251 + "color-dark-primary-015": {
252 + "value": "rgba(0, 225, 155, 0.15)",
253 + "type": "color"
254 + },
255 + "color-dark-primary-020": {
256 + "value": "rgba(0, 225, 155, 0.2)",
257 + "type": "color"
258 + },
259 + "color-dark-primary-030": {
260 + "value": "rgba(0, 225, 155, 0.3)",
261 + "type": "color"
262 + },
263 + "color-dark-primary-040": {
264 + "value": "rgba(0, 225, 155, 0.4)",
265 + "type": "color"
266 + },
267 + "color-dark-primary-050": {
268 + "value": "rgba(0, 225, 155, 0.5)",
269 + "type": "color"
270 + },
271 + "color-dark-primary-060": {
272 + "value": "rgba(0, 225, 155, 0.6)",
273 "type": "color"
274 },
275 "color-dark-info": {
@@ -133,27 +289,181 @@
289 "type": "color"
290 },
291 "color-dark-secondary-1": {
136 - "value": "#6267FF",
292 + "value": "rgb(98, 103, 255)",
293 + "type": "color"
294 + },
295 + "color-dark-secondary-1-opacity-005": {
296 + "value": "rgba(98, 103, 255, 0.05)",
297 + "type": "color"
298 + },
299 + "color-dark-secondary-1-opacity-010": {
300 + "value": "rgba(98, 103, 255, 0.1)",
301 + "type": "color"
302 + },
303 + "color-dark-secondary-1-opacity-020": {
304 + "value": "rgba(98, 103, 255, 0.2)",
305 + "type": "color"
306 + },
307 + "color-dark-secondary-1-opacity-030": {
308 + "value": "rgba(98, 103, 255, 0.3)",
309 "type": "color"
310 },
311 "color-dark-secondary-2": {
140 - "value": "#FF61C9",
312 + "value": "rgb(255, 97, 201)",
313 + "type": "color"
314 + },
315 + "color-dark-secondary-2-opacity-005": {
316 + "value": "rgba(255, 97, 201, 0.05)",
317 + "type": "color"
318 + },
319 + "color-dark-secondary-2-opacity-010": {
320 + "value": "rgba(255, 97, 201, 0.1)",
321 + "type": "color"
322 + },
323 + "color-dark-secondary-2-opacity-020": {
324 + "value": "rgba(255, 97, 201, 0.2)",
325 + "type": "color"
326 + },
327 + "color-dark-secondary-2-opacity-030": {
328 + "value": "rgba(255, 97, 201, 0.3)",
329 "type": "color"
330 },
331 "color-dark-secondary-3": {
144 - "value": "#FFB600",
332 + "value": "rgb(255, 182, 0)",
333 + "type": "color"
334 + },
335 + "color-dark-secondary-3-opacity-005": {
336 + "value": "rgba(255, 182, 0, 0.05)",
337 + "type": "color"
338 + },
339 + "color-dark-secondary-3-opacity-010": {
340 + "value": "rgba(255, 182, 0, 0.1)",
341 + "type": "color"
342 + },
343 + "color-dark-secondary-3-opacity-020": {
344 + "value": "rgba(255, 182, 0, 0.2)",
345 + "type": "color"
346 + },
347 + "color-dark-secondary-3-opacity-030": {
348 + "value": "rgba(255, 182, 0, 0.3)",
349 "type": "color"
350 },
351 "color-dark-secondary-4": {
148 - "value": "#FF0156",
352 + "value": "rgb(255, 1, 86)",
353 "type": "color"
354 },
151 - "color-dark-shade-1": {
152 - "value": "#26282d",
355 + "color-dark-secondary-4-opacity-005": {
356 + "value": "rgba(255, 1, 86, 0.05)",
357 + "type": "color"
358 + },
359 + "color-dark-secondary-4-opacity-010": {
360 + "value": "rgba(255, 1, 86, 0.1)",
361 "type": "color"
362 + },
363 + "color-dark-secondary-4-opacity-020": {
364 + "value": "rgba(255, 1, 86, 0.2)",
365 + "type": "color"
366 + },
367 + "color-dark-secondary-4-opacity-030": {
368 + "value": "rgba(255, 1, 86, 0.3)",
369 + "type": "color"
370 + },
371 + "color-dark-divider-005": {
372 + "value": "rgba(255, 255, 255, 0.05)",
373 + "type": "color"
374 + },
375 + "color-dark-divider-010": {
376 + "value": "rgba(255, 255, 255, 0.1)",
377 + "type": "color"
378 + },
379 + "color-dark-divider-020": {
380 + "value": "rgba(255, 255, 255, 0.2)",
381 + "type": "color"
382 + },
383 + "color-dark-hover-005": {
384 + "value": "rgba(255, 255, 255, 0.05)",
385 + "type": "color"
386 + },
387 + "color-dark-hover-010": {
388 + "value": "rgba(255, 255, 255, 0.1)",
389 + "type": "color"
390 + },
391 + "color-dark-hover-050": {
392 + "value": "rgba(255, 255, 255, 0.5)",
393 + "type": "color"
394 + },
395 + "typo-h1": {
396 + "value": {
397 + "fontFamily": "{font-families-display}",
398 + "fontSize": "30px",
399 + "fontWeight": "700",
400 + "lineHeight": "41"
401 + },
402 + "type": "typography"
403 + },
404 + "typo-h2": {
405 + "value": {
406 + "fontFamily": "{font-families-display}",
407 + "fontSize": "26px",
408 + "fontWeight": "700",
409 + "lineHeight": "35"
410 + },
411 + "type": "typography"
412 + },
413 + "typo-h3": {
414 + "value": {
415 + "fontFamily": "{font-families-display}",
416 + "fontSize": "22px",
417 + "fontWeight": "700",
418 + "lineHeight": "30"
419 + },
420 + "type": "typography"
421 + },
422 + "typo-h4": {
423 + "value": {
424 + "fontFamily": "{font-families-display}",
425 + "fontSize": "18px",
426 + "fontWeight": "500",
427 + "lineHeight": "24"
428 + },
429 + "type": "typography"
430 + },
431 + "typo-h5": {
432 + "value": {
433 + "fontFamily": "{font-families-display}",
434 + "fontSize": "14px",
435 + "fontWeight": "700",
436 + "lineHeight": "19"
437 + },
438 + "type": "typography"
439 + },
440 + "typo-h6": {
441 + "value": {
442 + "fontFamily": "{font-families-base}",
443 + "fontSize": "12px",
444 + "fontWeight": "500",
445 + "lineHeight": "16"
446 + },
447 + "type": "typography"
448 + },
449 + "typo-p": {
450 + "value": {
451 + "fontFamily": "{font-families-base}",
452 + "fontSize": "{font-sizes-base}",
453 + "lineHeight": "20"
454 + },
455 + "type": "typography"
456 }
457 },
458 "light": {
459 + "color-sidebar-background": {
460 + "value": "{color-light-sidebar-background}",
461 + "type": "color"
462 + },
463 + "color-body-background": {
464 + "value": "{color-light-body-background}",
465 + "type": "color"
466 + },
467 "color-text": {
468 "value": "{color-light-text}",
469 "type": "color"
@@ -166,18 +476,46 @@
476 "value": "{color-light-background}",
477 "type": "color"
478 },
169 - "color-sidebar-background": {
170 - "value": "{color-light-sidebar-background}",
171 - "type": "color"
172 - },
173 - "color-body-background": {
174 - "value": "{color-light-body-background}",
479 + "color-background-secondary": {
480 + "value": "{color-light-background-secondary}",
481 "type": "color"
482 },
483 "color-primary": {
484 "value": "{color-light-primary}",
485 "type": "color"
486 },
487 + "color-primary-005": {
488 + "value": "{color-light-primary-005}",
489 + "type": "color"
490 + },
491 + "color-primary-010": {
492 + "value": "{color-light-primary-010}",
493 + "type": "color"
494 + },
495 + "color-primary-015": {
496 + "value": "{color-light-primary-015}",
497 + "type": "color"
498 + },
499 + "color-primary-020": {
500 + "value": "{color-light-primary-020}",
501 + "type": "color"
502 + },
503 + "color-primary-030": {
504 + "value": "{color-light-primary-030}",
505 + "type": "color"
506 + },
507 + "color-primary-040": {
508 + "value": "{color-light-primary-040}",
509 + "type": "color"
510 + },
511 + "color-primary-050": {
512 + "value": "{color-light-primary-050}",
513 + "type": "color"
514 + },
515 + "color-primary-060": {
516 + "value": "{color-light-primary-060}",
517 + "type": "color"
518 + },
519 "color-info": {
520 "value": "{color-light-info}",
521 "type": "color"
@@ -198,24 +536,116 @@
536 "value": "{color-light-secondary-1}",
537 "type": "color"
538 },
539 + "color-secondary-1-opacity-005": {
540 + "value": "{color-light-secondary-1-opacity-005}",
541 + "type": "color"
542 + },
543 + "color-secondary-1-opacity-010": {
544 + "value": "{color-light-secondary-1-opacity-010}",
545 + "type": "color"
546 + },
547 + "color-secondary-1-opacity-020": {
548 + "value": "{color-light-secondary-1-opacity-020}",
549 + "type": "color"
550 + },
551 + "color-secondary-1-opacity-030": {
552 + "value": "{color-light-secondary-1-opacity-030}",
553 + "type": "color"
554 + },
555 "color-secondary-2": {
556 "value": "{color-light-secondary-2}",
557 "type": "color"
558 },
559 + "color-secondary-2-opacity-005": {
560 + "value": "{color-light-secondary-2-opacity-005}",
561 + "type": "color"
562 + },
563 + "color-secondary-2-opacity-010": {
564 + "value": "{color-light-secondary-2-opacity-010}",
565 + "type": "color"
566 + },
567 + "color-secondary-2-opacity-020": {
568 + "value": "{color-light-secondary-2-opacity-020}",
569 + "type": "color"
570 + },
571 + "color-secondary-2-opacity-030": {
572 + "value": "{color-light-secondary-2-opacity-030}",
573 + "type": "color"
574 + },
575 "color-secondary-3": {
576 "value": "{color-light-secondary-3}",
577 "type": "color"
578 },
579 + "color-secondary-3-opacity-005": {
580 + "value": "{color-light-secondary-3-opacity-005}",
581 + "type": "color"
582 + },
583 + "color-secondary-3-opacity-010": {
584 + "value": "{color-light-secondary-3-opacity-010}",
585 + "type": "color"
586 + },
587 + "color-secondary-3-opacity-020": {
588 + "value": "{color-light-secondary-3-opacity-020}",
589 + "type": "color"
590 + },
591 + "color-secondary-3-opacity-030": {
592 + "value": "{color-light-secondary-3-opacity-030}",
593 + "type": "color"
594 + },
595 "color-secondary-4": {
596 "value": "{color-light-secondary-4}",
597 "type": "color"
598 },
213 - "color-shade-1": {
214 - "value": "{color-light-shade-1}",
599 + "color-secondary-4-opacity-005": {
600 + "value": "{color-light-secondary-4-opacity-005}",
601 + "type": "color"
602 + },
603 + "color-secondary-4-opacity-010": {
604 + "value": "{color-light-secondary-4-opacity-010}",
605 + "type": "color"
606 + },
607 + "color-secondary-4-opacity-020": {
608 + "value": "{color-light-secondary-4-opacity-020}",
609 + "type": "color"
610 + },
611 + "color-secondary-4-opacity-030": {
612 + "value": "{color-light-secondary-4-opacity-030}",
613 + "type": "color"
614 + },
615 + "color-divider-005": {
616 + "value": "{color-light-divider-005}",
617 + "type": "color"
618 + },
619 + "color-divider-010": {
620 + "value": "{color-light-divider-010}",
621 + "type": "color"
622 + },
623 + "color-divider-020": {
624 + "value": "{color-light-divider-020}",
625 + "type": "color"
626 + },
627 + "color-hover-005": {
628 + "value": "{color-light-hover-005}",
629 + "type": "color"
630 + },
631 + "color-hover-010": {
632 + "value": "{color-light-hover-010}",
633 + "type": "color"
634 + },
635 + "color-hover-050": {
636 + "value": "{color-light-hover-050}",
637 "type": "color"
638 }
639 },
640 "dark": {
641 + "color-sidebar-background": {
642 + "value": "{color-dark-sidebar-background}",
643 + "type": "color"
644 + },
645 + "color-body-background": {
646 + "value": "{color-dark-body-background}",
647 + "type": "color"
648 + },
649 "color-text": {
650 "value": "{color-dark-text}",
651 "type": "color"
@@ -228,18 +658,46 @@
658 "value": "{color-dark-background}",
659 "type": "color"
660 },
231 - "color-sidebar-background": {
232 - "value": "{color-dark-sidebar-background}",
233 - "type": "color"
234 - },
235 - "color-body-background": {
236 - "value": "{color-dark-body-background}",
661 + "color-background-secondary": {
662 + "value": "{color-dark-background-secondary}",
663 "type": "color"
664 },
665 "color-primary": {
666 "value": "{color-dark-primary}",
667 "type": "color"
668 },
669 + "color-primary-005": {
670 + "value": "{color-dark-primary-005}",
671 + "type": "color"
672 + },
673 + "color-primary-010": {
674 + "value": "{color-dark-primary-010}",
675 + "type": "color"
676 + },
677 + "color-primary-015": {
678 + "value": "{color-dark-primary-015}",
679 + "type": "color"
680 + },
681 + "color-primary-020": {
682 + "value": "{color-dark-primary-020}",
683 + "type": "color"
684 + },
685 + "color-primary-030": {
686 + "value": "{color-dark-primary-030}",
687 + "type": "color"
688 + },
689 + "color-primary-040": {
690 + "value": "{color-dark-primary-040}",
691 + "type": "color"
692 + },
693 + "color-primary-050": {
694 + "value": "{color-dark-primary-050}",
695 + "type": "color"
696 + },
697 + "color-primary-060": {
698 + "value": "{color-dark-primary-060}",
699 + "type": "color"
700 + },
701 "color-info": {
702 "value": "{color-dark-info}",
703 "type": "color"
@@ -260,20 +718,104 @@
718 "value": "{color-dark-secondary-1}",
719 "type": "color"
720 },
721 + "color-secondary-1-opacity-005": {
722 + "value": "{color-dark-secondary-1-opacity-005}",
723 + "type": "color"
724 + },
725 + "color-secondary-1-opacity-010": {
726 + "value": "{color-dark-secondary-1-opacity-010}",
727 + "type": "color"
728 + },
729 + "color-secondary-1-opacity-020": {
730 + "value": "{color-dark-secondary-1-opacity-020}",
731 + "type": "color"
732 + },
733 + "color-secondary-1-opacity-030": {
734 + "value": "{color-dark-secondary-1-opacity-030}",
735 + "type": "color"
736 + },
737 "color-secondary-2": {
738 "value": "{color-dark-secondary-2}",
739 "type": "color"
740 },
741 + "color-secondary-2-opacity-005": {
742 + "value": "{color-dark-secondary-2-opacity-005}",
743 + "type": "color"
744 + },
745 + "color-secondary-2-opacity-010": {
746 + "value": "{color-dark-secondary-2-opacity-010}",
747 + "type": "color"
748 + },
749 + "color-secondary-2-opacity-020": {
750 + "value": "{color-dark-secondary-2-opacity-020}",
751 + "type": "color"
752 + },
753 + "color-secondary-2-opacity-030": {
754 + "value": "{color-dark-secondary-2-opacity-030}",
755 + "type": "color"
756 + },
757 "color-secondary-3": {
758 "value": "{color-dark-secondary-3}",
759 "type": "color"
760 },
761 + "color-secondary-3-opacity-005": {
762 + "value": "{color-dark-secondary-3-opacity-005}",
763 + "type": "color"
764 + },
765 + "color-secondary-3-opacity-010": {
766 + "value": "{color-dark-secondary-3-opacity-010}",
767 + "type": "color"
768 + },
769 + "color-secondary-3-opacity-020": {
770 + "value": "{color-dark-secondary-3-opacity-020}",
771 + "type": "color"
772 + },
773 + "color-secondary-3-opacity-030": {
774 + "value": "{color-dark-secondary-3-opacity-030}",
775 + "type": "color"
776 + },
777 "color-secondary-4": {
778 "value": "{color-dark-secondary-4}",
779 "type": "color"
780 },
275 - "color-shade-1": {
276 - "value": "{color-dark-shade-1}",
781 + "color-secondary-4-opacity-005": {
782 + "value": "{color-dark-secondary-4-opacity-005}",
783 + "type": "color"
784 + },
785 + "color-secondary-4-opacity-010": {
786 + "value": "{color-dark-secondary-4-opacity-010}",
787 + "type": "color"
788 + },
789 + "color-secondary-4-opacity-020": {
790 + "value": "{color-dark-secondary-4-opacity-020}",
791 + "type": "color"
792 + },
793 + "color-secondary-4-opacity-030": {
794 + "value": "{color-dark-secondary-4-opacity-030}",
795 + "type": "color"
796 + },
797 + "color-divider-005": {
798 + "value": "{color-dark-divider-005}",
799 + "type": "color"
800 + },
801 + "color-divider-010": {
802 + "value": "{color-dark-divider-010}",
803 + "type": "color"
804 + },
805 + "color-divider-020": {
806 + "value": "{color-dark-divider-020}",
807 + "type": "color"
808 + },
809 + "color-hover-005": {
810 + "value": "{color-dark-hover-005}",
811 + "type": "color"
812 + },
813 + "color-hover-010": {
814 + "value": "{color-dark-hover-010}",
815 + "type": "color"
816 + },
817 + "color-hover-050": {
818 + "value": "{color-dark-hover-050}",
819 "type": "color"
820 }
821 }
package-lock.json
+1501 -1937
@@ -9,72 +9,71 @@
9 "version": "1.0.0",
10 "dependencies": {
11 "@ajoelp/json-to-formdata": "^1.5.0",
12 - "@animxyz/core": "^0.6.6",
13 - "@animxyz/vue3": "^0.6.7",
12 "@fawmi/vue-google-maps": "^0.9.79",
15 - "@fontsource/jetbrains-mono": "^5.0.14",
16 - "@fontsource/lexend": "^5.0.14",
17 - "@fontsource/public-sans": "^5.0.12",
13 + "@fontsource/jetbrains-mono": "^5.0.17",
14 + "@fontsource/lexend": "^5.0.17",
15 + "@fontsource/public-sans": "^5.0.15",
16 "@fullcalendar/core": "^6.1.9",
17 "@fullcalendar/daygrid": "^6.1.9",
18 "@fullcalendar/interaction": "^6.1.9",
19 "@fullcalendar/list": "^6.1.9",
20 "@fullcalendar/timegrid": "^6.1.9",
21 "@fullcalendar/vue3": "^6.1.9",
24 - "@milkdown/core": "^7.3.0",
25 - "@milkdown/ctx": "^7.3.0",
26 - "@milkdown/preset-commonmark": "^7.3.0",
27 - "@milkdown/prose": "^7.3.0",
28 - "@milkdown/theme-nord": "^7.3.0",
29 - "@milkdown/transformer": "^7.3.0",
30 - "@milkdown/vue": "^7.3.0",
22 + "@milkdown/core": "^7.3.1",
23 + "@milkdown/ctx": "^7.3.1",
24 + "@milkdown/preset-commonmark": "^7.3.1",
25 + "@milkdown/prose": "^7.3.1",
26 + "@milkdown/theme-nord": "^7.3.1",
27 + "@milkdown/transformer": "^7.3.1",
28 + "@milkdown/vue": "^7.3.1",
29 "@popperjs/core": "^2.11.8",
32 - "@revolist/revogrid": "^4.7.0-next.3",
30 + "@revolist/revogrid": "^4.7.0-next.4",
31 "@revolist/revogrid-column-numeral": "^1.0.2",
32 "@revolist/vue3-datagrid": "^3.6.17",
35 - "@tiptap/extension-character-count": "^2.1.11",
36 - "@tiptap/extension-highlight": "^2.1.11",
37 - "@tiptap/extension-link": "^2.1.11",
38 - "@tiptap/extension-task-item": "^2.1.11",
39 - "@tiptap/extension-task-list": "^2.1.11",
40 - "@tiptap/extension-text-align": "^2.1.11",
41 - "@tiptap/pm": "^2.1.11",
42 - "@tiptap/starter-kit": "^2.1.11",
43 - "@tiptap/vue-3": "^2.1.11",
33 + "@tiptap/extension-character-count": "^2.1.12",
34 + "@tiptap/extension-highlight": "^2.1.12",
35 + "@tiptap/extension-link": "^2.1.12",
36 + "@tiptap/extension-task-item": "^2.1.12",
37 + "@tiptap/extension-task-list": "^2.1.12",
38 + "@tiptap/extension-text-align": "^2.1.12",
39 + "@tiptap/extension-underline": "^2.1.12",
40 + "@tiptap/pm": "^2.1.12",
41 + "@tiptap/starter-kit": "^2.1.12",
42 + "@tiptap/vue-3": "^2.1.12",
43 "@vueup/vue-quill": "^1.2.0",
45 - "@vueuse/components": "^10.4.1",
46 - "@vueuse/core": "^10.4.1",
47 - "apexcharts": "^3.43.0",
44 + "@vueuse/components": "^10.5.0",
45 + "@vueuse/core": "^10.5.0",
46 + "apexcharts": "^3.44.0",
47 "bytes": "^3.1.2",
48 "chart.js": "^4.4.0",
49 "colord": "^2.9.3",
50 "dayjs": "^1.11.10",
51 "detect-touch-device": "^1.1.6",
52 "echarts": "^5.4.3",
54 - "flag-icons": "^6.11.1",
53 "geojson": "^0.5.0",
56 - "highlight.js": "^11.8.0",
57 - "jose": "^4.15.2",
54 + "highlight.js": "^11.9.0",
55 + "jose": "^5.0.1",
56 "lodash": "^4.17.21",
59 - "maplibre-gl": "3.3.1",
57 + "maplibre-gl": "^3.5.2",
58 "mitt": "^3.0.1",
59 "naive-ui": "^2.35.0",
60 "password-validator": "^5.3.0",
63 - "pinia": "^2.1.6",
61 + "pinia": "^2.1.7",
62 "pinia-plugin-persistedstate": "^3.2.0",
63 "quill": "^1.3.7",
64 "secure-ls": "^1.2.6",
65 "shepherd.js": "^11.2.0",
68 - "v-calendar": "^3.1.0",
66 + "v-calendar": "^3.1.2",
67 "validator": "^13.11.0",
70 - "vue": "^3.3.4",
68 + "vue": "^3.3.7",
69 "vue-advanced-cropper": "^2.8.8",
70 "vue-cal": "^4.8.1",
71 "vue-chartjs": "^5.2.0",
72 "vue-highlight-words": "^3.0.1",
75 - "vue-i18n": "9.5.0",
73 + "vue-i18n": "^9.6.2",
74 "vue-maplibre-gl": "^3.0.3",
75 "vue-router": "^4.2.5",
76 + "vue-sjv": "^0.0.6",
77 "vue3-apexcharts": "^1.4.4",
78 "vue3-marquee": "^4.1.0",
79 "vuedraggable": "^4.1.0",
@@ -83,26 +82,17 @@
82 "devDependencies": {
83 "@clack/prompts": "^0.7.0",
84 "@css-render/vue3-ssr": "^0.15.12",
86 - "@faker-js/faker": "^8.1.0",
87 - "@intlify/shared": "^9.5.0",
88 - "@intlify/vue-devtools": "^9.5.0",
85 + "@faker-js/faker": "^8.2.0",
86 + "@iconify/vue": "^4.1.1",
87 "@rushstack/eslint-patch": "^1.5.1",
88 "@tsconfig/node18": "^18.2.2",
91 - "@types/bytes": "^3.1.2",
92 - "@types/fs-extra": "^11.0.2",
93 - "@types/inquirer": "^9.0.3",
94 - "@types/jsdom": "^21.1.3",
95 - "@types/lodash": "^4.14.199",
96 - "@types/node": "^20.8.2",
97 - "@types/validator": "^13.11.2",
98 - "@vicons/antd": "^0.12.0",
99 - "@vicons/carbon": "^0.12.0",
100 - "@vicons/fa": "^0.12.0",
101 - "@vicons/fluent": "^0.12.0",
102 - "@vicons/ionicons5": "^0.12.0",
103 - "@vicons/material": "^0.12.0",
104 - "@vicons/tabler": "^0.12.0",
105 - "@vicons/utils": "^0.1.4",
89 + "@types/bytes": "^3.1.3",
90 + "@types/fs-extra": "^11.0.3",
91 + "@types/inquirer": "^9.0.6",
92 + "@types/jsdom": "^21.1.4",
93 + "@types/lodash": "^4.14.200",
94 + "@types/node": "^20.8.9",
95 + "@types/validator": "^13.11.5",
96 "@vitejs/plugin-vue": "^4.4.0",
97 "@vitejs/plugin-vue-jsx": "^3.0.2",
98 "@vue-leaflet/vue-leaflet": "^0.10.1",
@@ -111,10 +101,10 @@
101 "@vue/test-utils": "^2.4.1",
102 "@vue/tsconfig": "^0.4.0",
103 "autoprefixer": "^10.4.16",
114 - "cypress": "^13.3.0",
115 - "eslint": "^8.50.0",
104 + "cypress": "^13.3.3",
105 + "eslint": "^8.52.0",
106 "eslint-plugin-cypress": "^2.15.1",
117 - "eslint-plugin-vue": "^9.17.0",
107 + "eslint-plugin-vue": "^9.18.1",
108 "fs-extra": "^11.1.1",
109 "jsdom": "^22.1.0",
110 "json5": "^2.2.3",
@@ -123,18 +113,18 @@
113 "picocolors": "^1.0.0",
114 "postcss": "^8.4.31",
115 "prettier": "^3.0.3",
126 - "sass": "^1.69.0",
116 + "sass": "^1.69.5",
117 "start-server-and-test": "^2.0.1",
118 "tailwind-config-viewer": "^1.7.2",
129 - "tailwindcss": "^3.3.3",
130 - "taze": "^0.11.3",
119 + "tailwindcss": "^3.3.5",
120 + "taze": "^0.12.0",
121 "ts-node": "^10.9.1",
122 "typescript": "~5.2.2",
123 "unplugin-vue-components": "^0.25.2",
134 - "vite": "^4.4.11",
124 + "vite": "^4.5.0",
125 "vite-svg-loader": "^4.0.0",
126 "vitest": "^0.34.6",
137 - "vue-tsc": "^1.8.15"
127 + "vue-tsc": "^1.8.22"
128 },
129 "engines": {
130 "node": ">=16.0.0 <20.5.0"
@@ -182,23 +172,6 @@
172 "node": ">=6.0.0"
173 }
174 },
185 - "node_modules/@animxyz/core": {
186 - "version": "0.6.6",
187 - "resolved": "https://registry.npmjs.org/@animxyz/core/-/core-0.6.6.tgz",
188 - "integrity": "sha512-NtAA/G0Gq3hzAiL6yuE/4U8IgHMPUl3MxbWUbhO443T9UCsf9rBY94P5aK79Zd+/529FeoNdDphIOcOZLsI2sA=="
189 - },
190 - "node_modules/@animxyz/vue3": {
191 - "version": "0.6.7",
192 - "resolved": "https://registry.npmjs.org/@animxyz/vue3/-/vue3-0.6.7.tgz",
193 - "integrity": "sha512-tLx4HfFcoxR5wgIMFDrmjS2mZifFRNMiwn7skPT3as9ViuCW3QdCsBIDYY2tR37GSvSUC1NhNwkkCZ1pt0C+Hg==",
194 - "dependencies": {
195 - "@animxyz/core": "^0.6.6",
196 - "clsx": "^1.1.1"
197 - },
198 - "peerDependencies": {
199 - "vue": ">= 3"
200 - }
201 - },
175 "node_modules/@antfu/ni": {
176 "version": "0.21.8",
177 "resolved": "https://registry.npmjs.org/@antfu/ni/-/ni-0.21.8.tgz",
@@ -548,9 +521,9 @@
521 }
522 },
523 "node_modules/@babel/parser": {
551 - "version": "7.22.16",
552 - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.16.tgz",
553 - "integrity": "sha512-+gPfKv8UWeKKeJTUxe59+OobVcrYHETCsORl61EmSkmgymguYk/X5bp7GuUIXaFsc6y++v8ZxPsLSSuujqDphA==",
524 + "version": "7.23.0",
525 + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz",
526 + "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==",
527 "bin": {
528 "parser": "bin/babel-parser.js"
529 },
@@ -693,7 +666,6 @@
666 },
667 "node_modules/@clack/prompts/node_modules/is-unicode-supported": {
668 "version": "1.3.0",
696 - "dev": true,
669 "inBundle": true,
670 "license": "MIT",
671 "engines": {
@@ -1231,18 +1203,18 @@
1203 }
1204 },
1205 "node_modules/@eslint/js": {
1234 - "version": "8.50.0",
1235 - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.50.0.tgz",
1236 - "integrity": "sha512-NCC3zz2+nvYd+Ckfh87rA47zfu2QsQpvc6k1yzTk+b9KzRj0wkGa8LSoGOXN6Zv4lRf/EIoZ80biDh9HOI+RNQ==",
1206 + "version": "8.52.0",
1207 + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.52.0.tgz",
1208 + "integrity": "sha512-mjZVbpaeMZludF2fsWLD0Z9gCref1Tk4i9+wddjRvpUNqqcndPkBD09N/Mapey0b3jaXbLm2kICwFv2E64QinA==",
1209 "dev": true,
1210 "engines": {
1211 "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
1212 }
1213 },
1214 "node_modules/@faker-js/faker": {
1243 - "version": "8.1.0",
1244 - "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-8.1.0.tgz",
1245 - "integrity": "sha512-38DT60rumHfBYynif3lmtxMqMqmsOQIxQgEuPZxCk2yUYN0eqWpTACgxi0VpidvsJB8CRxCpvP7B3anK85FjtQ==",
1215 + "version": "8.2.0",
1216 + "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-8.2.0.tgz",
1217 + "integrity": "sha512-VacmzZqVxdWdf9y64lDOMZNDMM/FQdtM9IsaOPKOm2suYwEatb8VkdHqOzXcDnZbk7YDE2BmsJmy/2Hmkn563g==",
1218 "dev": true,
1219 "funding": [
1220 {
@@ -1286,19 +1258,19 @@
1258 "integrity": "sha512-uvnFKtPgzLnpzzTRfhDlvXX0kLYi9lDRQbcDmT8iXl71Rx+uwSuaUIQl3DNC7w5OweAQ7XQMDObML+KaYDQfng=="
1259 },
1260 "node_modules/@fontsource/jetbrains-mono": {
1289 - "version": "5.0.14",
1290 - "resolved": "https://registry.npmjs.org/@fontsource/jetbrains-mono/-/jetbrains-mono-5.0.14.tgz",
1291 - "integrity": "sha512-hqo/zJSnzQZbN871j3LXOEfLGSqk6P7lEBnbOwUbLo8Xoyszt0Doo48+1ik1fpOU7NskPGiErnRVyKWCZG65QA=="
1261 + "version": "5.0.17",
1262 + "resolved": "https://registry.npmjs.org/@fontsource/jetbrains-mono/-/jetbrains-mono-5.0.17.tgz",
1263 + "integrity": "sha512-Y/EtdbwKwNQTGpnMrexX8SVW6Jqlh0nX2bNHI9Z9m6FsyjbocZIFNJqwSY9bDUoi7irGtz8nuidAN7FF8wYuJA=="
1264 },
1265 "node_modules/@fontsource/lexend": {
1294 - "version": "5.0.14",
1295 - "resolved": "https://registry.npmjs.org/@fontsource/lexend/-/lexend-5.0.14.tgz",
1296 - "integrity": "sha512-bjvKAWaV6STkMNa5ITQP+F1BSO5vNCfbhfJ0XWHSFFC50+JfElZywarmiaUDtsZSRP8qxadxmIr/j1ByDt/aqQ=="
1266 + "version": "5.0.17",
1267 + "resolved": "https://registry.npmjs.org/@fontsource/lexend/-/lexend-5.0.17.tgz",
1268 + "integrity": "sha512-3rtbeiOx4EqGxcOMfsgq23RRDbhdMGJULLdNCHCN6oAGN06WDesrH6ZL+r6ZF8fpdJZ63F0ViOj/PFG2kOtKdA=="
1269 },
1270 "node_modules/@fontsource/public-sans": {
1299 - "version": "5.0.12",
1300 - "resolved": "https://registry.npmjs.org/@fontsource/public-sans/-/public-sans-5.0.12.tgz",
1301 - "integrity": "sha512-V1Tir6MhBPYtMNVBmCIkJy9x81a/LMdwnTaS+sDq5mSBIANt1hnIKm/YOzM+qfdxdCnzEemRBXExoG3C8t57Dg=="
1271 + "version": "5.0.15",
1272 + "resolved": "https://registry.npmjs.org/@fontsource/public-sans/-/public-sans-5.0.15.tgz",
1273 + "integrity": "sha512-3UKtCVDbwt8FeurOHYBybDzYYJH0peyisGjsQe2aRFR4M693m0DdE3v4BZl+60OjvnXGWhO8O/rmET2kwPF6SQ=="
1274 },
1275 "node_modules/@fullcalendar/core": {
1276 "version": "6.1.9",
@@ -1377,12 +1349,12 @@
1349 }
1350 },
1351 "node_modules/@humanwhocodes/config-array": {
1380 - "version": "0.11.11",
1381 - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz",
1382 - "integrity": "sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==",
1352 + "version": "0.11.13",
1353 + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz",
1354 + "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==",
1355 "dev": true,
1356 "dependencies": {
1385 - "@humanwhocodes/object-schema": "^1.2.1",
1357 + "@humanwhocodes/object-schema": "^2.0.1",
1358 "debug": "^4.1.1",
1359 "minimatch": "^3.0.5"
1360 },
@@ -1404,18 +1376,39 @@
1376 }
1377 },
1378 "node_modules/@humanwhocodes/object-schema": {
1407 - "version": "1.2.1",
1408 - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz",
1409 - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
1379 + "version": "2.0.1",
1380 + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz",
1381 + "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==",
1382 "dev": true
1383 },
1384 + "node_modules/@iconify/types": {
1385 + "version": "2.0.0",
1386 + "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz",
1387 + "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==",
1388 + "dev": true
1389 + },
1390 + "node_modules/@iconify/vue": {
1391 + "version": "4.1.1",
1392 + "resolved": "https://registry.npmjs.org/@iconify/vue/-/vue-4.1.1.tgz",
1393 + "integrity": "sha512-RL85Bm/DAe8y6rT6pux7D2FJSiUEM/TPfyK7GrbAOfTSwrhvwJW+S5yijdGcmtXouA8MtuH9C7l4hiSE4mLMjg==",
1394 + "dev": true,
1395 + "dependencies": {
1396 + "@iconify/types": "^2.0.0"
1397 + },
1398 + "funding": {
1399 + "url": "https://github.com/sponsors/cyberalien"
1400 + },
1401 + "peerDependencies": {
1402 + "vue": ">=3"
1403 + }
1404 + },
1405 "node_modules/@intlify/core-base": {
1413 - "version": "9.5.0",
1414 - "resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.5.0.tgz",
1415 - "integrity": "sha512-y3ufM1RJbI/DSmJf3lYs9ACq3S/iRvaSsE3rPIk0MGH7fp+JxU6rdryv/EYcwfcr3Y1aHFlCBir6S391hRZ57w==",
1406 + "version": "9.6.2",
1407 + "resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.6.2.tgz",
1408 + "integrity": "sha512-ci0j2nbEL/pamvqgcCqyIVeQ3LS41F1IRqI5rCBNnpSp0FjNnH8bpha8R3OifkhqatzlP4wGOuN/UqfLYVDv7g==",
1409 "dependencies": {
1417 - "@intlify/message-compiler": "9.5.0",
1418 - "@intlify/shared": "9.5.0"
1410 + "@intlify/message-compiler": "9.6.2",
1411 + "@intlify/shared": "9.6.2"
1412 },
1413 "engines": {
1414 "node": ">= 16"
@@ -1425,11 +1418,11 @@
1418 }
1419 },
1420 "node_modules/@intlify/message-compiler": {
1428 - "version": "9.5.0",
1429 - "resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.5.0.tgz",
1430 - "integrity": "sha512-CAhVNfEZcOVFg0/5MNyt+OFjvs4J/ARjCj2b+54/FvFP0EDJI5lIqMTSDBE7k0atMROSP0SvWCkwu/AZ5xkK1g==",
1421 + "version": "9.6.2",
1422 + "resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.6.2.tgz",
1423 + "integrity": "sha512-kgZQL9zeJDeEB5vvD93Y++HvFUELnT48PjnpfCcF3EJaLLVs9he8IzODiNK42Z40lWbFyja0SXJZjsalybQygA==",
1424 "dependencies": {
1432 - "@intlify/shared": "9.5.0",
1425 + "@intlify/shared": "9.6.2",
1426 "source-map-js": "^1.0.2"
1427 },
1428 "engines": {
@@ -1440,25 +1433,9 @@
1433 }
1434 },
1435 "node_modules/@intlify/shared": {
1443 - "version": "9.5.0",
1444 - "resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.5.0.tgz",
1445 - "integrity": "sha512-tAxV14LMXZDZbu32XzLMTsowNlgJNmLwWHYzvMUl6L8gvQeoYiZONjY7AUsqZW8TOZDX9lfvF6adPkk9FSRdDA==",
1446 - "engines": {
1447 - "node": ">= 16"
1448 - },
1449 - "funding": {
1450 - "url": "https://github.com/sponsors/kazupon"
1451 - }
1452 - },
1453 - "node_modules/@intlify/vue-devtools": {
1454 - "version": "9.5.0",
1455 - "resolved": "https://registry.npmjs.org/@intlify/vue-devtools/-/vue-devtools-9.5.0.tgz",
1456 - "integrity": "sha512-OZ5HkCvhSEhU+wDY7G4TD0mbZw/ZwRdH7O7xVWYVa4ryrwUIoii6h5zS4tZj9rQY7xF2Hn+/0qxUsyOZPLYjHQ==",
1457 - "dev": true,
1458 - "dependencies": {
1459 - "@intlify/core-base": "9.5.0",
1460 - "@intlify/shared": "9.5.0"
1461 - },
1436 + "version": "9.6.2",
1437 + "resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.6.2.tgz",
1438 + "integrity": "sha512-9KBcXmJNxElp7QMnU8V0/tScTOitDqyFi4HceEZqJyyDkMi8K5DBPMTIuXIAMmtMlXpe/nj5pke7tRw97VeQRA==",
1439 "engines": {
1440 "node": ">= 16"
1441 },
@@ -1710,9 +1687,9 @@
1687 }
1688 },
1689 "node_modules/@maplibre/maplibre-gl-style-spec": {
1713 - "version": "19.3.1",
1714 - "resolved": "https://registry.npmjs.org/@maplibre/maplibre-gl-style-spec/-/maplibre-gl-style-spec-19.3.1.tgz",
1715 - "integrity": "sha512-ss5+b3/a8I1wD5PYmAYPYxg0Nag0cxvw4GGOnQroTP59sobTPI3KeHP9OjUr/es7uNtYEodr54fgoEnCBF6gaQ==",
1690 + "version": "19.3.3",
1691 + "resolved": "https://registry.npmjs.org/@maplibre/maplibre-gl-style-spec/-/maplibre-gl-style-spec-19.3.3.tgz",
1692 + "integrity": "sha512-cOZZOVhDSulgK0meTsTkmNXb1ahVvmTmWmfx9gRBwc6hq98wS9JP35ESIoNq3xqEan+UN+gn8187Z6E4NKhLsw==",
1693 "dependencies": {
1694 "@mapbox/jsonlint-lines-primitives": "~2.0.2",
1695 "@mapbox/unitbezier": "^0.0.1",
@@ -1728,15 +1705,15 @@
1705 }
1706 },
1707 "node_modules/@milkdown/core": {
1731 - "version": "7.3.0",
1732 - "resolved": "https://registry.npmjs.org/@milkdown/core/-/core-7.3.0.tgz",
1733 - "integrity": "sha512-spB2mti5glP/ZJROgNZuaVgDC0djhMEnd9eu5iEmm4qqUj4RabKDZkVox9kZE0ngn6msWtDW/qQwnIFd4iFGdQ==",
1708 + "version": "7.3.1",
1709 + "resolved": "https://registry.npmjs.org/@milkdown/core/-/core-7.3.1.tgz",
1710 + "integrity": "sha512-W4wxS87YkXbRMTs9DEWNQWQfbDpfDvJ6EJJA5t/4eMXYymVHEr9h2QsHI23Tiznthgro1X0+kW3nxtfWWHw2xA==",
1711 "dependencies": {
1735 - "@milkdown/exception": "7.3.0",
1736 - "remark-parse": "^10.0.1",
1737 - "remark-stringify": "^10.0.2",
1712 + "@milkdown/exception": "7.3.1",
1713 + "remark-parse": "^11.0.0",
1714 + "remark-stringify": "^11.0.0",
1715 "tslib": "^2.5.0",
1739 - "unified": "^10.1.0"
1716 + "unified": "^11.0.3"
1717 },
1718 "peerDependencies": {
1719 "@milkdown/ctx": "^7.2.0",
@@ -1745,31 +1722,31 @@
1722 }
1723 },
1724 "node_modules/@milkdown/ctx": {
1748 - "version": "7.3.0",
1749 - "resolved": "https://registry.npmjs.org/@milkdown/ctx/-/ctx-7.3.0.tgz",
1750 - "integrity": "sha512-VQG/Q0TrEcZ5HbTQrqiXgeOwZYv7qmnH8X5msS9GL8bvALGUHJ3QjxQONJrK53ZZ5HbHPSi7QOBDlAn57m69fg==",
1725 + "version": "7.3.1",
1726 + "resolved": "https://registry.npmjs.org/@milkdown/ctx/-/ctx-7.3.1.tgz",
1727 + "integrity": "sha512-Wmf7Bhz8AH2bz4sz38a4DY3jFsDlHaaQ4ZGFi9JzSr1NjFB+2dvc19RYyncF/P14qTwdJc9htzP/HD4mUKlSww==",
1728 "dependencies": {
1752 - "@milkdown/exception": "7.3.0",
1729 + "@milkdown/exception": "7.3.1",
1730 "tslib": "^2.5.0"
1731 }
1732 },
1733 "node_modules/@milkdown/exception": {
1757 - "version": "7.3.0",
1758 - "resolved": "https://registry.npmjs.org/@milkdown/exception/-/exception-7.3.0.tgz",
1759 - "integrity": "sha512-ZqN+3dOfTQ8OafkJz6WqNrNWxg3D6hACMa2XyPul+uVU0MLZXURfijCO26eByNlhG05w0Uo/FJfaapPZO3aE6g==",
1734 + "version": "7.3.1",
1735 + "resolved": "https://registry.npmjs.org/@milkdown/exception/-/exception-7.3.1.tgz",
1736 + "integrity": "sha512-e2x1h/zE8paoF9ygEbUVZZTNpp1acw6azqExSvL+3myoMgwTsmE/G6oK9YpajY1BfSsLM2J3ii9ByIcsRM6+9w==",
1737 "dependencies": {
1738 "tslib": "^2.5.0"
1739 }
1740 },
1741 "node_modules/@milkdown/preset-commonmark": {
1765 - "version": "7.3.0",
1766 - "resolved": "https://registry.npmjs.org/@milkdown/preset-commonmark/-/preset-commonmark-7.3.0.tgz",
1767 - "integrity": "sha512-9ehBLkiLomp2av6E7YiRwihx/YsZ+LR80Y21e+Qzs5d6eq+payhblKQSaYYlVECA7gwLIOy67Lu8jxkOnO++rw==",
1742 + "version": "7.3.1",
1743 + "resolved": "https://registry.npmjs.org/@milkdown/preset-commonmark/-/preset-commonmark-7.3.1.tgz",
1744 + "integrity": "sha512-8qiz/jQ4st6Sd3r7wuTSc9XCqsRqcOJFyI3N7Jt6I+Opi/JPE6AXiihsbbfPcN9y4w5w1MuuWH50fq6om5cXXA==",
1745 "dependencies": {
1769 - "@milkdown/exception": "7.3.0",
1770 - "@milkdown/utils": "7.3.0",
1746 + "@milkdown/exception": "7.3.1",
1747 + "@milkdown/utils": "7.3.1",
1748 "@sindresorhus/slugify": "^2.2.0",
1772 - "remark-inline-links": "^6.0.0",
1749 + "remark-inline-links": "^7.0.0",
1750 "tslib": "^2.5.0",
1751 "unist-util-visit": "^5.0.0"
1752 },
@@ -1781,11 +1758,11 @@
1758 }
1759 },
1760 "node_modules/@milkdown/prose": {
1784 - "version": "7.3.0",
1785 - "resolved": "https://registry.npmjs.org/@milkdown/prose/-/prose-7.3.0.tgz",
1786 - "integrity": "sha512-YUx30G++v9RYw6mM5ybvvezaJBdSyhrDsVeuAzYkBRfV2fwEV1airBt96BqY+FeICYqE1eTCUty/gaPzpcwbPg==",
1761 + "version": "7.3.1",
1762 + "resolved": "https://registry.npmjs.org/@milkdown/prose/-/prose-7.3.1.tgz",
1763 + "integrity": "sha512-l9xfPOYDwOnTrTCLYr328pUjg9gwNP5n1CKBVFlpkkGKdDVAfdRsH0QgzNHuw42amwDG+e+uu/s29HGqm9JZog==",
1764 "dependencies": {
1788 - "@milkdown/exception": "7.3.0",
1765 + "@milkdown/exception": "7.3.1",
1766 "prosemirror-changeset": "^2.2.1",
1767 "prosemirror-commands": "^1.5.2",
1768 "prosemirror-dropcursor": "^1.8.1",
@@ -1793,19 +1770,19 @@
1770 "prosemirror-history": "^1.3.2",
1771 "prosemirror-inputrules": "^1.2.1",
1772 "prosemirror-keymap": "^1.2.2",
1796 - "prosemirror-model": "^1.19.2",
1773 + "prosemirror-model": "^1.19.3",
1774 "prosemirror-schema-list": "^1.3.0",
1775 "prosemirror-state": "^1.4.3",
1776 "prosemirror-tables": "^1.3.4",
1800 - "prosemirror-transform": "^1.7.3",
1801 - "prosemirror-view": "^1.31.5",
1777 + "prosemirror-transform": "^1.7.5",
1778 + "prosemirror-view": "^1.31.7",
1779 "tslib": "^2.5.0"
1780 }
1781 },
1782 "node_modules/@milkdown/theme-nord": {
1806 - "version": "7.3.0",
1807 - "resolved": "https://registry.npmjs.org/@milkdown/theme-nord/-/theme-nord-7.3.0.tgz",
1808 - "integrity": "sha512-sXFY8wxn/38BbQJyrJQvj8/h0kKvrVr3q8U/lSzNoCywH1hQ/qapSFDs3HvXLSWOZm/V0CaIP7H/MwHKF8N6MA==",
1783 + "version": "7.3.1",
1784 + "resolved": "https://registry.npmjs.org/@milkdown/theme-nord/-/theme-nord-7.3.1.tgz",
1785 + "integrity": "sha512-vnloL2XXG9hDdHBkFES3GNi1UzLY7iahOVbuz/0N/m0LGRTkcvy0OUOKzE3csJ6j7ENQhAmv8PhFv60EdlVz0A==",
1786 "dependencies": {
1787 "clsx": "^2.0.0",
1788 "tslib": "^2.5.0"
@@ -1825,28 +1802,28 @@
1802 }
1803 },
1804 "node_modules/@milkdown/transformer": {
1828 - "version": "7.3.0",
1829 - "resolved": "https://registry.npmjs.org/@milkdown/transformer/-/transformer-7.3.0.tgz",
1830 - "integrity": "sha512-6QGutmJvg1sk3aVQGG4kX+MmemLnd0v1x7uoGeyOuQT4MIPAquxPmzECHwRJJal+eyhFLnssOZLzC6qF85b+bQ==",
1831 - "dependencies": {
1832 - "@milkdown/exception": "7.3.0",
1833 - "remark": "^14.0.1",
1834 - "remark-parse": "^10.0.2",
1835 - "remark-stringify": "^10.0.3",
1805 + "version": "7.3.1",
1806 + "resolved": "https://registry.npmjs.org/@milkdown/transformer/-/transformer-7.3.1.tgz",
1807 + "integrity": "sha512-vFGnoOxqVnMNnu+JGs+bxMgIEC1RYhaWWKj1LtpwQQlHTX3PUCN823gF6MjH5R4L98HIg2Ysc7GbE1O7JoL3pw==",
1808 + "dependencies": {
1809 + "@milkdown/exception": "7.3.1",
1810 + "remark": "^15.0.1",
1811 + "remark-parse": "^11.0.0",
1812 + "remark-stringify": "^11.0.0",
1813 "tslib": "^2.5.0",
1837 - "unified": "^10.1.0"
1814 + "unified": "^11.0.3"
1815 },
1816 "peerDependencies": {
1817 "@milkdown/prose": "^7.2.0"
1818 }
1819 },
1820 "node_modules/@milkdown/utils": {
1844 - "version": "7.3.0",
1845 - "resolved": "https://registry.npmjs.org/@milkdown/utils/-/utils-7.3.0.tgz",
1846 - "integrity": "sha512-hOb6UwRIX2e7lpAn3sjAtsF2idzQN/wQ9MLbjoIevQXjO1EgNYiBTFIfTDnI4ka+VrVScvZtZ2D8cAA+3+ndxw==",
1821 + "version": "7.3.1",
1822 + "resolved": "https://registry.npmjs.org/@milkdown/utils/-/utils-7.3.1.tgz",
1823 + "integrity": "sha512-GT54XYHwnHjHVmH3vBcCFwlzZhdaM3S+1Zp1+ci5bcfTX0Rmt1KUyzkeBJOcSDS4jCm8eTYd2kBCzodZOHqm9A==",
1824 "dependencies": {
1848 - "@milkdown/exception": "7.3.0",
1849 - "nanoid": "^4.0.0",
1825 + "@milkdown/exception": "7.3.1",
1826 + "nanoid": "^5.0.0",
1827 "tslib": "^2.5.0"
1828 },
1829 "peerDependencies": {
@@ -1857,11 +1834,11 @@
1834 }
1835 },
1836 "node_modules/@milkdown/vue": {
1860 - "version": "7.3.0",
1861 - "resolved": "https://registry.npmjs.org/@milkdown/vue/-/vue-7.3.0.tgz",
1862 - "integrity": "sha512-eNHlYPcisSZ4p8JjGemPPox5h4px/h6Ln9yf6Oo25wBMRyTtyuBZ7mWk2OfSxk6A9Ay5Qm6rOnR6ok7OI1x3bw==",
1837 + "version": "7.3.1",
1838 + "resolved": "https://registry.npmjs.org/@milkdown/vue/-/vue-7.3.1.tgz",
1839 + "integrity": "sha512-JkzmIlRlran60VYELOqCjEOqpenQEIgv/m5sKlfjP0c2AVBBl4XnBDdsS9BswjF/l50JlqA3MoqPzzg/eTdmEA==",
1840 "dependencies": {
1864 - "@milkdown/utils": "7.3.0",
1841 + "@milkdown/utils": "7.3.1",
1842 "tslib": "^2.5.0"
1843 },
1844 "peerDependencies": {
@@ -1983,9 +1960,9 @@
1960 }
1961 },
1962 "node_modules/@npmcli/config": {
1986 - "version": "6.3.0",
1987 - "resolved": "https://registry.npmjs.org/@npmcli/config/-/config-6.3.0.tgz",
1988 - "integrity": "sha512-gV64pm5cQ7F2oeoSJ5HTfaKxjFsvC4dAbCsQbtbOkEOymM6iZI62yNGCOLjcq/rfYX9+wVn34ThxK7GZpUwWFg==",
1963 + "version": "8.0.1",
1964 + "resolved": "https://registry.npmjs.org/@npmcli/config/-/config-8.0.1.tgz",
1965 + "integrity": "sha512-NKGmMYv/YTLwJr+qK9CvADSe82NTM9AFwFFpsZpVcPCT3XTdxvJBdXi8xvXWjHSCMb0Cb+7FtU/a5qqguCOhxA==",
1966 "dev": true,
1967 "dependencies": {
1968 "@npmcli/map-workspaces": "^3.0.2",
@@ -1998,7 +1975,7 @@
1975 "walk-up-path": "^3.0.1"
1976 },
1977 "engines": {
2001 - "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
1978 + "node": "^16.14.0 || >=18.0.0"
1979 }
1980 },
1981 "node_modules/@npmcli/config/node_modules/abbrev": {
@@ -2458,9 +2435,9 @@
2435 }
2436 },
2437 "node_modules/@revolist/revogrid": {
2461 - "version": "4.7.0-next.3",
2462 - "resolved": "https://registry.npmjs.org/@revolist/revogrid/-/revogrid-4.7.0-next.3.tgz",
2463 - "integrity": "sha512-MOwnLjnbiM42XrvtXm6DFhshvojTj6Zs3jNQaFqov4uzJS4RUkvqulUxkFd1AvDpll7PKgCIn+e7EkB8zXpMOQ==",
2438 + "version": "4.7.0-next.4",
2439 + "resolved": "https://registry.npmjs.org/@revolist/revogrid/-/revogrid-4.7.0-next.4.tgz",
2440 + "integrity": "sha512-4ypmIDmIEWgIGFghvwgMmCeDLS1OOVX3PO0LmPTjV4DhobTlWePGtYjBerdnQocunccbf/dh5Iy4460u3cAnCA==",
2441 "dependencies": {
2442 "@stencil/core": "^4.3.0",
2443 "lodash": "^4.17.21"
@@ -2671,9 +2648,9 @@
2648 }
2649 },
2650 "node_modules/@tiptap/core": {
2674 - "version": "2.1.11",
2675 - "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-2.1.11.tgz",
2676 - "integrity": "sha512-1W2DdjpPwfphHgQ3Qm4s5wzCnEjiXm1TeZ+6/zBl89yKURXgv8Mw1JGdj/NcImQjtDcsNn97MscACK3GKbEJBA==",
2651 + "version": "2.1.12",
2652 + "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-2.1.12.tgz",
2653 + "integrity": "sha512-ZGc3xrBJA9KY8kln5AYTj8y+GDrKxi7u95xIl2eccrqTY5CQeRu6HRNM1yT4mAjuSaG9jmazyjGRlQuhyxCKxQ==",
2654 "funding": {
2655 "type": "github",
2656 "url": "https://github.com/sponsors/ueberdosis"
@@ -2683,9 +2660,9 @@
2660 }
2661 },
2662 "node_modules/@tiptap/extension-blockquote": {
2686 - "version": "2.1.11",
2687 - "resolved": "https://registry.npmjs.org/@tiptap/extension-blockquote/-/extension-blockquote-2.1.11.tgz",
2688 - "integrity": "sha512-IEVe3goA0rgp1G8Wm733BSRJiy71Vh2fmTCyZKWmc2A6GREVSy1X3fCvAo6pMENRObhjIoaBQUCE3p4iJYOxqg==",
2663 + "version": "2.1.12",
2664 + "resolved": "https://registry.npmjs.org/@tiptap/extension-blockquote/-/extension-blockquote-2.1.12.tgz",
2665 + "integrity": "sha512-Qb3YRlCfugx9pw7VgLTb+jY37OY4aBJeZnqHzx4QThSm13edNYjasokbX0nTwL1Up4NPTcY19JUeHt6fVaVVGg==",
2666 "funding": {
2667 "type": "github",
2668 "url": "https://github.com/sponsors/ueberdosis"
@@ -2695,9 +2672,9 @@
2672 }
2673 },
2674 "node_modules/@tiptap/extension-bold": {
2698 - "version": "2.1.11",
2699 - "resolved": "https://registry.npmjs.org/@tiptap/extension-bold/-/extension-bold-2.1.11.tgz",
2700 - "integrity": "sha512-vhdkBtvd029ufOYt2ug49Gz+RLKSczO/CCqKYBqBmpIpsifyK7M6jkgamvAQg3c/vYk0LNcKiL2dp0Jp7L+5Gw==",
2675 + "version": "2.1.12",
2676 + "resolved": "https://registry.npmjs.org/@tiptap/extension-bold/-/extension-bold-2.1.12.tgz",
2677 + "integrity": "sha512-AZGxIxcGU1/y6V2YEbKsq6BAibL8yQrbRm6EdcBnby41vj1WziewEKswhLGmZx5IKM2r2ldxld03KlfSIlKQZg==",
2678 "funding": {
2679 "type": "github",
2680 "url": "https://github.com/sponsors/ueberdosis"
@@ -2707,9 +2684,9 @@
2684 }
2685 },
2686 "node_modules/@tiptap/extension-bubble-menu": {
2710 - "version": "2.1.11",
2711 - "resolved": "https://registry.npmjs.org/@tiptap/extension-bubble-menu/-/extension-bubble-menu-2.1.11.tgz",
2712 - "integrity": "sha512-WFJJpZvl9DP94Y5RQZB/THDxvDbrTo8tuhjT7yWlhseJ6zyhWmRXdutt39wfSZNFxitv/As+s7cO9aYLML/TVg==",
2687 + "version": "2.1.12",
2688 + "resolved": "https://registry.npmjs.org/@tiptap/extension-bubble-menu/-/extension-bubble-menu-2.1.12.tgz",
2689 + "integrity": "sha512-gAGi21EQ4wvLmT7klgariAc2Hf+cIjaNU2NWze3ut6Ku9gUo5ZLqj1t9SKHmNf4d5JG63O8GxpErqpA7lHlRtw==",
2690 "dependencies": {
2691 "tippy.js": "^6.3.7"
2692 },
@@ -2723,9 +2700,9 @@
2700 }
2701 },
2702 "node_modules/@tiptap/extension-bullet-list": {
2726 - "version": "2.1.11",
2727 - "resolved": "https://registry.npmjs.org/@tiptap/extension-bullet-list/-/extension-bullet-list-2.1.11.tgz",
2728 - "integrity": "sha512-SOOVH2aSmdMtjWL7TTLbN72xbAFz2G5jifT4UCXb7Qx6LsyhNCyDCu0ukOW8rSosGoSdmBXxAsD9sBJ1jEOmZw==",
2703 + "version": "2.1.12",
2704 + "resolved": "https://registry.npmjs.org/@tiptap/extension-bullet-list/-/extension-bullet-list-2.1.12.tgz",
2705 + "integrity": "sha512-vtD8vWtNlmAZX8LYqt2yU9w3mU9rPCiHmbp4hDXJs2kBnI0Ju/qAyXFx6iJ3C3XyuMnMbJdDI9ee0spAvFz7cQ==",
2706 "funding": {
2707 "type": "github",
2708 "url": "https://github.com/sponsors/ueberdosis"
@@ -2735,9 +2712,9 @@
2712 }
2713 },
2714 "node_modules/@tiptap/extension-character-count": {
2738 - "version": "2.1.11",
2739 - "resolved": "https://registry.npmjs.org/@tiptap/extension-character-count/-/extension-character-count-2.1.11.tgz",
2740 - "integrity": "sha512-qR50YtvY+hgskUQSlHl/Bitx6xPJVQ2wuNWdw48s8LOjrE2cqAmAj7BmeBrh9481EbhgXZrt3m6UygFiMfiCiA==",
2715 + "version": "2.1.12",
2716 + "resolved": "https://registry.npmjs.org/@tiptap/extension-character-count/-/extension-character-count-2.1.12.tgz",
2717 + "integrity": "sha512-+GFbBG13nvF8mFIeisSERG/Q3CuRsTNwVZIRbJTLgGdbHXFqPhJh4Xfm7cv7OaOYevUlVyO+z5pGD7wIl1bLqQ==",
2718 "funding": {
2719 "type": "github",
2720 "url": "https://github.com/sponsors/ueberdosis"
@@ -2748,9 +2725,9 @@
2725 }
2726 },
2727 "node_modules/@tiptap/extension-code": {
2751 - "version": "2.1.11",
2752 - "resolved": "https://registry.npmjs.org/@tiptap/extension-code/-/extension-code-2.1.11.tgz",
2753 - "integrity": "sha512-G0UEbMFunujy/F86yHN0/dumPLbwTis9C+6IQv1XRPNsV28U0MgxBhlPcJUgyO5lwuleePDxiBVcRv2XrysgKw==",
2728 + "version": "2.1.12",
2729 + "resolved": "https://registry.npmjs.org/@tiptap/extension-code/-/extension-code-2.1.12.tgz",
2730 + "integrity": "sha512-CRiRq5OTC1lFgSx6IMrECqmtb93a0ZZKujEnaRhzWliPBjLIi66va05f/P1vnV6/tHaC3yfXys6dxB5A4J8jxw==",
2731 "funding": {
2732 "type": "github",
2733 "url": "https://github.com/sponsors/ueberdosis"
@@ -2760,9 +2737,9 @@
2737 }
2738 },
2739 "node_modules/@tiptap/extension-code-block": {
2763 - "version": "2.1.11",
2764 - "resolved": "https://registry.npmjs.org/@tiptap/extension-code-block/-/extension-code-block-2.1.11.tgz",
2765 - "integrity": "sha512-QhmhCCWqg/5qLXpZ3sl2A0rqJqV8zMOegcxUFaqcJMOqNbsuHcRgc9C+1hWSVLbCmstB7M6sgF02QpTBOkYHxg==",
2740 + "version": "2.1.12",
2741 + "resolved": "https://registry.npmjs.org/@tiptap/extension-code-block/-/extension-code-block-2.1.12.tgz",
2742 + "integrity": "sha512-RXtSYCVsnk8D+K80uNZShClfZjvv1EgO42JlXLVGWQdIgaNyuOv/6I/Jdf+ZzhnpsBnHufW+6TJjwP5vJPSPHA==",
2743 "funding": {
2744 "type": "github",
2745 "url": "https://github.com/sponsors/ueberdosis"
@@ -2773,9 +2750,9 @@
2750 }
2751 },
2752 "node_modules/@tiptap/extension-document": {
2776 - "version": "2.1.11",
2777 - "resolved": "https://registry.npmjs.org/@tiptap/extension-document/-/extension-document-2.1.11.tgz",
2778 - "integrity": "sha512-L/iLuqxvJep33ycCFNrnUhdR0VtcZyeNnqB+ZvVHzEwLoRud+LBy44lpEdBrAFsvRm3DG14m/FGYL+TfaD0vxA==",
2753 + "version": "2.1.12",
2754 + "resolved": "https://registry.npmjs.org/@tiptap/extension-document/-/extension-document-2.1.12.tgz",
2755 + "integrity": "sha512-0QNfAkCcFlB9O8cUNSwTSIQMV9TmoEhfEaLz/GvbjwEq4skXK3bU+OQX7Ih07waCDVXIGAZ7YAZogbvrn/WbOw==",
2756 "funding": {
2757 "type": "github",
2758 "url": "https://github.com/sponsors/ueberdosis"
@@ -2785,9 +2762,9 @@
2762 }
2763 },
2764 "node_modules/@tiptap/extension-dropcursor": {
2788 - "version": "2.1.11",
2789 - "resolved": "https://registry.npmjs.org/@tiptap/extension-dropcursor/-/extension-dropcursor-2.1.11.tgz",
2790 - "integrity": "sha512-MiJepRpHlu93aInOMW8NeRCvm9VE5rL0MA9TONY/IspJFGFIqonc/01J6t33JQa3Xh/x3xAfis4nKa/UazeVJw==",
2765 + "version": "2.1.12",
2766 + "resolved": "https://registry.npmjs.org/@tiptap/extension-dropcursor/-/extension-dropcursor-2.1.12.tgz",
2767 + "integrity": "sha512-0tT/q8nL4NBCYPxr9T0Brck+RQbWuczm9nV0bnxgt0IiQXoRHutfPWdS7GA65PTuVRBS/3LOco30fbjFhkfz/A==",
2768 "funding": {
2769 "type": "github",
2770 "url": "https://github.com/sponsors/ueberdosis"
@@ -2798,9 +2775,9 @@
2775 }
2776 },
2777 "node_modules/@tiptap/extension-floating-menu": {
2801 - "version": "2.1.11",
2802 - "resolved": "https://registry.npmjs.org/@tiptap/extension-floating-menu/-/extension-floating-menu-2.1.11.tgz",
2803 - "integrity": "sha512-ExeoOQ6nT0CY0eWx6WjbG+osurXLXa7XrqIdhCAcTmzBAlGiKt8khX9qaZ+QF+BRK1r1lja2KX+5/fpLK7Dt1g==",
2778 + "version": "2.1.12",
2779 + "resolved": "https://registry.npmjs.org/@tiptap/extension-floating-menu/-/extension-floating-menu-2.1.12.tgz",
2780 + "integrity": "sha512-uo0ydCJNg6AWwLT6cMUJYVChfvw2PY9ZfvKRhh9YJlGfM02jS4RUG/bJBts6R37f+a5FsOvAVwg8EvqPlNND1A==",
2781 "dependencies": {
2782 "tippy.js": "^6.3.7"
2783 },
@@ -2814,9 +2791,9 @@
2791 }
2792 },
2793 "node_modules/@tiptap/extension-gapcursor": {
2817 - "version": "2.1.11",
2818 - "resolved": "https://registry.npmjs.org/@tiptap/extension-gapcursor/-/extension-gapcursor-2.1.11.tgz",
2819 - "integrity": "sha512-P/xjyhSOVyop5XXbNtRPgrooQrSlpYblwR67ClI9FAC7uQliuOwi5VcndmEItjWWSe85kJa2IHjOS7mLYvJe8A==",
2794 + "version": "2.1.12",
2795 + "resolved": "https://registry.npmjs.org/@tiptap/extension-gapcursor/-/extension-gapcursor-2.1.12.tgz",
2796 + "integrity": "sha512-zFYdZCqPgpwoB7whyuwpc8EYLYjUE5QYKb8vICvc+FraBUDM51ujYhFSgJC3rhs8EjI+8GcK8ShLbSMIn49YOQ==",
2797 "funding": {
2798 "type": "github",
2799 "url": "https://github.com/sponsors/ueberdosis"
@@ -2827,9 +2804,9 @@
2804 }
2805 },
2806 "node_modules/@tiptap/extension-hard-break": {
2830 - "version": "2.1.11",
2831 - "resolved": "https://registry.npmjs.org/@tiptap/extension-hard-break/-/extension-hard-break-2.1.11.tgz",
2832 - "integrity": "sha512-qhiPe6FA0b6PPb/ITlgSnY0l9tEVmXZ9e7eSjvks12ORfqL/dofSCLtChHWvhZxugzo92xejG2hXLi6lyOLbkg==",
2807 + "version": "2.1.12",
2808 + "resolved": "https://registry.npmjs.org/@tiptap/extension-hard-break/-/extension-hard-break-2.1.12.tgz",
2809 + "integrity": "sha512-nqKcAYGEOafg9D+2cy1E4gHNGuL12LerVa0eS2SQOb+PT8vSel9OTKU1RyZldsWSQJ5rq/w4uIjmLnrSR2w6Yw==",
2810 "funding": {
2811 "type": "github",
2812 "url": "https://github.com/sponsors/ueberdosis"
@@ -2839,9 +2816,9 @@
2816 }
2817 },
2818 "node_modules/@tiptap/extension-heading": {
2842 - "version": "2.1.11",
2843 - "resolved": "https://registry.npmjs.org/@tiptap/extension-heading/-/extension-heading-2.1.11.tgz",
2844 - "integrity": "sha512-QBtl0S1aDFB+F1wvTrS5iGdNUEeXp+WuTddj+L2f5EP4KqG2x7sj7e7ENMy20g/l8tbKwzd3AZZydvClH4Ybbw==",
2819 + "version": "2.1.12",
2820 + "resolved": "https://registry.npmjs.org/@tiptap/extension-heading/-/extension-heading-2.1.12.tgz",
2821 + "integrity": "sha512-MoANP3POAP68Ko9YXarfDKLM/kXtscgp6m+xRagPAghRNujVY88nK1qBMZ3JdvTVN6b/ATJhp8UdrZX96TLV2w==",
2822 "funding": {
2823 "type": "github",
2824 "url": "https://github.com/sponsors/ueberdosis"
@@ -2851,9 +2828,9 @@
2828 }
2829 },
2830 "node_modules/@tiptap/extension-highlight": {
2854 - "version": "2.1.11",
2855 - "resolved": "https://registry.npmjs.org/@tiptap/extension-highlight/-/extension-highlight-2.1.11.tgz",
2856 - "integrity": "sha512-pcs55B1lF2vyQ8VvZob9CsYdbFgVpIfG3+qchLsA1WflUJCcIexstTclWTS9N5UocADg4hBOeerZ4ecq1iXs3w==",
2831 + "version": "2.1.12",
2832 + "resolved": "https://registry.npmjs.org/@tiptap/extension-highlight/-/extension-highlight-2.1.12.tgz",
2833 + "integrity": "sha512-buen31cYPyiiHA2i0o2i/UcjRTg/42mNDCizGr1OJwvv3AELG3qOFc4Y58WJWIvWNv+1Dr4ZxHA3GNVn0ANWyg==",
2834 "funding": {
2835 "type": "github",
2836 "url": "https://github.com/sponsors/ueberdosis"
@@ -2863,9 +2840,9 @@
2840 }
2841 },
2842 "node_modules/@tiptap/extension-history": {
2866 - "version": "2.1.11",
2867 - "resolved": "https://registry.npmjs.org/@tiptap/extension-history/-/extension-history-2.1.11.tgz",
2868 - "integrity": "sha512-88dovV2O9icmBn0IvaArFFeS6X5ts6BxZPu5VbGML8KBL8iAu+Og7RXEPdOy5e13K0K4V21fDpO3n7KdvNOAYQ==",
2843 + "version": "2.1.12",
2844 + "resolved": "https://registry.npmjs.org/@tiptap/extension-history/-/extension-history-2.1.12.tgz",
2845 + "integrity": "sha512-6b7UFVkvPjq3LVoCTrYZAczt5sQrQUaoDWAieVClVZoFLfjga2Fwjcfgcie8IjdPt8YO2hG/sar/c07i9vM0Sg==",
2846 "funding": {
2847 "type": "github",
2848 "url": "https://github.com/sponsors/ueberdosis"
@@ -2876,9 +2853,9 @@
2853 }
2854 },
2855 "node_modules/@tiptap/extension-horizontal-rule": {
2879 - "version": "2.1.11",
2880 - "resolved": "https://registry.npmjs.org/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-2.1.11.tgz",
2881 - "integrity": "sha512-uvHPa2YCKnDhtSBSZB3lk5U4H3wRKP0DNvVx4Y2F7MdQianVzcyOd1pZYO9BQs+lUB1aZots6doE69Zqz3mU2Q==",
2856 + "version": "2.1.12",
2857 + "resolved": "https://registry.npmjs.org/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-2.1.12.tgz",
2858 + "integrity": "sha512-RRuoK4KxrXRrZNAjJW5rpaxjiP0FJIaqpi7nFbAua2oHXgsCsG8qbW2Y0WkbIoS8AJsvLZ3fNGsQ8gpdliuq3A==",
2859 "funding": {
2860 "type": "github",
2861 "url": "https://github.com/sponsors/ueberdosis"
@@ -2889,9 +2866,9 @@
2866 }
2867 },
2868 "node_modules/@tiptap/extension-italic": {
2892 - "version": "2.1.11",
2893 - "resolved": "https://registry.npmjs.org/@tiptap/extension-italic/-/extension-italic-2.1.11.tgz",
2894 - "integrity": "sha512-QmDsHtnBBit/1KtQpBPxjSPjDC1mVKtoNTgsEwMWK6YAkCKOKPj7oPEqqjaNZIRMKPPzE5XCsfBoS3jtVmo+6A==",
2869 + "version": "2.1.12",
2870 + "resolved": "https://registry.npmjs.org/@tiptap/extension-italic/-/extension-italic-2.1.12.tgz",
2871 + "integrity": "sha512-/XYrW4ZEWyqDvnXVKbgTXItpJOp2ycswk+fJ3vuexyolO6NSs0UuYC6X4f+FbHYL5VuWqVBv7EavGa+tB6sl3A==",
2872 "funding": {
2873 "type": "github",
2874 "url": "https://github.com/sponsors/ueberdosis"
@@ -2901,9 +2878,9 @@
2878 }
2879 },
2880 "node_modules/@tiptap/extension-link": {
2904 - "version": "2.1.11",
2905 - "resolved": "https://registry.npmjs.org/@tiptap/extension-link/-/extension-link-2.1.11.tgz",
2906 - "integrity": "sha512-Dn8hq4ld8br53fE4/QUZ7/y6ejY/kqAxeNhtud+OZKRs6VRn/CQd0H6A26opL+mKAK0kzrs0rh7rJPpHvahx/Q==",
2881 + "version": "2.1.12",
2882 + "resolved": "https://registry.npmjs.org/@tiptap/extension-link/-/extension-link-2.1.12.tgz",
2883 + "integrity": "sha512-Sti5hhlkCqi5vzdQjU/gbmr8kb578p+u0J4kWS+SSz3BknNThEm/7Id67qdjBTOQbwuN07lHjDaabJL0hSkzGQ==",
2884 "dependencies": {
2885 "linkifyjs": "^4.1.0"
2886 },
@@ -2917,9 +2894,9 @@
2894 }
2895 },
2896 "node_modules/@tiptap/extension-list-item": {
2920 - "version": "2.1.11",
2921 - "resolved": "https://registry.npmjs.org/@tiptap/extension-list-item/-/extension-list-item-2.1.11.tgz",
2922 - "integrity": "sha512-YhwHaPGhffsFsg/zjCu1G24//j/BTRDRZbZXmMwp77m1yEqPULcWyoWrI+gUzetQxJRD/ruAucqjLtoLLfICmQ==",
2897 + "version": "2.1.12",
2898 + "resolved": "https://registry.npmjs.org/@tiptap/extension-list-item/-/extension-list-item-2.1.12.tgz",
2899 + "integrity": "sha512-Gk7hBFofAPmNQ8+uw8w5QSsZOMEGf7KQXJnx5B022YAUJTYYxO3jYVuzp34Drk9p+zNNIcXD4kc7ff5+nFOTrg==",
2900 "funding": {
2901 "type": "github",
2902 "url": "https://github.com/sponsors/ueberdosis"
@@ -2929,9 +2906,9 @@
2906 }
2907 },
2908 "node_modules/@tiptap/extension-ordered-list": {
2932 - "version": "2.1.11",
2933 - "resolved": "https://registry.npmjs.org/@tiptap/extension-ordered-list/-/extension-ordered-list-2.1.11.tgz",
2934 - "integrity": "sha512-/tghfEJ5U7WFbF8xyOqRJks8KxP/lRjnroMXMglaushSMx8PYPo1dZDB/dJZw7ksy47MAaKJfKlx3gyN2CPXBQ==",
2909 + "version": "2.1.12",
2910 + "resolved": "https://registry.npmjs.org/@tiptap/extension-ordered-list/-/extension-ordered-list-2.1.12.tgz",
2911 + "integrity": "sha512-tF6VGl+D2avCgn9U/2YLJ8qVmV6sPE/iEzVAFZuOSe6L0Pj7SQw4K6AO640QBob/d8VrqqJFHCb6l10amJOnXA==",
2912 "funding": {
2913 "type": "github",
2914 "url": "https://github.com/sponsors/ueberdosis"
@@ -2941,9 +2918,9 @@
2918 }
2919 },
2920 "node_modules/@tiptap/extension-paragraph": {
2944 - "version": "2.1.11",
2945 - "resolved": "https://registry.npmjs.org/@tiptap/extension-paragraph/-/extension-paragraph-2.1.11.tgz",
2946 - "integrity": "sha512-gXMgJ2CU3X4yh1wKnb8RdbDmhITB76pH6DX0uWprmEgvzNMN3Qw+h5uBD9lgxg1WVghbCmkG9mY9J4PPbPTLxw==",
2921 + "version": "2.1.12",
2922 + "resolved": "https://registry.npmjs.org/@tiptap/extension-paragraph/-/extension-paragraph-2.1.12.tgz",
2923 + "integrity": "sha512-hoH/uWPX+KKnNAZagudlsrr4Xu57nusGekkJWBcrb5MCDE91BS+DN2xifuhwXiTHxnwOMVFjluc0bPzQbkArsw==",
2924 "funding": {
2925 "type": "github",
2926 "url": "https://github.com/sponsors/ueberdosis"
@@ -2953,9 +2930,9 @@
2930 }
2931 },
2932 "node_modules/@tiptap/extension-strike": {
2956 - "version": "2.1.11",
2957 - "resolved": "https://registry.npmjs.org/@tiptap/extension-strike/-/extension-strike-2.1.11.tgz",
2958 - "integrity": "sha512-UnjeSVgu3bDuyjjUdWsUErRCoQKAHCzH/pAiqTEPEEdFYgZFQPBpcJICRVdlYjRmI2ZKh6d0TMUS55m7ckmwmQ==",
2933 + "version": "2.1.12",
2934 + "resolved": "https://registry.npmjs.org/@tiptap/extension-strike/-/extension-strike-2.1.12.tgz",
2935 + "integrity": "sha512-HlhrzIjYUT8oCH9nYzEL2QTTn8d1ECnVhKvzAe6x41xk31PjLMHTUy8aYjeQEkWZOWZ34tiTmslV1ce6R3Dt8g==",
2936 "funding": {
2937 "type": "github",
2938 "url": "https://github.com/sponsors/ueberdosis"
@@ -2965,9 +2942,9 @@
2942 }
2943 },
2944 "node_modules/@tiptap/extension-task-item": {
2968 - "version": "2.1.11",
2969 - "resolved": "https://registry.npmjs.org/@tiptap/extension-task-item/-/extension-task-item-2.1.11.tgz",
2970 - "integrity": "sha512-721inc/MAZkljPup/EWCpNho4nf+XrYVKWRixqgX+AjikusTJefylbiZ5OeRn+71osTA7SdnXiKkM2ZbHtAsYA==",
2945 + "version": "2.1.12",
2946 + "resolved": "https://registry.npmjs.org/@tiptap/extension-task-item/-/extension-task-item-2.1.12.tgz",
2947 + "integrity": "sha512-uqrDTO4JwukZUt40GQdvB6S+oDhdp4cKNPMi0sbteWziQugkSMLlkYvxU0Hfb/YeziaWWwFI7ssPu/hahyk6dQ==",
2948 "funding": {
2949 "type": "github",
2950 "url": "https://github.com/sponsors/ueberdosis"
@@ -2978,9 +2955,9 @@
2955 }
2956 },
2957 "node_modules/@tiptap/extension-task-list": {
2981 - "version": "2.1.11",
2982 - "resolved": "https://registry.npmjs.org/@tiptap/extension-task-list/-/extension-task-list-2.1.11.tgz",
2983 - "integrity": "sha512-9C1M9N3jbNjm4001mPkgwUH19b6ZvKj5nnRT3zib/gFIQLOnSHE3VErDPHP/lkkjH84LgOMrm69cm8chQpgNsA==",
2958 + "version": "2.1.12",
2959 + "resolved": "https://registry.npmjs.org/@tiptap/extension-task-list/-/extension-task-list-2.1.12.tgz",
2960 + "integrity": "sha512-BUpYlEWK+Q3kw9KIiOqvhd0tUPhMcOf1+fJmCkluJok+okAxMbP1umAtCEQ3QkoCwLr+vpHJov7h3yi9+dwgeQ==",
2961 "funding": {
2962 "type": "github",
2963 "url": "https://github.com/sponsors/ueberdosis"
@@ -2990,9 +2967,9 @@
2967 }
2968 },
2969 "node_modules/@tiptap/extension-text": {
2993 - "version": "2.1.11",
2994 - "resolved": "https://registry.npmjs.org/@tiptap/extension-text/-/extension-text-2.1.11.tgz",
2995 - "integrity": "sha512-Iey0EXYv9079+lbHMvZtLc6XcYfKrq++msEXuFFNHxvL0i/XzndhGf+qlDhLROLgEtDiiTqzOBBwFCGlFjbDow==",
2970 + "version": "2.1.12",
2971 + "resolved": "https://registry.npmjs.org/@tiptap/extension-text/-/extension-text-2.1.12.tgz",
2972 + "integrity": "sha512-rCNUd505p/PXwU9Jgxo4ZJv4A3cIBAyAqlx/dtcY6cjztCQuXJhuQILPhjGhBTOLEEL4kW2wQtqzCmb7O8i2jg==",
2973 "funding": {
2974 "type": "github",
2975 "url": "https://github.com/sponsors/ueberdosis"
@@ -3002,9 +2979,21 @@
2979 }
2980 },
2981 "node_modules/@tiptap/extension-text-align": {
3005 - "version": "2.1.11",
3006 - "resolved": "https://registry.npmjs.org/@tiptap/extension-text-align/-/extension-text-align-2.1.11.tgz",
3007 - "integrity": "sha512-mRUouUZviU7OjzMbW5O728HsRl/T/Gue4DuNWaY2hiddlJWOpDmO/FYRR7JaAQjTr+16NCofRwgfWdJL3nyv5w==",
2982 + "version": "2.1.12",
2983 + "resolved": "https://registry.npmjs.org/@tiptap/extension-text-align/-/extension-text-align-2.1.12.tgz",
2984 + "integrity": "sha512-siMlwrkgVrAxxgmZn8GOc75J7UZi2CVrP9vDHkUPPyKm/fjssYekXwGCEk4Vswii1BbOh2gt+MDsRkeYRGyDlQ==",
2985 + "funding": {
2986 + "type": "github",
2987 + "url": "https://github.com/sponsors/ueberdosis"
2988 + },
2989 + "peerDependencies": {
2990 + "@tiptap/core": "^2.0.0"
2991 + }
2992 + },
2993 + "node_modules/@tiptap/extension-underline": {
2994 + "version": "2.1.12",
2995 + "resolved": "https://registry.npmjs.org/@tiptap/extension-underline/-/extension-underline-2.1.12.tgz",
2996 + "integrity": "sha512-NwwdhFT8gDD0VUNLQx85yFBhP9a8qg8GPuxlGzAP/lPTV8Ubh3vSeQ5N9k2ZF/vHlEvnugzeVCbmYn7wf8vn1g==",
2997 "funding": {
2998 "type": "github",
2999 "url": "https://github.com/sponsors/ueberdosis"
@@ -3014,9 +3003,9 @@
3003 }
3004 },
3005 "node_modules/@tiptap/pm": {
3017 - "version": "2.1.11",
3018 - "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-2.1.11.tgz",
3019 - "integrity": "sha512-vBIAic+H8fjHfT8r2qJkAOxdx1Iiss9+qMyujAoIdPkiyjEc4+sXcM0qSYgIr6KL5icITyuK8J7x/V62VfB7Uw==",
3006 + "version": "2.1.12",
3007 + "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-2.1.12.tgz",
3008 + "integrity": "sha512-Q3MXXQABG4CZBesSp82yV84uhJh/W0Gag6KPm2HRWPimSFELM09Z9/5WK9RItAYE0aLhe4Krnyiczn9AAa1tQQ==",
3009 "dependencies": {
3010 "prosemirror-changeset": "^2.2.0",
3011 "prosemirror-collab": "^1.3.0",
@@ -3043,29 +3032,29 @@
3032 }
3033 },
3034 "node_modules/@tiptap/starter-kit": {
3046 - "version": "2.1.11",
3047 - "resolved": "https://registry.npmjs.org/@tiptap/starter-kit/-/starter-kit-2.1.11.tgz",
3048 - "integrity": "sha512-kZXwuo9yxrs1ASxluRKjXThjdcy90d7owJxnJWD7SyEwXaXYc4h+Ar1M9rP3jieCDBuRTtCgvAOKbVbhnRJ2jg==",
3049 - "dependencies": {
3050 - "@tiptap/core": "^2.1.11",
3051 - "@tiptap/extension-blockquote": "^2.1.11",
3052 - "@tiptap/extension-bold": "^2.1.11",
3053 - "@tiptap/extension-bullet-list": "^2.1.11",
3054 - "@tiptap/extension-code": "^2.1.11",
3055 - "@tiptap/extension-code-block": "^2.1.11",
3056 - "@tiptap/extension-document": "^2.1.11",
3057 - "@tiptap/extension-dropcursor": "^2.1.11",
3058 - "@tiptap/extension-gapcursor": "^2.1.11",
3059 - "@tiptap/extension-hard-break": "^2.1.11",
3060 - "@tiptap/extension-heading": "^2.1.11",
3061 - "@tiptap/extension-history": "^2.1.11",
3062 - "@tiptap/extension-horizontal-rule": "^2.1.11",
3063 - "@tiptap/extension-italic": "^2.1.11",
3064 - "@tiptap/extension-list-item": "^2.1.11",
3065 - "@tiptap/extension-ordered-list": "^2.1.11",
3066 - "@tiptap/extension-paragraph": "^2.1.11",
3067 - "@tiptap/extension-strike": "^2.1.11",
3068 - "@tiptap/extension-text": "^2.1.11"
3035 + "version": "2.1.12",
3036 + "resolved": "https://registry.npmjs.org/@tiptap/starter-kit/-/starter-kit-2.1.12.tgz",
3037 + "integrity": "sha512-+RoP1rWV7rSCit2+3wl2bjvSRiePRJE/7YNKbvH8Faz/+AMO23AFegHoUFynR7U0ouGgYDljGkkj35e0asbSDA==",
3038 + "dependencies": {
3039 + "@tiptap/core": "^2.1.12",
3040 + "@tiptap/extension-blockquote": "^2.1.12",
3041 + "@tiptap/extension-bold": "^2.1.12",
3042 + "@tiptap/extension-bullet-list": "^2.1.12",
3043 + "@tiptap/extension-code": "^2.1.12",
3044 + "@tiptap/extension-code-block": "^2.1.12",
3045 + "@tiptap/extension-document": "^2.1.12",
3046 + "@tiptap/extension-dropcursor": "^2.1.12",
3047 + "@tiptap/extension-gapcursor": "^2.1.12",
3048 + "@tiptap/extension-hard-break": "^2.1.12",
3049 + "@tiptap/extension-heading": "^2.1.12",
3050 + "@tiptap/extension-history": "^2.1.12",
3051 + "@tiptap/extension-horizontal-rule": "^2.1.12",
3052 + "@tiptap/extension-italic": "^2.1.12",
3053 + "@tiptap/extension-list-item": "^2.1.12",
3054 + "@tiptap/extension-ordered-list": "^2.1.12",
3055 + "@tiptap/extension-paragraph": "^2.1.12",
3056 + "@tiptap/extension-strike": "^2.1.12",
3057 + "@tiptap/extension-text": "^2.1.12"
3058 },
3059 "funding": {
3060 "type": "github",
@@ -3073,12 +3062,12 @@
3062 }
3063 },
3064 "node_modules/@tiptap/vue-3": {
3076 - "version": "2.1.11",
3077 - "resolved": "https://registry.npmjs.org/@tiptap/vue-3/-/vue-3-2.1.11.tgz",
3078 - "integrity": "sha512-PA0ep7W4cXh1jSXpXgR/eKjTbBxP8b0rIKmwLHOLVLaXz2fGFYt+HwKmtZSnYMTcf+CscXmbhmajBJZQJVJQwQ==",
3065 + "version": "2.1.12",
3066 + "resolved": "https://registry.npmjs.org/@tiptap/vue-3/-/vue-3-2.1.12.tgz",
3067 + "integrity": "sha512-yAcfmWw/9jtIUbhb0uGQVI9NoPYgHRasX2sAGWnm9Al+0aJktgmQ3mLCifXfXfjyEbeMF0p2L6Ul8tO7eho7aQ==",
3068 "dependencies": {
3080 - "@tiptap/extension-bubble-menu": "^2.1.11",
3081 - "@tiptap/extension-floating-menu": "^2.1.11"
3069 + "@tiptap/extension-bubble-menu": "^2.1.12",
3070 + "@tiptap/extension-floating-menu": "^2.1.12"
3071 },
3072 "funding": {
3073 "type": "github",
@@ -3185,9 +3174,9 @@
3174 }
3175 },
3176 "node_modules/@types/bytes": {
3188 - "version": "3.1.2",
3189 - "resolved": "https://registry.npmjs.org/@types/bytes/-/bytes-3.1.2.tgz",
3190 - "integrity": "sha512-92b6q7CSYBMVZDtMZh5PuKm3LjZwcU7s6H8e9sU20Z1tOrTuXN+Hz3VuP9E8axiQRaCoiEOMN1duqPCEIhamrQ==",
3177 + "version": "3.1.3",
3178 + "resolved": "https://registry.npmjs.org/@types/bytes/-/bytes-3.1.3.tgz",
3179 + "integrity": "sha512-eEgZiWn6cjG8tc+AkI3FIa9ub9zhLMSRHqbecHe5yffqws+848zoHdbgFYxvUks4RElfJB9cupvqcd1gvDFQig==",
3180 "dev": true
3181 },
3182 "node_modules/@types/chai": {
@@ -3206,9 +3195,9 @@
3195 }
3196 },
3197 "node_modules/@types/debug": {
3209 - "version": "4.1.8",
3210 - "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.8.tgz",
3211 - "integrity": "sha512-/vPO1EPOs306Cvhwv7KfVfYvOJqA/S/AXjaHQiJboCZzcNDb+TIJFN9/2C9DZ//ijSKWioNyUxD792QmDJ+HKQ==",
3198 + "version": "4.1.10",
3199 + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.10.tgz",
3200 + "integrity": "sha512-tOSCru6s732pofZ+sMv9o4o3Zc+Sa8l3bxd/tweTQudFn06vAzb13ZX46Zi6m6EJ+RUbRTHvgQJ1gBtSgkaUYA==",
3201 "dependencies": {
3202 "@types/ms": "*"
3203 }
@@ -3220,9 +3209,9 @@
3209 "dev": true
3210 },
3211 "node_modules/@types/fs-extra": {
3223 - "version": "11.0.2",
3224 - "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-11.0.2.tgz",
3225 - "integrity": "sha512-c0hrgAOVYr21EX8J0jBMXGLMgJqVf/v6yxi0dLaJboW9aQPh16Id+z6w2Tx1hm+piJOLv8xPfVKZCLfjPw/IMQ==",
3212 + "version": "11.0.3",
3213 + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-11.0.3.tgz",
3214 + "integrity": "sha512-sF59BlXtUdzEAL1u0MSvuzWd7PdZvZEtnaVkzX5mjpdWTJ8brG0jUqve3jPCzSzvAKKMHTG8F8o/WMQLtleZdQ==",
3215 "dev": true,
3216 "dependencies": {
3217 "@types/jsonfile": "*",
@@ -3230,14 +3219,14 @@
3219 }
3220 },
3221 "node_modules/@types/geojson": {
3233 - "version": "7946.0.10",
3234 - "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.10.tgz",
3235 - "integrity": "sha512-Nmh0K3iWQJzniTuPRcJn5hxXkfB1T1pgB89SBig5PlJQU5yocazeu4jATJlaA0GYFKWMqDdvYemoSnF2pXgLVA=="
3222 + "version": "7946.0.12",
3223 + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.12.tgz",
3224 + "integrity": "sha512-uK2z1ZHJyC0nQRbuovXFt4mzXDwf27vQeUWNhfKGwRcWW429GOhP8HxUHlM6TLH4bzmlv/HlEjpvJh3JfmGsAA=="
3225 },
3226 "node_modules/@types/inquirer": {
3238 - "version": "9.0.3",
3239 - "resolved": "https://registry.npmjs.org/@types/inquirer/-/inquirer-9.0.3.tgz",
3240 - "integrity": "sha512-CzNkWqQftcmk2jaCWdBTf9Sm7xSw4rkI1zpU/Udw3HX5//adEZUIm9STtoRP1qgWj0CWQtJ9UTvqmO2NNjhMJw==",
3227 + "version": "9.0.6",
3228 + "resolved": "https://registry.npmjs.org/@types/inquirer/-/inquirer-9.0.6.tgz",
3229 + "integrity": "sha512-1Go1AAP/yOy3Pth5Xf1DC3nfZ03cJLCPx6E2YnSN/5I3w1jHBVH4170DkZ+JxfmA7c9kL9+bf9z3FRGa4kNAqg==",
3230 "dev": true,
3231 "dependencies": {
3232 "@types/through": "*",
@@ -3245,9 +3234,9 @@
3234 }
3235 },
3236 "node_modules/@types/jsdom": {
3248 - "version": "21.1.3",
3249 - "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-21.1.3.tgz",
3250 - "integrity": "sha512-1zzqSP+iHJYV4lB3lZhNBa012pubABkj9yG/GuXuf6LZH1cSPIJBqFDrm5JX65HHt6VOnNYdTui/0ySerRbMgA==",
3237 + "version": "21.1.4",
3238 + "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-21.1.4.tgz",
3239 + "integrity": "sha512-NzAMLEV0KQ4cBaDx3Ls8VfJUElyDUm1xrtYRmcMK0gF8L5xYbujFVaQlJ50yinQ/d47j2rEP1XUzkiYrw4YRFA==",
3240 "dev": true,
3241 "dependencies": {
3242 "@types/node": "*",
@@ -3276,9 +3265,9 @@
3265 "integrity": "sha512-CeVMX9EhVUW8MWnei05eIRks4D5Wscw/W9Byz1s3PA+yJvcdvq9SaDjiUKvRvEgjpdTyJMjQA43ae4KTwsvOPg=="
3266 },
3267 "node_modules/@types/lodash": {
3279 - "version": "4.14.199",
3280 - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.199.tgz",
3281 - "integrity": "sha512-Vrjz5N5Ia4SEzWWgIVwnHNEnb1UE1XMkvY5DGXrAeOGE9imk0hgTHh5GyDjLDJi9OTCn9oo9dXH1uToK1VRfrg=="
3268 + "version": "4.14.200",
3269 + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.200.tgz",
3270 + "integrity": "sha512-YI/M/4HRImtNf3pJgbF+W6FrXovqj+T+/HpENLTooK9PnkacBsDpeP3IpHab40CClUfhNmdM2WTNP2sa2dni5Q=="
3271 },
3272 "node_modules/@types/lodash-es": {
3273 "version": "4.17.9",
@@ -3289,14 +3278,14 @@
3278 }
3279 },
3280 "node_modules/@types/mapbox__point-geometry": {
3292 - "version": "0.1.2",
3293 - "resolved": "https://registry.npmjs.org/@types/mapbox__point-geometry/-/mapbox__point-geometry-0.1.2.tgz",
3294 - "integrity": "sha512-D0lgCq+3VWV85ey1MZVkE8ZveyuvW5VAfuahVTQRpXFQTxw03SuIf1/K4UQ87MMIXVKzpFjXFiFMZzLj2kU+iA=="
3281 + "version": "0.1.3",
3282 + "resolved": "https://registry.npmjs.org/@types/mapbox__point-geometry/-/mapbox__point-geometry-0.1.3.tgz",
3283 + "integrity": "sha512-2W46IOXlu7vC8m3+M5rDqSnuY22GFxxx3xhkoyqyPWrD+eP2iAwNst0A1+umLYjCTJMJTSpiofphn9h9k+Kw+w=="
3284 },
3285 "node_modules/@types/mapbox__vector-tile": {
3297 - "version": "1.3.0",
3298 - "resolved": "https://registry.npmjs.org/@types/mapbox__vector-tile/-/mapbox__vector-tile-1.3.0.tgz",
3299 - "integrity": "sha512-kDwVreQO5V4c8yAxzZVQLE5tyWF+IPToAanloQaSnwfXmIcJ7cyOrv8z4Ft4y7PsLYmhWXmON8MBV8RX0Rgr8g==",
3286 + "version": "1.3.3",
3287 + "resolved": "https://registry.npmjs.org/@types/mapbox__vector-tile/-/mapbox__vector-tile-1.3.3.tgz",
3288 + "integrity": "sha512-d263B3KCQtXKVZMHpMJrEW5EeLBsQ8jvAS9nhpUKC5hHIlQaACG9PWkW8qxEeNuceo9120AwPjeS91uNa4ltqA==",
3289 "dependencies": {
3290 "@types/geojson": "*",
3291 "@types/mapbox__point-geometry": "*",
@@ -3304,23 +3293,26 @@
3293 }
3294 },
3295 "node_modules/@types/mdast": {
3307 - "version": "3.0.12",
3308 - "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.12.tgz",
3309 - "integrity": "sha512-DT+iNIRNX884cx0/Q1ja7NyUPpZuv0KPyL5rGNxm1WC1OtHstl7n4Jb7nk+xacNShQMbczJjt8uFzznpp6kYBg==",
3296 + "version": "4.0.2",
3297 + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.2.tgz",
3298 + "integrity": "sha512-tYR83EignvhYO9iU3kDg8V28M0jqyh9zzp5GV+EO+AYnyUl3P5ltkTeJuTiFZQFz670FSb3EwT/6LQdX+UdKfw==",
3299 "dependencies": {
3311 - "@types/unist": "^2"
3300 + "@types/unist": "*"
3301 }
3302 },
3303 "node_modules/@types/ms": {
3315 - "version": "0.7.31",
3316 - "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz",
3317 - "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA=="
3304 + "version": "0.7.33",
3305 + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.33.tgz",
3306 + "integrity": "sha512-AuHIyzR5Hea7ij0P9q7vx7xu4z0C28ucwjAZC0ja7JhINyCnOw8/DnvAPQQ9TfOlCtZAmCERKQX9+o1mgQhuOQ=="
3307 },
3308 "node_modules/@types/node": {
3320 - "version": "20.8.2",
3321 - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.2.tgz",
3322 - "integrity": "sha512-Vvycsc9FQdwhxE3y3DzeIxuEJbWGDsnrxvMADzTDF/lcdR9/K+AQIeAghTQsHtotg/q0j3WEOYS/jQgSdWue3w==",
3323 - "dev": true
3309 + "version": "20.8.9",
3310 + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.9.tgz",
3311 + "integrity": "sha512-UzykFsT3FhHb1h7yD4CA4YhBHq545JC0YnEz41xkipN88eKQtL6rSgocL5tbAP6Ola9Izm/Aw4Ora8He4x0BHg==",
3312 + "dev": true,
3313 + "dependencies": {
3314 + "undici-types": "~5.26.4"
3315 + }
3316 },
3317 "node_modules/@types/numeral": {
3318 "version": "0.0.28",
@@ -3338,9 +3330,9 @@
3330 "integrity": "sha512-sn7L+qQ6RLPdXRoiaE7bZ/Ek+o4uICma/lBFPyJEKDTPTBP1W8u0c4baj3EiS4DiqLs+Hk+KUGvMVJtAw3ePJg=="
3331 },
3332 "node_modules/@types/pbf": {
3341 - "version": "3.0.2",
3342 - "resolved": "https://registry.npmjs.org/@types/pbf/-/pbf-3.0.2.tgz",
3343 - "integrity": "sha512-EDrLIPaPXOZqDjrkzxxbX7UlJSeQVgah3i0aA4pOSzmK9zq3BIh7/MZIQxED7slJByvKM4Gc6Hypyu2lJzh3SQ=="
3333 + "version": "3.0.4",
3334 + "resolved": "https://registry.npmjs.org/@types/pbf/-/pbf-3.0.4.tgz",
3335 + "integrity": "sha512-SOFlLGZkLbEXJRwcWCqeP/Koyaf/uAqLXHUsdo/nMfjLsNd8kqauwHe9GBOljSmpcHp/LC6kOjo3SidGjNirVA=="
3336 },
3337 "node_modules/@types/resize-observer-browser": {
3338 "version": "0.1.7",
@@ -3366,9 +3358,9 @@
3358 "dev": true
3359 },
3360 "node_modules/@types/supercluster": {
3369 - "version": "7.1.0",
3370 - "resolved": "https://registry.npmjs.org/@types/supercluster/-/supercluster-7.1.0.tgz",
3371 - "integrity": "sha512-6JapQ2GmEkH66r23BK49I+u6zczVDGTtiJEVvKDYZVSm/vepWaJuTq6BXzJ6I4agG5s8vA1KM7m/gXWDg03O4Q==",
3361 + "version": "7.1.2",
3362 + "resolved": "https://registry.npmjs.org/@types/supercluster/-/supercluster-7.1.2.tgz",
3363 + "integrity": "sha512-qMhofL945Z4njQUuntadexAgPtpiBC014WvVqU70Prj42LC77Xgmz04us7hSMmwjs7KbgAwGBmje+FSOvDbP0Q==",
3364 "dependencies": {
3365 "@types/geojson": "*"
3366 }
@@ -3394,20 +3386,20 @@
3386 "dev": true
3387 },
3388 "node_modules/@types/unist": {
3397 - "version": "2.0.8",
3398 - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.8.tgz",
3399 - "integrity": "sha512-d0XxK3YTObnWVp6rZuev3c49+j4Lo8g4L1ZRm9z5L0xpoZycUPshHgczK5gsUMaZOstjVYYi09p5gYvUtfChYw=="
3389 + "version": "3.0.1",
3390 + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.1.tgz",
3391 + "integrity": "sha512-ue/hDUpPjC85m+PM9OQDMZr3LywT+CT6mPsQq8OJtCLiERkGRcQUFvu9XASF5XWqyZFXbf15lvb3JFJ4dRLWPg=="
3392 },
3393 "node_modules/@types/validator": {
3402 - "version": "13.11.2",
3403 - "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.11.2.tgz",
3404 - "integrity": "sha512-nIKVVQKT6kGKysnNt+xLobr+pFJNssJRi2s034wgWeFBUx01fI8BeHTW2TcRp7VcFu9QCYG8IlChTuovcm0oKQ==",
3394 + "version": "13.11.5",
3395 + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.11.5.tgz",
3396 + "integrity": "sha512-xW4qsT4UIYILu+7ZrBnfQdBYniZrMLYYK3wN9M/NdeIHgBN5pZI2/8Q7UfdWIcr5RLJv/OGENsx91JIpUUoC7Q==",
3397 "dev": true
3398 },
3399 "node_modules/@types/web-bluetooth": {
3408 - "version": "0.0.17",
3409 - "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.17.tgz",
3410 - "integrity": "sha512-4p9vcSmxAayx72yn70joFoL44c9MO/0+iVEBIQXe3v2h2SiAsEIo/G5v6ObFWvNKRFjbrVadNf9LqEEZeQPzdA=="
3400 + "version": "0.0.18",
3401 + "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.18.tgz",
3402 + "integrity": "sha512-v/ZHEj9xh82usl8LMR3GarzFY1IrbXJw5L4QfQhokjRV91q+SelFqxQWSep1ucXEZ22+dSTwLFkXeur25sPIbw=="
3403 },
3404 "node_modules/@types/yauzl": {
3405 "version": "2.10.0",
@@ -3707,60 +3699,12 @@
3699 "url": "https://opencollective.com/typescript-eslint"
3700 }
3701 },
3710 - "node_modules/@vicons/antd": {
3711 - "version": "0.12.0",
3712 - "resolved": "https://registry.npmjs.org/@vicons/antd/-/antd-0.12.0.tgz",
3713 - "integrity": "sha512-C0p6aO1EmGG1QHrqgUWQS1No20934OdWSRQshM5NIDK5H1On6tC26U0hT6Rmp40KfUsvhvX5YW8BoWJdNFifPg==",
3714 - "dev": true
3715 - },
3716 - "node_modules/@vicons/carbon": {
3717 - "version": "0.12.0",
3718 - "resolved": "https://registry.npmjs.org/@vicons/carbon/-/carbon-0.12.0.tgz",
3719 - "integrity": "sha512-kCOgr/ZOhZzoiFLJ8pwxMa2TMxrkCUOA22qExPabus35F4+USqzcsxaPoYtqRd9ROOYiHrSqwapak/ywF0D9bg==",
3720 - "dev": true
3721 - },
3722 - "node_modules/@vicons/fa": {
3723 - "version": "0.12.0",
3724 - "resolved": "https://registry.npmjs.org/@vicons/fa/-/fa-0.12.0.tgz",
3725 - "integrity": "sha512-g2PIeJLsTHUjt6bK63LxqC0uYQB7iu+xViJOxvp1s8b9/akpXVPVWjDTTsP980/0KYyMMe4U7F/aUo7wY+MsXA==",
3726 - "dev": true
3727 - },
3728 - "node_modules/@vicons/fluent": {
3729 - "version": "0.12.0",
3730 - "resolved": "https://registry.npmjs.org/@vicons/fluent/-/fluent-0.12.0.tgz",
3731 - "integrity": "sha512-ATCiqPuiJ6RI5GBlD3BIpZ9Xw4MsCA4RpI5oR6MCti4quS4mX1Gp6N74FCzw7lgOj+80rV4HMKhZTVInwimpVQ==",
3732 - "dev": true
3733 - },
3734 - "node_modules/@vicons/ionicons5": {
3735 - "version": "0.12.0",
3736 - "resolved": "https://registry.npmjs.org/@vicons/ionicons5/-/ionicons5-0.12.0.tgz",
3737 - "integrity": "sha512-Iy1EUVRpX0WWxeu1VIReR1zsZLMc4fqpt223czR+Rpnrwu7pt46nbnC2ycO7ItI/uqDLJxnbcMC7FujKs9IfFA==",
3738 - "dev": true
3739 - },
3740 - "node_modules/@vicons/material": {
3741 - "version": "0.12.0",
3742 - "resolved": "https://registry.npmjs.org/@vicons/material/-/material-0.12.0.tgz",
3743 - "integrity": "sha512-chv1CYAl8P32P3Ycwgd5+vw/OFNc2mtkKdb1Rw4T5IJmKy6GVDsoUKV3N2l208HATn7CCQphZtuPDdsm7K2kmA==",
3744 - "dev": true
3745 - },
3746 - "node_modules/@vicons/tabler": {
3747 - "version": "0.12.0",
3748 - "resolved": "https://registry.npmjs.org/@vicons/tabler/-/tabler-0.12.0.tgz",
3749 - "integrity": "sha512-3+wUFuxb7e8OzZ8Wryct1pzfA2vyoF4lwW98O9s27ZrfCGaJGNmqG+q8A7vQ92Mf+COCgxpK+rhNPTtTvaU6qw==",
3702 + "node_modules/@ungap/structured-clone": {
3703 + "version": "1.2.0",
3704 + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz",
3705 + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==",
3706 "dev": true
3707 },
3752 - "node_modules/@vicons/utils": {
3753 - "version": "0.1.4",
3754 - "resolved": "https://registry.npmjs.org/@vicons/utils/-/utils-0.1.4.tgz",
3755 - "integrity": "sha512-OHI19qVNN6i+uPQ+Y3f2s0dUxwsYnOCcKBW7XOU4yXXO1aU3ZoKpblCc3+4N0qmgoJs5rWKRAaMisipqEXJwAg==",
3756 - "dev": true,
3757 - "dependencies": {
3758 - "@xicons/utils": "^0.1.4"
3759 - },
3760 - "peerDependencies": {
3761 - "vue": "^3.0.6"
3762 - }
3763 - },
3708 "node_modules/@vitejs/plugin-vue": {
3709 "version": "4.4.0",
3710 "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-4.4.0.tgz",
@@ -3888,30 +3832,30 @@
3832 }
3833 },
3834 "node_modules/@volar/language-core": {
3891 - "version": "1.10.1",
3892 - "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-1.10.1.tgz",
3893 - "integrity": "sha512-JnsM1mIPdfGPxmoOcK1c7HYAsL6YOv0TCJ4aW3AXPZN/Jb4R77epDyMZIVudSGjWMbvv/JfUa+rQ+dGKTmgwBA==",
3835 + "version": "1.10.5",
3836 + "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-1.10.5.tgz",
3837 + "integrity": "sha512-xD71j4Ee0Ycq8WsiAE6H/aCThGdTobiZZeD+jFD+bvmbopa1Az296pqJysr3Ck8c7n5+GGF+xlKCS3WxRFYgSQ==",
3838 "dev": true,
3839 "dependencies": {
3896 - "@volar/source-map": "1.10.1"
3840 + "@volar/source-map": "1.10.5"
3841 }
3842 },
3843 "node_modules/@volar/source-map": {
3900 - "version": "1.10.1",
3901 - "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-1.10.1.tgz",
3902 - "integrity": "sha512-3/S6KQbqa7pGC8CxPrg69qHLpOvkiPHGJtWPkI/1AXCsktkJ6gIk/5z4hyuMp8Anvs6eS/Kvp/GZa3ut3votKA==",
3844 + "version": "1.10.5",
3845 + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-1.10.5.tgz",
3846 + "integrity": "sha512-s4kgo66SA1kMzYvF9HFE6Vc1rxtXLUmcLrT2WKnchPDvLne+97Kw+xoR2NxJFmsvHoL18vmu/YGXYcN+Q5re1g==",
3847 "dev": true,
3848 "dependencies": {
3849 "muggle-string": "^0.3.1"
3850 }
3851 },
3852 "node_modules/@volar/typescript": {
3909 - "version": "1.10.1",
3910 - "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-1.10.1.tgz",
3911 - "integrity": "sha512-+iiO9yUSRHIYjlteT+QcdRq8b44qH19/eiUZtjNtuh6D9ailYM7DVR0zO2sEgJlvCaunw/CF9Ov2KooQBpR4VQ==",
3853 + "version": "1.10.5",
3854 + "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-1.10.5.tgz",
3855 + "integrity": "sha512-kfDehpeLJku9i1BgsFOYIczPmFFH4herl+GZrLGdvX5urTqeCKsKYlF36iNmFaADzjMb9WlENcUZzPjK8MxNrQ==",
3856 "dev": true,
3857 "dependencies": {
3914 - "@volar/language-core": "1.10.1"
3858 + "@volar/language-core": "1.10.5"
3859 }
3860 },
3861 "node_modules/@vue-leaflet/vue-leaflet": {
@@ -3959,49 +3903,49 @@
3903 }
3904 },
3905 "node_modules/@vue/compiler-core": {
3962 - "version": "3.3.4",
3963 - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.3.4.tgz",
3964 - "integrity": "sha512-cquyDNvZ6jTbf/+x+AgM2Arrp6G4Dzbb0R64jiG804HRMfRiFXWI6kqUVqZ6ZR0bQhIoQjB4+2bhNtVwndW15g==",
3906 + "version": "3.3.7",
3907 + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.3.7.tgz",
3908 + "integrity": "sha512-pACdY6YnTNVLXsB86YD8OF9ihwpolzhhtdLVHhBL6do/ykr6kKXNYABRtNMGrsQXpEXXyAdwvWWkuTbs4MFtPQ==",
3909 "dependencies": {
3966 - "@babel/parser": "^7.21.3",
3967 - "@vue/shared": "3.3.4",
3910 + "@babel/parser": "^7.23.0",
3911 + "@vue/shared": "3.3.7",
3912 "estree-walker": "^2.0.2",
3913 "source-map-js": "^1.0.2"
3914 }
3915 },
3916 "node_modules/@vue/compiler-dom": {
3973 - "version": "3.3.4",
3974 - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.3.4.tgz",
3975 - "integrity": "sha512-wyM+OjOVpuUukIq6p5+nwHYtj9cFroz9cwkfmP9O1nzH68BenTTv0u7/ndggT8cIQlnBeOo6sUT/gvHcIkLA5w==",
3917 + "version": "3.3.7",
3918 + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.3.7.tgz",
3919 + "integrity": "sha512-0LwkyJjnUPssXv/d1vNJ0PKfBlDoQs7n81CbO6Q0zdL7H1EzqYRrTVXDqdBVqro0aJjo/FOa1qBAPVI4PGSHBw==",
3920 "dependencies": {
3977 - "@vue/compiler-core": "3.3.4",
3978 - "@vue/shared": "3.3.4"
3921 + "@vue/compiler-core": "3.3.7",
3922 + "@vue/shared": "3.3.7"
3923 }
3924 },
3925 "node_modules/@vue/compiler-sfc": {
3982 - "version": "3.3.4",
3983 - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.3.4.tgz",
3984 - "integrity": "sha512-6y/d8uw+5TkCuzBkgLS0v3lSM3hJDntFEiUORM11pQ/hKvkhSKZrXW6i69UyXlJQisJxuUEJKAWEqWbWsLeNKQ==",
3985 - "dependencies": {
3986 - "@babel/parser": "^7.20.15",
3987 - "@vue/compiler-core": "3.3.4",
3988 - "@vue/compiler-dom": "3.3.4",
3989 - "@vue/compiler-ssr": "3.3.4",
3990 - "@vue/reactivity-transform": "3.3.4",
3991 - "@vue/shared": "3.3.4",
3926 + "version": "3.3.7",
3927 + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.3.7.tgz",
3928 + "integrity": "sha512-7pfldWy/J75U/ZyYIXRVqvLRw3vmfxDo2YLMwVtWVNew8Sm8d6wodM+OYFq4ll/UxfqVr0XKiVwti32PCrruAw==",
3929 + "dependencies": {
3930 + "@babel/parser": "^7.23.0",
3931 + "@vue/compiler-core": "3.3.7",
3932 + "@vue/compiler-dom": "3.3.7",
3933 + "@vue/compiler-ssr": "3.3.7",
3934 + "@vue/reactivity-transform": "3.3.7",
3935 + "@vue/shared": "3.3.7",
3936 "estree-walker": "^2.0.2",
3993 - "magic-string": "^0.30.0",
3994 - "postcss": "^8.1.10",
3937 + "magic-string": "^0.30.5",
3938 + "postcss": "^8.4.31",
3939 "source-map-js": "^1.0.2"
3940 }
3941 },
3942 "node_modules/@vue/compiler-ssr": {
3999 - "version": "3.3.4",
4000 - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.3.4.tgz",
4001 - "integrity": "sha512-m0v6oKpup2nMSehwA6Uuu+j+wEwcy7QmwMkVNVfrV9P2qE5KshC6RwOCq8fjGS/Eak/uNb8AaWekfiXxbBB6gQ==",
3943 + "version": "3.3.7",
3944 + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.3.7.tgz",
3945 + "integrity": "sha512-TxOfNVVeH3zgBc82kcUv+emNHo+vKnlRrkv8YvQU5+Y5LJGJwSNzcmLUoxD/dNzv0bhQ/F0s+InlgV0NrApJZg==",
3946 "dependencies": {
4003 - "@vue/compiler-dom": "3.3.4",
4004 - "@vue/shared": "3.3.4"
3947 + "@vue/compiler-dom": "3.3.7",
3948 + "@vue/shared": "3.3.7"
3949 }
3950 },
3951 "node_modules/@vue/devtools-api": {
@@ -4048,17 +3992,17 @@
3992 }
3993 },
3994 "node_modules/@vue/language-core": {
4051 - "version": "1.8.15",
4052 - "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-1.8.15.tgz",
4053 - "integrity": "sha512-zche5Aw8kkvp3YaghuLiOZyVIpoWHjSQ0EfjxGSsqHOPMamdCoa9x3HtbenpR38UMUoKJ88wiWuiOrV3B/Yq+A==",
3995 + "version": "1.8.22",
3996 + "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-1.8.22.tgz",
3997 + "integrity": "sha512-bsMoJzCrXZqGsxawtUea1cLjUT9dZnDsy5TuZ+l1fxRMzUGQUG9+Ypq4w//CqpWmrx7nIAJpw2JVF/t258miRw==",
3998 "dev": true,
3999 "dependencies": {
4056 - "@volar/language-core": "~1.10.0",
4057 - "@volar/source-map": "~1.10.0",
4000 + "@volar/language-core": "~1.10.5",
4001 + "@volar/source-map": "~1.10.5",
4002 "@vue/compiler-dom": "^3.3.0",
4059 - "@vue/reactivity": "^3.3.0",
4003 "@vue/shared": "^3.3.0",
4061 - "minimatch": "^9.0.0",
4004 + "computeds": "^0.0.1",
4005 + "minimatch": "^9.0.3",
4006 "muggle-string": "^0.3.1",
4007 "vue-template-compiler": "^2.7.14"
4008 },
@@ -4096,42 +4040,42 @@
4040 }
4041 },
4042 "node_modules/@vue/reactivity": {
4099 - "version": "3.3.4",
4100 - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.3.4.tgz",
4101 - "integrity": "sha512-kLTDLwd0B1jG08NBF3R5rqULtv/f8x3rOFByTDz4J53ttIQEDmALqKqXY0J+XQeN0aV2FBxY8nJDf88yvOPAqQ==",
4043 + "version": "3.3.7",
4044 + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.3.7.tgz",
4045 + "integrity": "sha512-cZNVjWiw00708WqT0zRpyAgduG79dScKEPYJXq2xj/aMtk3SKvL3FBt2QKUlh6EHBJ1m8RhBY+ikBUzwc7/khg==",
4046 "dependencies": {
4103 - "@vue/shared": "3.3.4"
4047 + "@vue/shared": "3.3.7"
4048 }
4049 },
4050 "node_modules/@vue/reactivity-transform": {
4107 - "version": "3.3.4",
4108 - "resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.3.4.tgz",
4109 - "integrity": "sha512-MXgwjako4nu5WFLAjpBnCj/ieqcjE2aJBINUNQzkZQfzIZA4xn+0fV1tIYBJvvva3N3OvKGofRLvQIwEQPpaXw==",
4051 + "version": "3.3.7",
4052 + "resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.3.7.tgz",
4053 + "integrity": "sha512-APhRmLVbgE1VPGtoLQoWBJEaQk4V8JUsqrQihImVqKT+8U6Qi3t5ATcg4Y9wGAPb3kIhetpufyZ1RhwbZCIdDA==",
4054 "dependencies": {
4111 - "@babel/parser": "^7.20.15",
4112 - "@vue/compiler-core": "3.3.4",
4113 - "@vue/shared": "3.3.4",
4055 + "@babel/parser": "^7.23.0",
4056 + "@vue/compiler-core": "3.3.7",
4057 + "@vue/shared": "3.3.7",
4058 "estree-walker": "^2.0.2",
4115 - "magic-string": "^0.30.0"
4059 + "magic-string": "^0.30.5"
4060 }
4061 },
4062 "node_modules/@vue/runtime-core": {
4119 - "version": "3.3.4",
4120 - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.3.4.tgz",
4121 - "integrity": "sha512-R+bqxMN6pWO7zGI4OMlmvePOdP2c93GsHFM/siJI7O2nxFRzj55pLwkpCedEY+bTMgp5miZ8CxfIZo3S+gFqvA==",
4063 + "version": "3.3.7",
4064 + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.3.7.tgz",
4065 + "integrity": "sha512-LHq9du3ubLZFdK/BP0Ysy3zhHqRfBn80Uc+T5Hz3maFJBGhci1MafccnL3rpd5/3wVfRHAe6c+PnlO2PAavPTQ==",
4066 "dependencies": {
4123 - "@vue/reactivity": "3.3.4",
4124 - "@vue/shared": "3.3.4"
4067 + "@vue/reactivity": "3.3.7",
4068 + "@vue/shared": "3.3.7"
4069 }
4070 },
4071 "node_modules/@vue/runtime-dom": {
4128 - "version": "3.3.4",
4129 - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.3.4.tgz",
4130 - "integrity": "sha512-Aj5bTJ3u5sFsUckRghsNjVTtxZQ1OyMWCr5dZRAPijF/0Vy4xEoRCwLyHXcj4D0UFbJ4lbx3gPTgg06K/GnPnQ==",
4072 + "version": "3.3.7",
4073 + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.3.7.tgz",
4074 + "integrity": "sha512-PFQU1oeJxikdDmrfoNQay5nD4tcPNYixUBruZzVX/l0eyZvFKElZUjW4KctCcs52nnpMGO6UDK+jF5oV4GT5Lw==",
4075 "dependencies": {
4132 - "@vue/runtime-core": "3.3.4",
4133 - "@vue/shared": "3.3.4",
4134 - "csstype": "^3.1.1"
4076 + "@vue/runtime-core": "3.3.7",
4077 + "@vue/shared": "3.3.7",
4078 + "csstype": "^3.1.2"
4079 }
4080 },
4081 "node_modules/@vue/runtime-dom/node_modules/csstype": {
@@ -4140,21 +4084,21 @@
4084 "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ=="
4085 },
4086 "node_modules/@vue/server-renderer": {
4143 - "version": "3.3.4",
4144 - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.3.4.tgz",
4145 - "integrity": "sha512-Q6jDDzR23ViIb67v+vM1Dqntu+HUexQcsWKhhQa4ARVzxOY2HbC7QRW/ggkDBd5BU+uM1sV6XOAP0b216o34JQ==",
4087 + "version": "3.3.7",
4088 + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.3.7.tgz",
4089 + "integrity": "sha512-UlpKDInd1hIZiNuVVVvLgxpfnSouxKQOSE2bOfQpBuGwxRV/JqqTCyyjXUWiwtVMyeRaZhOYYqntxElk8FhBhw==",
4090 "dependencies": {
4147 - "@vue/compiler-ssr": "3.3.4",
4148 - "@vue/shared": "3.3.4"
4091 + "@vue/compiler-ssr": "3.3.7",
4092 + "@vue/shared": "3.3.7"
4093 },
4094 "peerDependencies": {
4151 - "vue": "3.3.4"
4095 + "vue": "3.3.7"
4096 }
4097 },
4098 "node_modules/@vue/shared": {
4155 - "version": "3.3.4",
4156 - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.4.tgz",
4157 - "integrity": "sha512-7OjdcV8vQ74eiz1TZLzZP4JwqM5fA94K6yntPS5Z25r9HDuGNzaGdgvwKYq6S+MxwF0TFRwe50fIR/MYnakdkQ=="
4099 + "version": "3.3.7",
4100 + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.7.tgz",
4101 + "integrity": "sha512-N/tbkINRUDExgcPTBvxNkvHGu504k8lzlNQRITVnm6YjOjwa4r0nnbd4Jb01sNpur5hAllyRJzSK5PvB9PPwRg=="
4102 },
4103 "node_modules/@vue/test-utils": {
4104 "version": "2.4.1",
@@ -4181,16 +4125,6 @@
4125 "integrity": "sha512-CPuIReonid9+zOG/CGTT05FXrPYATEqoDGNrEaqS4hwcw5BUNM2FguC0mOwJD4Jr16UpRVl9N0pY3P+srIbqmg==",
4126 "dev": true
4127 },
4184 - "node_modules/@vue/typescript": {
4185 - "version": "1.8.15",
4186 - "resolved": "https://registry.npmjs.org/@vue/typescript/-/typescript-1.8.15.tgz",
4187 - "integrity": "sha512-qWyanQKXOsK84S8rP7QBrqsvUdQ0nZABZmTjXMpb3ox4Bp5IbkscREA3OPUrkgl64mAxwwCzIWcOc3BPTCPjQw==",
4188 - "dev": true,
4189 - "dependencies": {
4190 - "@volar/typescript": "~1.10.0",
4191 - "@vue/language-core": "1.8.15"
4192 - }
4193 - },
4128 "node_modules/@vueup/vue-quill": {
4129 "version": "1.2.0",
4130 "resolved": "https://registry.npmjs.org/@vueup/vue-quill/-/vue-quill-1.2.0.tgz",
@@ -4204,13 +4138,13 @@
4138 }
4139 },
4140 "node_modules/@vueuse/components": {
4207 - "version": "10.4.1",
4208 - "resolved": "https://registry.npmjs.org/@vueuse/components/-/components-10.4.1.tgz",
4209 - "integrity": "sha512-hEWeumCfH394fkEYc/hng6T5VcjVkdqx7b75Sd6z4Uw3anjeo93Zp9qqtzFOv5bAmHls3Zy04Kowo1glrxDFRQ==",
4141 + "version": "10.5.0",
4142 + "resolved": "https://registry.npmjs.org/@vueuse/components/-/components-10.5.0.tgz",
4143 + "integrity": "sha512-zWQZ8zkNBvX++VHfyiUaQ4otb+4PWI8679GR8FvdrNnj+01LXnqvrkyKd8yTCMJ9nHqwRRTJikS5fu4Zspn9DQ==",
4144 "dependencies": {
4211 - "@vueuse/core": "10.4.1",
4212 - "@vueuse/shared": "10.4.1",
4213 - "vue-demi": ">=0.14.5"
4145 + "@vueuse/core": "10.5.0",
4146 + "@vueuse/shared": "10.5.0",
4147 + "vue-demi": ">=0.14.6"
4148 }
4149 },
4150 "node_modules/@vueuse/components/node_modules/vue-demi": {
@@ -4239,14 +4173,14 @@
4173 }
4174 },
4175 "node_modules/@vueuse/core": {
4242 - "version": "10.4.1",
4243 - "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.4.1.tgz",
4244 - "integrity": "sha512-DkHIfMIoSIBjMgRRvdIvxsyboRZQmImofLyOHADqiVbQVilP8VVHDhBX2ZqoItOgu7dWa8oXiNnScOdPLhdEXg==",
4176 + "version": "10.5.0",
4177 + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.5.0.tgz",
4178 + "integrity": "sha512-z/tI2eSvxwLRjOhDm0h/SXAjNm8N5ld6/SC/JQs6o6kpJ6Ya50LnEL8g5hoYu005i28L0zqB5L5yAl8Jl26K3A==",
4179 "dependencies": {
4246 - "@types/web-bluetooth": "^0.0.17",
4247 - "@vueuse/metadata": "10.4.1",
4248 - "@vueuse/shared": "10.4.1",
4249 - "vue-demi": ">=0.14.5"
4180 + "@types/web-bluetooth": "^0.0.18",
4181 + "@vueuse/metadata": "10.5.0",
4182 + "@vueuse/shared": "10.5.0",
4183 + "vue-demi": ">=0.14.6"
4184 },
4185 "funding": {
4186 "url": "https://github.com/sponsors/antfu"
@@ -4278,19 +4212,19 @@
4212 }
4213 },
4214 "node_modules/@vueuse/metadata": {
4281 - "version": "10.4.1",
4282 - "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.4.1.tgz",
4283 - "integrity": "sha512-2Sc8X+iVzeuMGHr6O2j4gv/zxvQGGOYETYXEc41h0iZXIRnRbJZGmY/QP8dvzqUelf8vg0p/yEA5VpCEu+WpZg==",
4215 + "version": "10.5.0",
4216 + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.5.0.tgz",
4217 + "integrity": "sha512-fEbElR+MaIYyCkeM0SzWkdoMtOpIwO72x8WsZHRE7IggiOlILttqttM69AS13nrDxosnDBYdyy3C5mR1LCxHsw==",
4218 "funding": {
4219 "url": "https://github.com/sponsors/antfu"
4220 }
4221 },
4222 "node_modules/@vueuse/shared": {
4289 - "version": "10.4.1",
4290 - "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.4.1.tgz",
4291 - "integrity": "sha512-vz5hbAM4qA0lDKmcr2y3pPdU+2EVw/yzfRsBdu+6+USGa4PxqSQRYIUC9/NcT06y+ZgaTsyURw2I9qOFaaXHAg==",
4223 + "version": "10.5.0",
4224 + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.5.0.tgz",
4225 + "integrity": "sha512-18iyxbbHYLst9MqU1X1QNdMHIjks6wC7XTVf0KNOv5es/Ms6gjVFCAAWTVP2JStuGqydg3DT+ExpFORUEi9yhg==",
4226 "dependencies": {
4293 - "vue-demi": ">=0.14.5"
4227 + "vue-demi": ">=0.14.6"
4228 },
4229 "funding": {
4230 "url": "https://github.com/sponsors/antfu"
@@ -4321,15 +4255,6 @@
4255 }
4256 }
4257 },
4324 - "node_modules/@xicons/utils": {
4325 - "version": "0.1.4",
4326 - "resolved": "https://registry.npmjs.org/@xicons/utils/-/utils-0.1.4.tgz",
4327 - "integrity": "sha512-uXxKDLz9abr80yJC05XSTq6wlyFcdW+N/1IYJkeHjzzXVc4VQ0sEYMoMMTjAH7HQBOyOkzOB4pf5NGF72lwa8Q==",
4328 - "dev": true,
4329 - "dependencies": {
4330 - "css-render": "^0.13.2"
4331 - }
4332 - },
4258 "node_modules/@yr/monotone-cubic-spline": {
4259 "version": "1.0.3",
4260 "resolved": "https://registry.npmjs.org/@yr/monotone-cubic-spline/-/monotone-cubic-spline-1.0.3.tgz",
@@ -4508,9 +4433,9 @@
4433 }
4434 },
4435 "node_modules/apexcharts": {
4511 - "version": "3.43.0",
4512 - "resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-3.43.0.tgz",
4513 - "integrity": "sha512-YPw1aLatPQMUqVLMp5d+LDaXFi4QrRQND72/XO7/2NJdg+R5MjE9sifJ0GzOfgoZM7ltBUTjwfSxIvwR/9V8yw==",
4436 + "version": "3.44.0",
4437 + "resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-3.44.0.tgz",
4438 + "integrity": "sha512-u7Xzrbcxc2yWznN78Jh5NMCYVAsWDfBjRl5ea++rVzFAqjU2hLz4RgKIFwYOBDRQtW1e/Qz8azJTqIJ1+Vu9Qg==",
4439 "dependencies": {
4440 "@yr/monotone-cubic-spline": "^1.0.3",
4441 "svg.draggable.js": "^2.2.2",
@@ -5465,14 +5390,6 @@
5390 "node": ">=0.8"
5391 }
5392 },
5468 - "node_modules/clsx": {
5469 - "version": "1.2.1",
5470 - "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz",
5471 - "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==",
5472 - "engines": {
5473 - "node": ">=6"
5474 - }
5475 - },
5393 "node_modules/co": {
5394 "version": "4.6.0",
5395 "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
@@ -5548,6 +5465,12 @@
5465 "node": ">=4.0.0"
5466 }
5467 },
5468 + "node_modules/computeds": {
5469 + "version": "0.0.1",
5470 + "resolved": "https://registry.npmjs.org/computeds/-/computeds-0.0.1.tgz",
5471 + "integrity": "sha512-7CEBgcMjVmitjYo5q8JTJVra6X5mQ20uTThdK+0kR7UEaDrAWEQcRiBtWJzga4eRpP6afNwwLsX2SET2JhVB1Q==",
5472 + "dev": true
5473 + },
5474 "node_modules/concat-map": {
5475 "version": "0.0.1",
5476 "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -5774,9 +5697,9 @@
5697 "integrity": "sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw=="
5698 },
5699 "node_modules/cypress": {
5777 - "version": "13.3.0",
5778 - "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.3.0.tgz",
5779 - "integrity": "sha512-mpI8qcTwLGiA4zEQvTC/U1xGUezVV4V8HQCOYjlEOrVmU1etVvxOjkCXHGwrlYdZU/EPmUiWfsO3yt1o+Q2bgw==",
5700 + "version": "13.3.3",
5701 + "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.3.3.tgz",
5702 + "integrity": "sha512-mbdkojHhKB1xbrj7CrKWHi22uFx9P9vQFiR0sYDZZoK99OMp9/ZYN55TO5pjbXmV7xvCJ4JwBoADXjOJK8aCJw==",
5703 "dev": true,
5704 "hasInstallScript": true,
5705 "dependencies": {
@@ -6017,9 +5940,9 @@
5940 }
5941 },
5942 "node_modules/date-fns-tz": {
6020 - "version": "1.3.8",
6021 - "resolved": "https://registry.npmjs.org/date-fns-tz/-/date-fns-tz-1.3.8.tgz",
6022 - "integrity": "sha512-qwNXUFtMHTTU6CFSFjoJ80W8Fzzp24LntbjFFBgL/faqds4e5mo9mftoRLgr3Vi1trISsg4awSpYVsOQCRnapQ==",
5943 + "version": "2.0.0",
5944 + "resolved": "https://registry.npmjs.org/date-fns-tz/-/date-fns-tz-2.0.0.tgz",
5945 + "integrity": "sha512-OAtcLdB9vxSXTWHdT8b398ARImVwQMyjfYGkKD2zaGpHseG2UPHbHjXELReErZFxWdSLph3c2zOaaTyHfOhERQ==",
5946 "peerDependencies": {
5947 "date-fns": ">=2.0.0"
5948 }
@@ -6375,6 +6298,18 @@
6298 "resolved": "https://registry.npmjs.org/detect-touch-device/-/detect-touch-device-1.1.6.tgz",
6299 "integrity": "sha512-9DYLJE05EFGI9f8m/GyJtWjw7aMZMBQM2QVy6bb7zX8uC3iorOE3Erdrk3TWCj5FVOlHTfinU0bATTE9GWYebw=="
6300 },
6301 + "node_modules/devlop": {
6302 + "version": "1.1.0",
6303 + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz",
6304 + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==",
6305 + "dependencies": {
6306 + "dequal": "^2.0.0"
6307 + },
6308 + "funding": {
6309 + "type": "github",
6310 + "url": "https://github.com/sponsors/wooorm"
6311 + }
6312 + },
6313 "node_modules/didyoumean": {
6314 "version": "1.2.2",
6315 "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
@@ -6869,18 +6804,19 @@
6804 }
6805 },
6806 "node_modules/eslint": {
6872 - "version": "8.50.0",
6873 - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.50.0.tgz",
6874 - "integrity": "sha512-FOnOGSuFuFLv/Sa+FDVRZl4GGVAAFFi8LecRsI5a1tMO5HIE8nCm4ivAlzt4dT3ol/PaaGC0rJEEXQmHJBGoOg==",
6807 + "version": "8.52.0",
6808 + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.52.0.tgz",
6809 + "integrity": "sha512-zh/JHnaixqHZsolRB/w9/02akBk9EPrOs9JwcTP2ek7yL5bVvXuRariiaAjjoJ5DvuwQ1WAE/HsMz+w17YgBCg==",
6810 "dev": true,
6811 "dependencies": {
6812 "@eslint-community/eslint-utils": "^4.2.0",
6813 "@eslint-community/regexpp": "^4.6.1",
6814 "@eslint/eslintrc": "^2.1.2",
6880 - "@eslint/js": "8.50.0",
6881 - "@humanwhocodes/config-array": "^0.11.11",
6815 + "@eslint/js": "8.52.0",
6816 + "@humanwhocodes/config-array": "^0.11.13",
6817 "@humanwhocodes/module-importer": "^1.0.1",
6818 "@nodelib/fs.walk": "^1.2.8",
6819 + "@ungap/structured-clone": "^1.2.0",
6820 "ajv": "^6.12.4",
6821 "chalk": "^4.0.0",
6822 "cross-spawn": "^7.0.2",
@@ -7003,9 +6939,9 @@
6939 }
6940 },
6941 "node_modules/eslint-plugin-vue": {
7006 - "version": "9.17.0",
7007 - "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.17.0.tgz",
7008 - "integrity": "sha512-r7Bp79pxQk9I5XDP0k2dpUC7Ots3OSWgvGZNu3BxmKK6Zg7NgVtcOB6OCna5Kb9oQwJPl5hq183WD0SY5tZtIQ==",
6942 + "version": "9.18.1",
6943 + "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.18.1.tgz",
6944 + "integrity": "sha512-7hZFlrEgg9NIzuVik2I9xSnJA5RsmOfueYgsUGUokEDLJ1LHtxO0Pl4duje1BriZ/jDWb+44tcIlC3yi0tdlZg==",
6945 "dev": true,
6946 "dependencies": {
6947 "@eslint-community/eslint-utils": "^4.4.0",
@@ -7515,11 +7451,6 @@
7451 "url": "https://github.com/sponsors/sindresorhus"
7452 }
7453 },
7518 - "node_modules/flag-icons": {
7519 - "version": "6.11.1",
7520 - "resolved": "https://registry.npmjs.org/flag-icons/-/flag-icons-6.11.1.tgz",
7521 - "integrity": "sha512-c2UMJTFZoVQ47/sE1mb+9b5S1pi8SjXsx0MR063O31GV+O2EN4FMwMdEYSQItpien2bl9w1viLUoo2R3r6OK3g=="
7522 - },
7454 "node_modules/flat-cache": {
7455 "version": "3.1.0",
7456 "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.1.0.tgz",
@@ -8120,9 +8051,9 @@
8051 "integrity": "sha512-BXUKIkUuh6cmmxzi5OIbUJxrG8OAk2MqoL1DtO3Wo9D2faJg2ph5ntyuQeLqaHJmzER6H5tllCDA9ZnNe9BVGg=="
8052 },
8053 "node_modules/highlight.js": {
8123 - "version": "11.8.0",
8124 - "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.8.0.tgz",
8125 - "integrity": "sha512-MedQhoqVdr0U6SSnWPzfiadUcDHfN/Wzq25AkXiQv9oiOO/sG0S7XkvpFIqWBl9Yq1UYyYOOVORs5UW2XlPyzg==",
8054 + "version": "11.9.0",
8055 + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.9.0.tgz",
8056 + "integrity": "sha512-fJ7cW7fQGCYAkgv4CPfwFHrfd/cLS4Hau96JuJ+ZTOWhjnhoeN1ub1tFmALm/+lW5z4WCAuAV9bm05AP0mS6Gw==",
8057 "engines": {
8058 "node": ">=12.0.0"
8059 }
@@ -8493,28 +8424,6 @@
8424 "url": "https://github.com/sponsors/ljharb"
8425 }
8426 },
8496 - "node_modules/is-buffer": {
8497 - "version": "2.0.5",
8498 - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz",
8499 - "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==",
8500 - "funding": [
8501 - {
8502 - "type": "github",
8503 - "url": "https://github.com/sponsors/feross"
8504 - },
8505 - {
8506 - "type": "patreon",
8507 - "url": "https://www.patreon.com/feross"
8508 - },
8509 - {
8510 - "type": "consulting",
8511 - "url": "https://feross.org/support"
8512 - }
8513 - ],
8514 - "engines": {
8515 - "node": ">=4"
8516 - }
8517 - },
8427 "node_modules/is-callable": {
8428 "version": "1.2.7",
8429 "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
@@ -8956,9 +8865,9 @@
8865 }
8866 },
8867 "node_modules/jose": {
8959 - "version": "4.15.2",
8960 - "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.2.tgz",
8961 - "integrity": "sha512-IY73F228OXRl9ar3jJagh7Vnuhj/GzBunPiZP13K0lOl7Am9SoWW3kEzq3MCllJMTtZqHTiDXQvoRd4U95aU6A==",
8868 + "version": "5.0.1",
8869 + "resolved": "https://registry.npmjs.org/jose/-/jose-5.0.1.tgz",
8870 + "integrity": "sha512-gRVzy7s3RRdGbXmcTdlOswJOjhwPLx1ijIgAqLY6ktzFpOJxxYn4l0fC2vHaHHi4YBX/5FOL3aY+6W0cvQgpug==",
8871 "funding": {
8872 "url": "https://github.com/sponsors/panva"
8873 }
@@ -9218,14 +9127,6 @@
9127 "node": ">=0.10.0"
9128 }
9129 },
9221 - "node_modules/kleur": {
9222 - "version": "4.1.5",
9223 - "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz",
9224 - "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==",
9225 - "engines": {
9226 - "node": ">=6"
9227 - }
9228 - },
9130 "node_modules/koa": {
9131 "version": "2.14.2",
9132 "resolved": "https://registry.npmjs.org/koa/-/koa-2.14.2.tgz",
@@ -9694,9 +9595,9 @@
9595 }
9596 },
9597 "node_modules/magic-string": {
9697 - "version": "0.30.3",
9698 - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.3.tgz",
9699 - "integrity": "sha512-B7xGbll2fG/VjP+SWg4sX3JynwIU0mjoTc6MPpKNuIvftk6u6vqhDnk1R80b8C2GBR6ywqy+1DcKBrevBg+bmw==",
9598 + "version": "0.30.5",
9599 + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz",
9600 + "integrity": "sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==",
9601 "dependencies": {
9602 "@jridgewell/sourcemap-codec": "^1.4.15"
9603 },
@@ -9838,9 +9739,9 @@
9739 "dev": true
9740 },
9741 "node_modules/maplibre-gl": {
9841 - "version": "3.3.1",
9842 - "resolved": "https://registry.npmjs.org/maplibre-gl/-/maplibre-gl-3.3.1.tgz",
9843 - "integrity": "sha512-SfRq9bT68GytDzCOG0IoTGg2rASbgdYunW/6xhnp55QuLmwG1M/YOlXxqHaphwia7kZbMvBOocvY0fp5yfTjZA==",
9742 + "version": "3.5.2",
9743 + "resolved": "https://registry.npmjs.org/maplibre-gl/-/maplibre-gl-3.5.2.tgz",
9744 + "integrity": "sha512-deqYA/RiEyXMGroZMDbOWNQTLnFsxREC+mDkQnuyCUNdBWm1KHafsXJYZP7rlLa5RLQNq05IAUAizY9aHTpIUw==",
9745 "dependencies": {
9746 "@mapbox/geojson-rewind": "^0.5.2",
9747 "@mapbox/jsonlint-lines-primitives": "^2.0.2",
@@ -9849,12 +9750,12 @@
9750 "@mapbox/unitbezier": "^0.0.1",
9751 "@mapbox/vector-tile": "^1.3.1",
9752 "@mapbox/whoots-js": "^3.1.0",
9852 - "@maplibre/maplibre-gl-style-spec": "^19.3.0",
9853 - "@types/geojson": "^7946.0.10",
9854 - "@types/mapbox__point-geometry": "^0.1.2",
9855 - "@types/mapbox__vector-tile": "^1.3.0",
9856 - "@types/pbf": "^3.0.2",
9857 - "@types/supercluster": "^7.1.0",
9753 + "@maplibre/maplibre-gl-style-spec": "^19.3.3",
9754 + "@types/geojson": "^7946.0.12",
9755 + "@types/mapbox__point-geometry": "^0.1.3",
9756 + "@types/mapbox__vector-tile": "^1.3.3",
9757 + "@types/pbf": "^3.0.4",
9758 + "@types/supercluster": "^7.1.2",
9759 "earcut": "^2.2.4",
9760 "geojson-vt": "^3.2.1",
9761 "gl-matrix": "^3.4.3",
@@ -9903,40 +9804,13 @@
9804 }
9805 },
9806 "node_modules/mdast-util-definitions": {
9906 - "version": "5.1.2",
9907 - "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-5.1.2.tgz",
9908 - "integrity": "sha512-8SVPMuHqlPME/z3gqVwWY4zVXn8lqKv/pAhC57FuJ40ImXyBpmO5ukh98zB2v7Blql2FiHjHv9LVztSIqjY+MA==",
9909 - "dependencies": {
9910 - "@types/mdast": "^3.0.0",
9911 - "@types/unist": "^2.0.0",
9912 - "unist-util-visit": "^4.0.0"
9913 - },
9914 - "funding": {
9915 - "type": "opencollective",
9916 - "url": "https://opencollective.com/unified"
9917 - }
9918 - },
9919 - "node_modules/mdast-util-definitions/node_modules/unist-util-visit": {
9920 - "version": "4.1.2",
9921 - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-4.1.2.tgz",
9922 - "integrity": "sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg==",
9923 - "dependencies": {
9924 - "@types/unist": "^2.0.0",
9925 - "unist-util-is": "^5.0.0",
9926 - "unist-util-visit-parents": "^5.1.1"
9927 - },
9928 - "funding": {
9929 - "type": "opencollective",
9930 - "url": "https://opencollective.com/unified"
9931 - }
9932 - },
9933 - "node_modules/mdast-util-definitions/node_modules/unist-util-visit-parents": {
9934 - "version": "5.1.3",
9935 - "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-5.1.3.tgz",
9936 - "integrity": "sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg==",
9807 + "version": "6.0.0",
9808 + "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-6.0.0.tgz",
9809 + "integrity": "sha512-scTllyX6pnYNZH/AIp/0ePz6s4cZtARxImwoPJ7kS42n+MnVsI4XbnG6d4ibehRIldYMWM2LD7ImQblVhUejVQ==",
9810 "dependencies": {
9938 - "@types/unist": "^2.0.0",
9939 - "unist-util-is": "^5.0.0"
9811 + "@types/mdast": "^4.0.0",
9812 + "@types/unist": "^3.0.0",
9813 + "unist-util-visit": "^5.0.0"
9814 },
9815 "funding": {
9816 "type": "opencollective",
@@ -9944,22 +9818,22 @@
9818 }
9819 },
9820 "node_modules/mdast-util-from-markdown": {
9947 - "version": "1.3.1",
9948 - "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-1.3.1.tgz",
9949 - "integrity": "sha512-4xTO/M8c82qBcnQc1tgpNtubGUW/Y1tBQ1B0i5CtSoelOLKFYlElIr3bvgREYYO5iRqbMY1YuqZng0GVOI8Qww==",
9821 + "version": "2.0.0",
9822 + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.0.tgz",
9823 + "integrity": "sha512-n7MTOr/z+8NAX/wmhhDji8O3bRvPTV/U0oTCaZJkjhPSKTPhS3xufVhKGF8s1pJ7Ox4QgoIU7KHseh09S+9rTA==",
9824 "dependencies": {
9951 - "@types/mdast": "^3.0.0",
9952 - "@types/unist": "^2.0.0",
9825 + "@types/mdast": "^4.0.0",
9826 + "@types/unist": "^3.0.0",
9827 "decode-named-character-reference": "^1.0.0",
9954 - "mdast-util-to-string": "^3.1.0",
9955 - "micromark": "^3.0.0",
9956 - "micromark-util-decode-numeric-character-reference": "^1.0.0",
9957 - "micromark-util-decode-string": "^1.0.0",
9958 - "micromark-util-normalize-identifier": "^1.0.0",
9959 - "micromark-util-symbol": "^1.0.0",
9960 - "micromark-util-types": "^1.0.0",
9961 - "unist-util-stringify-position": "^3.0.0",
9962 - "uvu": "^0.5.0"
9828 + "devlop": "^1.0.0",
9829 + "mdast-util-to-string": "^4.0.0",
9830 + "micromark": "^4.0.0",
9831 + "micromark-util-decode-numeric-character-reference": "^2.0.0",
9832 + "micromark-util-decode-string": "^2.0.0",
9833 + "micromark-util-normalize-identifier": "^2.0.0",
9834 + "micromark-util-symbol": "^2.0.0",
9835 + "micromark-util-types": "^2.0.0",
9836 + "unist-util-stringify-position": "^4.0.0"
9837 },
9838 "funding": {
9839 "type": "opencollective",
@@ -9967,12 +9841,12 @@
9841 }
9842 },
9843 "node_modules/mdast-util-phrasing": {
9970 - "version": "3.0.1",
9971 - "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-3.0.1.tgz",
9972 - "integrity": "sha512-WmI1gTXUBJo4/ZmSk79Wcb2HcjPJBzM1nlI/OUWA8yk2X9ik3ffNbBGsU+09BFmXaL1IBb9fiuvq6/KMiNycSg==",
9844 + "version": "4.0.0",
9845 + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.0.0.tgz",
9846 + "integrity": "sha512-xadSsJayQIucJ9n053dfQwVu1kuXg7jCTdYsMK8rqzKZh52nLfSH/k0sAxE0u+pj/zKZX+o5wB+ML5mRayOxFA==",
9847 "dependencies": {
9974 - "@types/mdast": "^3.0.0",
9975 - "unist-util-is": "^5.0.0"
9848 + "@types/mdast": "^4.0.0",
9849 + "unist-util-is": "^6.0.0"
9850 },
9851 "funding": {
9852 "type": "opencollective",
@@ -9980,17 +9854,17 @@
9854 }
9855 },
9856 "node_modules/mdast-util-to-markdown": {
9983 - "version": "1.5.0",
9984 - "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-1.5.0.tgz",
9985 - "integrity": "sha512-bbv7TPv/WC49thZPg3jXuqzuvI45IL2EVAr/KxF0BSdHsU0ceFHOmwQn6evxAh1GaoK/6GQ1wp4R4oW2+LFL/A==",
9857 + "version": "2.1.0",
9858 + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.0.tgz",
9859 + "integrity": "sha512-SR2VnIEdVNCJbP6y7kVTJgPLifdr8WEU440fQec7qHoHOUz/oJ2jmNRqdDQ3rbiStOXb2mCDGTuwsK5OPUgYlQ==",
9860 "dependencies": {
9987 - "@types/mdast": "^3.0.0",
9988 - "@types/unist": "^2.0.0",
9861 + "@types/mdast": "^4.0.0",
9862 + "@types/unist": "^3.0.0",
9863 "longest-streak": "^3.0.0",
9990 - "mdast-util-phrasing": "^3.0.0",
9991 - "mdast-util-to-string": "^3.0.0",
9992 - "micromark-util-decode-string": "^1.0.0",
9993 - "unist-util-visit": "^4.0.0",
9864 + "mdast-util-phrasing": "^4.0.0",
9865 + "mdast-util-to-string": "^4.0.0",
9866 + "micromark-util-decode-string": "^2.0.0",
9867 + "unist-util-visit": "^5.0.0",
9868 "zwitch": "^2.0.0"
9869 },
9870 "funding": {
@@ -9998,39 +9872,12 @@
9872 "url": "https://opencollective.com/unified"
9873 }
9874 },
10001 - "node_modules/mdast-util-to-markdown/node_modules/unist-util-visit": {
10002 - "version": "4.1.2",
10003 - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-4.1.2.tgz",
10004 - "integrity": "sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg==",
10005 - "dependencies": {
10006 - "@types/unist": "^2.0.0",
10007 - "unist-util-is": "^5.0.0",
10008 - "unist-util-visit-parents": "^5.1.1"
10009 - },
10010 - "funding": {
10011 - "type": "opencollective",
10012 - "url": "https://opencollective.com/unified"
10013 - }
10014 - },
10015 - "node_modules/mdast-util-to-markdown/node_modules/unist-util-visit-parents": {
10016 - "version": "5.1.3",
10017 - "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-5.1.3.tgz",
10018 - "integrity": "sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg==",
10019 - "dependencies": {
10020 - "@types/unist": "^2.0.0",
10021 - "unist-util-is": "^5.0.0"
10022 - },
10023 - "funding": {
10024 - "type": "opencollective",
10025 - "url": "https://opencollective.com/unified"
10026 - }
10027 - },
9875 "node_modules/mdast-util-to-string": {
10029 - "version": "3.2.0",
10030 - "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-3.2.0.tgz",
10031 - "integrity": "sha512-V4Zn/ncyN1QNSqSBxTrMOLpjr+IKdHl2v3KVLoWmDPscP4r9GcCi71gjgvUV1SFSKh92AjAG4peFuBl2/YgCJg==",
9876 + "version": "4.0.0",
9877 + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz",
9878 + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==",
9879 "dependencies": {
10033 - "@types/mdast": "^3.0.0"
9880 + "@types/mdast": "^4.0.0"
9881 },
9882 "funding": {
9883 "type": "opencollective",
@@ -10091,9 +9938,9 @@
9938 }
9939 },
9940 "node_modules/micromark": {
10094 - "version": "3.2.0",
10095 - "resolved": "https://registry.npmjs.org/micromark/-/micromark-3.2.0.tgz",
10096 - "integrity": "sha512-uD66tJj54JLYq0De10AhWycZWGQNUvDI55xPgk2sQM5kn1JYlhbCMTtEeT27+vAhW2FBQxLlOmS3pmA7/2z4aA==",
9941 + "version": "4.0.0",
9942 + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.0.tgz",
9943 + "integrity": "sha512-o/sd0nMof8kYff+TqcDx3VSrgBTcZpSvYcAHIfHhv5VAuNmisCxjhx6YmxS8PFEpb9z5WKWKPdzf0jM23ro3RQ==",
9944 "funding": [
9945 {
9946 "type": "GitHub Sponsors",
@@ -10108,26 +9955,26 @@
9955 "@types/debug": "^4.0.0",
9956 "debug": "^4.0.0",
9957 "decode-named-character-reference": "^1.0.0",
10111 - "micromark-core-commonmark": "^1.0.1",
10112 - "micromark-factory-space": "^1.0.0",
10113 - "micromark-util-character": "^1.0.0",
10114 - "micromark-util-chunked": "^1.0.0",
10115 - "micromark-util-combine-extensions": "^1.0.0",
10116 - "micromark-util-decode-numeric-character-reference": "^1.0.0",
10117 - "micromark-util-encode": "^1.0.0",
10118 - "micromark-util-normalize-identifier": "^1.0.0",
10119 - "micromark-util-resolve-all": "^1.0.0",
10120 - "micromark-util-sanitize-uri": "^1.0.0",
10121 - "micromark-util-subtokenize": "^1.0.0",
10122 - "micromark-util-symbol": "^1.0.0",
10123 - "micromark-util-types": "^1.0.1",
10124 - "uvu": "^0.5.0"
9958 + "devlop": "^1.0.0",
9959 + "micromark-core-commonmark": "^2.0.0",
9960 + "micromark-factory-space": "^2.0.0",
9961 + "micromark-util-character": "^2.0.0",
9962 + "micromark-util-chunked": "^2.0.0",
9963 + "micromark-util-combine-extensions": "^2.0.0",
9964 + "micromark-util-decode-numeric-character-reference": "^2.0.0",
9965 + "micromark-util-encode": "^2.0.0",
9966 + "micromark-util-normalize-identifier": "^2.0.0",
9967 + "micromark-util-resolve-all": "^2.0.0",
9968 + "micromark-util-sanitize-uri": "^2.0.0",
9969 + "micromark-util-subtokenize": "^2.0.0",
9970 + "micromark-util-symbol": "^2.0.0",
9971 + "micromark-util-types": "^2.0.0"
9972 }
9973 },
9974 "node_modules/micromark-core-commonmark": {
10128 - "version": "1.1.0",
10129 - "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-1.1.0.tgz",
10130 - "integrity": "sha512-BgHO1aRbolh2hcrzL2d1La37V0Aoz73ymF8rAcKnohLy93titmv62E0gP8Hrx9PKcKrqCZ1BbLGbP3bEhoXYlw==",
9975 + "version": "2.0.0",
9976 + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.0.tgz",
9977 + "integrity": "sha512-jThOz/pVmAYUtkroV3D5c1osFXAMv9e0ypGDOIZuCeAe91/sD6BoE2Sjzt30yuXtwOYUmySOhMas/PVyh02itA==",
9978 "funding": [
9979 {
9980 "type": "GitHub Sponsors",
@@ -10140,27 +9987,27 @@
9987 ],
9988 "dependencies": {
9989 "decode-named-character-reference": "^1.0.0",
10143 - "micromark-factory-destination": "^1.0.0",
10144 - "micromark-factory-label": "^1.0.0",
10145 - "micromark-factory-space": "^1.0.0",
10146 - "micromark-factory-title": "^1.0.0",
10147 - "micromark-factory-whitespace": "^1.0.0",
10148 - "micromark-util-character": "^1.0.0",
10149 - "micromark-util-chunked": "^1.0.0",
10150 - "micromark-util-classify-character": "^1.0.0",
10151 - "micromark-util-html-tag-name": "^1.0.0",
10152 - "micromark-util-normalize-identifier": "^1.0.0",
10153 - "micromark-util-resolve-all": "^1.0.0",
10154 - "micromark-util-subtokenize": "^1.0.0",
10155 - "micromark-util-symbol": "^1.0.0",
10156 - "micromark-util-types": "^1.0.1",
10157 - "uvu": "^0.5.0"
9990 + "devlop": "^1.0.0",
9991 + "micromark-factory-destination": "^2.0.0",
9992 + "micromark-factory-label": "^2.0.0",
9993 + "micromark-factory-space": "^2.0.0",
9994 + "micromark-factory-title": "^2.0.0",
9995 + "micromark-factory-whitespace": "^2.0.0",
9996 + "micromark-util-character": "^2.0.0",
9997 + "micromark-util-chunked": "^2.0.0",
9998 + "micromark-util-classify-character": "^2.0.0",
9999 + "micromark-util-html-tag-name": "^2.0.0",
10000 + "micromark-util-normalize-identifier": "^2.0.0",
10001 + "micromark-util-resolve-all": "^2.0.0",
10002 + "micromark-util-subtokenize": "^2.0.0",
10003 + "micromark-util-symbol": "^2.0.0",
10004 + "micromark-util-types": "^2.0.0"
10005 }
10006 },
10007 "node_modules/micromark-factory-destination": {
10161 - "version": "1.1.0",
10162 - "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-1.1.0.tgz",
10163 - "integrity": "sha512-XaNDROBgx9SgSChd69pjiGKbV+nfHGDPVYFs5dOoDd7ZnMAE+Cuu91BCpsY8RT2NP9vo/B8pds2VQNCLiu0zhg==",
10008 + "version": "2.0.0",
10009 + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.0.tgz",
10010 + "integrity": "sha512-j9DGrQLm/Uhl2tCzcbLhy5kXsgkHUrjJHg4fFAeoMRwJmJerT9aw4FEhIbZStWN8A3qMwOp1uzHr4UL8AInxtA==",
10011 "funding": [
10012 {
10013 "type": "GitHub Sponsors",
@@ -10172,15 +10019,15 @@
10019 }
10020 ],
10021 "dependencies": {
10175 - "micromark-util-character": "^1.0.0",
10176 - "micromark-util-symbol": "^1.0.0",
10177 - "micromark-util-types": "^1.0.0"
10022 + "micromark-util-character": "^2.0.0",
10023 + "micromark-util-symbol": "^2.0.0",
10024 + "micromark-util-types": "^2.0.0"
10025 }
10026 },
10027 "node_modules/micromark-factory-label": {
10181 - "version": "1.1.0",
10182 - "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-1.1.0.tgz",
10183 - "integrity": "sha512-OLtyez4vZo/1NjxGhcpDSbHQ+m0IIGnT8BoPamh+7jVlzLJBH98zzuCoUeMxvM6WsNeh8wx8cKvqLiPHEACn0w==",
10028 + "version": "2.0.0",
10029 + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.0.tgz",
10030 + "integrity": "sha512-RR3i96ohZGde//4WSe/dJsxOX6vxIg9TimLAS3i4EhBAFx8Sm5SmqVfR8E87DPSR31nEAjZfbt91OMZWcNgdZw==",
10031 "funding": [
10032 {
10033 "type": "GitHub Sponsors",
@@ -10192,16 +10039,16 @@
10039 }
10040 ],
10041 "dependencies": {
10195 - "micromark-util-character": "^1.0.0",
10196 - "micromark-util-symbol": "^1.0.0",
10197 - "micromark-util-types": "^1.0.0",
10198 - "uvu": "^0.5.0"
10042 + "devlop": "^1.0.0",
10043 + "micromark-util-character": "^2.0.0",
10044 + "micromark-util-symbol": "^2.0.0",
10045 + "micromark-util-types": "^2.0.0"
10046 }
10047 },
10048 "node_modules/micromark-factory-space": {
10202 - "version": "1.1.0",
10203 - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-1.1.0.tgz",
10204 - "integrity": "sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ==",
10049 + "version": "2.0.0",
10050 + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.0.tgz",
10051 + "integrity": "sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==",
10052 "funding": [
10053 {
10054 "type": "GitHub Sponsors",
@@ -10213,14 +10060,14 @@
10060 }
10061 ],
10062 "dependencies": {
10216 - "micromark-util-character": "^1.0.0",
10217 - "micromark-util-types": "^1.0.0"
10063 + "micromark-util-character": "^2.0.0",
10064 + "micromark-util-types": "^2.0.0"
10065 }
10066 },
10067 "node_modules/micromark-factory-title": {
10221 - "version": "1.1.0",
10222 - "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-1.1.0.tgz",
10223 - "integrity": "sha512-J7n9R3vMmgjDOCY8NPw55jiyaQnH5kBdV2/UXCtZIpnHH3P6nHUKaH7XXEYuWwx/xUJcawa8plLBEjMPU24HzQ==",
10068 + "version": "2.0.0",
10069 + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.0.tgz",
10070 + "integrity": "sha512-jY8CSxmpWLOxS+t8W+FG3Xigc0RDQA9bKMY/EwILvsesiRniiVMejYTE4wumNc2f4UbAa4WsHqe3J1QS1sli+A==",
10071 "funding": [
10072 {
10073 "type": "GitHub Sponsors",
@@ -10232,16 +10079,16 @@
10079 }
10080 ],
10081 "dependencies": {
10235 - "micromark-factory-space": "^1.0.0",
10236 - "micromark-util-character": "^1.0.0",
10237 - "micromark-util-symbol": "^1.0.0",
10238 - "micromark-util-types": "^1.0.0"
10082 + "micromark-factory-space": "^2.0.0",
10083 + "micromark-util-character": "^2.0.0",
10084 + "micromark-util-symbol": "^2.0.0",
10085 + "micromark-util-types": "^2.0.0"
10086 }
10087 },
10088 "node_modules/micromark-factory-whitespace": {
10242 - "version": "1.1.0",
10243 - "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-1.1.0.tgz",
10244 - "integrity": "sha512-v2WlmiymVSp5oMg+1Q0N1Lxmt6pMhIHD457whWM7/GUlEks1hI9xj5w3zbc4uuMKXGisksZk8DzP2UyGbGqNsQ==",
10089 + "version": "2.0.0",
10090 + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.0.tgz",
10091 + "integrity": "sha512-28kbwaBjc5yAI1XadbdPYHX/eDnqaUFVikLwrO7FDnKG7lpgxnvk/XGRhX/PN0mOZ+dBSZ+LgunHS+6tYQAzhA==",
10092 "funding": [
10093 {
10094 "type": "GitHub Sponsors",
@@ -10253,16 +10100,16 @@
10100 }
10101 ],
10102 "dependencies": {
10256 - "micromark-factory-space": "^1.0.0",
10257 - "micromark-util-character": "^1.0.0",
10258 - "micromark-util-symbol": "^1.0.0",
10259 - "micromark-util-types": "^1.0.0"
10103 + "micromark-factory-space": "^2.0.0",
10104 + "micromark-util-character": "^2.0.0",
10105 + "micromark-util-symbol": "^2.0.0",
10106 + "micromark-util-types": "^2.0.0"
10107 }
10108 },
10109 "node_modules/micromark-util-character": {
10263 - "version": "1.2.0",
10264 - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.2.0.tgz",
10265 - "integrity": "sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==",
10110 + "version": "2.0.1",
10111 + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.0.1.tgz",
10112 + "integrity": "sha512-3wgnrmEAJ4T+mGXAUfMvMAbxU9RDG43XmGce4j6CwPtVxB3vfwXSZ6KhFwDzZ3mZHhmPimMAXg71veiBGzeAZw==",
10113 "funding": [
10114 {
10115 "type": "GitHub Sponsors",
@@ -10274,14 +10121,14 @@
10121 }
10122 ],
10123 "dependencies": {
10277 - "micromark-util-symbol": "^1.0.0",
10278 - "micromark-util-types": "^1.0.0"
10124 + "micromark-util-symbol": "^2.0.0",
10125 + "micromark-util-types": "^2.0.0"
10126 }
10127 },
10128 "node_modules/micromark-util-chunked": {
10282 - "version": "1.1.0",
10283 - "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-1.1.0.tgz",
10284 - "integrity": "sha512-Ye01HXpkZPNcV6FiyoW2fGZDUw4Yc7vT0E9Sad83+bEDiCJ1uXu0S3mr8WLpsz3HaG3x2q0HM6CTuPdcZcluFQ==",
10129 + "version": "2.0.0",
10130 + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.0.tgz",
10131 + "integrity": "sha512-anK8SWmNphkXdaKgz5hJvGa7l00qmcaUQoMYsBwDlSKFKjc6gjGXPDw3FNL3Nbwq5L8gE+RCbGqTw49FK5Qyvg==",
10132 "funding": [
10133 {
10134 "type": "GitHub Sponsors",
@@ -10293,13 +10140,13 @@
10140 }
10141 ],
10142 "dependencies": {
10296 - "micromark-util-symbol": "^1.0.0"
10143 + "micromark-util-symbol": "^2.0.0"
10144 }
10145 },
10146 "node_modules/micromark-util-classify-character": {
10300 - "version": "1.1.0",
10301 - "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-1.1.0.tgz",
10302 - "integrity": "sha512-SL0wLxtKSnklKSUplok1WQFoGhUdWYKggKUiqhX+Swala+BtptGCu5iPRc+xvzJ4PXE/hwM3FNXsfEVgoZsWbw==",
10147 + "version": "2.0.0",
10148 + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.0.tgz",
10149 + "integrity": "sha512-S0ze2R9GH+fu41FA7pbSqNWObo/kzwf8rN/+IGlW/4tC6oACOs8B++bh+i9bVyNnwCcuksbFwsBme5OCKXCwIw==",
10150 "funding": [
10151 {
10152 "type": "GitHub Sponsors",
@@ -10311,15 +10158,15 @@
10158 }
10159 ],
10160 "dependencies": {
10314 - "micromark-util-character": "^1.0.0",
10315 - "micromark-util-symbol": "^1.0.0",
10316 - "micromark-util-types": "^1.0.0"
10161 + "micromark-util-character": "^2.0.0",
10162 + "micromark-util-symbol": "^2.0.0",
10163 + "micromark-util-types": "^2.0.0"
10164 }
10165 },
10166 "node_modules/micromark-util-combine-extensions": {
10320 - "version": "1.1.0",
10321 - "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-1.1.0.tgz",
10322 - "integrity": "sha512-Q20sp4mfNf9yEqDL50WwuWZHUrCO4fEyeDCnMGmG5Pr0Cz15Uo7KBs6jq+dq0EgX4DPwwrh9m0X+zPV1ypFvUA==",
10167 + "version": "2.0.0",
10168 + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.0.tgz",
10169 + "integrity": "sha512-vZZio48k7ON0fVS3CUgFatWHoKbbLTK/rT7pzpJ4Bjp5JjkZeasRfrS9wsBdDJK2cJLHMckXZdzPSSr1B8a4oQ==",
10170 "funding": [
10171 {
10172 "type": "GitHub Sponsors",
@@ -10331,14 +10178,14 @@
10178 }
10179 ],
10180 "dependencies": {
10334 - "micromark-util-chunked": "^1.0.0",
10335 - "micromark-util-types": "^1.0.0"
10181 + "micromark-util-chunked": "^2.0.0",
10182 + "micromark-util-types": "^2.0.0"
10183 }
10184 },
10185 "node_modules/micromark-util-decode-numeric-character-reference": {
10339 - "version": "1.1.0",
10340 - "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-1.1.0.tgz",
10341 - "integrity": "sha512-m9V0ExGv0jB1OT21mrWcuf4QhP46pH1KkfWy9ZEezqHKAxkj4mPCy3nIH1rkbdMlChLHX531eOrymlwyZIf2iw==",
10186 + "version": "2.0.1",
10187 + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.1.tgz",
10188 + "integrity": "sha512-bmkNc7z8Wn6kgjZmVHOX3SowGmVdhYS7yBpMnuMnPzDq/6xwVA604DuOXMZTO1lvq01g+Adfa0pE2UKGlxL1XQ==",
10189 "funding": [
10190 {
10191 "type": "GitHub Sponsors",
@@ -10350,13 +10197,13 @@
10197 }
10198 ],
10199 "dependencies": {
10353 - "micromark-util-symbol": "^1.0.0"
10200 + "micromark-util-symbol": "^2.0.0"
10201 }
10202 },
10203 "node_modules/micromark-util-decode-string": {
10357 - "version": "1.1.0",
10358 - "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-1.1.0.tgz",
10359 - "integrity": "sha512-YphLGCK8gM1tG1bd54azwyrQRjCFcmgj2S2GoJDNnh4vYtnL38JS8M4gpxzOPNyHdNEpheyWXCTnnTDY3N+NVQ==",
10204 + "version": "2.0.0",
10205 + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.0.tgz",
10206 + "integrity": "sha512-r4Sc6leeUTn3P6gk20aFMj2ntPwn6qpDZqWvYmAG6NgvFTIlj4WtrAudLi65qYoaGdXYViXYw2pkmn7QnIFasA==",
10207 "funding": [
10208 {
10209 "type": "GitHub Sponsors",
@@ -10369,15 +10216,15 @@
10216 ],
10217 "dependencies": {
10218 "decode-named-character-reference": "^1.0.0",
10372 - "micromark-util-character": "^1.0.0",
10373 - "micromark-util-decode-numeric-character-reference": "^1.0.0",
10374 - "micromark-util-symbol": "^1.0.0"
10219 + "micromark-util-character": "^2.0.0",
10220 + "micromark-util-decode-numeric-character-reference": "^2.0.0",
10221 + "micromark-util-symbol": "^2.0.0"
10222 }
10223 },
10224 "node_modules/micromark-util-encode": {
10378 - "version": "1.1.0",
10379 - "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-1.1.0.tgz",
10380 - "integrity": "sha512-EuEzTWSTAj9PA5GOAs992GzNh2dGQO52UvAbtSOMvXTxv3Criqb6IOzJUBCmEqrrXSblJIJBbFFv6zPxpreiJw==",
10225 + "version": "2.0.0",
10226 + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.0.tgz",
10227 + "integrity": "sha512-pS+ROfCXAGLWCOc8egcBvT0kf27GoWMqtdarNfDcjb6YLuV5cM3ioG45Ys2qOVqeqSbjaKg72vU+Wby3eddPsA==",
10228 "funding": [
10229 {
10230 "type": "GitHub Sponsors",
@@ -10390,9 +10237,9 @@
10237 ]
10238 },
10239 "node_modules/micromark-util-html-tag-name": {
10393 - "version": "1.2.0",
10394 - "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-1.2.0.tgz",
10395 - "integrity": "sha512-VTQzcuQgFUD7yYztuQFKXT49KghjtETQ+Wv/zUjGSGBioZnkA4P1XXZPT1FHeJA6RwRXSF47yvJ1tsJdoxwO+Q==",
10240 + "version": "2.0.0",
10241 + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.0.tgz",
10242 + "integrity": "sha512-xNn4Pqkj2puRhKdKTm8t1YHC/BAjx6CEwRFXntTaRf/x16aqka6ouVoutm+QdkISTlT7e2zU7U4ZdlDLJd2Mcw==",
10243 "funding": [
10244 {
10245 "type": "GitHub Sponsors",
@@ -10405,9 +10252,9 @@
10252 ]
10253 },
10254 "node_modules/micromark-util-normalize-identifier": {
10408 - "version": "1.1.0",
10409 - "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-1.1.0.tgz",
10410 - "integrity": "sha512-N+w5vhqrBihhjdpM8+5Xsxy71QWqGn7HYNUvch71iV2PM7+E3uWGox1Qp90loa1ephtCxG2ftRV/Conitc6P2Q==",
10255 + "version": "2.0.0",
10256 + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.0.tgz",
10257 + "integrity": "sha512-2xhYT0sfo85FMrUPtHcPo2rrp1lwbDEEzpx7jiH2xXJLqBuy4H0GgXk5ToU8IEwoROtXuL8ND0ttVa4rNqYK3w==",
10258 "funding": [
10259 {
10260 "type": "GitHub Sponsors",
@@ -10419,13 +10266,13 @@
10266 }
10267 ],
10268 "dependencies": {
10422 - "micromark-util-symbol": "^1.0.0"
10269 + "micromark-util-symbol": "^2.0.0"
10270 }
10271 },
10272 "node_modules/micromark-util-resolve-all": {
10426 - "version": "1.1.0",
10427 - "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-1.1.0.tgz",
10428 - "integrity": "sha512-b/G6BTMSg+bX+xVCshPTPyAu2tmA0E4X98NSR7eIbeC6ycCqCeE7wjfDIgzEbkzdEVJXRtOG4FbEm/uGbCRouA==",
10273 + "version": "2.0.0",
10274 + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.0.tgz",
10275 + "integrity": "sha512-6KU6qO7DZ7GJkaCgwBNtplXCvGkJToU86ybBAUdavvgsCiG8lSSvYxr9MhwmQ+udpzywHsl4RpGJsYWG1pDOcA==",
10276 "funding": [
10277 {
10278 "type": "GitHub Sponsors",
@@ -10437,13 +10284,13 @@
10284 }
10285 ],
10286 "dependencies": {
10440 - "micromark-util-types": "^1.0.0"
10287 + "micromark-util-types": "^2.0.0"
10288 }
10289 },
10290 "node_modules/micromark-util-sanitize-uri": {
10444 - "version": "1.2.0",
10445 - "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-1.2.0.tgz",
10446 - "integrity": "sha512-QO4GXv0XZfWey4pYFndLUKEAktKkG5kZTdUNaTAkzbuJxn2tNBOr+QtxR2XpWaMhbImT2dPzyLrPXLlPhph34A==",
10291 + "version": "2.0.0",
10292 + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.0.tgz",
10293 + "integrity": "sha512-WhYv5UEcZrbAtlsnPuChHUAsu/iBPOVaEVsntLBIdpibO0ddy8OzavZz3iL2xVvBZOpolujSliP65Kq0/7KIYw==",
10294 "funding": [
10295 {
10296 "type": "GitHub Sponsors",
@@ -10455,15 +10302,15 @@
10302 }
10303 ],
10304 "dependencies": {
10458 - "micromark-util-character": "^1.0.0",
10459 - "micromark-util-encode": "^1.0.0",
10460 - "micromark-util-symbol": "^1.0.0"
10305 + "micromark-util-character": "^2.0.0",
10306 + "micromark-util-encode": "^2.0.0",
10307 + "micromark-util-symbol": "^2.0.0"
10308 }
10309 },
10310 "node_modules/micromark-util-subtokenize": {
10464 - "version": "1.1.0",
10465 - "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-1.1.0.tgz",
10466 - "integrity": "sha512-kUQHyzRoxvZO2PuLzMt2P/dwVsTiivCK8icYTeR+3WgbuPqfHgPPy7nFKbeqRivBvn/3N3GBiNC+JRTMSxEC7A==",
10311 + "version": "2.0.0",
10312 + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.0.0.tgz",
10313 + "integrity": "sha512-vc93L1t+gpR3p8jxeVdaYlbV2jTYteDje19rNSS/H5dlhxUYll5Fy6vJ2cDwP8RnsXi818yGty1ayP55y3W6fg==",
10314 "funding": [
10315 {
10316 "type": "GitHub Sponsors",
@@ -10475,16 +10322,16 @@
10322 }
10323 ],
10324 "dependencies": {
10478 - "micromark-util-chunked": "^1.0.0",
10479 - "micromark-util-symbol": "^1.0.0",
10480 - "micromark-util-types": "^1.0.0",
10481 - "uvu": "^0.5.0"
10325 + "devlop": "^1.0.0",
10326 + "micromark-util-chunked": "^2.0.0",
10327 + "micromark-util-symbol": "^2.0.0",
10328 + "micromark-util-types": "^2.0.0"
10329 }
10330 },
10331 "node_modules/micromark-util-symbol": {
10485 - "version": "1.1.0",
10486 - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz",
10487 - "integrity": "sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==",
10332 + "version": "2.0.0",
10333 + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz",
10334 + "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==",
10335 "funding": [
10336 {
10337 "type": "GitHub Sponsors",
@@ -10497,9 +10344,9 @@
10344 ]
10345 },
10346 "node_modules/micromark-util-types": {
10500 - "version": "1.1.0",
10501 - "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz",
10502 - "integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==",
10347 + "version": "2.0.0",
10348 + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.0.tgz",
10349 + "integrity": "sha512-oNh6S2WMHWRZrmutsRmDDfkzKtxF+bc2VxLC9dvtrDIRFln627VsFP6fLMgTryGDljgLPjkrzQSDcPrjPyDJ5w==",
10350 "funding": [
10351 {
10352 "type": "GitHub Sponsors",
@@ -10808,14 +10655,6 @@
10655 "ufo": "^1.3.0"
10656 }
10657 },
10811 - "node_modules/mri": {
10812 - "version": "1.2.0",
10813 - "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz",
10814 - "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==",
10815 - "engines": {
10816 - "node": ">=4"
10817 - }
10818 - },
10658 "node_modules/ms": {
10659 "version": "2.1.2",
10660 "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
@@ -10871,18 +10710,10 @@
10710 "vue": "^3.0.0"
10711 }
10712 },
10874 - "node_modules/naive-ui/node_modules/date-fns-tz": {
10875 - "version": "2.0.0",
10876 - "resolved": "https://registry.npmjs.org/date-fns-tz/-/date-fns-tz-2.0.0.tgz",
10877 - "integrity": "sha512-OAtcLdB9vxSXTWHdT8b398ARImVwQMyjfYGkKD2zaGpHseG2UPHbHjXELReErZFxWdSLph3c2zOaaTyHfOhERQ==",
10878 - "peerDependencies": {
10879 - "date-fns": ">=2.0.0"
10880 - }
10881 - },
10713 "node_modules/nanoid": {
10883 - "version": "4.0.2",
10884 - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-4.0.2.tgz",
10885 - "integrity": "sha512-7ZtY5KTCNheRGfEFxnedV5zFiORN1+Y1N6zvPTnHQd8ENUvfaDBeuJDZb2bN/oXwXxu3qkTXDzy57W5vAmDTBw==",
10714 + "version": "5.0.2",
10715 + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.0.2.tgz",
10716 + "integrity": "sha512-2ustYUX1R2rL/Br5B/FMhi8d5/QzvkJ912rBYxskcpu0myTHzSZfTr1LAS2Sm7jxRUObRrSBFoyzwAhL49aVSg==",
10717 "funding": [
10718 {
10719 "type": "github",
@@ -10893,7 +10724,7 @@
10724 "nanoid": "bin/nanoid.js"
10725 },
10726 "engines": {
10896 - "node": "^14 || ^16 || >=18"
10727 + "node": "^18 || >=20"
10728 }
10729 },
10730 "node_modules/natural-compare": {
@@ -11909,9 +11740,9 @@
11740 }
11741 },
11742 "node_modules/pinia": {
11912 - "version": "2.1.6",
11913 - "resolved": "https://registry.npmjs.org/pinia/-/pinia-2.1.6.tgz",
11914 - "integrity": "sha512-bIU6QuE5qZviMmct5XwCesXelb5VavdOWKWaB17ggk++NUwQWWbP5YnsONTk3b752QkW9sACiR81rorpeOMSvQ==",
11743 + "version": "2.1.7",
11744 + "resolved": "https://registry.npmjs.org/pinia/-/pinia-2.1.7.tgz",
11745 + "integrity": "sha512-+C2AHFtcFqjPih0zpYuvof37SFxMQ7OEG2zV9jRI12i9BOy3YQVAHwdKtyyc8pDcDyIc33WCIsZaCFWU7WWxGQ==",
11746 "dependencies": {
11747 "@vue/devtools-api": "^6.5.0",
11748 "vue-demi": ">=0.14.5"
@@ -12890,14 +12721,14 @@
12721 }
12722 },
12723 "node_modules/remark": {
12893 - "version": "14.0.3",
12894 - "resolved": "https://registry.npmjs.org/remark/-/remark-14.0.3.tgz",
12895 - "integrity": "sha512-bfmJW1dmR2LvaMJuAnE88pZP9DktIFYXazkTfOIKZzi3Knk9lT0roItIA24ydOucI3bV/g/tXBA6hzqq3FV9Ew==",
12724 + "version": "15.0.1",
12725 + "resolved": "https://registry.npmjs.org/remark/-/remark-15.0.1.tgz",
12726 + "integrity": "sha512-Eht5w30ruCXgFmxVUSlNWQ9iiimq07URKeFS3hNc8cUWy1llX4KDWfyEDZRycMc+znsN9Ux5/tJ/BFdgdOwA3A==",
12727 "dependencies": {
12897 - "@types/mdast": "^3.0.0",
12898 - "remark-parse": "^10.0.0",
12899 - "remark-stringify": "^10.0.0",
12900 - "unified": "^10.0.0"
12728 + "@types/mdast": "^4.0.0",
12729 + "remark-parse": "^11.0.0",
12730 + "remark-stringify": "^11.0.0",
12731 + "unified": "^11.0.0"
12732 },
12733 "funding": {
12734 "type": "opencollective",
@@ -12905,41 +12736,13 @@
12736 }
12737 },
12738 "node_modules/remark-inline-links": {
12908 - "version": "6.0.1",
12909 - "resolved": "https://registry.npmjs.org/remark-inline-links/-/remark-inline-links-6.0.1.tgz",
12910 - "integrity": "sha512-etdk1A0kRs+bXtT41XEFfyePOu583cmuHDF8bhAUfHJeCAPbPZpqmqZHD/wLhijIJV3ldjIvO4irM0jRGb1Dhg==",
12911 - "dependencies": {
12912 - "@types/mdast": "^3.0.0",
12913 - "mdast-util-definitions": "^5.0.0",
12914 - "unified": "^10.0.0",
12915 - "unist-util-visit": "^4.0.0"
12916 - },
12917 - "funding": {
12918 - "type": "opencollective",
12919 - "url": "https://opencollective.com/unified"
12920 - }
12921 - },
12922 - "node_modules/remark-inline-links/node_modules/unist-util-visit": {
12923 - "version": "4.1.2",
12924 - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-4.1.2.tgz",
12925 - "integrity": "sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg==",
12926 - "dependencies": {
12927 - "@types/unist": "^2.0.0",
12928 - "unist-util-is": "^5.0.0",
12929 - "unist-util-visit-parents": "^5.1.1"
12930 - },
12931 - "funding": {
12932 - "type": "opencollective",
12933 - "url": "https://opencollective.com/unified"
12934 - }
12935 - },
12936 - "node_modules/remark-inline-links/node_modules/unist-util-visit-parents": {
12937 - "version": "5.1.3",
12938 - "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-5.1.3.tgz",
12939 - "integrity": "sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg==",
12739 + "version": "7.0.0",
12740 + "resolved": "https://registry.npmjs.org/remark-inline-links/-/remark-inline-links-7.0.0.tgz",
12741 + "integrity": "sha512-4uj1pPM+F495ySZhTIB6ay2oSkTsKgmYaKk/q5HIdhX2fuyLEegpjWa0VdJRJ01sgOqAFo7MBKdDUejIYBMVMQ==",
12742 "dependencies": {
12941 - "@types/unist": "^2.0.0",
12942 - "unist-util-is": "^5.0.0"
12743 + "@types/mdast": "^4.0.0",
12744 + "mdast-util-definitions": "^6.0.0",
12745 + "unist-util-visit": "^5.0.0"
12746 },
12747 "funding": {
12748 "type": "opencollective",
@@ -12947,13 +12750,14 @@
12750 }
12751 },
12752 "node_modules/remark-parse": {
12950 - "version": "10.0.2",
12951 - "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-10.0.2.tgz",
12952 - "integrity": "sha512-3ydxgHa/ZQzG8LvC7jTXccARYDcRld3VfcgIIFs7bI6vbRSxJJmzgLEIIoYKyrfhaY+ujuWaf/PJiMZXoiCXgw==",
12753 + "version": "11.0.0",
12754 + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz",
12755 + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==",
12756 "dependencies": {
12954 - "@types/mdast": "^3.0.0",
12955 - "mdast-util-from-markdown": "^1.0.0",
12956 - "unified": "^10.0.0"
12757 + "@types/mdast": "^4.0.0",
12758 + "mdast-util-from-markdown": "^2.0.0",
12759 + "micromark-util-types": "^2.0.0",
12760 + "unified": "^11.0.0"
12761 },
12762 "funding": {
12763 "type": "opencollective",
@@ -12961,13 +12765,13 @@
12765 }
12766 },
12767 "node_modules/remark-stringify": {
12964 - "version": "10.0.3",
12965 - "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-10.0.3.tgz",
12966 - "integrity": "sha512-koyOzCMYoUHudypbj4XpnAKFbkddRMYZHwghnxd7ue5210WzGw6kOBwauJTRUMq16jsovXx8dYNvSSWP89kZ3A==",
12768 + "version": "11.0.0",
12769 + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz",
12770 + "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==",
12771 "dependencies": {
12968 - "@types/mdast": "^3.0.0",
12969 - "mdast-util-to-markdown": "^1.0.0",
12970 - "unified": "^10.0.0"
12772 + "@types/mdast": "^4.0.0",
12773 + "mdast-util-to-markdown": "^2.0.0",
12774 + "unified": "^11.0.0"
12775 },
12776 "funding": {
12777 "type": "opencollective",
@@ -13375,17 +13179,6 @@
13179 "tslib": "^2.1.0"
13180 }
13181 },
13378 - "node_modules/sade": {
13379 - "version": "1.8.1",
13380 - "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz",
13381 - "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==",
13382 - "dependencies": {
13383 - "mri": "^1.1.0"
13384 - },
13385 - "engines": {
13386 - "node": ">=6"
13387 - }
13388 - },
13182 "node_modules/safe-array-concat": {
13183 "version": "1.0.1",
13184 "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.0.1.tgz",
@@ -13445,9 +13238,9 @@
13238 "dev": true
13239 },
13240 "node_modules/sass": {
13448 - "version": "1.69.0",
13449 - "resolved": "https://registry.npmjs.org/sass/-/sass-1.69.0.tgz",
13450 - "integrity": "sha512-l3bbFpfTOGgQZCLU/gvm1lbsQ5mC/WnLz3djL2v4WCJBDrWm58PO+jgngcGRNnKUh6wSsdm50YaovTqskZ0xDQ==",
13241 + "version": "1.69.5",
13242 + "resolved": "https://registry.npmjs.org/sass/-/sass-1.69.5.tgz",
13243 + "integrity": "sha512-qg2+UCJibLr2LCVOt3OlPhr/dqVHWOa9XtZf2OjbLs/T4VPSJ00udtgJxH3neXZm+QqX8B+3cU7RaLqp1iVfcQ==",
13244 "dev": true,
13245 "dependencies": {
13246 "chokidar": ">=3.0.0 <4.0.0",
@@ -14452,9 +14245,9 @@
14245 }
14246 },
14247 "node_modules/tailwindcss": {
14455 - "version": "3.3.3",
14456 - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.3.tgz",
14457 - "integrity": "sha512-A0KgSkef7eE4Mf+nKJ83i75TMyq8HqY3qmFIJSWy8bNt0v1lG7jUcpGpoTFxAwYcWOphcTBLPPJg+bDfhDf52w==",
14248 + "version": "3.3.5",
14249 + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.5.tgz",
14250 + "integrity": "sha512-5SEZU4J7pxZgSkv7FP1zY8i2TIAOooNZ1e/OGtxIEv6GltpoiXUqWvLy89+a10qYTB1N5Ifkuw9lqQkN9sscvA==",
14251 "dev": true,
14252 "dependencies": {
14253 "@alloc/quick-lru": "^5.2.0",
@@ -14462,10 +14255,10 @@
14255 "chokidar": "^3.5.3",
14256 "didyoumean": "^1.2.2",
14257 "dlv": "^1.1.3",
14465 - "fast-glob": "^3.2.12",
14258 + "fast-glob": "^3.3.0",
14259 "glob-parent": "^6.0.2",
14260 "is-glob": "^4.0.3",
14468 - "jiti": "^1.18.2",
14261 + "jiti": "^1.19.1",
14262 "lilconfig": "^2.1.0",
14263 "micromatch": "^4.0.5",
14264 "normalize-path": "^3.0.0",
@@ -14557,13 +14350,13 @@
14350 "dev": true
14351 },
14352 "node_modules/taze": {
14560 - "version": "0.11.3",
14561 - "resolved": "https://registry.npmjs.org/taze/-/taze-0.11.3.tgz",
14562 - "integrity": "sha512-8VbKvowHtj9aO2/NYIHXv48bD1ysBHBbHS1YuAm6Zi0WndblZdzDCVoOwKaX0eN23BHX5Q/Y8FE6kqDf/sWmdA==",
14353 + "version": "0.12.0",
14354 + "resolved": "https://registry.npmjs.org/taze/-/taze-0.12.0.tgz",
14355 + "integrity": "sha512-ftDYS/dNF0EkiEpZZweJxJKQRVFPBFFDz3OPezFloVtOHoMmVwLiMxLM9KYLnA+30D8IFdnGgS15WiIaz2bOfA==",
14356 "dev": true,
14357 "dependencies": {
14358 "@antfu/ni": "^0.21.8",
14566 - "@npmcli/config": "^6.3.0",
14359 + "@npmcli/config": "^8.0.1",
14360 "cli-progress": "^3.12.0",
14361 "deepmerge": "^4.3.1",
14362 "detect-indent": "^7.0.1",
@@ -14572,7 +14365,7 @@
14365 "picocolors": "^1.0.0",
14366 "prompts": "^2.4.2",
14367 "semver": "^7.5.4",
14575 - "unconfig": "^0.3.10",
14368 + "unconfig": "^0.3.11",
14369 "yargs": "^17.7.2"
14370 },
14371 "bin": {
@@ -15250,18 +15043,24 @@
15043 "url": "https://github.com/sponsors/antfu"
15044 }
15045 },
15046 + "node_modules/undici-types": {
15047 + "version": "5.26.5",
15048 + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
15049 + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
15050 + "dev": true
15051 + },
15052 "node_modules/unified": {
15254 - "version": "10.1.2",
15255 - "resolved": "https://registry.npmjs.org/unified/-/unified-10.1.2.tgz",
15256 - "integrity": "sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q==",
15053 + "version": "11.0.4",
15054 + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.4.tgz",
15055 + "integrity": "sha512-apMPnyLjAX+ty4OrNap7yumyVAMlKx5IWU2wlzzUdYJO9A8f1p9m/gywF/GM2ZDFcjQPrx59Mc90KwmxsoklxQ==",
15056 "dependencies": {
15258 - "@types/unist": "^2.0.0",
15057 + "@types/unist": "^3.0.0",
15058 "bail": "^2.0.0",
15059 + "devlop": "^1.0.0",
15060 "extend": "^3.0.0",
15261 - "is-buffer": "^2.0.0",
15061 "is-plain-obj": "^4.0.0",
15062 "trough": "^2.0.0",
15264 - "vfile": "^5.0.0"
15063 + "vfile": "^6.0.0"
15064 },
15065 "funding": {
15066 "type": "opencollective",
@@ -15315,11 +15114,11 @@
15114 }
15115 },
15116 "node_modules/unist-util-is": {
15318 - "version": "5.2.1",
15319 - "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-5.2.1.tgz",
15320 - "integrity": "sha512-u9njyyfEh43npf1M+yGKDGVPbY/JWEemg5nH05ncKPfi+kBbKBJoTdsogMu33uhytuLlv9y0O7GH7fEdwLdLQw==",
15117 + "version": "6.0.0",
15118 + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz",
15119 + "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==",
15120 "dependencies": {
15322 - "@types/unist": "^2.0.0"
15121 + "@types/unist": "^3.0.0"
15122 },
15123 "funding": {
15124 "type": "opencollective",
@@ -15327,11 +15126,11 @@
15126 }
15127 },
15128 "node_modules/unist-util-stringify-position": {
15330 - "version": "3.0.3",
15331 - "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-3.0.3.tgz",
15332 - "integrity": "sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg==",
15129 + "version": "4.0.0",
15130 + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz",
15131 + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==",
15132 "dependencies": {
15334 - "@types/unist": "^2.0.0"
15133 + "@types/unist": "^3.0.0"
15134 },
15135 "funding": {
15136 "type": "opencollective",
@@ -15365,40 +15164,6 @@
15164 "url": "https://opencollective.com/unified"
15165 }
15166 },
15368 - "node_modules/unist-util-visit-parents/node_modules/@types/unist": {
15369 - "version": "3.0.0",
15370 - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.0.tgz",
15371 - "integrity": "sha512-MFETx3tbTjE7Uk6vvnWINA/1iJ7LuMdO4fcq8UfF0pRbj01aGLduVvQcRyswuACJdpnHgg8E3rQLhaRdNEJS0w=="
15372 - },
15373 - "node_modules/unist-util-visit-parents/node_modules/unist-util-is": {
15374 - "version": "6.0.0",
15375 - "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz",
15376 - "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==",
15377 - "dependencies": {
15378 - "@types/unist": "^3.0.0"
15379 - },
15380 - "funding": {
15381 - "type": "opencollective",
15382 - "url": "https://opencollective.com/unified"
15383 - }
15384 - },
15385 - "node_modules/unist-util-visit/node_modules/@types/unist": {
15386 - "version": "3.0.0",
15387 - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.0.tgz",
15388 - "integrity": "sha512-MFETx3tbTjE7Uk6vvnWINA/1iJ7LuMdO4fcq8UfF0pRbj01aGLduVvQcRyswuACJdpnHgg8E3rQLhaRdNEJS0w=="
15389 - },
15390 - "node_modules/unist-util-visit/node_modules/unist-util-is": {
15391 - "version": "6.0.0",
15392 - "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz",
15393 - "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==",
15394 - "dependencies": {
15395 - "@types/unist": "^3.0.0"
15396 - },
15397 - "funding": {
15398 - "type": "opencollective",
15399 - "url": "https://opencollective.com/unified"
15400 - }
15401 - },
15167 "node_modules/universalify": {
15168 "version": "2.0.0",
15169 "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
@@ -15554,40 +15319,15 @@
15319 "uuid": "dist/bin/uuid"
15320 }
15321 },
15557 - "node_modules/uvu": {
15558 - "version": "0.5.6",
15559 - "resolved": "https://registry.npmjs.org/uvu/-/uvu-0.5.6.tgz",
15560 - "integrity": "sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==",
15561 - "dependencies": {
15562 - "dequal": "^2.0.0",
15563 - "diff": "^5.0.0",
15564 - "kleur": "^4.0.3",
15565 - "sade": "^1.7.3"
15566 - },
15567 - "bin": {
15568 - "uvu": "bin.js"
15569 - },
15570 - "engines": {
15571 - "node": ">=8"
15572 - }
15573 - },
15574 - "node_modules/uvu/node_modules/diff": {
15575 - "version": "5.1.0",
15576 - "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz",
15577 - "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==",
15578 - "engines": {
15579 - "node": ">=0.3.1"
15580 - }
15581 - },
15322 "node_modules/v-calendar": {
15583 - "version": "3.1.0",
15584 - "resolved": "https://registry.npmjs.org/v-calendar/-/v-calendar-3.1.0.tgz",
15585 - "integrity": "sha512-AoVTpz5QkaK+qRSTOEHOW2IxH+KfuQGnEk/N64y4cj2K37kGeC3M7nWVqXjXqBHPHpo5WmtNZ5lidUT+nn4ZAA==",
15323 + "version": "3.1.2",
15324 + "resolved": "https://registry.npmjs.org/v-calendar/-/v-calendar-3.1.2.tgz",
15325 + "integrity": "sha512-QDWrnp4PWCpzUblctgo4T558PrHgHzDtQnTeUNzKxfNf29FkCeFpwGd9bKjAqktaa2aJLcyRl45T5ln1ku34kg==",
15326 "dependencies": {
15327 "@types/lodash": "^4.14.165",
15328 "@types/resize-observer-browser": "^0.1.7",
15329 "date-fns": "^2.16.1",
15590 - "date-fns-tz": "^1.0.12",
15330 + "date-fns-tz": "^2.0.0",
15331 "lodash": "^4.17.20",
15332 "vue-screen-utils": "^1.0.0-beta.13"
15333 },
@@ -15667,14 +15407,13 @@
15407 }
15408 },
15409 "node_modules/vfile": {
15670 - "version": "5.3.7",
15671 - "resolved": "https://registry.npmjs.org/vfile/-/vfile-5.3.7.tgz",
15672 - "integrity": "sha512-r7qlzkgErKjobAmyNIkkSpizsFPYiUPuJb5pNW1RB4JcYVZhs4lIbVqk8XPk033CV/1z8ss5pkax8SuhGpcG8g==",
15410 + "version": "6.0.1",
15411 + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.1.tgz",
15412 + "integrity": "sha512-1bYqc7pt6NIADBJ98UiG0Bn/CHIVOoZ/IyEkqIruLg0mE1BKzkOXY2D6CSqQIcKqgadppE5lrxgWXJmXd7zZJw==",
15413 "dependencies": {
15674 - "@types/unist": "^2.0.0",
15675 - "is-buffer": "^2.0.0",
15676 - "unist-util-stringify-position": "^3.0.0",
15677 - "vfile-message": "^3.0.0"
15414 + "@types/unist": "^3.0.0",
15415 + "unist-util-stringify-position": "^4.0.0",
15416 + "vfile-message": "^4.0.0"
15417 },
15418 "funding": {
15419 "type": "opencollective",
@@ -15682,12 +15421,12 @@
15421 }
15422 },
15423 "node_modules/vfile-message": {
15685 - "version": "3.1.4",
15686 - "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-3.1.4.tgz",
15687 - "integrity": "sha512-fa0Z6P8HUrQN4BZaX05SIVXic+7kE3b05PWAtPuYP9QLHsLKYR7/AlLW3NtOrpXRLeawpDLMsVkmk5DG0NXgWw==",
15424 + "version": "4.0.2",
15425 + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz",
15426 + "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==",
15427 "dependencies": {
15689 - "@types/unist": "^2.0.0",
15690 - "unist-util-stringify-position": "^3.0.0"
15428 + "@types/unist": "^3.0.0",
15429 + "unist-util-stringify-position": "^4.0.0"
15430 },
15431 "funding": {
15432 "type": "opencollective",
@@ -15695,9 +15434,9 @@
15434 }
15435 },
15436 "node_modules/vite": {
15698 - "version": "4.4.11",
15699 - "resolved": "https://registry.npmjs.org/vite/-/vite-4.4.11.tgz",
15700 - "integrity": "sha512-ksNZJlkcU9b0lBwAGZGGaZHCMqHsc8OpgtoYhsQ4/I2v5cnpmmmqe5pM4nv/4Hn6G/2GhTdj0DhZh2e+Er1q5A==",
15437 + "version": "4.5.0",
15438 + "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.0.tgz",
15439 + "integrity": "sha512-ulr8rNLA6rkyFAlVWw2q5YJ91v098AFQ2R0PRFwPzREXOUJQPtFUG0t+/ZikhaOCDqFoDhN6/v8Sq0o4araFAw==",
15440 "dev": true,
15441 "dependencies": {
15442 "esbuild": "^0.18.10",
@@ -15881,15 +15620,23 @@
15620 }
15621 },
15622 "node_modules/vue": {
15884 - "version": "3.3.4",
15885 - "resolved": "https://registry.npmjs.org/vue/-/vue-3.3.4.tgz",
15886 - "integrity": "sha512-VTyEYn3yvIeY1Py0WaYGZsXnz3y5UnGi62GjVEqvEGPl6nxbOrCXbVOTQWBEJUqAyTUk2uJ5JLVnYJ6ZzGbrSw==",
15623 + "version": "3.3.7",
15624 + "resolved": "https://registry.npmjs.org/vue/-/vue-3.3.7.tgz",
15625 + "integrity": "sha512-YEMDia1ZTv1TeBbnu6VybatmSteGOS3A3YgfINOfraCbf85wdKHzscD6HSS/vB4GAtI7sa1XPX7HcQaJ1l24zA==",
15626 "dependencies": {
15888 - "@vue/compiler-dom": "3.3.4",
15889 - "@vue/compiler-sfc": "3.3.4",
15890 - "@vue/runtime-dom": "3.3.4",
15891 - "@vue/server-renderer": "3.3.4",
15892 - "@vue/shared": "3.3.4"
15627 + "@vue/compiler-dom": "3.3.7",
15628 + "@vue/compiler-sfc": "3.3.7",
15629 + "@vue/runtime-dom": "3.3.7",
15630 + "@vue/server-renderer": "3.3.7",
15631 + "@vue/shared": "3.3.7"
15632 + },
15633 + "peerDependencies": {
15634 + "typescript": "*"
15635 + },
15636 + "peerDependenciesMeta": {
15637 + "typescript": {
15638 + "optional": true
15639 + }
15640 }
15641 },
15642 "node_modules/vue-advanced-cropper": {
@@ -16004,12 +15751,12 @@
15751 }
15752 },
15753 "node_modules/vue-i18n": {
16007 - "version": "9.5.0",
16008 - "resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.5.0.tgz",
16009 - "integrity": "sha512-NiI3Ph1qMstNf7uhYh8trQBOBFLxeJgcOxBq51pCcZ28Vs18Y7BDS58r8HGDKCYgXdLUYqPDXdKatIF4bvBVZg==",
15754 + "version": "9.6.2",
15755 + "resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.6.2.tgz",
15756 + "integrity": "sha512-J43grTQjPR8LCUxvx3mkoM+11xhTnej1Al4lvJCEeKmQqf8eqbuYPQb54HXnEg/UzZyaxLBAwPAUTbrZ8V7hcg==",
15757 "dependencies": {
16011 - "@intlify/core-base": "9.5.0",
16012 - "@intlify/shared": "9.5.0",
15758 + "@intlify/core-base": "9.6.2",
15759 + "@intlify/shared": "9.6.2",
15760 "@vue/devtools-api": "^6.5.0"
15761 },
15762 "engines": {
@@ -16057,10 +15804,18 @@
15804 "vue": "^3.2.0"
15805 }
15806 },
15807 + "node_modules/vue-sjv": {
15808 + "version": "0.0.6",
15809 + "resolved": "https://registry.npmjs.org/vue-sjv/-/vue-sjv-0.0.6.tgz",
15810 + "integrity": "sha512-mk0D/OjzTS/sAqMuEWllVbopzKIoluNdgXIe3OIfqSyl53xX71DmZbe/EAUuaxgilQ9OZiQa3Hq5LYr2+CMW/w==",
15811 + "peerDependencies": {
15812 + "vue": "^3.3.4"
15813 + }
15814 + },
15815 "node_modules/vue-template-compiler": {
16061 - "version": "2.7.14",
16062 - "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.7.14.tgz",
16063 - "integrity": "sha512-zyA5Y3ArvVG0NacJDkkzJuPQDF8RFeRlzV2vLeSnhSpieO6LK2OVbdLPi5MPPs09Ii+gMO8nY4S3iKQxBxDmWQ==",
15816 + "version": "2.7.15",
15817 + "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.7.15.tgz",
15818 + "integrity": "sha512-yQxjxMptBL7UAog00O8sANud99C6wJF+7kgbcwqkvA38vCGF7HWE66w0ZFnS/kX5gSoJr/PQ4/oS3Ne2pW37Og==",
15819 "dev": true,
15820 "dependencies": {
15821 "de-indent": "^1.0.2",
@@ -16068,14 +15823,14 @@
15823 }
15824 },
15825 "node_modules/vue-tsc": {
16071 - "version": "1.8.15",
16072 - "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-1.8.15.tgz",
16073 - "integrity": "sha512-4DoB3LUj7IToLmggoCxRiFG+QU5lem0nv03m1ocqugXA9rSVoTOEoYYaP8vu8b99Eh+/cCVdYOeIAQ+RsgUYUw==",
15826 + "version": "1.8.22",
15827 + "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-1.8.22.tgz",
15828 + "integrity": "sha512-j9P4kHtW6eEE08aS5McFZE/ivmipXy0JzrnTgbomfABMaVKx37kNBw//irL3+LlE3kOo63XpnRigyPC3w7+z+A==",
15829 "dev": true,
15830 "dependencies": {
16076 - "@vue/language-core": "1.8.15",
16077 - "@vue/typescript": "1.8.15",
16078 - "semver": "^7.3.8"
15831 + "@volar/typescript": "~1.10.5",
15832 + "@vue/language-core": "1.8.22",
15833 + "semver": "^7.5.4"
15834 },
15835 "bin": {
15836 "vue-tsc": "bin/vue-tsc.js"
@@ -16637,20 +16392,6 @@
16392 "@jridgewell/trace-mapping": "^0.3.9"
16393 }
16394 },
16640 - "@animxyz/core": {
16641 - "version": "0.6.6",
16642 - "resolved": "https://registry.npmjs.org/@animxyz/core/-/core-0.6.6.tgz",
16643 - "integrity": "sha512-NtAA/G0Gq3hzAiL6yuE/4U8IgHMPUl3MxbWUbhO443T9UCsf9rBY94P5aK79Zd+/529FeoNdDphIOcOZLsI2sA=="
16644 - },
16645 - "@animxyz/vue3": {
16646 - "version": "0.6.7",
16647 - "resolved": "https://registry.npmjs.org/@animxyz/vue3/-/vue3-0.6.7.tgz",
16648 - "integrity": "sha512-tLx4HfFcoxR5wgIMFDrmjS2mZifFRNMiwn7skPT3as9ViuCW3QdCsBIDYY2tR37GSvSUC1NhNwkkCZ1pt0C+Hg==",
16649 - "requires": {
16650 - "@animxyz/core": "^0.6.6",
16651 - "clsx": "^1.1.1"
16652 - }
16653 - },
16395 "@antfu/ni": {
16396 "version": "0.21.8",
16397 "resolved": "https://registry.npmjs.org/@antfu/ni/-/ni-0.21.8.tgz",
@@ -16903,9 +16644,9 @@
16644 }
16645 },
16646 "@babel/parser": {
16906 - "version": "7.22.16",
16907 - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.16.tgz",
16908 - "integrity": "sha512-+gPfKv8UWeKKeJTUxe59+OobVcrYHETCsORl61EmSkmgymguYk/X5bp7GuUIXaFsc6y++v8ZxPsLSSuujqDphA=="
16647 + "version": "7.23.0",
16648 + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz",
16649 + "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw=="
16650 },
16651 "@babel/plugin-syntax-jsx": {
16652 "version": "7.22.5",
@@ -17009,8 +16750,7 @@
16750 "dependencies": {
16751 "is-unicode-supported": {
16752 "version": "1.3.0",
17012 - "bundled": true,
17013 - "dev": true
16753 + "bundled": true
16754 }
16755 }
16756 },
@@ -17310,15 +17050,15 @@
17050 }
17051 },
17052 "@eslint/js": {
17313 - "version": "8.50.0",
17314 - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.50.0.tgz",
17315 - "integrity": "sha512-NCC3zz2+nvYd+Ckfh87rA47zfu2QsQpvc6k1yzTk+b9KzRj0wkGa8LSoGOXN6Zv4lRf/EIoZ80biDh9HOI+RNQ==",
17053 + "version": "8.52.0",
17054 + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.52.0.tgz",
17055 + "integrity": "sha512-mjZVbpaeMZludF2fsWLD0Z9gCref1Tk4i9+wddjRvpUNqqcndPkBD09N/Mapey0b3jaXbLm2kICwFv2E64QinA==",
17056 "dev": true
17057 },
17058 "@faker-js/faker": {
17319 - "version": "8.1.0",
17320 - "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-8.1.0.tgz",
17321 - "integrity": "sha512-38DT60rumHfBYynif3lmtxMqMqmsOQIxQgEuPZxCk2yUYN0eqWpTACgxi0VpidvsJB8CRxCpvP7B3anK85FjtQ==",
17059 + "version": "8.2.0",
17060 + "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-8.2.0.tgz",
17061 + "integrity": "sha512-VacmzZqVxdWdf9y64lDOMZNDMM/FQdtM9IsaOPKOm2suYwEatb8VkdHqOzXcDnZbk7YDE2BmsJmy/2Hmkn563g==",
17062 "dev": true
17063 },
17064 "@fawmi/vue-google-maps": {
@@ -17352,19 +17092,19 @@
17092 "integrity": "sha512-uvnFKtPgzLnpzzTRfhDlvXX0kLYi9lDRQbcDmT8iXl71Rx+uwSuaUIQl3DNC7w5OweAQ7XQMDObML+KaYDQfng=="
17093 },
17094 "@fontsource/jetbrains-mono": {
17355 - "version": "5.0.14",
17356 - "resolved": "https://registry.npmjs.org/@fontsource/jetbrains-mono/-/jetbrains-mono-5.0.14.tgz",
17357 - "integrity": "sha512-hqo/zJSnzQZbN871j3LXOEfLGSqk6P7lEBnbOwUbLo8Xoyszt0Doo48+1ik1fpOU7NskPGiErnRVyKWCZG65QA=="
17095 + "version": "5.0.17",
17096 + "resolved": "https://registry.npmjs.org/@fontsource/jetbrains-mono/-/jetbrains-mono-5.0.17.tgz",
17097 + "integrity": "sha512-Y/EtdbwKwNQTGpnMrexX8SVW6Jqlh0nX2bNHI9Z9m6FsyjbocZIFNJqwSY9bDUoi7irGtz8nuidAN7FF8wYuJA=="
17098 },
17099 "@fontsource/lexend": {
17360 - "version": "5.0.14",
17361 - "resolved": "https://registry.npmjs.org/@fontsource/lexend/-/lexend-5.0.14.tgz",
17362 - "integrity": "sha512-bjvKAWaV6STkMNa5ITQP+F1BSO5vNCfbhfJ0XWHSFFC50+JfElZywarmiaUDtsZSRP8qxadxmIr/j1ByDt/aqQ=="
17100 + "version": "5.0.17",
17101 + "resolved": "https://registry.npmjs.org/@fontsource/lexend/-/lexend-5.0.17.tgz",
17102 + "integrity": "sha512-3rtbeiOx4EqGxcOMfsgq23RRDbhdMGJULLdNCHCN6oAGN06WDesrH6ZL+r6ZF8fpdJZ63F0ViOj/PFG2kOtKdA=="
17103 },
17104 "@fontsource/public-sans": {
17365 - "version": "5.0.12",
17366 - "resolved": "https://registry.npmjs.org/@fontsource/public-sans/-/public-sans-5.0.12.tgz",
17367 - "integrity": "sha512-V1Tir6MhBPYtMNVBmCIkJy9x81a/LMdwnTaS+sDq5mSBIANt1hnIKm/YOzM+qfdxdCnzEemRBXExoG3C8t57Dg=="
17105 + "version": "5.0.15",
17106 + "resolved": "https://registry.npmjs.org/@fontsource/public-sans/-/public-sans-5.0.15.tgz",
17107 + "integrity": "sha512-3UKtCVDbwt8FeurOHYBybDzYYJH0peyisGjsQe2aRFR4M693m0DdE3v4BZl+60OjvnXGWhO8O/rmET2kwPF6SQ=="
17108 },
17109 "@fullcalendar/core": {
17110 "version": "6.1.9",
@@ -17431,12 +17171,12 @@
17171 }
17172 },
17173 "@humanwhocodes/config-array": {
17434 - "version": "0.11.11",
17435 - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz",
17436 - "integrity": "sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==",
17174 + "version": "0.11.13",
17175 + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz",
17176 + "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==",
17177 "dev": true,
17178 "requires": {
17439 - "@humanwhocodes/object-schema": "^1.2.1",
17179 + "@humanwhocodes/object-schema": "^2.0.1",
17180 "debug": "^4.1.1",
17181 "minimatch": "^3.0.5"
17182 }
@@ -17448,43 +17188,48 @@
17188 "dev": true
17189 },
17190 "@humanwhocodes/object-schema": {
17451 - "version": "1.2.1",
17452 - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz",
17453 - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
17191 + "version": "2.0.1",
17192 + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz",
17193 + "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==",
17194 + "dev": true
17195 + },
17196 + "@iconify/types": {
17197 + "version": "2.0.0",
17198 + "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz",
17199 + "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==",
17200 "dev": true
17201 },
17202 + "@iconify/vue": {
17203 + "version": "4.1.1",
17204 + "resolved": "https://registry.npmjs.org/@iconify/vue/-/vue-4.1.1.tgz",
17205 + "integrity": "sha512-RL85Bm/DAe8y6rT6pux7D2FJSiUEM/TPfyK7GrbAOfTSwrhvwJW+S5yijdGcmtXouA8MtuH9C7l4hiSE4mLMjg==",
17206 + "dev": true,
17207 + "requires": {
17208 + "@iconify/types": "^2.0.0"
17209 + }
17210 + },
17211 "@intlify/core-base": {
17457 - "version": "9.5.0",
17458 - "resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.5.0.tgz",
17459 - "integrity": "sha512-y3ufM1RJbI/DSmJf3lYs9ACq3S/iRvaSsE3rPIk0MGH7fp+JxU6rdryv/EYcwfcr3Y1aHFlCBir6S391hRZ57w==",
17212 + "version": "9.6.2",
17213 + "resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.6.2.tgz",
17214 + "integrity": "sha512-ci0j2nbEL/pamvqgcCqyIVeQ3LS41F1IRqI5rCBNnpSp0FjNnH8bpha8R3OifkhqatzlP4wGOuN/UqfLYVDv7g==",
17215 "requires": {
17461 - "@intlify/message-compiler": "9.5.0",
17462 - "@intlify/shared": "9.5.0"
17216 + "@intlify/message-compiler": "9.6.2",
17217 + "@intlify/shared": "9.6.2"
17218 }
17219 },
17220 "@intlify/message-compiler": {
17466 - "version": "9.5.0",
17467 - "resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.5.0.tgz",
17468 - "integrity": "sha512-CAhVNfEZcOVFg0/5MNyt+OFjvs4J/ARjCj2b+54/FvFP0EDJI5lIqMTSDBE7k0atMROSP0SvWCkwu/AZ5xkK1g==",
17221 + "version": "9.6.2",
17222 + "resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.6.2.tgz",
17223 + "integrity": "sha512-kgZQL9zeJDeEB5vvD93Y++HvFUELnT48PjnpfCcF3EJaLLVs9he8IzODiNK42Z40lWbFyja0SXJZjsalybQygA==",
17224 "requires": {
17470 - "@intlify/shared": "9.5.0",
17225 + "@intlify/shared": "9.6.2",
17226 "source-map-js": "^1.0.2"
17227 }
17228 },
17229 "@intlify/shared": {
17475 - "version": "9.5.0",
17476 - "resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.5.0.tgz",
17477 - "integrity": "sha512-tAxV14LMXZDZbu32XzLMTsowNlgJNmLwWHYzvMUl6L8gvQeoYiZONjY7AUsqZW8TOZDX9lfvF6adPkk9FSRdDA=="
17478 - },
17479 - "@intlify/vue-devtools": {
17480 - "version": "9.5.0",
17481 - "resolved": "https://registry.npmjs.org/@intlify/vue-devtools/-/vue-devtools-9.5.0.tgz",
17482 - "integrity": "sha512-OZ5HkCvhSEhU+wDY7G4TD0mbZw/ZwRdH7O7xVWYVa4ryrwUIoii6h5zS4tZj9rQY7xF2Hn+/0qxUsyOZPLYjHQ==",
17483 - "dev": true,
17484 - "requires": {
17485 - "@intlify/core-base": "9.5.0",
17486 - "@intlify/shared": "9.5.0"
17487 - }
17230 + "version": "9.6.2",
17231 + "resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.6.2.tgz",
17232 + "integrity": "sha512-9KBcXmJNxElp7QMnU8V0/tScTOitDqyFi4HceEZqJyyDkMi8K5DBPMTIuXIAMmtMlXpe/nj5pke7tRw97VeQRA=="
17233 },
17234 "@isaacs/cliui": {
17235 "version": "8.0.2",
@@ -17671,9 +17416,9 @@
17416 "integrity": "sha512-Es6WcD0nO5l+2BOQS4uLfNPYQaNDfbot3X1XUoloz+x0mPDS3eeORZJl06HXjwBG1fOGwCRnzK88LMdxKRrd6Q=="
17417 },
17418 "@maplibre/maplibre-gl-style-spec": {
17674 - "version": "19.3.1",
17675 - "resolved": "https://registry.npmjs.org/@maplibre/maplibre-gl-style-spec/-/maplibre-gl-style-spec-19.3.1.tgz",
17676 - "integrity": "sha512-ss5+b3/a8I1wD5PYmAYPYxg0Nag0cxvw4GGOnQroTP59sobTPI3KeHP9OjUr/es7uNtYEodr54fgoEnCBF6gaQ==",
17419 + "version": "19.3.3",
17420 + "resolved": "https://registry.npmjs.org/@maplibre/maplibre-gl-style-spec/-/maplibre-gl-style-spec-19.3.3.tgz",
17421 + "integrity": "sha512-cOZZOVhDSulgK0meTsTkmNXb1ahVvmTmWmfx9gRBwc6hq98wS9JP35ESIoNq3xqEan+UN+gn8187Z6E4NKhLsw==",
17422 "requires": {
17423 "@mapbox/jsonlint-lines-primitives": "~2.0.2",
17424 "@mapbox/unitbezier": "^0.0.1",
@@ -17684,53 +17429,53 @@
17429 }
17430 },
17431 "@milkdown/core": {
17687 - "version": "7.3.0",
17688 - "resolved": "https://registry.npmjs.org/@milkdown/core/-/core-7.3.0.tgz",
17689 - "integrity": "sha512-spB2mti5glP/ZJROgNZuaVgDC0djhMEnd9eu5iEmm4qqUj4RabKDZkVox9kZE0ngn6msWtDW/qQwnIFd4iFGdQ==",
17432 + "version": "7.3.1",
17433 + "resolved": "https://registry.npmjs.org/@milkdown/core/-/core-7.3.1.tgz",
17434 + "integrity": "sha512-W4wxS87YkXbRMTs9DEWNQWQfbDpfDvJ6EJJA5t/4eMXYymVHEr9h2QsHI23Tiznthgro1X0+kW3nxtfWWHw2xA==",
17435 "requires": {
17691 - "@milkdown/exception": "7.3.0",
17692 - "remark-parse": "^10.0.1",
17693 - "remark-stringify": "^10.0.2",
17436 + "@milkdown/exception": "7.3.1",
17437 + "remark-parse": "^11.0.0",
17438 + "remark-stringify": "^11.0.0",
17439 "tslib": "^2.5.0",
17695 - "unified": "^10.1.0"
17440 + "unified": "^11.0.3"
17441 }
17442 },
17443 "@milkdown/ctx": {
17699 - "version": "7.3.0",
17700 - "resolved": "https://registry.npmjs.org/@milkdown/ctx/-/ctx-7.3.0.tgz",
17701 - "integrity": "sha512-VQG/Q0TrEcZ5HbTQrqiXgeOwZYv7qmnH8X5msS9GL8bvALGUHJ3QjxQONJrK53ZZ5HbHPSi7QOBDlAn57m69fg==",
17444 + "version": "7.3.1",
17445 + "resolved": "https://registry.npmjs.org/@milkdown/ctx/-/ctx-7.3.1.tgz",
17446 + "integrity": "sha512-Wmf7Bhz8AH2bz4sz38a4DY3jFsDlHaaQ4ZGFi9JzSr1NjFB+2dvc19RYyncF/P14qTwdJc9htzP/HD4mUKlSww==",
17447 "requires": {
17703 - "@milkdown/exception": "7.3.0",
17448 + "@milkdown/exception": "7.3.1",
17449 "tslib": "^2.5.0"
17450 }
17451 },
17452 "@milkdown/exception": {
17708 - "version": "7.3.0",
17709 - "resolved": "https://registry.npmjs.org/@milkdown/exception/-/exception-7.3.0.tgz",
17710 - "integrity": "sha512-ZqN+3dOfTQ8OafkJz6WqNrNWxg3D6hACMa2XyPul+uVU0MLZXURfijCO26eByNlhG05w0Uo/FJfaapPZO3aE6g==",
17453 + "version": "7.3.1",
17454 + "resolved": "https://registry.npmjs.org/@milkdown/exception/-/exception-7.3.1.tgz",
17455 + "integrity": "sha512-e2x1h/zE8paoF9ygEbUVZZTNpp1acw6azqExSvL+3myoMgwTsmE/G6oK9YpajY1BfSsLM2J3ii9ByIcsRM6+9w==",
17456 "requires": {
17457 "tslib": "^2.5.0"
17458 }
17459 },
17460 "@milkdown/preset-commonmark": {
17716 - "version": "7.3.0",
17717 - "resolved": "https://registry.npmjs.org/@milkdown/preset-commonmark/-/preset-commonmark-7.3.0.tgz",
17718 - "integrity": "sha512-9ehBLkiLomp2av6E7YiRwihx/YsZ+LR80Y21e+Qzs5d6eq+payhblKQSaYYlVECA7gwLIOy67Lu8jxkOnO++rw==",
17461 + "version": "7.3.1",
17462 + "resolved": "https://registry.npmjs.org/@milkdown/preset-commonmark/-/preset-commonmark-7.3.1.tgz",
17463 + "integrity": "sha512-8qiz/jQ4st6Sd3r7wuTSc9XCqsRqcOJFyI3N7Jt6I+Opi/JPE6AXiihsbbfPcN9y4w5w1MuuWH50fq6om5cXXA==",
17464 "requires": {
17720 - "@milkdown/exception": "7.3.0",
17721 - "@milkdown/utils": "7.3.0",
17465 + "@milkdown/exception": "7.3.1",
17466 + "@milkdown/utils": "7.3.1",
17467 "@sindresorhus/slugify": "^2.2.0",
17723 - "remark-inline-links": "^6.0.0",
17468 + "remark-inline-links": "^7.0.0",
17469 "tslib": "^2.5.0",
17470 "unist-util-visit": "^5.0.0"
17471 }
17472 },
17473 "@milkdown/prose": {
17729 - "version": "7.3.0",
17730 - "resolved": "https://registry.npmjs.org/@milkdown/prose/-/prose-7.3.0.tgz",
17731 - "integrity": "sha512-YUx30G++v9RYw6mM5ybvvezaJBdSyhrDsVeuAzYkBRfV2fwEV1airBt96BqY+FeICYqE1eTCUty/gaPzpcwbPg==",
17474 + "version": "7.3.1",
17475 + "resolved": "https://registry.npmjs.org/@milkdown/prose/-/prose-7.3.1.tgz",
17476 + "integrity": "sha512-l9xfPOYDwOnTrTCLYr328pUjg9gwNP5n1CKBVFlpkkGKdDVAfdRsH0QgzNHuw42amwDG+e+uu/s29HGqm9JZog==",
17477 "requires": {
17733 - "@milkdown/exception": "7.3.0",
17478 + "@milkdown/exception": "7.3.1",
17479 "prosemirror-changeset": "^2.2.1",
17480 "prosemirror-commands": "^1.5.2",
17481 "prosemirror-dropcursor": "^1.8.1",
@@ -17738,19 +17483,19 @@
17483 "prosemirror-history": "^1.3.2",
17484 "prosemirror-inputrules": "^1.2.1",
17485 "prosemirror-keymap": "^1.2.2",
17741 - "prosemirror-model": "^1.19.2",
17486 + "prosemirror-model": "^1.19.3",
17487 "prosemirror-schema-list": "^1.3.0",
17488 "prosemirror-state": "^1.4.3",
17489 "prosemirror-tables": "^1.3.4",
17745 - "prosemirror-transform": "^1.7.3",
17746 - "prosemirror-view": "^1.31.5",
17490 + "prosemirror-transform": "^1.7.5",
17491 + "prosemirror-view": "^1.31.7",
17492 "tslib": "^2.5.0"
17493 }
17494 },
17495 "@milkdown/theme-nord": {
17751 - "version": "7.3.0",
17752 - "resolved": "https://registry.npmjs.org/@milkdown/theme-nord/-/theme-nord-7.3.0.tgz",
17753 - "integrity": "sha512-sXFY8wxn/38BbQJyrJQvj8/h0kKvrVr3q8U/lSzNoCywH1hQ/qapSFDs3HvXLSWOZm/V0CaIP7H/MwHKF8N6MA==",
17496 + "version": "7.3.1",
17497 + "resolved": "https://registry.npmjs.org/@milkdown/theme-nord/-/theme-nord-7.3.1.tgz",
17498 + "integrity": "sha512-vnloL2XXG9hDdHBkFES3GNi1UzLY7iahOVbuz/0N/m0LGRTkcvy0OUOKzE3csJ6j7ENQhAmv8PhFv60EdlVz0A==",
17499 "requires": {
17500 "clsx": "^2.0.0",
17501 "tslib": "^2.5.0"
@@ -17764,34 +17509,34 @@
17509 }
17510 },
17511 "@milkdown/transformer": {
17767 - "version": "7.3.0",
17768 - "resolved": "https://registry.npmjs.org/@milkdown/transformer/-/transformer-7.3.0.tgz",
17769 - "integrity": "sha512-6QGutmJvg1sk3aVQGG4kX+MmemLnd0v1x7uoGeyOuQT4MIPAquxPmzECHwRJJal+eyhFLnssOZLzC6qF85b+bQ==",
17770 - "requires": {
17771 - "@milkdown/exception": "7.3.0",
17772 - "remark": "^14.0.1",
17773 - "remark-parse": "^10.0.2",
17774 - "remark-stringify": "^10.0.3",
17512 + "version": "7.3.1",
17513 + "resolved": "https://registry.npmjs.org/@milkdown/transformer/-/transformer-7.3.1.tgz",
17514 + "integrity": "sha512-vFGnoOxqVnMNnu+JGs+bxMgIEC1RYhaWWKj1LtpwQQlHTX3PUCN823gF6MjH5R4L98HIg2Ysc7GbE1O7JoL3pw==",
17515 + "requires": {
17516 + "@milkdown/exception": "7.3.1",
17517 + "remark": "^15.0.1",
17518 + "remark-parse": "^11.0.0",
17519 + "remark-stringify": "^11.0.0",
17520 "tslib": "^2.5.0",
17776 - "unified": "^10.1.0"
17521 + "unified": "^11.0.3"
17522 }
17523 },
17524 "@milkdown/utils": {
17780 - "version": "7.3.0",
17781 - "resolved": "https://registry.npmjs.org/@milkdown/utils/-/utils-7.3.0.tgz",
17782 - "integrity": "sha512-hOb6UwRIX2e7lpAn3sjAtsF2idzQN/wQ9MLbjoIevQXjO1EgNYiBTFIfTDnI4ka+VrVScvZtZ2D8cAA+3+ndxw==",
17525 + "version": "7.3.1",
17526 + "resolved": "https://registry.npmjs.org/@milkdown/utils/-/utils-7.3.1.tgz",
17527 + "integrity": "sha512-GT54XYHwnHjHVmH3vBcCFwlzZhdaM3S+1Zp1+ci5bcfTX0Rmt1KUyzkeBJOcSDS4jCm8eTYd2kBCzodZOHqm9A==",
17528 "requires": {
17784 - "@milkdown/exception": "7.3.0",
17785 - "nanoid": "^4.0.0",
17529 + "@milkdown/exception": "7.3.1",
17530 + "nanoid": "^5.0.0",
17531 "tslib": "^2.5.0"
17532 }
17533 },
17534 "@milkdown/vue": {
17790 - "version": "7.3.0",
17791 - "resolved": "https://registry.npmjs.org/@milkdown/vue/-/vue-7.3.0.tgz",
17792 - "integrity": "sha512-eNHlYPcisSZ4p8JjGemPPox5h4px/h6Ln9yf6Oo25wBMRyTtyuBZ7mWk2OfSxk6A9Ay5Qm6rOnR6ok7OI1x3bw==",
17535 + "version": "7.3.1",
17536 + "resolved": "https://registry.npmjs.org/@milkdown/vue/-/vue-7.3.1.tgz",
17537 + "integrity": "sha512-JkzmIlRlran60VYELOqCjEOqpenQEIgv/m5sKlfjP0c2AVBBl4XnBDdsS9BswjF/l50JlqA3MoqPzzg/eTdmEA==",
17538 "requires": {
17794 - "@milkdown/utils": "7.3.0",
17539 + "@milkdown/utils": "7.3.1",
17540 "tslib": "^2.5.0"
17541 }
17542 },
@@ -17883,9 +17628,9 @@
17628 }
17629 },
17630 "@npmcli/config": {
17886 - "version": "6.3.0",
17887 - "resolved": "https://registry.npmjs.org/@npmcli/config/-/config-6.3.0.tgz",
17888 - "integrity": "sha512-gV64pm5cQ7F2oeoSJ5HTfaKxjFsvC4dAbCsQbtbOkEOymM6iZI62yNGCOLjcq/rfYX9+wVn34ThxK7GZpUwWFg==",
17631 + "version": "8.0.1",
17632 + "resolved": "https://registry.npmjs.org/@npmcli/config/-/config-8.0.1.tgz",
17633 + "integrity": "sha512-NKGmMYv/YTLwJr+qK9CvADSe82NTM9AFwFFpsZpVcPCT3XTdxvJBdXi8xvXWjHSCMb0Cb+7FtU/a5qqguCOhxA==",
17634 "dev": true,
17635 "requires": {
17636 "@npmcli/map-workspaces": "^3.0.2",
@@ -18241,9 +17986,9 @@
17986 }
17987 },
17988 "@revolist/revogrid": {
18244 - "version": "4.7.0-next.3",
18245 - "resolved": "https://registry.npmjs.org/@revolist/revogrid/-/revogrid-4.7.0-next.3.tgz",
18246 - "integrity": "sha512-MOwnLjnbiM42XrvtXm6DFhshvojTj6Zs3jNQaFqov4uzJS4RUkvqulUxkFd1AvDpll7PKgCIn+e7EkB8zXpMOQ==",
17989 + "version": "4.7.0-next.4",
17990 + "resolved": "https://registry.npmjs.org/@revolist/revogrid/-/revogrid-4.7.0-next.4.tgz",
17991 + "integrity": "sha512-4ypmIDmIEWgIGFghvwgMmCeDLS1OOVX3PO0LmPTjV4DhobTlWePGtYjBerdnQocunccbf/dh5Iy4460u3cAnCA==",
17992 "requires": {
17993 "@stencil/core": "^4.3.0",
17994 "lodash": "^4.17.21"
@@ -18406,177 +18151,183 @@
18151 "integrity": "sha512-YlLyCqGBsMEuZb3XTO/STT0TX9eSwjoVhCJgtjVfQOF+ebIMVlojTh40CmDveWiWbth687cbr6S2heeussV8Sg=="
18152 },
18153 "@tiptap/core": {
18409 - "version": "2.1.11",
18410 - "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-2.1.11.tgz",
18411 - "integrity": "sha512-1W2DdjpPwfphHgQ3Qm4s5wzCnEjiXm1TeZ+6/zBl89yKURXgv8Mw1JGdj/NcImQjtDcsNn97MscACK3GKbEJBA==",
18154 + "version": "2.1.12",
18155 + "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-2.1.12.tgz",
18156 + "integrity": "sha512-ZGc3xrBJA9KY8kln5AYTj8y+GDrKxi7u95xIl2eccrqTY5CQeRu6HRNM1yT4mAjuSaG9jmazyjGRlQuhyxCKxQ==",
18157 "requires": {}
18158 },
18159 "@tiptap/extension-blockquote": {
18415 - "version": "2.1.11",
18416 - "resolved": "https://registry.npmjs.org/@tiptap/extension-blockquote/-/extension-blockquote-2.1.11.tgz",
18417 - "integrity": "sha512-IEVe3goA0rgp1G8Wm733BSRJiy71Vh2fmTCyZKWmc2A6GREVSy1X3fCvAo6pMENRObhjIoaBQUCE3p4iJYOxqg==",
18160 + "version": "2.1.12",
18161 + "resolved": "https://registry.npmjs.org/@tiptap/extension-blockquote/-/extension-blockquote-2.1.12.tgz",
18162 + "integrity": "sha512-Qb3YRlCfugx9pw7VgLTb+jY37OY4aBJeZnqHzx4QThSm13edNYjasokbX0nTwL1Up4NPTcY19JUeHt6fVaVVGg==",
18163 "requires": {}
18164 },
18165 "@tiptap/extension-bold": {
18421 - "version": "2.1.11",
18422 - "resolved": "https://registry.npmjs.org/@tiptap/extension-bold/-/extension-bold-2.1.11.tgz",
18423 - "integrity": "sha512-vhdkBtvd029ufOYt2ug49Gz+RLKSczO/CCqKYBqBmpIpsifyK7M6jkgamvAQg3c/vYk0LNcKiL2dp0Jp7L+5Gw==",
18166 + "version": "2.1.12",
18167 + "resolved": "https://registry.npmjs.org/@tiptap/extension-bold/-/extension-bold-2.1.12.tgz",
18168 + "integrity": "sha512-AZGxIxcGU1/y6V2YEbKsq6BAibL8yQrbRm6EdcBnby41vj1WziewEKswhLGmZx5IKM2r2ldxld03KlfSIlKQZg==",
18169 "requires": {}
18170 },
18171 "@tiptap/extension-bubble-menu": {
18427 - "version": "2.1.11",
18428 - "resolved": "https://registry.npmjs.org/@tiptap/extension-bubble-menu/-/extension-bubble-menu-2.1.11.tgz",
18429 - "integrity": "sha512-WFJJpZvl9DP94Y5RQZB/THDxvDbrTo8tuhjT7yWlhseJ6zyhWmRXdutt39wfSZNFxitv/As+s7cO9aYLML/TVg==",
18172 + "version": "2.1.12",
18173 + "resolved": "https://registry.npmjs.org/@tiptap/extension-bubble-menu/-/extension-bubble-menu-2.1.12.tgz",
18174 + "integrity": "sha512-gAGi21EQ4wvLmT7klgariAc2Hf+cIjaNU2NWze3ut6Ku9gUo5ZLqj1t9SKHmNf4d5JG63O8GxpErqpA7lHlRtw==",
18175 "requires": {
18176 "tippy.js": "^6.3.7"
18177 }
18178 },
18179 "@tiptap/extension-bullet-list": {
18435 - "version": "2.1.11",
18436 - "resolved": "https://registry.npmjs.org/@tiptap/extension-bullet-list/-/extension-bullet-list-2.1.11.tgz",
18437 - "integrity": "sha512-SOOVH2aSmdMtjWL7TTLbN72xbAFz2G5jifT4UCXb7Qx6LsyhNCyDCu0ukOW8rSosGoSdmBXxAsD9sBJ1jEOmZw==",
18180 + "version": "2.1.12",
18181 + "resolved": "https://registry.npmjs.org/@tiptap/extension-bullet-list/-/extension-bullet-list-2.1.12.tgz",
18182 + "integrity": "sha512-vtD8vWtNlmAZX8LYqt2yU9w3mU9rPCiHmbp4hDXJs2kBnI0Ju/qAyXFx6iJ3C3XyuMnMbJdDI9ee0spAvFz7cQ==",
18183 "requires": {}
18184 },
18185 "@tiptap/extension-character-count": {
18441 - "version": "2.1.11",
18442 - "resolved": "https://registry.npmjs.org/@tiptap/extension-character-count/-/extension-character-count-2.1.11.tgz",
18443 - "integrity": "sha512-qR50YtvY+hgskUQSlHl/Bitx6xPJVQ2wuNWdw48s8LOjrE2cqAmAj7BmeBrh9481EbhgXZrt3m6UygFiMfiCiA==",
18186 + "version": "2.1.12",
18187 + "resolved": "https://registry.npmjs.org/@tiptap/extension-character-count/-/extension-character-count-2.1.12.tgz",
18188 + "integrity": "sha512-+GFbBG13nvF8mFIeisSERG/Q3CuRsTNwVZIRbJTLgGdbHXFqPhJh4Xfm7cv7OaOYevUlVyO+z5pGD7wIl1bLqQ==",
18189 "requires": {}
18190 },
18191 "@tiptap/extension-code": {
18447 - "version": "2.1.11",
18448 - "resolved": "https://registry.npmjs.org/@tiptap/extension-code/-/extension-code-2.1.11.tgz",
18449 - "integrity": "sha512-G0UEbMFunujy/F86yHN0/dumPLbwTis9C+6IQv1XRPNsV28U0MgxBhlPcJUgyO5lwuleePDxiBVcRv2XrysgKw==",
18192 + "version": "2.1.12",
18193 + "resolved": "https://registry.npmjs.org/@tiptap/extension-code/-/extension-code-2.1.12.tgz",
18194 + "integrity": "sha512-CRiRq5OTC1lFgSx6IMrECqmtb93a0ZZKujEnaRhzWliPBjLIi66va05f/P1vnV6/tHaC3yfXys6dxB5A4J8jxw==",
18195 "requires": {}
18196 },
18197 "@tiptap/extension-code-block": {
18453 - "version": "2.1.11",
18454 - "resolved": "https://registry.npmjs.org/@tiptap/extension-code-block/-/extension-code-block-2.1.11.tgz",
18455 - "integrity": "sha512-QhmhCCWqg/5qLXpZ3sl2A0rqJqV8zMOegcxUFaqcJMOqNbsuHcRgc9C+1hWSVLbCmstB7M6sgF02QpTBOkYHxg==",
18198 + "version": "2.1.12",
18199 + "resolved": "https://registry.npmjs.org/@tiptap/extension-code-block/-/extension-code-block-2.1.12.tgz",
18200 + "integrity": "sha512-RXtSYCVsnk8D+K80uNZShClfZjvv1EgO42JlXLVGWQdIgaNyuOv/6I/Jdf+ZzhnpsBnHufW+6TJjwP5vJPSPHA==",
18201 "requires": {}
18202 },
18203 "@tiptap/extension-document": {
18459 - "version": "2.1.11",
18460 - "resolved": "https://registry.npmjs.org/@tiptap/extension-document/-/extension-document-2.1.11.tgz",
18461 - "integrity": "sha512-L/iLuqxvJep33ycCFNrnUhdR0VtcZyeNnqB+ZvVHzEwLoRud+LBy44lpEdBrAFsvRm3DG14m/FGYL+TfaD0vxA==",
18204 + "version": "2.1.12",
18205 + "resolved": "https://registry.npmjs.org/@tiptap/extension-document/-/extension-document-2.1.12.tgz",
18206 + "integrity": "sha512-0QNfAkCcFlB9O8cUNSwTSIQMV9TmoEhfEaLz/GvbjwEq4skXK3bU+OQX7Ih07waCDVXIGAZ7YAZogbvrn/WbOw==",
18207 "requires": {}
18208 },
18209 "@tiptap/extension-dropcursor": {
18465 - "version": "2.1.11",
18466 - "resolved": "https://registry.npmjs.org/@tiptap/extension-dropcursor/-/extension-dropcursor-2.1.11.tgz",
18467 - "integrity": "sha512-MiJepRpHlu93aInOMW8NeRCvm9VE5rL0MA9TONY/IspJFGFIqonc/01J6t33JQa3Xh/x3xAfis4nKa/UazeVJw==",
18210 + "version": "2.1.12",
18211 + "resolved": "https://registry.npmjs.org/@tiptap/extension-dropcursor/-/extension-dropcursor-2.1.12.tgz",
18212 + "integrity": "sha512-0tT/q8nL4NBCYPxr9T0Brck+RQbWuczm9nV0bnxgt0IiQXoRHutfPWdS7GA65PTuVRBS/3LOco30fbjFhkfz/A==",
18213 "requires": {}
18214 },
18215 "@tiptap/extension-floating-menu": {
18471 - "version": "2.1.11",
18472 - "resolved": "https://registry.npmjs.org/@tiptap/extension-floating-menu/-/extension-floating-menu-2.1.11.tgz",
18473 - "integrity": "sha512-ExeoOQ6nT0CY0eWx6WjbG+osurXLXa7XrqIdhCAcTmzBAlGiKt8khX9qaZ+QF+BRK1r1lja2KX+5/fpLK7Dt1g==",
18216 + "version": "2.1.12",
18217 + "resolved": "https://registry.npmjs.org/@tiptap/extension-floating-menu/-/extension-floating-menu-2.1.12.tgz",
18218 + "integrity": "sha512-uo0ydCJNg6AWwLT6cMUJYVChfvw2PY9ZfvKRhh9YJlGfM02jS4RUG/bJBts6R37f+a5FsOvAVwg8EvqPlNND1A==",
18219 "requires": {
18220 "tippy.js": "^6.3.7"
18221 }
18222 },
18223 "@tiptap/extension-gapcursor": {
18479 - "version": "2.1.11",
18480 - "resolved": "https://registry.npmjs.org/@tiptap/extension-gapcursor/-/extension-gapcursor-2.1.11.tgz",
18481 - "integrity": "sha512-P/xjyhSOVyop5XXbNtRPgrooQrSlpYblwR67ClI9FAC7uQliuOwi5VcndmEItjWWSe85kJa2IHjOS7mLYvJe8A==",
18224 + "version": "2.1.12",
18225 + "resolved": "https://registry.npmjs.org/@tiptap/extension-gapcursor/-/extension-gapcursor-2.1.12.tgz",
18226 + "integrity": "sha512-zFYdZCqPgpwoB7whyuwpc8EYLYjUE5QYKb8vICvc+FraBUDM51ujYhFSgJC3rhs8EjI+8GcK8ShLbSMIn49YOQ==",
18227 "requires": {}
18228 },
18229 "@tiptap/extension-hard-break": {
18485 - "version": "2.1.11",
18486 - "resolved": "https://registry.npmjs.org/@tiptap/extension-hard-break/-/extension-hard-break-2.1.11.tgz",
18487 - "integrity": "sha512-qhiPe6FA0b6PPb/ITlgSnY0l9tEVmXZ9e7eSjvks12ORfqL/dofSCLtChHWvhZxugzo92xejG2hXLi6lyOLbkg==",
18230 + "version": "2.1.12",
18231 + "resolved": "https://registry.npmjs.org/@tiptap/extension-hard-break/-/extension-hard-break-2.1.12.tgz",
18232 + "integrity": "sha512-nqKcAYGEOafg9D+2cy1E4gHNGuL12LerVa0eS2SQOb+PT8vSel9OTKU1RyZldsWSQJ5rq/w4uIjmLnrSR2w6Yw==",
18233 "requires": {}
18234 },
18235 "@tiptap/extension-heading": {
18491 - "version": "2.1.11",
18492 - "resolved": "https://registry.npmjs.org/@tiptap/extension-heading/-/extension-heading-2.1.11.tgz",
18493 - "integrity": "sha512-QBtl0S1aDFB+F1wvTrS5iGdNUEeXp+WuTddj+L2f5EP4KqG2x7sj7e7ENMy20g/l8tbKwzd3AZZydvClH4Ybbw==",
18236 + "version": "2.1.12",
18237 + "resolved": "https://registry.npmjs.org/@tiptap/extension-heading/-/extension-heading-2.1.12.tgz",
18238 + "integrity": "sha512-MoANP3POAP68Ko9YXarfDKLM/kXtscgp6m+xRagPAghRNujVY88nK1qBMZ3JdvTVN6b/ATJhp8UdrZX96TLV2w==",
18239 "requires": {}
18240 },
18241 "@tiptap/extension-highlight": {
18497 - "version": "2.1.11",
18498 - "resolved": "https://registry.npmjs.org/@tiptap/extension-highlight/-/extension-highlight-2.1.11.tgz",
18499 - "integrity": "sha512-pcs55B1lF2vyQ8VvZob9CsYdbFgVpIfG3+qchLsA1WflUJCcIexstTclWTS9N5UocADg4hBOeerZ4ecq1iXs3w==",
18242 + "version": "2.1.12",
18243 + "resolved": "https://registry.npmjs.org/@tiptap/extension-highlight/-/extension-highlight-2.1.12.tgz",
18244 + "integrity": "sha512-buen31cYPyiiHA2i0o2i/UcjRTg/42mNDCizGr1OJwvv3AELG3qOFc4Y58WJWIvWNv+1Dr4ZxHA3GNVn0ANWyg==",
18245 "requires": {}
18246 },
18247 "@tiptap/extension-history": {
18503 - "version": "2.1.11",
18504 - "resolved": "https://registry.npmjs.org/@tiptap/extension-history/-/extension-history-2.1.11.tgz",
18505 - "integrity": "sha512-88dovV2O9icmBn0IvaArFFeS6X5ts6BxZPu5VbGML8KBL8iAu+Og7RXEPdOy5e13K0K4V21fDpO3n7KdvNOAYQ==",
18248 + "version": "2.1.12",
18249 + "resolved": "https://registry.npmjs.org/@tiptap/extension-history/-/extension-history-2.1.12.tgz",
18250 + "integrity": "sha512-6b7UFVkvPjq3LVoCTrYZAczt5sQrQUaoDWAieVClVZoFLfjga2Fwjcfgcie8IjdPt8YO2hG/sar/c07i9vM0Sg==",
18251 "requires": {}
18252 },
18253 "@tiptap/extension-horizontal-rule": {
18509 - "version": "2.1.11",
18510 - "resolved": "https://registry.npmjs.org/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-2.1.11.tgz",
18511 - "integrity": "sha512-uvHPa2YCKnDhtSBSZB3lk5U4H3wRKP0DNvVx4Y2F7MdQianVzcyOd1pZYO9BQs+lUB1aZots6doE69Zqz3mU2Q==",
18254 + "version": "2.1.12",
18255 + "resolved": "https://registry.npmjs.org/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-2.1.12.tgz",
18256 + "integrity": "sha512-RRuoK4KxrXRrZNAjJW5rpaxjiP0FJIaqpi7nFbAua2oHXgsCsG8qbW2Y0WkbIoS8AJsvLZ3fNGsQ8gpdliuq3A==",
18257 "requires": {}
18258 },
18259 "@tiptap/extension-italic": {
18515 - "version": "2.1.11",
18516 - "resolved": "https://registry.npmjs.org/@tiptap/extension-italic/-/extension-italic-2.1.11.tgz",
18517 - "integrity": "sha512-QmDsHtnBBit/1KtQpBPxjSPjDC1mVKtoNTgsEwMWK6YAkCKOKPj7oPEqqjaNZIRMKPPzE5XCsfBoS3jtVmo+6A==",
18260 + "version": "2.1.12",
18261 + "resolved": "https://registry.npmjs.org/@tiptap/extension-italic/-/extension-italic-2.1.12.tgz",
18262 + "integrity": "sha512-/XYrW4ZEWyqDvnXVKbgTXItpJOp2ycswk+fJ3vuexyolO6NSs0UuYC6X4f+FbHYL5VuWqVBv7EavGa+tB6sl3A==",
18263 "requires": {}
18264 },
18265 "@tiptap/extension-link": {
18521 - "version": "2.1.11",
18522 - "resolved": "https://registry.npmjs.org/@tiptap/extension-link/-/extension-link-2.1.11.tgz",
18523 - "integrity": "sha512-Dn8hq4ld8br53fE4/QUZ7/y6ejY/kqAxeNhtud+OZKRs6VRn/CQd0H6A26opL+mKAK0kzrs0rh7rJPpHvahx/Q==",
18266 + "version": "2.1.12",
18267 + "resolved": "https://registry.npmjs.org/@tiptap/extension-link/-/extension-link-2.1.12.tgz",
18268 + "integrity": "sha512-Sti5hhlkCqi5vzdQjU/gbmr8kb578p+u0J4kWS+SSz3BknNThEm/7Id67qdjBTOQbwuN07lHjDaabJL0hSkzGQ==",
18269 "requires": {
18270 "linkifyjs": "^4.1.0"
18271 }
18272 },
18273 "@tiptap/extension-list-item": {
18529 - "version": "2.1.11",
18530 - "resolved": "https://registry.npmjs.org/@tiptap/extension-list-item/-/extension-list-item-2.1.11.tgz",
18531 - "integrity": "sha512-YhwHaPGhffsFsg/zjCu1G24//j/BTRDRZbZXmMwp77m1yEqPULcWyoWrI+gUzetQxJRD/ruAucqjLtoLLfICmQ==",
18274 + "version": "2.1.12",
18275 + "resolved": "https://registry.npmjs.org/@tiptap/extension-list-item/-/extension-list-item-2.1.12.tgz",
18276 + "integrity": "sha512-Gk7hBFofAPmNQ8+uw8w5QSsZOMEGf7KQXJnx5B022YAUJTYYxO3jYVuzp34Drk9p+zNNIcXD4kc7ff5+nFOTrg==",
18277 "requires": {}
18278 },
18279 "@tiptap/extension-ordered-list": {
18535 - "version": "2.1.11",
18536 - "resolved": "https://registry.npmjs.org/@tiptap/extension-ordered-list/-/extension-ordered-list-2.1.11.tgz",
18537 - "integrity": "sha512-/tghfEJ5U7WFbF8xyOqRJks8KxP/lRjnroMXMglaushSMx8PYPo1dZDB/dJZw7ksy47MAaKJfKlx3gyN2CPXBQ==",
18280 + "version": "2.1.12",
18281 + "resolved": "https://registry.npmjs.org/@tiptap/extension-ordered-list/-/extension-ordered-list-2.1.12.tgz",
18282 + "integrity": "sha512-tF6VGl+D2avCgn9U/2YLJ8qVmV6sPE/iEzVAFZuOSe6L0Pj7SQw4K6AO640QBob/d8VrqqJFHCb6l10amJOnXA==",
18283 "requires": {}
18284 },
18285 "@tiptap/extension-paragraph": {
18541 - "version": "2.1.11",
18542 - "resolved": "https://registry.npmjs.org/@tiptap/extension-paragraph/-/extension-paragraph-2.1.11.tgz",
18543 - "integrity": "sha512-gXMgJ2CU3X4yh1wKnb8RdbDmhITB76pH6DX0uWprmEgvzNMN3Qw+h5uBD9lgxg1WVghbCmkG9mY9J4PPbPTLxw==",
18286 + "version": "2.1.12",
18287 + "resolved": "https://registry.npmjs.org/@tiptap/extension-paragraph/-/extension-paragraph-2.1.12.tgz",
18288 + "integrity": "sha512-hoH/uWPX+KKnNAZagudlsrr4Xu57nusGekkJWBcrb5MCDE91BS+DN2xifuhwXiTHxnwOMVFjluc0bPzQbkArsw==",
18289 "requires": {}
18290 },
18291 "@tiptap/extension-strike": {
18547 - "version": "2.1.11",
18548 - "resolved": "https://registry.npmjs.org/@tiptap/extension-strike/-/extension-strike-2.1.11.tgz",
18549 - "integrity": "sha512-UnjeSVgu3bDuyjjUdWsUErRCoQKAHCzH/pAiqTEPEEdFYgZFQPBpcJICRVdlYjRmI2ZKh6d0TMUS55m7ckmwmQ==",
18292 + "version": "2.1.12",
18293 + "resolved": "https://registry.npmjs.org/@tiptap/extension-strike/-/extension-strike-2.1.12.tgz",
18294 + "integrity": "sha512-HlhrzIjYUT8oCH9nYzEL2QTTn8d1ECnVhKvzAe6x41xk31PjLMHTUy8aYjeQEkWZOWZ34tiTmslV1ce6R3Dt8g==",
18295 "requires": {}
18296 },
18297 "@tiptap/extension-task-item": {
18553 - "version": "2.1.11",
18554 - "resolved": "https://registry.npmjs.org/@tiptap/extension-task-item/-/extension-task-item-2.1.11.tgz",
18555 - "integrity": "sha512-721inc/MAZkljPup/EWCpNho4nf+XrYVKWRixqgX+AjikusTJefylbiZ5OeRn+71osTA7SdnXiKkM2ZbHtAsYA==",
18298 + "version": "2.1.12",
18299 + "resolved": "https://registry.npmjs.org/@tiptap/extension-task-item/-/extension-task-item-2.1.12.tgz",
18300 + "integrity": "sha512-uqrDTO4JwukZUt40GQdvB6S+oDhdp4cKNPMi0sbteWziQugkSMLlkYvxU0Hfb/YeziaWWwFI7ssPu/hahyk6dQ==",
18301 "requires": {}
18302 },
18303 "@tiptap/extension-task-list": {
18559 - "version": "2.1.11",
18560 - "resolved": "https://registry.npmjs.org/@tiptap/extension-task-list/-/extension-task-list-2.1.11.tgz",
18561 - "integrity": "sha512-9C1M9N3jbNjm4001mPkgwUH19b6ZvKj5nnRT3zib/gFIQLOnSHE3VErDPHP/lkkjH84LgOMrm69cm8chQpgNsA==",
18304 + "version": "2.1.12",
18305 + "resolved": "https://registry.npmjs.org/@tiptap/extension-task-list/-/extension-task-list-2.1.12.tgz",
18306 + "integrity": "sha512-BUpYlEWK+Q3kw9KIiOqvhd0tUPhMcOf1+fJmCkluJok+okAxMbP1umAtCEQ3QkoCwLr+vpHJov7h3yi9+dwgeQ==",
18307 "requires": {}
18308 },
18309 "@tiptap/extension-text": {
18565 - "version": "2.1.11",
18566 - "resolved": "https://registry.npmjs.org/@tiptap/extension-text/-/extension-text-2.1.11.tgz",
18567 - "integrity": "sha512-Iey0EXYv9079+lbHMvZtLc6XcYfKrq++msEXuFFNHxvL0i/XzndhGf+qlDhLROLgEtDiiTqzOBBwFCGlFjbDow==",
18310 + "version": "2.1.12",
18311 + "resolved": "https://registry.npmjs.org/@tiptap/extension-text/-/extension-text-2.1.12.tgz",
18312 + "integrity": "sha512-rCNUd505p/PXwU9Jgxo4ZJv4A3cIBAyAqlx/dtcY6cjztCQuXJhuQILPhjGhBTOLEEL4kW2wQtqzCmb7O8i2jg==",
18313 "requires": {}
18314 },
18315 "@tiptap/extension-text-align": {
18571 - "version": "2.1.11",
18572 - "resolved": "https://registry.npmjs.org/@tiptap/extension-text-align/-/extension-text-align-2.1.11.tgz",
18573 - "integrity": "sha512-mRUouUZviU7OjzMbW5O728HsRl/T/Gue4DuNWaY2hiddlJWOpDmO/FYRR7JaAQjTr+16NCofRwgfWdJL3nyv5w==",
18316 + "version": "2.1.12",
18317 + "resolved": "https://registry.npmjs.org/@tiptap/extension-text-align/-/extension-text-align-2.1.12.tgz",
18318 + "integrity": "sha512-siMlwrkgVrAxxgmZn8GOc75J7UZi2CVrP9vDHkUPPyKm/fjssYekXwGCEk4Vswii1BbOh2gt+MDsRkeYRGyDlQ==",
18319 + "requires": {}
18320 + },
18321 + "@tiptap/extension-underline": {
18322 + "version": "2.1.12",
18323 + "resolved": "https://registry.npmjs.org/@tiptap/extension-underline/-/extension-underline-2.1.12.tgz",
18324 + "integrity": "sha512-NwwdhFT8gDD0VUNLQx85yFBhP9a8qg8GPuxlGzAP/lPTV8Ubh3vSeQ5N9k2ZF/vHlEvnugzeVCbmYn7wf8vn1g==",
18325 "requires": {}
18326 },
18327 "@tiptap/pm": {
18577 - "version": "2.1.11",
18578 - "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-2.1.11.tgz",
18579 - "integrity": "sha512-vBIAic+H8fjHfT8r2qJkAOxdx1Iiss9+qMyujAoIdPkiyjEc4+sXcM0qSYgIr6KL5icITyuK8J7x/V62VfB7Uw==",
18328 + "version": "2.1.12",
18329 + "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-2.1.12.tgz",
18330 + "integrity": "sha512-Q3MXXQABG4CZBesSp82yV84uhJh/W0Gag6KPm2HRWPimSFELM09Z9/5WK9RItAYE0aLhe4Krnyiczn9AAa1tQQ==",
18331 "requires": {
18332 "prosemirror-changeset": "^2.2.0",
18333 "prosemirror-collab": "^1.3.0",
@@ -18599,38 +18350,38 @@
18350 }
18351 },
18352 "@tiptap/starter-kit": {
18602 - "version": "2.1.11",
18603 - "resolved": "https://registry.npmjs.org/@tiptap/starter-kit/-/starter-kit-2.1.11.tgz",
18604 - "integrity": "sha512-kZXwuo9yxrs1ASxluRKjXThjdcy90d7owJxnJWD7SyEwXaXYc4h+Ar1M9rP3jieCDBuRTtCgvAOKbVbhnRJ2jg==",
18605 - "requires": {
18606 - "@tiptap/core": "^2.1.11",
18607 - "@tiptap/extension-blockquote": "^2.1.11",
18608 - "@tiptap/extension-bold": "^2.1.11",
18609 - "@tiptap/extension-bullet-list": "^2.1.11",
18610 - "@tiptap/extension-code": "^2.1.11",
18611 - "@tiptap/extension-code-block": "^2.1.11",
18612 - "@tiptap/extension-document": "^2.1.11",
18613 - "@tiptap/extension-dropcursor": "^2.1.11",
18614 - "@tiptap/extension-gapcursor": "^2.1.11",
18615 - "@tiptap/extension-hard-break": "^2.1.11",
18616 - "@tiptap/extension-heading": "^2.1.11",
18617 - "@tiptap/extension-history": "^2.1.11",
18618 - "@tiptap/extension-horizontal-rule": "^2.1.11",
18619 - "@tiptap/extension-italic": "^2.1.11",
18620 - "@tiptap/extension-list-item": "^2.1.11",
18621 - "@tiptap/extension-ordered-list": "^2.1.11",
18622 - "@tiptap/extension-paragraph": "^2.1.11",
18623 - "@tiptap/extension-strike": "^2.1.11",
18624 - "@tiptap/extension-text": "^2.1.11"
18353 + "version": "2.1.12",
18354 + "resolved": "https://registry.npmjs.org/@tiptap/starter-kit/-/starter-kit-2.1.12.tgz",
18355 + "integrity": "sha512-+RoP1rWV7rSCit2+3wl2bjvSRiePRJE/7YNKbvH8Faz/+AMO23AFegHoUFynR7U0ouGgYDljGkkj35e0asbSDA==",
18356 + "requires": {
18357 + "@tiptap/core": "^2.1.12",
18358 + "@tiptap/extension-blockquote": "^2.1.12",
18359 + "@tiptap/extension-bold": "^2.1.12",
18360 + "@tiptap/extension-bullet-list": "^2.1.12",
18361 + "@tiptap/extension-code": "^2.1.12",
18362 + "@tiptap/extension-code-block": "^2.1.12",
18363 + "@tiptap/extension-document": "^2.1.12",
18364 + "@tiptap/extension-dropcursor": "^2.1.12",
18365 + "@tiptap/extension-gapcursor": "^2.1.12",
18366 + "@tiptap/extension-hard-break": "^2.1.12",
18367 + "@tiptap/extension-heading": "^2.1.12",
18368 + "@tiptap/extension-history": "^2.1.12",
18369 + "@tiptap/extension-horizontal-rule": "^2.1.12",
18370 + "@tiptap/extension-italic": "^2.1.12",
18371 + "@tiptap/extension-list-item": "^2.1.12",
18372 + "@tiptap/extension-ordered-list": "^2.1.12",
18373 + "@tiptap/extension-paragraph": "^2.1.12",
18374 + "@tiptap/extension-strike": "^2.1.12",
18375 + "@tiptap/extension-text": "^2.1.12"
18376 }
18377 },
18378 "@tiptap/vue-3": {
18628 - "version": "2.1.11",
18629 - "resolved": "https://registry.npmjs.org/@tiptap/vue-3/-/vue-3-2.1.11.tgz",
18630 - "integrity": "sha512-PA0ep7W4cXh1jSXpXgR/eKjTbBxP8b0rIKmwLHOLVLaXz2fGFYt+HwKmtZSnYMTcf+CscXmbhmajBJZQJVJQwQ==",
18379 + "version": "2.1.12",
18380 + "resolved": "https://registry.npmjs.org/@tiptap/vue-3/-/vue-3-2.1.12.tgz",
18381 + "integrity": "sha512-yAcfmWw/9jtIUbhb0uGQVI9NoPYgHRasX2sAGWnm9Al+0aJktgmQ3mLCifXfXfjyEbeMF0p2L6Ul8tO7eho7aQ==",
18382 "requires": {
18632 - "@tiptap/extension-bubble-menu": "^2.1.11",
18633 - "@tiptap/extension-floating-menu": "^2.1.11"
18383 + "@tiptap/extension-bubble-menu": "^2.1.12",
18384 + "@tiptap/extension-floating-menu": "^2.1.12"
18385 }
18386 },
18387 "@tootallnate/once": {
@@ -18712,9 +18463,9 @@
18463 }
18464 },
18465 "@types/bytes": {
18715 - "version": "3.1.2",
18716 - "resolved": "https://registry.npmjs.org/@types/bytes/-/bytes-3.1.2.tgz",
18717 - "integrity": "sha512-92b6q7CSYBMVZDtMZh5PuKm3LjZwcU7s6H8e9sU20Z1tOrTuXN+Hz3VuP9E8axiQRaCoiEOMN1duqPCEIhamrQ==",
18466 + "version": "3.1.3",
18467 + "resolved": "https://registry.npmjs.org/@types/bytes/-/bytes-3.1.3.tgz",
18468 + "integrity": "sha512-eEgZiWn6cjG8tc+AkI3FIa9ub9zhLMSRHqbecHe5yffqws+848zoHdbgFYxvUks4RElfJB9cupvqcd1gvDFQig==",
18469 "dev": true
18470 },
18471 "@types/chai": {
@@ -18733,9 +18484,9 @@
18484 }
18485 },
18486 "@types/debug": {
18736 - "version": "4.1.8",
18737 - "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.8.tgz",
18738 - "integrity": "sha512-/vPO1EPOs306Cvhwv7KfVfYvOJqA/S/AXjaHQiJboCZzcNDb+TIJFN9/2C9DZ//ijSKWioNyUxD792QmDJ+HKQ==",
18487 + "version": "4.1.10",
18488 + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.10.tgz",
18489 + "integrity": "sha512-tOSCru6s732pofZ+sMv9o4o3Zc+Sa8l3bxd/tweTQudFn06vAzb13ZX46Zi6m6EJ+RUbRTHvgQJ1gBtSgkaUYA==",
18490 "requires": {
18491 "@types/ms": "*"
18492 }
@@ -18747,9 +18498,9 @@
18498 "dev": true
18499 },
18500 "@types/fs-extra": {
18750 - "version": "11.0.2",
18751 - "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-11.0.2.tgz",
18752 - "integrity": "sha512-c0hrgAOVYr21EX8J0jBMXGLMgJqVf/v6yxi0dLaJboW9aQPh16Id+z6w2Tx1hm+piJOLv8xPfVKZCLfjPw/IMQ==",
18501 + "version": "11.0.3",
18502 + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-11.0.3.tgz",
18503 + "integrity": "sha512-sF59BlXtUdzEAL1u0MSvuzWd7PdZvZEtnaVkzX5mjpdWTJ8brG0jUqve3jPCzSzvAKKMHTG8F8o/WMQLtleZdQ==",
18504 "dev": true,
18505 "requires": {
18506 "@types/jsonfile": "*",
@@ -18757,14 +18508,14 @@
18508 }
18509 },
18510 "@types/geojson": {
18760 - "version": "7946.0.10",
18761 - "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.10.tgz",
18762 - "integrity": "sha512-Nmh0K3iWQJzniTuPRcJn5hxXkfB1T1pgB89SBig5PlJQU5yocazeu4jATJlaA0GYFKWMqDdvYemoSnF2pXgLVA=="
18511 + "version": "7946.0.12",
18512 + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.12.tgz",
18513 + "integrity": "sha512-uK2z1ZHJyC0nQRbuovXFt4mzXDwf27vQeUWNhfKGwRcWW429GOhP8HxUHlM6TLH4bzmlv/HlEjpvJh3JfmGsAA=="
18514 },
18515 "@types/inquirer": {
18765 - "version": "9.0.3",
18766 - "resolved": "https://registry.npmjs.org/@types/inquirer/-/inquirer-9.0.3.tgz",
18767 - "integrity": "sha512-CzNkWqQftcmk2jaCWdBTf9Sm7xSw4rkI1zpU/Udw3HX5//adEZUIm9STtoRP1qgWj0CWQtJ9UTvqmO2NNjhMJw==",
18516 + "version": "9.0.6",
18517 + "resolved": "https://registry.npmjs.org/@types/inquirer/-/inquirer-9.0.6.tgz",
18518 + "integrity": "sha512-1Go1AAP/yOy3Pth5Xf1DC3nfZ03cJLCPx6E2YnSN/5I3w1jHBVH4170DkZ+JxfmA7c9kL9+bf9z3FRGa4kNAqg==",
18519 "dev": true,
18520 "requires": {
18521 "@types/through": "*",
@@ -18772,9 +18523,9 @@
18523 }
18524 },
18525 "@types/jsdom": {
18775 - "version": "21.1.3",
18776 - "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-21.1.3.tgz",
18777 - "integrity": "sha512-1zzqSP+iHJYV4lB3lZhNBa012pubABkj9yG/GuXuf6LZH1cSPIJBqFDrm5JX65HHt6VOnNYdTui/0ySerRbMgA==",
18526 + "version": "21.1.4",
18527 + "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-21.1.4.tgz",
18528 + "integrity": "sha512-NzAMLEV0KQ4cBaDx3Ls8VfJUElyDUm1xrtYRmcMK0gF8L5xYbujFVaQlJ50yinQ/d47j2rEP1XUzkiYrw4YRFA==",
18529 "dev": true,
18530 "requires": {
18531 "@types/node": "*",
@@ -18803,9 +18554,9 @@
18554 "integrity": "sha512-CeVMX9EhVUW8MWnei05eIRks4D5Wscw/W9Byz1s3PA+yJvcdvq9SaDjiUKvRvEgjpdTyJMjQA43ae4KTwsvOPg=="
18555 },
18556 "@types/lodash": {
18806 - "version": "4.14.199",
18807 - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.199.tgz",
18808 - "integrity": "sha512-Vrjz5N5Ia4SEzWWgIVwnHNEnb1UE1XMkvY5DGXrAeOGE9imk0hgTHh5GyDjLDJi9OTCn9oo9dXH1uToK1VRfrg=="
18557 + "version": "4.14.200",
18558 + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.200.tgz",
18559 + "integrity": "sha512-YI/M/4HRImtNf3pJgbF+W6FrXovqj+T+/HpENLTooK9PnkacBsDpeP3IpHab40CClUfhNmdM2WTNP2sa2dni5Q=="
18560 },
18561 "@types/lodash-es": {
18562 "version": "4.17.9",
@@ -18816,14 +18567,14 @@
18567 }
18568 },
18569 "@types/mapbox__point-geometry": {
18819 - "version": "0.1.2",
18820 - "resolved": "https://registry.npmjs.org/@types/mapbox__point-geometry/-/mapbox__point-geometry-0.1.2.tgz",
18821 - "integrity": "sha512-D0lgCq+3VWV85ey1MZVkE8ZveyuvW5VAfuahVTQRpXFQTxw03SuIf1/K4UQ87MMIXVKzpFjXFiFMZzLj2kU+iA=="
18570 + "version": "0.1.3",
18571 + "resolved": "https://registry.npmjs.org/@types/mapbox__point-geometry/-/mapbox__point-geometry-0.1.3.tgz",
18572 + "integrity": "sha512-2W46IOXlu7vC8m3+M5rDqSnuY22GFxxx3xhkoyqyPWrD+eP2iAwNst0A1+umLYjCTJMJTSpiofphn9h9k+Kw+w=="
18573 },
18574 "@types/mapbox__vector-tile": {
18824 - "version": "1.3.0",
18825 - "resolved": "https://registry.npmjs.org/@types/mapbox__vector-tile/-/mapbox__vector-tile-1.3.0.tgz",
18826 - "integrity": "sha512-kDwVreQO5V4c8yAxzZVQLE5tyWF+IPToAanloQaSnwfXmIcJ7cyOrv8z4Ft4y7PsLYmhWXmON8MBV8RX0Rgr8g==",
18575 + "version": "1.3.3",
18576 + "resolved": "https://registry.npmjs.org/@types/mapbox__vector-tile/-/mapbox__vector-tile-1.3.3.tgz",
18577 + "integrity": "sha512-d263B3KCQtXKVZMHpMJrEW5EeLBsQ8jvAS9nhpUKC5hHIlQaACG9PWkW8qxEeNuceo9120AwPjeS91uNa4ltqA==",
18578 "requires": {
18579 "@types/geojson": "*",
18580 "@types/mapbox__point-geometry": "*",
@@ -18831,23 +18582,26 @@
18582 }
18583 },
18584 "@types/mdast": {
18834 - "version": "3.0.12",
18835 - "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.12.tgz",
18836 - "integrity": "sha512-DT+iNIRNX884cx0/Q1ja7NyUPpZuv0KPyL5rGNxm1WC1OtHstl7n4Jb7nk+xacNShQMbczJjt8uFzznpp6kYBg==",
18585 + "version": "4.0.2",
18586 + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.2.tgz",
18587 + "integrity": "sha512-tYR83EignvhYO9iU3kDg8V28M0jqyh9zzp5GV+EO+AYnyUl3P5ltkTeJuTiFZQFz670FSb3EwT/6LQdX+UdKfw==",
18588 "requires": {
18838 - "@types/unist": "^2"
18589 + "@types/unist": "*"
18590 }
18591 },
18592 "@types/ms": {
18842 - "version": "0.7.31",
18843 - "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz",
18844 - "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA=="
18593 + "version": "0.7.33",
18594 + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.33.tgz",
18595 + "integrity": "sha512-AuHIyzR5Hea7ij0P9q7vx7xu4z0C28ucwjAZC0ja7JhINyCnOw8/DnvAPQQ9TfOlCtZAmCERKQX9+o1mgQhuOQ=="
18596 },
18597 "@types/node": {
18847 - "version": "20.8.2",
18848 - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.2.tgz",
18849 - "integrity": "sha512-Vvycsc9FQdwhxE3y3DzeIxuEJbWGDsnrxvMADzTDF/lcdR9/K+AQIeAghTQsHtotg/q0j3WEOYS/jQgSdWue3w==",
18850 - "dev": true
18598 + "version": "20.8.9",
18599 + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.9.tgz",
18600 + "integrity": "sha512-UzykFsT3FhHb1h7yD4CA4YhBHq545JC0YnEz41xkipN88eKQtL6rSgocL5tbAP6Ola9Izm/Aw4Ora8He4x0BHg==",
18601 + "dev": true,
18602 + "requires": {
18603 + "undici-types": "~5.26.4"
18604 + }
18605 },
18606 "@types/numeral": {
18607 "version": "0.0.28",
@@ -18865,9 +18619,9 @@
18619 "integrity": "sha512-sn7L+qQ6RLPdXRoiaE7bZ/Ek+o4uICma/lBFPyJEKDTPTBP1W8u0c4baj3EiS4DiqLs+Hk+KUGvMVJtAw3ePJg=="
18620 },
18621 "@types/pbf": {
18868 - "version": "3.0.2",
18869 - "resolved": "https://registry.npmjs.org/@types/pbf/-/pbf-3.0.2.tgz",
18870 - "integrity": "sha512-EDrLIPaPXOZqDjrkzxxbX7UlJSeQVgah3i0aA4pOSzmK9zq3BIh7/MZIQxED7slJByvKM4Gc6Hypyu2lJzh3SQ=="
18622 + "version": "3.0.4",
18623 + "resolved": "https://registry.npmjs.org/@types/pbf/-/pbf-3.0.4.tgz",
18624 + "integrity": "sha512-SOFlLGZkLbEXJRwcWCqeP/Koyaf/uAqLXHUsdo/nMfjLsNd8kqauwHe9GBOljSmpcHp/LC6kOjo3SidGjNirVA=="
18625 },
18626 "@types/resize-observer-browser": {
18627 "version": "0.1.7",
@@ -18893,9 +18647,9 @@
18647 "dev": true
18648 },
18649 "@types/supercluster": {
18896 - "version": "7.1.0",
18897 - "resolved": "https://registry.npmjs.org/@types/supercluster/-/supercluster-7.1.0.tgz",
18898 - "integrity": "sha512-6JapQ2GmEkH66r23BK49I+u6zczVDGTtiJEVvKDYZVSm/vepWaJuTq6BXzJ6I4agG5s8vA1KM7m/gXWDg03O4Q==",
18650 + "version": "7.1.2",
18651 + "resolved": "https://registry.npmjs.org/@types/supercluster/-/supercluster-7.1.2.tgz",
18652 + "integrity": "sha512-qMhofL945Z4njQUuntadexAgPtpiBC014WvVqU70Prj42LC77Xgmz04us7hSMmwjs7KbgAwGBmje+FSOvDbP0Q==",
18653 "requires": {
18654 "@types/geojson": "*"
18655 }
@@ -18921,20 +18675,20 @@
18675 "dev": true
18676 },
18677 "@types/unist": {
18924 - "version": "2.0.8",
18925 - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.8.tgz",
18926 - "integrity": "sha512-d0XxK3YTObnWVp6rZuev3c49+j4Lo8g4L1ZRm9z5L0xpoZycUPshHgczK5gsUMaZOstjVYYi09p5gYvUtfChYw=="
18678 + "version": "3.0.1",
18679 + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.1.tgz",
18680 + "integrity": "sha512-ue/hDUpPjC85m+PM9OQDMZr3LywT+CT6mPsQq8OJtCLiERkGRcQUFvu9XASF5XWqyZFXbf15lvb3JFJ4dRLWPg=="
18681 },
18682 "@types/validator": {
18929 - "version": "13.11.2",
18930 - "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.11.2.tgz",
18931 - "integrity": "sha512-nIKVVQKT6kGKysnNt+xLobr+pFJNssJRi2s034wgWeFBUx01fI8BeHTW2TcRp7VcFu9QCYG8IlChTuovcm0oKQ==",
18683 + "version": "13.11.5",
18684 + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.11.5.tgz",
18685 + "integrity": "sha512-xW4qsT4UIYILu+7ZrBnfQdBYniZrMLYYK3wN9M/NdeIHgBN5pZI2/8Q7UfdWIcr5RLJv/OGENsx91JIpUUoC7Q==",
18686 "dev": true
18687 },
18688 "@types/web-bluetooth": {
18935 - "version": "0.0.17",
18936 - "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.17.tgz",
18937 - "integrity": "sha512-4p9vcSmxAayx72yn70joFoL44c9MO/0+iVEBIQXe3v2h2SiAsEIo/G5v6ObFWvNKRFjbrVadNf9LqEEZeQPzdA=="
18689 + "version": "0.0.18",
18690 + "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.18.tgz",
18691 + "integrity": "sha512-v/ZHEj9xh82usl8LMR3GarzFY1IrbXJw5L4QfQhokjRV91q+SelFqxQWSep1ucXEZ22+dSTwLFkXeur25sPIbw=="
18692 },
18693 "@types/yauzl": {
18694 "version": "2.10.0",
@@ -19124,57 +18878,12 @@
18878 "eslint-visitor-keys": "^3.4.1"
18879 }
18880 },
19127 - "@vicons/antd": {
19128 - "version": "0.12.0",
19129 - "resolved": "https://registry.npmjs.org/@vicons/antd/-/antd-0.12.0.tgz",
19130 - "integrity": "sha512-C0p6aO1EmGG1QHrqgUWQS1No20934OdWSRQshM5NIDK5H1On6tC26U0hT6Rmp40KfUsvhvX5YW8BoWJdNFifPg==",
19131 - "dev": true
19132 - },
19133 - "@vicons/carbon": {
19134 - "version": "0.12.0",
19135 - "resolved": "https://registry.npmjs.org/@vicons/carbon/-/carbon-0.12.0.tgz",
19136 - "integrity": "sha512-kCOgr/ZOhZzoiFLJ8pwxMa2TMxrkCUOA22qExPabus35F4+USqzcsxaPoYtqRd9ROOYiHrSqwapak/ywF0D9bg==",
19137 - "dev": true
19138 - },
19139 - "@vicons/fa": {
19140 - "version": "0.12.0",
19141 - "resolved": "https://registry.npmjs.org/@vicons/fa/-/fa-0.12.0.tgz",
19142 - "integrity": "sha512-g2PIeJLsTHUjt6bK63LxqC0uYQB7iu+xViJOxvp1s8b9/akpXVPVWjDTTsP980/0KYyMMe4U7F/aUo7wY+MsXA==",
19143 - "dev": true
19144 - },
19145 - "@vicons/fluent": {
19146 - "version": "0.12.0",
19147 - "resolved": "https://registry.npmjs.org/@vicons/fluent/-/fluent-0.12.0.tgz",
19148 - "integrity": "sha512-ATCiqPuiJ6RI5GBlD3BIpZ9Xw4MsCA4RpI5oR6MCti4quS4mX1Gp6N74FCzw7lgOj+80rV4HMKhZTVInwimpVQ==",
19149 - "dev": true
19150 - },
19151 - "@vicons/ionicons5": {
19152 - "version": "0.12.0",
19153 - "resolved": "https://registry.npmjs.org/@vicons/ionicons5/-/ionicons5-0.12.0.tgz",
19154 - "integrity": "sha512-Iy1EUVRpX0WWxeu1VIReR1zsZLMc4fqpt223czR+Rpnrwu7pt46nbnC2ycO7ItI/uqDLJxnbcMC7FujKs9IfFA==",
19155 - "dev": true
19156 - },
19157 - "@vicons/material": {
19158 - "version": "0.12.0",
19159 - "resolved": "https://registry.npmjs.org/@vicons/material/-/material-0.12.0.tgz",
19160 - "integrity": "sha512-chv1CYAl8P32P3Ycwgd5+vw/OFNc2mtkKdb1Rw4T5IJmKy6GVDsoUKV3N2l208HATn7CCQphZtuPDdsm7K2kmA==",
19161 - "dev": true
19162 - },
19163 - "@vicons/tabler": {
19164 - "version": "0.12.0",
19165 - "resolved": "https://registry.npmjs.org/@vicons/tabler/-/tabler-0.12.0.tgz",
19166 - "integrity": "sha512-3+wUFuxb7e8OzZ8Wryct1pzfA2vyoF4lwW98O9s27ZrfCGaJGNmqG+q8A7vQ92Mf+COCgxpK+rhNPTtTvaU6qw==",
18881 + "@ungap/structured-clone": {
18882 + "version": "1.2.0",
18883 + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz",
18884 + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==",
18885 "dev": true
18886 },
19169 - "@vicons/utils": {
19170 - "version": "0.1.4",
19171 - "resolved": "https://registry.npmjs.org/@vicons/utils/-/utils-0.1.4.tgz",
19172 - "integrity": "sha512-OHI19qVNN6i+uPQ+Y3f2s0dUxwsYnOCcKBW7XOU4yXXO1aU3ZoKpblCc3+4N0qmgoJs5rWKRAaMisipqEXJwAg==",
19173 - "dev": true,
19174 - "requires": {
19175 - "@xicons/utils": "^0.1.4"
19176 - }
19177 - },
18887 "@vitejs/plugin-vue": {
18888 "version": "4.4.0",
18889 "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-4.4.0.tgz",
@@ -19264,30 +18973,30 @@
18973 }
18974 },
18975 "@volar/language-core": {
19267 - "version": "1.10.1",
19268 - "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-1.10.1.tgz",
19269 - "integrity": "sha512-JnsM1mIPdfGPxmoOcK1c7HYAsL6YOv0TCJ4aW3AXPZN/Jb4R77epDyMZIVudSGjWMbvv/JfUa+rQ+dGKTmgwBA==",
18976 + "version": "1.10.5",
18977 + "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-1.10.5.tgz",
18978 + "integrity": "sha512-xD71j4Ee0Ycq8WsiAE6H/aCThGdTobiZZeD+jFD+bvmbopa1Az296pqJysr3Ck8c7n5+GGF+xlKCS3WxRFYgSQ==",
18979 "dev": true,
18980 "requires": {
19272 - "@volar/source-map": "1.10.1"
18981 + "@volar/source-map": "1.10.5"
18982 }
18983 },
18984 "@volar/source-map": {
19276 - "version": "1.10.1",
19277 - "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-1.10.1.tgz",
19278 - "integrity": "sha512-3/S6KQbqa7pGC8CxPrg69qHLpOvkiPHGJtWPkI/1AXCsktkJ6gIk/5z4hyuMp8Anvs6eS/Kvp/GZa3ut3votKA==",
18985 + "version": "1.10.5",
18986 + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-1.10.5.tgz",
18987 + "integrity": "sha512-s4kgo66SA1kMzYvF9HFE6Vc1rxtXLUmcLrT2WKnchPDvLne+97Kw+xoR2NxJFmsvHoL18vmu/YGXYcN+Q5re1g==",
18988 "dev": true,
18989 "requires": {
18990 "muggle-string": "^0.3.1"
18991 }
18992 },
18993 "@volar/typescript": {
19285 - "version": "1.10.1",
19286 - "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-1.10.1.tgz",
19287 - "integrity": "sha512-+iiO9yUSRHIYjlteT+QcdRq8b44qH19/eiUZtjNtuh6D9ailYM7DVR0zO2sEgJlvCaunw/CF9Ov2KooQBpR4VQ==",
18994 + "version": "1.10.5",
18995 + "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-1.10.5.tgz",
18996 + "integrity": "sha512-kfDehpeLJku9i1BgsFOYIczPmFFH4herl+GZrLGdvX5urTqeCKsKYlF36iNmFaADzjMb9WlENcUZzPjK8MxNrQ==",
18997 "dev": true,
18998 "requires": {
19290 - "@volar/language-core": "1.10.1"
18999 + "@volar/language-core": "1.10.5"
19000 }
19001 },
19002 "@vue-leaflet/vue-leaflet": {
@@ -19323,49 +19032,49 @@
19032 }
19033 },
19034 "@vue/compiler-core": {
19326 - "version": "3.3.4",
19327 - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.3.4.tgz",
19328 - "integrity": "sha512-cquyDNvZ6jTbf/+x+AgM2Arrp6G4Dzbb0R64jiG804HRMfRiFXWI6kqUVqZ6ZR0bQhIoQjB4+2bhNtVwndW15g==",
19035 + "version": "3.3.7",
19036 + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.3.7.tgz",
19037 + "integrity": "sha512-pACdY6YnTNVLXsB86YD8OF9ihwpolzhhtdLVHhBL6do/ykr6kKXNYABRtNMGrsQXpEXXyAdwvWWkuTbs4MFtPQ==",
19038 "requires": {
19330 - "@babel/parser": "^7.21.3",
19331 - "@vue/shared": "3.3.4",
19039 + "@babel/parser": "^7.23.0",
19040 + "@vue/shared": "3.3.7",
19041 "estree-walker": "^2.0.2",
19042 "source-map-js": "^1.0.2"
19043 }
19044 },
19045 "@vue/compiler-dom": {
19337 - "version": "3.3.4",
19338 - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.3.4.tgz",
19339 - "integrity": "sha512-wyM+OjOVpuUukIq6p5+nwHYtj9cFroz9cwkfmP9O1nzH68BenTTv0u7/ndggT8cIQlnBeOo6sUT/gvHcIkLA5w==",
19046 + "version": "3.3.7",
19047 + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.3.7.tgz",
19048 + "integrity": "sha512-0LwkyJjnUPssXv/d1vNJ0PKfBlDoQs7n81CbO6Q0zdL7H1EzqYRrTVXDqdBVqro0aJjo/FOa1qBAPVI4PGSHBw==",
19049 "requires": {
19341 - "@vue/compiler-core": "3.3.4",
19342 - "@vue/shared": "3.3.4"
19050 + "@vue/compiler-core": "3.3.7",
19051 + "@vue/shared": "3.3.7"
19052 }
19053 },
19054 "@vue/compiler-sfc": {
19346 - "version": "3.3.4",
19347 - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.3.4.tgz",
19348 - "integrity": "sha512-6y/d8uw+5TkCuzBkgLS0v3lSM3hJDntFEiUORM11pQ/hKvkhSKZrXW6i69UyXlJQisJxuUEJKAWEqWbWsLeNKQ==",
19349 - "requires": {
19350 - "@babel/parser": "^7.20.15",
19351 - "@vue/compiler-core": "3.3.4",
19352 - "@vue/compiler-dom": "3.3.4",
19353 - "@vue/compiler-ssr": "3.3.4",
19354 - "@vue/reactivity-transform": "3.3.4",
19355 - "@vue/shared": "3.3.4",
19055 + "version": "3.3.7",
19056 + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.3.7.tgz",
19057 + "integrity": "sha512-7pfldWy/J75U/ZyYIXRVqvLRw3vmfxDo2YLMwVtWVNew8Sm8d6wodM+OYFq4ll/UxfqVr0XKiVwti32PCrruAw==",
19058 + "requires": {
19059 + "@babel/parser": "^7.23.0",
19060 + "@vue/compiler-core": "3.3.7",
19061 + "@vue/compiler-dom": "3.3.7",
19062 + "@vue/compiler-ssr": "3.3.7",
19063 + "@vue/reactivity-transform": "3.3.7",
19064 + "@vue/shared": "3.3.7",
19065 "estree-walker": "^2.0.2",
19357 - "magic-string": "^0.30.0",
19358 - "postcss": "^8.1.10",
19066 + "magic-string": "^0.30.5",
19067 + "postcss": "^8.4.31",
19068 "source-map-js": "^1.0.2"
19069 }
19070 },
19071 "@vue/compiler-ssr": {
19363 - "version": "3.3.4",
19364 - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.3.4.tgz",
19365 - "integrity": "sha512-m0v6oKpup2nMSehwA6Uuu+j+wEwcy7QmwMkVNVfrV9P2qE5KshC6RwOCq8fjGS/Eak/uNb8AaWekfiXxbBB6gQ==",
19072 + "version": "3.3.7",
19073 + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.3.7.tgz",
19074 + "integrity": "sha512-TxOfNVVeH3zgBc82kcUv+emNHo+vKnlRrkv8YvQU5+Y5LJGJwSNzcmLUoxD/dNzv0bhQ/F0s+InlgV0NrApJZg==",
19075 "requires": {
19367 - "@vue/compiler-dom": "3.3.4",
19368 - "@vue/shared": "3.3.4"
19076 + "@vue/compiler-dom": "3.3.7",
19077 + "@vue/shared": "3.3.7"
19078 }
19079 },
19080 "@vue/devtools-api": {
@@ -19395,17 +19104,17 @@
19104 }
19105 },
19106 "@vue/language-core": {
19398 - "version": "1.8.15",
19399 - "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-1.8.15.tgz",
19400 - "integrity": "sha512-zche5Aw8kkvp3YaghuLiOZyVIpoWHjSQ0EfjxGSsqHOPMamdCoa9x3HtbenpR38UMUoKJ88wiWuiOrV3B/Yq+A==",
19107 + "version": "1.8.22",
19108 + "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-1.8.22.tgz",
19109 + "integrity": "sha512-bsMoJzCrXZqGsxawtUea1cLjUT9dZnDsy5TuZ+l1fxRMzUGQUG9+Ypq4w//CqpWmrx7nIAJpw2JVF/t258miRw==",
19110 "dev": true,
19111 "requires": {
19403 - "@volar/language-core": "~1.10.0",
19404 - "@volar/source-map": "~1.10.0",
19112 + "@volar/language-core": "~1.10.5",
19113 + "@volar/source-map": "~1.10.5",
19114 "@vue/compiler-dom": "^3.3.0",
19406 - "@vue/reactivity": "^3.3.0",
19115 "@vue/shared": "^3.3.0",
19408 - "minimatch": "^9.0.0",
19116 + "computeds": "^0.0.1",
19117 + "minimatch": "^9.0.3",
19118 "muggle-string": "^0.3.1",
19119 "vue-template-compiler": "^2.7.14"
19120 },
@@ -19431,42 +19140,42 @@
19140 }
19141 },
19142 "@vue/reactivity": {
19434 - "version": "3.3.4",
19435 - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.3.4.tgz",
19436 - "integrity": "sha512-kLTDLwd0B1jG08NBF3R5rqULtv/f8x3rOFByTDz4J53ttIQEDmALqKqXY0J+XQeN0aV2FBxY8nJDf88yvOPAqQ==",
19143 + "version": "3.3.7",
19144 + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.3.7.tgz",
19145 + "integrity": "sha512-cZNVjWiw00708WqT0zRpyAgduG79dScKEPYJXq2xj/aMtk3SKvL3FBt2QKUlh6EHBJ1m8RhBY+ikBUzwc7/khg==",
19146 "requires": {
19438 - "@vue/shared": "3.3.4"
19147 + "@vue/shared": "3.3.7"
19148 }
19149 },
19150 "@vue/reactivity-transform": {
19442 - "version": "3.3.4",
19443 - "resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.3.4.tgz",
19444 - "integrity": "sha512-MXgwjako4nu5WFLAjpBnCj/ieqcjE2aJBINUNQzkZQfzIZA4xn+0fV1tIYBJvvva3N3OvKGofRLvQIwEQPpaXw==",
19151 + "version": "3.3.7",
19152 + "resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.3.7.tgz",
19153 + "integrity": "sha512-APhRmLVbgE1VPGtoLQoWBJEaQk4V8JUsqrQihImVqKT+8U6Qi3t5ATcg4Y9wGAPb3kIhetpufyZ1RhwbZCIdDA==",
19154 "requires": {
19446 - "@babel/parser": "^7.20.15",
19447 - "@vue/compiler-core": "3.3.4",
19448 - "@vue/shared": "3.3.4",
19155 + "@babel/parser": "^7.23.0",
19156 + "@vue/compiler-core": "3.3.7",
19157 + "@vue/shared": "3.3.7",
19158 "estree-walker": "^2.0.2",
19450 - "magic-string": "^0.30.0"
19159 + "magic-string": "^0.30.5"
19160 }
19161 },
19162 "@vue/runtime-core": {
19454 - "version": "3.3.4",
19455 - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.3.4.tgz",
19456 - "integrity": "sha512-R+bqxMN6pWO7zGI4OMlmvePOdP2c93GsHFM/siJI7O2nxFRzj55pLwkpCedEY+bTMgp5miZ8CxfIZo3S+gFqvA==",
19163 + "version": "3.3.7",
19164 + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.3.7.tgz",
19165 + "integrity": "sha512-LHq9du3ubLZFdK/BP0Ysy3zhHqRfBn80Uc+T5Hz3maFJBGhci1MafccnL3rpd5/3wVfRHAe6c+PnlO2PAavPTQ==",
19166 "requires": {
19458 - "@vue/reactivity": "3.3.4",
19459 - "@vue/shared": "3.3.4"
19167 + "@vue/reactivity": "3.3.7",
19168 + "@vue/shared": "3.3.7"
19169 }
19170 },
19171 "@vue/runtime-dom": {
19463 - "version": "3.3.4",
19464 - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.3.4.tgz",
19465 - "integrity": "sha512-Aj5bTJ3u5sFsUckRghsNjVTtxZQ1OyMWCr5dZRAPijF/0Vy4xEoRCwLyHXcj4D0UFbJ4lbx3gPTgg06K/GnPnQ==",
19172 + "version": "3.3.7",
19173 + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.3.7.tgz",
19174 + "integrity": "sha512-PFQU1oeJxikdDmrfoNQay5nD4tcPNYixUBruZzVX/l0eyZvFKElZUjW4KctCcs52nnpMGO6UDK+jF5oV4GT5Lw==",
19175 "requires": {
19467 - "@vue/runtime-core": "3.3.4",
19468 - "@vue/shared": "3.3.4",
19469 - "csstype": "^3.1.1"
19176 + "@vue/runtime-core": "3.3.7",
19177 + "@vue/shared": "3.3.7",
19178 + "csstype": "^3.1.2"
19179 },
19180 "dependencies": {
19181 "csstype": {
@@ -19477,18 +19186,18 @@
19186 }
19187 },
19188 "@vue/server-renderer": {
19480 - "version": "3.3.4",
19481 - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.3.4.tgz",
19482 - "integrity": "sha512-Q6jDDzR23ViIb67v+vM1Dqntu+HUexQcsWKhhQa4ARVzxOY2HbC7QRW/ggkDBd5BU+uM1sV6XOAP0b216o34JQ==",
19189 + "version": "3.3.7",
19190 + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.3.7.tgz",
19191 + "integrity": "sha512-UlpKDInd1hIZiNuVVVvLgxpfnSouxKQOSE2bOfQpBuGwxRV/JqqTCyyjXUWiwtVMyeRaZhOYYqntxElk8FhBhw==",
19192 "requires": {
19484 - "@vue/compiler-ssr": "3.3.4",
19485 - "@vue/shared": "3.3.4"
19193 + "@vue/compiler-ssr": "3.3.7",
19194 + "@vue/shared": "3.3.7"
19195 }
19196 },
19197 "@vue/shared": {
19489 - "version": "3.3.4",
19490 - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.4.tgz",
19491 - "integrity": "sha512-7OjdcV8vQ74eiz1TZLzZP4JwqM5fA94K6yntPS5Z25r9HDuGNzaGdgvwKYq6S+MxwF0TFRwe50fIR/MYnakdkQ=="
19198 + "version": "3.3.7",
19199 + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.7.tgz",
19200 + "integrity": "sha512-N/tbkINRUDExgcPTBvxNkvHGu504k8lzlNQRITVnm6YjOjwa4r0nnbd4Jb01sNpur5hAllyRJzSK5PvB9PPwRg=="
19201 },
19202 "@vue/test-utils": {
19203 "version": "2.4.1",
@@ -19506,16 +19215,6 @@
19215 "integrity": "sha512-CPuIReonid9+zOG/CGTT05FXrPYATEqoDGNrEaqS4hwcw5BUNM2FguC0mOwJD4Jr16UpRVl9N0pY3P+srIbqmg==",
19216 "dev": true
19217 },
19509 - "@vue/typescript": {
19510 - "version": "1.8.15",
19511 - "resolved": "https://registry.npmjs.org/@vue/typescript/-/typescript-1.8.15.tgz",
19512 - "integrity": "sha512-qWyanQKXOsK84S8rP7QBrqsvUdQ0nZABZmTjXMpb3ox4Bp5IbkscREA3OPUrkgl64mAxwwCzIWcOc3BPTCPjQw==",
19513 - "dev": true,
19514 - "requires": {
19515 - "@volar/typescript": "~1.10.0",
19516 - "@vue/language-core": "1.8.15"
19517 - }
19518 - },
19218 "@vueup/vue-quill": {
19219 "version": "1.2.0",
19220 "resolved": "https://registry.npmjs.org/@vueup/vue-quill/-/vue-quill-1.2.0.tgz",
@@ -19526,13 +19225,13 @@
19225 }
19226 },
19227 "@vueuse/components": {
19529 - "version": "10.4.1",
19530 - "resolved": "https://registry.npmjs.org/@vueuse/components/-/components-10.4.1.tgz",
19531 - "integrity": "sha512-hEWeumCfH394fkEYc/hng6T5VcjVkdqx7b75Sd6z4Uw3anjeo93Zp9qqtzFOv5bAmHls3Zy04Kowo1glrxDFRQ==",
19228 + "version": "10.5.0",
19229 + "resolved": "https://registry.npmjs.org/@vueuse/components/-/components-10.5.0.tgz",
19230 + "integrity": "sha512-zWQZ8zkNBvX++VHfyiUaQ4otb+4PWI8679GR8FvdrNnj+01LXnqvrkyKd8yTCMJ9nHqwRRTJikS5fu4Zspn9DQ==",
19231 "requires": {
19533 - "@vueuse/core": "10.4.1",
19534 - "@vueuse/shared": "10.4.1",
19535 - "vue-demi": ">=0.14.5"
19232 + "@vueuse/core": "10.5.0",
19233 + "@vueuse/shared": "10.5.0",
19234 + "vue-demi": ">=0.14.6"
19235 },
19236 "dependencies": {
19237 "vue-demi": {
@@ -19544,14 +19243,14 @@
19243 }
19244 },
19245 "@vueuse/core": {
19547 - "version": "10.4.1",
19548 - "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.4.1.tgz",
19549 - "integrity": "sha512-DkHIfMIoSIBjMgRRvdIvxsyboRZQmImofLyOHADqiVbQVilP8VVHDhBX2ZqoItOgu7dWa8oXiNnScOdPLhdEXg==",
19246 + "version": "10.5.0",
19247 + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.5.0.tgz",
19248 + "integrity": "sha512-z/tI2eSvxwLRjOhDm0h/SXAjNm8N5ld6/SC/JQs6o6kpJ6Ya50LnEL8g5hoYu005i28L0zqB5L5yAl8Jl26K3A==",
19249 "requires": {
19551 - "@types/web-bluetooth": "^0.0.17",
19552 - "@vueuse/metadata": "10.4.1",
19553 - "@vueuse/shared": "10.4.1",
19554 - "vue-demi": ">=0.14.5"
19250 + "@types/web-bluetooth": "^0.0.18",
19251 + "@vueuse/metadata": "10.5.0",
19252 + "@vueuse/shared": "10.5.0",
19253 + "vue-demi": ">=0.14.6"
19254 },
19255 "dependencies": {
19256 "vue-demi": {
@@ -19563,16 +19262,16 @@
19262 }
19263 },
19264 "@vueuse/metadata": {
19566 - "version": "10.4.1",
19567 - "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.4.1.tgz",
19568 - "integrity": "sha512-2Sc8X+iVzeuMGHr6O2j4gv/zxvQGGOYETYXEc41h0iZXIRnRbJZGmY/QP8dvzqUelf8vg0p/yEA5VpCEu+WpZg=="
19265 + "version": "10.5.0",
19266 + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.5.0.tgz",
19267 + "integrity": "sha512-fEbElR+MaIYyCkeM0SzWkdoMtOpIwO72x8WsZHRE7IggiOlILttqttM69AS13nrDxosnDBYdyy3C5mR1LCxHsw=="
19268 },
19269 "@vueuse/shared": {
19571 - "version": "10.4.1",
19572 - "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.4.1.tgz",
19573 - "integrity": "sha512-vz5hbAM4qA0lDKmcr2y3pPdU+2EVw/yzfRsBdu+6+USGa4PxqSQRYIUC9/NcT06y+ZgaTsyURw2I9qOFaaXHAg==",
19270 + "version": "10.5.0",
19271 + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.5.0.tgz",
19272 + "integrity": "sha512-18iyxbbHYLst9MqU1X1QNdMHIjks6wC7XTVf0KNOv5es/Ms6gjVFCAAWTVP2JStuGqydg3DT+ExpFORUEi9yhg==",
19273 "requires": {
19575 - "vue-demi": ">=0.14.5"
19274 + "vue-demi": ">=0.14.6"
19275 },
19276 "dependencies": {
19277 "vue-demi": {
@@ -19583,15 +19282,6 @@
19282 }
19283 }
19284 },
19586 - "@xicons/utils": {
19587 - "version": "0.1.4",
19588 - "resolved": "https://registry.npmjs.org/@xicons/utils/-/utils-0.1.4.tgz",
19589 - "integrity": "sha512-uXxKDLz9abr80yJC05XSTq6wlyFcdW+N/1IYJkeHjzzXVc4VQ0sEYMoMMTjAH7HQBOyOkzOB4pf5NGF72lwa8Q==",
19590 - "dev": true,
19591 - "requires": {
19592 - "css-render": "^0.15.12"
19593 - }
19594 - },
19285 "@yr/monotone-cubic-spline": {
19286 "version": "1.0.3",
19287 "resolved": "https://registry.npmjs.org/@yr/monotone-cubic-spline/-/monotone-cubic-spline-1.0.3.tgz",
@@ -19725,9 +19415,9 @@
19415 }
19416 },
19417 "apexcharts": {
19728 - "version": "3.43.0",
19729 - "resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-3.43.0.tgz",
19730 - "integrity": "sha512-YPw1aLatPQMUqVLMp5d+LDaXFi4QrRQND72/XO7/2NJdg+R5MjE9sifJ0GzOfgoZM7ltBUTjwfSxIvwR/9V8yw==",
19418 + "version": "3.44.0",
19419 + "resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-3.44.0.tgz",
19420 + "integrity": "sha512-u7Xzrbcxc2yWznN78Jh5NMCYVAsWDfBjRl5ea++rVzFAqjU2hLz4RgKIFwYOBDRQtW1e/Qz8azJTqIJ1+Vu9Qg==",
19421 "requires": {
19422 "@yr/monotone-cubic-spline": "^1.0.3",
19423 "svg.draggable.js": "^2.2.2",
@@ -20386,11 +20076,6 @@
20076 "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
20077 "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w=="
20078 },
20389 - "clsx": {
20390 - "version": "1.2.1",
20391 - "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz",
20392 - "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg=="
20393 - },
20079 "co": {
20080 "version": "4.6.0",
20081 "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
@@ -20450,6 +20135,12 @@
20135 "integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==",
20136 "dev": true
20137 },
20138 + "computeds": {
20139 + "version": "0.0.1",
20140 + "resolved": "https://registry.npmjs.org/computeds/-/computeds-0.0.1.tgz",
20141 + "integrity": "sha512-7CEBgcMjVmitjYo5q8JTJVra6X5mQ20uTThdK+0kR7UEaDrAWEQcRiBtWJzga4eRpP6afNwwLsX2SET2JhVB1Q==",
20142 + "dev": true
20143 + },
20144 "concat-map": {
20145 "version": "0.0.1",
20146 "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -20638,9 +20329,9 @@
20329 "integrity": "sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw=="
20330 },
20331 "cypress": {
20641 - "version": "13.3.0",
20642 - "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.3.0.tgz",
20643 - "integrity": "sha512-mpI8qcTwLGiA4zEQvTC/U1xGUezVV4V8HQCOYjlEOrVmU1etVvxOjkCXHGwrlYdZU/EPmUiWfsO3yt1o+Q2bgw==",
20332 + "version": "13.3.3",
20333 + "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.3.3.tgz",
20334 + "integrity": "sha512-mbdkojHhKB1xbrj7CrKWHi22uFx9P9vQFiR0sYDZZoK99OMp9/ZYN55TO5pjbXmV7xvCJ4JwBoADXjOJK8aCJw==",
20335 "dev": true,
20336 "requires": {
20337 "@cypress/request": "^3.0.0",
@@ -20826,9 +20517,9 @@
20517 }
20518 },
20519 "date-fns-tz": {
20829 - "version": "1.3.8",
20830 - "resolved": "https://registry.npmjs.org/date-fns-tz/-/date-fns-tz-1.3.8.tgz",
20831 - "integrity": "sha512-qwNXUFtMHTTU6CFSFjoJ80W8Fzzp24LntbjFFBgL/faqds4e5mo9mftoRLgr3Vi1trISsg4awSpYVsOQCRnapQ==",
20520 + "version": "2.0.0",
20521 + "resolved": "https://registry.npmjs.org/date-fns-tz/-/date-fns-tz-2.0.0.tgz",
20522 + "integrity": "sha512-OAtcLdB9vxSXTWHdT8b398ARImVwQMyjfYGkKD2zaGpHseG2UPHbHjXELReErZFxWdSLph3c2zOaaTyHfOhERQ==",
20523 "requires": {}
20524 },
20525 "dayjs": {
@@ -21069,6 +20760,14 @@
20760 "resolved": "https://registry.npmjs.org/detect-touch-device/-/detect-touch-device-1.1.6.tgz",
20761 "integrity": "sha512-9DYLJE05EFGI9f8m/GyJtWjw7aMZMBQM2QVy6bb7zX8uC3iorOE3Erdrk3TWCj5FVOlHTfinU0bATTE9GWYebw=="
20762 },
20763 + "devlop": {
20764 + "version": "1.1.0",
20765 + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz",
20766 + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==",
20767 + "requires": {
20768 + "dequal": "^2.0.0"
20769 + }
20770 + },
20771 "didyoumean": {
20772 "version": "1.2.2",
20773 "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
@@ -21464,18 +21163,19 @@
21163 "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="
21164 },
21165 "eslint": {
21467 - "version": "8.50.0",
21468 - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.50.0.tgz",
21469 - "integrity": "sha512-FOnOGSuFuFLv/Sa+FDVRZl4GGVAAFFi8LecRsI5a1tMO5HIE8nCm4ivAlzt4dT3ol/PaaGC0rJEEXQmHJBGoOg==",
21166 + "version": "8.52.0",
21167 + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.52.0.tgz",
21168 + "integrity": "sha512-zh/JHnaixqHZsolRB/w9/02akBk9EPrOs9JwcTP2ek7yL5bVvXuRariiaAjjoJ5DvuwQ1WAE/HsMz+w17YgBCg==",
21169 "dev": true,
21170 "requires": {
21171 "@eslint-community/eslint-utils": "^4.2.0",
21172 "@eslint-community/regexpp": "^4.6.1",
21173 "@eslint/eslintrc": "^2.1.2",
21475 - "@eslint/js": "8.50.0",
21476 - "@humanwhocodes/config-array": "^0.11.11",
21174 + "@eslint/js": "8.52.0",
21175 + "@humanwhocodes/config-array": "^0.11.13",
21176 "@humanwhocodes/module-importer": "^1.0.1",
21177 "@nodelib/fs.walk": "^1.2.8",
21178 + "@ungap/structured-clone": "^1.2.0",
21179 "ajv": "^6.12.4",
21180 "chalk": "^4.0.0",
21181 "cross-spawn": "^7.0.2",
@@ -21624,9 +21324,9 @@
21324 }
21325 },
21326 "eslint-plugin-vue": {
21627 - "version": "9.17.0",
21628 - "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.17.0.tgz",
21629 - "integrity": "sha512-r7Bp79pxQk9I5XDP0k2dpUC7Ots3OSWgvGZNu3BxmKK6Zg7NgVtcOB6OCna5Kb9oQwJPl5hq183WD0SY5tZtIQ==",
21327 + "version": "9.18.1",
21328 + "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.18.1.tgz",
21329 + "integrity": "sha512-7hZFlrEgg9NIzuVik2I9xSnJA5RsmOfueYgsUGUokEDLJ1LHtxO0Pl4duje1BriZ/jDWb+44tcIlC3yi0tdlZg==",
21330 "dev": true,
21331 "requires": {
21332 "@eslint-community/eslint-utils": "^4.4.0",
@@ -21937,11 +21637,6 @@
21637 "path-exists": "^4.0.0"
21638 }
21639 },
21940 - "flag-icons": {
21941 - "version": "6.11.1",
21942 - "resolved": "https://registry.npmjs.org/flag-icons/-/flag-icons-6.11.1.tgz",
21943 - "integrity": "sha512-c2UMJTFZoVQ47/sE1mb+9b5S1pi8SjXsx0MR063O31GV+O2EN4FMwMdEYSQItpien2bl9w1viLUoo2R3r6OK3g=="
21944 - },
21640 "flat-cache": {
21641 "version": "3.1.0",
21642 "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.1.0.tgz",
@@ -22373,9 +22068,9 @@
22068 "integrity": "sha512-BXUKIkUuh6cmmxzi5OIbUJxrG8OAk2MqoL1DtO3Wo9D2faJg2ph5ntyuQeLqaHJmzER6H5tllCDA9ZnNe9BVGg=="
22069 },
22070 "highlight.js": {
22376 - "version": "11.8.0",
22377 - "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.8.0.tgz",
22378 - "integrity": "sha512-MedQhoqVdr0U6SSnWPzfiadUcDHfN/Wzq25AkXiQv9oiOO/sG0S7XkvpFIqWBl9Yq1UYyYOOVORs5UW2XlPyzg=="
22071 + "version": "11.9.0",
22072 + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.9.0.tgz",
22073 + "integrity": "sha512-fJ7cW7fQGCYAkgv4CPfwFHrfd/cLS4Hau96JuJ+ZTOWhjnhoeN1ub1tFmALm/+lW5z4WCAuAV9bm05AP0mS6Gw=="
22074 },
22075 "hosted-git-info": {
22076 "version": "2.8.9",
@@ -22652,11 +22347,6 @@
22347 "has-tostringtag": "^1.0.0"
22348 }
22349 },
22655 - "is-buffer": {
22656 - "version": "2.0.5",
22657 - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz",
22658 - "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ=="
22659 - },
22350 "is-callable": {
22351 "version": "1.2.7",
22352 "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
@@ -22945,9 +22635,9 @@
22635 }
22636 },
22637 "jose": {
22948 - "version": "4.15.2",
22949 - "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.2.tgz",
22950 - "integrity": "sha512-IY73F228OXRl9ar3jJagh7Vnuhj/GzBunPiZP13K0lOl7Am9SoWW3kEzq3MCllJMTtZqHTiDXQvoRd4U95aU6A=="
22638 + "version": "5.0.1",
22639 + "resolved": "https://registry.npmjs.org/jose/-/jose-5.0.1.tgz",
22640 + "integrity": "sha512-gRVzy7s3RRdGbXmcTdlOswJOjhwPLx1ijIgAqLY6ktzFpOJxxYn4l0fC2vHaHHi4YBX/5FOL3aY+6W0cvQgpug=="
22641 },
22642 "js-beautify": {
22643 "version": "1.14.9",
@@ -23152,11 +22842,6 @@
22842 "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
22843 "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw=="
22844 },
23155 - "kleur": {
23156 - "version": "4.1.5",
23157 - "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz",
23158 - "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="
23159 - },
22845 "koa": {
22846 "version": "2.14.2",
22847 "resolved": "https://registry.npmjs.org/koa/-/koa-2.14.2.tgz",
@@ -23524,9 +23209,9 @@
23209 "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ=="
23210 },
23211 "magic-string": {
23527 - "version": "0.30.3",
23528 - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.3.tgz",
23529 - "integrity": "sha512-B7xGbll2fG/VjP+SWg4sX3JynwIU0mjoTc6MPpKNuIvftk6u6vqhDnk1R80b8C2GBR6ywqy+1DcKBrevBg+bmw==",
23212 + "version": "0.30.5",
23213 + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz",
23214 + "integrity": "sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==",
23215 "requires": {
23216 "@jridgewell/sourcemap-codec": "^1.4.15"
23217 }
@@ -23639,9 +23324,9 @@
23324 "dev": true
23325 },
23326 "maplibre-gl": {
23642 - "version": "3.3.1",
23643 - "resolved": "https://registry.npmjs.org/maplibre-gl/-/maplibre-gl-3.3.1.tgz",
23644 - "integrity": "sha512-SfRq9bT68GytDzCOG0IoTGg2rASbgdYunW/6xhnp55QuLmwG1M/YOlXxqHaphwia7kZbMvBOocvY0fp5yfTjZA==",
23327 + "version": "3.5.2",
23328 + "resolved": "https://registry.npmjs.org/maplibre-gl/-/maplibre-gl-3.5.2.tgz",
23329 + "integrity": "sha512-deqYA/RiEyXMGroZMDbOWNQTLnFsxREC+mDkQnuyCUNdBWm1KHafsXJYZP7rlLa5RLQNq05IAUAizY9aHTpIUw==",
23330 "requires": {
23331 "@mapbox/geojson-rewind": "^0.5.2",
23332 "@mapbox/jsonlint-lines-primitives": "^2.0.2",
@@ -23650,12 +23335,12 @@
23335 "@mapbox/unitbezier": "^0.0.1",
23336 "@mapbox/vector-tile": "^1.3.1",
23337 "@mapbox/whoots-js": "^3.1.0",
23653 - "@maplibre/maplibre-gl-style-spec": "^19.3.0",
23654 - "@types/geojson": "^7946.0.10",
23655 - "@types/mapbox__point-geometry": "^0.1.2",
23656 - "@types/mapbox__vector-tile": "^1.3.0",
23657 - "@types/pbf": "^3.0.2",
23658 - "@types/supercluster": "^7.1.0",
23338 + "@maplibre/maplibre-gl-style-spec": "^19.3.3",
23339 + "@types/geojson": "^7946.0.12",
23340 + "@types/mapbox__point-geometry": "^0.1.3",
23341 + "@types/mapbox__vector-tile": "^1.3.3",
23342 + "@types/pbf": "^3.0.4",
23343 + "@types/supercluster": "^7.1.2",
23344 "earcut": "^2.2.4",
23345 "geojson-vt": "^3.2.1",
23346 "gl-matrix": "^3.4.3",
@@ -23690,106 +23375,64 @@
23375 }
23376 },
23377 "mdast-util-definitions": {
23693 - "version": "5.1.2",
23694 - "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-5.1.2.tgz",
23695 - "integrity": "sha512-8SVPMuHqlPME/z3gqVwWY4zVXn8lqKv/pAhC57FuJ40ImXyBpmO5ukh98zB2v7Blql2FiHjHv9LVztSIqjY+MA==",
23378 + "version": "6.0.0",
23379 + "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-6.0.0.tgz",
23380 + "integrity": "sha512-scTllyX6pnYNZH/AIp/0ePz6s4cZtARxImwoPJ7kS42n+MnVsI4XbnG6d4ibehRIldYMWM2LD7ImQblVhUejVQ==",
23381 "requires": {
23697 - "@types/mdast": "^3.0.0",
23698 - "@types/unist": "^2.0.0",
23699 - "unist-util-visit": "^4.0.0"
23700 - },
23701 - "dependencies": {
23702 - "unist-util-visit": {
23703 - "version": "4.1.2",
23704 - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-4.1.2.tgz",
23705 - "integrity": "sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg==",
23706 - "requires": {
23707 - "@types/unist": "^2.0.0",
23708 - "unist-util-is": "^5.0.0",
23709 - "unist-util-visit-parents": "^5.1.1"
23710 - }
23711 - },
23712 - "unist-util-visit-parents": {
23713 - "version": "5.1.3",
23714 - "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-5.1.3.tgz",
23715 - "integrity": "sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg==",
23716 - "requires": {
23717 - "@types/unist": "^2.0.0",
23718 - "unist-util-is": "^5.0.0"
23719 - }
23720 - }
23382 + "@types/mdast": "^4.0.0",
23383 + "@types/unist": "^3.0.0",
23384 + "unist-util-visit": "^5.0.0"
23385 }
23386 },
23387 "mdast-util-from-markdown": {
23724 - "version": "1.3.1",
23725 - "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-1.3.1.tgz",
23726 - "integrity": "sha512-4xTO/M8c82qBcnQc1tgpNtubGUW/Y1tBQ1B0i5CtSoelOLKFYlElIr3bvgREYYO5iRqbMY1YuqZng0GVOI8Qww==",
23388 + "version": "2.0.0",
23389 + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.0.tgz",
23390 + "integrity": "sha512-n7MTOr/z+8NAX/wmhhDji8O3bRvPTV/U0oTCaZJkjhPSKTPhS3xufVhKGF8s1pJ7Ox4QgoIU7KHseh09S+9rTA==",
23391 "requires": {
23728 - "@types/mdast": "^3.0.0",
23729 - "@types/unist": "^2.0.0",
23392 + "@types/mdast": "^4.0.0",
23393 + "@types/unist": "^3.0.0",
23394 "decode-named-character-reference": "^1.0.0",
23731 - "mdast-util-to-string": "^3.1.0",
23732 - "micromark": "^3.0.0",
23733 - "micromark-util-decode-numeric-character-reference": "^1.0.0",
23734 - "micromark-util-decode-string": "^1.0.0",
23735 - "micromark-util-normalize-identifier": "^1.0.0",
23736 - "micromark-util-symbol": "^1.0.0",
23737 - "micromark-util-types": "^1.0.0",
23738 - "unist-util-stringify-position": "^3.0.0",
23739 - "uvu": "^0.5.0"
23395 + "devlop": "^1.0.0",
23396 + "mdast-util-to-string": "^4.0.0",
23397 + "micromark": "^4.0.0",
23398 + "micromark-util-decode-numeric-character-reference": "^2.0.0",
23399 + "micromark-util-decode-string": "^2.0.0",
23400 + "micromark-util-normalize-identifier": "^2.0.0",
23401 + "micromark-util-symbol": "^2.0.0",
23402 + "micromark-util-types": "^2.0.0",
23403 + "unist-util-stringify-position": "^4.0.0"
23404 }
23405 },
23406 "mdast-util-phrasing": {
23743 - "version": "3.0.1",
23744 - "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-3.0.1.tgz",
23745 - "integrity": "sha512-WmI1gTXUBJo4/ZmSk79Wcb2HcjPJBzM1nlI/OUWA8yk2X9ik3ffNbBGsU+09BFmXaL1IBb9fiuvq6/KMiNycSg==",
23407 + "version": "4.0.0",
23408 + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.0.0.tgz",
23409 + "integrity": "sha512-xadSsJayQIucJ9n053dfQwVu1kuXg7jCTdYsMK8rqzKZh52nLfSH/k0sAxE0u+pj/zKZX+o5wB+ML5mRayOxFA==",
23410 "requires": {
23747 - "@types/mdast": "^3.0.0",
23748 - "unist-util-is": "^5.0.0"
23411 + "@types/mdast": "^4.0.0",
23412 + "unist-util-is": "^6.0.0"
23413 }
23414 },
23415 "mdast-util-to-markdown": {
23752 - "version": "1.5.0",
23753 - "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-1.5.0.tgz",
23754 - "integrity": "sha512-bbv7TPv/WC49thZPg3jXuqzuvI45IL2EVAr/KxF0BSdHsU0ceFHOmwQn6evxAh1GaoK/6GQ1wp4R4oW2+LFL/A==",
23416 + "version": "2.1.0",
23417 + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.0.tgz",
23418 + "integrity": "sha512-SR2VnIEdVNCJbP6y7kVTJgPLifdr8WEU440fQec7qHoHOUz/oJ2jmNRqdDQ3rbiStOXb2mCDGTuwsK5OPUgYlQ==",
23419 "requires": {
23756 - "@types/mdast": "^3.0.0",
23757 - "@types/unist": "^2.0.0",
23420 + "@types/mdast": "^4.0.0",
23421 + "@types/unist": "^3.0.0",
23422 "longest-streak": "^3.0.0",
23759 - "mdast-util-phrasing": "^3.0.0",
23760 - "mdast-util-to-string": "^3.0.0",
23761 - "micromark-util-decode-string": "^1.0.0",
23762 - "unist-util-visit": "^4.0.0",
23423 + "mdast-util-phrasing": "^4.0.0",
23424 + "mdast-util-to-string": "^4.0.0",
23425 + "micromark-util-decode-string": "^2.0.0",
23426 + "unist-util-visit": "^5.0.0",
23427 "zwitch": "^2.0.0"
23764 - },
23765 - "dependencies": {
23766 - "unist-util-visit": {
23767 - "version": "4.1.2",
23768 - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-4.1.2.tgz",
23769 - "integrity": "sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg==",
23770 - "requires": {
23771 - "@types/unist": "^2.0.0",
23772 - "unist-util-is": "^5.0.0",
23773 - "unist-util-visit-parents": "^5.1.1"
23774 - }
23775 - },
23776 - "unist-util-visit-parents": {
23777 - "version": "5.1.3",
23778 - "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-5.1.3.tgz",
23779 - "integrity": "sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg==",
23780 - "requires": {
23781 - "@types/unist": "^2.0.0",
23782 - "unist-util-is": "^5.0.0"
23783 - }
23784 - }
23428 }
23429 },
23430 "mdast-util-to-string": {
23788 - "version": "3.2.0",
23789 - "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-3.2.0.tgz",
23790 - "integrity": "sha512-V4Zn/ncyN1QNSqSBxTrMOLpjr+IKdHl2v3KVLoWmDPscP4r9GcCi71gjgvUV1SFSKh92AjAG4peFuBl2/YgCJg==",
23431 + "version": "4.0.0",
23432 + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz",
23433 + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==",
23434 "requires": {
23792 - "@types/mdast": "^3.0.0"
23435 + "@types/mdast": "^4.0.0"
23436 }
23437 },
23438 "mdn-data": {
@@ -23834,215 +23477,215 @@
23477 "dev": true
23478 },
23479 "micromark": {
23837 - "version": "3.2.0",
23838 - "resolved": "https://registry.npmjs.org/micromark/-/micromark-3.2.0.tgz",
23839 - "integrity": "sha512-uD66tJj54JLYq0De10AhWycZWGQNUvDI55xPgk2sQM5kn1JYlhbCMTtEeT27+vAhW2FBQxLlOmS3pmA7/2z4aA==",
23480 + "version": "4.0.0",
23481 + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.0.tgz",
23482 + "integrity": "sha512-o/sd0nMof8kYff+TqcDx3VSrgBTcZpSvYcAHIfHhv5VAuNmisCxjhx6YmxS8PFEpb9z5WKWKPdzf0jM23ro3RQ==",
23483 "requires": {
23484 "@types/debug": "^4.0.0",
23485 "debug": "^4.0.0",
23486 "decode-named-character-reference": "^1.0.0",
23844 - "micromark-core-commonmark": "^1.0.1",
23845 - "micromark-factory-space": "^1.0.0",
23846 - "micromark-util-character": "^1.0.0",
23847 - "micromark-util-chunked": "^1.0.0",
23848 - "micromark-util-combine-extensions": "^1.0.0",
23849 - "micromark-util-decode-numeric-character-reference": "^1.0.0",
23850 - "micromark-util-encode": "^1.0.0",
23851 - "micromark-util-normalize-identifier": "^1.0.0",
23852 - "micromark-util-resolve-all": "^1.0.0",
23853 - "micromark-util-sanitize-uri": "^1.0.0",
23854 - "micromark-util-subtokenize": "^1.0.0",
23855 - "micromark-util-symbol": "^1.0.0",
23856 - "micromark-util-types": "^1.0.1",
23857 - "uvu": "^0.5.0"
23487 + "devlop": "^1.0.0",
23488 + "micromark-core-commonmark": "^2.0.0",
23489 + "micromark-factory-space": "^2.0.0",
23490 + "micromark-util-character": "^2.0.0",
23491 + "micromark-util-chunked": "^2.0.0",
23492 + "micromark-util-combine-extensions": "^2.0.0",
23493 + "micromark-util-decode-numeric-character-reference": "^2.0.0",
23494 + "micromark-util-encode": "^2.0.0",
23495 + "micromark-util-normalize-identifier": "^2.0.0",
23496 + "micromark-util-resolve-all": "^2.0.0",
23497 + "micromark-util-sanitize-uri": "^2.0.0",
23498 + "micromark-util-subtokenize": "^2.0.0",
23499 + "micromark-util-symbol": "^2.0.0",
23500 + "micromark-util-types": "^2.0.0"
23501 }
23502 },
23503 "micromark-core-commonmark": {
23861 - "version": "1.1.0",
23862 - "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-1.1.0.tgz",
23863 - "integrity": "sha512-BgHO1aRbolh2hcrzL2d1La37V0Aoz73ymF8rAcKnohLy93titmv62E0gP8Hrx9PKcKrqCZ1BbLGbP3bEhoXYlw==",
23504 + "version": "2.0.0",
23505 + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.0.tgz",
23506 + "integrity": "sha512-jThOz/pVmAYUtkroV3D5c1osFXAMv9e0ypGDOIZuCeAe91/sD6BoE2Sjzt30yuXtwOYUmySOhMas/PVyh02itA==",
23507 "requires": {
23508 "decode-named-character-reference": "^1.0.0",
23866 - "micromark-factory-destination": "^1.0.0",
23867 - "micromark-factory-label": "^1.0.0",
23868 - "micromark-factory-space": "^1.0.0",
23869 - "micromark-factory-title": "^1.0.0",
23870 - "micromark-factory-whitespace": "^1.0.0",
23871 - "micromark-util-character": "^1.0.0",
23872 - "micromark-util-chunked": "^1.0.0",
23873 - "micromark-util-classify-character": "^1.0.0",
23874 - "micromark-util-html-tag-name": "^1.0.0",
23875 - "micromark-util-normalize-identifier": "^1.0.0",
23876 - "micromark-util-resolve-all": "^1.0.0",
23877 - "micromark-util-subtokenize": "^1.0.0",
23878 - "micromark-util-symbol": "^1.0.0",
23879 - "micromark-util-types": "^1.0.1",
23880 - "uvu": "^0.5.0"
23509 + "devlop": "^1.0.0",
23510 + "micromark-factory-destination": "^2.0.0",
23511 + "micromark-factory-label": "^2.0.0",
23512 + "micromark-factory-space": "^2.0.0",
23513 + "micromark-factory-title": "^2.0.0",
23514 + "micromark-factory-whitespace": "^2.0.0",
23515 + "micromark-util-character": "^2.0.0",
23516 + "micromark-util-chunked": "^2.0.0",
23517 + "micromark-util-classify-character": "^2.0.0",
23518 + "micromark-util-html-tag-name": "^2.0.0",
23519 + "micromark-util-normalize-identifier": "^2.0.0",
23520 + "micromark-util-resolve-all": "^2.0.0",
23521 + "micromark-util-subtokenize": "^2.0.0",
23522 + "micromark-util-symbol": "^2.0.0",
23523 + "micromark-util-types": "^2.0.0"
23524 }
23525 },
23526 "micromark-factory-destination": {
23884 - "version": "1.1.0",
23885 - "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-1.1.0.tgz",
23886 - "integrity": "sha512-XaNDROBgx9SgSChd69pjiGKbV+nfHGDPVYFs5dOoDd7ZnMAE+Cuu91BCpsY8RT2NP9vo/B8pds2VQNCLiu0zhg==",
23527 + "version": "2.0.0",
23528 + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.0.tgz",
23529 + "integrity": "sha512-j9DGrQLm/Uhl2tCzcbLhy5kXsgkHUrjJHg4fFAeoMRwJmJerT9aw4FEhIbZStWN8A3qMwOp1uzHr4UL8AInxtA==",
23530 "requires": {
23888 - "micromark-util-character": "^1.0.0",
23889 - "micromark-util-symbol": "^1.0.0",
23890 - "micromark-util-types": "^1.0.0"
23531 + "micromark-util-character": "^2.0.0",
23532 + "micromark-util-symbol": "^2.0.0",
23533 + "micromark-util-types": "^2.0.0"
23534 }
23535 },
23536 "micromark-factory-label": {
23894 - "version": "1.1.0",
23895 - "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-1.1.0.tgz",
23896 - "integrity": "sha512-OLtyez4vZo/1NjxGhcpDSbHQ+m0IIGnT8BoPamh+7jVlzLJBH98zzuCoUeMxvM6WsNeh8wx8cKvqLiPHEACn0w==",
23537 + "version": "2.0.0",
23538 + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.0.tgz",
23539 + "integrity": "sha512-RR3i96ohZGde//4WSe/dJsxOX6vxIg9TimLAS3i4EhBAFx8Sm5SmqVfR8E87DPSR31nEAjZfbt91OMZWcNgdZw==",
23540 "requires": {
23898 - "micromark-util-character": "^1.0.0",
23899 - "micromark-util-symbol": "^1.0.0",
23900 - "micromark-util-types": "^1.0.0",
23901 - "uvu": "^0.5.0"
23541 + "devlop": "^1.0.0",
23542 + "micromark-util-character": "^2.0.0",
23543 + "micromark-util-symbol": "^2.0.0",
23544 + "micromark-util-types": "^2.0.0"
23545 }
23546 },
23547 "micromark-factory-space": {
23905 - "version": "1.1.0",
23906 - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-1.1.0.tgz",
23907 - "integrity": "sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ==",
23548 + "version": "2.0.0",
23549 + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.0.tgz",
23550 + "integrity": "sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==",
23551 "requires": {
23909 - "micromark-util-character": "^1.0.0",
23910 - "micromark-util-types": "^1.0.0"
23552 + "micromark-util-character": "^2.0.0",
23553 + "micromark-util-types": "^2.0.0"
23554 }
23555 },
23556 "micromark-factory-title": {
23914 - "version": "1.1.0",
23915 - "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-1.1.0.tgz",
23916 - "integrity": "sha512-J7n9R3vMmgjDOCY8NPw55jiyaQnH5kBdV2/UXCtZIpnHH3P6nHUKaH7XXEYuWwx/xUJcawa8plLBEjMPU24HzQ==",
23557 + "version": "2.0.0",
23558 + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.0.tgz",
23559 + "integrity": "sha512-jY8CSxmpWLOxS+t8W+FG3Xigc0RDQA9bKMY/EwILvsesiRniiVMejYTE4wumNc2f4UbAa4WsHqe3J1QS1sli+A==",
23560 "requires": {
23918 - "micromark-factory-space": "^1.0.0",
23919 - "micromark-util-character": "^1.0.0",
23920 - "micromark-util-symbol": "^1.0.0",
23921 - "micromark-util-types": "^1.0.0"
23561 + "micromark-factory-space": "^2.0.0",
23562 + "micromark-util-character": "^2.0.0",
23563 + "micromark-util-symbol": "^2.0.0",
23564 + "micromark-util-types": "^2.0.0"
23565 }
23566 },
23567 "micromark-factory-whitespace": {
23925 - "version": "1.1.0",
23926 - "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-1.1.0.tgz",
23927 - "integrity": "sha512-v2WlmiymVSp5oMg+1Q0N1Lxmt6pMhIHD457whWM7/GUlEks1hI9xj5w3zbc4uuMKXGisksZk8DzP2UyGbGqNsQ==",
23568 + "version": "2.0.0",
23569 + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.0.tgz",
23570 + "integrity": "sha512-28kbwaBjc5yAI1XadbdPYHX/eDnqaUFVikLwrO7FDnKG7lpgxnvk/XGRhX/PN0mOZ+dBSZ+LgunHS+6tYQAzhA==",
23571 "requires": {
23929 - "micromark-factory-space": "^1.0.0",
23930 - "micromark-util-character": "^1.0.0",
23931 - "micromark-util-symbol": "^1.0.0",
23932 - "micromark-util-types": "^1.0.0"
23572 + "micromark-factory-space": "^2.0.0",
23573 + "micromark-util-character": "^2.0.0",
23574 + "micromark-util-symbol": "^2.0.0",
23575 + "micromark-util-types": "^2.0.0"
23576 }
23577 },
23578 "micromark-util-character": {
23936 - "version": "1.2.0",
23937 - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.2.0.tgz",
23938 - "integrity": "sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==",
23579 + "version": "2.0.1",
23580 + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.0.1.tgz",
23581 + "integrity": "sha512-3wgnrmEAJ4T+mGXAUfMvMAbxU9RDG43XmGce4j6CwPtVxB3vfwXSZ6KhFwDzZ3mZHhmPimMAXg71veiBGzeAZw==",
23582 "requires": {
23940 - "micromark-util-symbol": "^1.0.0",
23941 - "micromark-util-types": "^1.0.0"
23583 + "micromark-util-symbol": "^2.0.0",
23584 + "micromark-util-types": "^2.0.0"
23585 }
23586 },
23587 "micromark-util-chunked": {
23945 - "version": "1.1.0",
23946 - "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-1.1.0.tgz",
23947 - "integrity": "sha512-Ye01HXpkZPNcV6FiyoW2fGZDUw4Yc7vT0E9Sad83+bEDiCJ1uXu0S3mr8WLpsz3HaG3x2q0HM6CTuPdcZcluFQ==",
23588 + "version": "2.0.0",
23589 + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.0.tgz",
23590 + "integrity": "sha512-anK8SWmNphkXdaKgz5hJvGa7l00qmcaUQoMYsBwDlSKFKjc6gjGXPDw3FNL3Nbwq5L8gE+RCbGqTw49FK5Qyvg==",
23591 "requires": {
23949 - "micromark-util-symbol": "^1.0.0"
23592 + "micromark-util-symbol": "^2.0.0"
23593 }
23594 },
23595 "micromark-util-classify-character": {
23953 - "version": "1.1.0",
23954 - "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-1.1.0.tgz",
23955 - "integrity": "sha512-SL0wLxtKSnklKSUplok1WQFoGhUdWYKggKUiqhX+Swala+BtptGCu5iPRc+xvzJ4PXE/hwM3FNXsfEVgoZsWbw==",
23596 + "version": "2.0.0",
23597 + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.0.tgz",
23598 + "integrity": "sha512-S0ze2R9GH+fu41FA7pbSqNWObo/kzwf8rN/+IGlW/4tC6oACOs8B++bh+i9bVyNnwCcuksbFwsBme5OCKXCwIw==",
23599 "requires": {
23957 - "micromark-util-character": "^1.0.0",
23958 - "micromark-util-symbol": "^1.0.0",
23959 - "micromark-util-types": "^1.0.0"
23600 + "micromark-util-character": "^2.0.0",
23601 + "micromark-util-symbol": "^2.0.0",
23602 + "micromark-util-types": "^2.0.0"
23603 }
23604 },
23605 "micromark-util-combine-extensions": {
23963 - "version": "1.1.0",
23964 - "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-1.1.0.tgz",
23965 - "integrity": "sha512-Q20sp4mfNf9yEqDL50WwuWZHUrCO4fEyeDCnMGmG5Pr0Cz15Uo7KBs6jq+dq0EgX4DPwwrh9m0X+zPV1ypFvUA==",
23606 + "version": "2.0.0",
23607 + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.0.tgz",
23608 + "integrity": "sha512-vZZio48k7ON0fVS3CUgFatWHoKbbLTK/rT7pzpJ4Bjp5JjkZeasRfrS9wsBdDJK2cJLHMckXZdzPSSr1B8a4oQ==",
23609 "requires": {
23967 - "micromark-util-chunked": "^1.0.0",
23968 - "micromark-util-types": "^1.0.0"
23610 + "micromark-util-chunked": "^2.0.0",
23611 + "micromark-util-types": "^2.0.0"
23612 }
23613 },
23614 "micromark-util-decode-numeric-character-reference": {

This file is too large to show in full.

package.json
+50 -60
@@ -29,72 +29,71 @@
29 },
30 "dependencies": {
31 "@ajoelp/json-to-formdata": "^1.5.0",
32 - "@animxyz/core": "^0.6.6",
33 - "@animxyz/vue3": "^0.6.7",
32 "@fawmi/vue-google-maps": "^0.9.79",
35 - "@fontsource/jetbrains-mono": "^5.0.14",
36 - "@fontsource/lexend": "^5.0.14",
37 - "@fontsource/public-sans": "^5.0.12",
33 + "@fontsource/jetbrains-mono": "^5.0.17",
34 + "@fontsource/lexend": "^5.0.17",
35 + "@fontsource/public-sans": "^5.0.15",
36 "@fullcalendar/core": "^6.1.9",
37 "@fullcalendar/daygrid": "^6.1.9",
38 "@fullcalendar/interaction": "^6.1.9",
39 "@fullcalendar/list": "^6.1.9",
40 "@fullcalendar/timegrid": "^6.1.9",
41 "@fullcalendar/vue3": "^6.1.9",
44 - "@milkdown/core": "^7.3.0",
45 - "@milkdown/ctx": "^7.3.0",
46 - "@milkdown/preset-commonmark": "^7.3.0",
47 - "@milkdown/prose": "^7.3.0",
48 - "@milkdown/theme-nord": "^7.3.0",
49 - "@milkdown/transformer": "^7.3.0",
50 - "@milkdown/vue": "^7.3.0",
42 + "@milkdown/core": "^7.3.1",
43 + "@milkdown/ctx": "^7.3.1",
44 + "@milkdown/preset-commonmark": "^7.3.1",
45 + "@milkdown/prose": "^7.3.1",
46 + "@milkdown/theme-nord": "^7.3.1",
47 + "@milkdown/transformer": "^7.3.1",
48 + "@milkdown/vue": "^7.3.1",
49 "@popperjs/core": "^2.11.8",
52 - "@revolist/revogrid": "^4.7.0-next.3",
50 + "@revolist/revogrid": "^4.7.0-next.4",
51 "@revolist/revogrid-column-numeral": "^1.0.2",
52 "@revolist/vue3-datagrid": "^3.6.17",
55 - "@tiptap/extension-character-count": "^2.1.11",
56 - "@tiptap/extension-highlight": "^2.1.11",
57 - "@tiptap/extension-link": "^2.1.11",
58 - "@tiptap/extension-task-item": "^2.1.11",
59 - "@tiptap/extension-task-list": "^2.1.11",
60 - "@tiptap/extension-text-align": "^2.1.11",
61 - "@tiptap/pm": "^2.1.11",
62 - "@tiptap/starter-kit": "^2.1.11",
63 - "@tiptap/vue-3": "^2.1.11",
53 + "@tiptap/extension-character-count": "^2.1.12",
54 + "@tiptap/extension-highlight": "^2.1.12",
55 + "@tiptap/extension-link": "^2.1.12",
56 + "@tiptap/extension-task-item": "^2.1.12",
57 + "@tiptap/extension-task-list": "^2.1.12",
58 + "@tiptap/extension-text-align": "^2.1.12",
59 + "@tiptap/extension-underline": "^2.1.12",
60 + "@tiptap/pm": "^2.1.12",
61 + "@tiptap/starter-kit": "^2.1.12",
62 + "@tiptap/vue-3": "^2.1.12",
63 "@vueup/vue-quill": "^1.2.0",
65 - "@vueuse/components": "^10.4.1",
66 - "@vueuse/core": "^10.4.1",
67 - "apexcharts": "^3.43.0",
64 + "@vueuse/components": "^10.5.0",
65 + "@vueuse/core": "^10.5.0",
66 + "apexcharts": "^3.44.0",
67 "bytes": "^3.1.2",
68 "chart.js": "^4.4.0",
69 "colord": "^2.9.3",
70 "dayjs": "^1.11.10",
71 "detect-touch-device": "^1.1.6",
72 "echarts": "^5.4.3",
74 - "flag-icons": "^6.11.1",
73 "geojson": "^0.5.0",
76 - "highlight.js": "^11.8.0",
77 - "jose": "^4.15.2",
74 + "highlight.js": "^11.9.0",
75 + "jose": "^5.0.1",
76 "lodash": "^4.17.21",
79 - "maplibre-gl": "3.3.1",
77 + "maplibre-gl": "^3.5.2",
78 "mitt": "^3.0.1",
79 "naive-ui": "^2.35.0",
80 "password-validator": "^5.3.0",
83 - "pinia": "^2.1.6",
81 + "pinia": "^2.1.7",
82 "pinia-plugin-persistedstate": "^3.2.0",
83 "quill": "^1.3.7",
84 "secure-ls": "^1.2.6",
85 "shepherd.js": "^11.2.0",
88 - "v-calendar": "^3.1.0",
86 + "v-calendar": "^3.1.2",
87 "validator": "^13.11.0",
90 - "vue": "^3.3.4",
88 + "vue": "^3.3.7",
89 "vue-advanced-cropper": "^2.8.8",
90 "vue-cal": "^4.8.1",
91 "vue-chartjs": "^5.2.0",
92 "vue-highlight-words": "^3.0.1",
95 - "vue-i18n": "9.5.0",
93 + "vue-i18n": "^9.6.2",
94 "vue-maplibre-gl": "^3.0.3",
95 "vue-router": "^4.2.5",
96 + "vue-sjv": "^0.0.6",
97 "vue3-apexcharts": "^1.4.4",
98 "vue3-marquee": "^4.1.0",
99 "vuedraggable": "^4.1.0",
@@ -103,26 +102,17 @@
102 "devDependencies": {
103 "@clack/prompts": "^0.7.0",
104 "@css-render/vue3-ssr": "^0.15.12",
106 - "@faker-js/faker": "^8.1.0",
107 - "@intlify/shared": "^9.5.0",
108 - "@intlify/vue-devtools": "^9.5.0",
105 + "@faker-js/faker": "^8.2.0",
106 + "@iconify/vue": "^4.1.1",
107 "@rushstack/eslint-patch": "^1.5.1",
108 "@tsconfig/node18": "^18.2.2",
111 - "@types/bytes": "^3.1.2",
112 - "@types/fs-extra": "^11.0.2",
113 - "@types/inquirer": "^9.0.3",
114 - "@types/jsdom": "^21.1.3",
115 - "@types/lodash": "^4.14.199",
116 - "@types/node": "^20.8.2",
117 - "@types/validator": "^13.11.2",
118 - "@vicons/antd": "^0.12.0",
119 - "@vicons/carbon": "^0.12.0",
120 - "@vicons/fa": "^0.12.0",
121 - "@vicons/fluent": "^0.12.0",
122 - "@vicons/ionicons5": "^0.12.0",
123 - "@vicons/material": "^0.12.0",
124 - "@vicons/tabler": "^0.12.0",
125 - "@vicons/utils": "^0.1.4",
109 + "@types/bytes": "^3.1.3",
110 + "@types/fs-extra": "^11.0.3",
111 + "@types/inquirer": "^9.0.6",
112 + "@types/jsdom": "^21.1.4",
113 + "@types/lodash": "^4.14.200",
114 + "@types/node": "^20.8.9",
115 + "@types/validator": "^13.11.5",
116 "@vitejs/plugin-vue": "^4.4.0",
117 "@vitejs/plugin-vue-jsx": "^3.0.2",
118 "@vue-leaflet/vue-leaflet": "^0.10.1",
@@ -131,10 +121,10 @@
121 "@vue/test-utils": "^2.4.1",
122 "@vue/tsconfig": "^0.4.0",
123 "autoprefixer": "^10.4.16",
134 - "cypress": "^13.3.0",
135 - "eslint": "^8.50.0",
124 + "cypress": "^13.3.3",
125 + "eslint": "^8.52.0",
126 "eslint-plugin-cypress": "^2.15.1",
137 - "eslint-plugin-vue": "^9.17.0",
127 + "eslint-plugin-vue": "^9.18.1",
128 "fs-extra": "^11.1.1",
129 "jsdom": "^22.1.0",
130 "json5": "^2.2.3",
@@ -143,20 +133,20 @@
133 "picocolors": "^1.0.0",
134 "postcss": "^8.4.31",
135 "prettier": "^3.0.3",
146 - "sass": "^1.69.0",
136 + "sass": "^1.69.5",
137 "start-server-and-test": "^2.0.1",
138 "tailwind-config-viewer": "^1.7.2",
149 - "tailwindcss": "^3.3.3",
150 - "taze": "^0.11.3",
139 + "tailwindcss": "^3.3.5",
140 + "taze": "^0.12.0",
141 "ts-node": "^10.9.1",
142 "typescript": "~5.2.2",
143 "unplugin-vue-components": "^0.25.2",
154 - "vite": "^4.4.11",
144 + "vite": "^4.5.0",
145 "vite-svg-loader": "^4.0.0",
146 "vitest": "^0.34.6",
157 - "vue-tsc": "^1.8.15"
147 + "vue-tsc": "^1.8.22"
148 },
149 "engines": {
150 "node": ">=16.0.0 <20.5.0"
151 }
162 -}
\ No newline at end of file
152 +}
scripts/tokens-tool.js
+90 -12
@@ -6,6 +6,7 @@ const os = require("node:os")
6 const { intro, outro, select, spinner, isCancel, cancel, text } = require("@clack/prompts")
7
8 const GLOBAL_KEYS = ["border-radius", "line-heights", "font-sizes", "font-families"]
9 +const TYPO_KEYS = ["typo"]
10 const SET_KEYS = ["color"]
11 const TOKENS_MAP = [
12 {
@@ -23,8 +24,27 @@ const TOKENS_MAP = [
24 {
25 token: "lineHeight",
26 type: "lineHeights"
27 + },
28 + {
29 + token: "typography",
30 + type: "typo"
31 }
32 ]
33 +
34 +const DESIGN_TOKEN_PATH = fs.pathExistsSync(path.join(process.cwd(), "src"))
35 + ? path.join(process.cwd(), "src", "design-tokens.json")
36 + : path.join(process.cwd(), "design-tokens.json")
37 +const FIGMA_TOKEN_PATH = path.join(process.cwd(), "figma-tokens.json")
38 +
39 +function getValue(origin, val) {
40 + if (val && val.indexOf("{") === 0) {
41 + const path = val.replace("{", "").replace("}", "")
42 + return _.get(origin, path)
43 + }
44 +
45 + return val
46 +}
47 +
48 /**
49 *
50 * @param {string} name
@@ -48,7 +68,6 @@ function tokenNameSanitize(name, from) {
68 */
69 async function importTokens(tokensPath) {
70 const filePath = path.normalize(tokensPath.trim().replace("~/", os.homedir() + "/"))
51 - const projectFilePath = path.join(process.cwd(), "src", "design-tokens.json")
71 const tokens = await fs.readJSON(filePath)
72
73 const projectFile = {}
@@ -71,6 +90,31 @@ async function importTokens(tokensPath) {
90 }
91 }
92
93 + for (const k in globalTokens) {
94 + for (const tk of TYPO_KEYS) {
95 + const kIndex = k.indexOf(tk)
96 + if (kIndex !== -1) {
97 + const value = globalTokens[k].value
98 + const element = _.split(k, "-")[1]
99 + const tkParsed = tokenNameSanitize(_.camelCase(tk), "type")
100 +
101 + for (const k in value) {
102 + const prop = value[k]
103 + if (prop.indexOf("{") === 0) {
104 + const prefix = tokenNameSanitize(k, "token")
105 + const ref = prop
106 + .replace(_.kebabCase(prefix) + "-", "")
107 + .replace("{", "")
108 + .replace("}", "")
109 + value[k] = `{${k}.${_.camelCase(ref)}}`
110 + }
111 + }
112 +
113 + _.set(projectFile, `${tkParsed}.${element}`, value)
114 + }
115 + }
116 + }
117 +
118 for (const set of setTokens) {
119 const setName = set.key
120 const group = set.value
@@ -97,9 +141,9 @@ async function importTokens(tokensPath) {
141 }
142 }
143
100 - await fs.writeJSON(projectFilePath, projectFile, { spaces: "\t" })
144 + await fs.writeJSON(DESIGN_TOKEN_PATH, projectFile, { spaces: "\t" })
145
102 - return projectFilePath
146 + return DESIGN_TOKEN_PATH
147 }
148
149 /**
@@ -107,9 +151,7 @@ async function importTokens(tokensPath) {
151 * @returns {string}
152 */
153 async function exportTokens() {
110 - const projectFilePath = path.join(process.cwd(), "src", "design-tokens.json")
111 - const figmaFilePath = path.join(process.cwd(), "figma-tokens.json")
112 - const tokens = await fs.readJSON(projectFilePath)
154 + const tokens = await fs.readJSON(DESIGN_TOKEN_PATH)
155
156 const groups = _.chain(tokens)
157 .toPairs()
@@ -122,8 +164,9 @@ async function exportTokens() {
164 dark: {}
165 }
166
125 - const globalTokens = groups.filter(o => o.key !== "colors")
126 - const setsTokens = groups.filter(o => o.key === "colors")
167 + const globalTokens = groups.filter(o => !["colors", "typography"].includes(o.key))
168 + const setsTokens = groups.filter(o => ["colors"].includes(o.key))
169 + const typoTokens = groups.filter(o => ["typography"].includes(o.key))
170
171 for (const group of globalTokens) {
172 const type = tokenNameSanitize(group.key, "token")
@@ -160,9 +203,44 @@ async function exportTokens() {
203 }
204 }
205
163 - await fs.writeJSON(figmaFilePath, exportFile, { spaces: "\t" })
206 + for (const group of typoTokens) {
207 + const type = group.key
208 + const set = group.value
209 +
210 + for (const setName in set) {
211 + const globalName = `typo-${setName}`
212 + const value = set[setName]
213 + const newValue = {}
214 +
215 + for (const k in value) {
216 + const prop = value[k]
217 + if (prop.indexOf("{") === 0) {
218 + const ref = prop.replace("{", "").replace("}", "")
219 + const path = _.split(ref, ".")[1]
220 + const prefix = tokenNameSanitize(k, "token")
221 + newValue[k] = "{" + _.kebabCase(`${prefix}-${_.kebabCase(path)}`) + "}"
222 + } else {
223 + newValue[k] = prop
224 + }
225 + }
226 +
227 + // sanitize lineHeight for figma
228 + if (value.fontSize && tokens?.lineHeight?.base) {
229 + newValue.lineHeight = Math.round(
230 + parseInt(getValue(tokens, value.fontSize)) * parseFloat(tokens.lineHeight.base)
231 + ).toString()
232 + }
233 +
234 + exportFile.global[globalName] = {
235 + value: newValue,
236 + type
237 + }
238 + }
239 + }
240 +
241 + await fs.writeJSON(FIGMA_TOKEN_PATH, exportFile, { spaces: "\t" })
242
165 - return figmaFilePath
243 + return FIGMA_TOKEN_PATH
244 }
245
246 async function main() {
@@ -172,8 +250,8 @@ async function main() {
250 const flowType = await select({
251 message: "Choose an action.",
252 options: [
175 - { value: "import", label: "Import tokens file" },
176 - { value: "export", label: "Create tokens json" }
253 + { value: "import", label: "Import figma tokens" },
254 + { value: "export", label: "Export figma json" }
255 ]
256 })
257
src/App.vue
+8 -4
@@ -13,6 +13,7 @@
13 </component>
14
15 <SplashScreen :loading="loading" />
16 + <SearchDialog v-if="isLogged" />
17 <LayoutSettings />
18 </Provider>
19 </template>
@@ -20,13 +21,15 @@
21 <script lang="ts" setup>
22 import { computed, onBeforeMount, ref, type Component } from "vue"
23 import { useMainStore } from "@/stores/main"
24 +import { useAuthStore } from "@/stores/auth"
25 import { useThemeStore } from "@/stores/theme"
24 -import VerticalNav from "@/layouts/VerticalNav"
25 -import HorizontalNav from "@/layouts/HorizontalNav"
26 -import Blank from "@/layouts/Blank"
26 +import VerticalNav from "@/layouts/VerticalNav/index.vue"
27 +import HorizontalNav from "@/layouts/HorizontalNav/index.vue"
28 +import Blank from "@/layouts/Blank/index.vue"
29 import Provider from "@/layouts/common/Provider.vue"
30 import SplashScreen from "@/layouts/common/SplashScreen.vue"
29 -import LayoutSettings from "@/components/LayoutSettings"
31 +import LayoutSettings from "@/components/common/LayoutSettings.vue"
32 +import SearchDialog from "@/components/common/SearchDialog.vue"
33 import { Layout, RouterTransition, type ThemeName } from "@/types/theme.d"
34 import { type RouteLocationNormalized, useRouter, useRoute } from "vue-router"
35 import "@/assets/scss/index.scss"
@@ -46,6 +49,7 @@ const layout = computed<Layout>(() => useThemeStore().layout)
49 const layoutComponent = computed<Component>(() => layoutComponents[forceLayout.value || layout.value])
50 const routerTransition = computed<RouterTransition>(() => useThemeStore().routerTransition)
51 const themeName = computed<ThemeName>(() => useThemeStore().themeName)
52 +const isLogged = computed(() => useAuthStore().isLogged)
53
54 function checkForcedLayout(route: RouteLocationNormalized) {
55 if (route.meta?.forceLayout) {
src/api/graylog.ts
+66 -29
@@ -1,51 +1,88 @@
1 import { HttpClient } from "./httpClient"
2 import type { FlaskBaseResponse } from "@/types/flask.d"
3 -import {
4 - type Message,
5 - type ThroughputMetric,
6 - type IndexData,
7 - type Inputs,
8 - InputState,
9 - type Streams
10 -} from "@/types/graylog.d" // Import Graylog interfaces
3 +import type { Message, ThroughputMetric, IndexData } from "@/types/graylog/index.d"
4 +import type { Alerts, AlertsQuery } from "@/types/graylog/alerts.d"
5 +import type { EventDefinition } from "@/types/graylog/event-definition.d"
6 +import type { Stream } from "@/types/graylog/stream.d"
7 +import type { ConfiguredInput, RunningInput } from "@/types/graylog/inputs.d"
8 +import type { PipelineRule, Pipeline, PipelineFull } from "@/types/graylog/pipelines.d"
9
10 export default {
13 - getMessages() {
14 - return HttpClient.get<FlaskBaseResponse & { messages: Message[] }>(`/graylog/messages`)
11 + getMessages(page?: number) {
12 + return HttpClient.get<FlaskBaseResponse & { graylog_messages: Message[]; total_messages: number }>(
13 + `/graylog/messages`,
14 + {
15 + params: {
16 + page_number: page || 1
17 + }
18 + }
19 + )
20 },
16 - getMetrics() {
17 - return HttpClient.get<FlaskBaseResponse & { metrics: ThroughputMetric[] }>(`/graylog/metrics`)
21 + getAlerts(query: AlertsQuery) {
22 + return HttpClient.post<FlaskBaseResponse & { alerts: Alerts }>(`/graylog/event/alerts`, query)
23 },
19 - getIndices() {
20 - return HttpClient.get<FlaskBaseResponse & { indexData: IndexData }>(`/graylog/indices`)
24 + getEventDefinitions() {
25 + return HttpClient.get<FlaskBaseResponse & { event_definitions: EventDefinition[] }>(
26 + `/graylog/event/definitions`
27 + )
28 },
22 - deleteIndex(indexName: string) {
23 - return HttpClient.delete<FlaskBaseResponse>(`/graylog/index`, {
24 - data: { index_name: indexName }
29 + getStreams() {
30 + return HttpClient.get<FlaskBaseResponse & { streams: Stream[]; total: number }>(`/graylog/streams`)
31 + },
32 + startStream(streamId: string) {
33 + return HttpClient.post<FlaskBaseResponse>(`/graylog/stream/start`, {
34 + stream_id: streamId
35 + })
36 + },
37 + stopStream(streamId: string) {
38 + return HttpClient.post<FlaskBaseResponse>(`/graylog/stream/stop`, {
39 + stream_id: streamId
40 })
41 },
42 + getInputs() {
43 + return HttpClient.get<
44 + FlaskBaseResponse & { configured_inputs: ConfiguredInput[]; running_inputs: RunningInput[] }
45 + >(`/graylog/inputs`)
46 + },
47 getInputsRunning() {
28 - return HttpClient.get<FlaskBaseResponse & { inputs: Inputs }>(`/graylog/inputs/running`)
48 + return HttpClient.get<FlaskBaseResponse & { configured_inputs: ConfiguredInput[] }>(`/graylog/inputs/running`)
49 },
50 getInputsConfigured() {
31 - return HttpClient.get<FlaskBaseResponse & { inputs: Inputs }>(`/graylog/inputs/configured`)
51 + return HttpClient.get<FlaskBaseResponse & { running_inputs: RunningInput[] }>(`/graylog/inputs/configured`)
52 },
53 startInput(inputId: string) {
34 - return HttpClient.put<FlaskBaseResponse>(`/graylog/inputs/${inputId}/start`)
54 + return HttpClient.post<FlaskBaseResponse>(`/graylog/input/start`, {
55 + input_id: inputId
56 + })
57 },
58 stopInput(inputId: string) {
37 - return HttpClient.delete<FlaskBaseResponse>(`/graylog/inputs/${inputId}/stop`)
59 + return HttpClient.post<FlaskBaseResponse>(`/graylog/input/stop`, {
60 + input_id: inputId
61 + })
62 },
39 - getInputState(inputId: string) {
40 - return HttpClient.get<FlaskBaseResponse & { state: InputState }>(`/graylog/inputs/${inputId}/state`)
63 + getMetrics() {
64 + return HttpClient.get<
65 + FlaskBaseResponse & { throughput_metrics: ThroughputMetric[]; uncommitted_journal_entries: number }
66 + >(`/graylog/metrics`)
67 },
42 - getStreams() {
43 - return HttpClient.get<FlaskBaseResponse & { streams: Streams }>(`/graylog/streams`)
68 + getPipelines() {
69 + return HttpClient.get<FlaskBaseResponse & { pipelines: Pipeline[] }>(`/graylog/pipelines`)
70 },
45 - stopStream(streamId: string) {
46 - return HttpClient.post<FlaskBaseResponse>(`/graylog/streams/${streamId}/pause`)
71 + getPipelinesFull() {
72 + return HttpClient.get<FlaskBaseResponse & { pipelines: PipelineFull[] }>(`/graylog/pipeline/full`)
73 },
48 - startStream(streamId: string) {
49 - return HttpClient.post<FlaskBaseResponse>(`/graylog/streams/${streamId}/resume`)
74 + getPipelinesRules() {
75 + return HttpClient.get<FlaskBaseResponse & { pipeline_rules: PipelineRule[] }>(`/graylog/pipeline/rules`)
76 + },
77 +
78 + // TODO: review --------------------------------------------------------------------
79 +
80 + getIndices() {
81 + return HttpClient.get<FlaskBaseResponse & { indices: IndexData }>(`/graylog/indices`)
82 + },
83 + deleteIndex(indexName: string) {
84 + return HttpClient.delete<FlaskBaseResponse>(`/graylog/index`, {
85 + data: { index_name: indexName }
86 + })
87 }
88 }
src/api/httpClient.ts
+9 -36
@@ -1,7 +1,8 @@
1 import { useAuthStore } from "@/stores/auth"
2 -import { isJwtExpiring } from "@/utils/auth"
3 -import Api from "@/api"
2 +import { isDebounceTimeOver, isJwtExpiring } from "@/utils/auth"
3 import axios from "axios"
4 +import { useGlobalActions } from "@/composables/useGlobalActions"
5 +
6 const BASE_URL = import.meta.env.VITE_API_URL
7
8 const HttpClient = axios.create({
@@ -9,8 +10,7 @@ const HttpClient = axios.create({
10 })
11
12 let __TOKEN_REFRESHING = false
12 -let __TOKEN_ATTEMPTS: number[] = []
13 -const TOKEN_MAX_ATTEMPTS = 3 // TODO: ?? replace with debounce time
13 +let __TOKEN_LAST_CHECK: Date | null = null
14
15 HttpClient.interceptors.request.use(
16 config => {
@@ -19,52 +19,25 @@ HttpClient.interceptors.request.use(
19 if (!config.headers) config.headers = {}
20 config.headers.Authorization = `Bearer ${store.userToken}`
21
22 - if (isJwtExpiring(store.userToken, 60 * 60) && !__TOKEN_REFRESHING) {
22 + if (isJwtExpiring(store.userToken, 60 * 60) && !__TOKEN_REFRESHING && isDebounceTimeOver(__TOKEN_LAST_CHECK)) {
23 __TOKEN_REFRESHING = true
24 - __TOKEN_ATTEMPTS.push(new Date().getTime())
25 -
26 - if (__TOKEN_ATTEMPTS.length >= TOKEN_MAX_ATTEMPTS) {
27 - window.location.href = "/logout"
28 - }
29 -
30 - Api.auth.refresh().then(res => {
31 - if (res.data.access_token) {
32 - store.setToken(res.data.access_token)
24 + __TOKEN_LAST_CHECK = new Date()
25
34 - __TOKEN_REFRESHING = false
35 - __TOKEN_ATTEMPTS = []
36 - }
26 + store.refreshToken().then(res => {
27 + __TOKEN_REFRESHING = false
28 })
38 -
39 - console.log("is expired")
29 }
30
42 - console.log(__TOKEN_ATTEMPTS, __TOKEN_REFRESHING)
31 return config
32 },
33 error => Promise.reject(error)
34 )
35
48 -// TODO: to complete
36 HttpClient.interceptors.response.use(
37 response => response,
38 error => {
52 - /*
53 - if (error.response) {
54 - if (error.response.status === 401 && !error.config.data?._retry) {
55 - if (!error.config.data) error.config.data = {}
56 - error.config.data._retry = true
57 -
58 - if (window.location.pathname.indexOf("login") === -1) {
59 - window.location.href = "/logout"
60 - }
61 - }
62 - }
63 - */
39 if (error.response && error.response.status === 401) {
65 - if (window.location.pathname.indexOf("login") === -1) {
66 - window.location.href = "/logout"
67 - }
40 + useGlobalActions().message("You are not authorized to access the resource", { type: "error" })
41 }
42
43 return Promise.reject(error)
src/assets/scss/_variables.scss
+2 -2
@@ -1,4 +1,4 @@
1 :root {
2 - --border-small-050: 1px solid rgba(var(--fg-color-rgb), 0.05);
3 - --border-small-100: 1px solid rgba(var(--fg-color-rgb), 0.1);
2 + --border-small-050: 1px solid var(--border-color);
3 + --border-small-100: 1px solid var(--divider-010-color);
4 }
src/assets/scss/common.scss
+4 -1
@@ -47,6 +47,7 @@
47 display: flex;
48 align-items: baseline;
49 justify-content: space-between;
50 + gap: 20px;
51
52 .title {
53 font-family: var(--font-family-display);
@@ -64,7 +65,8 @@
65 position: relative;
66 top: -2px;
67
67 - .n-icon {
68 + .n-icon,
69 + .n-icon-wrapper {
70 position: relative;
71 top: 3px;
72 }
@@ -73,6 +75,7 @@
75
76 @media (max-width: 700px) {
77 flex-direction: column;
78 + gap: 6px;
79
80 .title {
81 font-size: 20px;
src/assets/scss/helpers.scss new
+36
@@ -0,0 +1,36 @@
1 +.scrollbar-styled {
2 + /* Works on Firefox */
3 +
4 + scrollbar-width: thin;
5 + scrollbar-color: var(--hover-010-color) var(--bg-sidebar);
6 +
7 + /* Works on Chrome, Edge, and Safari */
8 + &::-webkit-scrollbar,
9 + ::-webkit-scrollbar {
10 + width: 12px;
11 + }
12 +
13 + &::-webkit-scrollbar-track,
14 + ::-webkit-scrollbar-track {
15 + background: var(--bg-sidebar);
16 + }
17 +
18 + &::-webkit-scrollbar-thumb,
19 + ::-webkit-scrollbar-thumb {
20 + background-color: var(--hover-010-color);
21 + border-radius: 20px;
22 + border: 2px solid var(--bg-sidebar);
23 + }
24 +}
25 +
26 +.bg-color {
27 + background-color: var(--bg-color);
28 +}
29 +
30 +.border-radius {
31 + border-radius: var(--border-radius);
32 +}
33 +
34 +.font-mono {
35 + font-family: var(--font-family-mono);
36 +}
src/assets/scss/index.scss
+17 -30
@@ -4,6 +4,7 @@
4
5 @import "_variables";
6 @import "common";
7 +@import "helpers";
8 @import "fonts";
9 @import "router-animations";
10 @import "naive-override";
@@ -38,8 +39,20 @@ body {
39 display: none !important;
40 }
41
42 +::view-transition-old(root),
43 +::view-transition-new(root) {
44 + animation: none;
45 + mix-blend-mode: normal;
46 +}
47 +::view-transition-old(root) {
48 + z-index: 1;
49 +}
50 +::view-transition-new(root) {
51 + z-index: 99999;
52 +}
53 +
54 ::selection {
42 - background-color: rgba(var(--primary-color-rgb), 0.2);
55 + background-color: var(--primary-020-color);
56 }
57
58 #app {
@@ -56,32 +69,6 @@ input {
69 accent-color: var(--primary-color);
70 }
71
59 -h1,
60 -h2,
61 -h3,
62 -h4,
63 -h5 {
64 - font-family: var(--font-family-display);
65 -}
66 -h1 {
67 - font-size: 30px;
68 -}
69 -h2 {
70 - font-size: 26px;
71 -}
72 -h3 {
73 - font-size: 22px;
74 -}
75 -h4 {
76 - font-size: 18px;
77 -}
78 -h5 {
79 - font-size: 14px;
80 -}
81 -h6 {
82 - font-size: 12px;
83 -}
84 -
72 p {
73 color: var(--fg-secondary-color);
74 }
@@ -96,7 +83,7 @@ pre {
83 code {
84 padding: 1px 6px;
85 border-radius: var(--border-radius-small);
99 - background-color: rgba(var(--fg-color-rgb), 0.05);
86 + background-color: var(--hover-005-color);
87 font-size: 13px;
88 }
89 pre {
@@ -117,7 +104,7 @@ a {
104 blockquote {
105 display: block;
106 padding-left: 1em;
120 - border-left: 4px solid rgba(var(--fg-color-rgb), 0.1);
107 + border-left: 4px solid var(--border-color);
108 }
109
110 dl {
@@ -160,6 +147,6 @@ ol {
147 mark {
148 padding: 2px 0px;
149 border-radius: var(--border-radius-small);
163 - background-color: rgba(var(--primary-color-rgb), 0.3);
150 + background-color: var(--primary-030-color);
151 color: var(--fg-color);
152 }
src/assets/scss/naive-override.scss
+10 -1
@@ -3,9 +3,10 @@
3 }
4 .n-modal,
5 .n-card.n-modal[role] {
6 - background-color: rgba(var(--modal-color-rgb), 0.7);
6 + //background-color: rgba(var(--modal-color-rgb), 0.7);
7 backdrop-filter: blur(20px);
8 max-width: 90%;
9 + margin: 10vh auto;
10 }
11
12 .n-image-preview-overlay {
@@ -45,3 +46,11 @@
46 max-height: 100%;
47 }
48 }
49 +.n-avatar .n-avatar__text {
50 + transform: translateX(-50%) translateY(-50%) scale(1);
51 +}
52 +
53 +// popover max-width
54 +.v-binder-follower-content {
55 + max-width: calc(100vw - (var(--view-padding) * 2));
56 +}
src/assets/scss/prosemirror-override.scss
+3 -3
@@ -30,7 +30,7 @@
30 }
31
32 code {
33 - background-color: rgba(var(--fg-color-rgb), 0.8);
33 + background-color: var(--fg-secondary-color);
34 padding: 3px 8px;
35 border-radius: var(--border-radius-small);
36 font-size: 13px;
@@ -66,13 +66,13 @@
66 }
67
68 blockquote {
69 - border-left: 2px solid rgba(var(--fg-color-rgb), 0.2);
69 + border-left: 2px solid var(--divider-020-color);
70 padding-left: 1rem;
71 }
72
73 hr {
74 border: none;
75 - border-top: 2px solid rgba(var(--fg-color-rgb), 0.2);
75 + border-top: 2px solid var(--divider-020-color);
76 margin: 2rem 0;
77 }
78
src/assets/scss/quill-override.scss
+25 -24
@@ -1,8 +1,7 @@
1 @import "@vueup/vue-quill/dist/vue-quill.snow.css";
2
3 .ql-snow.ql-toolbar {
4 - border: none;
5 - border: 1px solid rgba(var(--fg-color-rgb), 0.13);
4 + border: 1px solid var(--border-color);
5 border-radius: var(--border-radius);
6
7 .ql-picker {
@@ -10,14 +9,14 @@
9
10 .ql-picker-label {
11 &.ql-active {
13 - background-color: rgba(var(--primary-color-rgb), 0.05);
14 - color: rgba(var(--primary-color-rgb), 0.9);
12 + background-color: var(--primary-005-color);
13 + color: var(--primary-color);
14
15 .ql-fill {
17 - fill: rgba(var(--primary-color-rgb), 0.9);
16 + fill: var(--primary-color);
17 }
18 .ql-stroke {
20 - stroke: rgba(var(--primary-color-rgb), 0.9);
19 + stroke: var(--primary-color);
20 }
21 }
22
@@ -30,8 +29,8 @@
29 border-color: transparent;
30
31 &.ql-active {
33 - background-color: rgba(var(--primary-color-rgb), 0.05);
34 - color: rgba(var(--primary-color-rgb), 0.9);
32 + background-color: var(--primary-005-color);
33 + color: var(--primary-color);
34 }
35 }
36 .ql-picker-options {
@@ -49,25 +48,25 @@
48 }
49
50 &.ql-selected {
52 - background-color: rgba(var(--primary-color-rgb), 0.05);
53 - color: rgba(var(--primary-color-rgb), 0.9);
51 + background-color: var(--primary-005-color);
52 + color: var(--primary-color);
53 .ql-fill {
55 - fill: rgba(var(--primary-color-rgb), 0.9);
54 + fill: var(--primary-color);
55 }
56 .ql-stroke {
58 - stroke: rgba(var(--primary-color-rgb), 0.9);
57 + stroke: var(--primary-color);
58 }
59 }
60
61 &:hover {
63 - background-color: rgba(var(--fg-color-rgb), 0.1);
62 + background-color: var(--hover-010-color);
63 }
64 }
65 }
66 }
67
68 &:hover {
70 - background-color: rgba(var(--fg-color-rgb), 0.04);
69 + background-color: var(--hover-005-color);
70 }
71 }
72
@@ -75,33 +74,33 @@
74 border-radius: var(--border-radius-small);
75
76 &.ql-active {
78 - background-color: rgba(var(--primary-color-rgb), 0.05);
79 - color: rgba(var(--primary-color-rgb), 0.9);
77 + background-color: var(--primary-005-color);
78 + color: var(--primary-color);
79
80 .ql-fill {
82 - fill: rgba(var(--primary-color-rgb), 0.9);
81 + fill: var(--primary-color);
82 }
83 .ql-stroke {
85 - stroke: rgba(var(--primary-color-rgb), 0.9);
84 + stroke: var(--primary-color);
85 }
86 }
87
88 &:hover {
90 - background-color: rgba(var(--fg-color-rgb), 0.04);
89 + background-color: var(--hover-005-color);
90 }
91 }
92
93 .ql-stroke {
95 - stroke: rgba(var(--fg-color-rgb), 0.7);
94 + stroke: var(--fg-secondary-color);
95 }
96
97 .ql-fill {
99 - fill: rgba(var(--fg-color-rgb), 0.7);
98 + fill: var(--fg-secondary-color);
99 }
100
101 .ql-picker-options {
102 background-color: var(--bg-color);
104 - border: 1px solid rgba(var(--fg-color-rgb), 0.15) !important;
103 + border: 1px solid var(--border-color) !important;
104
105 .ql-stroke {
106 stroke: #000;
@@ -115,11 +114,13 @@
114
115 .ql-snow.ql-container {
116 flex-grow: 1;
118 - border: none !important;
117 height: inherit;
118 border-radius: var(--border-radius);
119 font-family: var(--font-family);
122 - background-color: rgba(var(--fg-color-rgb), 0.04);
120 + border: 1px solid var(--border-color);
121 margin-top: 10px;
122 font-size: 16px;
123 }
124 +.ql-toolbar.ql-snow + .ql-container.ql-snow {
125 + border-top: 1px solid var(--border-color);
126 +}
src/assets/scss/router-animations.scss
+9 -9
@@ -1,7 +1,7 @@
1 /* router-fade */
2 .router-fade-enter-active,
3 .router-fade-leave-active {
4 - transition: opacity 0.2s ease-in-out;
4 + transition: opacity var(--router-transition-duration) ease-in-out;
5 }
6 .router-fade-enter-from,
7 .router-fade-leave-to {
@@ -12,8 +12,8 @@
12 .router-fade-up-enter-active,
13 .router-fade-up-leave-active {
14 transition:
15 - opacity 0.2s ease-in-out,
16 - transform 0.3s ease-in-out;
15 + opacity var(--router-transition-duration) ease-in-out,
16 + transform var(--router-transition-duration) ease-in-out;
17 }
18 .router-fade-up-enter-from {
19 opacity: 0;
@@ -28,8 +28,8 @@
28 .router-fade-bottom-enter-active,
29 .router-fade-bottom-leave-active {
30 transition:
31 - opacity 0.2s ease-in-out,
32 - transform 0.3s ease-in-out;
31 + opacity var(--router-transition-duration) ease-in-out,
32 + transform var(--router-transition-duration) ease-in-out;
33 }
34 .router-fade-bottom-enter-from {
35 opacity: 0;
@@ -44,8 +44,8 @@
44 .router-fade-left-enter-active,
45 .router-fade-left-leave-active {
46 transition:
47 - opacity 0.2s ease-in-out,
48 - transform 0.3s ease-in-out;
47 + opacity var(--router-transition-duration) ease-in-out,
48 + transform var(--router-transition-duration) ease-in-out;
49 }
50 .router-fade-left-enter-from {
51 opacity: 0;
@@ -60,8 +60,8 @@
60 .router-fade-right-enter-active,
61 .router-fade-right-leave-active {
62 transition:
63 - opacity 0.2s ease-in-out,
64 - transform 0.3s ease-in-out;
63 + opacity var(--router-transition-duration) ease-in-out,
64 + transform var(--router-transition-duration) ease-in-out;
65 }
66 .router-fade-right-enter-from {
67 opacity: 0;
src/assets/scss/shepherd-override.scss
+2 -2
@@ -23,12 +23,12 @@
23 background-color: var(--bg-color);
24
25 .shepherd-button {
26 - background-color: rgba(var(--primary-color-rgb), 0.1);
26 + background-color: var(--primary-010-color);
27 color: var(--primary-color);
28 padding: 4px 16px;
29
30 &.shepherd-button-secondary {
31 - background-color: rgba(var(--fg-color-rgb), 0.05);
31 + background-color: var(--hover-005-color);
32 color: var(--fg-color);
33 }
34 }
src/assets/scss/vcalendar-override.scss
+6 -6
@@ -7,23 +7,23 @@
7 &.vc-dark .vc-attr,
8 &.vc-dark.vc-attr {
9 --vc-color: var(--fg-color);
10 - --vc-focus-ring: 0 0 0 2px rgba(var(--primary-color-rgb), 0.6);
10 + --vc-focus-ring: 0 0 0 2px var(--primary-060-color);
11 --vc-highlight-solid-content-color: var(--bg-color);
12 --vc-highlight-solid-bg: var(--primary-color);
13 --vc-nav-item-current-color: var(--primary-color);
14 --vc-nav-item-active-bg: var(--primary-color);
15 - --vc-highlight-solid-bg: rgba(var(--primary-color-rgb), 1);
16 - --vc-weekday-color: rgba(var(--fg-color-rgb), 0.4);
15 + --vc-highlight-solid-bg: var(--primary-color);
16 + --vc-weekday-color: var(--fg-secondary-color);
17 --vc-popover-content-bg: var(--bg-color);
18 - --vc-popover-content-border: rgba(var(--primary-color-rgb), 0.2);
18 + --vc-popover-content-border: var(--primary-020-color);
19 --vc-header-title-color: var(--fg-color);
20 --vc-header-arrow-color: var(--fg-color);
21 - --vc-day-content-hover-bg: rgba(var(--primary-color-rgb), 0.2);
21 + --vc-day-content-hover-bg: var(--primary-020-color);
22 --vc-header-arrow-hover-bg: transparent;
23 --vc-nav-title-color: var(--fg-color);
24 --vc-popover-content-color: var(--fg-color);
25 --vc-nav-item-active-box-shadow: none;
26 - --vc-nav-hover-bg: rgba(var(--primary-color-rgb), 0.2);
26 + --vc-nav-hover-bg: var(--primary-020-color);
27 --vc-font-family: var(--font-family);
28 --vc-text-lg: 16px;
29 }
src/assets/scss/vuesjv-override.scss new
+58
@@ -0,0 +1,58 @@
1 +.vuesjv-override {
2 + color: var(--fg-color);
3 + font-family: var(--font-family-mono);
4 + line-height: 1.7;
5 +
6 + .font-mono {
7 + font-family: var(--font-family-mono);
8 + }
9 +
10 + i {
11 + font-size: 1rem;
12 +
13 + &.i-mdi-content-copy {
14 + font-size: 0.85rem;
15 + }
16 + }
17 +
18 + .bg-gray-200 {
19 + background-color: var(--divider-010-color);
20 + }
21 + .c-gray-400 {
22 + color: var(--fg-secondary-color);
23 + }
24 + .c-indigo-600 {
25 + color: var(--secondary2-color);
26 + }
27 + .c-blue-600,
28 + .c-purple-600,
29 + .c-red-600,
30 + .c-pink-600,
31 + .c-blue-900,
32 + .c-blue-600,
33 + .c-amber-600 {
34 + color: var(--primary-color);
35 + font-weight: bold;
36 + }
37 + .hover\:bg-gray-100 {
38 + &:hover {
39 + background-color: transparent;
40 + }
41 + }
42 +
43 + .cursor-pointer {
44 + color: var(--fg-color);
45 + background-color: transparent;
46 + height: 18px;
47 + border-radius: 0;
48 +
49 + &:has(.i-mdi-content-copy) {
50 + width: 30px;
51 + }
52 +
53 + &:hover {
54 + color: var(--primary-color);
55 + background-color: transparent;
56 + }
57 + }
58 +}
src/components/AuthForm/ForgotPassword.vue renamed
src/components/AuthForm/SignIn.vue renamed
-1
@@ -45,7 +45,6 @@ import {
45 NButton
46 } from "naive-ui"
47 import { useAuthStore } from "@/stores/auth"
48 -import Api from "@/api"
48 import { useRouter } from "vue-router"
49 import type { LoginPayload } from "@/types/auth.d"
50
src/components/AuthForm/SignUp.vue renamed
+15 -25
@@ -42,9 +42,7 @@
42 :disabled="!accountStepValid"
43 >
44 <template #icon>
45 - <n-icon>
46 - <ArrowRightIcon />
47 - </n-icon>
45 + <Icon :name="ArrowRightIcon"></Icon>
46 </template>
47 Next
48 </n-button>
@@ -75,18 +73,14 @@
73 >
74 <n-button type="primary" @click="openCropper()" size="small">
75 <template #icon>
78 - <n-icon>
79 - <ImageIcon />
80 - </n-icon>
76 + <Icon :name="ImageIcon"></Icon>
77 </template>
78 {{ model.propic ? "Edit" : "Add" }} Photo
79 </n-button>
80 </ImageCropper>
81 <n-button @click="model.propic = ''" v-if="model.propic" size="small">
82 <template #icon>
87 - <n-icon>
88 - <RemoveImageIcon />
89 - </n-icon>
83 + <Icon :name="RemoveImageIcon"></Icon>
84 </template>
85 Remove Photo
86 </n-button>
@@ -121,9 +115,7 @@
115 <div class="flex items-center justify-between mt-3 gap-3">
116 <n-button @click="wizardCurrent = 1" size="large">
117 <template #icon>
124 - <n-icon>
125 - <ArrowLeftIcon />
126 - </n-icon>
118 + <Icon :name="ArrowLeftIcon"></Icon>
119 </template>
120 Back
121 </n-button>
@@ -135,9 +127,7 @@
127 :disabled="!accountStepValid || !detailsStepValid"
128 >
129 <template #icon>
138 - <n-icon>
139 - <UserAddIcon />
140 - </n-icon>
130 + <Icon :name="UserAddIcon"></Icon>
131 </template>
132 Create account
133 </n-button>
@@ -163,21 +153,16 @@ import {
153 NButton,
154 NSteps,
155 NStep,
166 - NIcon,
156 NAvatar,
157 NSpin,
158 type FormItemRule
159 } from "naive-ui"
171 -import ArrowRightIcon from "@vicons/carbon/ArrowRight"
172 -import ArrowLeftIcon from "@vicons/carbon/ArrowLeft"
173 -import ImageIcon from "@vicons/carbon/Image"
174 -import RemoveImageIcon from "@vicons/carbon/NoImage"
175 -import UserAddIcon from "@vicons/carbon/UserAdmin"
160 import isEmail from "validator/es/lib/isEmail"
161 import ImageCropper, { type ImageCropperResult } from "@/components/common/ImageCropper.vue"
162 import passwordValidator from "password-validator"
163 import Api from "@/api"
164 import type { RegisterPayload } from "@/types/auth.d"
165 +import Icon from "@/components/common/Icon.vue"
166
167 interface ModelType {
168 email: string
@@ -192,6 +177,12 @@ interface ModelType {
177 */
178 }
179
180 +const ArrowRightIcon = "carbon:arrow-right"
181 +const ArrowLeftIcon = "carbon:arrow-left"
182 +const ImageIcon = "carbon:image"
183 +const RemoveImageIcon = "carbon:no-image"
184 +const UserAddIcon = "carbon:user-admin"
185 +
186 const emit = defineEmits<{
187 (e: "goto-signin"): void
188 }>()
@@ -229,8 +220,8 @@ passwordSchema
220 .lowercase() // Must have lowercase letters
221 .has()
222 .digits(1) // Must have at least 1 digit
232 -//.has()
233 -//.symbols(1) // Must have at least 1 symbol
223 + .has()
224 + .symbols(1) // Must have at least 1 symbol
225
226 const rules: FormRules = {
227 email: [
@@ -258,8 +249,7 @@ const rules: FormRules = {
249 return !!passwordSchema.validate(value, { details: false })
250 },
251 message:
261 - "The string should have a minimum length of 8 characters, minimum of 1 uppercase and lowercase letter and minimum of 1 digit",
262 - //"The string should have a minimum length of 8 characters, minimum of 1 uppercase and lowercase letter, minimum of 1 digit and 1 symbol",
252 + "The string should have a minimum length of 8 characters, minimum of 1 uppercase and lowercase letter, minimum of 1 digit and 1 symbol",
253 trigger: ["blur"]
254 }
255 ],
src/components/AuthForm/index.vue renamed
src/components/LayoutSettings/index.ts deleted
-2
@@ -1,2 +0,0 @@
1 -import LayoutSettings from "./LayoutSettings.vue"
2 -export default LayoutSettings
src/components/agents/AgentCard.vue
+11 -7
@@ -21,7 +21,7 @@
21 @click.stop="toggleCritical(agent.agent_id, agent.critical_asset)"
22 >
23 <template #icon>
24 - <n-icon><StarIcon /></n-icon>
24 + <Icon :name="StarIcon"></Icon>
25 </template>
26 </n-button>
27 </template>
@@ -44,7 +44,7 @@
44 <template #trigger>
45 <n-button quaternary circle type="error" @click.stop="handleDelete">
46 <template #icon>
47 - <n-icon><DeleteIcon /></n-icon>
47 + <Icon :name="DeleteIcon"></Icon>
48 </template>
49 </n-button>
50 </template>
@@ -59,11 +59,14 @@
59 <script setup lang="ts">
60 import { computed, ref, toRefs } from "vue"
61 import { type Agent } from "@/types/agents.d"
62 -import dayjs from "dayjs"
62 +import dayjs from "@/utils/dayjs"
63 import { handleDeleteAgent, isAgentOnline, toggleAgentCritical } from "./utils"
64 -import StarIcon from "@vicons/carbon/Star"
65 -import DeleteIcon from "@vicons/carbon/Delete"
66 -import { NTooltip, NButton, NIcon, NSpin, NCard, useMessage, useDialog } from "naive-ui"
64 +import { NTooltip, NButton, NSpin, NCard, useMessage, useDialog } from "naive-ui"
65 +import Icon from "@/components/common/Icon.vue"
66 +import { useSettingsStore } from "@/stores/settings"
67 +
68 +const StarIcon = "carbon:star"
69 +const DeleteIcon = "ph:trash"
70
71 const emit = defineEmits<{
72 (e: "delete"): void
@@ -75,6 +78,7 @@ const props = defineProps<{
78 }>()
79 const { agent, showActions } = toRefs(props)
80
81 +const dFormats = useSettingsStore().dateFormat
82 const loading = ref(false)
83 const message = useMessage()
84 const dialog = useDialog()
@@ -85,7 +89,7 @@ const formatLastSeen = computed(() => {
89 const lastSeenDate = dayjs(agent.value.wazuh_last_seen)
90 if (!lastSeenDate.isValid()) return agent.value.wazuh_last_seen
91
88 - return lastSeenDate.format("DD/MM/YYYY @ HH:mm")
92 + return lastSeenDate.format(dFormats.datetime)
93 })
94
95 function handleDelete() {
src/components/agents/AgentToolbar.vue
+5 -3
@@ -5,7 +5,7 @@
5 <div class="agent-search flex gap-3">
6 <n-input placeholder="Search for an agent" clearable v-model:value="textFilter">
7 <template #prefix>
8 - <n-icon :component="SearchIcon" />
8 + <Icon :name="SearchIcon" />
9 </template>
10 </n-input>
11 <n-button @click="emit('sync')" :loading="syncing">Sync</n-button>
@@ -61,8 +61,10 @@
61 <script setup lang="ts">
62 import { computed, toRefs } from "vue"
63 import { type Agent } from "@/types/agents.d"
64 -import SearchIcon from "@vicons/carbon/Search"
65 -import { NInput, NButton, NIcon, NCard, NScrollbar } from "naive-ui"
64 +import { NInput, NButton, NCard, NScrollbar } from "naive-ui"
65 +import Icon from "@/components/common/Icon.vue"
66 +
67 +const SearchIcon = "carbon:search"
68
69 const emit = defineEmits<{
70 (e: "sync"): void
src/components/agents/OverviewSection.vue
+5 -2
@@ -11,15 +11,18 @@
11
12 <script setup lang="ts">
13 import { computed, toRefs } from "vue"
14 -import dayjs from "dayjs"
14 +import dayjs from "@/utils/dayjs"
15 import { type Agent } from "@/types/agents.d"
16 import { NCard } from "naive-ui"
17 +import { useSettingsStore } from "@/stores/settings"
18
19 const props = defineProps<{
20 agent: Agent
21 }>()
22 const { agent } = toRefs(props)
23
24 +const dFormats = useSettingsStore().dateFormat
25 +
26 const propsSanitized = computed(() => {
27 const obj = []
28 for (const key in agent.value) {
@@ -39,7 +42,7 @@ const formatDate = (date: string) => {
42 const datejs = dayjs(date)
43 if (!datejs.isValid()) return date
44
42 - return datejs.format("DD/MM/YYYY @ HH:mm")
45 + return datejs.format(dFormats.datetime)
46 }
47 </script>
48
src/components/agents/VulnerabilityCard.vue
+15 -10
@@ -10,7 +10,7 @@
10 <span>Detection time</span>
11 <template #trigger>
12 <div>
13 - <n-icon><ClockIcon /></n-icon>
13 + <Icon :name="ClockIcon"></Icon>
14 <span>{{ detectionTime }}</span>
15 </div>
16 </template>
@@ -21,7 +21,7 @@
21 <span>{{ `CVSS2: ${vulnerability.cvss2_score} - CVSS3: ${vulnerability.cvss3_score}` }}</span>
22 <template #trigger>
23 <div>
24 - <n-icon><CounterIcon /></n-icon>
24 + <Icon :name="CounterIcon"></Icon>
25 <span>{{ vulnerability.cve }}</span>
26 </div>
27 </template>
@@ -32,7 +32,7 @@
32 <span>{{ `Version: ${vulnerability.version}` }}</span>
33 <template #trigger>
34 <div>
35 - <n-icon><VulnerabilityIcon /></n-icon>
35 + <Icon :name="VulnerabilityIcon"></Icon>
36 <span>{{ vulnerability.name }}</span>
37 </div>
38 </template>
@@ -69,18 +69,23 @@
69 <script setup lang="ts">
70 import { computed, ref, toRefs } from "vue"
71 import { type AgentVulnerabilities } from "@/types/agents.d"
72 -import dayjs from "dayjs"
72 +import dayjs from "@/utils/dayjs"
73 import { cloneDeep } from "lodash"
74 -import { NModal, NIcon, NTooltip, NCard } from "naive-ui"
75 -import ClockIcon from "@vicons/carbon/Time"
76 -import CounterIcon from "@vicons/material/ScoreboardOutlined"
77 -import VulnerabilityIcon from "@vicons/material/CrisisAlertTwotone"
74 +import { NModal, NTooltip, NCard } from "naive-ui"
75 +import Icon from "@/components/common/Icon.vue"
76 +import { useSettingsStore } from "@/stores/settings"
77 +
78 +const ClockIcon = "carbon:time"
79 +const CounterIcon = "mdi:counter"
80 +const VulnerabilityIcon = "bi:radioactive"
81
82 const props = defineProps<{
83 vulnerability: AgentVulnerabilities
84 }>()
85 const { vulnerability } = toRefs(props)
86
87 +const dFormats = useSettingsStore().dateFormat
88 +
89 const vulnerabilitySanitized = computed(() => {
90 const newObj = []
91 const obj: { [key: string]: any } = cloneDeep(vulnerability.value)
@@ -88,7 +93,7 @@ const vulnerabilitySanitized = computed(() => {
93 if (typeof obj[k] === "string") {
94 const maybeTime = dayjs(obj[k])
95 if (maybeTime.isValid()) {
91 - obj[k] = maybeTime.format("DD/MM/YYYY @ HH:mm")
96 + obj[k] = maybeTime.format(dFormats.datetime)
97 }
98 }
99
@@ -105,7 +110,7 @@ const detectionTime = computed(() => {
110 const detection_time = dayjs(vulnerability.value.detection_time)
111 if (!detection_time.isValid()) return vulnerability.value.detection_time
112
108 - return detection_time.format("DD/MM/YYYY @ HH:mm")
113 + return detection_time.format(dFormats.datetime)
114 })
115
116 const showDialog = ref(false)
src/components/agents/utils.ts
+1 -1
@@ -1,4 +1,4 @@
1 -import dayjs from "dayjs"
1 +import dayjs from "@/utils/dayjs"
2 import Api from "@/api"
3 import { type Agent } from "@/types/agents.d"
4 import type { MessageApiInjection } from "naive-ui/es/message/src/MessageProvider"
src/components/apps/FullCalendar/EventEditor.vue
+1 -1
@@ -78,8 +78,8 @@ import {
78 NPopconfirm,
79 type FormValidationError
80 } from "naive-ui"
81 +import type { CalendarEditEvent } from "@/mock/fullcalendar"
82 import { useFullCalendarStore } from "@/stores/apps/useFullCalendarStore"
82 -import { type CalendarEditEvent } from "@/views/Apps/Calendars/FullCalendar.vue"
83
84 defineOptions({
85 name: "EventEditor"
src/components/apps/Kanban/TaskCard.vue
+4 -10
@@ -2,9 +2,7 @@
2 <div class="task-card flex flex-col justify-between">
3 <div class="task-header flex justify-between gap-3">
4 <div class="task-title">{{ task.title }}</div>
5 - <n-icon :size="20" class="pan-area" v-if="mobile">
6 - <PanIcon />
7 - </n-icon>
5 + <Icon :size="20" class="pan-area" v-if="mobile" :name="PanIcon"></Icon>
6 </div>
7 <div class="task-footer flex justify-between items-end">
8 <span class="task-date">{{ task.dateText }}</span>
@@ -19,16 +17,12 @@
17 </div>
18 </template>
19 <script lang="ts" setup>
22 -import { NIcon } from "naive-ui"
23 -import PanIcon from "@vicons/carbon/Move"
20 +import Icon from "@/components/common/Icon.vue"
21 +const PanIcon = "carbon:move"
22 import { type Task } from "@/mock/kanban"
23 import { toRefs, computed } from "vue"
24 import { useThemeStore } from "@/stores/theme"
25
28 -defineOptions({
29 - name: "TaskCard"
30 -})
31 -
26 const props = defineProps<{
27 task: Task
28 mobile: boolean
@@ -93,7 +87,7 @@ const labelsColors = {
87 }
88
89 &:hover {
96 - transform: translateY(-1px);
90 + border-color: var(--primary-color);
91 }
92 }
93 </style>
src/components/apps/Mailbox/ComposeView.vue
+26 -16
@@ -2,9 +2,7 @@
2 <div class="compose-view flex flex-col">
3 <div class="compose-view-toolbar flex items-center">
4 <n-button text @click="goBack()">
5 - <n-icon :size="24">
6 - <ArrowLeftIcon />
7 - </n-icon>
5 + <Icon :size="24" :name="ArrowLeftIcon"></Icon>
6 </n-button>
7 <span class="compose-view-title">Compose message</span>
8 </div>
@@ -30,21 +28,19 @@
28 </n-form>
29 <div class="compose-view-attachments flex justify-end">
30 <n-button ghost>
33 - <n-icon :size="16" class="mr-2">
34 - <DocumentAddIcon />
35 - </n-icon>
31 + <Icon :size="16" class="mr-2" :name="DocumentAddIcon"></Icon>
32
33 Add attachment
34 </n-button>
35 </div>
36 <div class="compose-view-body grow flex flex-col">
41 - <QuillEditor theme="snow" toolbar="minimal" @blur="resetScroll()" />
37 + <QuillEditor v-if="mounted" theme="snow" toolbar="minimal" @blur="resetScroll()" />
38 </div>
39 <div class="compose-view-footer flex justify-end">
40 <n-button-group>
41 <n-button type="primary" ghost>
42 <template #icon>
47 - <n-icon><SentIcon /></n-icon>
43 + <Icon :name="SentIcon"></Icon>
44 </template>
45 Send
46 </n-button>
@@ -63,7 +59,7 @@
59 >
60 <n-button type="primary" ghost>
61 <template #icon>
66 - <n-icon><ChevronDownIcon /></n-icon>
62 + <Icon :name="ChevronDownIcon"></Icon>
63 </template>
64 </n-button>
65 </n-dropdown>
@@ -74,14 +70,15 @@
70 </template>
71
72 <script setup lang="ts">
77 -import { NIcon, NInput, NAutoComplete, NInputGroup, NForm, NFormItem, NButton, NButtonGroup, NDropdown } from "naive-ui"
78 -import SentIcon from "@vicons/carbon/Send"
79 -import ArrowLeftIcon from "@vicons/carbon/ArrowLeft"
80 -import ChevronDownIcon from "@vicons/carbon/ChevronDown"
81 -import DocumentAddIcon from "@vicons/carbon/DocumentAdd"
82 -import { computed, toRefs } from "vue"
73 +import { NInput, NAutoComplete, NInputGroup, NForm, NFormItem, NButton, NButtonGroup, NDropdown } from "naive-ui"
74 +import Icon from "@/components/common/Icon.vue"
75 +
76 +const SentIcon = "carbon:send"
77 +const ArrowLeftIcon = "carbon:arrow-left"
78 +const ChevronDownIcon = "carbon:chevron-down"
79 +const DocumentAddIcon = "carbon:document-add"
80 +import { computed, toRefs, ref, defineAsyncComponent, type Component, onMounted } from "vue"
81 import { type Email } from "@/mock/mailbox"
84 -import { QuillEditor } from "@vueup/vue-quill"
82 import "@/assets/scss/quill-override.scss"
83
84 defineOptions({
@@ -97,6 +94,15 @@ const emit = defineEmits<{
94 (e: "back"): void
95 }>()
96
97 +const mounted = ref(false)
98 +
99 +const QuillEditor = defineAsyncComponent<Component>(() => {
100 + return (async () => {
101 + const { QuillEditor } = await import("@vueup/vue-quill")
102 + return QuillEditor
103 + })()
104 +})
105 +
106 function goBack() {
107 emit("back")
108 }
@@ -114,6 +120,10 @@ const autoCompleteOptions = computed(() => {
120 function resetScroll() {
121 window.scrollTo(0, 0)
122 }
123 +
124 +onMounted(() => {
125 + mounted.value = true
126 +})
127 </script>
128
129 <style lang="scss" scoped>
src/components/apps/Mailbox/Email.vue
+28 -36
@@ -5,10 +5,8 @@
5 </div>
6 <div class="starred flex" :class="{ 'opacity-50': !email.starred }">
7 <n-button text @click.stop="toggleStar(email)">
8 - <n-icon :size="16">
9 - <StarActiveIcon v-if="email.starred" :color="primaryColor" />
10 - <StarIcon v-else />
11 - </n-icon>
8 + <Icon :size="16" :name="StarActiveIcon" v-if="email.starred" :color="primaryColor"></Icon>
9 + <Icon :size="16" :name="StarIcon" v-else></Icon>
10 </n-button>
11 </div>
12 <div class="avatar flex">
@@ -23,47 +21,45 @@
21 </span>
22 </div>
23 <div class="labels flex">
26 - <n-icon :size="16" v-for="label of email.labels" :key="label.id">
27 - <LabelIcon :color="labelsColors[label.id]" />
28 - </n-icon>
24 + <Icon
25 + :size="16"
26 + v-for="label of email.labels"
27 + :key="label.id"
28 + :color="labelsColors[label.id]"
29 + :name="LabelIcon"
30 + ></Icon>
31 </div>
32 <div class="attachments flex" v-if="email.attachments.length">
31 - <n-icon :size="16">
32 - <AttachmentIcon />
33 - </n-icon>
33 + <Icon :size="16" :name="AttachmentIcon"></Icon>
34 </div>
35 <div class="date opacity-70">
36 {{ email.dateText }}
37 </div>
38 <div class="actions opacity-70 flex items-start gap-3">
39 <n-button text>
40 - <n-icon :size="20">
41 - <TrashIcon />
42 - </n-icon>
40 + <Icon :size="20" :name="TrashIcon"></Icon>
41 </n-button>
42 <n-button text>
45 - <n-icon :size="20">
46 - <LabelOutIcon />
47 - </n-icon>
43 + <Icon :size="20" :name="LabelOutIcon"></Icon>
44 </n-button>
45 <n-button text>
50 - <n-icon :size="20">
51 - <FolderIcon />
52 - </n-icon>
46 + <Icon :size="20" :name="FolderIcon"></Icon>
47 </n-button>
48 </div>
49 </div>
50 </template>
51
52 <script setup lang="ts">
59 -import { NIcon, NCheckbox, NAvatar, NButton } from "naive-ui"
60 -import StarActiveIcon from "@vicons/carbon/StarFilled"
61 -import StarIcon from "@vicons/carbon/Star"
62 -import TrashIcon from "@vicons/carbon/TrashCan"
63 -import LabelIcon from "@vicons/carbon/BookmarkFilled"
64 -import LabelOutIcon from "@vicons/carbon/Bookmark"
65 -import AttachmentIcon from "@vicons/carbon/Attachment"
66 -import FolderIcon from "@vicons/carbon/FolderMoveTo"
53 +import { NCheckbox, NAvatar, NButton } from "naive-ui"
54 +import Icon from "@/components/common/Icon.vue"
55 +
56 +const StarActiveIcon = "carbon:star-filled"
57 +const StarIcon = "carbon:star"
58 +const TrashIcon = "carbon:trash-can"
59 +const LabelIcon = "carbon:bookmark-filled"
60 +const LabelOutIcon = "carbon:bookmark"
61 +const AttachmentIcon = "carbon:attachment"
62 +const FolderIcon = "carbon:folder-move-to"
63 import { useMailboxStore } from "@/stores/apps/useMailboxStore"
64 import { type Email } from "@/mock/mailbox"
65 import { toRefs, computed } from "vue"
@@ -111,13 +107,13 @@ function toggleStar(email: Email) {
107 .email {
108 height: 52px;
109 padding: 0 30px;
114 - border-block-end: var(--border-small-050);
110 + border-bottom: var(--border-small-050);
111 gap: 18px;
112 line-height: 1.2;
113 white-space: nowrap;
114 cursor: pointer;
115 opacity: 0;
120 - transition: all 0.25s ease-in;
116 + transition: all 0.1s ease-in;
117 animation: email-fade 0.3s forwards;
118 container-type: inline-size;
119
@@ -134,7 +130,6 @@ function toggleStar(email: Email) {
130 font-size: 15px;
131
132 .name {
137 - //font-weight: bold;
133 margin-right: 14px;
134 }
135 .subject {
@@ -147,7 +142,7 @@ function toggleStar(email: Email) {
142 }
143
144 &.seen {
150 - background-color: var(--bg-sidebar);
145 + background-color: var(--bg-secondary-color);
146 .title {
147 opacity: 0.85;
148 .subject {
@@ -157,14 +152,11 @@ function toggleStar(email: Email) {
152 }
153
154 &.selected {
160 - background-color: rgba(var(--primary-color-rgb), 0.05);
155 + background-color: var(--primary-005-color);
156 }
157
158 &:hover {
164 - background-color: rgba(var(--bg-color-rgb), 0.03);
165 - border-bottom-color: transparent;
166 - box-shadow: 0px 1px 8px -4px rgba(var(--fg-color-rgb), 0.6);
167 - transform: translateY(-1px);
159 + box-shadow: 0px 0px 0px 1px var(--primary-050-color) inset;
160
161 .actions {
162 display: flex;
src/components/apps/Mailbox/EmailView.vue
+31 -59
@@ -2,17 +2,13 @@
2 <div class="email-view flex flex-col">
3 <div class="email-view-toolbar flex items-center">
4 <n-button text @click="goBack()">
5 - <n-icon :size="24">
6 - <ArrowLeftIcon />
7 - </n-icon>
5 + <Icon :size="24" :name="ArrowLeftIcon" />
6 </n-button>
7 <div class="actions-btns flex items-center gap-2">
8 <n-tooltip>
9 <template #trigger>
10 <n-button text>
13 - <n-icon :size="20">
14 - <TrashIcon />
15 - </n-icon>
11 + <Icon :size="20" :name="TrashIcon" />
12 </n-button>
13 </template>
14 <span>Delete</span>
@@ -20,9 +16,7 @@
16 <n-tooltip>
17 <template #trigger>
18 <n-button text>
23 - <n-icon :size="20">
24 - <LabelOutIcon />
25 - </n-icon>
19 + <Icon :size="20" :name="LabelOutIcon" />
20 </n-button>
21 </template>
22 <span>Add label</span>
@@ -30,9 +24,7 @@
24 <n-tooltip>
25 <template #trigger>
26 <n-button text>
33 - <n-icon :size="20">
34 - <FolderIcon />
35 - </n-icon>
27 + <Icon :size="20" :name="FolderIcon" />
28 </n-button>
29 </template>
30 <span>Move to folder</span>
@@ -40,9 +32,7 @@
32 <n-tooltip>
33 <template #trigger>
34 <n-button text>
43 - <n-icon :size="20">
44 - <PrinterIcon />
45 - </n-icon>
35 + <Icon :size="20" :name="PrinterIcon" />
36 </n-button>
37 </template>
38 <span>Print</span>
@@ -50,10 +40,8 @@
40 <n-tooltip>
41 <template #trigger>
42 <n-button text @click.stop="toggleStar(email)">
53 - <n-icon :size="20">
54 - <StarActiveIcon v-if="email.starred" :color="primaryColor" />
55 - <StarIcon v-else />
56 - </n-icon>
43 + <Icon :size="20" :name="StarActiveIcon" v-if="email.starred" :color="primaryColor" />
44 + <Icon :size="20" :name="StarIcon" v-else />
45 </n-button>
46 </template>
47 <span>Star</span>
@@ -62,41 +50,29 @@
50 <div class="menu-btns flex items-center">
51 <n-dropdown :options="menuOptions">
52 <n-button text>
65 - <n-icon :size="24">
66 - <MenuHorizontalIcon />
67 - </n-icon>
53 + <Icon :size="24" :name="MenuHorizontalIcon" />
54 </n-button>
55 </n-dropdown>
56 </div>
57 <div class="grow"></div>
58 <div class="reply-btns flex items-center gap-2">
59 <n-button text>
74 - <n-icon :size="20">
75 - <ReplyIcon />
76 - </n-icon>
60 + <Icon :size="20" :name="ReplyIcon" />
61 </n-button>
62 <n-button text>
79 - <n-icon :size="20">
80 - <ReplyAllIcon />
81 - </n-icon>
63 + <Icon :size="20" :name="ReplyAllIcon" />
64 </n-button>
65 <n-button text>
84 - <n-icon :size="20">
85 - <ForwardIcon />
86 - </n-icon>
66 + <Icon :size="20" :name="ForwardIcon" />
67 </n-button>
68 </div>
69 <div class="nav-btns flex items-center gap-2">
70 <span class="opacity-70">1 - 30 of 635</span>
71 <n-button text size="small">
92 - <n-icon :size="24">
93 - <ChevronLeftIcon />
94 - </n-icon>
72 + <Icon :size="24" :name="ChevronLeftIcon" />
73 </n-button>
74 <n-button text size="small">
97 - <n-icon :size="24">
98 - <ChevronRightIcon />
99 - </n-icon>
75 + <Icon :size="24" :name="ChevronRightIcon" />
76 </n-button>
77 </div>
78 </div>
@@ -137,9 +113,7 @@
113 <div class="email-view-attachments flex flex-wrap" v-if="email.attachments.length">
114 <div class="attachment-item flex" v-for="attachment of email.attachments" :key="attachment.name">
115 <div class="attachment-icon">
140 - <n-icon :size="26">
141 - <FileIcon />
142 - </n-icon>
116 + <Icon :size="26" :name="FileIcon" />
117 </div>
118 <div class="attachment-info">
119 <div class="attachment-name">{{ attachment.name }}</div>
@@ -153,30 +127,28 @@
127 </template>
128
129 <script setup lang="ts">
156 -import { NIcon, NScrollbar, NAvatar, NButton, NTime, NTooltip, NDropdown } from "naive-ui"
157 -import StarActiveIcon from "@vicons/carbon/StarFilled"
158 -import StarIcon from "@vicons/carbon/Star"
159 -import TrashIcon from "@vicons/carbon/TrashCan"
160 -import LabelOutIcon from "@vicons/carbon/Bookmark"
161 -import MenuHorizontalIcon from "@vicons/carbon/OverflowMenuHorizontal"
162 -import FolderIcon from "@vicons/carbon/FolderMoveTo"
163 -import ArrowLeftIcon from "@vicons/carbon/ArrowLeft"
164 -import ChevronLeftIcon from "@vicons/carbon/ChevronLeft"
165 -import ChevronRightIcon from "@vicons/carbon/ChevronRight"
166 -import PrinterIcon from "@vicons/carbon/Printer"
167 -import FileIcon from "@vicons/tabler/FileInvoice"
168 -import ReplyAllIcon from "@vicons/fluent/ArrowReplyAll20Filled"
169 -import ReplyIcon from "@vicons/fluent/ArrowReply20Filled"
170 -import ForwardIcon from "@vicons/fluent/ArrowForward20Filled"
130 +import { NScrollbar, NAvatar, NButton, NTime, NTooltip, NDropdown } from "naive-ui"
131 +import Icon from "@/components/common/Icon.vue"
132 import { useMailboxStore } from "@/stores/apps/useMailboxStore"
133 import { toRefs, computed } from "vue"
134 import { type Email } from "@/mock/mailbox"
135 import { renderIcon } from "@/utils"
136 import { useThemeStore } from "@/stores/theme"
137
177 -defineOptions({
178 - name: "EmailView"
179 -})
138 +const StarActiveIcon = "carbon:star-filled"
139 +const StarIcon = "carbon:star"
140 +const TrashIcon = "carbon:trash-can"
141 +const LabelOutIcon = "carbon:bookmark"
142 +const MenuHorizontalIcon = "carbon:overflow-menu-horizontal"
143 +const FolderIcon = "carbon:folder-move-to"
144 +const ArrowLeftIcon = "carbon:arrow-left"
145 +const ChevronLeftIcon = "carbon:chevron-left"
146 +const ChevronRightIcon = "carbon:chevron-right"
147 +const PrinterIcon = "carbon:printer"
148 +const FileIcon = "tabler:file-invoice"
149 +const ReplyAllIcon = "fluent:arrow-reply-all-20-filled"
150 +const ReplyIcon = "fluent:arrow-reply-20-filled"
151 +const ForwardIcon = "fluent:arrow-forward-20-filled"
152
153 const props = defineProps<{
154 email: Email
@@ -335,7 +307,7 @@ const menuOptions = [
307 gap: 20px;
308
309 .attachment-item {
338 - background-color: rgba(var(--primary-color-rgb), 0.1);
310 + background-color: var(--primary-010-color);
311 padding: 14px;
312 border-radius: var(--border-radius);
313 max-width: 100%;
src/components/cards/CardActions.vue
+10 -10
@@ -9,9 +9,7 @@
9 </template>
10 <template #header-extra>
11 <n-dropdown :options="menuOptions" placement="bottom-end" @select="menuSelect" v-if="!hideMenu">
12 - <n-icon :size="20" class="ml-3">
13 - <MenuIcon />
14 - </n-icon>
12 + <Icon :size="20" :name="MenuIcon" class="ml-3" />
13 </n-dropdown>
14 </template>
15 <template #default>
@@ -32,21 +30,23 @@
30
31 <script setup lang="ts">
32 import { faker } from "@faker-js/faker"
35 -import { NCard, NIcon, NDropdown, NScrollbar } from "naive-ui"
36 -import MenuIcon from "@vicons/carbon/OverflowMenuVertical"
37 -import ContractIcon from "@vicons/fluent/ContractDownLeft24Regular"
38 -import ExpandIcon from "@vicons/fluent/ExpandUpRight24Regular"
39 -import ReloadIcon from "@vicons/tabler/Refresh"
33 +import { NCard, NDropdown, NScrollbar } from "naive-ui"
34 +import Icon from "@/components/common/Icon.vue"
35 import { computed, toRefs, onMounted, ref } from "vue"
36 import { renderIcon } from "@/utils"
37
38 +const MenuIcon = "carbon:overflow-menu-vertical"
39 +const ContractIcon = "fluent:contract-down-left-24-regular"
40 +const ExpandIcon = "fluent:expand-up-right-24-regular"
41 +const ReloadIcon = "tabler:refresh"
42 +
43 const props = defineProps<{
44 showImage?: boolean
45 hideSubtitle?: boolean
46 actionBoxTransparent?: boolean
47 hideMenu?: boolean
48 - reload?: (state: boolean) => {}
49 - expand?: (state: boolean) => {}
48 + reload?: (state: boolean) => void
49 + expand?: (state: boolean) => void
50 isExpand?: () => boolean
51 title?: string
52 image?: string
src/components/cards/CardCodeExample.vue
+5 -4
@@ -9,7 +9,7 @@
9 <template #action v-if="$slots.html || $slots.js || $slots.css || $slots.code">
10 <n-collapse>
11 <template #header-extra>
12 - <n-icon><CodeIcon /></n-icon>
12 + <Icon :name="CodeIcon"></Icon>
13 </template>
14 <n-collapse-item title="Code" name="code">
15 <div class="code-container">
@@ -40,11 +40,12 @@
40
41 <script setup lang="ts">
42 import { hljs, resetIndent } from "@/directives/v-hl"
43 -import { NCollapse, NCollapseItem, NCard, NScrollbar, NIcon } from "naive-ui"
44 -import CodeIcon from "@vicons/carbon/Code"
43 +import { NCollapse, NCollapseItem, NCard, NScrollbar } from "naive-ui"
44 import { ref } from "vue"
45 +import Icon from "@/components/common/Icon.vue"
46
47 type LangType = "html" | "js" | "css"
48 +const CodeIcon = "carbon:code"
49
50 const refHTML = ref<HTMLElement | null>(null)
51 const refJS = ref<HTMLElement | null>(null)
@@ -109,7 +110,7 @@ function css(code: string) {
110 margin: 15px 0;
111
112 .label {
112 - background-color: rgba(var(--fg-color-rgb), 0.1);
113 + background-color: var(--hover-010-color);
114 opacity: 0.5;
115 display: inline-block;
116 padding: 4px 6px;
src/components/cards/CardWrapper.vue
+2 -2
@@ -18,11 +18,11 @@ import { ref } from "vue"
18 const showModal = ref(false)
19 const showSpin = ref(false)
20
21 -function expand(state: boolean) {
21 +function expand(state: boolean): void {
22 showModal.value = state
23 }
24
25 -function reload(state: boolean) {
25 +function reload(state: boolean): void {
26 showSpin.value = state
27 }
28
src/components/cards/basic/CardBasic5.vue
+13 -21
@@ -13,21 +13,11 @@
13 </template>
14 <template #action>
15 <div class="flex justify-around">
16 - <n-icon :size="20">
17 - <CheckIcon />
18 - </n-icon>
19 - <n-icon :size="20">
20 - <StarIcon />
21 - </n-icon>
22 - <n-icon :size="20">
23 - <ShieldIcon />
24 - </n-icon>
25 - <n-icon :size="20">
26 - <PremiumIcon />
27 - </n-icon>
28 - <n-icon :size="20">
29 - <EcoIcon />
30 - </n-icon>
16 + <Icon :size="20" :name="CheckIcon"></Icon>
17 + <Icon :size="20" :name="StarIcon"></Icon>
18 + <Icon :size="20" :name="ShieldIcon"></Icon>
19 + <Icon :size="20" :name="PremiumIcon"></Icon>
20 + <Icon :size="20" :name="EcoIcon"></Icon>
21 </div>
22 </template>
23 </n-card>
@@ -35,12 +25,14 @@
25
26 <script setup lang="ts">
27 import { faker } from "@faker-js/faker"
38 -import { NCard, NIcon } from "naive-ui"
39 -import CheckIcon from "@vicons/fluent/CheckmarkStarburst16Regular"
40 -import StarIcon from "@vicons/fluent/Star16Regular"
41 -import ShieldIcon from "@vicons/fluent/ShieldKeyhole16Regular"
42 -import PremiumIcon from "@vicons/fluent/Premium24Regular"
43 -import EcoIcon from "@vicons/material/EcoOutlined"
28 +import { NCard } from "naive-ui"
29 +import Icon from "@/components/common/Icon.vue"
30 +
31 +const CheckIcon = "fluent:checkmark-starburst-16-regular"
32 +const StarIcon = "fluent:star-16-regular"
33 +const ShieldIcon = "fluent:shield-keyhole-16-regular"
34 +const PremiumIcon = "fluent:premium-24-regular"
35 +const EcoIcon = "material-symbols:eco-outline"
36
37 const title = faker.lorem.sentence({ min: 2, max: 5 })
38 const text = faker.lorem.paragraph()
src/components/cards/basic/CardBasic6.vue
+6 -6
@@ -3,9 +3,7 @@
3 <template #header>
4 <div class="flex items-center justify-between !text-white">
5 <span>Alt Background</span>
6 - <n-icon class="ml-3 icon-ring">
7 - <ColorIcon />
8 - </n-icon>
6 + <Icon class="ml-3 icon-ring" :name="ColorIcon"></Icon>
7 </div>
8 </template>
9 <div class="!text-white">
@@ -19,13 +17,15 @@
17 </n-card>
18 </template>
19 <script setup lang="ts">
22 -import { NCard, NIcon } from "naive-ui"
23 -import ColorIcon from "@vicons/ionicons5/ColorFillOutline"
20 +import { NCard } from "naive-ui"
21 +import Icon from "@/components/common/Icon.vue"
22 +
23 +const ColorIcon = "ion:color-fill-outline"
24 </script>
25
26 <style scoped lang="scss">
27 .icon-ring {
28 - background-color: rgba(var(--fg-color-rgb), 0.05);
28 + background-color: var(--hover-005-color);
29 width: 30px;
30 height: 30px;
31 border-radius: 50%;
src/components/cards/combo/CardCombo1.vue
+8 -7
@@ -11,9 +11,7 @@
11 {{ currentTitle ?? title }}
12 </span>
13 <span class="hint flex items-center" v-if="!isHovered">
14 - <n-icon :size="12">
15 - <InfoIcon />
16 - </n-icon>
14 + <Icon :size="12" :name="InfoIcon"></Icon>
15 <span class="ml-2">hover to see details</span>
16 </span>
17 </div>
@@ -21,7 +19,7 @@
19 </div>
20 </div>
21 <div class="chart-box" :style="{ height: chartHeight + 'px' }" :class="`type-${type}`">
24 - <apexchart :type="type" height="100%" :options="chartOptions" :series="series" ref="chart"></apexchart>
22 + <Apex :type="type" height="100%" :options="chartOptions" :series="series" ref="chart"></Apex>
23 </div>
24 </div>
25 </n-card>
@@ -29,16 +27,19 @@
27
28 <script setup lang="ts">
29 import { faker } from "@faker-js/faker"
32 -import { NCard, NIcon } from "naive-ui"
30 +import { NCard } from "naive-ui"
31 import { ref, watch, toRefs, computed } from "vue"
34 -import InfoIcon from "@vicons/carbon/Information"
32 import { onClickOutside, useElementHover } from "@vueuse/core"
33 import dayjs from "@/utils/dayjs"
34 import { useThemeStore } from "@/stores/theme"
35 +import Apex from "@/components/charts/Apex.vue"
36 +import Icon from "@/components/common/Icon.vue"
37
38 type ChartData = [number, number][]
39 type ChartType = "area" | "bar"
40
41 +const InfoIcon = "carbon:information"
42 +
43 const props = withDefaults(
44 defineProps<{
45 type?: ChartType
@@ -61,7 +62,7 @@ const chart = ref()
62 const isHovered = useElementHover(chart)
63 const hoveredTimer = ref<NodeJS.Timeout | null>(null)
64
64 -const style: { [key: string]: any } = computed(() => useThemeStore().style)
65 +const style = computed<{ [key: string]: any }>(() => useThemeStore().style)
66
67 const data = ref<ChartData>([])
68
src/components/cards/combo/CardCombo3.vue
+16 -17
@@ -39,9 +39,7 @@
39 secondary
40 :type="series.active ? 'default' : 'tertiary'"
41 >
42 - <n-icon :size="12" :color="series.color">
43 - <DotIcon />
44 - </n-icon>
42 + <Icon :size="12" :color="series.color" :name="DotIcon"></Icon>
43 <span class="ml-2">
44 {{ series.name }}
45 </span>
@@ -56,9 +54,7 @@
54 ]"
55 >
56 <n-button secondary>
59 - <n-icon :size="14">
60 - <TimeIcon />
61 - </n-icon>
57 + <Icon :size="14" :name="TimeIcon"></Icon>
58 <span class="ml-2">
59 {{ capitalized(chartTypeValue) }}
60 </span>
@@ -85,14 +81,15 @@
81 </template>
82
83 <script setup lang="ts">
88 -import { NCard, NSpin, NButton, NIcon, NPopselect } from "naive-ui"
84 +import { NCard, NSpin, NButton, NPopselect } from "naive-ui"
85 import { useThemeStore } from "@/stores/theme"
86 import dayjs from "@/utils/dayjs"
91 -import { computed, ref, toRefs } from "vue"
92 -import DemoChart, { type DataType } from "@/components/charts/Apex.vue"
93 -import DotIcon from "@vicons/carbon/CircleSolid"
94 -import TimeIcon from "@vicons/carbon/Time"
95 -import { type VueApexChartsComponent } from "vue3-apexcharts"
87 +import { computed, ref, toRefs, onMounted } from "vue"
88 +import DemoChart, { type DataType, type VueApexChartsComponent } from "@/components/charts/DemoApex.vue"
89 +import Icon from "@/components/common/Icon.vue"
90 +
91 +const DotIcon = "carbon:circle-solid"
92 +const TimeIcon = "carbon:time"
93
94 interface ChartSeries {
95 active: boolean
@@ -110,7 +107,7 @@ const props = withDefaults(
107 const { oneSeries } = toRefs(props)
108
109 const twoSeries = computed(() => !oneSeries.value)
113 -const style: { [key: string]: any } = computed(() => useThemeStore().style)
110 +const style = computed<{ [key: string]: any }>(() => useThemeStore().style)
111 const textSecondaryColor = computed<string>(() => style.value["--fg-secondary-color"])
112 const loaded = ref(false)
113
@@ -134,7 +131,7 @@ function toggleSeries(series: ChartSeries) {
131
132 function getSeries() {
133 const chartColors: string[] = chartCTX.value?.options?.colors || []
137 - chartSeries.value = chartCTX.value?.series.map((s: any, index: number) => {
134 + chartSeries.value = (chartCTX.value?.series || []).map((s: any, index: number) => {
135 return {
136 active: true,
137 name: s.name,
@@ -150,9 +147,11 @@ function capitalized(text: string) {
147 return capitalizedFirst + rest
148 }
149
153 -setTimeout(() => {
154 - loaded.value = true
155 -}, 400)
150 +onMounted(() => {
151 + setTimeout(() => {
152 + loaded.value = true
153 + }, 1000)
154 +})
155 </script>
156
157 <style scoped lang="scss">
src/components/cards/combo/CardCombo5.vue
+18 -21
@@ -15,9 +15,7 @@
15 }"
16 >
17 <template #icon>
18 - <CardComboIcon boxed>
19 - <ActiveUsersIcon />
20 - </CardComboIcon>
18 + <CardComboIcon boxed :iconName="ActiveUsersIcon" />
19 </template>
20 </CardCombo4>
21
@@ -32,9 +30,11 @@
30 }"
31 >
32 <template #icon>
35 - <CardComboIcon boxed :color="style['--secondary4-color']">
36 - <CanceledUsersIcon />
37 - </CardComboIcon>
33 + <CardComboIcon
34 + boxed
35 + :color="style['--secondary4-color']"
36 + :iconName="CanceledUsersIcon"
37 + ></CardComboIcon>
38 </template>
39 </CardCombo4>
40
@@ -49,9 +49,11 @@
49 }"
50 >
51 <template #icon>
52 - <CardComboIcon boxed :color="style['--secondary3-color']">
53 - <AFKUsersIcon />
54 - </CardComboIcon>
52 + <CardComboIcon
53 + boxed
54 + :color="style['--secondary3-color']"
55 + :iconName="AFKUsersIcon"
56 + ></CardComboIcon>
57 </template>
58 </CardCombo4>
59 </div>
@@ -60,14 +62,7 @@
62 <div class="title">Users target</div>
63
64 <div class="chart overflow-hidden">
63 - <apexchart
64 - type="radialBar"
65 - height="270"
66 - width="270"
67 - :options="chartOptions"
68 - :series="series"
69 - ref="chart"
70 - ></apexchart>
65 + <Apex type="radialBar" height="270" width="270" :options="chartOptions" :series="series"></Apex>
66 </div>
67 </div>
68 </div>
@@ -77,13 +72,15 @@
72 <script setup lang="ts">
73 import { NCard } from "naive-ui"
74 import { ref, computed, watchEffect } from "vue"
80 -import ActiveUsersIcon from "@vicons/carbon/Activity"
81 -import CanceledUsersIcon from "@vicons/carbon/TrashCan"
82 -import AFKUsersIcon from "@vicons/carbon/Pause"
75 import { useResizeObserver } from "@vueuse/core"
76 +import Apex from "@/components/charts/Apex.vue"
77 import { useThemeStore } from "@/stores/theme"
78
86 -const style: { [key: string]: any } = computed(() => useThemeStore().style)
79 +const ActiveUsersIcon = "carbon:activity"
80 +const CanceledUsersIcon = "carbon:trash-can"
81 +const AFKUsersIcon = "carbon:pause"
82 +
83 +const style = computed<{ [key: string]: any }>(() => useThemeStore().style)
84 const secondaryColors = computed(() => Object.values(useThemeStore().secondaryColors))
85 const palette = computed<string[]>(() => new Array(4).fill(secondaryColors.value).flat())
86 const trigger = ref(null)
src/components/cards/combo/CardComboIcon.vue
+6 -4
@@ -1,21 +1,23 @@
1 <template>
2 <div class="icon" :class="{ boxed }" :style="`--size:${boxSize}px`">
3 <div class="bg" v-if="boxed"></div>
4 - <n-icon :size="iconFinalSize">
4 + <Icon :size="iconFinalSize" v-if="$slots.default">
5 <slot></slot>
6 - </n-icon>
6 + </Icon>
7 + <Icon :size="iconFinalSize" :name="iconName" v-else></Icon>
8 </div>
9 </template>
10
11 <script setup lang="ts">
11 -import { NIcon } from "naive-ui"
12 import { toRefs, computed } from "vue"
13 import { useThemeStore } from "@/stores/theme"
14 +import Icon from "@/components/common/Icon.vue"
15
16 const props = withDefaults(
17 defineProps<{
18 boxSize?: number
19 iconSize?: number
20 + iconName?: string
21 boxed?: boolean
22 color?: string
23 }>(),
@@ -23,7 +25,7 @@ const props = withDefaults(
25 )
26 const { boxed, boxSize, iconSize, color } = toRefs(props)
27
26 -const style: { [key: string]: any } = computed(() => useThemeStore().style)
28 +const style = computed<{ [key: string]: any }>(() => useThemeStore().style)
29
30 const iconColor = computed(() => color?.value || style.value["--primary-color"])
31 const iconBoxedSize = computed(() => (boxSize.value / 100) * 45)
src/components/cards/ecommerce/CardEcommerce1.vue
+9 -13
@@ -8,12 +8,8 @@
8 <span class="ml-3 opacity-70">$644,00</span>
9 </template>
10 <template #header-extra>
11 - <n-icon class="mr-3" :size="20" color="#FF0156">
12 - <HeartIcon />
13 - </n-icon>
14 - <n-icon :size="20">
15 - <ShareIcon />
16 - </n-icon>
11 + <Icon class="mr-3" :size="20" color="#FF0156" :name="HeartIcon"></Icon>
12 + <Icon :size="20" :name="ShareIcon"></Icon>
13 </template>
14 <template #default>
15 <p>
@@ -32,9 +28,7 @@
28 <div class="flex items-center justify-between">
29 <n-button type="primary" quaternary>Details</n-button>
30 <n-button type="primary">
35 - <n-icon class="mr-3">
36 - <CartIcon />
37 - </n-icon>
31 + <Icon class="mr-3" :name="CartIcon"></Icon>
32 Add to cart
33 </n-button>
34 </div>
@@ -43,8 +37,10 @@
37 </template>
38
39 <script setup lang="ts">
46 -import { NCard, NButton, NIcon } from "naive-ui"
47 -import CartIcon from "@vicons/tabler/ShoppingCart"
48 -import HeartIcon from "@vicons/ionicons5/Heart"
49 -import ShareIcon from "@vicons/carbon/Share"
40 +import { NCard, NButton } from "naive-ui"
41 +import Icon from "@/components/common/Icon.vue"
42 +
43 +const CartIcon = "tabler:shopping-cart"
44 +const HeartIcon = "ion:heart"
45 +const ShareIcon = "carbon:share"
46 </script>
src/components/cards/ecommerce/CardEcommerce2.vue
+9 -13
@@ -11,12 +11,8 @@
11 <span class="ml-3 opacity-70">$399,00</span>
12 </div>
13 <div class="flex items-center">
14 - <n-icon class="mr-3" :size="20">
15 - <HeartIcon />
16 - </n-icon>
17 - <n-icon :size="20">
18 - <ShareIcon />
19 - </n-icon>
14 + <Icon class="mr-3" :size="20" :name="HeartIcon"></Icon>
15 + <Icon :size="20" :name="ShareIcon"></Icon>
16 </div>
17 </div>
18 <div class="card-content grow">
@@ -39,9 +35,7 @@
35 <div class="flex items-center justify-between">
36 <n-button type="primary" quaternary>Details</n-button>
37 <n-button type="primary">
42 - <n-icon class="mr-3">
43 - <CartIcon />
44 - </n-icon>
38 + <Icon class="mr-3" :name="CartIcon"></Icon>
39 Add to cart
40 </n-button>
41 </div>
@@ -52,10 +46,12 @@
46 </template>
47
48 <script setup lang="ts">
55 -import { NCard, NButton, NIcon, NRate } from "naive-ui"
56 -import CartIcon from "@vicons/tabler/ShoppingCart"
57 -import HeartIcon from "@vicons/ionicons5/HeartOutline"
58 -import ShareIcon from "@vicons/carbon/Share"
49 +import { NCard, NButton, NRate } from "naive-ui"
50 +import Icon from "@/components/common/Icon.vue"
51 +
52 +const CartIcon = "tabler:shopping-cart"
53 +const HeartIcon = "ion:heart-outline"
54 +const ShareIcon = "carbon:share"
55 </script>
56
57 <style scoped lang="scss">
src/components/cards/ecommerce/CardEcommerce4.vue
+18 -29
@@ -16,21 +16,11 @@
16 <p v-html="text"></p>
17 <div class="divider"></div>
18 <div class="features flex justify-around">
19 - <n-icon :size="20">
20 - <CheckIcon />
21 - </n-icon>
22 - <n-icon :size="20">
23 - <StarIcon />
24 - </n-icon>
25 - <n-icon :size="20">
26 - <ShieldIcon />
27 - </n-icon>
28 - <n-icon :size="20">
29 - <PremiumIcon />
30 - </n-icon>
31 - <n-icon :size="20">
32 - <EcoIcon />
33 - </n-icon>
19 + <Icon :size="20" :name="CheckIcon"></Icon>
20 + <Icon :size="20" :name="StarIcon"></Icon>
21 + <Icon :size="20" :name="ShieldIcon"></Icon>
22 + <Icon :size="20" :name="PremiumIcon"></Icon>
23 + <Icon :size="20" :name="EcoIcon"></Icon>
24 </div>
25 </div>
26 </div>
@@ -54,9 +44,7 @@
44 </div>
45 </div>
46 <n-button type="primary">
57 - <n-icon class="mr-3">
58 - <PremiumIcon />
59 - </n-icon>
47 + <Icon class="mr-3" :name="PremiumIcon"></Icon>
48 Subscribe now
49 </n-button>
50 </div>
@@ -66,14 +54,16 @@
54
55 <script setup lang="ts">
56 import { faker } from "@faker-js/faker"
69 -import { NCard, NButton, NIcon, NRate, NRadioGroup, NRadioButton } from "naive-ui"
70 -import CheckIcon from "@vicons/fluent/CheckmarkStarburst16Regular"
71 -import StarIcon from "@vicons/fluent/Star16Regular"
72 -import ShieldIcon from "@vicons/fluent/ShieldKeyhole16Regular"
73 -import PremiumIcon from "@vicons/fluent/Premium24Regular"
74 -import EcoIcon from "@vicons/material/EcoOutlined"
57 +import { NCard, NButton, NRate, NRadioGroup, NRadioButton } from "naive-ui"
58 +import Icon from "@/components/common/Icon.vue"
59 import { ref } from "vue"
60
61 +const CheckIcon = "fluent:checkmark-starburst-16-regular"
62 +const StarIcon = "fluent:star-16-regular"
63 +const ShieldIcon = "fluent:shield-keyhole-16-regular"
64 +const PremiumIcon = "fluent:premium-24-regular"
65 +const EcoIcon = "material-symbols:eco-outline"
66 +
67 const subscription = ref("monthly")
68
69 const title = faker.lorem.sentence({ min: 2, max: 4 })
@@ -108,14 +98,13 @@ const text = faker.lorem.sentences(2, "<br/><br/>") + faker.lorem.paragraph()
98 background-clip: padding-box;
99 background-color: var(--n-action-color);
100 padding: var(--n-padding-bottom) var(--n-padding-left);
111 - border-bottom-left-radius: var(--n-border-radius);
112 - border-bottom-right-radius: var(--n-border-radius);
101 }
102
103 .divider {
116 - background-color: rgba(var(--fg-color-rgb), 0.2);
117 - width: 80%;
118 - margin: 20px auto;
104 + background-color: var(--border-color);
105 + margin: 20px 0;
106 + margin-left: calc(var(--n-padding-left) * -1);
107 + margin-right: calc(var(--n-padding-left) * -1);
108 height: 1px;
109 }
110 }
src/components/cards/extra/CardExtra3.vue
+1 -1
@@ -18,7 +18,7 @@ import { Calendar } from "v-calendar"
18 import "v-calendar/style.css"
19 import "@/assets/scss/vcalendar-override.scss"
20 import { useThemeStore } from "@/stores/theme"
21 -import type { AttributeConfig } from "v-calendar/dist/types/src/utils/attribute"
21 +import { type AttributeConfig } from "v-calendar/dist/types/src/utils/attribute.d"
22 import dayjs from "@/utils/dayjs"
23 import { faker } from "@faker-js/faker"
24
src/components/cards/extra/CardExtra5.vue
+11 -23
@@ -11,9 +11,7 @@
11 <n-timeline>
12 <n-timeline-item :content="text1" line-type="dashed">
13 <template #icon>
14 - <n-icon :size="8">
15 - <DotIcon />
16 - </n-icon>
14 + <Icon :size="8" :name="DotIcon"></Icon>
15 </template>
16 </n-timeline-item>
17 <n-timeline-item type="success" :content="text2" :time="date1">
@@ -21,16 +19,12 @@
19 <n-tag type="success" size="small">Success</n-tag>
20 </template>
21 <template #icon>
24 - <n-icon :size="8">
25 - <DotIcon />
26 - </n-icon>
22 + <Icon :size="8" :name="DotIcon"></Icon>
23 </template>
24 </n-timeline-item>
25 <n-timeline-item type="error" :content="text3" :time="date2">
26 <template #icon>
31 - <n-icon :size="8">
32 - <DotIcon />
33 - </n-icon>
27 + <Icon :size="8" :name="DotIcon"></Icon>
28 </template>
29 </n-timeline-item>
30 <n-timeline-item type="warning" :content="text4" :time="date3">
@@ -38,9 +32,7 @@
32 <n-tag type="warning" size="small">Warning</n-tag>
33 </template>
34 <template #icon>
41 - <n-icon :size="8">
42 - <DotIcon />
43 - </n-icon>
35 + <Icon :size="8" :name="DotIcon"></Icon>
36 </template>
37 </n-timeline-item>
38 <n-timeline-item
@@ -54,9 +46,7 @@
46 <n-tag :type="item.type" size="small" v-if="item.title">{{ item.title }}</n-tag>
47 </template>
48 <template #icon>
57 - <n-icon :size="8">
58 - <DotIcon />
59 - </n-icon>
49 + <Icon :size="8" :name="DotIcon"></Icon>
50 </template>
51 </n-timeline-item>
52 <n-timeline-item
@@ -70,16 +60,12 @@
60 <n-tag type="info" size="small">Info</n-tag>
61 </template>
62 <template #icon>
73 - <n-icon :size="8">
74 - <DotIcon />
75 - </n-icon>
63 + <Icon :size="8" :name="DotIcon"></Icon>
64 </template>
65 </n-timeline-item>
66 <n-timeline-item :content="text6">
67 <template #icon>
80 - <n-icon :size="8">
81 - <DotIcon />
82 - </n-icon>
68 + <Icon :size="8" :name="DotIcon"></Icon>
69 </template>
70 </n-timeline-item>
71 </n-timeline>
@@ -95,10 +81,12 @@
81 import dayjs from "@/utils/dayjs"
82 import { faker } from "@faker-js/faker"
83 import _capitalize from "lodash/capitalize"
98 -import { NCard, NTimeline, NTimelineItem, NScrollbar, NSpin, NIcon, NTag } from "naive-ui"
84 +import { NCard, NTimeline, NTimelineItem, NScrollbar, NSpin, NTag } from "naive-ui"
85 import { toRefs, ref } from "vue"
86 import { useResizeObserver } from "@vueuse/core"
101 -import DotIcon from "@vicons/carbon/CircleSolid"
87 +import Icon from "@/components/common/Icon.vue"
88 +
89 +const DotIcon = "carbon:circle-solid"
90
91 type TimelineType = "default" | "success" | "error" | "info" | "warning" | undefined
92
src/components/cards/extra/CardExtra6.vue
+10 -10
@@ -2,9 +2,7 @@
2 <n-card hoverable content-style="padding: 0;" :title="`Last ${tableRows || 5} orders`" class="overflow-hidden">
3 <template #header-extra>
4 <n-dropdown :options="menuOptions" placement="bottom-end" @select="menuSelect">
5 - <n-icon :size="20" class="ml-3">
6 - <MenuIcon />
7 - </n-icon>
5 + <Icon :size="20" :name="MenuIcon" class="ml-3" />
6 </n-dropdown>
7 </template>
8 <template #default>
@@ -18,22 +16,24 @@
16 </template>
17
18 <script setup lang="ts">
21 -import { NCard, NIcon, NDropdown, NScrollbar } from "naive-ui"
19 +import { NCard, NDropdown, NScrollbar } from "naive-ui"
20 import TableBase from "@/components/tables/Base.vue"
23 -import MenuIcon from "@vicons/carbon/OverflowMenuVertical"
24 -import ExpandIcon from "@vicons/fluent/ExpandUpRight24Regular"
25 -import ContractIcon from "@vicons/fluent/ContractDownLeft24Regular"
26 -import ReloadIcon from "@vicons/tabler/Refresh"
21 +import Icon from "@/components/common/Icon.vue"
22 import { renderIcon } from "@/utils"
23 import { computed, onMounted, ref, toRefs } from "vue"
24
25 +const MenuIcon = "carbon:overflow-menu-vertical"
26 +const ExpandIcon = "fluent:expand-up-right-24-regular"
27 +const ContractIcon = "fluent:contract-down-left-24-regular"
28 +const ReloadIcon = "tabler:refresh"
29 +
30 const props = defineProps<{
31 showActions?: boolean
32 showDate?: boolean
33 minWidth?: number
34 tableRows?: number
35 - reload?: (state: boolean) => {}
36 - expand?: (state: boolean) => {}
35 + reload?: (state: boolean) => void
36 + expand?: (state: boolean) => void
37 isExpand?: () => boolean
38 }>()
39 const { showActions, showDate, minWidth, reload, expand, isExpand } = toRefs(props)
src/components/cards/extra/CardExtra7.vue
+4 -6
@@ -8,7 +8,7 @@
8 "
9 >
10 <n-spin :show="loading">
11 - <div style="height: 250px; width: 100%; overflow: hidden">
11 + <div style="height: 250px; width: calc(100% - 4px); margin: 0 auto; overflow: hidden">
12 <vuevectormap
13 v-if="!loading"
14 ref="map"
@@ -26,18 +26,17 @@
26
27 <script setup lang="ts">
28 import { NCard, NSpin } from "naive-ui"
29 -// Import your preferred map
30 -import "jsvectormap/dist/maps/world-merc"
29
30 import { computed, ref, watchEffect, watch } from "vue"
31 import { useResizeObserver, useWindowSize } from "@vueuse/core"
32 import { useThemeStore } from "@/stores/theme"
33
36 -const style: { [key: string]: any } = computed(() => useThemeStore().style)
34 +const style = computed<{ [key: string]: any }>(() => useThemeStore().style)
35
36 function getOption() {
37 return {
38 map: "world_merc",
39 + showTooltip: false,
40 bindTouchEvents: false,
41 zoomButtons: false,
42 zoomOnScroll: false,
@@ -74,8 +73,7 @@ function getOption() {
73 return marker.name
74 }
75 }
77 - },
78 - showTooltip: true
76 + }
77 }
78 }
79
src/components/cards/social/CardSocial1.vue
+13 -17
@@ -31,17 +31,13 @@
31 :class="{ active: commentActive }"
32 @click="commentActive = !commentActive"
33 >
34 - <n-icon :size="18">
35 - <CommentsActiveIcon v-if="commentActive" />
36 - <CommentsIcon v-else />
37 - </n-icon>
34 + <Icon :size="18" v-if="commentActive" :name="CommentsActiveIcon"></Icon>
35 + <Icon :size="18" v-else :name="CommentsIcon"></Icon>
36 <span class="count">{{ commentsCount }}</span>
37 </n-button>
38 <n-button text class="item likes" :class="{ active: likeActive }" @click="likeActive = !likeActive">
41 - <n-icon :size="18">
42 - <HeartActiveIcon v-if="likeActive" />
43 - <HeartIcon v-else />
44 - </n-icon>
39 + <Icon :size="18" v-if="likeActive" :name="HeartActiveIcon"></Icon>
40 + <Icon :size="18" v-else :name="HeartIcon"></Icon>
41 <span class="count">{{ likesCount }}</span>
42 </n-button>
43 </div>
@@ -80,9 +76,7 @@
76 </div>
77 <div class="actions-group flex items-center">
78 <n-button text type="primary" :disabled="!reply" @click="send()">
83 - <n-icon :size="20">
84 - <SendIcon />
85 - </n-icon>
79 + <Icon :size="20" :name="SendIcon"></Icon>
80 </n-button>
81 </div>
82 </div>
@@ -91,14 +85,16 @@
85
86 <script setup lang="ts">
87 import { faker } from "@faker-js/faker"
94 -import { NCard, NIcon, NAvatar, NInput, NButton, NTime, NImage } from "naive-ui"
95 -import SendIcon from "@vicons/carbon/Send"
96 -import HeartIcon from "@vicons/ionicons5/HeartOutline"
97 -import HeartActiveIcon from "@vicons/ionicons5/Heart"
98 -import CommentsIcon from "@vicons/ionicons5/ChatbubblesOutline"
99 -import CommentsActiveIcon from "@vicons/ionicons5/Chatbubbles"
88 +import { NCard, NAvatar, NInput, NButton, NTime, NImage } from "naive-ui"
89 import { toRefs, ref } from "vue"
90 import dayjs from "@/utils/dayjs"
91 +import Icon from "@/components/common/Icon.vue"
92 +
93 +const SendIcon = "carbon:send"
94 +const HeartIcon = "ion:heart-outline"
95 +const HeartActiveIcon = "ion:heart"
96 +const CommentsIcon = "ion:chatbubbles-outline"
97 +const CommentsActiveIcon = "ion:chatbubbles"
98
99 export interface CardSocial {
100 showImage?: boolean
src/components/charts/Apex.vue
+51 -249
@@ -1,269 +1,71 @@
1 <template>
2 <apexchart
3 - ref="chart"
4 - width="100%"
5 - height="100%"
3 + :width="width"
4 + :height="height"
5 + v-if="ready"
6 :type="type"
7 :options="options"
8 :series="series"
9 - :class="{ 'time-buttons': timeButtons }"
10 - :style="legendOffset && `--legend-offset:${legendOffset}px`"
9 + ref="chart"
10 ></apexchart>
11 </template>
12
13 <script lang="ts" setup>
14 import { useThemeStore } from "@/stores/theme"
16 -import { computed, onMounted, toRefs, watch, watchEffect } from "vue"
17 -import { ref } from "vue"
18 -import { type VueApexChartsComponent } from "vue3-apexcharts"
19 -import { getAreaOpts, getBarOpts, getMonthsSeries, getWeekSeries, getYearsSeries } from "./data"
20 -import { getChartColors, getHighlightMap } from "./utils"
21 -
22 -export type DataType = "years" | `years-${number}` | "months" | "week"
23 -export type ChartsType = "area" | "bar"
15 +import { toRefs, ref, onMounted, nextTick } from "vue"
16 +
17 +export interface VueApexChartsComponent {
18 + type?:
19 + | "line"
20 + | "area"
21 + | "bar"
22 + | "histogram"
23 + | "pie"
24 + | "donut"
25 + | "radialBar"
26 + | "rangeBar"
27 + | "scatter"
28 + | "bubble"
29 + | "heatmap"
30 + | "candlestick"
31 + | "radar"
32 + | "polarArea"
33 + updateSeries(newSeries: any, animate?: boolean): Promise<void>
34 + refresh(): Promise<void>
35 +}
36
25 -export type ChartCTX = VueApexChartsComponent
37 +const props = defineProps<{
38 + width?: string | number
39 + height?: string | number
40 + type?: VueApexChartsComponent["type"]
41 + options?: any
42 + series?: any
43 +}>()
44 +const { width, height, type, options, series } = toRefs(props)
45
46 const emit = defineEmits<{
47 (e: "mounted", value: VueApexChartsComponent): void
48 }>()
49
31 -const props = withDefaults(
32 - defineProps<{
33 - type: ChartsType
34 - dataType?: DataType
35 - dark?: boolean
36 - highlight?: boolean
37 - colorsRandom?: boolean
38 - colorsSecondary?: boolean
39 - color?: string
40 - fontColor?: string
41 - strokeWidth?: number
42 - legendOffset?: number
43 - seriesList?: string[]
44 - timeButtons?: boolean
45 - hideLegend?: boolean
46 - hideXaxisLabels?: boolean
47 - }>(),
48 - {
49 - dark: undefined,
50 - dataType: "years",
51 - highlight: false,
52 - colorsRandom: false,
53 - colorsSecondary: false,
54 - timeButtons: false,
55 - hideLegend: false,
56 - hideXaxisLabels: false,
57 - seriesList: () => ["Trend"]
58 - }
59 -)
60 -const {
61 - type,
62 - dark,
63 - dataType,
64 - highlight,
65 - colorsRandom,
66 - colorsSecondary,
67 - color,
68 - fontColor,
69 - strokeWidth,
70 - seriesList,
71 - timeButtons,
72 - legendOffset,
73 - hideLegend,
74 - hideXaxisLabels
75 -} = toRefs(props)
76 -
77 -const chart = ref<VueApexChartsComponent>()
78 -
79 -const isThemeDark = computed(() => useThemeStore().isThemeDark)
80 -const style: { [key: string]: any } = computed(() => useThemeStore().style)
81 -
82 -const customButtons = [
83 - {
84 - icon: "years",
85 - title: "years view",
86 - class: "custom-icon",
87 - click: function () {
88 - setData("years")
89 - }
90 - },
91 - {
92 - icon: "months",
93 - title: "months view",
94 - class: "custom-icon",
95 - click: function () {
96 - setData("months")
97 - }
98 - },
99 - {
100 - icon: "week",
101 - title: "week view",
102 - class: "custom-icon",
103 - click: function () {
104 - setData("week")
105 - }
106 - }
107 -]
108 -
109 -const series = ref<any[]>([])
110 -const categories = ref<any[]>([])
111 -
112 -function setData(type: DataType) {
113 - series.value = []
114 - categories.value = []
115 -
116 - if (type === "years") {
117 - for (const name of seriesList.value) {
118 - const data = getYearsSeries({ name })
119 - series.value.push(data.series)
120 - if (!categories.value.length) {
121 - categories.value = data.categories
122 - }
123 - }
124 - }
125 - if (type.startsWith("years-")) {
126 - const years = type.split("-")[1]
127 - for (const name of seriesList.value) {
128 - const data = getYearsSeries({ yearsCount: +years, name })
129 - series.value.push(data.series)
130 - if (!categories.value.length) {
131 - categories.value = data.categories
132 - }
133 - }
134 - }
135 - if (type === "months") {
136 - for (const name of seriesList.value) {
137 - const data = getMonthsSeries({ name })
138 - series.value.push(data.series)
139 - if (!categories.value.length) {
140 - categories.value = data.categories
141 - }
142 - }
143 - }
144 - if (type === "week") {
145 - for (const name of seriesList.value) {
146 - const data = getWeekSeries({ name })
147 - series.value.push(data.series)
148 - if (!categories.value.length) {
149 - categories.value = data.categories
150 - }
151 - }
152 - }
153 -}
154 -
155 -setData(dataType.value)
156 -
157 -const highlightMap = getHighlightMap(series.value[0].data)
158 -
159 -const secondaryColors = computed(() => Object.values(useThemeStore().secondaryColors))
160 -const palette = computed<string[]>(() => new Array(4).fill(secondaryColors.value).flat())
161 -
162 -const optsFunction = type.value === "area" ? getAreaOpts : getBarOpts
163 -const getArgs = () => ({
164 - dark: dark.value === undefined ? isThemeDark.value : dark.value,
165 - colors: colorsSecondary.value
166 - ? palette.value
167 - : getChartColors({
168 - type: colorsRandom.value ? "random" : undefined,
169 - color: color?.value ?? style.value["--primary-color"],
170 - highlight: highlight.value ? highlightMap : undefined
171 - }) /*eslint no-mixed-spaces-and-tabs: "off"*/,
172 - fontColor:
173 - fontColor?.value || colorsRandom.value || color?.value
174 - ? style.value["--fg-secondary-color"]
175 - : style.value["--primary-color"],
176 - fontFamily: style.value["--font-family-mono"],
177 - categories: categories.value,
178 - strokeWidth: strokeWidth?.value,
179 - hideLegend: hideLegend?.value,
180 - hideXaxisLabels: hideXaxisLabels?.value,
181 - customButtons: timeButtons.value ? customButtons : []
182 -})
50 +const ready = ref(false)
51 +const chart = ref<VueApexChartsComponent | null>()
52 +const store = useThemeStore()
53
184 -const options = ref(optsFunction(getArgs()))
54 +onMounted(() =>
55 + nextTick(() => {
56 + const duration = 1000 * store.routerTransitionDuration
57 + const gap = 500
58
186 -watch(dataType, () => {
187 - setData(dataType.value)
188 -})
189 -
190 -watchEffect(async () => {
191 - options.value = optsFunction(getArgs())
192 -})
193 -
194 -onMounted(() => {
195 - if (chart.value) {
196 - emit("mounted", chart.value)
197 - }
198 -})
199 -</script>
59 + // TIMEOUT REQUIRED BY PAGE ANIMATION
60 + setTimeout(() => {
61 + ready.value = true
62
201 -<style scoped lang="scss">
202 -.vue-apexcharts {
203 - height: 100%;
204 -
205 - :deep() {
206 - .apexcharts-canvas {
207 - height: 100% !important;
208 -
209 - .apexcharts-legend {
210 - top: 28px !important;
211 - right: var(--legend-offset, 0) !important;
212 - padding: 0;
213 - .apexcharts-legend-series {
214 - display: inline-block;
215 - .apexcharts-legend-marker {
216 - z-index: 1;
217 - margin-right: -6px;
218 - margin-left: 4px;
219 - }
220 - .apexcharts-legend-text {
221 - background-color: var(--tab-color-active);
222 - padding: 0px 12px;
223 - padding-left: 24px;
224 - border-radius: var(--border-radius);
225 - border: 1px solid var(--border-color);
226 - box-sizing: border-box;
227 - height: 34px;
228 - font-size: 14px !important;
229 - font-family: var(--font-family) !important;
230 - display: inline-block;
231 - color: var(--fg-color) !important;
232 - line-height: 34px;
233 - }
234 - }
235 - }
236 - }
237 - }
238 - &.time-buttons {
239 - :deep() {
240 - .apexcharts-legend {
241 - right: 239px !important;
242 - }
243 - .apexcharts-toolbar {
244 - max-width: 100%;
245 - z-index: 0;
246 - top: 30px !important;
247 - right: 25px !important;
248 - display: flex;
249 - gap: 4px;
250 - .apexcharts-toolbar-custom-icon {
251 - background-color: var(--tab-color-active);
252 - padding: 0px 8px;
253 - border-radius: var(--border-radius);
254 - border: 1px solid var(--border-color);
255 - box-sizing: border-box;
256 - height: 34px;
257 - font-size: 14px !important;
258 - font-family: var(--font-family) !important;
259 - display: inline-block;
260 - line-height: 34px;
261 - width: 70px;
262 - text-transform: capitalize;
263 - color: var(--fg-color);
63 + setTimeout(() => {
64 + if (chart.value) {
65 + emit("mounted", chart.value)
66 }
265 - }
266 - }
267 - }
268 -}
269 -</style>
67 + }, 100)
68 + }, duration + gap)
69 + })
70 +)
71 +</script>
src/components/charts/DemoApex.vue new
+291
@@ -0,0 +1,291 @@
1 +<template>
2 + <apexchart
3 + ref="chart"
4 + width="100%"
5 + height="100%"
6 + v-if="ready"
7 + :type="type"
8 + :options="options"
9 + :series="series"
10 + :class="{ 'time-buttons': timeButtons }"
11 + :style="legendOffset && `--legend-offset:${legendOffset}px`"
12 + ></apexchart>
13 +</template>
14 +
15 +<script lang="ts" setup>
16 +import { useThemeStore } from "@/stores/theme"
17 +import { computed, onMounted, toRefs, watch, watchEffect, nextTick } from "vue"
18 +import { ref } from "vue"
19 +import { getAreaOpts, getBarOpts, getMonthsSeries, getWeekSeries, getYearsSeries } from "./data"
20 +import { getChartColors, getHighlightMap } from "./utils"
21 +
22 +export interface VueApexChartsComponent {
23 + toggleSeries(seriesName: string): any
24 + options: {
25 + colors?: string[]
26 + }
27 + series?: any[]
28 +}
29 +
30 +export type DataType = "years" | `years-${number}` | "months" | "week"
31 +export type ChartsType = "area" | "bar"
32 +
33 +export type ChartCTX = VueApexChartsComponent
34 +
35 +const emit = defineEmits<{
36 + (e: "mounted", value: VueApexChartsComponent): void
37 +}>()
38 +
39 +const props = withDefaults(
40 + defineProps<{
41 + type: ChartsType
42 + dataType?: DataType
43 + dark?: boolean
44 + highlight?: boolean
45 + colorsRandom?: boolean
46 + colorsSecondary?: boolean
47 + color?: string
48 + fontColor?: string
49 + strokeWidth?: number
50 + legendOffset?: number
51 + seriesList?: string[]
52 + timeButtons?: boolean
53 + hideLegend?: boolean
54 + hideXaxisLabels?: boolean
55 + }>(),
56 + {
57 + dark: undefined,
58 + dataType: "years",
59 + highlight: false,
60 + colorsRandom: false,
61 + colorsSecondary: false,
62 + timeButtons: false,
63 + hideLegend: false,
64 + hideXaxisLabels: false,
65 + seriesList: () => ["Trend"]
66 + }
67 +)
68 +const {
69 + type,
70 + dark,
71 + dataType,
72 + highlight,
73 + colorsRandom,
74 + colorsSecondary,
75 + color,
76 + fontColor,
77 + strokeWidth,
78 + seriesList,
79 + timeButtons,
80 + legendOffset,
81 + hideLegend,
82 + hideXaxisLabels
83 +} = toRefs(props)
84 +
85 +const ready = ref(false)
86 +const chart = ref<VueApexChartsComponent | null>()
87 +
88 +const isThemeDark = computed(() => useThemeStore().isThemeDark)
89 +const style: { [key: string]: any } = computed(() => useThemeStore().style)
90 +
91 +const customButtons = [
92 + {
93 + icon: "years",
94 + title: "years view",
95 + class: "custom-icon",
96 + click: function () {
97 + setData("years")
98 + }
99 + },
100 + {
101 + icon: "months",
102 + title: "months view",
103 + class: "custom-icon",
104 + click: function () {
105 + setData("months")
106 + }
107 + },
108 + {
109 + icon: "week",
110 + title: "week view",
111 + class: "custom-icon",
112 + click: function () {
113 + setData("week")
114 + }
115 + }
116 +]
117 +
118 +const series = ref<any[]>([])
119 +const categories = ref<any[]>([])
120 +
121 +function setData(type: DataType) {
122 + series.value = []
123 + categories.value = []
124 +
125 + if (type === "years") {
126 + for (const name of seriesList.value) {
127 + const data = getYearsSeries({ name })
128 + series.value.push(data.series)
129 + if (!categories.value.length) {
130 + categories.value = data.categories
131 + }
132 + }
133 + }
134 + if (type.startsWith("years-")) {
135 + const years = type.split("-")[1]
136 + for (const name of seriesList.value) {
137 + const data = getYearsSeries({ yearsCount: +years, name })
138 + series.value.push(data.series)
139 + if (!categories.value.length) {
140 + categories.value = data.categories
141 + }
142 + }
143 + }
144 + if (type === "months") {
145 + for (const name of seriesList.value) {
146 + const data = getMonthsSeries({ name })
147 + series.value.push(data.series)
148 + if (!categories.value.length) {
149 + categories.value = data.categories
150 + }
151 + }
152 + }
153 + if (type === "week") {
154 + for (const name of seriesList.value) {
155 + const data = getWeekSeries({ name })
156 + series.value.push(data.series)
157 + if (!categories.value.length) {
158 + categories.value = data.categories
159 + }
160 + }
161 + }
162 +}
163 +
164 +setData(dataType.value)
165 +
166 +const highlightMap = getHighlightMap(series.value[0].data)
167 +
168 +const secondaryColors = computed(() => Object.values(useThemeStore().secondaryColors))
169 +const palette = computed<string[]>(() => new Array(4).fill(secondaryColors.value).flat())
170 +
171 +const optsFunction = type.value === "area" ? getAreaOpts : getBarOpts
172 +const getArgs = () => ({
173 + dark: dark.value === undefined ? isThemeDark.value : dark.value,
174 + colors: colorsSecondary.value
175 + ? palette.value
176 + : getChartColors({
177 + type: colorsRandom.value ? "random" : undefined,
178 + color: color?.value ?? style.value["--primary-color"],
179 + highlight: highlight.value ? highlightMap : undefined
180 + }) /*eslint no-mixed-spaces-and-tabs: "off"*/,
181 + fontColor:
182 + fontColor?.value || colorsRandom.value || color?.value
183 + ? style.value["--fg-secondary-color"]
184 + : style.value["--primary-color"],
185 + fontFamily: style.value["--font-family-mono"],
186 + categories: categories.value,
187 + strokeWidth: strokeWidth?.value,
188 + hideLegend: hideLegend?.value,
189 + hideXaxisLabels: hideXaxisLabels?.value,
190 + customButtons: timeButtons.value ? customButtons : []
191 +})
192 +
193 +const store = useThemeStore()
194 +const options = ref(optsFunction(getArgs()))
195 +
196 +watch(dataType, () => {
197 + setData(dataType.value)
198 +})
199 +
200 +watchEffect(async () => {
201 + options.value = optsFunction(getArgs())
202 +})
203 +
204 +onMounted(() =>
205 + nextTick(() => {
206 + const duration = 1000 * store.routerTransitionDuration
207 + const gap = 500
208 +
209 + // TIMEOUT REQUIRED BY PAGE ANIMATION
210 + setTimeout(() => {
211 + ready.value = true
212 +
213 + setTimeout(() => {
214 + if (chart.value) {
215 + emit("mounted", chart.value)
216 + }
217 + }, 100)
218 + }, duration + gap)
219 + })
220 +)
221 +</script>
222 +
223 +<style scoped lang="scss">
224 +.vue-apexcharts {
225 + height: 100%;
226 +
227 + :deep() {
228 + .apexcharts-canvas {
229 + height: 100% !important;
230 +
231 + .apexcharts-legend {
232 + top: 28px !important;
233 + right: var(--legend-offset, 0) !important;
234 + padding: 0;
235 + .apexcharts-legend-series {
236 + display: inline-block;
237 + .apexcharts-legend-marker {
238 + z-index: 1;
239 + margin-right: -6px;
240 + margin-left: 4px;
241 + }
242 + .apexcharts-legend-text {
243 + background-color: var(--tab-color-active);
244 + padding: 0px 12px;
245 + padding-left: 24px;
246 + border-radius: var(--border-radius);
247 + border: 1px solid var(--border-color);
248 + box-sizing: border-box;
249 + height: 34px;
250 + font-size: 14px !important;
251 + font-family: var(--font-family) !important;
252 + display: inline-block;
253 + color: var(--fg-color) !important;
254 + line-height: 34px;
255 + }
256 + }
257 + }
258 + }
259 + }
260 + &.time-buttons {
261 + :deep() {
262 + .apexcharts-legend {
263 + right: 239px !important;
264 + }
265 + .apexcharts-toolbar {
266 + max-width: 100%;
267 + z-index: 0;
268 + top: 30px !important;
269 + right: 25px !important;
270 + display: flex;
271 + gap: 4px;
272 + .apexcharts-toolbar-custom-icon {
273 + background-color: var(--tab-color-active);
274 + padding: 0px 8px;
275 + border-radius: var(--border-radius);
276 + border: 1px solid var(--border-color);
277 + box-sizing: border-box;
278 + height: 34px;
279 + font-size: 14px !important;
280 + font-family: var(--font-family) !important;
281 + display: inline-block;
282 + line-height: 34px;
283 + width: 70px;
284 + text-transform: capitalize;
285 + color: var(--fg-color);
286 + }
287 + }
288 + }
289 + }
290 +}
291 +</style>
src/components/charts/data.ts
+2 -3
@@ -1,6 +1,5 @@
1 import dayjs from "@/utils/dayjs"
2 import { faker } from "@faker-js/faker"
3 -import { type ApexOptions } from "apexcharts"
3
4 export function getYearsSeries({ yearsCount = 8, name = "Trend" }) {
5 const years = []
@@ -66,7 +65,7 @@ export function getAreaOpts({
65 strokeWidth?: number
66 hideLegend?: boolean
67 hideXaxisLabels?: boolean
69 -}): ApexOptions {
68 +}) {
69 const id = faker.string.nanoid()
70
71 const overwriteCategories: any[] = categories ? [...categories] : []
@@ -207,7 +206,7 @@ export function getBarOpts({
206 strokeWidth?: number
207 hideLegend?: boolean
208 hideXaxisLabels?: boolean
210 -}): ApexOptions {
209 +}) {
210 const id = faker.string.nanoid()
211
212 const funcColor = function ({ dataPointIndex }: { dataPointIndex: number }) {
src/components/charts/demo-pages/apex-charts-components/Bar.vue renamed
+2 -1
@@ -1,12 +1,13 @@
1 <template>
2 <CardCodeExample title="Bar">
3 - <apexchart type="bar" height="350" :options="chartOptions" :series="series"></apexchart>
3 + <Apex type="bar" height="350" :options="chartOptions" :series="series"></Apex>
4 </CardCodeExample>
5 </template>
6
7 <script setup lang="ts">
8 import "@/assets/scss/apexchart-override.scss"
9 import { computed, ref, watch } from "vue"
10 +import Apex from "@/components/charts/Apex.vue"
11 import { useThemeStore } from "@/stores/theme"
12
13 const isThemeDark = computed(() => useThemeStore().isThemeDark)
src/components/charts/demo-pages/apex-charts-components/Brush.vue renamed
src/components/charts/demo-pages/apex-charts-components/Column.vue renamed
+2 -1
@@ -1,12 +1,13 @@
1 <template>
2 <CardCodeExample title="Column">
3 - <apexchart type="bar" height="350" :options="chartOptions" :series="series"></apexchart>
3 + <Apex type="bar" height="350" :options="chartOptions" :series="series"></Apex>
4 </CardCodeExample>
5 </template>
6
7 <script setup lang="ts">
8 import "@/assets/scss/apexchart-override.scss"
9 import { computed, ref, watch } from "vue"
10 +import Apex from "@/components/charts/Apex.vue"
11 import { useThemeStore } from "@/stores/theme"
12
13 const isThemeDark = computed(() => useThemeStore().isThemeDark)
src/components/charts/demo-pages/apex-charts-components/Pie.vue renamed
+2 -1
@@ -1,10 +1,11 @@
1 <template>
2 <CardCodeExample title="Pie" class="!grid card">
3 - <apexchart type="pie" width="70%" :options="chartOptions" :series="series"></apexchart>
3 + <Apex type="pie" width="70%" :options="chartOptions" :series="series"></Apex>
4 </CardCodeExample>
5 </template>
6
7 <script setup lang="ts">
8 +import Apex from "@/components/charts/Apex.vue"
9 import { ref } from "vue"
10
11 const series = ref([25, 15, 44, 55, 41, 17])
src/components/charts/demo-pages/apex-charts-components/Radar.vue renamed
+2 -1
@@ -1,10 +1,11 @@
1 <template>
2 <CardCodeExample title="Radar" class="!grid card">
3 - <apexchart type="radar" width="100%" :options="chartOptions" :series="series"></apexchart>
3 + <Apex type="radar" width="100%" :options="chartOptions" :series="series"></Apex>
4 </CardCodeExample>
5 </template>
6
7 <script setup lang="ts">
8 +import Apex from "@/components/charts/Apex.vue"
9 import { ref, computed, watch } from "vue"
10 import { useThemeStore } from "@/stores/theme"
11
src/components/charts/demo-pages/apex-charts-components/Realtime.vue renamed
+14 -6
@@ -1,15 +1,15 @@
1 <template>
2 <CardCodeExample title="Realtime">
3 - <apexchart type="line" height="350" ref="chart" :options="chartOptions" :series="series"></apexchart>
3 + <Apex type="line" height="350" :options="chartOptions" :series="series" @mounted="chart = $event"></Apex>
4 </CardCodeExample>
5 </template>
6
7 <script setup lang="ts">
8 import { useThemeStore } from "@/stores/theme"
9 -import { computed, onMounted, ref, watch } from "vue"
10 -import { type VueApexChartsComponent } from "vue3-apexcharts"
9 +import { computed, onMounted, ref, watch, onBeforeUnmount } from "vue"
10 +import Apex, { type VueApexChartsComponent } from "@/components/charts/Apex.vue"
11
12 -const chart = ref<VueApexChartsComponent>()
12 +const chart = ref<VueApexChartsComponent | null>(null)
13 const isThemeDark = computed(() => useThemeStore().isThemeDark)
14 const style: { [key: string]: any } = computed(() => useThemeStore().style)
15
@@ -17,6 +17,7 @@ let lastDate = 0
17 let data: any[] = []
18 const TICKINTERVAL = 86400000
19 let XAXISRANGE = 777600000
20 +
21 function getDayWiseTimeSeries(
22 baseval: number,
23 count: number,
@@ -163,9 +164,11 @@ function getOptions() {
164 }
165
166 const chartOptions = ref(getOptions())
167 +let updateTimer = null as NodeJS.Timeout | null
168 +let resetTimer = null as NodeJS.Timeout | null
169
170 onMounted(() => {
168 - window.setInterval(function () {
171 + updateTimer = setInterval(() => {
172 getNewSeries(lastDate, {
173 min: 10,
174 max: 90
@@ -179,7 +182,7 @@ onMounted(() => {
182 }, 1000)
183
184 // every 60 seconds, we reset the data to prevent memory leaks
182 - window.setInterval(function () {
185 + resetTimer = setInterval(() => {
186 resetData()
187
188 chart.value?.updateSeries(
@@ -193,6 +196,11 @@ onMounted(() => {
196 }, 60000)
197 })
198
199 +onBeforeUnmount(() => {
200 + if (updateTimer) clearInterval(updateTimer)
201 + if (resetTimer) clearInterval(resetTimer)
202 +})
203 +
204 watch(isThemeDark, () => {
205 chartOptions.value = getOptions()
206 })
src/components/charts/demo-pages/apex-charts-components/Sync.vue renamed
+12 -12
@@ -1,27 +1,27 @@
1 <template>
2 <CardCodeExample title="Sync">
3 <div class="flex flex-col">
4 - <apexchart type="line" height="160" :options="chartOptionsLine1" :series="seriesLine1"></apexchart>
5 - <apexchart type="line" height="160" :options="chartOptionsLine2" :series="seriesLine2"></apexchart>
6 - <apexchart type="area" height="160" :options="chartOptionsArea1" :series="seriesArea1"></apexchart>
4 + <Apex type="line" height="160" :options="chartOptionsLine1" :series="seriesLine1"></Apex>
5 + <Apex type="line" height="160" :options="chartOptionsLine2" :series="seriesLine2"></Apex>
6 + <Apex type="area" height="160" :options="chartOptionsArea1" :series="seriesArea1"></Apex>
7 <div class="grid sm:grid-cols-2 grid-cols-1">
8 <div class="overflow-hidden">
9 - <apexchart
10 - ref="chart1"
9 + <Apex
10 + @mounted="chart1 = $event"
11 type="area"
12 height="160"
13 :options="chartOptionsSmall1"
14 :series="seriesSmall1"
15 - ></apexchart>
15 + ></Apex>
16 </div>
17 <div class="overflow-hidden">
18 - <apexchart
19 - ref="chart2"
18 + <Apex
19 + @mounted="chart2 = $event"
20 type="area"
21 height="160"
22 :options="chartOptionsSmall2"
23 :series="seriesSmall2"
24 - ></apexchart>
24 + ></Apex>
25 </div>
26 </div>
27 </div>
@@ -36,16 +36,16 @@ import _merge from "lodash/merge"
36 import _clone from "lodash/cloneDeep"
37 import { useThemeStore } from "@/stores/theme"
38 import dayjs from "@/utils/dayjs"
39 +import Apex, { type VueApexChartsComponent } from "@/components/charts/Apex.vue"
40 import { useResizeObserver } from "@vueuse/core"
40 -import { type VueApexChartsComponent } from "vue3-apexcharts"
41
42 const isThemeDark = computed(() => useThemeStore().isThemeDark)
43 const style: { [key: string]: any } = computed(() => useThemeStore().style)
44 const startDate = dayjs().subtract(20, "d").valueOf()
45
46 const card = ref(null)
47 -const chart1 = ref<VueApexChartsComponent>()
48 -const chart2 = ref<VueApexChartsComponent>()
47 +const chart1 = ref<VueApexChartsComponent | null>(null)
48 +const chart2 = ref<VueApexChartsComponent | null>(null)
49
50 function getCommonOptions() {
51 return {
src/components/charts/demo-pages/apex-charts-components/utils.ts renamed
src/components/charts/demo-pages/chartjs-components/Bar.vue renamed
src/components/charts/demo-pages/chartjs-components/Line.vue renamed
src/components/common/Icon.vue new
+58
@@ -0,0 +1,58 @@
1 +<template>
2 + <component :is="componentName" v-bind="options">
3 + <template v-if="$slots.default">
4 + <slot />
5 + </template>
6 + <template v-else>
7 + <Icon v-if="icon" :icon="icon" :width="size" :height="size" />
8 + </template>
9 + </component>
10 +</template>
11 +
12 +<script setup lang="ts">
13 +import { NIconWrapper, NIcon } from "naive-ui"
14 +import { Icon, loadIcon, type IconifyIcon } from "@iconify/vue"
15 +import { computed, ref, watchEffect } from "vue"
16 +
17 +const props = defineProps<{
18 + name?: string
19 + size?: number
20 + bgSize?: number
21 + color?: string
22 + bgColor?: string
23 + borderRadius?: number
24 + depth?: 1 | 2 | 3 | 4 | 5
25 +}>()
26 +
27 +const useWrapper = computed(() => !!(props.bgColor || props.bgSize || props.borderRadius))
28 +const componentName = computed(() => (useWrapper.value ? NIconWrapper : NIcon))
29 +
30 +const options = computed(() => {
31 + const opt: any = {}
32 + if (useWrapper.value) {
33 + if (props.bgSize !== undefined) opt.size = props.bgSize
34 + if (props.bgColor !== undefined) opt.color = props.bgColor
35 + if (props.borderRadius !== undefined) opt.borderRadius = props.borderRadius
36 + if (props.color !== undefined) opt.iconColor = props.color
37 + } else {
38 + if (props.color !== undefined) opt.color = props.color
39 + if (props.depth !== undefined) opt.depth = props.depth
40 + if (props.size !== undefined) opt.size = props.size
41 + }
42 + return opt
43 +})
44 +
45 +const load = (name: string) => loadIcon(name).catch(() => console.error(`Failed to load icon ${name}`))
46 +
47 +const icon = ref<void | Required<IconifyIcon>>()
48 +
49 +function setIcon(name: string | undefined) {
50 + if (name) {
51 + load(name).then(res => (icon.value = res))
52 + }
53 +}
54 +
55 +setIcon(props.name)
56 +
57 +watchEffect(() => setIcon(props.name))
58 +</script>
src/components/common/LayoutSettings.vue renamed
+37 -44
@@ -1,24 +1,15 @@
1 <template>
2 <div class="layout-settings flex items-center justify-center shadow-xl" :class="{ open }">
3 - <XyzTransition mode="out-in">
4 - <div
5 - class="open-btn flex items-center justify-center"
6 - @click="open = true"
7 - v-if="!open"
8 - key="btn"
9 - xyz="left-100%"
10 - >
11 - <n-icon :size="24">
12 - <SettingsIcon />
13 - </n-icon>
3 + <Transition mode="out-in" name="anim">
4 + <div class="open-btn flex items-center justify-center" @click="open = true" v-if="!open" key="btn">
5 + <Icon :size="24" :name="SettingsIcon"></Icon>
6 </div>
15 - <div class="ls-form flex flex-col" v-if="open" key="form" xyz="right-100%">
7 +
8 + <div class="ls-form flex flex-col" v-else key="form">
9 <div class="ls-header flex items-center justify-between">
10 <div class="ls-title">Layout settings</div>
11 <div class="ls-icon flex items-center">
19 - <n-icon @click="open = false" :size="20">
20 - <CloseIcon />
21 - </n-icon>
12 + <Icon @click="open = false" :size="20" :name="CloseIcon"></Icon>
13 </div>
14 </div>
15 <n-scrollbar class="ls-main">
@@ -36,9 +27,11 @@
27 <div class="palette flex justify-between">
28 <n-button text v-for="color of palette" :key="color.light" @click="setPrimary(color)">
29 <template #icon>
39 - <n-icon :color="theme === ThemeEnum.Dark ? color.dark : color.light" :size="24">
40 - <ColorIcon />
41 - </n-icon>
30 + <Icon
31 + :color="theme === ThemeEnum.Dark ? color.dark : color.light"
32 + :size="24"
33 + :name="ColorIcon"
34 + ></Icon>
35 </template>
36 </n-button>
37 </div>
@@ -54,10 +47,8 @@
47 :type="theme === ThemeEnum.Light ? 'primary' : 'default'"
48 >
49 <template #icon>
57 - <n-icon>
58 - <LigthIcon v-if="theme === ThemeEnum.Light" />
59 - <LigthOutlineIcon v-else />
60 - </n-icon>
50 + <Icon :name="LigthIcon" v-if="theme === ThemeEnum.Light"></Icon>
51 + <Icon :name="LigthOutlineIcon" v-else></Icon>
52 </template>
53 Light
54 </n-button>
@@ -69,10 +60,8 @@
60 :type="theme === ThemeEnum.Dark ? 'primary' : 'default'"
61 >
62 <template #icon>
72 - <n-icon>
73 - <DarkIcon v-if="theme === ThemeEnum.Dark" />
74 - <DarkOutlineIcon v-else />
75 - </n-icon>
63 + <Icon :name="DarkIcon" v-if="theme === ThemeEnum.Dark"></Icon>
64 + <Icon :name="DarkOutlineIcon" v-else></Icon>
65 </template>
66 Dark
67 </n-button>
@@ -156,24 +145,27 @@
145 </div>
146 </n-scrollbar>
147 </div>
159 - </XyzTransition>
148 + </Transition>
149 </div>
150 </template>
151
152 <script setup lang="ts">
153 import { computed, ref } from "vue"
165 -import { NIcon, NColorPicker, NButton, NSelect, useOsTheme, NScrollbar, NSwitch } from "naive-ui"
154 +import { NColorPicker, NButton, NSelect, useOsTheme, NScrollbar, NSwitch } from "naive-ui"
155 import { useThemeStore } from "@/stores/theme"
167 -import SettingsIcon from "@vicons/carbon/SettingsAdjust"
168 -import CloseIcon from "@vicons/carbon/Close"
169 -import LigthIcon from "@vicons/ionicons5/Sunny"
170 -import DarkIcon from "@vicons/ionicons5/Moon"
171 -import LigthOutlineIcon from "@vicons/ionicons5/SunnyOutline"
172 -import DarkOutlineIcon from "@vicons/ionicons5/MoonOutline"
173 -import ColorIcon from "@vicons/carbon/CircleSolid"
156 +import Icon from "@/components/common/Icon.vue"
157 +
158 import { Layout, RouterTransition, ThemeEnum } from "@/types/theme.d"
159 import { useWindowSize } from "@vueuse/core"
160
161 +const SettingsIcon = "carbon:settings-adjust"
162 +const CloseIcon = "carbon:close"
163 +const LigthIcon = "ion:sunny"
164 +const DarkIcon = "ion:moon"
165 +const LigthOutlineIcon = "ion:sunny-outline"
166 +const DarkOutlineIcon = "ion:moon-outline"
167 +const ColorIcon = "carbon:circle-solid"
168 +
169 interface ColorPalette {
170 light: string
171 dark: string
@@ -303,8 +295,6 @@ function reset() {
295 position: absolute;
296 height: 100%;
297 width: 100%;
306 - transition: opacity 0.1s;
307 - opacity: 0;
298
299 .ls-header {
300 border-bottom: var(--border-small-050);
@@ -423,16 +413,19 @@ function reset() {
413 background-color: var(--bg-color);
414 color: var(--fg-color);
415 border-color: var(--border-color);
416 + }
417
427 - .ls-form {
428 - opacity: 1;
429 - }
418 + .anim-enter-active,
419 + .anim-leave-active {
420 + transition:
421 + opacity 0.1s var(--bezier-ease),
422 + transform 0.2s var(--bezier-ease);
423 }
424
432 - @media (max-width: 700px) {
433 - top: initial;
434 - bottom: 10px;
435 - transform: translateY(0);
425 + .anim-enter-from,
426 + .anim-leave-to {
427 + opacity: 0;
428 + transform: translateY(1%);
429 }
430 }
431 </style>
src/components/common/LocaleSelect.vue
+11 -29
@@ -5,18 +5,12 @@
5 </template>
6
7 <script lang="ts" setup>
8 -import { NIcon, NSelect, type SelectOption } from "naive-ui"
9 -import it from "flag-icons/flags/4x3/it.svg"
10 -import en from "flag-icons/flags/4x3/us.svg"
11 -import fr from "flag-icons/flags/4x3/fr.svg"
12 -import es from "flag-icons/flags/4x3/es.svg"
13 -import de from "flag-icons/flags/4x3/de.svg"
14 -import jp from "flag-icons/flags/4x3/jp.svg"
8 +import { NSelect, type SelectOption } from "naive-ui"
9 +import Icon from "@/components/common/Icon.vue"
10 import { computed, h, type VNodeChild } from "vue"
16 -import { useI18n } from "vue-i18n"
17 -import { getAvailableLocales, getLocale, setLocale } from "@/utils/i18n"
11 +import { useStoreI18n } from "@/composables/useStoreI18n"
12
19 -const { t } = useI18n()
13 +const { getAvailableLocales, getLocale, setLocale, t } = useStoreI18n()
14
15 const list = computed(() =>
16 getAvailableLocales().map(i => ({
@@ -32,26 +26,14 @@ const currentLocale = computed({
26
27 function renderLabel(option: SelectOption): VNodeChild {
28 return [
35 - h(
36 - NIcon,
37 - {
38 - color: "#000",
39 - style: {
40 - verticalAlign: "-0.15em",
41 - marginRight: "8px"
42 - }
29 + h(Icon, {
30 + color: "#000",
31 + style: {
32 + verticalAlign: "-0.15em",
33 + marginRight: "8px"
34 },
44 - {
45 - default: () => {
46 - if (option.label === "it") return h(it)
47 - if (option.label === "en") return h(en)
48 - if (option.label === "es") return h(es)
49 - if (option.label === "fr") return h(fr)
50 - if (option.label === "de") return h(de)
51 - if (option.label === "jp") return h(jp)
52 - }
53 - }
54 - ),
35 + name: `circle-flags:${option.label}`
36 + }),
37 h(
38 "span",
39 {},
src/components/common/Notifications.vue new
+140
@@ -0,0 +1,140 @@
1 +<template>
2 + <n-scrollbar class="notifications-list">
3 + <div
4 + class="item flex"
5 + v-for="item of listSanitized"
6 + :key="item.id"
7 + @click="item.action ? item.action() : () => {}"
8 + :class="{ pointer: !!item.action }"
9 + >
10 + <div class="icon-box" :class="item.type">
11 + <Icon :name="MessageIcon" :size="21" v-if="item.type === 'message'"></Icon>
12 + <Icon :name="CalendarIcon" :size="21" v-else-if="item.type === 'reminder'"></Icon>
13 + <Icon :name="NewsIcon" :size="21" v-else-if="item.type === 'news'"></Icon>
14 + <Icon :name="AlertIcon" :size="21" v-else-if="item.type === 'alert'"></Icon>
15 + </div>
16 + <div class="content grow">
17 + <div class="title">{{ item.title }}</div>
18 + <div class="description">{{ item.description }}</div>
19 + <div class="date">{{ item.date }}</div>
20 + </div>
21 + <div class="read-badge" v-if="!item.read"></div>
22 + </div>
23 + <slot name="last"></slot>
24 + </n-scrollbar>
25 +</template>
26 +
27 +<script lang="ts" setup>
28 +import { NScrollbar } from "naive-ui"
29 +import Icon from "@/components/common/Icon.vue"
30 +import { useNotifications } from "@/composables/useNotifications"
31 +import { computed } from "vue"
32 +import _take from "lodash/take"
33 +
34 +const MessageIcon = "carbon:email"
35 +const CalendarIcon = "carbon:calendar"
36 +const NewsIcon = "fluent:news-24-regular"
37 +const AlertIcon = "mdi:alert-outline"
38 +
39 +const props = defineProps<{
40 + maxItems?: number
41 +}>()
42 +
43 +const list = useNotifications().list
44 +
45 +const listSanitized = computed(() => {
46 + if (props.maxItems) {
47 + return _take(list.value, props.maxItems)
48 + }
49 + return list.value
50 +})
51 +</script>
52 +
53 +<style lang="scss" scoped>
54 +.notifications-list {
55 + .item {
56 + position: relative;
57 + padding: 14px 0;
58 + .icon-box {
59 + width: 70px;
60 + min-width: 70px;
61 + display: flex;
62 + justify-content: center;
63 +
64 + .n-icon {
65 + display: flex;
66 + justify-content: center;
67 + align-items: center;
68 + background-color: var(--primary-005-color);
69 + color: var(--primary-color);
70 + border-radius: 50%;
71 + width: 42px;
72 + height: 42px;
73 + margin-top: 2px;
74 + }
75 +
76 + &.message {
77 + .n-icon {
78 + background-color: var(--secondary1-opacity-010-color);
79 + color: var(--secondary1-color);
80 + }
81 + }
82 + &.reminder {
83 + .n-icon {
84 + background-color: var(--secondary2-opacity-010-color);
85 + color: var(--secondary2-color);
86 + }
87 + }
88 + &.news {
89 + .n-icon {
90 + background-color: var(--secondary3-opacity-010-color);
91 + color: var(--secondary3-color);
92 + }
93 + }
94 + &.alert {
95 + .n-icon {
96 + background-color: var(--secondary4-opacity-010-color);
97 + color: var(--secondary4-color);
98 + }
99 + }
100 + }
101 + .content {
102 + max-width: 250px;
103 + padding-right: 20px;
104 + font-size: 14px;
105 +
106 + .title {
107 + font-weight: bold;
108 + }
109 + .date {
110 + font-size: 12px;
111 + margin-top: 6px;
112 + opacity: 0.5;
113 + }
114 + }
115 +
116 + .read-badge {
117 + position: absolute;
118 + top: 0;
119 + left: 0;
120 + width: 0;
121 + height: 0;
122 + border-style: solid;
123 + border-width: 20px 20px 0 0;
124 + border-color: var(--primary-050-color) transparent transparent transparent;
125 + }
126 +
127 + &.pointer {
128 + cursor: pointer;
129 + }
130 +
131 + &:not(:last-child) {
132 + border-bottom: var(--border-small-050);
133 + }
134 +
135 + &:hover {
136 + background-color: var(--hover-005-color);
137 + }
138 + }
139 +}
140 +</style>
src/components/common/PageSplitted.vue renamed
+9 -8
@@ -20,9 +20,7 @@
20 <div class="main-toolbar flex items-center">
21 <div class="menu-btn flex justify-center opacity-50">
22 <n-button text @click="sidebarOpen = true">
23 - <n-icon :size="24">
24 - <MenuIcon />
25 - </n-icon>
23 + <Icon :size="24" :name="MenuIcon"></Icon>
24 </n-button>
25 </div>
26
@@ -45,8 +43,10 @@
43 </template>
44
45 <script setup lang="ts">
48 -import { NIcon, NScrollbar, NButton } from "naive-ui"
49 -import MenuIcon from "@vicons/ionicons5/MenuSharp"
46 +import { NScrollbar, NButton } from "naive-ui"
47 +import Icon from "@/components/common/Icon.vue"
48 +
49 +const MenuIcon = "ion:menu-sharp"
50 import { ref, toRefs } from "vue"
51 import { onClickOutside } from "@vueuse/core"
52
@@ -74,9 +74,10 @@ onClickOutside(sidebar, () => (sidebarOpen.value = false))
74 overflow: hidden;
75 border-radius: var(--border-radius);
76 border: 1px solid var(--border-color);
77 + background-color: var(--bg-color);
78
79 .sidebar {
79 - background-color: var(--bg-sidebar);
80 + background-color: var(--bg-secondary-color);
81 min-width: 250px;
82 width: 40%;
83 max-width: 350px;
@@ -150,7 +151,7 @@ onClickOutside(sidebar, () => (sidebarOpen.value = false))
151 content: "";
152 width: 100vw;
153 display: block;
153 - background-color: rgba(var(--bg-body-rgb), 0.4);
154 + background-color: var(--bg-body);
155 position: absolute;
156 top: 0;
157 left: 0;
@@ -232,7 +233,7 @@ onClickOutside(sidebar, () => (sidebarOpen.value = false))
233 &.sidebar-open {
234 &::before {
235 transform: translateX(0);
235 - opacity: 1;
236 + opacity: 0.4;
237 transition:
238 opacity 0.25s ease-in-out,
239 transform 0s linear 0s;
src/components/common/Percentage.vue
+7 -9
@@ -21,12 +21,8 @@
21 />
22 </span>
23 <span v-if="icon && icon === 'arrow'" class="flex items-center percentage-icon">
24 - <n-icon v-if="direction === 'up'">
25 - <ChevronUp />
26 - </n-icon>
27 - <n-icon v-if="direction === 'down'">
28 - <ChevronDown />
29 - </n-icon>
24 + <Icon v-if="direction === 'up'" :name="ChevronUp"></Icon>
25 + <Icon v-if="direction === 'down'" :name="ChevronDown"></Icon>
26 </span>
27 <span v-if="icon && icon === 'operator'" class="percentage-icon">
28 {{ direction === "up" ? "+" : "-" }}
@@ -47,9 +43,11 @@
43
44 <script setup lang="ts">
45 import { toRefs } from "vue"
50 -import { NIcon, NProgress } from "naive-ui"
51 -import ChevronUp from "@vicons/tabler/ChevronUp"
52 -import ChevronDown from "@vicons/tabler/ChevronDown"
46 +import { NProgress } from "naive-ui"
47 +import Icon from "@/components/common/Icon.vue"
48 +
49 +const ChevronUp = "tabler:chevron-up"
50 +const ChevronDown = "tabler:chevron-down"
51
52 export interface PercentageProps {
53 value: number
src/components/common/SearchDialog.vue new
+437
@@ -0,0 +1,437 @@
1 +<template>
2 + <n-modal v-model:show="showSearchBox" class="search-box-modal">
3 + <n-card
4 + style="width: 600px"
5 + content-style="padding: 0;"
6 + :bordered="false"
7 + size="huge"
8 + role="dialog"
9 + aria-modal="true"
10 + >
11 + <div class="search-box" @keydown.up="prevItem()" @keydown.down="nextItem()">
12 + <div class="search-input flex items-center">
13 + <Icon :name="SearchIcon" :size="16"></Icon>
14 + <input placeholder="Search" v-model="search" class="grow" />
15 + <n-text code>ESC</n-text>
16 + <Icon :name="CloseIcon" :size="20" @click="closeBox()" class="cursor-pointer"></Icon>
17 + </div>
18 + <n-divider />
19 + <n-scrollbar style="height: 400px" ref="scrollContent">
20 + <div class="conten-wrap">
21 + <div class="group" v-for="group of filteredGroups" :key="group.name">
22 + <div class="group-title">{{ group.name }}</div>
23 + <div class="group-list">
24 + <button
25 + v-for="item of group.items"
26 + :key="item.key"
27 + :id="item.key.toString()"
28 + class="item flex items-center"
29 + :class="{ active: item.key === activeItem }"
30 + @click="callAction(item.action)"
31 + >
32 + <div class="icon">
33 + <n-avatar v-if="item.iconImage" round :size="28" :src="item.iconImage" />
34 + <Icon :name="item.iconName" v-if="item.iconName" :size="18"></Icon>
35 + </div>
36 + <div class="title grow">
37 + <Highlighter
38 + highlightClassName="highlight"
39 + :searchWords="keywords"
40 + :autoEscape="true"
41 + :textToHighlight="item.title"
42 + />
43 + </div>
44 + <div class="label">{{ item.label }}</div>
45 + </button>
46 + </div>
47 + </div>
48 + <div v-if="!filteredGroups.length" class="group-empty">
49 + We couldn't find anything matching "{{ search }}"
50 + </div>
51 + </div>
52 + </n-scrollbar>
53 + <n-divider />
54 + <div class="hint-bar flex items-center justify-center">
55 + <div class="hint flex items-center justify-center">
56 + <div class="icon">
57 + <Icon :name="ArrowEnterIcon" :size="12"></Icon>
58 + </div>
59 + <span class="label">to select</span>
60 + </div>
61 + <div class="hint flex items-center justify-center">
62 + <div class="icon">
63 + <Icon :name="ArrowSortIcon" :size="12"></Icon>
64 + </div>
65 + <span class="label">to navigate</span>
66 + </div>
67 + </div>
68 + </div>
69 + </n-card>
70 + </n-modal>
71 +</template>
72 +
73 +<script lang="ts" setup>
74 +import { computed, onMounted, ref } from "vue"
75 +import { NText, NModal, NCard, NDivider, NAvatar, NScrollbar, type ScrollbarInst } from "naive-ui"
76 +import { useMagicKeys, whenever } from "@vueuse/core"
77 +import { faker } from "@faker-js/faker"
78 +import Highlighter from "vue-highlight-words"
79 +import { useRouter } from "vue-router"
80 +import { useThemeSwitch } from "@/composables/useThemeSwitch"
81 +import { useFullscreenSwitch } from "@/composables/useFullscreenSwitch"
82 +import { useSearchDialog } from "@/composables/useSearchDialog"
83 +import { getOS } from "@/utils"
84 +import Icon from "@/components/common/Icon.vue"
85 +
86 +const SearchIcon = "ion:search-outline"
87 +const TodoIcon = "fluent:task-list-square-add-20-regular"
88 +const EmailIcon = "fluent:mail-edit-20-regular"
89 +const NotesIcon = "fluent:chart-person-20-regular"
90 +const ArrowEnterIcon = "fluent:arrow-enter-left-24-regular"
91 +const ArrowSortIcon = "fluent:arrow-sort-24-regular"
92 +const FullScreenIcon = "fluent:full-screen-maximize-24-regular"
93 +const DarkModeIcon = "ion:moon-outline"
94 +const CloseIcon = "ion:close"
95 +
96 +interface GroupItem {
97 + iconName: string | null
98 + iconImage: string | null
99 + key: number | string
100 + title: string
101 + label: string
102 + tags?: string
103 + action: () => void
104 +}
105 +
106 +interface Group {
107 + name: string
108 + items: GroupItem[]
109 +}
110 +type Groups = Group[]
111 +
112 +const router = useRouter()
113 +
114 +const showSearchBox = ref(false)
115 +const search = ref("")
116 +const activeItem = ref<null | string | number>(null)
117 +const commandIcon = ref("⌘")
118 +const scrollContent = ref<(ScrollbarInst & { $el: any }) | null>(null)
119 +
120 +const groups = ref<Groups>([
121 + {
122 + name: "Applications",
123 + items: [
124 + {
125 + iconName: TodoIcon,
126 + iconImage: null,
127 + key: 1,
128 + title: "Add todo list",
129 + label: "Shortcut",
130 + action() {
131 + router.push({ name: "Apps-Kanban" })
132 + }
133 + },
134 + {
135 + iconName: EmailIcon,
136 + iconImage: null,
137 + key: 2,
138 + title: "Compose new email",
139 + label: "Shortcut",
140 + action() {
141 + router.push({ name: "Apps-Mailbox" })
142 + }
143 + },
144 + {
145 + iconName: NotesIcon,
146 + iconImage: null,
147 + key: 3,
148 + title: "View Notes",
149 + label: "Shortcut",
150 + action() {
151 + router.push({ name: "Apps-Notes" })
152 + }
153 + }
154 + ]
155 + },
156 + {
157 + name: "Contacts",
158 + items: [
159 + {
160 + iconName: null,
161 + iconImage: "https://i.pravatar.cc/56?_=" + Math.random(),
162 + key: 4,
163 + title: faker.person.fullName(),
164 + label: faker.internet.email().toLowerCase(),
165 + action() {
166 + router.push({ name: "Apps-Chat" })
167 + }
168 + },
169 + {
170 + iconName: null,
171 + iconImage: "https://i.pravatar.cc/56?_=" + Math.random(),
172 + key: 5,
173 + title: faker.person.fullName(),
174 + label: faker.internet.email().toLowerCase(),
175 + action() {
176 + router.push({ name: "Apps-Chat" })
177 + }
178 + },
179 + {
180 + iconName: null,
181 + iconImage: "https://i.pravatar.cc/56?_=" + Math.random(),
182 + key: 6,
183 + title: faker.person.fullName(),
184 + label: faker.internet.email().toLowerCase(),
185 + action() {
186 + router.push({ name: "Apps-Chat" })
187 + }
188 + }
189 + ]
190 + },
191 + {
192 + name: "Actions",
193 + items: [
194 + {
195 + iconName: FullScreenIcon,
196 + iconImage: null,
197 + key: 7,
198 + title: "Toggle fullscreen",
199 + label: "Action",
200 + action() {
201 + useFullscreenSwitch().toggle()
202 + }
203 + },
204 + {
205 + iconName: DarkModeIcon,
206 + iconImage: null,
207 + key: 8,
208 + title: "Toggle dark mode",
209 + label: "Action",
210 + action() {
211 + useThemeSwitch().toggle()
212 + }
213 + }
214 + ]
215 + }
216 +])
217 +
218 +const keywords = computed<string[]>(() => {
219 + if (search.value.length > 1) {
220 + return search.value.split(" ").filter(k => k)
221 + } else {
222 + return []
223 + }
224 +})
225 +const filteredGroups = computed<Groups>(() => {
226 + if (keywords.value.length === 0) {
227 + return groups.value
228 + }
229 + const newGroups: Groups = []
230 + for (const group of groups.value) {
231 + const items = group.items.filter(item => {
232 + if (keywords.value.filter(k => item.title.toLowerCase().indexOf(k.toLowerCase()) !== -1).length !== 0) {
233 + return true
234 + }
235 + if (
236 + item.tags &&
237 + keywords.value.filter(k => item.tags?.toLowerCase().indexOf(k.toLowerCase()) !== -1).length !== 0
238 + ) {
239 + return true
240 + }
241 + return false
242 + })
243 + if (items.length) {
244 + newGroups.push({
245 + name: group.name,
246 + items
247 + })
248 + }
249 + }
250 + return newGroups
251 +})
252 +
253 +/*eslint @typescript-eslint/no-unused-vars: "off"*/
254 +const filteredFlattenItems = computed<GroupItem[]>(() => {
255 + const items = []
256 +
257 + for (const group of filteredGroups.value) {
258 + items.push(...group.items)
259 + }
260 +
261 + return items
262 +})
263 +
264 +function openBox(e?: MouseEvent) {
265 + if (!showSearchBox.value) {
266 + showSearchBox.value = true
267 + setTimeout(() => {
268 + search.value = ""
269 + activeItem.value = null
270 + }, 100)
271 + }
272 + return e
273 +}
274 +function closeBox() {
275 + showSearchBox.value = false
276 + search.value = ""
277 + activeItem.value = null
278 +}
279 +function callAction(action: () => void) {
280 + action()
281 + closeBox()
282 +}
283 +function nextItem() {
284 + const currentIndex = filteredFlattenItems.value.findIndex(item => item.key === activeItem.value)
285 + if (currentIndex === filteredFlattenItems.value.length - 1 || activeItem.value === null) {
286 + activeItem.value = filteredFlattenItems.value[0].key
287 + } else {
288 + activeItem.value = filteredFlattenItems.value[currentIndex + 1].key
289 + }
290 + centerItem()
291 +}
292 +function prevItem() {
293 + const currentIndex = filteredFlattenItems.value.findIndex(item => item.key === activeItem.value)
294 + if (currentIndex === 0 || activeItem.value === null) {
295 + activeItem.value = filteredFlattenItems.value[filteredFlattenItems.value.length - 1].key
296 + } else {
297 + activeItem.value = filteredFlattenItems.value[currentIndex - 1].key
298 + }
299 + centerItem()
300 +}
301 +function performAction() {
302 + const item = filteredFlattenItems.value.find(item => item.key === activeItem.value)
303 + if (item) {
304 + callAction(item.action)
305 + }
306 +}
307 +function centerItem() {
308 + const element = document.getElementById(activeItem.value?.toString() || "")
309 + if (element && scrollContent.value) {
310 + element.scrollIntoView({ block: "nearest" })
311 + }
312 +}
313 +
314 +onMounted(() => {
315 + const isWindows = getOS() === "Windows"
316 + commandIcon.value = isWindows ? "CTRL" : "⌘"
317 +
318 + const keys = useMagicKeys()
319 + const ActiveCMD = isWindows ? keys["ctrl+k"] : keys["cmd+k"]
320 + const Enter = keys["enter"]
321 +
322 + useSearchDialog().trigger(openBox)
323 +
324 + whenever(ActiveCMD, () => {
325 + openBox()
326 + })
327 +
328 + whenever(Enter, () => {
329 + if (showSearchBox.value) {
330 + performAction()
331 + }
332 + })
333 +})
334 +</script>
335 +
336 +<style lang="scss" scoped>
337 +.search-box-modal {
338 + .search-box {
339 + border-radius: 4px;
340 +
341 + .search-input {
342 + height: 50px;
343 + gap: 20px;
344 + padding: 20px;
345 +
346 + input {
347 + background: transparent;
348 + outline: none;
349 + border: none;
350 + min-width: 100px;
351 + }
352 +
353 + .n-text--code {
354 + white-space: nowrap;
355 + }
356 + }
357 +
358 + .n-divider {
359 + margin-top: 0;
360 + margin-bottom: 0;
361 + }
362 +
363 + .conten-wrap {
364 + padding-bottom: 30px;
365 +
366 + .group-empty {
367 + text-align: center;
368 + padding: 30px 0 40px 0;
369 + }
370 + .group {
371 + padding: 0 10px;
372 + .group-title {
373 + opacity: 0.6;
374 + margin-bottom: 5px;
375 + padding: 5px 10px;
376 + padding-top: 20px;
377 + }
378 + .group-list {
379 + .item {
380 + padding: 7px 10px;
381 + gap: 10px;
382 + cursor: pointer;
383 + border-radius: 10px;
384 + width: 100%;
385 + text-align: left;
386 +
387 + .icon {
388 + width: 28px;
389 + height: 28px;
390 + border-radius: 50%;
391 + background-color: var(--primary-005-color);
392 + display: flex;
393 + justify-content: center;
394 + align-items: center;
395 + }
396 + .title {
397 + font-weight: bold;
398 + }
399 + .label {
400 + opacity: 0.8;
401 + font-size: 0.9em;
402 + }
403 +
404 + &.active {
405 + background-color: var(--hover-005-color);
406 + }
407 + &:hover {
408 + box-shadow: 0px 0px 0px 1px var(--primary-color) inset;
409 + }
410 + }
411 + }
412 + }
413 + }
414 +
415 + .hint-bar {
416 + font-size: 12px;
417 + gap: 20px;
418 + padding: 10px 0;
419 + .icon {
420 + background-color: var(--code-color);
421 + width: 18px;
422 + height: 18px;
423 + padding-top: 1px;
424 + text-align: center;
425 + border-radius: 4px;
426 + margin-right: 5px;
427 + display: flex;
428 + align-items: center;
429 + justify-content: center;
430 + }
431 + .label {
432 + opacity: 0.7;
433 + }
434 + }
435 + }
436 +}
437 +</style>
src/components/common/TestScope.vue
+1
@@ -2,6 +2,7 @@
2 <div>{{ msg }}</div>
3 </template>
4 <script setup lang="ts">
5 +// THIS COMPONENT IS USED FOR VITEST TESTING PURPOSES
6 import { toRefs } from "vue"
7 const props = defineProps(["msg"])
8 const { msg } = toRefs(props)
src/components/connectors/ConfigForm/FormTypes/FileType.vue
+4 -5
@@ -12,9 +12,7 @@
12 >
13 <n-upload-dragger>
14 <div>
15 - <n-icon size="48" :depth="3">
16 - <UploadIcon />
17 - </n-icon>
15 + <Icon :name="UploadIcon" :size="48" :depth="3"></Icon>
16 </div>
17 <h4>Click or drag a file to this area to upload</h4>
18 <p class="mt-2">Limit 1 file .YAML, new file will cover the old file</p>
@@ -25,12 +23,10 @@
23 </template>
24
25 <script setup lang="ts">
28 -import UploadIcon from "@vicons/carbon/CloudUpload"
26 import { onMounted, ref, toRefs } from "vue"
27 import {
28 NForm,
29 NFormItem,
33 - NIcon,
30 NUpload,
31 NUploadDragger,
32 type FormRules,
@@ -39,11 +35,14 @@ import {
35 type UploadInst,
36 type UploadFileInfo
37 } from "naive-ui"
38 +import Icon from "@/components/common/Icon.vue"
39
40 export interface IFileForm {
41 connector_file: File | null
42 }
43
44 +const UploadIcon = "carbon:cloud-upload"
45 +
46 const emit = defineEmits<{
47 (e: "mounted", value: FormInst): void
48 }>()
src/components/editors/Milkdown/index.ts deleted
-2
@@ -1,2 +0,0 @@
1 -import Milkdown from "./Milkdown.vue"
2 -export default Milkdown
src/components/editors/Milkdown/index.vue renamed
src/components/editors/Tiptap/MenuBar.vue
+6
@@ -35,6 +35,12 @@ const items: (ItemProps | { type: string; icon: string })[] = [
35 action: () => editor.value.chain().focus().toggleItalic().run(),
36 isActive: () => editor.value.isActive("italic")
37 },
38 + {
39 + icon: "underline",
40 + title: "Underline",
41 + action: () => editor.value.chain().focus().toggleUnderline().run(),
42 + isActive: () => editor.value.isActive("underline")
43 + },
44 {
45 icon: "strikethrough",
46 title: "Strike",
src/components/editors/Tiptap/MenuItem.vue
+31 -31
@@ -1,38 +1,37 @@
1 <template>
2 <button class="menu-item" :class="{ 'is-active': isActive ? isActive() : null }" @click="action" :title="title">
3 - <n-icon class="remix" :size="22">
4 - <component :is="iconComponent" />
5 - </n-icon>
3 + <Icon :size="22" :name="iconComponent"></Icon>
4 </button>
5 </template>
6
7 <script setup lang="ts">
10 -import { NIcon } from "naive-ui"
11 -import Bold from "@vicons/fluent/TextBold16Regular"
12 -import Italic from "@vicons/fluent/TextItalic16Filled"
13 -import Strikethrough from "@vicons/fluent/TextStrikethrough16Filled"
14 -import CodeView from "@vicons/tabler/Code"
15 -import MarkPen from "@vicons/fluent/Highlight24Regular"
16 -import H1 from "@vicons/fluent/TextHeader124Filled"
17 -import H2 from "@vicons/fluent/TextHeader224Filled"
18 -import Paragraph from "@vicons/carbon/Paragraph"
19 -import ListUnordered from "@vicons/fluent/AppsList24Regular"
20 -import ListOrdered from "@vicons/fluent/TextNumberListLtr24Regular"
21 -import ListCheck from "@vicons/fluent/TaskListLtr24Regular"
22 -import CodeBox from "@vicons/tabler/FileCode"
23 -import DoubleQuotes from "@vicons/fluent/TextQuote24Regular"
24 -import Separator from "@vicons/tabler/Separator"
25 -import TextWrap from "@vicons/fluent/TextWrap24Regular"
26 -import FormatClear from "@vicons/tabler/ClearFormatting"
27 -import ArrowBack from "@vicons/fluent/ArrowHookUpLeft24Regular"
28 -import ArrowForward from "@vicons/fluent/ArrowHookUpRight24Regular"
29 -import TextLeft from "@vicons/fluent/TextAlignLeft24Regular"
30 -import TextCenter from "@vicons/fluent/TextAlignCenter24Regular"
31 -import TextRight from "@vicons/fluent/TextAlignRight24Regular"
32 -import TextJustify from "@vicons/fluent/TextAlignJustify24Regular"
33 -import Link from "@vicons/fluent/Link24Regular"
8 +import Icon from "@/components/common/Icon.vue"
9 +
10 +const Bold = "fluent:text-bold-16-regular"
11 +const Italic = "fluent:text-italic-16-filled"
12 +const Strikethrough = "fluent:text-strikethrough-16-filled"
13 +const Underline = "fluent:text-underline-16-filled"
14 +const CodeView = "tabler:code"
15 +const MarkPen = "fluent:highlight-24-regular"
16 +const H1 = "fluent:text-header-1-24-filled"
17 +const H2 = "fluent:text-header-2-24-filled"
18 +const Paragraph = "carbon:paragraph"
19 +const ListUnordered = "fluent:apps-list-24-regular"
20 +const ListOrdered = "fluent:text-number-list-ltr-24-regular"
21 +const ListCheck = "fluent:task-list-ltr-24-regular"
22 +const CodeBox = "tabler:file-code"
23 +const DoubleQuotes = "fluent:text-quote-24-regular"
24 +const Separator = "tabler:separator"
25 +const TextWrap = "fluent:text-wrap-24-regular"
26 +const FormatClear = "tabler:clear-formatting"
27 +const ArrowBack = "fluent:arrow-hook-up-left-24-regular"
28 +const ArrowForward = "fluent:arrow-hook-up-right-24-regular"
29 +const TextLeft = "fluent:text-align-left-24-regular"
30 +const TextCenter = "fluent:text-align-center-24-regular"
31 +const TextRight = "fluent:text-align-right-24-regular"
32 +const TextJustify = "fluent:text-align-justify-24-regular"
33 +const Link = "fluent:link-24-regular"
34 import { computed, toRefs } from "vue"
35 -import type { Component } from "vue"
35
36 export interface ItemProps {
37 type?: string
@@ -48,6 +47,7 @@ const icons = {
47 bold: Bold,
48 italic: Italic,
49 strikethrough: Strikethrough,
50 + underline: Underline,
51 "code-view": CodeView,
52 "mark-pen-line": MarkPen,
53 "h-1": H1,
@@ -68,9 +68,9 @@ const icons = {
68 "text-align-right": TextRight,
69 "text-align-justify": TextJustify,
70 link: Link
71 -} as { [key: string]: Component }
71 +} as { [key: string]: string }
72
73 -const iconComponent = computed<Component>(() => icons[icon.value])
73 +const iconComponent = computed(() => icons[icon.value])
74 </script>
75
76 <style lang="scss">
@@ -93,7 +93,7 @@ const iconComponent = computed<Component>(() => icons[icon.value])
93
94 &.is-active,
95 &:hover {
96 - background-color: rgba(var(--primary-color-rgb), 0.05);
96 + background-color: var(--primary-005-color);
97 color: var(--primary-color);
98 }
99 }
src/components/editors/Tiptap/index.ts deleted
-2
@@ -1,2 +0,0 @@
1 -import Tiptap from "./Tiptap.vue"
2 -export default Tiptap
src/components/editors/Tiptap/index.vue renamed
+4 -4
@@ -8,18 +8,17 @@
8 </template>
9
10 <script lang="ts" setup>
11 +import { watch } from "vue"
12 import { NScrollbar } from "naive-ui"
12 -
13 import Highlight from "@tiptap/extension-highlight"
14 import TaskItem from "@tiptap/extension-task-item"
15 import TaskList from "@tiptap/extension-task-list"
16 import TextAlign from "@tiptap/extension-text-align"
17 +import Underline from "@tiptap/extension-underline"
18 import Link from "@tiptap/extension-link"
19 import StarterKit from "@tiptap/starter-kit"
20 import { useEditor, EditorContent } from "@tiptap/vue-3"
20 -
21 import MenuBar from "./MenuBar.vue"
22 -import { watch } from "vue"
22
23 const text = defineModel<string>({ default: "" })
24
@@ -30,6 +29,7 @@ const editor = useEditor({
29 Highlight,
30 TaskList,
31 TaskItem,
32 + Underline,
33 Link.configure({ openOnClick: false }),
34 TextAlign.configure({
35 types: ["heading", "paragraph"]
@@ -70,7 +70,7 @@ watch(text, val => {
70
71 &__header {
72 align-items: center;
73 - background: rgba(var(--fg-color-rgb), 0.01);
73 + background: var(--bg-secondary-color);
74 display: flex;
75 flex: 0 0 auto;
76 flex-wrap: wrap;
src/components/graylog/Alerts/Item.vue new
+151
@@ -0,0 +1,151 @@
1 +<template>
2 + <div class="item flex flex-col mb-2 gap-2 px-5 py-3">
3 + <div class="header-box flex justify-between">
4 + <div class="id">
5 + <n-popover overlap placement="bottom-start">
6 + <template #trigger>
7 + <div class="flex items-center gap-2 cursor-help">
8 + <span>#{{ alertsEvent.event.id }}</span>
9 + <Icon :name="InfoIcon" :size="16"></Icon>
10 + </div>
11 + </template>
12 + <div class="flex flex-col gap-1">
13 + <div class="box">
14 + event_definition_id:
15 + <code>{{ alertsEvent.event.event_definition_id }}</code>
16 + </div>
17 + <div class="box">
18 + event_definition_type:
19 + <code>{{ alertsEvent.event.event_definition_type }}</code>
20 + </div>
21 + <div class="box">
22 + source:
23 + <code>{{ alertsEvent.event.source }}</code>
24 + </div>
25 + <div class="box">
26 + index_name:
27 + <code>{{ alertsEvent.index_name }}</code>
28 + </div>
29 + <div class="box">
30 + index_type:
31 + <code>{{ alertsEvent.index_type }}</code>
32 + </div>
33 + <div class="box">
34 + timestamp:
35 + <code>{{ formatDate(alertsEvent.event.timestamp) }}</code>
36 + </div>
37 + <div class="box">
38 + timestamp processing:
39 + <code>{{ formatDate(alertsEvent.event.timestamp_processing) }}</code>
40 + </div>
41 + </div>
42 + </n-popover>
43 + </div>
44 + <div class="time">
45 + <n-popover overlap placement="bottom-end">
46 + <template #trigger>
47 + <div class="flex items-center gap-2 cursor-help">
48 + <span>
49 + {{ formatDate(alertsEvent.event.timestamp) }}
50 + </span>
51 + <Icon :name="TimeIcon" :size="16"></Icon>
52 + </div>
53 + </template>
54 + <div class="flex flex-col gap-1">
55 + <div class="box">
56 + timestamp:
57 + <code>{{ formatDate(alertsEvent.event.timestamp) }}</code>
58 + </div>
59 + <div class="box">
60 + timestamp processing:
61 + <code>{{ formatDate(alertsEvent.event.timestamp_processing) }}</code>
62 + </div>
63 + </div>
64 + </n-popover>
65 + </div>
66 + </div>
67 + <div class="main-box">
68 + <div class="content">{{ alertsEvent.event.message }}</div>
69 + </div>
70 + <div class="footer-box flex justify-end items-center gap-3">
71 + <div class="time">{{ formatDate(alertsEvent.event.timestamp) }}</div>
72 + </div>
73 + </div>
74 +</template>
75 +
76 +<script setup lang="ts">
77 +import { type AlertsEventElement } from "@/types/graylog/alerts.d"
78 +import { NPopover } from "naive-ui"
79 +import { useSettingsStore } from "@/stores/settings"
80 +import dayjs from "@/utils/dayjs"
81 +import Icon from "@/components/common/Icon.vue"
82 +
83 +const { alertsEvent } = defineProps<{ alertsEvent: AlertsEventElement }>()
84 +
85 +const InfoIcon = "carbon:information"
86 +const TimeIcon = "carbon:time"
87 +const dFormats = useSettingsStore().dateFormat
88 +
89 +function formatDate(timestamp: string): string {
90 + return dayjs(timestamp).format(dFormats.datetimesec)
91 +}
92 +</script>
93 +
94 +<style lang="scss" scoped>
95 +.item {
96 + border-radius: var(--border-radius);
97 + background-color: var(--bg-color);
98 + transition: all 0.2s var(--bezier-ease);
99 +
100 + .header-box {
101 + font-family: var(--font-family-mono);
102 + font-size: 13px;
103 + .id {
104 + word-break: break-word;
105 + color: var(--fg-secondary-color);
106 +
107 + &:hover {
108 + color: var(--primary-color);
109 + }
110 + }
111 + .time {
112 + color: var(--fg-secondary-color);
113 +
114 + &:hover {
115 + color: var(--primary-color);
116 + }
117 + }
118 + }
119 + .main-box {
120 + .content {
121 + word-break: break-word;
122 + }
123 + }
124 + .footer-box {
125 + font-family: var(--font-family-mono);
126 + font-size: 13px;
127 + margin-top: 10px;
128 + display: none;
129 +
130 + .time {
131 + text-align: right;
132 + color: var(--fg-secondary-color);
133 + }
134 + }
135 +
136 + &:hover {
137 + box-shadow: 0px 0px 0px 1px inset var(--primary-color);
138 + }
139 +
140 + @container (max-width: 650px) {
141 + .header-box {
142 + .time {
143 + display: none;
144 + }
145 + }
146 + .footer-box {
147 + display: flex;
148 + }
149 + }
150 +}
151 +</style>
src/components/graylog/Alerts/List.vue new
+208
@@ -0,0 +1,208 @@
1 +<template>
2 + <n-spin :show="loading">
3 + <div class="header flex items-center justify-end gap-2" ref="header">
4 + <div class="info grow flex gap-5">
5 + <n-popover overlap placement="bottom-start">
6 + <template #trigger>
7 + <div class="bg-color border-radius">
8 + <n-button size="small">
9 + <template #icon>
10 + <Icon :name="InfoIcon"></Icon>
11 + </template>
12 + </n-button>
13 + </div>
14 + </template>
15 + <div class="flex flex-col gap-2">
16 + <div class="box">
17 + Total:
18 + <code>{{ total }}</code>
19 + </div>
20 + <div class="box">
21 + Indicies:
22 + <code>{{ usedIndicies }}</code>
23 + </div>
24 + </div>
25 + </n-popover>
26 + </div>
27 + <n-pagination
28 + v-model:page="currentPage"
29 + v-model:page-size="pageSize"
30 + :item-count="total"
31 + :page-slot="pageSlot"
32 + :show-size-picker="showSizePicker"
33 + :page-sizes="pageSizes"
34 + :simple="simpleMode"
35 + />
36 + <n-select size="small" v-model:value="timerange" :options="timeOptions" class="!w-32" v-if="!compactMode" />
37 + <n-popover overlap v-if="compactMode" placement="right">
38 + <template #trigger>
39 + <div class="bg-color border-radius">
40 + <n-button size="small">
41 + <template #icon>
42 + <Icon :name="FilterIcon"></Icon>
43 + </template>
44 + </n-button>
45 + </div>
46 + </template>
47 + <div class="mb-2">
48 + <div class="opacity-50 text-sm my-1">Time range:</div>
49 + <n-select size="small" v-model:value="timerange" :options="timeOptions" class="!w-32 mb-1" />
50 + </div>
51 + </n-popover>
52 + </div>
53 + <div class="list my-3">
54 + <template v-if="alertsEvents.length">
55 + <AlertsEventItem
56 + v-for="alertsEvent of alertsEvents"
57 + :key="alertsEvent.event.id"
58 + :alertsEvent="alertsEvent"
59 + />
60 + </template>
61 + <template v-else>
62 + <n-empty description="No items found" v-if="!loading" />
63 + </template>
64 + </div>
65 + <div class="footer flex justify-end">
66 + <n-pagination
67 + v-model:page="currentPage"
68 + :page-size="pageSize"
69 + :item-count="total"
70 + :page-slot="6"
71 + v-if="alertsEvents.length > 3"
72 + />
73 + </div>
74 + </n-spin>
75 +</template>
76 +
77 +<script setup lang="ts">
78 +import { ref, onBeforeMount, watch, computed } from "vue"
79 +import { useMessage, NSpin, NPagination, NSelect, NPopover, NButton, NEmpty } from "naive-ui"
80 +import Api from "@/api"
81 +import AlertsEventItem from "./Item.vue"
82 +import { useResizeObserver } from "@vueuse/core"
83 +import dayjs from "@/utils/dayjs"
84 +import Icon from "@/components/common/Icon.vue"
85 +import type { AlertsQuery, AlertsEventElement } from "@/types/graylog/alerts.d"
86 +
87 +const message = useMessage()
88 +const loading = ref(false)
89 +const alertsEvents = ref<AlertsEventElement[]>([])
90 +const total = ref(0)
91 +const pageSize = ref(50)
92 +const currentPage = ref(1)
93 +const header = ref()
94 +const compactMode = ref(false)
95 +const simpleMode = ref(false)
96 +const showSizePicker = computed(() => !compactMode.value)
97 +const pageSizes = [25, 50, 100, 150, 200]
98 +const pageSlot = ref(8)
99 +const usedIndicies = ref("")
100 +
101 +const FilterIcon = "carbon:filter-edit"
102 +const InfoIcon = "carbon:information"
103 +
104 +const hour = 60 * 60
105 +const day = hour * 24
106 +const week = day * 7
107 +const month = week * 4
108 +const year = month * 12
109 +
110 +const timerange = ref(year)
111 +
112 +const timeOptions = [
113 + {
114 + label: "24 Hours",
115 + value: day
116 + },
117 + {
118 + label: "This week",
119 + value: dayjs().startOf("week").unix()
120 + },
121 + {
122 + label: "Last week",
123 + value: week
124 + },
125 + {
126 + label: "This month",
127 + value: dayjs().startOf("month").unix()
128 + },
129 + {
130 + label: "Last month",
131 + value: month
132 + },
133 + {
134 + label: "This year",
135 + value: dayjs().startOf("year").unix()
136 + },
137 + {
138 + label: "Last year",
139 + value: year
140 + }
141 +]
142 +
143 +function getData(page: number, pageSize: number, timerange: number) {
144 + loading.value = true
145 +
146 + const query: AlertsQuery = {
147 + query: "",
148 + page,
149 + per_page: pageSize,
150 + filter: {
151 + alerts: "only",
152 + event_definitions: []
153 + },
154 + timerange: {
155 + range: timerange,
156 + type: "relative"
157 + }
158 + }
159 +
160 + Api.graylog
161 + .getAlerts(query)
162 + .then(res => {
163 + if (res.data.success) {
164 + alertsEvents.value = res.data?.alerts?.events || []
165 + total.value = res.data?.alerts?.total_events || 0
166 + usedIndicies.value = res.data?.alerts?.used_indices?.join(", ")
167 + } else {
168 + message.warning(res.data?.message || "An error occurred. Please try again later.")
169 + }
170 + })
171 + .catch(err => {
172 + message.error(err.response?.data?.message || "An error occurred. Please try again later.")
173 + })
174 + .finally(() => {
175 + loading.value = false
176 + })
177 +}
178 +
179 +useResizeObserver(header, entries => {
180 + const entry = entries[0]
181 + const { width } = entry.contentRect
182 +
183 + if (width < 650) {
184 + compactMode.value = true
185 + pageSize.value = pageSizes[0]
186 + pageSlot.value = 5
187 + } else {
188 + compactMode.value = false
189 + pageSlot.value = 8
190 + }
191 +
192 + simpleMode.value = width < 450
193 +})
194 +
195 +watch([currentPage, pageSize, timerange], ([page, pageSize, timerange]) => {
196 + getData(page, pageSize, timerange)
197 +})
198 +
199 +onBeforeMount(() => {
200 + getData(currentPage.value, pageSize.value, timerange.value)
201 +})
202 +</script>
203 +
204 +<style lang="scss" scoped>
205 +.list {
206 + container-type: inline-size;
207 +}
208 +</style>
src/components/graylog/Events/Item.vue new
+155
@@ -0,0 +1,155 @@
1 +<template>
2 + <div class="item flex flex-col mb-2 gap-2 px-5 py-3">
3 + <div class="header-box flex justify-between">
4 + <div class="flex items-center gap-3">
5 + <n-tooltip trigger="hover">
6 + <template #trigger>
7 + <div class="priority cursor-help">
8 + {{ event.priority }}
9 + </div>
10 + </template>
11 + Priority
12 + </n-tooltip>
13 + <div class="id">
14 + <div class="flex items-center gap-2 cursor-pointer" @click="showDetails = true">
15 + <span>#{{ event.id }}</span>
16 + <Icon :name="InfoIcon" :size="16"></Icon>
17 + </div>
18 + </div>
19 + </div>
20 + <div class="notification">
21 + Notifications:
22 + <strong>{{ event.notifications.length }}</strong>
23 + </div>
24 + </div>
25 + <div class="main-box">
26 + <div class="title">{{ event.title }}</div>
27 + <div class="description">{{ event.description }}</div>
28 + </div>
29 + <div class="footer-box flex justify-end items-center gap-3">
30 + <div class="notification">
31 + Notifications:
32 + <strong>{{ event.notifications.length }}</strong>
33 + </div>
34 + </div>
35 +
36 + <n-modal
37 + v-model:show="showDetails"
38 + preset="card"
39 + content-style="padding:0px"
40 + :style="{ maxWidth: 'min(600px, 90vw)', overflow: 'hidden' }"
41 + :title="event.title"
42 + :bordered="false"
43 + segmented
44 + >
45 + <n-tabs type="line" animated justify-content="space-evenly">
46 + <n-tab-pane name="query" tab="Query" display-directive="show">
47 + <div class="p-7 pt-4">
48 + <n-input
49 + :value="event?.config?.query"
50 + type="textarea"
51 + readonly
52 + :autosize="{
53 + minRows: 3,
54 + maxRows: 10
55 + }"
56 + />
57 + </div>
58 + </n-tab-pane>
59 + <n-tab-pane name="fieldSpec" tab="Field Spec" display-directive="show:lazy">
60 + <div class="p-7 pt-4">
61 + <SimpleJsonViewer
62 + class="vuesjv-override"
63 + :model-value="event.field_spec"
64 + :initialExpandedDepth="1"
65 + />
66 + </div>
67 + </n-tab-pane>
68 + </n-tabs>
69 + </n-modal>
70 + </div>
71 +</template>
72 +
73 +<script setup lang="ts">
74 +import { ref } from "vue"
75 +import type { EventDefinition } from "@/types/graylog/event-definition.d"
76 +import Icon from "@/components/common/Icon.vue"
77 +import { SimpleJsonViewer } from "vue-sjv"
78 +import "@/assets/scss/vuesjv-override.scss"
79 +import { NModal, NTabs, NTabPane, NInput, NTooltip } from "naive-ui"
80 +
81 +const { event } = defineProps<{ event: EventDefinition }>()
82 +
83 +const InfoIcon = "carbon:information"
84 +
85 +const showDetails = ref(false)
86 +</script>
87 +
88 +<style lang="scss" scoped>
89 +.item {
90 + border-radius: var(--border-radius);
91 + background-color: var(--bg-color);
92 + transition: all 0.2s var(--bezier-ease);
93 +
94 + .header-box {
95 + font-family: var(--font-family-mono);
96 + font-size: 13px;
97 +
98 + .priority {
99 + background-color: var(--hover-005-color);
100 + border: var(--border-small-100);
101 + width: 20px;
102 + height: 20px;
103 + border-radius: 99999px;
104 + text-align: center;
105 + line-height: 20px;
106 + font-size: 12px;
107 + }
108 + .id {
109 + word-break: break-word;
110 + color: var(--fg-secondary-color);
111 +
112 + &:hover {
113 + color: var(--primary-color);
114 + }
115 + }
116 + .notification {
117 + color: var(--fg-secondary-color);
118 + }
119 + }
120 + .main-box {
121 + word-break: break-word;
122 +
123 + .description {
124 + color: var(--fg-secondary-color);
125 + font-size: 13px;
126 + }
127 + }
128 + .footer-box {
129 + font-family: var(--font-family-mono);
130 + font-size: 13px;
131 + margin-top: 10px;
132 + display: none;
133 +
134 + .notification {
135 + text-align: right;
136 + color: var(--fg-secondary-color);
137 + }
138 + }
139 +
140 + &:hover {
141 + box-shadow: 0px 0px 0px 1px inset var(--primary-color);
142 + }
143 +
144 + @container (max-width: 650px) {
145 + .header-box {
146 + .notification {
147 + display: none;
148 + }
149 + }
150 + .footer-box {
151 + display: flex;
152 + }
153 + }
154 +}
155 +</style>
src/components/graylog/Events/List.vue new
+106
@@ -0,0 +1,106 @@
1 +<template>
2 + <n-spin :show="loading">
3 + <div class="header flex items-center justify-end gap-2">
4 + <div class="info grow flex gap-5">
5 + <n-popover overlap placement="bottom-start">
6 + <template #trigger>
7 + <div class="bg-color border-radius">
8 + <n-button size="small">
9 + <template #icon>
10 + <Icon :name="InfoIcon"></Icon>
11 + </template>
12 + </n-button>
13 + </div>
14 + </template>
15 + <div class="flex flex-col gap-2">
16 + <div class="box">
17 + Total:
18 + <code>{{ total }}</code>
19 + </div>
20 + </div>
21 + </n-popover>
22 + </div>
23 + <n-select
24 + v-model:value="prioritySelected"
25 + :options="priorities"
26 + clearable
27 + placeholder="Priority..."
28 + size="small"
29 + style="width: 110px"
30 + />
31 + </div>
32 + <div class="list my-3">
33 + <template v-if="itemsPaginated.length">
34 + <EventItem v-for="event of itemsPaginated" :key="event.id" :event="event" />
35 + </template>
36 + <template v-else>
37 + <n-empty description="No items found" v-if="!loading" />
38 + </template>
39 + </div>
40 + </n-spin>
41 +</template>
42 +
43 +<script setup lang="ts">
44 +import { ref, onBeforeMount, computed } from "vue"
45 +import { useMessage, NSpin, NPopover, NButton, NSelect, NEmpty } from "naive-ui"
46 +import EventItem from "./Item.vue"
47 +import Api from "@/api"
48 +import Icon from "@/components/common/Icon.vue"
49 +import type { EventDefinition } from "@/types/graylog/event-definition.d"
50 +import type { SelectMixedOption } from "naive-ui/es/select/src/interface"
51 +
52 +const InfoIcon = "carbon:information"
53 +
54 +const message = useMessage()
55 +const total = ref(0)
56 +const loading = ref(false)
57 +const events = ref<EventDefinition[]>([])
58 +const priorities = computed<SelectMixedOption[]>(() =>
59 + [...new Set(events.value.map(o => o.priority))].map(o => ({
60 + label: "Priority " + o.toString(),
61 + value: o
62 + }))
63 +)
64 +const prioritySelected = ref<null | number>(null)
65 +
66 +const itemsPaginated = computed(() => {
67 + return events.value.filter(o => {
68 + if (!prioritySelected.value) {
69 + return true
70 + } else {
71 + return o.priority === prioritySelected.value
72 + }
73 + })
74 +})
75 +
76 +function getData() {
77 + loading.value = true
78 +
79 + Api.graylog
80 + .getEventDefinitions()
81 + .then(res => {
82 + if (res.data.success) {
83 + events.value = res.data.event_definitions || []
84 + total.value = events.value.length || 0
85 + } else {
86 + message.warning(res.data?.message || "An error occurred. Please try again later.")
87 + }
88 + })
89 + .catch(err => {
90 + message.error(err.response?.data?.message || "An error occurred. Please try again later.")
91 + })
92 + .finally(() => {
93 + loading.value = false
94 + })
95 +}
96 +
97 +onBeforeMount(() => {
98 + getData()
99 +})
100 +</script>
101 +
102 +<style lang="scss" scoped>
103 +.list {
104 + container-type: inline-size;
105 +}
106 +</style>
src/components/graylog/Inputs/Item.vue new
+299
@@ -0,0 +1,299 @@
1 +<template>
2 + <div class="item flex flex-col mb-2 gap-2 px-5 py-3">
3 + <div class="header-box flex justify-between">
4 + <div class="info flex items-center gap-2">
5 + <div class="user flex items-center gap-2">
6 + <Icon :name="UserIcon" :size="14"></Icon>
7 + {{ input.creator_user_id }}
8 + </div>
9 + </div>
10 + <div class="time">{{ formatDate(input.created_at) }}</div>
11 + </div>
12 + <div class="main-box flex justify-between">
13 + <div class="content">
14 + <div class="title">{{ input.title }}</div>
15 + <div class="name mb-2">{{ input.name }}</div>
16 + <div class="badges-box flex flex-wrap items-center gap-3">
17 + <div class="badge cursor" @click="showDetails = true">
18 + <Icon :name="InfoIcon" :size="14"></Icon>
19 + </div>
20 + <div class="badge" :class="{ active: input.global }">
21 + <span>Global</span>
22 + <Icon :name="input.global ? GlobalIcon : DisabledIcon" :size="14"></Icon>
23 + </div>
24 + <n-tooltip trigger="hover" :disabled="!isRunning">
25 + <template #trigger>
26 + <div class="badge" :class="{ active: isRunning, 'cursor-help': isRunning }">
27 + <span>Running</span>
28 + <Icon :name="isRunning ? TimeIcon : DisabledIcon" :size="14"></Icon>
29 + </div>
30 + </template>
31 + {{ formatDate(input.started_at) }}
32 + </n-tooltip>
33 + </div>
34 + </div>
35 +
36 + <div class="actions-box flex flex-col justify-end">
37 + <n-button @click="stop()" :loading="loading" v-if="isRunning">
38 + <template #icon><Icon :name="StopIcon"></Icon></template>
39 + Stop input
40 + </n-button>
41 + <n-button @click="start()" :loading="loading" v-else type="primary">
42 + <template #icon><Icon :name="StartIcon"></Icon></template>
43 + Start input
44 + </n-button>
45 + </div>
46 + </div>
47 + <div class="footer-box flex justify-between items-center">
48 + <div class="actions-box flex flex-col justify-end">
49 + <n-button @click="stop()" :loading="loading" v-if="isRunning" size="small">
50 + <template #icon><Icon :name="StopIcon"></Icon></template>
51 + Stop
52 + </n-button>
53 + <n-button @click="start()" :loading="loading" v-else type="primary" size="small">
54 + <template #icon><Icon :name="StartIcon"></Icon></template>
55 + Start
56 + </n-button>
57 + </div>
58 +
59 + <div class="time">{{ formatDate(input.created_at) }}</div>
60 + </div>
61 +
62 + <n-modal
63 + v-model:show="showDetails"
64 + preset="card"
65 + content-style="padding:0px"
66 + :style="{ maxWidth: 'min(600px, 90vw)', overflow: 'hidden' }"
67 + :title="input.title"
68 + :bordered="false"
69 + segmented
70 + >
71 + <n-tabs type="line" animated justify-content="space-evenly">
72 + <n-tab-pane name="info" tab="Info" display-directive="show:lazy">
73 + <div class="p-7 pt-4">
74 + <div class="mb-2">
75 + Id :
76 + <code>{{ input.id }}</code>
77 + </div>
78 + <div class="mb-2">
79 + Node :
80 + <code>{{ input.node }}</code>
81 + </div>
82 + <div class="mb-2">
83 + Type :
84 + <code>{{ input.type }}</code>
85 + </div>
86 + <div class="mb-2">
87 + Content pack :
88 + <code>{{ input.content_pack || "-" }}</code>
89 + </div>
90 + <div class="mb-2">Static fields :</div>
91 + <SimpleJsonViewer
92 + class="vuesjv-override"
93 + :model-value="input.static_fields"
94 + :initialExpandedDepth="1"
95 + />
96 + </div>
97 + </n-tab-pane>
98 + <n-tab-pane name="attributes" tab="Attributes" display-directive="show:lazy">
99 + <div class="p-7 pt-4">
100 + <SimpleJsonViewer
101 + class="vuesjv-override"
102 + :model-value="input.attributes"
103 + :initialExpandedDepth="1"
104 + />
105 + </div>
106 + </n-tab-pane>
107 + </n-tabs>
108 + </n-modal>
109 + </div>
110 +</template>
111 +
112 +<script setup lang="ts">
113 +import { useSettingsStore } from "@/stores/settings"
114 +import Icon from "@/components/common/Icon.vue"
115 +import dayjs from "@/utils/dayjs"
116 +import { NModal, NButton, useMessage, NTooltip, NTabs, NTabPane } from "naive-ui"
117 +import { computed, ref } from "vue"
118 +import { SimpleJsonViewer } from "vue-sjv"
119 +import "@/assets/scss/vuesjv-override.scss"
120 +import Api from "@/api"
121 +import type { InputExtended } from "@/types/graylog/inputs.d"
122 +
123 +const emit = defineEmits<{
124 + (e: "updated"): void
125 +}>()
126 +
127 +const { input } = defineProps<{ input: InputExtended }>()
128 +
129 +const UserIcon = "carbon:user"
130 +const InfoIcon = "carbon:information"
131 +const DisabledIcon = "ph:minus-bold"
132 +const TimeIcon = "carbon:time"
133 +const GlobalIcon = "ph:globe-light"
134 +const StopIcon = "carbon:stop"
135 +const StartIcon = "carbon:play"
136 +
137 +const message = useMessage()
138 +const loading = ref(false)
139 +const showDetails = ref(false)
140 +const isRunning = computed(() => input?.state === "RUNNING")
141 +const dFormats = useSettingsStore().dateFormat
142 +
143 +function formatDate(timestamp: string): string {
144 + return dayjs(timestamp).format(dFormats.datetimesec)
145 +}
146 +
147 +function stop() {
148 + loading.value = true
149 +
150 + Api.graylog
151 + .stopInput(input.id)
152 + .then(res => {
153 + if (res.data.success) {
154 + emit("updated")
155 + message.success(res.data?.message || "Successfully stopped input.")
156 + } else {
157 + message.warning(res.data?.message || "An error occurred. Please try again later.")
158 + }
159 + })
160 + .catch(err => {
161 + message.error(err.response?.data?.message || "An error occurred. Please try again later.")
162 + })
163 + .finally(() => {
164 + loading.value = false
165 + })
166 +}
167 +
168 +function start() {
169 + loading.value = true
170 +
171 + Api.graylog
172 + .startInput(input.id)
173 + .then(res => {
174 + if (res.data.success) {
175 + emit("updated")
176 + message.success(res.data?.message || "Successfully started input.")
177 + } else {
178 + message.warning(res.data?.message || "An error occurred. Please try again later.")
179 + }
180 + })
181 + .catch(err => {
182 + message.error(err.response?.data?.message || "An error occurred. Please try again later.")
183 + })
184 + .finally(() => {
185 + loading.value = false
186 + })
187 +}
188 +</script>
189 +
190 +<style lang="scss" scoped>
191 +.item {
192 + border-radius: var(--border-radius);
193 + background-color: var(--bg-secondary-color);
194 + transition: all 0.2s var(--bezier-ease);
195 + border: var(--border-small-100);
196 +
197 + .header-box {
198 + font-family: var(--font-family-mono);
199 + font-size: 13px;
200 + .user {
201 + word-break: break-word;
202 + color: var(--fg-secondary-color);
203 + }
204 + .time {
205 + color: var(--fg-secondary-color);
206 + }
207 + }
208 + .main-box {
209 + word-break: break-word;
210 +
211 + .name {
212 + color: var(--fg-secondary-color);
213 + font-size: 13px;
214 + }
215 +
216 + .badges-box {
217 + .badge {
218 + border-radius: var(--border-radius);
219 + border: var(--border-small-100);
220 + display: flex;
221 + align-items: center;
222 + font-size: 14px;
223 + padding: 0px 6px;
224 + height: 26px;
225 + line-height: 1;
226 + gap: 6px;
227 + transition: all 0.3s var(--bezier-ease);
228 +
229 + span,
230 + i {
231 + opacity: 0.5;
232 + }
233 +
234 + &.active {
235 + color: var(--primary-color);
236 + background-color: var(--primary-005-color);
237 +
238 + span,
239 + i {
240 + opacity: 1;
241 + }
242 +
243 + border-color: var(--primary-color);
244 + }
245 +
246 + &.cursor {
247 + cursor: pointer;
248 +
249 + i {
250 + opacity: 1;
251 + }
252 +
253 + &:hover {
254 + color: var(--primary-color);
255 + border-color: var(--primary-color);
256 + }
257 + }
258 + }
259 + }
260 + }
261 +
262 + .footer-box {
263 + display: none;
264 + text-align: right;
265 + font-size: 13px;
266 + margin-top: 10px;
267 +
268 + .time {
269 + font-family: var(--font-family-mono);
270 + color: var(--fg-secondary-color);
271 + width: 100%;
272 + }
273 + }
274 +
275 + &.default {
276 + background-color: var(--primary-005-color);
277 + box-shadow: 0px 0px 0px 1px inset var(--primary-030-color);
278 + }
279 + &:hover {
280 + box-shadow: 0px 0px 0px 1px inset var(--primary-color);
281 + }
282 +
283 + @container (max-width: 650px) {
284 + .header-box {
285 + .time {
286 + display: none;
287 + }
288 + }
289 + .main-box {
290 + .actions-box {
291 + display: none;
292 + }
293 + }
294 + .footer-box {
295 + display: flex;
296 + }
297 + }
298 +}
299 +</style>
src/components/graylog/Inputs/List.vue new
+170
@@ -0,0 +1,170 @@
1 +<template>
2 + <n-spin :show="loading" class="flex flex-col">
3 + <div class="header flex items-center justify-end gap-2">
4 + <div class="info grow flex gap-5">
5 + <n-popover overlap placement="bottom-start">
6 + <template #trigger>
7 + <div class="bg-color border-radius">
8 + <n-button size="small">
9 + <template #icon>
10 + <Icon :name="InfoIcon"></Icon>
11 + </template>
12 + </n-button>
13 + </div>
14 + </template>
15 + <div class="flex flex-col gap-2">
16 + <div class="box">
17 + Total:
18 + <code>{{ total }}</code>
19 + </div>
20 + <div class="box">
21 + Running:
22 + <code>{{ totalRunning }}</code>
23 + </div>
24 + </div>
25 + </n-popover>
26 + </div>
27 + <n-select
28 + v-model:value="stateFilter"
29 + :options="stateOptions"
30 + clearable
31 + placeholder="State..."
32 + size="small"
33 + style="width: 125px"
34 + />
35 + </div>
36 + <n-scrollbar class="my-3">
37 + <div class="list">
38 + <template v-if="itemsFiltered.length">
39 + <InputItem
40 + v-for="input of itemsFiltered"
41 + :key="input.id"
42 + :input="input"
43 + @updated="getData('running')"
44 + />
45 + </template>
46 + <template v-else>
47 + <n-empty description="No items found" v-if="!loading" />
48 + </template>
49 + </div>
50 + </n-scrollbar>
51 + </n-spin>
52 +</template>
53 +
54 +<script setup lang="ts">
55 +import { ref, onBeforeMount, computed } from "vue"
56 +import { useMessage, NSpin, NPopover, NButton, NSelect, NEmpty, NScrollbar } from "naive-ui"
57 +import Api from "@/api"
58 +import InputItem from "./Item.vue"
59 +import Icon from "@/components/common/Icon.vue"
60 +import type { ConfiguredInput, InputExtended, RunningInput } from "@/types/graylog/inputs.d"
61 +
62 +const InfoIcon = "carbon:information"
63 +
64 +const message = useMessage()
65 +const loading = ref(false)
66 +const configuredInputs = ref<ConfiguredInput[]>([])
67 +const runningInputs = ref<RunningInput[]>([])
68 +
69 +const total = computed(() => configuredInputs.value.length)
70 +const totalRunning = computed(() => runningInputs.value.length)
71 +
72 +const stateFilter = ref<null | number>(null)
73 +const stateOptions = [
74 + { label: "Not Running", value: 0 },
75 + { label: "Running", value: 1 }
76 +]
77 +
78 +const itemsSanitized = computed<InputExtended[]>(() => {
79 + return configuredInputs.value.map(c => {
80 + const runItem = runningInputs.value.find(r => r.id === c.id)
81 + const res = c as InputExtended
82 +
83 + res.state = runItem?.state || ""
84 + res.started_at = runItem?.started_at || ""
85 + res.detailed_message = runItem?.detailed_message || null
86 +
87 + return res
88 + })
89 +})
90 +
91 +const itemsFiltered = computed(() => {
92 + return itemsSanitized.value.filter(o => {
93 + switch (stateFilter.value) {
94 + case 1:
95 + return o.state === "RUNNING"
96 + case 0:
97 + return o.state === ""
98 + default:
99 + return true
100 + }
101 + })
102 +})
103 +
104 +function getData(type: "configured" | "running") {
105 + loading.value = true
106 +
107 + const endpoint = type === "configured" ? "getInputsConfigured" : "getInputsRunning"
108 +
109 + Api.graylog[endpoint]()
110 + .then(res => {
111 + if (res.data.success) {
112 + const data = res.data as {
113 + configured_inputs?: ConfiguredInput[]
114 + running_inputs?: RunningInput[]
115 + }
116 +
117 + if (data.configured_inputs !== undefined) {
118 + configuredInputs.value = data?.configured_inputs || []
119 + }
120 + if (data.running_inputs !== undefined) {
121 + runningInputs.value = data?.running_inputs || []
122 + }
123 + } else {
124 + message.warning(res.data?.message || "An error occurred. Please try again later.")
125 + }
126 + })
127 + .catch(err => {
128 + message.error(err.response?.data?.message || "An error occurred. Please try again later.")
129 + })
130 + .finally(() => {
131 + loading.value = false
132 + })
133 +}
134 +
135 +onBeforeMount(() => {
136 + getData("configured")
137 + getData("running")
138 +})
139 +</script>
140 +
141 +<style lang="scss" scoped>
142 +.n-spin-container {
143 + height: 100%;
144 + max-height: 100%;
145 + overflow: hidden;
146 + box-sizing: border-box;
147 +
148 + :deep() {
149 + .n-spin-content {
150 + height: 100%;
151 + box-sizing: border-box;
152 + display: flex;
153 + flex-direction: column;
154 + }
155 + }
156 +}
157 +
158 +.header {
159 + padding: var(--n-header-padding);
160 + box-sizing: border-box;
161 + padding-bottom: 0;
162 +}
163 +.list {
164 + padding: var(--n-body-padding);
165 + padding-top: 0;
166 + padding-bottom: 0;
167 + container-type: inline-size;
168 + box-sizing: border-box;
169 +}
170 +</style>
src/components/graylog/Messages/Item.vue new
+80
@@ -0,0 +1,80 @@
1 +<template>
2 + <div class="item flex flex-col mb-2 gap-2 px-5 py-3">
3 + <div class="header-box flex justify-between">
4 + <div class="caller">{{ message.caller }}</div>
5 + <div class="time">{{ formatDate(message.timestamp) }}</div>
6 + </div>
7 + <div class="main-box">
8 + <div class="content">{{ message.content }}</div>
9 + </div>
10 + <div class="footer-box">
11 + <div class="time">{{ formatDate(message.timestamp) }}</div>
12 + </div>
13 + </div>
14 +</template>
15 +
16 +<script setup lang="ts">
17 +import { type Message } from "@/types/graylog/index.d"
18 +import { useSettingsStore } from "@/stores/settings"
19 +import dayjs from "@/utils/dayjs"
20 +
21 +const { message } = defineProps<{ message: Message }>()
22 +
23 +const dFormats = useSettingsStore().dateFormat
24 +
25 +function formatDate(timestamp: string): string {
26 + return dayjs(timestamp).format(dFormats.datetimesec)
27 +}
28 +</script>
29 +
30 +<style lang="scss" scoped>
31 +.item {
32 + border-radius: var(--border-radius);
33 + background-color: var(--bg-color);
34 + transition: all 0.2s var(--bezier-ease);
35 +
36 + .header-box {
37 + font-family: var(--font-family-mono);
38 + font-size: 13px;
39 + .caller {
40 + word-break: break-word;
41 + color: var(--fg-secondary-color);
42 + }
43 + .time {
44 + color: var(--fg-secondary-color);
45 + }
46 + }
47 + .main-box {
48 + .content {
49 + word-break: break-word;
50 + }
51 + }
52 + .footer-box {
53 + font-family: var(--font-family-mono);
54 + display: none;
55 + text-align: right;
56 + font-size: 13px;
57 + margin-top: 10px;
58 +
59 + .time {
60 + color: var(--fg-secondary-color);
61 + width: 100%;
62 + }
63 + }
64 +
65 + &:hover {
66 + box-shadow: 0px 0px 0px 1px inset var(--primary-color);
67 + }
68 +
69 + @container (max-width: 650px) {
70 + .header-box {
71 + .time {
72 + display: none;
73 + }
74 + }
75 + .footer-box {
76 + display: flex;
77 + }
78 + }
79 +}
80 +</style>
src/components/graylog/Messages/List.vue new
+102
@@ -0,0 +1,102 @@
1 +<template>
2 + <n-spin :show="loading">
3 + <div class="header flex items-center justify-end gap-2">
4 + <div class="info grow flex gap-5">
5 + <n-popover overlap placement="bottom-start">
6 + <template #trigger>
7 + <div class="bg-color border-radius">
8 + <n-button size="small">
9 + <template #icon>
10 + <Icon :name="InfoIcon"></Icon>
11 + </template>
12 + </n-button>
13 + </div>
14 + </template>
15 + <div class="flex flex-col gap-2">
16 + <div class="box">
17 + Total:
18 + <code>{{ total }}</code>
19 + </div>
20 + </div>
21 + </n-popover>
22 + </div>
23 + <n-pagination v-model:page="currentPage" :page-size="pageSize" :item-count="total" :page-slot="5" />
24 + </div>
25 + <div class="list my-3">
26 + <template v-if="messages.length">
27 + <MessageItem v-for="msg of messages" :key="msg.id" :message="msg" />
28 + </template>
29 + <template v-else>
30 + <n-empty description="No items found" v-if="!loading" />
31 + </template>
32 + </div>
33 + <div class="footer flex justify-end">
34 + <n-pagination
35 + v-model:page="currentPage"
36 + :page-size="pageSize"
37 + :item-count="total"
38 + :page-slot="6"
39 + v-if="messages.length > 3"
40 + />
41 + </div>
42 + </n-spin>
43 +</template>
44 +
45 +<script setup lang="ts">
46 +import { ref, onBeforeMount, watch } from "vue"
47 +import { useMessage, NSpin, NPagination, NPopover, NButton, NEmpty } from "naive-ui"
48 +import Api from "@/api"
49 +import MessageItem from "./Item.vue"
50 +import Icon from "@/components/common/Icon.vue"
51 +import { nanoid } from "nanoid"
52 +import type { MessageExtended } from "@/types/graylog/index.d"
53 +
54 +const InfoIcon = "carbon:information"
55 +
56 +const message = useMessage()
57 +const loading = ref(false)
58 +const messages = ref<MessageExtended[]>([])
59 +const total = ref(0)
60 +const pageSize = ref(1)
61 +const currentPage = ref(1)
62 +
63 +function getData(page: number) {
64 + loading.value = true
65 +
66 + Api.graylog
67 + .getMessages(page)
68 + .then(res => {
69 + if (res.data.success) {
70 + const data = (res.data.graylog_messages || []) as MessageExtended[]
71 + messages.value = data.map(o => {
72 + o.id = nanoid()
73 + return o
74 + })
75 + total.value = res.data.total_messages || 0
76 + if (pageSize.value <= 1) pageSize.value = messages.value.length
77 + } else {
78 + message.warning(res.data?.message || "An error occurred. Please try again later.")
79 + }
80 + })
81 + .catch(err => {
82 + message.error(err.response?.data?.message || "An error occurred. Please try again later.")
83 + })
84 + .finally(() => {
85 + loading.value = false
86 + })
87 +}
88 +
89 +watch(currentPage, val => {
90 + getData(val)
91 +})
92 +
93 +onBeforeMount(() => {
94 + getData(currentPage.value)
95 +})
96 +</script>
97 +
98 +<style lang="scss" scoped>
99 +.list {
100 + container-type: inline-size;
101 +}
102 +</style>
src/components/graylog/Metrics/List.vue new
+106
@@ -0,0 +1,106 @@
1 +<template>
2 + <div class="metrics-list">
3 + <n-card
4 + v-for="group of sanitizedMetrics"
5 + :key="group.groupName"
6 + :title="group.groupName"
7 + size="small"
8 + segmented
9 + class="metrics-group"
10 + content-style="padding:0"
11 + >
12 + <div class="list">
13 + <div
14 + v-for="metric of group.throughputMetrics"
15 + :key="metric.metric"
16 + class="flex items-center gap-4 metric-wrap"
17 + >
18 + <div class="metric basis-2/3">
19 + {{ metric.metric }}
20 + </div>
21 + <div class="value basis-1/3">
22 + <n-progress type="line" status="success" :percentage="metric.percentage">
23 + <span class="font-mono">
24 + {{ metric.value }}
25 + </span>
26 + </n-progress>
27 + </div>
28 + </div>
29 + </div>
30 + </n-card>
31 + </div>
32 +</template>
33 +
34 +<script setup lang="ts">
35 +import { toRefs, computed } from "vue"
36 +import { NCard, NProgress } from "naive-ui"
37 +import type { ThroughputMetric } from "@/types/graylog/index.d"
38 +import _groupBy from "lodash/groupBy"
39 +import _map from "lodash/map"
40 +import _trim from "lodash/trim"
41 +
42 +interface Metrics {
43 + groupName: string
44 + throughputMetrics: (ThroughputMetric & { name: string; percentage: number })[]
45 +}
46 +
47 +const props = defineProps<{
48 + throughputMetrics: ThroughputMetric[]
49 +}>()
50 +const { throughputMetrics } = toRefs(props)
51 +
52 +const sanitizedMetrics = computed<Metrics[]>(() => {
53 + return sanitizeMetrics(throughputMetrics.value)
54 +})
55 +
56 +function sanitizeMetrics(metrics: ThroughputMetric[]): Metrics[] {
57 + const keywords = ["input", "output", "process"]
58 +
59 + const tempData = metrics.map(o => {
60 + const obj = { ...o } as ThroughputMetric & { name: string; percentage: number }
61 + obj.name = obj.metric
62 + for (const key of keywords) {
63 + obj.name = _trim(obj.name.replace(key, "").replace("..", "."), ".")
64 + }
65 + return obj
66 + })
67 +
68 + const groups = _groupBy(tempData, "name")
69 +
70 + return _map(groups, group => {
71 + const max = Math.max(...group.map(g => g.value)) || 1
72 +
73 + for (const m of group) {
74 + m.percentage = (m.value / max) * 100
75 + }
76 +
77 + const groupObj: Metrics = {
78 + groupName: group[0].name,
79 + throughputMetrics: group
80 + }
81 + return groupObj
82 + })
83 +}
84 +</script>
85 +
86 +<style lang="scss" scoped>
87 +.metrics-list {
88 + .metrics-group {
89 + @apply mb-6;
90 +
91 + .list {
92 + background-color: var(--bg-secondary-color);
93 + .metric-wrap {
94 + @apply py-3 px-4;
95 + .metric {
96 + line-height: 1.1;
97 + }
98 +
99 + &:not(:last-child) {
100 + border-bottom: var(--border-small-100);
101 + }
102 + }
103 + }
104 + }
105 +}
106 +</style>
src/components/graylog/Metrics/UncommittedEntries.vue new
+84
@@ -0,0 +1,84 @@
1 +<template>
2 + <div class="uncommitted-entries-wrap">
3 + <div class="line"></div>
4 + <div class="uncommitted-entries">
5 + <div class="label flex items-center gap-3">
6 + <Icon :name="DangerIcon" v-if="isWarning"></Icon>
7 + <span>Uncommitted Journal Entries</span>
8 + </div>
9 + <div class="value" :class="{ warning: isWarning }">
10 + <span>{{ value }}</span>
11 + </div>
12 + </div>
13 + </div>
14 +</template>
15 +
16 +<script setup lang="ts">
17 +import Icon from "@/components/common/Icon.vue"
18 +import { computed, toRefs } from "vue"
19 +
20 +const props = defineProps<{
21 + value: number
22 +}>()
23 +const { value } = toRefs(props)
24 +
25 +const DangerIcon = "majesticons:exclamation-line"
26 +
27 +const isWarning = computed<boolean>(() => {
28 + return value.value > 50000
29 +})
30 +</script>
31 +
32 +<style lang="scss" scoped>
33 +.uncommitted-entries-wrap {
34 + width: 100%;
35 + position: relative;
36 + .line {
37 + width: 100%;
38 + height: 1px;
39 + background: var(--divider-010-color);
40 + background: linear-gradient(
41 + 90deg,
42 + transparent 0%,
43 + var(--divider-010-color) 5%,
44 + var(--divider-010-color) 95%,
45 + transparent 100%
46 + );
47 + position: absolute;
48 + top: 50%;
49 + }
50 +
51 + .uncommitted-entries {
52 + position: relative;
53 + background-color: var(--bg-color);
54 + display: flex;
55 + border-radius: var(--border-radius);
56 + border: var(--border-small-050);
57 + overflow: hidden;
58 + max-width: 400px;
59 + margin: 0 auto;
60 +
61 + .label {
62 + padding: 18px 22px;
63 + font-size: 18px;
64 + flex-grow: 1;
65 + font-weight: 700;
66 +
67 + i {
68 + color: var(--secondary3-color);
69 + }
70 + }
71 + .value {
72 + padding: 18px 22px;
73 + background-color: var(--bg-secondary-color);
74 + font-size: 20px;
75 + font-family: var(--font-family-mono);
76 +
77 + &.warning {
78 + color: var(--secondary3-color);
79 + background-color: var(--secondary3-opacity-005-color);
80 + }
81 + }
82 + }
83 +}
84 +</style>
src/components/graylog/Pipelines/PipeDetails.vue new
+110
@@ -0,0 +1,110 @@
1 +<template>
2 + <div class="pipe-details flex flex-wrap justify-between gap-1">
3 + <div class="description">
4 + <p v-if="pipeline.description">
5 + {{ pipeline.description }}
6 + </p>
7 + </div>
8 +
9 + <div class="time">
10 + {{ formatDate(pipeline.modified_at) }}
11 + </div>
12 + </div>
13 +
14 + <n-scrollbar x-scrollable trigger="none" class="mt-5">
15 + <n-timeline horizontal size="large" style="width: max-content" class="mb-4">
16 + <n-timeline-item
17 + v-for="stage of stages"
18 + :key="stage.stage"
19 + :type="stage.match === 'EITHER' ? undefined : 'info'"
20 + :title="`Stage ${stage.stage}`"
21 + >
22 + <p class="mb-1">
23 + {{ stage.match }}
24 + </p>
25 + <n-popover trigger="click" style="max-height: 240px" scrollable placement="bottom">
26 + <template #trigger>
27 + <n-button size="tiny">
28 + <template #icon>
29 + <Icon :name="RulesIcon" :size="18"></Icon>
30 + </template>
31 + Rules
32 + <span class="font-mono ml-2 opacity-60">{{ stage.rules.length }}</span>
33 + </n-button>
34 + </template>
35 +
36 + <RulesSmallList :rules="stage.rules" style="margin: 0 -10px" @click="emit('clickRule', $event)" />
37 + </n-popover>
38 + </n-timeline-item>
39 + </n-timeline>
40 + </n-scrollbar>
41 +</template>
42 +
43 +<script setup lang="ts">
44 +import { NTimeline, NTimelineItem, NButton, NScrollbar, NPopover } from "naive-ui"
45 +import { computed, toRefs } from "vue"
46 +import type { PipelineFull, PipelineFullStage } from "@/types/graylog/pipelines.d"
47 +import Icon from "@/components/common/Icon.vue"
48 +import RulesSmallList, { type RuleExtended } from "./RulesSmallList.vue"
49 +import dayjs from "@/utils/dayjs"
50 +import { useSettingsStore } from "@/stores/settings"
51 +
52 +interface PipelineFullStageExt extends Omit<PipelineFullStage, "rules" | "rule_ids"> {
53 + rules: RuleExtended[]
54 +}
55 +
56 +const emit = defineEmits<{
57 + (e: "clickRule", value: string): void
58 +}>()
59 +
60 +const props = defineProps<{ pipeline: PipelineFull }>()
61 +const { pipeline } = toRefs(props)
62 +
63 +const TimeIcon = "carbon:time"
64 +const RulesIcon = "ic:outline-swipe-right-alt"
65 +
66 +const dFormats = useSettingsStore().dateFormat
67 +
68 +function sanitizeStage(stage: PipelineFullStage): PipelineFullStageExt {
69 + const rules: RuleExtended[] = []
70 +
71 + for (const i in stage.rules) {
72 + rules.push({
73 + title: stage.rules[i],
74 + id: stage.rule_ids[i]
75 + })
76 + }
77 +
78 + const stageExt: PipelineFullStageExt = {
79 + rules,
80 + match: stage.match,
81 + stage: stage.stage
82 + }
83 +
84 + return stageExt
85 +}
86 +
87 +const stages = computed<PipelineFullStageExt[]>(() => {
88 + const stages: PipelineFullStageExt[] = []
89 +
90 + for (const stage of pipeline.value.stages) {
91 + stages.push(sanitizeStage(stage))
92 + }
93 +
94 + return stages
95 +})
96 +
97 +function formatDate(timestamp: string): string {
98 + return dayjs(timestamp).format(dFormats.datetimesec)
99 +}
100 +</script>
101 +
102 +<style lang="scss" scoped>
103 +.time {
104 + font-family: var(--font-family-mono);
105 + font-size: 13px;
106 + text-align: right;
107 + color: var(--fg-secondary-color);
108 + line-height: 1.6;
109 +}
110 +</style>
src/components/graylog/Pipelines/PipeInfo.vue new
+53
@@ -0,0 +1,53 @@
1 +<template>
2 + <n-tabs type="line" animated justify-content="space-evenly">
3 + <n-tab-pane name="info" tab="Info" display-directive="show">
4 + <div class="p-7 pt-4">
5 + <div class="mb-2">
6 + Id :
7 + <code>{{ pipeline?.id }}</code>
8 + </div>
9 + <div class="mb-2">
10 + Created:
11 + <code>{{ pipeline?.created_at ? formatDate(pipeline.created_at) : "-" }}</code>
12 + </div>
13 + <div class="mb-2">
14 + Modified:
15 + <code>{{ pipeline?.modified_at ? formatDate(pipeline.modified_at) : "-" }}</code>
16 + </div>
17 + <div class="mb-2">
18 + Errors :
19 + <code>{{ pipeline?.errors || "-" }}</code>
20 + </div>
21 + </div>
22 + </n-tab-pane>
23 + <n-tab-pane name="source" tab="Source" display-directive="show">
24 + <div class="p-7 pt-4">
25 + <n-input
26 + :value="pipeline?.source"
27 + type="textarea"
28 + readonly
29 + :autosize="{
30 + minRows: 3,
31 + maxRows: 10
32 + }"
33 + />
34 + </div>
35 + </n-tab-pane>
36 + </n-tabs>
37 +</template>
38 +
39 +<script setup lang="ts">
40 +import { NTabs, NTabPane, NInput } from "naive-ui"
41 +import { toRefs } from "vue"
42 +import type { Pipeline } from "@/types/graylog/pipelines.d"
43 +import { useSettingsStore } from "@/stores/settings"
44 +import dayjs from "@/utils/dayjs"
45 +
46 +const props = defineProps<{ pipeline?: Pipeline }>()
47 +const { pipeline } = toRefs(props)
48 +const dFormats = useSettingsStore().dateFormat
49 +
50 +function formatDate(timestamp: string): string {
51 + return dayjs(timestamp).format(dFormats.datetimesec)
52 +}
53 +</script>
src/components/graylog/Pipelines/PipeTitle.vue new
+37
@@ -0,0 +1,37 @@
1 +<template>
2 + <div class="pipe-title flex items-center gap-1" :class="{ warning: isWarning }">
3 + <n-tooltip placement="top-start" trigger="hover" v-if="isWarning">
4 + <template #trigger>
5 + <Icon :name="DangerIcon" :size="18"></Icon>
6 + </template>
7 + Open Info dialog to see errors
8 + </n-tooltip>
9 + <span>{{ pipeline?.title }}</span>
10 + </div>
11 +</template>
12 +
13 +<script setup lang="ts">
14 +import { NTooltip } from "naive-ui"
15 +import { computed, toRefs } from "vue"
16 +import type { Pipeline } from "@/types/graylog/pipelines.d"
17 +import Icon from "@/components/common/Icon.vue"
18 +
19 +const props = defineProps<{ pipeline: Pipeline }>()
20 +const { pipeline } = toRefs(props)
21 +
22 +const DangerIcon = "majesticons:exclamation-line"
23 +
24 +const isWarning = computed<boolean>(() => {
25 + return !!pipeline.value.errors
26 +})
27 +</script>
28 +
29 +<style lang="scss" scoped>
30 +.pipe-title {
31 + line-height: 1.1;
32 +
33 + &.warning {
34 + color: var(--secondary3-color);
35 + }
36 +}
37 +</style>
src/components/graylog/Pipelines/Rule.vue new
+170
@@ -0,0 +1,170 @@
1 +<template>
2 + <div class="item flex flex-col mb-2 gap-2 px-5 py-3" :class="{ highlight }" :id="'rule-' + rule.id">
3 + <div class="header-box flex justify-between">
4 + <div class="flex items-center gap-3">
5 + <div class="id">
6 + <div class="flex items-center gap-2 cursor-pointer" @click="showDetails = true">
7 + <span>#{{ rule.id }}</span>
8 + <Icon :name="InfoIcon" :size="16"></Icon>
9 + </div>
10 + </div>
11 + </div>
12 + <div class="time">
13 + <n-popover overlap placement="bottom-end">
14 + <template #trigger>
15 + <div class="flex items-center gap-2 cursor-help">
16 + <span>
17 + {{ formatDate(rule.modified_at) }}
18 + </span>
19 + <Icon :name="TimeIcon" :size="16"></Icon>
20 + </div>
21 + </template>
22 + <div class="flex flex-col gap-1">
23 + <div class="box">
24 + created:
25 + <code>{{ formatDate(rule.created_at) }}</code>
26 + </div>
27 + <div class="box">
28 + modified:
29 + <code>{{ formatDate(rule.modified_at) }}</code>
30 + </div>
31 + </div>
32 + </n-popover>
33 + </div>
34 + </div>
35 + <div class="main-box">
36 + <div class="title">{{ rule.title }}</div>
37 + <div class="description">{{ rule.description }}</div>
38 + </div>
39 + <div class="footer-box flex justify-end items-center gap-3">
40 + <div class="time">
41 + {{ formatDate(rule.modified_at) }}
42 + </div>
43 + </div>
44 +
45 + <n-modal
46 + v-model:show="showDetails"
47 + preset="card"
48 + content-style="padding:0px"
49 + :style="{ maxWidth: 'min(600px, 90vw)', overflow: 'hidden' }"
50 + :title="rule.title"
51 + :bordered="false"
52 + segmented
53 + >
54 + <div class="p-7 pt-4">
55 + <div class="mb-2">
56 + Created:
57 + <code>{{ formatDate(rule.created_at) }}</code>
58 + </div>
59 + <div class="mb-2">
60 + Modified:
61 + <code>{{ formatDate(rule.modified_at) }}</code>
62 + </div>
63 + <div class="mb-2">
64 + Errors :
65 + <code>{{ rule.errors || "-" }}</code>
66 + </div>
67 + <div class="mb-1">Source :</div>
68 + <n-input
69 + :value="rule.source"
70 + type="textarea"
71 + readonly
72 + :autosize="{
73 + minRows: 3,
74 + maxRows: 10
75 + }"
76 + />
77 + </div>
78 + </n-modal>
79 + </div>
80 +</template>
81 +
82 +<script setup lang="ts">
83 +import { ref, toRefs } from "vue"
84 +import { NModal, NInput, NPopover } from "naive-ui"
85 +import Icon from "@/components/common/Icon.vue"
86 +import type { PipelineRule } from "@/types/graylog/pipelines.d"
87 +import { useSettingsStore } from "@/stores/settings"
88 +import dayjs from "@/utils/dayjs"
89 +
90 +const props = defineProps<{ rule: PipelineRule; highlight: boolean | null | undefined }>()
91 +const { rule, highlight } = toRefs(props)
92 +
93 +const TimeIcon = "carbon:time"
94 +const InfoIcon = "carbon:information"
95 +
96 +const showDetails = ref(false)
97 +const dFormats = useSettingsStore().dateFormat
98 +
99 +function formatDate(timestamp: string): string {
100 + return dayjs(timestamp).format(dFormats.datetimesec)
101 +}
102 +</script>
103 +
104 +<style lang="scss" scoped>
105 +.item {
106 + border-radius: var(--border-radius);
107 + background-color: var(--bg-secondary-color);
108 + transition: all 0.2s var(--bezier-ease);
109 + border: var(--border-small-100);
110 + color: var(--fg-color);
111 +
112 + .header-box {
113 + font-family: var(--font-family-mono);
114 + font-size: 13px;
115 + .id {
116 + word-break: break-word;
117 + color: var(--fg-secondary-color);
118 +
119 + &:hover {
120 + color: var(--primary-color);
121 + }
122 + }
123 + .time {
124 + color: var(--fg-secondary-color);
125 +
126 + &:hover {
127 + color: var(--primary-color);
128 + }
129 + }
130 + }
131 + .main-box {
132 + word-break: break-word;
133 +
134 + .description {
135 + color: var(--fg-secondary-color);
136 + font-size: 13px;
137 + }
138 + }
139 + .footer-box {
140 + font-family: var(--font-family-mono);
141 + font-size: 13px;
142 + margin-top: 10px;
143 + display: none;
144 +
145 + .time {
146 + text-align: right;
147 + color: var(--fg-secondary-color);
148 + }
149 + }
150 +
151 + &.highlight {
152 + background-color: var(--primary-005-color);
153 + box-shadow: 0px 0px 0px 1px inset var(--primary-030-color);
154 + }
155 + &:hover {
156 + box-shadow: 0px 0px 0px 1px inset var(--primary-color);
157 + }
158 +
159 + @container (max-width: 650px) {
160 + .header-box {
161 + .time {
162 + display: none;
163 + }
164 + }
165 + .footer-box {
166 + display: flex;
167 + }
168 + }
169 +}
170 +</style>
src/components/graylog/Pipelines/RulesList.vue new
+104
@@ -0,0 +1,104 @@
1 +<template>
2 + <n-spin :show="loading" class="rules-list">
3 + <n-scrollbar ref="scrollContent">
4 + <div class="list">
5 + <Rule v-for="rule of rules" :key="rule.id" :rule="rule" :highlight="highlight === rule.id" />
6 + </div>
7 + </n-scrollbar>
8 + </n-spin>
9 +</template>
10 +
11 +<script setup lang="ts">
12 +import { useMessage, NSpin, NScrollbar, type ScrollbarInst } from "naive-ui"
13 +import { onBeforeMount, ref, toRefs, watch, nextTick } from "vue"
14 +import type { PipelineRule } from "@/types/graylog/pipelines.d"
15 +import Rule from "./Rule.vue"
16 +import Api from "@/api"
17 +
18 +const emit = defineEmits<{
19 + (e: "loaded", value: { total: number }): void
20 +}>()
21 +
22 +const props = defineProps<{ highlight: string | null | undefined }>()
23 +const { highlight } = toRefs(props)
24 +
25 +const message = useMessage()
26 +const loading = ref(false)
27 +const rules = ref<PipelineRule[]>([])
28 +const scrollContent = ref<(ScrollbarInst & { $el: any }) | null>(null)
29 +
30 +function scrollToRule(id: string) {
31 + const element = document.getElementById(`rule-${id}`)
32 + if (element && scrollContent.value) {
33 + const wrap: HTMLElement = scrollContent.value.$el.nextSibling || scrollContent.value.$el.nextElementSibling
34 + const middle = element.offsetTop - wrap.offsetHeight / 2
35 + scrollContent.value?.scrollTo({ top: middle, behavior: "smooth" })
36 + }
37 +}
38 +
39 +function getRules() {
40 + loading.value = true
41 +
42 + Api.graylog
43 + .getPipelinesRules()
44 + .then(res => {
45 + if (res.data.success) {
46 + rules.value = res.data.pipeline_rules || []
47 + emit("loaded", { total: rules.value.length })
48 + nextTick(() => {
49 + setTimeout(() => {
50 + if (highlight.value) {
51 + scrollToRule(highlight.value)
52 + }
53 + }, 300)
54 + })
55 + } else {
56 + message.warning(res.data?.message || "An error occurred. Please try again later.")
57 + }
58 + })
59 + .catch(err => {
60 + message.error(err.response?.data?.message || "An error occurred. Please try again later.")
61 + })
62 + .finally(() => {
63 + loading.value = false
64 + })
65 +}
66 +
67 +watch(highlight, val => {
68 + if (val) {
69 + nextTick(() => {
70 + setTimeout(() => {
71 + scrollToRule(val)
72 + })
73 + })
74 + }
75 +})
76 +
77 +onBeforeMount(() => {
78 + getRules()
79 +})
80 +</script>
81 +
82 +<style lang="scss" scoped>
83 +.rules-list {
84 + height: 100%;
85 + max-height: 100%;
86 + overflow: hidden;
87 + box-sizing: border-box;
88 +
89 + :deep() {
90 + .n-spin-content {
91 + height: 100%;
92 + box-sizing: border-box;
93 + display: flex;
94 + flex-direction: column;
95 + }
96 + }
97 +
98 + .list {
99 + padding: var(--n-body-padding);
100 + container-type: inline-size;
101 + box-sizing: border-box;
102 + }
103 +}
104 +</style>
src/components/graylog/Pipelines/RulesSmallList.vue new
+77
@@ -0,0 +1,77 @@
1 +<template>
2 + <div class="rules-list flex flex-col">
3 + <n-button v-for="rule of rules" :key="rule.id" quaternary size="tiny" @click="emit('click', rule.id)">
4 + <div class="btn-wrap flex items-center">
5 + <span class="spacer">
6 + <Icon :name="ViewIcon" :size="16"></Icon>
7 + </span>
8 + <span class="grow title">
9 + {{ rule.title }}
10 + </span>
11 + <span class="spacer small"></span>
12 + </div>
13 + </n-button>
14 + </div>
15 +</template>
16 +
17 +<script setup lang="ts">
18 +import { toRefs } from "vue"
19 +import { NButton } from "naive-ui"
20 +import Icon from "@/components/common/Icon.vue"
21 +
22 +export interface RuleExtended {
23 + title: string
24 + id: string
25 +}
26 +
27 +const emit = defineEmits<{
28 + (e: "click", value: string): void
29 +}>()
30 +
31 +const props = defineProps<{ rules: RuleExtended[] }>()
32 +const { rules } = toRefs(props)
33 +
34 +const ViewIcon = "iconoir:eye-alt"
35 +</script>
36 +
37 +<style lang="scss" scoped>
38 +.rules-list {
39 + .n-button {
40 + min-width: 100%;
41 +
42 + :deep(.n-button__content) {
43 + min-width: 100%;
44 + }
45 +
46 + .btn-wrap {
47 + max-width: 270px;
48 + overflow: hidden;
49 +
50 + .title {
51 + overflow: hidden;
52 + text-overflow: ellipsis;
53 + white-space: nowrap;
54 + }
55 +
56 + .spacer {
57 + min-width: 24px;
58 +
59 + &.small {
60 + min-width: 20px;
61 + }
62 + }
63 + }
64 +
65 + i {
66 + opacity: 0;
67 + transition: opacity 0.2s;
68 + }
69 +
70 + &:hover {
71 + i {
72 + opacity: 1;
73 + }
74 + }
75 + }
76 +}
77 +</style>
src/components/graylog/Streams/Item.vue new
+262
@@ -0,0 +1,262 @@
1 +<template>
2 + <div class="item flex flex-col mb-2 gap-2 px-5 py-3" :class="{ default: stream.is_default }">
3 + <div class="header-box flex justify-between">
4 + <div class="info flex items-center gap-2">
5 + <div class="user flex items-center gap-2">
6 + <Icon :name="UserIcon" :size="14"></Icon>
7 + {{ stream.creator_user_id }}
8 + </div>
9 + </div>
10 + <div class="time">{{ formatDate(stream.created_at) }}</div>
11 + </div>
12 + <div class="main-box flex justify-between">
13 + <div class="content">
14 + <div class="title">{{ stream.title }}</div>
15 + <div class="description mb-2">{{ stream.description }}</div>
16 + <div class="badges-box flex flex-wrap items-center gap-3">
17 + <div class="badge cursor" @click="showDetails = true">
18 + <Icon :name="InfoIcon" :size="14"></Icon>
19 + </div>
20 + <div class="badge" :class="{ active: !stream.disabled }">
21 + <span>Enabled</span>
22 + <Icon :name="stream.disabled ? DisabledIcon : EnabledIcon" :size="14"></Icon>
23 + </div>
24 + <div class="badge" :class="{ active: stream.is_default }">
25 + <span>Default</span>
26 + <Icon :name="stream.is_default ? EnabledIcon : DisabledIcon" :size="14"></Icon>
27 + </div>
28 + <div class="badge" :class="{ active: stream.is_editable }">
29 + <span>Editable</span>
30 + <Icon :name="stream.is_editable ? EnabledIcon : DisabledIcon" :size="14"></Icon>
31 + </div>
32 + </div>
33 + </div>
34 + <div class="actions-box flex flex-col justify-end" v-if="stream.is_editable">
35 + <n-button @click="stop()" :loading="loading" v-if="!stream.disabled">
36 + <template #icon><Icon :name="StopIcon"></Icon></template>
37 + Stop stream
38 + </n-button>
39 + <n-button @click="start()" :loading="loading" v-else type="primary">
40 + <template #icon><Icon :name="StartIcon"></Icon></template>
41 + Start stream
42 + </n-button>
43 + </div>
44 + </div>
45 + <div class="footer-box flex justify-between items-center">
46 + <div class="actions-box flex flex-col justify-end" v-if="stream.is_editable">
47 + <n-button @click="stop()" :loading="loading" v-if="!stream.disabled" size="small">
48 + <template #icon><Icon :name="StopIcon"></Icon></template>
49 + Stop
50 + </n-button>
51 + <n-button @click="start()" :loading="loading" v-else type="primary" size="small">
52 + <template #icon><Icon :name="StartIcon"></Icon></template>
53 + Start
54 + </n-button>
55 + </div>
56 + <div class="time">{{ formatDate(stream.created_at) }}</div>
57 + </div>
58 +
59 + <n-modal
60 + v-model:show="showDetails"
61 + preset="card"
62 + :style="{ maxWidth: 'min(600px, 90vw)', overflow: 'hidden' }"
63 + :title="stream.title"
64 + :bordered="false"
65 + segmented
66 + >
67 + <div class="mb-2">
68 + Matching type :
69 + <code>{{ stream.matching_type }}</code>
70 + </div>
71 + <div class="mb-2">
72 + Remove matches from default stream :
73 + <code>{{ stream.remove_matches_from_default_stream }}</code>
74 + </div>
75 + <div class="mb-1">Rules :</div>
76 + <SimpleJsonViewer class="vuesjv-override" :model-value="stream.rules" :initialExpandedDepth="2" />
77 + </n-modal>
78 + </div>
79 +</template>
80 +
81 +<script setup lang="ts">
82 +import { type Stream } from "@/types/graylog/stream.d"
83 +import { useSettingsStore } from "@/stores/settings"
84 +import Icon from "@/components/common/Icon.vue"
85 +import dayjs from "@/utils/dayjs"
86 +import { NModal, NButton, useMessage } from "naive-ui"
87 +import { ref, toRefs } from "vue"
88 +import { SimpleJsonViewer } from "vue-sjv"
89 +import "@/assets/scss/vuesjv-override.scss"
90 +import Api from "@/api"
91 +
92 +const props = defineProps<{ stream: Stream }>()
93 +const { stream } = toRefs(props)
94 +
95 +const UserIcon = "carbon:user"
96 +const InfoIcon = "carbon:information"
97 +const DisabledIcon = "ph:minus-bold"
98 +const EnabledIcon = "ph:check-bold"
99 +const StopIcon = "carbon:stop"
100 +const StartIcon = "carbon:play"
101 +
102 +const message = useMessage()
103 +const loading = ref(false)
104 +const showDetails = ref(false)
105 +const dFormats = useSettingsStore().dateFormat
106 +
107 +function formatDate(timestamp: string): string {
108 + return dayjs(timestamp).format(dFormats.datetimesec)
109 +}
110 +
111 +function stop() {
112 + loading.value = true
113 +
114 + Api.graylog
115 + .stopStream(stream.value.id)
116 + .then(res => {
117 + if (res.data.success) {
118 + stream.value.disabled = true
119 + message.success(res.data?.message || "Stream stopped.")
120 + } else {
121 + message.warning(res.data?.message || "An error occurred. Please try again later.")
122 + }
123 + })
124 + .catch(err => {
125 + message.error(err.response?.data?.message || "An error occurred. Please try again later.")
126 + })
127 + .finally(() => {
128 + loading.value = false
129 + })
130 +}
131 +
132 +function start() {
133 + loading.value = true
134 +
135 + Api.graylog
136 + .startStream(stream.value.id)
137 + .then(res => {
138 + if (res.data.success) {
139 + stream.value.disabled = false
140 + message.success(res.data?.message || "Stream started.")
141 + } else {
142 + message.warning(res.data?.message || "An error occurred. Please try again later.")
143 + }
144 + })
145 + .catch(err => {
146 + message.error(err.response?.data?.message || "An error occurred. Please try again later.")
147 + })
148 + .finally(() => {
149 + loading.value = false
150 + })
151 +}
152 +</script>
153 +
154 +<style lang="scss" scoped>
155 +.item {
156 + border-radius: var(--border-radius);
157 + background-color: var(--bg-color);
158 + transition: all 0.2s var(--bezier-ease);
159 +
160 + .header-box {
161 + font-family: var(--font-family-mono);
162 + font-size: 13px;
163 + .user {
164 + word-break: break-word;
165 + color: var(--fg-secondary-color);
166 + }
167 + .time {
168 + color: var(--fg-secondary-color);
169 + }
170 + }
171 + .main-box {
172 + word-break: break-word;
173 +
174 + .description {
175 + color: var(--fg-secondary-color);
176 + font-size: 13px;
177 + }
178 +
179 + .badges-box {
180 + .badge {
181 + border-radius: var(--border-radius);
182 + border: var(--border-small-100);
183 + display: flex;
184 + align-items: center;
185 + font-size: 14px;
186 + padding: 0px 6px;
187 + height: 26px;
188 + line-height: 1;
189 + gap: 6px;
190 + transition: all 0.3s var(--bezier-ease);
191 +
192 + span,
193 + i {
194 + opacity: 0.5;
195 + }
196 +
197 + &.active {
198 + color: var(--primary-color);
199 + background-color: var(--primary-005-color);
200 +
201 + span,
202 + i {
203 + opacity: 1;
204 + }
205 +
206 + border-color: var(--primary-color);
207 + }
208 +
209 + &.cursor {
210 + cursor: pointer;
211 +
212 + i {
213 + opacity: 1;
214 + }
215 +
216 + &:hover {
217 + color: var(--primary-color);
218 + border-color: var(--primary-color);
219 + }
220 + }
221 + }
222 + }
223 + }
224 +
225 + .footer-box {
226 + display: none;
227 + text-align: right;
228 + font-size: 13px;
229 + margin-top: 10px;
230 +
231 + .time {
232 + font-family: var(--font-family-mono);
233 + color: var(--fg-secondary-color);
234 + width: 100%;
235 + }
236 + }
237 +
238 + &.default {
239 + background-color: var(--primary-005-color);
240 + box-shadow: 0px 0px 0px 1px inset var(--primary-030-color);
241 + }
242 + &:hover {
243 + box-shadow: 0px 0px 0px 1px inset var(--primary-color);
244 + }
245 +
246 + @container (max-width: 650px) {
247 + .header-box {
248 + .time {
249 + display: none;
250 + }
251 + }
252 + .main-box {
253 + .actions-box {
254 + display: none;
255 + }
256 + }
257 + .footer-box {
258 + display: flex;
259 + }
260 + }
261 +}
262 +</style>
src/components/graylog/Streams/List.vue new
+194
@@ -0,0 +1,194 @@
1 +<template>
2 + <n-spin :show="loading">
3 + <div class="header flex items-center justify-end gap-2" ref="header">
4 + <div class="info grow flex gap-5">
5 + <n-popover overlap placement="bottom-start">
6 + <template #trigger>
7 + <div class="bg-color border-radius">
8 + <n-button size="small">
9 + <template #icon>
10 + <Icon :name="InfoIcon"></Icon>
11 + </template>
12 + </n-button>
13 + </div>
14 + </template>
15 + <div class="flex flex-col gap-2">
16 + <div class="box">
17 + Total:
18 + <code>{{ total }}</code>
19 + </div>
20 + <div class="box">
21 + Enabled:
22 + <code>{{ totalEnabled }}</code>
23 + </div>
24 + </div>
25 + </n-popover>
26 + </div>
27 + <n-pagination
28 + v-model:page="currentPage"
29 + v-model:page-size="pageSize"
30 + :page-slot="pageSlot"
31 + :show-size-picker="showSizePicker"
32 + :page-sizes="pageSizes"
33 + :item-count="total"
34 + :simple="simpleMode"
35 + />
36 + <n-popover overlap placement="right" style="padding-left: 0; padding-right: 0">
37 + <template #trigger>
38 + <div class="bg-color border-radius">
39 + <n-button size="small">
40 + <template #icon>
41 + <Icon :name="FilterIcon"></Icon>
42 + </template>
43 + </n-button>
44 + </div>
45 + </template>
46 + <div class="py-1">
47 + <div class="px-3">
48 + <div class="opacity-50 text-sm mb-1">Enabled:</div>
49 + <n-select
50 + size="small"
51 + v-model:value="enabledFilter"
52 + :options="enabledOptions"
53 + clearable
54 + placeholder="All"
55 + class="!w-36"
56 + />
57 + </div>
58 + <n-divider class="!my-3" />
59 + <div class="px-3">
60 + <div class="opacity-50 text-sm mb-1">Editable:</div>
61 + <n-select
62 + size="small"
63 + v-model:value="editableFilter"
64 + :options="editableOptions"
65 + clearable
66 + placeholder="All"
67 + class="!w-36"
68 + />
69 + </div>
70 + </div>
71 + </n-popover>
72 + </div>
73 + <div class="list my-3">
74 + <template v-if="itemsPaginated.length">
75 + <StreamItem v-for="stream of itemsPaginated" :key="stream.id" :stream="stream" />
76 + </template>
77 + <template v-else>
78 + <n-empty description="No items found" v-if="!loading" />
79 + </template>
80 + </div>
81 + <div class="footer flex justify-end">
82 + <n-pagination
83 + v-model:page="currentPage"
84 + :page-size="pageSize"
85 + :item-count="total"
86 + :page-slot="6"
87 + v-if="itemsPaginated.length > 3"
88 + />
89 + </div>
90 + </n-spin>
91 +</template>
92 +
93 +<script setup lang="ts">
94 +import { ref, onBeforeMount, computed } from "vue"
95 +import { useMessage, NSpin, NPagination, NPopover, NButton, NSelect, NDivider, NEmpty } from "naive-ui"
96 +import Api from "@/api"
97 +import StreamItem from "./Item.vue"
98 +import Icon from "@/components/common/Icon.vue"
99 +import type { Stream } from "@/types/graylog/stream.d"
100 +import { useResizeObserver } from "@vueuse/core"
101 +
102 +const FilterIcon = "carbon:filter-edit"
103 +const InfoIcon = "carbon:information"
104 +
105 +const message = useMessage()
106 +const loading = ref(false)
107 +const streams = ref<Stream[]>([])
108 +const total = ref(0)
109 +const totalEnabled = computed(() => streams.value.filter(o => !o.disabled).length)
110 +const pageSize = ref(25)
111 +const currentPage = ref(1)
112 +const simpleMode = ref(false)
113 +const showSizePicker = ref(true)
114 +const pageSizes = [10, 25, 50, 100]
115 +const header = ref()
116 +const pageSlot = ref(8)
117 +const enabledFilter = ref<null | number>(null)
118 +const editableFilter = ref<null | number>(null)
119 +const enabledOptions = [
120 + { label: "Enabled", value: 1 },
121 + { label: "Not Enabled", value: 0 }
122 +]
123 +const editableOptions = [
124 + { label: "Editable", value: 1 },
125 + { label: "Not Editable", value: 0 }
126 +]
127 +
128 +const itemsPaginated = computed(() => {
129 + const from = (currentPage.value - 1) * pageSize.value
130 + const to = currentPage.value * pageSize.value
131 +
132 + return streams.value
133 + .filter(o => {
134 + switch (enabledFilter.value) {
135 + case 1:
136 + return o.disabled === false
137 + case 0:
138 + return o.disabled === true
139 + default:
140 + return true
141 + }
142 + })
143 + .filter(o => {
144 + switch (editableFilter.value) {
145 + case 1:
146 + return o.is_editable === true
147 + case 0:
148 + return o.is_editable === false
149 + default:
150 + return true
151 + }
152 + })
153 + .slice(from, to)
154 +})
155 +
156 +function getData() {
157 + loading.value = true
158 +
159 + Api.graylog
160 + .getStreams()
161 + .then(res => {
162 + if (res.data.success) {
163 + streams.value = res.data.streams || []
164 + total.value = res.data.total || 0
165 + } else {
166 + message.warning(res.data?.message || "An error occurred. Please try again later.")
167 + }
168 + })
169 + .catch(err => {
170 + message.error(err.response?.data?.message || "An error occurred. Please try again later.")
171 + })
172 + .finally(() => {
173 + loading.value = false
174 + })
175 +}
176 +
177 +useResizeObserver(header, entries => {
178 + const entry = entries[0]
179 + const { width } = entry.contentRect
180 +
181 + pageSlot.value = width < 650 ? 5 : 8
182 + simpleMode.value = width < 450
183 +})
184 +
185 +onBeforeMount(() => {
186 + getData()
187 +})
188 +</script>
189 +
190 +<style lang="scss" scoped>
191 +.list {
192 + container-type: inline-size;
193 +}
194 +</style>
src/components/indices/IndexCard.vue
+5 -3
@@ -41,7 +41,7 @@
41 <template #trigger>
42 <n-button quaternary circle type="error" @click.stop="handleDelete">
43 <template #icon>
44 - <n-icon><DeleteIcon /></n-icon>
44 + <Icon :name="DeleteIcon"></Icon>
45 </template>
46 </n-button>
47 </template>
@@ -58,8 +58,10 @@ import { h, ref, toRefs } from "vue"
58 import IndexIcon from "@/components/indices/IndexIcon.vue"
59 import type { IndexStats } from "@/types/indices.d"
60 import Api from "@/api"
61 -import DeleteIcon from "@vicons/carbon/Delete"
62 -import { useMessage, useDialog, NTooltip, NButton, NSpin, NIcon } from "naive-ui"
61 +import { useMessage, useDialog, NTooltip, NButton, NSpin } from "naive-ui"
62 +import Icon from "@/components/common/Icon.vue"
63 +
64 +const DeleteIcon = "ph:trash"
65
66 const emit = defineEmits<{
67 (e: "delete"): void
src/components/indices/IndexIcon.vue
+8 -13
@@ -1,24 +1,19 @@
1 <template>
2 <span class="index-icon" :class="[`health-${health}`, { color }]">
3 - <n-icon v-if="health === IndexHealth.GREEN" :size="18">
4 - <ShieldIcon />
5 - </n-icon>
6 - <n-icon v-if="health === IndexHealth.YELLOW">
7 - <WarningIcon />
8 - </n-icon>
9 - <n-icon v-if="health === IndexHealth.RED">
10 - <DangerIcon />
11 - </n-icon>
3 + <Icon :name="ShieldIcon" v-if="health === IndexHealth.GREEN" :size="18"></Icon>
4 + <Icon :name="WarningIcon" v-if="health === IndexHealth.YELLOW" :size="18"></Icon>
5 + <Icon :name="DangerIcon" v-if="health === IndexHealth.RED" :size="18"></Icon>
6 </span>
7 </template>
8
9 <script setup lang="ts">
10 import { toRefs } from "vue"
11 import { type IndexStats, IndexHealth } from "@/types/indices.d"
18 -import ShieldIcon from "@vicons/fluent/ShieldTask20Regular"
19 -import WarningIcon from "@vicons/fluent/ShieldError20Regular"
20 -import DangerIcon from "@vicons/fluent/Warning20Regular"
21 -import { NIcon } from "naive-ui"
12 +import Icon from "@/components/common/Icon.vue"
13 +
14 +const ShieldIcon = "majesticons:shield-check-line"
15 +const WarningIcon = "majesticons:shield-exclamation-line"
16 +const DangerIcon = "majesticons:exclamation-line"
17
18 const props = defineProps<{
19 health: IndexStats["health"]
src/components/indices/UnhealthyIndices.vue
+5 -5
@@ -22,9 +22,7 @@
22 </template>
23 <n-empty description="No Unhealthy Indices found" v-else>
24 <template #icon>
25 - <n-icon>
26 - <ShieldIcon />
27 - </n-icon>
25 + <Icon :name="ShieldIcon"></Icon>
26 </template>
27 <template #extra>Great, all indices are healthy!</template>
28 </n-empty>
@@ -37,8 +35,10 @@
35 import { computed, toRefs } from "vue"
36 import { type IndexStats, IndexHealth } from "@/types/indices.d"
37 import IndexCard from "@/components/indices/IndexCard.vue"
40 -import { NSpin, NCard, NEmpty, NIcon } from "naive-ui"
41 -import ShieldIcon from "@vicons/fluent/ShieldTask20Regular"
38 +import { NSpin, NCard, NEmpty } from "naive-ui"
39 +import Icon from "@/components/common/Icon.vue"
40 +
41 +const ShieldIcon = "fluent:shield-task-20-regular"
42
43 const emit = defineEmits<{
44 (e: "click", value: IndexStats): void
src/components/list/List.vue
-13
@@ -116,19 +116,6 @@ const list = ref(data)
116 .n-avatar {
117 font-size: 12px;
118 position: relative;
119 - /*
120 - text-shadow: 0px 0px 0px rgba(var(--fg-color-rgb), 0.9);
121 -
122 - &::before {
123 - content: "";
124 - display: block;
125 - position: absolute;
126 - width: 100%;
127 - height: 100%;
128 - background-color: rgba(var(--fg-color-rgb), 0.1);
129 - z-index: 0;
130 - }
131 - */
119 }
120 }
121 .info {
src/components/maps/leaflet/Map.vue new
+135
@@ -0,0 +1,135 @@
1 +<template>
2 + <l-map ref="map" v-model:zoom="zoom" :center="[47.41322, -1.219482]" :useGlobalLeaflet="false">
3 + <!--
4 + <l-tile-layer
5 + url="http://tile.stamen.com/watercolor/{z}/{x}/{y}.jpg"
6 + layer-type="base"
7 + name="Stamen Watercolor"
8 + attribution="Map tiles by <a href='http://stamen.com'>Stamen Design</a>, under <a href='http://creativecommons.org/licenses/by/3.0'>CC BY 3.0</a>. Data by <a href='http://openstreetmap.org'>OpenStreetMap</a>, under <a href='http://creativecommons.org/licenses/by-sa/3.0'>CC BY SA</a>."
9 + />
10 + -->
11 + <l-tile-layer
12 + url="https://stamen-tiles.a.ssl.fastly.net/toner/{z}/{x}/{y}.png"
13 + layer-type="base"
14 + name="Stamen Toner"
15 + attribution="Map tiles by <a href='http://stamen.com'>Stamen Design</a>, under <a href='http://creativecommons.org/licenses/by/3.0'>CC BY 3.0</a>. Data by <a href='http://openstreetmap.org'>OpenStreetMap</a>, under <a href='http://creativecommons.org/licenses/by-sa/3.0'>CC BY SA</a>."
16 + ></l-tile-layer>
17 + <l-tile-layer
18 + url="https://stamen-tiles.a.ssl.fastly.net/terrain/{z}/{x}/{y}.png"
19 + layer-type="base"
20 + name="Stamen Terrain"
21 + attribution="Map tiles by <a href='http://stamen.com'>Stamen Design</a>, under <a href='http://creativecommons.org/licenses/by/3.0'>CC BY 3.0</a>. Data by <a href='http://openstreetmap.org'>OpenStreetMap</a>, under <a href='http://creativecommons.org/licenses/by-sa/3.0'>CC BY SA</a>."
22 + ></l-tile-layer>
23 + <l-tile-layer
24 + url="https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png"
25 + layer-type="base"
26 + name="OpenTopoMap"
27 + attribution="Map data: &copy; <a href='https://www.openstreetmap.org/copyright'>OpenStreetMap</a> contributors, <a href='http://viewfinderpanoramas.org'>SRTM</a> | Map style: &copy; <a href='https://opentopomap.org'>OpenTopoMap</a> (<a href='https://creativecommons.org/licenses/by-sa/3.0/'>CC-BY-SA</a>)"
28 + ></l-tile-layer>
29 + <l-tile-layer
30 + url="https://map1.vis.earthdata.nasa.gov/wmts-webmerc/VIIRS_CityLights_2012/default//GoogleMapsCompatible_Level{maxZoom}/{z}/{y}/{x}.jpg"
31 + layer-type="base"
32 + name="NASA/GSFC/Earth"
33 + attribution="Imagery provided by services from the Global Imagery Browse Services (GIBS), operated by the NASA/GSFC/Earth Science Data and Information System (<a href='https://earthdata.nasa.gov'>ESDIS</a>) with funding provided by NASA/HQ."
34 + :bounds="[
35 + [-85.0511287776, -179.999999975],
36 + [85.0511287776, 179.999999975]
37 + ]"
38 + :minZoom="1"
39 + :maxZoom="8"
40 + ></l-tile-layer>
41 + <l-tile-layer
42 + url="https://cartodb-basemaps-{s}.global.ssl.fastly.net/light_all/{z}/{x}/{y}.png"
43 + layer-type="base"
44 + name="Positron"
45 + attribution="&copy; <a href='https://www.openstreetmap.org/copyright'>OpenStreetMap</a> contributors &copy; <a href='https://carto.com/attributions'>CARTO</a>"
46 + ></l-tile-layer>
47 + <l-tile-layer
48 + url="https://cartodb-basemaps-{s}.global.ssl.fastly.net/dark_all/{z}/{x}/{y}.png"
49 + layer-type="base"
50 + name="Dark Matter"
51 + attribution="&copy; <a href='https://www.openstreetmap.org/copyright'>OpenStreetMap</a> contributors &copy; <a href='https://carto.com/attributions'>CARTO</a>"
52 + ></l-tile-layer>
53 + <l-tile-layer
54 + url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
55 + layer-type="base"
56 + name="OpenStreetMap"
57 + attribution="&copy; <a href='https://www.openstreetmap.org/copyright'>OpenStreetMap</a> contributors"
58 + ></l-tile-layer>
59 +
60 + <l-control-layers />
61 + <l-marker :lat-lng="[47.85549887088562, 10.087190790477521]" draggable>
62 + <l-tooltip>tooltip</l-tooltip>
63 + </l-marker>
64 +
65 + <l-marker :lat-lng="[45.39799982105989, 9.05183645038641]">
66 + <l-icon :icon-url="logo" :icon-size="iconSize" />
67 + </l-marker>
68 +
69 + <l-marker :lat-lng="[46.731739550358135, -1.3987144591730958]" draggable>
70 + <l-popup>popup</l-popup>
71 + </l-marker>
72 +
73 + <l-polyline
74 + :lat-lngs="[
75 + [47.334852, -1.509485],
76 + [47.342596, -1.328731],
77 + [47.241487, -1.190568],
78 + [47.234787, -1.358337]
79 + ]"
80 + color="green"
81 + ></l-polyline>
82 + <l-polygon
83 + :lat-lngs="[
84 + [46.334852, -1.509485],
85 + [46.342596, -1.328731],
86 + [46.241487, -1.190568],
87 + [46.234787, -1.358337]
88 + ]"
89 + color="#41b782"
90 + :fill="true"
91 + :fillOpacity="0.5"
92 + fillColor="#41b782"
93 + />
94 + <l-rectangle
95 + :lat-lngs="[
96 + [46.334852, -1.509485],
97 + [46.342596, -1.328731],
98 + [46.241487, -1.190568],
99 + [46.234787, -1.358337]
100 + ]"
101 + :fill="true"
102 + color="#35495d"
103 + />
104 + <l-rectangle
105 + :bounds="[
106 + [46.334852, -1.190568],
107 + [46.241487, -1.090357]
108 + ]"
109 + >
110 + <l-popup>lol</l-popup>
111 + </l-rectangle>
112 + </l-map>
113 +</template>
114 +<script setup lang="ts">
115 +import logo from "@/assets/images/brand-logo_light.svg?url"
116 +
117 +import { ref } from "vue"
118 +
119 +import "leaflet/dist/leaflet.css"
120 +import {
121 + LMap,
122 + LIcon,
123 + LTileLayer,
124 + LMarker,
125 + LControlLayers,
126 + LTooltip,
127 + LPopup,
128 + LPolyline,
129 + LPolygon,
130 + LRectangle
131 +} from "@vue-leaflet/vue-leaflet"
132 +
133 +const zoom = ref(4)
134 +const iconSize = ref([50, 50])
135 +</script>
src/components/maps/maplibre/Map.vue new
+171
@@ -0,0 +1,171 @@
1 +<template>
2 + <mgl-map :center="center" :zoom="zoom" :attribution-control="false">
3 + <mgl-frame-rate-control />
4 + <mgl-fullscreen-control />
5 + <mgl-attribution-control />
6 + <mgl-navigation-control />
7 + <mgl-scale-control />
8 + <mgl-geolocation-control />
9 + <mgl-style-switch-control :map-styles="mapStyles" :position="controlPosition" />
10 + <mgl-marker :coordinates="markerCoordinates" color="#cc0000" :scale="0.5" />
11 + <mgl-geo-json-source source-id="geojson" :data="geoJsonSource">
12 + <mgl-line-layer layer-id="geojson" :layout="layout" :paint="paint" />
13 + </mgl-geo-json-source>
14 + <mgl-vector-source source-id="libraries" :tiles="librariesSourceTiles">
15 + <mgl-circle-layer layer-id="libraries" source-layer="libraries" :paint="librariesLayerCirclesPaint" />
16 + </mgl-vector-source>
17 + </mgl-map>
18 +</template>
19 +
20 +<script lang="ts" setup>
21 +/*eslint @typescript-eslint/no-unused-vars: "off"*/
22 +import {
23 + MglMap,
24 + MglDefaults,
25 + useMap,
26 + MglCircleLayer,
27 + MglVectorSource,
28 + MglLineLayer,
29 + MglGeoJsonSource,
30 + MglMarker,
31 + MglStyleSwitchControl,
32 + MglButton,
33 + MglCustomControl,
34 + MglGeolocationControl,
35 + MglScaleControl,
36 + MglNavigationControl,
37 + MglAttributionControl,
38 + MglFullscreenControl,
39 + MglFrameRateControl
40 +} from "vue-maplibre-gl"
41 +import type { StyleSwitchItem } from "vue-maplibre-gl"
42 +import type { LngLatLike, LineLayerSpecification, CircleLayerSpecification } from "maplibre-gl"
43 +import type { Feature } from "geojson"
44 +import { ref } from "vue"
45 +
46 +enum Position {
47 + TOP_LEFT = "top-left",
48 + TOP_RIGHT = "top-right",
49 + BOTTOM_LEFT = "bottom-left",
50 + BOTTOM_RIGHT = "bottom-right"
51 +}
52 +
53 +MglDefaults.style = "https://api.maptiler.com/maps/streets/style.json?key=cQX2iET1gmOW38bedbUh"
54 +
55 +const mapStyles = [
56 + {
57 + name: "Streets",
58 + label: "Streets",
59 + style: "https://api.maptiler.com/maps/streets/style.json?key=cQX2iET1gmOW38bedbUh"
60 + },
61 + { name: "Basic", label: "Basic", style: "https://api.maptiler.com/maps/basic/style.json?key=cQX2iET1gmOW38bedbUh" },
62 + {
63 + name: "Bright",
64 + label: "Bright",
65 + style: "https://api.maptiler.com/maps/bright/style.json?key=cQX2iET1gmOW38bedbUh"
66 + },
67 + {
68 + name: "Satellite",
69 + label: "Satellite",
70 + style: "https://api.maptiler.com/maps/hybrid/style.json?key=cQX2iET1gmOW38bedbUh"
71 + },
72 + {
73 + name: "Voyager",
74 + label: "Voyager",
75 + style: "https://api.maptiler.com/maps/voyager/style.json?key=cQX2iET1gmOW38bedbUh"
76 + },
77 + {
78 + name: "watercolor",
79 + label: "Water color",
80 + style: {
81 + version: 8,
82 + sources: {
83 + "raster-tiles": {
84 + type: "raster",
85 + tiles: ["https://stamen-tiles.a.ssl.fastly.net/watercolor/{z}/{x}/{y}.jpg"],
86 + tileSize: 256,
87 + attribution:
88 + 'Map tiles by <a target="_top" rel="noopener" href="http://stamen.com">Stamen Design</a>, under <a target="_top" rel="noopener" href="http://creativecommons.org/licenses/by/3.0">CC BY 3.0</a>. Data by <a target="_top" rel="noopener" href="http://openstreetmap.org">OpenStreetMap</a>, under <a target="_top" rel="noopener" href="http://creativecommons.org/licenses/by-sa/3.0">CC BY SA</a>'
89 + }
90 + },
91 + layers: [
92 + {
93 + id: "simple-tiles",
94 + type: "raster",
95 + source: "raster-tiles",
96 + minzoom: 0,
97 + maxzoom: 22
98 + }
99 + ]
100 + }
101 + }
102 +] as StyleSwitchItem[]
103 +
104 +const geoJsonSource = ref({
105 + type: "Feature",
106 + geometry: {
107 + type: "Polygon",
108 + coordinates: [
109 + [
110 + [-67.13734351262877, 45.137451890638886],
111 + [-66.96466, 44.8097],
112 + [-68.03252, 44.3252],
113 + [-69.06, 43.98],
114 + [-70.11617, 43.68405],
115 + [-70.64573401557249, 43.090083319667144],
116 + [-70.75102474636725, 43.08003225358635],
117 + [-70.79761105007827, 43.21973948828747],
118 + [-70.98176001655037, 43.36789581966826],
119 + [-70.94416541205806, 43.46633942318431],
120 + [-71.08482, 45.3052400000002],
121 + [-70.6600225491012, 45.46022288673396],
122 + [-70.30495378282376, 45.914794623389355],
123 + [-70.00014034695016, 46.69317088478567],
124 + [-69.23708614772835, 47.44777598732787],
125 + [-68.90478084987546, 47.184794623394396],
126 + [-68.23430497910454, 47.35462921812177],
127 + [-67.79035274928509, 47.066248887716995],
128 + [-67.79141211614706, 45.702585354182816],
129 + [-67.13734351262877, 45.137451890638886]
130 + ]
131 + ]
132 + }
133 +} as Feature)
134 +
135 +const librariesSourceTiles = ["https://api.librarydata.uk/libraries/{z}/{x}/{y}.mvt"]
136 +const librariesLayerCirclesPaint = {
137 + "circle-radius": 5,
138 + "circle-color": "#1b5e20"
139 +} as CircleLayerSpecification["paint"]
140 +
141 +const controlPosition = ref(Position.TOP_LEFT)
142 +const markerCoordinates = ref<LngLatLike>([13.377507, 52.516267])
143 +const map = useMap()
144 +
145 +const layout = {
146 + "line-join": "round",
147 + "line-cap": "round"
148 +} as LineLayerSpecification["layout"]
149 +const paint = {
150 + "line-color": "#FF0000",
151 + "line-width": 8
152 +} as LineLayerSpecification["paint"]
153 +
154 +const center = ref<LngLatLike>([10.288107, 49.405078])
155 +const zoom = ref(3)
156 +</script>
157 +
158 +<style lang="scss">
159 +@import "maplibre-gl/dist/maplibre-gl.css";
160 +@import "vue-maplibre-gl/src/lib/css/maplibre.scss";
161 +
162 +.maplibregl-ctrl .maplibregl-ctrl-icon svg {
163 + margin: 0 auto;
164 + path {
165 + fill: #333333;
166 + }
167 +}
168 +.maplibregl-style-list {
169 + color: #333333;
170 +}
171 +</style>
src/components/profile/ProfileSettings.vue
+72 -65
@@ -1,82 +1,73 @@
1 <template>
2 - <n-card class="settings">
3 - <n-form ref="refForm" :label-width="80" :model="formValue" :rules="formRules">
4 - <div class="title">General</div>
5 - <div class="flex justify-between flex-col md:flex-row md:gap-4">
6 - <n-form-item label="Username" path="username" class="basis-1/2">
7 - <n-input v-model:value="formValue.username" placeholder="Type username">
8 - <template #prefix>@</template>
9 - </n-input>
2 + <n-spin class="settings" :show="loading">
3 + <n-card>
4 + <n-form ref="formRef" :label-width="80" :model="formValue" :rules="formRules">
5 + <div class="title">General</div>
6 + <div class="flex flex-col md:flex-row md:gap-4">
7 + <n-form-item label="Date Format" path="dateFormat" class="basis-1/3">
8 + <n-select v-model:value="formValue.dateFormat" :options="dateFormatsAvailables" />
9 + </n-form-item>
10 + <n-form-item label="24 Hour" path="hours24" class="basis-1/3">
11 + <n-checkbox v-model:checked="formValue.hours24">Time 24 Hour</n-checkbox>
12 + </n-form-item>
13 + </div>
14 + <div class="title">Profile</div>
15 + <div class="flex justify-between flex-col md:flex-row md:gap-4">
16 + <n-form-item label="Username" path="username" class="basis-1/2">
17 + <n-input v-model:value="formValue.username" placeholder="Type username">
18 + <template #prefix>@</template>
19 + </n-input>
20 + </n-form-item>
21 + <n-form-item label="Email" path="email" class="basis-1/2">
22 + <n-input v-model:value="formValue.email" placeholder="Type email" />
23 + </n-form-item>
24 + </div>
25 + <n-form-item label="Fullname" path="name">
26 + <n-input v-model:value="formValue.name" placeholder="Type Fullname" />
27 </n-form-item>
11 - <n-form-item label="Email" path="email" class="basis-1/2">
12 - <n-input v-model:value="formValue.email" placeholder="Type email" />
13 - </n-form-item>
14 - </div>
15 - <div class="title">Profile</div>
16 - <n-form-item label="Fullname" path="name">
17 - <n-input v-model:value="formValue.name" placeholder="Type Fullname" />
18 - </n-form-item>
19 - <div class="flex justify-between flex-col md:flex-row md:gap-4">
20 - <n-form-item label="Location" path="location" class="basis-1/2">
21 - <n-input v-model:value="formValue.location" placeholder="Type location" />
22 - </n-form-item>
23 - <n-form-item label="Website" path="website" class="basis-1/2">
24 - <n-input v-model:value="formValue.website" placeholder="Type website" />
25 - </n-form-item>
26 - </div>
27 - <n-form-item label="Bio" path="bio">
28 - <n-input v-model:value="formValue.bio" type="textarea" placeholder="Type bio" />
29 - </n-form-item>
30 - <div class="title">Social</div>
31 - <div class="flex justify-between flex-col md:flex-row md:gap-4">
32 - <n-form-item label="Twitter" path="twitter" class="basis-1/3">
33 - <n-input v-model:value="formValue.twitter" placeholder="Type twitter" />
34 - </n-form-item>
35 - <n-form-item label="Facebook" path="facebook" class="basis-1/3">
36 - <n-input v-model:value="formValue.facebook" placeholder="Type facebook" />
37 - </n-form-item>
38 - <n-form-item label="Google" path="google" class="basis-1/3">
39 - <n-input v-model:value="formValue.google" placeholder="Type google" />
40 - </n-form-item>
41 - </div>
42 - <div class="flex justify-between flex-col md:flex-row md:gap-4">
43 - <n-form-item label="Instagram" path="instagram" class="basis-1/3">
44 - <n-input v-model:value="formValue.instagram" placeholder="Type instagram" />
45 - </n-form-item>
46 - <n-form-item label="Github" path="github" class="basis-1/3">
47 - <n-input v-model:value="formValue.github" placeholder="Type github" />
48 - </n-form-item>
49 - <n-form-item label="Threads" path="threads" class="basis-1/3">
50 - <n-input v-model:value="formValue.threads" placeholder="Type threads" />
28 +
29 + <n-form-item>
30 + <n-button type="primary" @click="save()">Save</n-button>
31 </n-form-item>
52 - </div>
53 - <n-form-item>
54 - <n-button type="primary">Save</n-button>
55 - </n-form-item>
56 - </n-form>
57 - </n-card>
32 + </n-form>
33 + </n-card>
34 + </n-spin>
35 </template>
36
37 <script setup lang="ts">
38 import { ref } from "vue"
62 -import { NCard, NForm, NFormItem, NInput, NButton } from "naive-ui"
39 +import {
40 + NSpin,
41 + NCard,
42 + NForm,
43 + NFormItem,
44 + NInput,
45 + NButton,
46 + NSelect,
47 + NCheckbox,
48 + type FormValidationError,
49 + useMessage,
50 + type FormInst
51 +} from "naive-ui"
52 +import { useSettingsStore } from "@/stores/settings"
53 +
54 +const settingsStore = useSettingsStore()
55 +
56 +const dateFormatsAvailables = settingsStore.dateFormatsAvailables.map(i => ({ label: i, value: i }))
57 +const currentSateFormat = settingsStore.rawDateFormat
58 +const hours24 = settingsStore.hours24
59
60 const formValue = ref({
61 username: "sigmund67",
62 email: "sigmund67@gmail.com",
63 name: "Margie Dibbert",
68 - location: "New York No. 1 Lake Park",
69 - bio: "",
70 - website: "",
71 - twitter: "",
72 - facebook: "",
73 - google: "",
74 - instagram: "",
75 - threads: "",
76 - github: ""
64 + dateFormat: currentSateFormat,
65 + hours24
66 })
67
79 -const refForm = ref()
68 +const loading = ref(false)
69 +const formRef = ref<FormInst | null>(null)
70 +const message = useMessage()
71
72 const formRules = {
73 username: {
@@ -90,6 +81,22 @@ const formRules = {
81 trigger: "blur"
82 }
83 }
84 +
85 +function save() {
86 + loading.value = true
87 +
88 + formRef.value?.validate((errors: Array<FormValidationError> | undefined) => {
89 + if (!errors) {
90 + settingsStore.setDateFormat(formValue.value.dateFormat)
91 + settingsStore.setHours24(formValue.value.hours24)
92 +
93 + message.success("Settings saved")
94 + } else {
95 + message.error("Something was wrong")
96 + }
97 + loading.value = false
98 + })
99 +}
100 </script>
101
102 <style lang="scss" scoped>
src/components/tables/Base.vue
+10 -13
@@ -64,16 +64,12 @@
64 <div class="actions flex items-center justify-end gap-2">
65 <n-button secondary>
66 <template #icon>
67 - <n-icon>
68 - <DeleteIcon />
69 - </n-icon>
67 + <Icon :name="DeleteIcon"></Icon>
68 </template>
69 </n-button>
70 <n-button secondary>
71 <template #icon>
74 - <n-icon>
75 - <DownloadIcon />
76 - </n-icon>
72 + <Icon :name="DownloadIcon"></Icon>
73 </template>
74 </n-button>
75 <n-popselect
@@ -84,9 +80,7 @@
80 >
81 <n-button secondary>
82 <template #icon>
87 - <n-icon>
88 - <MenuIcon />
89 - </n-icon>
83 + <Icon :name="MenuIcon"></Icon>
84 </template>
85 </n-button>
86 </n-popselect>
@@ -98,10 +92,13 @@
92 </template>
93
94 <script lang="ts" setup>
101 -import { NTable, NImage, NProgress, NTag, NButton, NIcon, NPopselect } from "naive-ui"
102 -import DeleteIcon from "@vicons/carbon/Delete"
103 -import MenuIcon from "@vicons/carbon/OverflowMenuVertical"
104 -import DownloadIcon from "@vicons/carbon/CloudDownload"
95 +import { NTable, NImage, NProgress, NTag, NButton, NPopselect } from "naive-ui"
96 +import Icon from "@/components/common/Icon.vue"
97 +
98 +const DeleteIcon = "carbon:delete"
99 +const MenuIcon = "carbon:overflow-menu-vertical"
100 +const DownloadIcon = "carbon:cloud-download"
101 +
102 import dayjs from "@/utils/dayjs"
103 import { faker } from "@faker-js/faker"
104 import { ref, toRefs } from "vue"
src/composables/useFullscreenSwitch.ts new
+11
@@ -0,0 +1,11 @@
1 +import { useFullscreen } from "@vueuse/core"
2 +const { toggle, isFullscreen } = useFullscreen()
3 +
4 +export function useFullscreenSwitch() {
5 + return {
6 + toggle: () => {
7 + toggle()
8 + },
9 + isFullscreen
10 + }
11 +}
src/composables/useGlobalActions.ts new
+19
@@ -0,0 +1,19 @@
1 +import { type MessageOptions } from "naive-ui"
2 +import type { MessageApiInjection, MessageReactive } from "naive-ui/es/message/src/MessageProvider"
3 +
4 +interface InitPayload {
5 + message: MessageApiInjection
6 +}
7 +
8 +let message: MessageApiInjection | null = null
9 +
10 +export function useGlobalActions() {
11 + return {
12 + init: (payload: InitPayload): void => {
13 + message = payload.message
14 + },
15 + message: (content: string, options?: MessageOptions): MessageReactive | undefined => {
16 + return message?.create(content, options || { type: "info" })
17 + }
18 + }
19 +}
src/composables/useHideLayoutFooter.ts
+2 -1
@@ -4,9 +4,10 @@ import { onBeforeMount, onBeforeUnmount } from "vue"
4 // :has() CSS relational pseudo-class not yet supported by Firefox
5 // (https://caniuse.com/css-has)
6 // at the moment this worker around permit to hide Layout Footer
7 -const store = useThemeStore()
7
8 export function useHideLayoutFooter() {
9 + const store = useThemeStore()
10 +
11 if (store.isFooterShown) {
12 onBeforeMount(() => {
13 store.setFooterShow(false)
src/composables/useNotifications.ts new
+118
@@ -0,0 +1,118 @@
1 +import { computed, ref } from "vue"
2 +import dayjs from "@/utils/dayjs"
3 +
4 +type NotificationType = "message" | "reminder" | "alert" | "news" | string
5 +interface Notification {
6 + id: number
7 + type: NotificationType
8 + title: string
9 + description: string
10 + read: boolean
11 + date: string
12 + action?: () => void
13 +}
14 +
15 +const items: Notification[] = [
16 + {
17 + id: 1,
18 + type: "message",
19 + title: "New Email",
20 + description: "Important document to read",
21 + read: false,
22 + date: "Today"
23 + },
24 + {
25 + id: 2,
26 + type: "reminder",
27 + title: "Appointment",
28 + description: "Meeting with client at 3:00 PM",
29 + read: false,
30 + date: "Yesterday"
31 + },
32 + {
33 + id: 9,
34 + type: "alert",
35 + title: "Alert",
36 + description: "Limited-time super offer on desired product",
37 + read: true,
38 + date: "Yesterday"
39 + },
40 + {
41 + id: 5,
42 + type: "news",
43 + title: "News",
44 + description: "Networking event in your city",
45 + read: false,
46 + date: dayjs().subtract(3, "d").format("D MMM")
47 + },
48 + {
49 + id: 3,
50 + type: "reminder",
51 + title: "Reminder",
52 + description: "Overdue bill payment",
53 + read: true,
54 + date: dayjs().subtract(7, "d").format("D MMM")
55 + },
56 + {
57 + id: 4,
58 + type: "reminder",
59 + title: "Deadline",
60 + description: "Submit report by tomorrow",
61 + read: true,
62 + date: dayjs().subtract(2, "d").format("D MMM")
63 + },
64 + {
65 + id: 6,
66 + type: "message",
67 + title: "Message",
68 + description: "New comment on your post",
69 + read: false,
70 + date: dayjs().subtract(4, "d").format("D MMM")
71 + },
72 + {
73 + id: 7,
74 + type: "reminder",
75 + title: "Reminder",
76 + description: "Complete purchase in your online cart",
77 + read: false,
78 + date: dayjs().subtract(5, "d").format("D MMM")
79 + },
80 + {
81 + id: 8,
82 + type: "reminder",
83 + title: "Invitation",
84 + description: "Friend's birthday party",
85 + read: true,
86 + date: dayjs().subtract(6, "d").format("D MMM")
87 + }
88 +]
89 +
90 +const list = ref<Notification[]>([])
91 +
92 +for (let i = 0; i < 30; i++) {
93 + const item = items[i % items.length]
94 + item.id = i
95 +
96 + if (i > 2) {
97 + item.date = dayjs().subtract(i, "d").format("D MMM")
98 + }
99 +
100 + list.value.push({ ...item })
101 +}
102 +
103 +export function useNotifications() {
104 + const hasNotifications = computed(() => list.value.filter(o => !o.read).length !== 0)
105 +
106 + return {
107 + list,
108 + hasNotifications,
109 + setAllRead: () => {
110 + for (const item of list.value) {
111 + item.read = true
112 + }
113 + },
114 + prepend: (newItem: Notification) => {
115 + list.value = [newItem, ...list.value]
116 + }
117 + }
118 +}
src/composables/useSearchDialog.ts new
+13
@@ -0,0 +1,13 @@
1 +import { ref } from "vue"
2 +
3 +const listener = ref()
4 +export function useSearchDialog() {
5 + return {
6 + trigger: (cb: () => void): void => {
7 + listener.value = cb
8 + },
9 + open: (): void => {
10 + listener.value && listener.value()
11 + }
12 + }
13 +}
src/composables/useStoreI18n.ts new
+23
@@ -0,0 +1,23 @@
1 +import { useI18n } from "vue-i18n"
2 +import { useLocalesStore } from "@/stores/i18n"
3 +
4 +export function useStoreI18n() {
5 + const { t } = useI18n()
6 +
7 + return {
8 + initLocale: (): string => {
9 + return useLocalesStore().locale
10 + },
11 + getAvailableLocales: (): string[] => {
12 + return useLocalesStore().available
13 + },
14 + getLocale: (): string => {
15 + return useLocalesStore().locale
16 + },
17 + setLocale: (newLocale: string): string => {
18 + useLocalesStore().setLocale(newLocale)
19 + return newLocale
20 + },
21 + t
22 + }
23 +}
src/composables/useThemeSwitch.ts new
+9
@@ -0,0 +1,9 @@
1 +import { useThemeStore } from "@/stores/theme"
2 +
3 +export function useThemeSwitch() {
4 + return {
5 + toggle: () => {
6 + useThemeStore().toggleTheme()
7 + }
8 + }
9 +}
src/design-tokens.json
+135 -32
@@ -1,54 +1,157 @@
1 {
2 + "borderRadius": {
3 + "base": "6px",
4 + "small": "3px"
5 + },
6 + "lineHeight": {
7 + "base": "1.35"
8 + },
9 + "fontSize": {
10 + "base": "15px",
11 + "cardTitle": "18px"
12 + },
13 + "fontFamily": {
14 + "base": "'Public Sans', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'",
15 + "display": "'Lexend', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'",
16 + "mono": "'JetBrains Mono', SFMono-Regular, Menlo, Consolas, Courier, monospace"
17 + },
18 + "typography": {
19 + "h1": {
20 + "fontFamily": "{fontFamily.display}",
21 + "fontSize": "30px",
22 + "fontWeight": "700",
23 + "lineHeight": "41"
24 + },
25 + "h2": {
26 + "fontFamily": "{fontFamily.display}",
27 + "fontSize": "26px",
28 + "fontWeight": "700",
29 + "lineHeight": "35"
30 + },
31 + "h3": {
32 + "fontFamily": "{fontFamily.display}",
33 + "fontSize": "22px",
34 + "fontWeight": "700",
35 + "lineHeight": "30"
36 + },
37 + "h4": {
38 + "fontFamily": "{fontFamily.display}",
39 + "fontSize": "18px",
40 + "fontWeight": "500",
41 + "lineHeight": "24"
42 + },
43 + "h5": {
44 + "fontFamily": "{fontFamily.display}",
45 + "fontSize": "14px",
46 + "fontWeight": "700",
47 + "lineHeight": "19"
48 + },
49 + "h6": {
50 + "fontFamily": "{fontFamily.base}",
51 + "fontSize": "12px",
52 + "fontWeight": "500",
53 + "lineHeight": "16"
54 + },
55 + "p": {
56 + "fontFamily": "{fontFamily.base}",
57 + "fontSize": "{fontSize.base}",
58 + "lineHeight": "20"
59 + }
60 + },
61 "colors": {
62 "light": {
63 + "sidebarBackground": "#ffffff",
64 + "bodyBackground": "#f5f7f9",
65 "text": "#000000",
66 "textSecondary": "#495465",
67 "background": "#ffffff",
7 - "sidebarBackground": "#F6F7F9",
8 - "bodyBackground": "#E8EDF2",
9 - "primary": "#00B27B",
68 + "backgroundSecondary": "#fafbfc",
69 + "primary": "rgb(0, 178, 123)",
70 + "primary005": "rgba(0, 178, 123, 0.05)",
71 + "primary010": "rgba(0, 178, 123, 0.1)",
72 + "primary015": "rgba(0, 178, 123, 0.15)",
73 + "primary020": "rgba(0, 178, 123, 0.2)",
74 + "primary030": "rgba(0, 178, 123, 0.3)",
75 + "primary040": "rgba(0, 178, 123, 0.4)",
76 + "primary050": "rgba(0, 178, 123, 0.5)",
77 + "primary060": "rgba(0, 178, 123, 0.6)",
78 "info": "#6267FF",
79 "success": "#00B27B",
80 "warning": "#FFB600",
81 "error": "#FF0156",
14 - "secondary1": "#6267FF",
15 - "secondary2": "#FF61C9",
16 - "secondary3": "#FFB600",
17 - "secondary4": "#FF0156",
18 - "shade1": "#ffffff"
82 + "secondary1": "rgb(98, 103, 255)",
83 + "secondary1Opacity005": "rgba(98, 103, 255, 0.05)",
84 + "secondary1Opacity010": "rgba(98, 103, 255, 0.1)",
85 + "secondary1Opacity020": "rgba(98, 103, 255, 0.2)",
86 + "secondary1Opacity030": "rgba(98, 103, 255, 0.3)",
87 + "secondary2": "rgb(255, 97, 201)",
88 + "secondary2Opacity005": "rgba(255, 97, 201, 0.05)",
89 + "secondary2Opacity010": "rgba(255, 97, 201, 0.1)",
90 + "secondary2Opacity020": "rgba(255, 97, 201, 0.2)",
91 + "secondary2Opacity030": "rgba(255, 97, 201, 0.3)",
92 + "secondary3": "rgb(255, 182, 0)",
93 + "secondary3Opacity005": "rgba(255, 182, 0, 0.05)",
94 + "secondary3Opacity010": "rgba(255, 182, 0, 0.1)",
95 + "secondary3Opacity020": "rgba(255, 182, 0, 0.2)",
96 + "secondary3Opacity030": "rgba(255, 182, 0, 0.3)",
97 + "secondary4": "rgb(255, 1, 86)",
98 + "secondary4Opacity005": "rgba(255, 1, 86, 0.05)",
99 + "secondary4Opacity010": "rgba(255, 1, 86, 0.1)",
100 + "secondary4Opacity020": "rgba(255, 1, 86, 0.2)",
101 + "secondary4Opacity030": "rgba(255, 1, 86, 0.3)",
102 + "divider005": "rgba(0, 0, 0, 0.05)",
103 + "divider010": "rgba(0, 0, 0, 0.1)",
104 + "divider020": "rgba(0, 0, 0, 0.2)",
105 + "hover005": "rgba(0, 0, 0, 0.05)",
106 + "hover010": "rgba(0, 0, 0, 0.1)",
107 + "hover050": "rgba(0, 0, 0, 0.5)"
108 },
109 "dark": {
110 + "sidebarBackground": "#1D1F25",
111 + "bodyBackground": "#14161A",
112 "text": "#ffffff",
113 "textSecondary": "#ACB5BE",
114 "background": "#26282d",
24 - "sidebarBackground": "#1D1F25",
25 - "bodyBackground": "#14161A",
26 - "primary": "#00E19B",
115 + "backgroundSecondary": "#1D1F25",
116 + "primary": "rgb(0, 225, 155)",
117 + "primary005": "rgba(0, 225, 155, 0.05)",
118 + "primary010": "rgba(0, 225, 155, 0.1)",
119 + "primary015": "rgba(0, 225, 155, 0.15)",
120 + "primary020": "rgba(0, 225, 155, 0.2)",
121 + "primary030": "rgba(0, 225, 155, 0.3)",
122 + "primary040": "rgba(0, 225, 155, 0.4)",
123 + "primary050": "rgba(0, 225, 155, 0.5)",
124 + "primary060": "rgba(0, 225, 155, 0.6)",
125 "info": "#6267FF",
126 "success": "#00E19B",
127 "warning": "#FFB600",
128 "error": "#FF0156",
31 - "secondary1": "#6267FF",
32 - "secondary2": "#FF61C9",
33 - "secondary3": "#FFB600",
34 - "secondary4": "#FF0156",
35 - "shade1": "#26282d"
129 + "secondary1": "rgb(98, 103, 255)",
130 + "secondary1Opacity005": "rgba(98, 103, 255, 0.05)",
131 + "secondary1Opacity010": "rgba(98, 103, 255, 0.1)",
132 + "secondary1Opacity020": "rgba(98, 103, 255, 0.2)",
133 + "secondary1Opacity030": "rgba(98, 103, 255, 0.3)",
134 + "secondary2": "rgb(255, 97, 201)",
135 + "secondary2Opacity005": "rgba(255, 97, 201, 0.05)",
136 + "secondary2Opacity010": "rgba(255, 97, 201, 0.1)",
137 + "secondary2Opacity020": "rgba(255, 97, 201, 0.2)",
138 + "secondary2Opacity030": "rgba(255, 97, 201, 0.3)",
139 + "secondary3": "rgb(255, 182, 0)",
140 + "secondary3Opacity005": "rgba(255, 182, 0, 0.05)",
141 + "secondary3Opacity010": "rgba(255, 182, 0, 0.1)",
142 + "secondary3Opacity020": "rgba(255, 182, 0, 0.2)",
143 + "secondary3Opacity030": "rgba(255, 182, 0, 0.3)",
144 + "secondary4": "rgb(255, 1, 86)",
145 + "secondary4Opacity005": "rgba(255, 1, 86, 0.05)",
146 + "secondary4Opacity010": "rgba(255, 1, 86, 0.1)",
147 + "secondary4Opacity020": "rgba(255, 1, 86, 0.2)",
148 + "secondary4Opacity030": "rgba(255, 1, 86, 0.3)",
149 + "divider005": "rgba(255, 255, 255, 0.05)",
150 + "divider010": "rgba(255, 255, 255, 0.1)",
151 + "divider020": "rgba(255, 255, 255, 0.2)",
152 + "hover005": "rgba(255, 255, 255, 0.05)",
153 + "hover010": "rgba(255, 255, 255, 0.1)",
154 + "hover050": "rgba(255, 255, 255, 0.5)"
155 }
37 - },
38 - "borderRadius": {
39 - "base": "6px",
40 - "small": "3px"
41 - },
42 - "lineHeight": {
43 - "base": "1.35"
44 - },
45 - "fontSize": {
46 - "base": "15px",
47 - "cardTitle": "18px"
48 - },
49 - "fontFamily": {
50 - "base": "'Public Sans', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'",
51 - "display": "'Lexend', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'",
52 - "mono": "'JetBrains Mono', SFMono-Regular, Menlo, Consolas, Courier, monospace"
156 }
157 }
src/emitter.ts
+2 -1
@@ -1,2 +1,3 @@
1 -import mitt from "mitt"
1 +import mitt, { type Emitter as Mitt, type EventType } from "mitt"
2 export const emitter = mitt()
3 +export type Emitter<T extends Record<EventType, unknown>> = Mitt<T>
src/layouts/Blank/index.ts deleted
-2
@@ -1,2 +0,0 @@
1 -import Blank from "./Blank.vue"
2 -export default Blank
src/layouts/Blank/index.vue renamed
src/layouts/HorizontalNav/HeaderBar.vue
+1 -1
@@ -10,7 +10,7 @@
10 <script lang="ts" setup>
11 import { computed } from "vue"
12 import { NScrollbar } from "naive-ui"
13 -import Navbar from "@/layouts/common/Navbar"
13 +import Navbar from "@/layouts/common/Navbar/index.vue"
14 import Logo from "@/layouts/common/Logo.vue"
15 import { useThemeStore } from "@/stores/theme"
16
src/layouts/HorizontalNav/MainContainer.vue
+1 -1
@@ -14,7 +14,7 @@
14 import { computed, ref, onMounted } from "vue"
15 import { NScrollbar } from "naive-ui"
16 import { useRoute, useRouter } from "vue-router"
17 -import Toolbar from "@/layouts/common/Toolbar"
17 +import Toolbar from "@/layouts/common/Toolbar/index.vue"
18 import FooterEL from "@/layouts/common/FooterEL.vue"
19 import { useThemeStore } from "@/stores/theme"
20
src/layouts/HorizontalNav/Sidebar.vue
+1 -1
@@ -19,7 +19,7 @@ import { computed, onMounted, ref, watch } from "vue"
19 import { NScrollbar } from "naive-ui"
20 import { isMobile } from "@/utils"
21 import { onClickOutside, useElementHover } from "@vueuse/core"
22 -import Navbar from "@/layouts/common/Navbar"
22 +import Navbar from "@/layouts/common/Navbar/index.vue"
23 import SidebarHeader from "./SidebarHeader.vue"
24 import SidebarFooter from "./SidebarFooter.vue"
25 import { useThemeStore } from "@/stores/theme"
src/layouts/HorizontalNav/SidebarFooter.vue
+3 -2
@@ -9,8 +9,9 @@ import { computed, h, ref, toRefs } from "vue"
9 import { NMenu } from "naive-ui"
10 import { useThemeStore } from "@/stores/theme"
11 import { renderIcon } from "@/utils"
12 -import BuyIcon from "@vicons/carbon/ShoppingCart"
13 -import DocsIcon from "@vicons/ionicons5/BookOutline"
12 +
13 +const BuyIcon = "carbon:shopping-cart"
14 +const DocsIcon = "ion:book-outline"
15
16 defineOptions({
17 name: "SidebarFooter"
src/layouts/HorizontalNav/SidebarHeader.vue
+10 -11
@@ -10,15 +10,15 @@
10 </div>
11 <Transition name="fade" mode="out-in">
12 <div class="sidebar-pin flex items-center" v-if="showPin">
13 - <n-icon size="20" @click="sidebarCollapsed = !sidebarCollapsed">
13 + <Icon :size="20" @click="sidebarCollapsed = !sidebarCollapsed">
14 <span class="i-large">
15 - <CircleRegular v-if="sidebarCollapsed" />
16 - <DotCircleRegular v-if="!sidebarCollapsed" />
15 + <Iconify :icon="CircleRegular" v-if="sidebarCollapsed" />
16 + <Iconify :icon="DotCircleRegular" v-if="!sidebarCollapsed" />
17 </span>
18 <span class="i-small">
19 - <CloseOutline v-if="!sidebarCollapsed" />
19 + <Iconify :icon="CloseOutline" v-if="!sidebarCollapsed" />
20 </span>
21 - </n-icon>
21 + </Icon>
22 </div>
23 </Transition>
24 </div>
@@ -27,14 +27,13 @@
27 <script lang="ts" setup>
28 import { computed, toRefs } from "vue"
29 import { useThemeStore } from "@/stores/theme"
30 -import { NIcon } from "naive-ui"
31 -import { CircleRegular, DotCircleRegular } from "@vicons/fa"
32 -import { CloseOutline } from "@vicons/carbon"
30 +import Icon from "@/components/common/Icon.vue"
31 +import { Icon as Iconify } from "@iconify/vue"
32 import Logo from "@/layouts/common/Logo.vue"
33
35 -defineOptions({
36 - name: "SidebarHeader"
37 -})
34 +const CircleRegular = "fa6-regular:circle"
35 +const DotCircleRegular = "fa6-regular:circle-dot"
36 +const CloseOutline = "fa6-regular:circle-xmark"
37
38 const props = defineProps<{
39 logoMini?: boolean
src/layouts/HorizontalNav/index.ts deleted
-2
@@ -1,2 +0,0 @@
1 -import HorizontalNav from "./HorizontalNav.vue"
2 -export default HorizontalNav
src/layouts/HorizontalNav/index.vue renamed
src/layouts/VerticalNav/MainContainer.vue
+1 -1
@@ -14,7 +14,7 @@
14 import { computed, ref, onMounted } from "vue"
15 import { NScrollbar } from "naive-ui"
16 import { useRoute, useRouter } from "vue-router"
17 -import Toolbar from "@/layouts/common/Toolbar"
17 +import Toolbar from "@/layouts/common/Toolbar/index.vue"
18 import FooterEL from "@/layouts/common/FooterEL.vue"
19 import { useThemeStore } from "@/stores/theme"
20
src/layouts/VerticalNav/Sidebar.vue
+1 -1
@@ -19,7 +19,7 @@ import { computed, onMounted, ref, watch } from "vue"
19 import { NScrollbar } from "naive-ui"
20 import { isMobile } from "@/utils"
21 import { onClickOutside, useElementHover } from "@vueuse/core"
22 -import Navbar from "@/layouts/common/Navbar"
22 +import Navbar from "@/layouts/common/Navbar/index.vue"
23 import SidebarHeader from "./SidebarHeader.vue"
24 import SidebarFooter from "./SidebarFooter.vue"
25 import { useThemeStore } from "@/stores/theme"
src/layouts/VerticalNav/SidebarFooter.vue
+4 -2
@@ -9,12 +9,14 @@ import { computed, h, ref, toRefs } from "vue"
9 import { NMenu } from "naive-ui"
10 import { useThemeStore } from "@/stores/theme"
11 import { renderIcon } from "@/utils"
12 -import BuyIcon from "@vicons/carbon/ShoppingCart"
13 -import DocsIcon from "@vicons/ionicons5/BookOutline"
12
13 defineOptions({
14 name: "SidebarFooter"
15 })
16 +
17 +const BuyIcon = "carbon:shopping-cart"
18 +const DocsIcon = "ion:book-outline"
19 +
20 const props = withDefaults(
21 defineProps<{
22 collapsed?: boolean
src/layouts/VerticalNav/SidebarHeader.vue
+11 -8
@@ -10,15 +10,15 @@
10 </div>
11 <Transition name="fade" mode="out-in">
12 <div class="sidebar-pin flex items-center" v-if="showPin">
13 - <n-icon size="20" @click="sidebarCollapsed = !sidebarCollapsed">
13 + <Icon :size="20" @click="sidebarCollapsed = !sidebarCollapsed">
14 <span class="i-large">
15 - <CircleRegular v-if="sidebarCollapsed" />
16 - <DotCircleRegular v-if="!sidebarCollapsed" />
15 + <Iconify :icon="CircleRegular" v-if="sidebarCollapsed" />
16 + <Iconify :icon="DotCircleRegular" v-if="!sidebarCollapsed" />
17 </span>
18 <span class="i-small">
19 - <CloseOutline v-if="!sidebarCollapsed" />
19 + <Iconify :icon="CloseOutline" v-if="!sidebarCollapsed" />
20 </span>
21 - </n-icon>
21 + </Icon>
22 </div>
23 </Transition>
24 </div>
@@ -27,15 +27,18 @@
27 <script lang="ts" setup>
28 import { computed, toRefs } from "vue"
29 import { useThemeStore } from "@/stores/theme"
30 -import { NIcon } from "naive-ui"
31 -import { CircleRegular, DotCircleRegular } from "@vicons/fa"
32 -import { CloseOutline } from "@vicons/carbon"
30 +import Icon from "@/components/common/Icon.vue"
31 +import { Icon as Iconify } from "@iconify/vue"
32 import Logo from "@/layouts/common/Logo.vue"
33
34 defineOptions({
35 name: "SidebarHeader"
36 })
37
38 +const CircleRegular = "fa6-regular:circle"
39 +const DotCircleRegular = "fa6-regular:circle-dot"
40 +const CloseOutline = "fa6-regular:circle-xmark"
41 +
42 const props = defineProps<{
43 logoMini?: boolean
44 }>()
src/layouts/VerticalNav/index.ts deleted
-2
@@ -1,2 +0,0 @@
1 -import VerticalNav from "./VerticalNav.vue"
2 -export default VerticalNav
src/layouts/VerticalNav/index.vue renamed
src/layouts/common/FooterEL.vue
+3 -3
@@ -3,9 +3,9 @@
3 <div class="wrap flex items-center justify-end gap-3">
4 <div class="copy">
5 Made with
6 - <n-icon :size="22" :color="style['--primary-color']" class="mx-1">
6 + <Icon :size="22" :color="style['--primary-color']" class="mx-1">
7 <BrainIcon />
8 - </n-icon>
8 + </Icon>
9 By
10 <a href="https://dverse.studio/" target="_blank" alt="D*VERSE" rel="noopener noreferrer" class="mx-1">
11 D*VERSE Studio
@@ -18,7 +18,7 @@
18
19 <script lang="ts" setup>
20 import { computed, ref, toRefs } from "vue"
21 -import { NIcon } from "naive-ui"
21 +import Icon from "@/components/common/Icon.vue"
22 import BrainIcon from "@/assets/icons/brain-icon.svg"
23 import { useThemeStore } from "@/stores/theme"
24
src/layouts/common/GlobalListener.vue new
+12
@@ -0,0 +1,12 @@
1 +<template>
2 + <slot />
3 +</template>
4 +
5 +<script setup>
6 +import { useMessage } from "naive-ui"
7 +import { useGlobalActions } from "@/composables/useGlobalActions"
8 +
9 +const message = useMessage()
10 +
11 +useGlobalActions().init({ message })
12 +</script>
src/layouts/common/Navbar/apps.ts new
+67
@@ -0,0 +1,67 @@
1 +import { renderIcon } from "@/utils"
2 +import { h } from "vue"
3 +import { RouterLink } from "vue-router"
4 +
5 +const EmailIcon = "carbon:email"
6 +const ChatIcon = "carbon:chat"
7 +const KanbanIcon = "lucide:kanban-square"
8 +const NotesIcon = "carbon:notebook"
9 +
10 +export default [
11 + {
12 + label: () =>
13 + h(
14 + RouterLink,
15 + {
16 + to: {
17 + name: "Apps-Mailbox"
18 + }
19 + },
20 + { default: () => "Email" }
21 + ),
22 + key: "Apps-Mailbox",
23 + icon: renderIcon(EmailIcon)
24 + },
25 + {
26 + label: () =>
27 + h(
28 + RouterLink,
29 + {
30 + to: {
31 + name: "Apps-Chat"
32 + }
33 + },
34 + { default: () => "Chat" }
35 + ),
36 + key: "Apps-Chat",
37 + icon: renderIcon(ChatIcon)
38 + },
39 + {
40 + label: () =>
41 + h(
42 + RouterLink,
43 + {
44 + to: {
45 + name: "Apps-Kanban"
46 + }
47 + },
48 + { default: () => "Kanban" }
49 + ),
50 + key: "Apps-Kanban",
51 + icon: renderIcon(KanbanIcon)
52 + },
53 + {
54 + label: () =>
55 + h(
56 + RouterLink,
57 + {
58 + to: {
59 + name: "Apps-Notes"
60 + }
61 + },
62 + { default: () => "Notes" }
63 + ),
64 + key: "Apps-Notes",
65 + icon: renderIcon(NotesIcon)
66 + }
67 +]
src/layouts/common/Navbar/authentication.ts
+1 -1
@@ -1,7 +1,7 @@
1 import { renderIcon } from "@/utils"
2 import { h } from "vue"
3
4 -import AuthenticationIcon from "@vicons/fluent/LockClosed24Regular"
4 +const AuthenticationIcon = "fluent:lock-closed-24-regular"
5
6 export default {
7 label: "Authentication",
src/layouts/common/Navbar/calendars.ts
+16 -3
@@ -2,12 +2,24 @@ import { renderIcon } from "@/utils"
2 import { h } from "vue"
3 import { RouterLink } from "vue-router"
4
5 -import CalendarIcon from "@vicons/carbon/Calendar"
5 +const CalendarIcon = "carbon:calendar"
6
7 export default {
8 - label: "Calendars",
9 - key: "calendars",
8 + key: "Apps-Calendars-FullCalendar",
9 icon: renderIcon(CalendarIcon),
10 + label: () =>
11 + h(
12 + RouterLink,
13 + {
14 + to: {
15 + name: "Apps-Calendars-FullCalendar"
16 + }
17 + },
18 + { default: () => "Calendar" }
19 + )
20 +
21 + /*
22 + label: "Calendars",
23 children: [
24 {
25 label: () =>
@@ -36,4 +48,5 @@ export default {
48 key: "vue-cal"
49 }
50 ]
51 + */
52 }
src/layouts/common/Navbar/cards.ts
+12 -12
@@ -2,11 +2,11 @@ import { renderIcon } from "@/utils"
2 import { h } from "vue"
3 import { RouterLink } from "vue-router"
4
5 -import CardsIcon from "@vicons/fluent/PreviewLink20Regular"
5 +const CardsIcon = "fluent:preview-link-20-regular"
6
7 export default {
8 label: "Cards",
9 - key: "cards",
9 + key: "Cards",
10 icon: renderIcon(CardsIcon),
11 children: [
12 {
@@ -15,12 +15,12 @@ export default {
15 RouterLink,
16 {
17 to: {
18 - name: "cards-basic"
18 + name: "Cards-Basic"
19 }
20 },
21 { default: () => "Basic" }
22 ),
23 - key: "cards-basic"
23 + key: "Cards-Basic"
24 },
25 {
26 label: () =>
@@ -28,12 +28,12 @@ export default {
28 RouterLink,
29 {
30 to: {
31 - name: "cards-ecommerce"
31 + name: "Cards-Ecommerce"
32 }
33 },
34 { default: () => "Ecommerce" }
35 ),
36 - key: "cards-ecommerce"
36 + key: "Cards-Ecommerce"
37 },
38 {
39 label: () =>
@@ -41,12 +41,12 @@ export default {
41 RouterLink,
42 {
43 to: {
44 - name: "cards-list"
44 + name: "Cards-List"
45 }
46 },
47 { default: () => "List" }
48 ),
49 - key: "cards-list"
49 + key: "Cards-List"
50 },
51 {
52 label: () =>
@@ -54,12 +54,12 @@ export default {
54 RouterLink,
55 {
56 to: {
57 - name: "cards-extra"
57 + name: "Cards-Extra"
58 }
59 },
60 { default: () => "Extra" }
61 ),
62 - key: "cards-extra"
62 + key: "Cards-Extra"
63 },
64 {
65 label: () =>
@@ -67,12 +67,12 @@ export default {
67 RouterLink,
68 {
69 to: {
70 - name: "cards-combo"
70 + name: "Cards-Combo"
71 }
72 },
73 { default: () => "Combo" }
74 ),
75 - key: "cards-combo"
75 + key: "Cards-Combo"
76 }
77 ]
78 }
src/layouts/common/Navbar/charts.ts
+6 -6
@@ -2,11 +2,11 @@ import { renderIcon } from "@/utils"
2 import { h } from "vue"
3 import { RouterLink } from "vue-router"
4
5 -import ChartIcon from "@vicons/carbon/ChartHistogram"
5 +const ChartIcon = "carbon:chart-histogram"
6
7 export default {
8 label: "Charts",
9 - key: "charts",
9 + key: "Charts",
10 icon: renderIcon(ChartIcon),
11 children: [
12 {
@@ -15,12 +15,12 @@ export default {
15 RouterLink,
16 {
17 to: {
18 - name: "charts-apexcharts"
18 + name: "Charts-ApexCharts"
19 }
20 },
21 { default: () => "ApexCharts" }
22 ),
23 - key: "charts-apexcharts"
23 + key: "Charts-ApexCharts"
24 },
25 {
26 label: () =>
@@ -28,12 +28,12 @@ export default {
28 RouterLink,
29 {
30 to: {
31 - name: "charts-chartjs"
31 + name: "Charts-ChartJS"
32 }
33 },
34 { default: () => "ChartJS" }
35 ),
36 - key: "charts-chartjs"
36 + key: "Charts-ChartJS"
37 }
38 ]
39 }
src/layouts/common/Navbar/components.tsx
+83 -83
@@ -2,120 +2,120 @@ import { renderIcon } from "@/utils"
2 import { h } from "vue"
3 import { RouterLink } from "vue-router"
4
5 -import ComponentsIcon from "@vicons/fluent/Apps24Regular"
5 +const ComponentsIcon = "fluent:apps-24-regular"
6 import { type MenuMixedOption } from "naive-ui/es/menu/src/interface"
7
8 const components = [
9 {
10 - key: "components-group-common",
10 + key: "Components-Group-common",
11 label: "Common",
12 items: [
13 - { key: "components-avatar", label: "Avatar" },
14 - { key: "components-button", label: "Button" },
15 - { key: "components-card", label: "Card" },
16 - { key: "components-carousel", label: "Carousel" },
17 - { key: "components-collapse", label: "Collapse" },
18 - { key: "components-divider", label: "Divider" },
19 - { key: "components-dropdown", label: "Dropdown" },
20 - { key: "components-ellipsis", label: "Ellipsis" },
21 - { key: "components-gradient-text", label: "Gradient Text" },
22 - { key: "components-icon", label: "Icon" },
23 - { key: "components-page-header", label: "Page Header" },
24 - { key: "components-tag", label: "Tag" },
25 - { key: "components-typography", label: "Typography" },
26 - { key: "components-watermark", label: "Watermark" }
13 + { key: "Components-Avatar", label: "Avatar" },
14 + { key: "Components-Button", label: "Button" },
15 + { key: "Components-Card", label: "Card" },
16 + { key: "Components-Carousel", label: "Carousel" },
17 + { key: "Components-Collapse", label: "Collapse" },
18 + { key: "Components-Divider", label: "Divider" },
19 + { key: "Components-Dropdown", label: "Dropdown" },
20 + { key: "Components-Ellipsis", label: "Ellipsis" },
21 + { key: "Components-GradientText", label: "Gradient Text" },
22 + { key: "Components-Icon", label: "Icon" },
23 + { key: "Components-PageHeader", label: "Page Header" },
24 + { key: "Components-Tag", label: "Tag" },
25 + { key: "Components-Typography", label: "Typography" },
26 + { key: "Components-Watermark", label: "Watermark" }
27 ]
28 },
29 {
30 - key: "components-group-input",
30 + key: "Components-Group-input",
31 label: "Input",
32 items: [
33 - { key: "components-auto-complete", label: "Auto Complete" },
34 - { key: "components-cascader", label: "Cascader" },
35 - { key: "components-color-picker", label: "Color Picker" },
36 - { key: "components-checkbox", label: "Checkbox" },
37 - { key: "components-date-picker", label: "Date Picker" },
38 - { key: "components-dynamic-input", label: "Dynamic Input" },
39 - { key: "components-dynamic-tags", label: "Dynamic Tags" },
40 - { key: "components-form", label: "Form" },
41 - { key: "components-input", label: "Input" },
42 - { key: "components-input-number", label: "Input Number" },
43 - { key: "components-mention", label: "Mention" },
44 - { key: "components-radio", label: "Radio" },
45 - { key: "components-rate", label: "Rate" },
46 - { key: "components-select", label: "Select" },
47 - { key: "components-slider", label: "Slider" },
48 - { key: "components-switch", label: "Switch" },
49 - { key: "components-time-picker", label: "Time Picker" },
50 - { key: "components-transfer", label: "Transfer" },
51 - { key: "components-tree-select", label: "Tree Select" },
52 - { key: "components-upload", label: "Upload" }
33 + { key: "Components-AutoComplete", label: "Auto Complete" },
34 + { key: "Components-Cascader", label: "Cascader" },
35 + { key: "Components-ColorPicker", label: "Color Picker" },
36 + { key: "Components-Checkbox", label: "Checkbox" },
37 + { key: "Components-DatePicker", label: "Date Picker" },
38 + { key: "Components-DynamicInput", label: "Dynamic Input" },
39 + { key: "Components-DynamicTags", label: "Dynamic Tags" },
40 + { key: "Components-Form", label: "Form" },
41 + { key: "Components-Input", label: "Input" },
42 + { key: "Components-InputNumber", label: "Input Number" },
43 + { key: "Components-Mention", label: "Mention" },
44 + { key: "Components-Radio", label: "Radio" },
45 + { key: "Components-Rate", label: "Rate" },
46 + { key: "Components-Select", label: "Select" },
47 + { key: "Components-Slider", label: "Slider" },
48 + { key: "Components-Switch", label: "Switch" },
49 + { key: "Components-TimePicker", label: "Time Picker" },
50 + { key: "Components-Transfer", label: "Transfer" },
51 + { key: "Components-TreeSelect", label: "Tree Select" },
52 + { key: "Components-Upload", label: "Upload" }
53 ]
54 },
55 {
56 - key: "components-group-data-display",
56 + key: "Components-Group-Data-Display",
57 label: "Data Display",
58 items: [
59 - { key: "components-calendar", label: "Calendar" },
60 - { key: "components-countdown", label: "Countdown" },
61 - { key: "components-data-table", label: "Data Table" },
62 - { key: "components-descriptions", label: "Descriptions" },
63 - { key: "components-empty", label: "Empty" },
64 - { key: "components-image", label: "Image" },
65 - { key: "components-list", label: "List" },
66 - { key: "components-number-animation", label: "Number Animation" },
67 - { key: "components-scrollbar", label: "Scrollbar" },
68 - { key: "components-statistic", label: "Statistic" },
69 - { key: "components-table", label: "Table" },
70 - { key: "components-thing", label: "Thing" },
71 - { key: "components-time", label: "Time" },
72 - { key: "components-timeline", label: "Timeline" },
73 - { key: "components-tree", label: "Tree" }
59 + { key: "Components-Calendar", label: "Calendar" },
60 + { key: "Components-Countdown", label: "Countdown" },
61 + { key: "Components-DataTable", label: "Data Table" },
62 + { key: "Components-Descriptions", label: "Descriptions" },
63 + { key: "Components-Empty", label: "Empty" },
64 + { key: "Components-Image", label: "Image" },
65 + { key: "Components-List", label: "List" },
66 + { key: "Components-NumberAnimation", label: "Number Animation" },
67 + { key: "Components-Scrollbar", label: "Scrollbar" },
68 + { key: "Components-Statistic", label: "Statistic" },
69 + { key: "Components-Table", label: "Table" },
70 + { key: "Components-Thing", label: "Thing" },
71 + { key: "Components-Time", label: "Time" },
72 + { key: "Components-Timeline", label: "Timeline" },
73 + { key: "Components-Tree", label: "Tree" }
74 ]
75 },
76 {
77 - key: "components-group-navigation",
77 + key: "Components-Group-Navigation",
78 label: "Navigation",
79 items: [
80 - { key: "components-affix", label: "Affix" },
81 - { key: "components-anchor", label: "Anchor" },
82 - { key: "components-back-top", label: "Back Top" },
83 - { key: "components-breadcrumb", label: "Breadcrumb" },
84 - { key: "components-menu", label: "Menu" },
85 - { key: "components-pagination", label: "Pagination" },
86 - { key: "components-steps", label: "Steps" },
87 - { key: "components-tabs", label: "Tabs" }
80 + { key: "Components-Affix", label: "Affix" },
81 + { key: "Components-Anchor", label: "Anchor" },
82 + { key: "Components-BackTop", label: "Back Top" },
83 + { key: "Components-Breadcrumb", label: "Breadcrumb" },
84 + { key: "Components-Menu", label: "Menu" },
85 + { key: "Components-Pagination", label: "Pagination" },
86 + { key: "Components-Steps", label: "Steps" },
87 + { key: "Components-Tabs", label: "Tabs" }
88 ]
89 },
90 {
91 - key: "components-group-feedback",
91 + key: "Components-Group-Feedback",
92 label: "Feedback",
93 items: [
94 - { key: "components-alert", label: "Alert" },
95 - { key: "components-badge", label: "Badge" },
96 - { key: "components-dialog", label: "Dialog" },
97 - { key: "components-drawer", label: "Drawer" },
98 - { key: "components-message", label: "Message" },
99 - { key: "components-modal", label: "Modal" },
100 - { key: "components-notification", label: "Notification" },
101 - { key: "components-popconfirm", label: "Popconfirm" },
102 - { key: "components-popover", label: "Popover" },
103 - { key: "components-popselect", label: "Popselect" },
104 - { key: "components-progress", label: "Progress" },
105 - { key: "components-result", label: "Result" },
106 - { key: "components-skeleton", label: "Skeleton" },
107 - { key: "components-spin", label: "Spin" },
108 - { key: "components-tooltip", label: "Tooltip" }
94 + { key: "Components-Alert", label: "Alert" },
95 + { key: "Components-Badge", label: "Badge" },
96 + { key: "Components-Dialog", label: "Dialog" },
97 + { key: "Components-Drawer", label: "Drawer" },
98 + { key: "Components-Message", label: "Message" },
99 + { key: "Components-Modal", label: "Modal" },
100 + { key: "Components-Notification", label: "Notification" },
101 + { key: "Components-Popconfirm", label: "Popconfirm" },
102 + { key: "Components-Popover", label: "Popover" },
103 + { key: "Components-Popselect", label: "Popselect" },
104 + { key: "Components-Progress", label: "Progress" },
105 + { key: "Components-Result", label: "Result" },
106 + { key: "Components-Skeleton", label: "Skeleton" },
107 + { key: "Components-Spin", label: "Spin" },
108 + { key: "Components-Tooltip", label: "Tooltip" }
109 ]
110 },
111 {
112 - key: "components-group-layout",
112 + key: "Components-Group-Layout",
113 label: "Layout",
114 items: [
115 - { key: "components-layout", label: "Layout" },
116 - { key: "components-legacy-grid", label: "Legacy Grid" },
117 - { key: "components-grid", label: "Grid" },
118 - { key: "components-space", label: "Space" }
115 + { key: "Components-Layout", label: "Layout" },
116 + { key: "Components-LegacyGrid", label: "Legacy Grid" },
117 + { key: "Components-Grid", label: "Grid" },
118 + { key: "Components-Space", label: "Space" }
119 ]
120 }
121 ]
src/layouts/common/Navbar/dashboard.ts
+6 -6
@@ -2,11 +2,11 @@ import { renderIcon } from "@/utils"
2 import { h } from "vue"
3 import { RouterLink } from "vue-router"
4
5 -import DashboardIcon from "@vicons/carbon/Dashboard"
5 +const DashboardIcon = "carbon:dashboard"
6
7 export default {
8 label: "Dashboard",
9 - key: "dashboard",
9 + key: "Dashboard",
10 icon: renderIcon(DashboardIcon),
11 children: [
12 {
@@ -15,12 +15,12 @@ export default {
15 RouterLink,
16 {
17 to: {
18 - name: "analytics"
18 + name: "Dashboard-Analytics"
19 }
20 },
21 { default: () => "Analytics" }
22 ),
23 - key: "analytics"
23 + key: "Dashboard-Analytics"
24 },
25 {
26 label: () =>
@@ -28,12 +28,12 @@ export default {
28 RouterLink,
29 {
30 to: {
31 - name: "ecommerce"
31 + name: "Dashboard-eCommerce"
32 }
33 },
34 { default: () => "eCommerce" }
35 ),
36 - key: "ecommerce"
36 + key: "Dashboard-eCommerce"
37 }
38 ]
39 }
src/layouts/common/Navbar/editors.ts
+8 -8
@@ -2,11 +2,11 @@ import { renderIcon } from "@/utils"
2 import { h } from "vue"
3 import { RouterLink } from "vue-router"
4
5 -import EditorIcon from "@vicons/carbon/Pen"
5 +const EditorIcon = "carbon:pen"
6
7 export default {
8 label: "Editors",
9 - key: "editors",
9 + key: "Editors",
10 icon: renderIcon(EditorIcon),
11 children: [
12 {
@@ -15,12 +15,12 @@ export default {
15 RouterLink,
16 {
17 to: {
18 - name: "editors-quill"
18 + name: "Editors-Quill"
19 }
20 },
21 { default: () => "Quill" }
22 ),
23 - key: "editors-quill"
23 + key: "Editors-Quill"
24 },
25 {
26 label: () =>
@@ -28,12 +28,12 @@ export default {
28 RouterLink,
29 {
30 to: {
31 - name: "editors-tiptap"
31 + name: "Editors-Tiptap"
32 }
33 },
34 { default: () => "Tiptap" }
35 ),
36 - key: "editors-tiptap"
36 + key: "Editors-Tiptap"
37 },
38 {
39 label: () =>
@@ -41,12 +41,12 @@ export default {
41 RouterLink,
42 {
43 to: {
44 - name: "editors-milkdown"
44 + name: "Editors-Milkdown"
45 }
46 },
47 { default: () => "Milkdown" }
48 ),
49 - key: "editors-milkdown"
49 + key: "Editors-Milkdown"
50 }
51 ]
52 }
src/layouts/common/Navbar/icons.ts deleted
-39
@@ -1,39 +0,0 @@
1 -import { renderIcon } from "@/utils"
2 -import { h } from "vue"
3 -import { RouterLink } from "vue-router"
4 -
5 -import IconsIcon from "@vicons/fluent/Icons24Regular"
6 -
7 -export default {
8 - label: "Icons",
9 - key: "icons",
10 - icon: renderIcon(IconsIcon),
11 - children: [
12 - {
13 - label: () =>
14 - h(
15 - RouterLink,
16 - {
17 - to: {
18 - name: "icons-xicons"
19 - }
20 - },
21 - { default: () => "xIcons" }
22 - ),
23 - key: "icons-xicons"
24 - },
25 - {
26 - label: () =>
27 - h(
28 - RouterLink,
29 - {
30 - to: {
31 - name: "icons-flag"
32 - }
33 - },
34 - { default: () => "Flag" }
35 - ),
36 - key: "icons-flag"
37 - }
38 - ]
39 -}
src/layouts/common/Navbar/index.ts deleted
-2
@@ -1,2 +0,0 @@
1 -import Navbar from "./Navbar.vue"
2 -export default Navbar
src/layouts/common/Navbar/index.vue renamed
+19 -12
@@ -26,13 +26,14 @@ import getItems from "./items"
26 import { useThemeStore } from "@/stores/theme"
27 import { type MenuMixedOption } from "naive-ui/es/menu/src/interface"
28 import { computed, onBeforeMount, ref, toRefs } from "vue"
29 -import { useRouter } from "vue-router"
29 +import { useRouter, useRoute, type RouteRecordNormalized } from "vue-router"
30 import _uniq from "lodash/uniq"
31
32 defineOptions({
33 name: "Navbar"
34 })
35
36 +const route = useRoute()
37 const router = useRouter()
38
39 const props = withDefaults(
@@ -52,17 +53,23 @@ const menuOptions = computed<MenuMixedOption[]>(() => getItems(mode.value, colla
53 const collapsedWidth = computed<number>(() => useThemeStore().sidebar.closeWidth)
54 const sidebarCollapsed = computed<boolean>(() => useThemeStore().sidebar.collapsed)
55
56 +function setMenuKey(matched: RouteRecordNormalized[]) {
57 + for (const match of matched) {
58 + if (match.name && typeof match.name === "string") {
59 + selectedKey.value = match.name?.toString() || null
60 + if (selectedKey.value) {
61 + menu.value?.showOption(selectedKey.value)
62 + }
63 + }
64 + }
65 +}
66 +
67 onBeforeMount(() => {
68 + setMenuKey(route.matched)
69 +
70 router.afterEach(route => {
71 if (route?.matched?.length) {
58 - for (const match of route.matched) {
59 - if (match.name && typeof match.name === "string") {
60 - selectedKey.value = match.name?.toString() || null
61 - if (selectedKey.value) {
62 - menu.value?.showOption(selectedKey.value)
63 - }
64 - }
65 - }
72 + setMenuKey(route.matched)
73
74 if (window.innerWidth <= 700 && !sidebarCollapsed.value) {
75 useThemeStore().closeSidebar()
@@ -109,7 +116,7 @@ function handleUpdateExpandedKeys(value: string[]) {
116
117 :nth-child(2) {
118 color: var(--fg-color);
112 - background: rgba(var(--fg-color-rgb), 0.07);
119 + background: var(--hover-005-color);
120 margin-right: 10px;
121 height: 22px;
122 line-height: 24px;
@@ -197,7 +204,7 @@ function handleUpdateExpandedKeys(value: string[]) {
204
205 :nth-child(2) {
206 color: var(--fg-color);
200 - background: rgba(var(--fg-color-rgb), 0.07);
207 + background: var(--hover-005-color);
208 font-weight: bold;
209 font-family: var(--font-family-mono);
210 font-size: 10px;
@@ -214,7 +221,7 @@ function handleUpdateExpandedKeys(value: string[]) {
221 .item-badge {
222 :nth-child(2) {
223 color: var(--n-item-text-color-active);
217 - background: rgba(var(--primary-color-rgb), 0.1);
224 + background: var(--primary-010-color);
225 }
226 }
227 }
src/layouts/common/Navbar/items.tsx
+80 -98
@@ -3,20 +3,17 @@ import { h } from "vue"
3 import { RouterLink } from "vue-router"
4 import { type MenuMixedOption } from "naive-ui/es/menu/src/interface"
5
6 -import BlankIcon from "@vicons/carbon/DocumentBlank"
7 -import EmailIcon from "@vicons/carbon/Email"
8 -import ChatIcon from "@vicons/carbon/Chat"
9 -import KanbanIcon from "@vicons/fluent/GridKanban20Regular"
10 -import NotesIcon from "@vicons/carbon/Notebook"
11 -import TypographyIcon from "@vicons/fluent/TextFont16Regular"
12 -import MultiLanguageIcon from "@vicons/ionicons5/LanguageOutline"
13 -import GroupIcon from "@vicons/carbon/TreeView"
14 -import CalendarIcon from "@vicons/carbon/Calendar"
6 +const BlankIcon = "carbon:document-blank"
7 +const TypographyIcon = "fluent:text-font-16-regular"
8 +const MultiLanguageIcon = "ion:language-outline"
9 +const GroupIcon = "carbon:tree-view"
10 +const IconsIcon = "fluent:icons-24-regular"
11
12 import dashboard from "./dashboard"
13 +import calendars from "./calendars"
14 +import apps from "./apps"
15 import cards from "./cards"
16 import getComponents from "./components"
19 -import icons from "./icons"
17 import tables from "./tables"
18 import layout from "./layout"
19 import maps from "./maps"
@@ -33,13 +30,13 @@ export default function getItems(mode: "vertical" | "horizontal", collapsed: boo
30 RouterLink,
31 {
32 to: {
36 - name: "indices"
33 + name: "Indices"
34 }
35 },
36 { default: () => "Indices" }
37 ),
41 - key: "indices",
42 - icon: renderIcon(NotesIcon)
38 + key: "Indices",
39 + icon: renderIcon(BlankIcon)
40 },
41 {
42 label: () =>
@@ -47,13 +44,13 @@ export default function getItems(mode: "vertical" | "horizontal", collapsed: boo
44 RouterLink,
45 {
46 to: {
50 - name: "agents"
47 + name: "Agents"
48 }
49 },
50 { default: () => "Agents" }
51 ),
55 - key: "agents",
56 - icon: renderIcon(NotesIcon)
52 + key: "Agents",
53 + icon: renderIcon(BlankIcon)
54 },
55 {
56 label: () =>
@@ -61,16 +58,69 @@ export default function getItems(mode: "vertical" | "horizontal", collapsed: boo
58 RouterLink,
59 {
60 to: {
64 - name: "connectors"
61 + name: "Connectors"
62 }
63 },
64 { default: () => "Connectors" }
65 ),
69 - key: "connectors",
70 - icon: renderIcon(NotesIcon)
66 + key: "Connectors",
67 + icon: renderIcon(BlankIcon)
68 },
69 {
73 - key: "divider",
70 + label: "Graylog",
71 + key: "Graylog",
72 + icon: renderIcon(BlankIcon),
73 + children: [
74 + {
75 + label: () =>
76 + h(
77 + RouterLink,
78 + {
79 + to: {
80 + name: "Graylog-Management"
81 + }
82 + },
83 + { default: () => "Management" }
84 + ),
85 + key: "Graylog-Management"
86 + },
87 + {
88 + label: () =>
89 + h(
90 + RouterLink,
91 + {
92 + to: {
93 + name: "Graylog-Metrics"
94 + }
95 + },
96 + { default: () => "Metrics" }
97 + ),
98 + key: "Graylog-Metrics"
99 + },
100 + {
101 + label: () =>
102 + h(
103 + RouterLink,
104 + {
105 + to: {
106 + name: "Graylog-Pipelines"
107 + }
108 + },
109 + { default: () => "Pipelines" }
110 + ),
111 + key: "Graylog-Pipelines"
112 + }
113 + ]
114 + },
115 + {
116 + type: "divider"
117 + },
118 +
119 + dashboard,
120 + calendars,
121 + ...apps,
122 + {
123 + key: "divider-1",
124 type: "divider",
125 props: {
126 style: {
@@ -78,62 +128,19 @@ export default function getItems(mode: "vertical" | "horizontal", collapsed: boo
128 }
129 }
130 },
81 - dashboard,
82 - {
83 - label: () =>
84 - h(
85 - RouterLink,
86 - {
87 - to: {
88 - name: "calendar"
89 - }
90 - },
91 - { default: () => "Calendar" }
92 - ),
93 - key: "calendar",
94 - icon: renderIcon(CalendarIcon)
95 - },
96 - {
97 - label: () =>
98 - h(
99 - RouterLink,
100 - {
101 - to: {
102 - name: "email"
103 - }
104 - },
105 - { default: () => "Email" }
106 - ),
107 - key: "email",
108 - icon: renderIcon(EmailIcon)
109 - },
110 - {
111 - label: () =>
112 - h(
113 - RouterLink,
114 - {
115 - to: {
116 - name: "chat"
117 - }
118 - },
119 - { default: () => "Chat" }
120 - ),
121 - key: "chat",
122 - icon: renderIcon(ChatIcon)
123 - },
131 {
132 label: () =>
133 h(
134 RouterLink,
135 {
136 to: {
130 - name: "kanban"
137 + name: "Icons"
138 }
139 },
133 - { default: () => "Kanban" }
140 + { default: () => "Icons" }
141 ),
135 - key: "kanban",
136 - icon: renderIcon(KanbanIcon)
142 + key: "Icons",
143 + icon: renderIcon(IconsIcon)
144 },
145 {
146 label: () =>
@@ -141,22 +148,13 @@ export default function getItems(mode: "vertical" | "horizontal", collapsed: boo
148 RouterLink,
149 {
150 to: {
144 - name: "notes"
151 + name: "Typography"
152 }
153 },
147 - { default: () => "Notes" }
154 + { default: () => "Typography" }
155 ),
149 - key: "notes",
150 - icon: renderIcon(NotesIcon)
151 - },
152 - {
153 - key: "divider",
154 - type: "divider",
155 - props: {
156 - style: {
157 - //marginLeft: "32px"
158 - }
159 - }
156 + key: "Typography",
157 + icon: renderIcon(TypographyIcon)
158 },
159 {
160 label: () =>
@@ -164,30 +162,15 @@ export default function getItems(mode: "vertical" | "horizontal", collapsed: boo
162 RouterLink,
163 {
164 to: {
167 - name: "multi-language"
165 + name: "MultiLanguage"
166 }
167 },
168 { default: () => "Multi Language" }
169 ),
172 - key: "multi-language",
170 + key: "MultiLanguage",
171 icon: renderIcon(MultiLanguageIcon)
172 },
175 - {
176 - label: () =>
177 - h(
178 - RouterLink,
179 - {
180 - to: {
181 - name: "typography"
182 - }
183 - },
184 - { default: () => "Typography" }
185 - ),
186 - key: "typography",
187 - icon: renderIcon(TypographyIcon)
188 - },
173 authentication,
190 - icons,
174 cards,
175 tables,
176 getComponents(),
@@ -196,7 +179,6 @@ export default function getItems(mode: "vertical" | "horizontal", collapsed: boo
179 editors,
180 layout,
181 toolbox,
199 -
182 {
183 label: () => (
184 <div class={"item-badge"}>
src/layouts/common/Navbar/layout.ts
+8 -8
@@ -2,11 +2,11 @@ import { renderIcon } from "@/utils"
2 import { h } from "vue"
3 import { RouterLink } from "vue-router"
4
5 -import LayoutIcon from "@vicons/fluent/DualScreenVerticalScroll24Regular"
5 +const LayoutIcon = "fluent:dual-screen-vertical-scroll-24-regular"
6
7 export default {
8 label: "Layout",
9 - key: "layout",
9 + key: "Layout",
10 icon: renderIcon(LayoutIcon),
11 children: [
12 {
@@ -15,12 +15,12 @@ export default {
15 RouterLink,
16 {
17 to: {
18 - name: "layout-full-width"
18 + name: "Layout-FullWidth"
19 }
20 },
21 { default: () => "Full Width" }
22 ),
23 - key: "layout-full-width"
23 + key: "Layout-FullWidth"
24 },
25 {
26 label: () =>
@@ -28,12 +28,12 @@ export default {
28 RouterLink,
29 {
30 to: {
31 - name: "layout-left-sidebar"
31 + name: "Layout-LeftSidebar"
32 }
33 },
34 { default: () => "Left Sidebar" }
35 ),
36 - key: "layout-left-sidebar"
36 + key: "Layout-LeftSidebar"
37 },
38 {
39 label: () =>
@@ -41,12 +41,12 @@ export default {
41 RouterLink,
42 {
43 to: {
44 - name: "layout-right-sidebar"
44 + name: "Layout-RightSidebar"
45 }
46 },
47 { default: () => "Right Sidebar" }
48 ),
49 - key: "layout-right-sidebar"
49 + key: "Layout-RightSidebar"
50 }
51 ]
52 }
src/layouts/common/Navbar/maps.ts
+10 -10
@@ -2,11 +2,11 @@ import { renderIcon } from "@/utils"
2 import { h } from "vue"
3 import { RouterLink } from "vue-router"
4
5 -import MapIcon from "@vicons/carbon/Map"
5 +const MapIcon = "carbon:map"
6
7 export default {
8 label: "Maps",
9 - key: "maps",
9 + key: "Maps",
10 icon: renderIcon(MapIcon),
11 children: [
12 {
@@ -15,12 +15,12 @@ export default {
15 RouterLink,
16 {
17 to: {
18 - name: "maps-google-maps"
18 + name: "Maps-GoogleMaps"
19 }
20 },
21 { default: () => "Google Maps" }
22 ),
23 - key: "maps-google-maps"
23 + key: "Maps-GoogleMaps"
24 },
25 {
26 label: () =>
@@ -28,12 +28,12 @@ export default {
28 RouterLink,
29 {
30 to: {
31 - name: "maps-maplibre"
31 + name: "Maps-MapLibre"
32 }
33 },
34 { default: () => "MapLibre" }
35 ),
36 - key: "maps-maplibre"
36 + key: "Maps-MapLibre"
37 },
38 {
39 label: () =>
@@ -41,12 +41,12 @@ export default {
41 RouterLink,
42 {
43 to: {
44 - name: "maps-leaflet"
44 + name: "Maps-Leaflet"
45 }
46 },
47 { default: () => "Leaflet" }
48 ),
49 - key: "maps-leaflet"
49 + key: "Maps-Leaflet"
50 },
51 {
52 label: () =>
@@ -54,12 +54,12 @@ export default {
54 RouterLink,
55 {
56 to: {
57 - name: "maps-vectormap"
57 + name: "Maps-VectorMap"
58 }
59 },
60 { default: () => "Vector Map" }
61 ),
62 - key: "maps-vectormap"
62 + key: "Maps-VectorMap"
63 }
64 ]
65 }
src/layouts/common/Navbar/tables.ts
+21 -6
@@ -2,11 +2,11 @@ import { renderIcon } from "@/utils"
2 import { h } from "vue"
3 import { RouterLink } from "vue-router"
4
5 -import TablesIcon from "@vicons/carbon/DataTable"
5 +const TablesIcon = "carbon:data-table"
6
7 export default {
8 label: "Tables",
9 - key: "tables",
9 + key: "Tables",
10 icon: renderIcon(TablesIcon),
11 children: [
12 {
@@ -15,12 +15,12 @@ export default {
15 RouterLink,
16 {
17 to: {
18 - name: "tables-base"
18 + name: "Tables-Base"
19 }
20 },
21 { default: () => "Base" }
22 ),
23 - key: "tables-base"
23 + key: "Tables-Base"
24 },
25 {
26 label: () =>
@@ -28,12 +28,27 @@ export default {
28 RouterLink,
29 {
30 to: {
31 - name: "tables-data-table"
31 + name: "Tables-DataTable"
32 }
33 },
34 { default: () => "Data Table" }
35 ),
36 - key: "tables-data-table"
36 + key: "Tables-Data-table"
37 }
38 + /*
39 + {
40 + label: () =>
41 + h(
42 + RouterLink,
43 + {
44 + to: {
45 + name: "Tables-Grid"
46 + }
47 + },
48 + { default: () => "Data Grid" }
49 + ),
50 + key: "Tables-Grid"
51 + }
52 + */
53 ]
54 }
src/layouts/common/Navbar/toolbox.ts
+5 -5
@@ -2,7 +2,7 @@ import { renderIcon } from "@/utils"
2 import { h } from "vue"
3 import { RouterLink } from "vue-router"
4
5 -import ToolboxIcon from "@vicons/carbon/ToolBox"
5 +const ToolboxIcon = "carbon:tool-box"
6
7 export default {
8 label: "Toolbox",
@@ -15,14 +15,14 @@ export default {
15 RouterLink,
16 {
17 to: {
18 - name: "toolbox-refresh-tool"
18 + name: "Toolbox-RefreshTool"
19 }
20 },
21 {
22 default: () => "Refresh Tool"
23 }
24 ),
25 - key: "toolbox-refresh-tool"
25 + key: "Toolbox-RefreshTool"
26 },
27 {
28 label: () =>
@@ -30,14 +30,14 @@ export default {
30 RouterLink,
31 {
32 to: {
33 - name: "toolbox-tour"
33 + name: "Toolbox-Tour"
34 }
35 },
36 {
37 default: () => "Tour"
38 }
39 ),
40 - key: "toolbox-tour"
40 + key: "Toolbox-Tour"
41 }
42 ]
43 }
src/layouts/common/Provider.vue
+5 -2
@@ -4,7 +4,9 @@
4 <n-message-provider>
5 <n-notification-provider>
6 <n-dialog-provider>
7 - <slot />
7 + <GlobalListener>
8 + <slot />
9 + </GlobalListener>
10 </n-dialog-provider>
11 </n-notification-provider>
12 </n-message-provider>
@@ -14,6 +16,7 @@
16 </template>
17
18 <script lang="ts" setup>
19 +import { computed, onBeforeMount, watch } from "vue"
20 import {
21 NGlobalStyle,
22 NConfigProvider,
@@ -25,7 +28,7 @@ import {
28 } from "naive-ui"
29 import { useThemeStore } from "@/stores/theme"
30 import { useWindowSize } from "@vueuse/core"
28 -import { computed, onBeforeMount, watch } from "vue"
31 +import GlobalListener from "@/layouts/common/GlobalListener.vue"
32
33 const { width } = useWindowSize()
34
src/layouts/common/Toolbar/Avatar.vue
+6 -4
@@ -6,12 +6,14 @@
6
7 <script lang="ts" setup>
8 import { NAvatar, NDropdown } from "naive-ui"
9 -import { PersonOutline as UserIcon, LogOutOutline as LogoutIcon } from "@vicons/ionicons5"
10 -import DocsIcon from "@vicons/ionicons5/BookOutline"
9 import { renderIcon } from "@/utils"
10 import { useRouter } from "vue-router"
11 import { ref, h } from "vue"
12
13 +const UserIcon = "ion:person-outline"
14 +const LogoutIcon = "ion:log-out-outline"
15 +const DocsIcon = "ion:book-outline"
16 +
17 defineOptions({
18 name: "Avatar"
19 })
@@ -21,7 +23,7 @@ const router = useRouter()
23 const options = ref([
24 {
25 label: "Profile",
24 - key: "route-profile",
26 + key: "route-Profile",
27 icon: renderIcon(UserIcon)
28 },
29 {
@@ -40,7 +42,7 @@ const options = ref([
42 },
43 {
44 label: "Logout",
43 - key: "route-logout",
45 + key: "route-Logout",
46 icon: renderIcon(LogoutIcon)
47 }
48 ])
src/layouts/common/Toolbar/Breadcrumb.vue
+46 -54
@@ -1,44 +1,37 @@
1 <template>
2 <n-breadcrumb class="breadcrumb">
3 - <XyzTransitionGroup class="item-group" xyz="fade stagger-1 left-1">
4 - <n-breadcrumb-item @click="goto({ path: '/' })" :key="'///'">
5 - <n-icon :size="16"><Home24Regular /></n-icon>
6 - </n-breadcrumb-item>
3 + <n-breadcrumb-item @click="goto({ path: '/' })">
4 + <Icon :size="16" :name="HomeIcon"></Icon>
5 + </n-breadcrumb-item>
6 + <TransitionGroup name="anim">
7 <n-breadcrumb-item
8 - v-for="item of items"
8 + v-for="(item, index) of items"
9 :key="item.key"
10 - @click="item.children?.length ? () => {} : goto(item)"
10 + :clickable="false"
11 + :class="`index-${index}`"
12 >
12 - <n-dropdown :options="item.children" v-if="item.children?.length">
13 - <div class="trigger">
14 - {{ item.title }}
15 - </div>
16 - </n-dropdown>
17 - <span v-else>
18 - {{ item.title }}
19 - </span>
13 + {{ item.name }}
14 </n-breadcrumb-item>
21 - </XyzTransitionGroup>
15 + </TransitionGroup>
16 </n-breadcrumb>
17 </template>
18
19 <script lang="ts" setup>
26 -import { NBreadcrumb, NBreadcrumbItem, NIcon, NDropdown } from "naive-ui"
27 -import Home24Regular from "@vicons/fluent/Home24Regular"
28 -import _trim from "lodash/trim"
20 +import { NBreadcrumb, NBreadcrumbItem } from "naive-ui"
21 +import _upperCase from "lodash/upperCase"
22 import _capitalize from "lodash/capitalize"
30 -import { type RouteLocationNormalizedLoaded, type RouteRecordNormalized, type RouteRecordRaw } from "vue-router"
23 +import _split from "lodash/split"
24 +import { type RouteLocationNormalizedLoaded } from "vue-router"
25 import { onBeforeMount, ref } from "vue"
26 import { useRouter, useRoute } from "vue-router"
27 +import Icon from "@/components/common/Icon.vue"
28 +
29 +const HomeIcon = "fluent:home-24-regular"
30
31 interface Page {
32 name: string
33 path: string
37 - title: string
38 - children?: Page[]
39 - props?: { onClick: () => void }
40 - label?: string
41 - key?: string
34 + key: string
35 }
36
37 defineOptions({
@@ -60,36 +53,18 @@ function goto(page: Partial<Page>) {
53 return
54 }
55 }
63 -function transformRoute(route: RouteRecordNormalized | RouteRecordRaw): Page {
64 - const name = route.name?.toString() || ""
65 - const path = route.path?.toString() || ""
66 - const title = route.meta?.title?.toString() || _capitalize(name || _trim(path, "/"))
67 - const label = title
68 - const key = name || path
69 - const props = {
70 - onClick: () => {
71 - goto({ name, path })
72 - }
73 - }
56
75 - return { name, path, title, props, label, key }
76 -}
57 function checkRoute(route: RouteLocationNormalizedLoaded) {
58 const newItems: Page[] = []
59 + const pathChunks = route?.path?.indexOf("/") !== -1 ? _split(route?.path || "", "/") : [route?.path]
60
80 - if (route?.matched?.length) {
81 - for (const match of route.matched) {
82 - const partial = transformRoute(match)
83 - if (match.children?.length) {
84 - partial.children =
85 - match.children
86 - .filter(p => p.name !== route.name && p.path !== route.path)
87 - .map(p => transformRoute(p)) || []
88 - }
89 -
90 - if (partial.name || partial.path) {
91 - newItems.push(partial)
92 - }
61 + for (const chunk of pathChunks) {
62 + if (chunk) {
63 + newItems.push({
64 + name: _capitalize(_upperCase(chunk)),
65 + path: chunk.toLowerCase(),
66 + key: chunk + new Date().getTime()
67 + })
68 }
69 }
70
@@ -99,15 +74,32 @@ function checkRoute(route: RouteLocationNormalizedLoaded) {
74 onBeforeMount(() => {
75 checkRoute(router.currentRoute.value)
76
102 - router.afterEach(route => {
77 + router.beforeResolve(route => {
78 checkRoute(route)
79 })
80 })
81 </script>
82
83 <style lang="scss" scoped>
109 -.item-group {
110 - --xyz-out-duration: 0;
111 - --xyz-out-delay: 0;
84 +.breadcrumb {
85 + .anim-move,
86 + .anim-enter-active {
87 + transition: all 0.5s var(--bezier-ease);
88 +
89 + @for $i from 0 through 10 {
90 + &.index-#{$i} {
91 + transition-delay: $i * 0.1s;
92 + }
93 + }
94 + }
95 +
96 + .anim-leave-active {
97 + display: none;
98 + }
99 +
100 + .anim-enter-from {
101 + opacity: 0;
102 + transform: translateX(-5px);
103 + }
104 }
105 </style>
src/layouts/common/Toolbar/FullscreenBtn.vue deleted
-48
@@ -1,48 +0,0 @@
1 -<template>
2 - <button class="fullscreen-switch" @click="toggleFullscreen" alt="fullscreen-switch" aria-label="fullscreen-switch">
3 - <n-icon size="20">
4 - <FullScreenMinimize24Regular v-if="isFullscreen" />
5 - <FullScreenMaximize24Regular v-else />
6 - </n-icon>
7 - </button>
8 -</template>
9 -
10 -<script lang="ts" setup>
11 -import { NIcon } from "naive-ui"
12 -import FullScreenMaximize24Regular from "@vicons/fluent/FullScreenMaximize24Regular"
13 -import FullScreenMinimize24Regular from "@vicons/fluent/FullScreenMinimize24Regular"
14 -import { useFullscreen } from "@vueuse/core"
15 -import { emitter } from "@/emitter"
16 -import { onMounted } from "vue"
17 -const { isFullscreen, toggle } = useFullscreen()
18 -
19 -defineOptions({
20 - name: "FullscreenBtn"
21 -})
22 -
23 -function toggleFullscreen(e?: MouseEvent) {
24 - toggle()
25 - return e
26 -}
27 -
28 -onMounted(() => {
29 - emitter.on("toggle:fullscreen", () => {
30 - toggleFullscreen()
31 - })
32 -})
33 -</script>
34 -
35 -<style scoped lang="scss">
36 -.fullscreen-switch {
37 - position: relative;
38 - width: 20px;
39 - height: 20px;
40 - overflow: hidden;
41 - outline: none;
42 - border: none;
43 -
44 - @media (max-width: 1000px) {
45 - display: none;
46 - }
47 -}
48 -</style>
src/layouts/common/Toolbar/FullscreenSwitch.vue new
+40
@@ -0,0 +1,40 @@
1 +<template>
2 + <button class="fullscreen-switch" @click="toggleFullscreen" alt="fullscreen-switch" aria-label="fullscreen-switch">
3 + <Icon :size="20" :name="CloseIcon" v-if="isFullscreen"></Icon>
4 + <Icon :size="20" :name="OpenIcon" v-else></Icon>
5 + </button>
6 +</template>
7 +
8 +<script lang="ts" setup>
9 +import Icon from "@/components/common/Icon.vue"
10 +import { useFullscreenSwitch } from "@/composables/useFullscreenSwitch"
11 +
12 +const { isFullscreen, toggle } = useFullscreenSwitch()
13 +
14 +const OpenIcon = "fluent:full-screen-maximize-24-regular"
15 +const CloseIcon = "fluent:full-screen-minimize-24-regular"
16 +
17 +defineOptions({
18 + name: "FullscreenSwitch"
19 +})
20 +
21 +function toggleFullscreen(e?: MouseEvent) {
22 + toggle()
23 + return e
24 +}
25 +</script>
26 +
27 +<style scoped lang="scss">
28 +.fullscreen-switch {
29 + position: relative;
30 + width: 20px;
31 + height: 20px;
32 + overflow: hidden;
33 + outline: none;
34 + border: none;
35 +
36 + @media (max-width: 1000px) {
37 + display: none;
38 + }
39 +}
40 +</style>
src/layouts/common/Toolbar/LocaleSwitch.vue
+13 -36
@@ -1,29 +1,18 @@
1 <template>
2 <n-popselect v-model:value="currentLocale" :options="list" :render-label="renderLabel">
3 - <n-icon size="19">
4 - <MultiLanguageIcon />
5 - </n-icon>
3 + <Icon :size="19" :name="MultiLanguageIcon"></Icon>
4 </n-popselect>
5 </template>
6
7 <script lang="ts" setup>
10 -import { NIcon, NPopselect, type SelectOption } from "naive-ui"
11 -import MultiLanguageIcon from "@vicons/ionicons5/LanguageOutline"
12 -import it from "flag-icons/flags/4x3/it.svg"
13 -import en from "flag-icons/flags/4x3/us.svg"
14 -import fr from "flag-icons/flags/4x3/fr.svg"
15 -import es from "flag-icons/flags/4x3/es.svg"
16 -import de from "flag-icons/flags/4x3/de.svg"
17 -import jp from "flag-icons/flags/4x3/jp.svg"
18 -import { getAvailableLocales, getLocale, setLocale } from "@/utils/i18n"
8 +import { NPopselect, type SelectOption } from "naive-ui"
9 +import Icon from "@/components/common/Icon.vue"
10 +import { useStoreI18n } from "@/composables/useStoreI18n"
11 import { computed, h, type VNodeChild } from "vue"
20 -import { useI18n } from "vue-i18n"
12
22 -defineOptions({
23 - name: "LocaleSwitch"
24 -})
13 +const MultiLanguageIcon = "ion:language-outline"
14
26 -const { t } = useI18n()
15 +const { getAvailableLocales, getLocale, setLocale, t } = useStoreI18n()
16
17 const list = computed(() =>
18 getAvailableLocales().map(i => ({
@@ -39,26 +28,14 @@ const currentLocale = computed({
28
29 function renderLabel(option: SelectOption): VNodeChild {
30 return [
42 - h(
43 - NIcon,
44 - {
45 - color: "#000",
46 - style: {
47 - verticalAlign: "-0.15em",
48 - marginRight: "8px"
49 - }
31 + h(Icon, {
32 + color: "#000",
33 + style: {
34 + verticalAlign: "-0.15em",
35 + marginRight: "8px"
36 },
51 - {
52 - default: () => {
53 - if (option.label === "it") return h(it)
54 - if (option.label === "en") return h(en)
55 - if (option.label === "es") return h(es)
56 - if (option.label === "fr") return h(fr)
57 - if (option.label === "de") return h(de)
58 - if (option.label === "jp") return h(jp)
59 - }
60 - }
61 - ),
37 + name: `circle-flags:${option.label}`
38 + }),
39 h(
40 "span",
41 {},
src/layouts/common/Toolbar/Notifications.vue
+38 -215
@@ -1,163 +1,72 @@
1 <template>
2 <n-popover :show-arrow="false" placement="bottom" content-style="padding:0" style="max-width: 280px">
3 <template #trigger>
4 - <n-badge :show="counter !== 0" dot :color="primaryColor">
5 - <n-icon size="21" class="trigger-icon">
6 - <BellIcon />
7 - </n-icon>
4 + <n-badge :show="hasNotifications" dot :color="primaryColor">
5 + <Icon :name="BellIcon" :size="21" class="trigger-icon"></Icon>
6 </n-badge>
7 </template>
8 <template #header>
9 <n-text strong depth="1">Notifications</n-text>
10 </template>
13 - <n-scrollbar class="notifications-list" style="max-height: 50vh">
14 - <div
15 - class="item flex"
16 - v-for="item of list"
17 - :key="item.id"
18 - @click="item.action ? item.action() : () => {}"
19 - :class="{ pointer: !!item.action }"
20 - >
21 - <div class="icon-box" :class="item.type">
22 - <n-icon size="21" v-if="item.type === 'message'"><MessageIcon /></n-icon>
23 - <n-icon size="21" v-else-if="item.type === 'reminder'"><CalendarIcon /></n-icon>
24 - <n-icon size="21" v-else-if="item.type === 'news'"><NewsIcon /></n-icon>
25 - <n-icon size="21" v-else-if="item.type === 'alert'"><AlertIcon style="margin-top: -4px" /></n-icon>
26 - </div>
27 - <div class="content grow">
28 - <div class="title">{{ item.title }}</div>
29 - <div class="description">{{ item.description }}</div>
30 - <div class="date">{{ item.date }}</div>
31 - </div>
32 - <div class="read-badge" v-if="!item.read"></div>
33 - </div>
34 - </n-scrollbar>
11 + <template #default>
12 + <Notifications :max-items="7" style="max-height: 50vh">
13 + <template #last>
14 + <div class="p-4 flex justify-center">
15 + <n-button text @click="showDrawer = true">View all</n-button>
16 + </div>
17 + </template>
18 + </Notifications>
19 + </template>
20 <template #footer>
21 <div class="flex justify-end">
37 - <n-button strong secondary type="primary" :disabled="!counter" @click="setAllRead()">
22 + <n-button strong secondary type="primary" :disabled="!hasNotifications" @click="setAllRead()">
23 Mark all as read
24 </n-button>
25 </div>
26 </template>
27 </n-popover>
28 +
29 + <n-drawer v-model:show="showDrawer" :width="400" style="max-width: 90vw" :trap-focus="false">
30 + <n-drawer-content title="Notifications" closable body-content-style="padding:0">
31 + <Notifications />
32 + <template #footer>
33 + <div class="flex justify-end">
34 + <n-button strong secondary type="primary" :disabled="!hasNotifications" @click="setAllRead()">
35 + Mark all as read
36 + </n-button>
37 + </div>
38 + </template>
39 + </n-drawer-content>
40 + </n-drawer>
41 </template>
42
43 <script lang="ts" setup>
46 -import { NIcon, NButton, NText, NPopover, NScrollbar, NBadge, useNotification } from "naive-ui"
47 -import { ref, computed, onMounted, h } from "vue"
48 -import BellIcon from "@vicons/antd/BellOutlined"
49 -import MessageIcon from "@vicons/carbon/Email"
50 -import CalendarIcon from "@vicons/carbon/Calendar"
51 -import NewsIcon from "@vicons/fluent/News24Regular"
52 -import AlertIcon from "@vicons/carbon/WarningAlt"
44 +import { NButton, NText, NPopover, NBadge, useNotification, NDrawer, NDrawerContent } from "naive-ui"
45 +import { computed, onMounted, h, ref } from "vue"
46 import dayjs from "@/utils/dayjs"
47 import { useThemeStore } from "@/stores/theme"
48 +import Icon from "@/components/common/Icon.vue"
49 +import Notifications from "@/components/common/Notifications.vue"
50 +import { useNotifications } from "@/composables/useNotifications"
51
56 -type NotificationType = "message" | "reminder" | "alert" | "news" | string
57 -interface Notification {
58 - id: number
59 - type: NotificationType
60 - title: string
61 - description: string
62 - read: boolean
63 - date: string
64 - action?: () => void
65 -}
66 -
67 -defineOptions({
68 - name: "Notifications"
69 -})
52 +const BellIcon = "ph:bell"
53
54 const notification = useNotification()
55 const primaryColor = computed(() => useThemeStore().primaryColor)
73 -const counter = computed(() => list.value.filter(o => !o.read).length)
56 +const hasNotifications = useNotifications().hasNotifications
57
75 -const list = ref<Notification[]>([
76 - {
77 - id: 1,
78 - type: "message",
79 - title: "New Email",
80 - description: "Important document to read",
81 - read: false,
82 - date: "Today"
83 - },
84 - {
85 - id: 2,
86 - type: "reminder",
87 - title: "Appointment",
88 - description: "Meeting with client at 3:00 PM",
89 - read: false,
90 - date: "Yesterday"
91 - },
92 - {
93 - id: 9,
94 - type: "alert",
95 - title: "Alert",
96 - description: "Limited-time super offer on desired product",
97 - read: true,
98 - date: dayjs().subtract(7, "d").format("D MMM")
99 - },
100 - {
101 - id: 5,
102 - type: "news",
103 - title: "News",
104 - description: "Networking event in your city",
105 - read: false,
106 - date: dayjs().subtract(3, "d").format("D MMM")
107 - },
108 - {
109 - id: 3,
110 - type: "reminder",
111 - title: "Reminder",
112 - description: "Overdue bill payment",
113 - read: true,
114 - date: "Yesterday"
115 - },
116 - {
117 - id: 4,
118 - type: "reminder",
119 - title: "Deadline",
120 - description: "Submit report by tomorrow",
121 - read: true,
122 - date: dayjs().subtract(2, "d").format("D MMM")
123 - },
124 - {
125 - id: 6,
126 - type: "message",
127 - title: "Message",
128 - description: "New comment on your post",
129 - read: false,
130 - date: dayjs().subtract(4, "d").format("D MMM")
131 - },
132 - {
133 - id: 7,
134 - type: "reminder",
135 - title: "Reminder",
136 - description: "Complete purchase in your online cart",
137 - read: false,
138 - date: dayjs().subtract(5, "d").format("D MMM")
139 - },
140 - {
141 - id: 8,
142 - type: "reminder",
143 - title: "Invitation",
144 - description: "Friend's birthday party",
145 - read: true,
146 - date: dayjs().subtract(6, "d").format("D MMM")
147 - }
148 -])
58 +const showDrawer = ref(false)
59 +const list = useNotifications().list
60
61 function setAllRead() {
151 - for (const item of list.value) {
152 - item.read = true
153 - }
62 + useNotifications().setAllRead()
63 }
64
65 onMounted(() => {
157 - if (window.innerWidth > 700) {
66 + if (window.innerWidth > 700 && list?.value[0] && list?.value[0].id !== 9999) {
67 setTimeout(() => {
68 const newItem = {
160 - id: 8,
69 + id: 9999,
70 type: "news",
71 title: "Good news",
72 description: "HI! You can buy this template on Themeforest, click here.",
@@ -168,7 +77,7 @@ onMounted(() => {
77 }
78 }
79
171 - list.value = [newItem, ...list.value]
80 + useNotifications().prepend(newItem)
81
82 notification.success({
83 title: newItem.title,
@@ -200,90 +109,4 @@ onMounted(() => {
109 .trigger-icon {
110 color: var(--fg-color);
111 }
203 -.notifications-list {
204 - .item {
205 - position: relative;
206 - padding: 14px 0;
207 - .icon-box {
208 - width: 70px;
209 - min-width: 70px;
210 - display: flex;
211 - justify-content: center;
212 -
213 - .n-icon {
214 - display: flex;
215 - justify-content: center;
216 - align-items: center;
217 - background-color: rgba(var(--primary-color-rgb), 0.05);
218 - color: var(--primary-color);
219 - border-radius: 50%;
220 - width: 42px;
221 - height: 42px;
222 - margin-top: 2px;
223 - }
224 -
225 - &.message {
226 - .n-icon {
227 - background-color: rgba(var(--secondary1-color-rgb), 0.1);
228 - color: var(--secondary1-color);
229 - }
230 - }
231 - &.reminder {
232 - .n-icon {
233 - background-color: rgba(var(--secondary2-color-rgb), 0.1);
234 - color: var(--secondary2-color);
235 - }
236 - }
237 - &.news {
238 - .n-icon {
239 - background-color: rgba(var(--secondary3-color-rgb), 0.1);
240 - color: var(--secondary3-color);
241 - }
242 - }
243 - &.alert {
244 - .n-icon {
245 - background-color: rgba(var(--secondary4-color-rgb), 0.1);
246 - color: var(--secondary4-color);
247 - }
248 - }
249 - }
250 - .content {
251 - max-width: 250px;
252 - padding-right: 20px;
253 - font-size: 14px;
254 -
255 - .title {
256 - font-weight: bold;
257 - }
258 - .date {
259 - font-size: 12px;
260 - margin-top: 6px;
261 - opacity: 0.5;
262 - }
263 - }
264 -
265 - .read-badge {
266 - position: absolute;
267 - top: 0;
268 - left: 0;
269 - width: 0;
270 - height: 0;
271 - border-style: solid;
272 - border-width: 20px 20px 0 0;
273 - border-color: rgba(var(--primary-color-rgb), 0.5) transparent transparent transparent;
274 - }
275 -
276 - &.pointer {
277 - cursor: pointer;
278 - }
279 -
280 - &:not(:last-child) {
281 - border-bottom: var(--border-small-050);
282 - }
283 -
284 - &:hover {
285 - background-color: rgba(var(--fg-color-rgb), 0.02);
286 - }
287 - }
288 -}
112 </style>
src/layouts/common/Toolbar/PinnedPages.vue
+33 -21
@@ -1,6 +1,6 @@
1 <template>
2 <div class="flex pinned-pages items-end">
3 - <XyzTransitionGroup class="latest-list flex items-center gap-4" xyz="fade stagger-1 down-1">
3 + <TransitionGroup name="anim" tag="div" class="latest-list flex items-center gap-4">
4 <n-tag
5 round
6 :bordered="false"
@@ -14,16 +14,15 @@
14 </span>
15 <template #icon>
16 <div class="icon-box" @click="pinPage(page)">
17 - <n-icon :size="14">
18 - <PinnedIcon />
19 - </n-icon>
17 + <Icon :size="14" :name="PinnedIcon"></Icon>
18 </div>
19 </template>
20 </n-tag>
23 - </XyzTransitionGroup>
21 + </TransitionGroup>
22
23 <div class="divider" v-if="latestSanitized.length && pinned.length"></div>
26 - <XyzTransitionGroup class="pinned-list flex items-center gap-4" xyz="fade stagger-1 down-1">
24 +
25 + <TransitionGroup name="anim" tag="div" class="pinned-list flex items-center gap-4">
26 <n-tag
27 round
28 :bordered="false"
@@ -36,7 +35,7 @@
35 {{ page.title }}
36 </div>
37 </n-tag>
39 - </XyzTransitionGroup>
38 + </TransitionGroup>
39 <div class="bar"></div>
40 </div>
41 </template>
@@ -45,10 +44,13 @@
44 import { useRouter, type RouteRecordName } from "vue-router"
45 import { type RemovableRef, useStorage } from "@vueuse/core"
46 import { computed, type ComputedRef } from "vue"
48 -import _uniqBy from "lodash/uniqBy"
49 -import PinnedIcon from "@vicons/tabler/Pinned"
50 -import { NIcon, NTag } from "naive-ui"
47 +import { NTag } from "naive-ui"
48 import _takeRight from "lodash/takeRight"
49 +import _split from "lodash/split"
50 +import _uniqBy from "lodash/uniqBy"
51 +import Icon from "@/components/common/Icon.vue"
52 +
53 +const PinnedIcon = "tabler:pinned"
54
55 interface Page {
56 name: RouteRecordName | string
@@ -77,7 +79,7 @@ const gotoPage = (pageName: RouteRecordName | string) => {
79 const pinPage = (page: Page) => {
80 const isPresent = pinned.value.findIndex(p => p.name === page.name) !== -1
81 if (!isPresent) {
80 - pinned.value = _uniqBy([page, ...pinned.value], "name").reverse()
82 + pinned.value = [page, ...pinned.value]
83 }
84 return true
85 }
@@ -92,11 +94,13 @@ const latestSanitized: ComputedRef<Page[]> = computed(() => {
94 })
95
96 router.afterEach(route => {
95 - if (route.name && route.meta?.title) {
97 + const title = route.meta?.title || _split(route.name?.toString(), "-").at(-1)
98 +
99 + if (route.name && title) {
100 const page: Page = {
101 name: route.name,
102 fullPath: route.fullPath,
99 - title: route.meta.title as string
103 + title
104 }
105 latest.value = _uniqBy([page, ...latest.value, page], "name")
106 }
@@ -142,19 +146,11 @@ router.afterEach(route => {
146 }
147
148 .pinned-list {
145 - --xyz-out-duration: 0;
146 - --xyz-out-delay: 0;
147 -
149 .page-name {
150 color: var(--primary-color);
151 }
152 }
153
153 - .latest-list {
154 - --xyz-out-duration: 0;
155 - --xyz-out-delay: 0;
156 - }
157 -
154 .bar {
155 background-color: var(--bg-sidebar);
156 position: absolute;
@@ -197,5 +193,21 @@ router.afterEach(route => {
193 color: var(--primary-color);
194 }
195 }
196 +
197 + .anim-move,
198 + .anim-enter-active,
199 + .anim-leave-active {
200 + transition: all 0.5s var(--bezier-ease);
201 + }
202 +
203 + .anim-enter-from,
204 + .anim-leave-to {
205 + opacity: 0;
206 + transform: scale(0);
207 + }
208 +
209 + .anim-leave-active {
210 + position: absolute;
211 + }
212 }
213 </style>
src/layouts/common/Toolbar/Search.vue
+10 -487
@@ -1,421 +1,40 @@
1 <template>
2 - <div class="flex items-center search-btn" @click="openBox" ref="searchBtn">
3 - <n-icon size="16">
4 - <SearchOutline />
5 - </n-icon>
2 + <div class="flex items-center search-btn" @click="openBox">
3 + <Icon :name="SearchIcon" :size="16"></Icon>
4 <span>Search</span>
5 <n-text code class="search-command">
6 <span :class="{ win: commandIcon === 'CTRL' }">{{ commandIcon }}</span>
7 K
8 </n-text>
9 </div>
12 - <n-modal v-model:show="showSearchBox" class="search-box-modal">
13 - <n-card
14 - style="width: 600px"
15 - content-style="padding: 0;"
16 - :bordered="false"
17 - size="huge"
18 - role="dialog"
19 - aria-modal="true"
20 - >
21 - <div class="search-box">
22 - <div class="search-input flex items-center">
23 - <n-icon size="16">
24 - <SearchOutline />
25 - </n-icon>
26 - <input placeholder="Search" v-model="search" class="grow" />
27 - <n-text code>ESC</n-text>
28 - <n-icon size="20" @click="closeBox()" class="cursor-pointer">
29 - <CloseIcon />
30 - </n-icon>
31 - </div>
32 - <n-divider />
33 - <n-scrollbar style="height: 400px" ref="scrollContent">
34 - <div class="conten-wrap">
35 - <div class="group" v-for="group of filteredGroups" :key="group.name">
36 - <div class="group-title">{{ group.name }}</div>
37 - <div class="group-list">
38 - <div
39 - v-for="item of group.items"
40 - :key="item.key"
41 - :id="item.key.toString()"
42 - class="item flex items-center"
43 - :class="{ active: item.key === activeItem }"
44 - v-element-hover="
45 - () => {
46 - activeItem = item.key
47 - }
48 - "
49 - @click="callAction(item.action)"
50 - >
51 - <div class="icon">
52 - <n-avatar v-if="item.iconImage" round :size="28" :src="item.iconImage" />
53 - <n-icon
54 - v-if="item.iconComponent"
55 - size="18"
56 - :component="item.iconComponent"
57 - ></n-icon>
58 - </div>
59 - <div class="title grow">
60 - <Highlighter
61 - highlightClassName="highlight"
62 - :searchWords="keywords"
63 - :autoEscape="true"
64 - :textToHighlight="item.title"
65 - />
66 - </div>
67 - <div class="label">{{ item.label }}</div>
68 - </div>
69 - </div>
70 - </div>
71 - <div v-if="!filteredGroups.length" class="group-empty">
72 - We couldn't find anything matching "{{ search }}"
73 - </div>
74 - </div>
75 - </n-scrollbar>
76 - <n-divider />
77 - <div class="hint-bar flex items-center justify-center">
78 - <div class="hint flex items-center justify-center">
79 - <div class="icon">
80 - <n-icon size="12">
81 - <ArrowEnterLeft24Regular />
82 - </n-icon>
83 - </div>
84 - <span class="label">to select</span>
85 - </div>
86 - <div class="hint flex items-center justify-center">
87 - <div class="icon">
88 - <n-icon size="12">
89 - <ArrowSort24Regular />
90 - </n-icon>
91 - </div>
92 - <span class="label">to navigate</span>
93 - </div>
94 - </div>
95 - </div>
96 - </n-card>
97 - </n-modal>
10 </template>
11
12 <script lang="ts" setup>
101 -import { type DefineComponent, type Raw, computed, shallowRef, onMounted, ref, watch, type ShallowRef } from "vue"
102 -import { NIcon, NText, NModal, NCard, NDivider, NAvatar, NScrollbar, type ScrollbarInst } from "naive-ui"
103 -import SearchOutline from "@vicons/ionicons5/SearchOutline"
104 -import TaskListSquareAdd20Regular from "@vicons/fluent/TaskListSquareAdd20Regular"
105 -import MailEdit20Regular from "@vicons/fluent/MailEdit20Regular"
106 -import ChartPerson20Regular from "@vicons/fluent/ChartPerson20Regular"
107 -import ArrowEnterLeft24Regular from "@vicons/fluent/ArrowEnterLeft24Regular"
108 -import FullScreenMaximize24Regular from "@vicons/fluent/FullScreenMaximize24Regular"
109 -import MoonOutline from "@vicons/ionicons5/MoonOutline"
110 -import ArrowSort24Regular from "@vicons/fluent/ArrowSort24Regular"
111 -import CloseIcon from "@vicons/ionicons5/Close"
112 -import { useMagicKeys } from "@vueuse/core"
113 -import { faker } from "@faker-js/faker"
114 -import Highlighter from "vue-highlight-words"
115 -import { useRouter } from "vue-router"
116 -import { vElementHover } from "@vueuse/components"
117 -import { emitter } from "@/emitter"
13 +import { onMounted, ref } from "vue"
14 +import { NText } from "naive-ui"
15 import { getOS } from "@/utils"
16 +import Icon from "@/components/common/Icon.vue"
17 +import { useSearchDialog } from "@/composables/useSearchDialog"
18
120 -interface GroupItem {
121 - iconComponent: DefineComponent | Raw<DefineComponent> | ShallowRef<DefineComponent> | null
122 - iconImage: string | null
123 - key: number | string
124 - title: string
125 - label: string
126 - tags?: string
127 - action: () => void
128 -}
129 -
130 -interface Group {
131 - name: string
132 - items: GroupItem[]
133 -}
134 -type Groups = Group[]
19 +const SearchIcon = "ion:search-outline"
20
21 defineOptions({
22 name: "Search"
23 })
24
140 -const router = useRouter()
141 -
142 -const showSearchBox = ref(false)
143 -const search = ref("")
144 -const activeItem = ref<null | string | number>(null)
145 -const downupTimer = ref<NodeJS.Timeout | null>(null)
146 -const searchBtn = ref<null | HTMLElement>(null)
25 const commandIcon = ref("⌘")
148 -const scrollContent = ref<(ScrollbarInst & { $el: any }) | null>(null)
149 -
150 -const groups = ref<Groups>([
151 - {
152 - name: "Applications",
153 - items: [
154 - {
155 - iconComponent: shallowRef(TaskListSquareAdd20Regular as DefineComponent),
156 - iconImage: null,
157 - key: 1,
158 - title: "Add todo list",
159 - label: "Shortcut",
160 - action() {
161 - router.push({ path: "/kanban" })
162 - }
163 - },
164 - {
165 - iconComponent: shallowRef(MailEdit20Regular as DefineComponent),
166 - iconImage: null,
167 - key: 2,
168 - title: "Compose new email",
169 - label: "Shortcut",
170 - action() {
171 - router.push({ path: "/email" })
172 - }
173 - },
174 - {
175 - iconComponent: shallowRef(ChartPerson20Regular as DefineComponent),
176 - iconImage: null,
177 - key: 3,
178 - title: "View Notes",
179 - label: "Shortcut",
180 - action() {
181 - router.push({ path: "/notes" })
182 - }
183 - }
184 - ]
185 - },
186 - {
187 - name: "Contacts",
188 - items: [
189 - {
190 - iconComponent: null,
191 - iconImage: "https://i.pravatar.cc/56?_=" + Math.random(),
192 - key: 4,
193 - title: faker.person.fullName(),
194 - label: faker.internet.email().toLowerCase(),
195 - action() {
196 - router.push({ path: "/chat" })
197 - }
198 - },
199 - {
200 - iconComponent: null,
201 - iconImage: "https://i.pravatar.cc/56?_=" + Math.random(),
202 - key: 5,
203 - title: faker.person.fullName(),
204 - label: faker.internet.email().toLowerCase(),
205 - action() {
206 - router.push({ path: "/chat" })
207 - }
208 - },
209 - {
210 - iconComponent: null,
211 - iconImage: "https://i.pravatar.cc/56?_=" + Math.random(),
212 - key: 6,
213 - title: faker.person.fullName(),
214 - label: faker.internet.email().toLowerCase(),
215 - action() {
216 - router.push({ path: "/chat" })
217 - }
218 - }
219 - ]
220 - },
221 - {
222 - name: "Actions",
223 - items: [
224 - {
225 - iconComponent: shallowRef(FullScreenMaximize24Regular as DefineComponent),
226 - iconImage: null,
227 - key: 7,
228 - title: "Toggle fullscreen",
229 - label: "Action",
230 - action() {
231 - emitter.emit("toggle:fullscreen")
232 - }
233 - },
234 - {
235 - iconComponent: shallowRef(MoonOutline as DefineComponent),
236 - iconImage: null,
237 - key: 8,
238 - title: "Toggle dark mode",
239 - label: "Action",
240 - action() {
241 - emitter.emit("toggle:darkmode")
242 - }
243 - }
244 - ]
245 - }
246 -])
247 -
248 -const keywords = computed<string[]>(() => {
249 - if (search.value.length > 1) {
250 - return search.value.split(" ").filter(k => k)
251 - } else {
252 - return []
253 - }
254 -})
255 -const filteredGroups = computed<Groups>(() => {
256 - if (keywords.value.length === 0) {
257 - return groups.value
258 - }
259 - const newGroups: Groups = []
260 - for (const group of groups.value) {
261 - const items = group.items.filter(item => {
262 - if (keywords.value.filter(k => item.title.toLowerCase().indexOf(k.toLowerCase()) !== -1).length !== 0) {
263 - return true
264 - }
265 - if (
266 - item.tags &&
267 - keywords.value.filter(k => item.tags?.toLowerCase().indexOf(k.toLowerCase()) !== -1).length !== 0
268 - ) {
269 - return true
270 - }
271 - return false
272 - })
273 - if (items.length) {
274 - newGroups.push({
275 - name: group.name,
276 - items
277 - })
278 - }
279 - }
280 - return newGroups
281 -})
282 -
283 -/*eslint @typescript-eslint/no-unused-vars: "off"*/
284 -const flattenItems = computed<GroupItem[]>(() => {
285 - const items = []
26
287 - for (const group of groups.value) {
288 - items.push(...group.items)
289 - }
290 -
291 - return items
292 -})
293 -
294 -const filteredFlattenItems = computed<GroupItem[]>(() => {
295 - const items = []
296 -
297 - for (const group of filteredGroups.value) {
298 - items.push(...group.items)
299 - }
300 -
301 - return items
302 -})
303 -
304 -function openBox(e?: MouseEvent) {
305 - if (!showSearchBox.value) {
306 - showSearchBox.value = true
307 - setTimeout(() => {
308 - search.value = ""
309 - activeItem.value = null
310 - if (downupTimer.value) {
311 - clearInterval(downupTimer.value)
312 - }
313 - }, 100)
314 - }
315 - return e
316 -}
317 -function closeBox() {
318 - showSearchBox.value = false
319 - search.value = ""
320 - activeItem.value = null
321 - if (downupTimer.value) {
322 - clearInterval(downupTimer.value)
323 - }
324 -}
325 -function callAction(action: () => void) {
326 - action()
327 - closeBox()
328 -}
329 -function nextItem() {
330 - const currentIndex = filteredFlattenItems.value.findIndex(item => item.key === activeItem.value)
331 - if (currentIndex === filteredFlattenItems.value.length - 1 || activeItem.value === null) {
332 - activeItem.value = filteredFlattenItems.value[0].key
333 - } else {
334 - activeItem.value = filteredFlattenItems.value[currentIndex + 1].key
335 - }
336 - centerItem()
337 -}
338 -function prevItem() {
339 - const currentIndex = filteredFlattenItems.value.findIndex(item => item.key === activeItem.value)
340 - if (currentIndex === 0 || activeItem.value === null) {
341 - activeItem.value = filteredFlattenItems.value[filteredFlattenItems.value.length - 1].key
342 - } else {
343 - activeItem.value = filteredFlattenItems.value[currentIndex - 1].key
344 - }
345 - centerItem()
346 -}
347 -function performAction() {
348 - const item = filteredFlattenItems.value.find(item => item.key === activeItem.value)
349 - if (item) {
350 - callAction(item.action)
351 - }
352 -}
353 -function centerItem() {
354 - const element = document.getElementById(activeItem.value?.toString() || "")
355 - if (element && scrollContent.value) {
356 - const wrap: HTMLElement = scrollContent.value.$el.nextSibling || scrollContent.value.$el.nextElementSibling
357 - const middle = element.offsetTop - wrap.offsetHeight / 2
358 - scrollContent.value?.scrollTo({ top: middle })
359 - }
27 +function openBox() {
28 + useSearchDialog().open()
29 }
30
31 onMounted(() => {
32 const isWindows = getOS() === "Windows"
33 commandIcon.value = isWindows ? "CTRL" : "⌘"
365 -
366 - const keys = useMagicKeys()
367 - const ActiveCMD = isWindows ? keys["ctrl+k"] : keys["cmd+k"]
368 - const Up = keys["arrowup"]
369 - const Down = keys["arrowdown"]
370 - const Enter = keys["enter"]
371 - // const Esc = keys["escape"]
372 -
373 - watch(ActiveCMD, v => {
374 - if (v) searchBtn.value?.click()
375 - })
376 -
377 - watch(Down, v => {
378 - if (showSearchBox.value) {
379 - if (v) {
380 - nextItem()
381 - downupTimer.value = setInterval(() => {
382 - nextItem()
383 - }, 100)
384 - } else {
385 - if (downupTimer.value) {
386 - clearInterval(downupTimer.value)
387 - }
388 - }
389 - }
390 - })
391 -
392 - watch(Up, v => {
393 - if (showSearchBox.value) {
394 - if (v) {
395 - prevItem()
396 - downupTimer.value = setInterval(() => {
397 - prevItem()
398 - }, 100)
399 - } else {
400 - if (downupTimer.value) {
401 - clearInterval(downupTimer.value)
402 - }
403 - }
404 - }
405 - })
406 -
407 - watch(Enter, v => {
408 - if (showSearchBox.value) {
409 - if (v) {
410 - performAction()
411 - }
412 - }
413 - })
34 })
35 </script>
36
37 <style lang="scss" scoped>
418 -@import "@/assets/scss/common.scss";
38 .search-btn {
39 border-radius: 50px;
40 background-color: var(--bg-body);
@@ -456,7 +75,7 @@ onMounted(() => {
75
76 :deep() {
77 & > code {
459 - background-color: var(--bg-sidebar);
78 + background-color: var(--hover-005-color);
79 border-top-right-radius: 10px;
80 border-bottom-right-radius: 10px;
81 padding-right: 10px;
@@ -485,100 +104,4 @@ onMounted(() => {
104 }
105 }
106 }
488 -
489 -.search-box-modal {
490 - .search-box {
491 - border-radius: 4px;
492 -
493 - .search-input {
494 - height: 50px;
495 - gap: 20px;
496 - padding: 20px;
497 -
498 - input {
499 - background: transparent;
500 - outline: none;
501 - border: none;
502 - min-width: 100px;
503 - }
504 -
505 - .n-text--code {
506 - white-space: nowrap;
507 - }
508 - }
509 -
510 - .n-divider {
511 - margin-top: 0;
512 - margin-bottom: 0;
513 - }
514 -
515 - .conten-wrap {
516 - padding-bottom: 30px;
517 -
518 - .group-empty {
519 - text-align: center;
520 - padding: 30px 0 40px 0;
521 - }
522 - .group {
523 - padding: 0 10px;
524 - .group-title {
525 - opacity: 0.6;
526 - margin-bottom: 5px;
527 - padding: 5px 10px;
528 - padding-top: 20px;
529 - }
530 - .group-list {
531 - .item {
532 - padding: 7px 10px;
533 - gap: 10px;
534 - cursor: pointer;
535 - border-radius: 10px;
536 -
537 - .icon {
538 - width: 28px;
539 - height: 28px;
540 - border-radius: 50%;
541 - background-color: rgba(var(--primary-color-rgb), 0.05);
542 - display: flex;
543 - justify-content: center;
544 - align-items: center;
545 - }
546 - .title {
547 - font-weight: bold;
548 - }
549 - .label {
550 - opacity: 0.8;
551 - font-size: 0.9em;
552 - }
553 -
554 - &.active {
555 - background-color: rgba(var(--fg-color-rgb), 0.03);
556 - }
557 - }
558 - }
559 - }
560 - }
561 -
562 - .hint-bar {
563 - font-size: 12px;
564 - gap: 20px;
565 - padding: 10px 0;
566 - .icon {
567 - background-color: var(--code-color);
568 - width: 18px;
569 - height: 18px;
570 - padding-top: 1px;
571 - text-align: center;
572 - border-radius: 4px;
573 - margin-right: 5px;
574 - display: flex;
575 - align-items: center;
576 - justify-content: center;
577 - }
578 - .label {
579 - opacity: 0.7;
580 - }
581 - }
582 - }
583 -}
107 </style>
src/layouts/common/Toolbar/ThemeSwitch.vue
+53 -23
@@ -1,43 +1,73 @@
1 <template>
2 <button class="theme-switch" @click="toggleTheme" alt="theme-switch" aria-label="theme-switch">
3 <Transition name="rotate">
4 - <n-icon size="20" v-if="isThemeDark">
5 - <Sunny class="hover" />
6 - <SunnyOutline />
7 - </n-icon>
8 - <n-icon size="20" v-else>
9 - <Moon class="hover" />
10 - <MoonOutline />
11 - </n-icon>
4 + <Icon v-if="isThemeDark" :size="20">
5 + <Iconify :icon="Sunny" class="hover"></Iconify>
6 + <Iconify :icon="SunnyOutline"></Iconify>
7 + </Icon>
8 + <Icon v-else :size="20">
9 + <Iconify :icon="Moon" class="hover"></Iconify>
10 + <Iconify :icon="MoonOutline"></Iconify>
11 + </Icon>
12 </Transition>
13 </button>
14 </template>
15
16 <script lang="ts" setup>
17 import { useThemeStore } from "@/stores/theme"
18 -import { computed, onMounted } from "vue"
19 -import { NIcon } from "naive-ui"
20 -import Sunny from "@vicons/ionicons5/Sunny"
21 -import Moon from "@vicons/ionicons5/Moon"
22 -import SunnyOutline from "@vicons/ionicons5/SunnyOutline"
23 -import MoonOutline from "@vicons/ionicons5/MoonOutline"
24 -import { emitter } from "@/emitter"
18 +import { computed, nextTick } from "vue"
19 +import Icon from "@/components/common/Icon.vue"
20 +import { Icon as Iconify } from "@iconify/vue"
21 +
22 +const Sunny = "ion:sunny"
23 +const Moon = "ion:moon"
24 +const SunnyOutline = "ion:sunny-outline"
25 +const MoonOutline = "ion:moon-outline"
26
27 defineOptions({
28 name: "ThemeSwitch"
29 })
30
31 const isThemeDark = computed<boolean>(() => useThemeStore().isThemeDark)
31 -function toggleTheme(e?: MouseEvent) {
32 - useThemeStore().toggleTheme()
33 - return e
34 -}
32
36 -onMounted(() => {
37 - emitter.on("toggle:darkmode", () => {
38 - toggleTheme()
33 +function toggleTheme(event?: MouseEvent) {
34 + const isAppearanceTransition =
35 + typeof document !== "undefined" &&
36 + // @ts-expect-error: Transition API
37 + document.startViewTransition &&
38 + !window.matchMedia("(prefers-reduced-motion: reduce)").matches
39 +
40 + if (!isAppearanceTransition || !event) {
41 + useThemeStore().toggleTheme()
42 + return
43 + }
44 +
45 + const x = event.clientX ?? innerWidth / 2
46 + const y = event.clientY ?? innerHeight / 2
47 + const endRadius = Math.hypot(Math.max(x, innerWidth - x), Math.max(y, innerHeight - y))
48 +
49 + // @ts-expect-error: Transition API
50 + const transition = document.startViewTransition(async () => {
51 + useThemeStore().toggleTheme()
52 + await nextTick()
53 })
40 -})
54 +
55 + transition.ready.then(() => {
56 + const clipPath = [`circle(0px at ${x}px ${y}px)`, `circle(${endRadius}px at ${x}px ${y}px)`]
57 + //const clipPath = [`inset(50%)`, `inset(0)`]
58 +
59 + document.documentElement.animate(
60 + {
61 + clipPath
62 + },
63 + {
64 + duration: 300,
65 + easing: "ease-in",
66 + pseudoElement: "::view-transition-new(root)"
67 + }
68 + )
69 + })
70 +}
71 </script>
72
73 <style scoped lang="scss">
src/layouts/common/Toolbar/index.ts deleted
-2
@@ -1,2 +0,0 @@
1 -import Toolbar from "./Toolbar.vue"
2 -export default Toolbar
src/layouts/common/Toolbar/index.vue renamed
+2 -2
@@ -9,7 +9,7 @@
9 <div class="bubble flex items-center">
10 <Search />
11 <LocaleSwitch />
12 - <FullscreenBtn />
12 + <FullscreenSwitch />
13 <ThemeSwitch />
14 <Notifications />
15 <Avatar />
@@ -28,7 +28,7 @@ import PinnedPages from "./PinnedPages.vue"
28 import ThemeSwitch from "./ThemeSwitch.vue"
29 import Notifications from "./Notifications.vue"
30 import LocaleSwitch from "./LocaleSwitch.vue"
31 -import FullscreenBtn from "./FullscreenBtn.vue"
31 +import FullscreenSwitch from "./FullscreenSwitch.vue"
32 import { useLoadingBar } from "naive-ui"
33 import { useRouter } from "vue-router"
34 import { useMainStore } from "@/stores/main"
src/main.ts
+1 -3
@@ -6,13 +6,12 @@ import { createApp } from "vue"
6 import { createPinia } from "pinia"
7 import { createI18n } from "vue-i18n"
8 import piniaPluginPersistedstate from "pinia-plugin-persistedstate"
9 -import VueAnimXyz from "@animxyz/vue3"
10 -import "@animxyz/core"
9 import VueApexCharts from "vue3-apexcharts"
10 import VueGoogleMaps from "@fawmi/vue-google-maps"
11 // @ts-ignore
12 import VueVectorMap from "vuevectormap"
13 import "vuevectormap/src/scss/vuevectormap.scss"
14 +import "jsvectormap/dist/maps/world-merc"
15
16 import App from "@/App.vue"
17 import router from "@/router"
@@ -27,7 +26,6 @@ const app = createApp(App)
26 app.use(pinia)
27 app.use(i18n)
28 app.use(router)
30 -app.use(VueAnimXyz)
29 app.use(VueApexCharts)
30 app.use(VueGoogleMaps, {
31 load: {}
src/mock/fullcalendar.ts
+5
@@ -14,6 +14,11 @@ export interface CalendarEvent {
14 }
15 }
16
17 +export interface CalendarEditEvent extends Omit<CalendarEvent, "start" | "end"> {
18 + start: number
19 + end: number
20 +}
21 +
22 export const availableCalendars = [
23 {
24 label: "Personal"
src/router/components.ts
+90 -90
@@ -10,457 +10,457 @@ export default {
10 children: [
11 {
12 path: "avatar",
13 - name: "components-avatar",
13 + name: "Components-Avatar",
14 component: () => import("@/views/Components/Avatar.vue"),
15 meta: { title: "Components Avatar" }
16 },
17 {
18 path: "button",
19 - name: "components-button",
19 + name: "Components-Button",
20 component: () => import("@/views/Components/Button.vue"),
21 meta: { title: "Components Button" }
22 },
23 {
24 path: "card",
25 - name: "components-card",
25 + name: "Components-Card",
26 component: () => import("@/views/Components/Card.vue"),
27 meta: { title: "Components Card" }
28 },
29 {
30 path: "carousel",
31 - name: "components-carousel",
31 + name: "Components-Carousel",
32 component: () => import("@/views/Components/Carousel.vue"),
33 meta: { title: "Components Carousel" }
34 },
35 {
36 path: "collapse",
37 - name: "components-collapse",
37 + name: "Components-Collapse",
38 component: () => import("@/views/Components/Collapse.vue"),
39 meta: { title: "Components Collapse" }
40 },
41 {
42 path: "divider",
43 - name: "components-divider",
43 + name: "Components-Divider",
44 component: () => import("@/views/Components/Divider.vue"),
45 meta: { title: "Components Divider" }
46 },
47 {
48 path: "dropdown",
49 - name: "components-dropdown",
49 + name: "Components-Dropdown",
50 component: () => import("@/views/Components/Dropdown.vue"),
51 meta: { title: "Components Dropdown" }
52 },
53 {
54 path: "ellipsis",
55 - name: "components-ellipsis",
55 + name: "Components-Ellipsis",
56 component: () => import("@/views/Components/Ellipsis.vue"),
57 meta: { title: "Components Ellipsis" }
58 },
59 {
60 - path: "gradient-text",
61 - name: "components-gradient-text",
60 + path: "gradienttext",
61 + name: "Components-GradientText",
62 component: () => import("@/views/Components/GradientText.vue"),
63 meta: { title: "Components Gradient Text" }
64 },
65 {
66 path: "icon",
67 - name: "components-icon",
67 + name: "Components-Icon",
68 component: () => import("@/views/Components/Icon.vue"),
69 meta: { title: "Components Icon" }
70 },
71 {
72 - path: "page-header",
73 - name: "components-page-header",
72 + path: "pageheader",
73 + name: "Components-PageHeader",
74 component: () => import("@/views/Components/PageHeader.vue"),
75 meta: { title: "Components Page Header" }
76 },
77 {
78 path: "tag",
79 - name: "components-tag",
79 + name: "Components-Tag",
80 component: () => import("@/views/Components/Tag.vue"),
81 meta: { title: "Components Tag" }
82 },
83 {
84 path: "typography",
85 - name: "components-typography",
85 + name: "Components-Typography",
86 component: () => import("@/views/Components/Typography.vue"),
87 meta: { title: "Components Typography" }
88 },
89 {
90 path: "watermark",
91 - name: "components-watermark",
91 + name: "Components-Watermark",
92 component: () => import("@/views/Components/Watermark.vue"),
93 meta: { title: "Components Watermark" }
94 },
95 {
96 - path: "auto-complete",
97 - name: "components-auto-complete",
96 + path: "autocomplete",
97 + name: "Components-AutoComplete",
98 component: () => import("@/views/Components/AutoComplete.vue"),
99 meta: { title: "Components Auto Complete" }
100 },
101 {
102 path: "cascader",
103 - name: "components-cascader",
103 + name: "Components-Cascader",
104 component: () => import("@/views/Components/Cascader.vue"),
105 meta: { title: "Components Cascader" }
106 },
107 {
108 - path: "color-picker",
109 - name: "components-color-picker",
108 + path: "colorpicker",
109 + name: "Components-ColorPicker",
110 component: () => import("@/views/Components/ColorPicker.vue"),
111 meta: { title: "Components Color Picker" }
112 },
113 {
114 path: "checkbox",
115 - name: "components-checkbox",
115 + name: "Components-Checkbox",
116 component: () => import("@/views/Components/Checkbox.vue"),
117 meta: { title: "Components Checkbox" }
118 },
119 {
120 - path: "date-picker",
121 - name: "components-date-picker",
120 + path: "datepicker",
121 + name: "Components-DatePicker",
122 component: () => import("@/views/Components/DatePicker.vue"),
123 meta: { title: "Components Date Picker" }
124 },
125 {
126 - path: "dynamic-input",
127 - name: "components-dynamic-input",
126 + path: "dynamicinput",
127 + name: "Components-DynamicInput",
128 component: () => import("@/views/Components/DynamicInput.vue"),
129 meta: { title: "Components Dynamic Input" }
130 },
131 {
132 - path: "dynamic-tags",
133 - name: "components-dynamic-tags",
132 + path: "dynamictags",
133 + name: "Components-DynamicTags",
134 component: () => import("@/views/Components/DynamicTags.vue"),
135 meta: { title: "Components Dynamic Tags" }
136 },
137 {
138 path: "form",
139 - name: "components-form",
139 + name: "Components-Form",
140 component: () => import("@/views/Components/Form.vue"),
141 meta: { title: "Components Form" }
142 },
143 {
144 path: "input",
145 - name: "components-input",
145 + name: "Components-Input",
146 component: () => import("@/views/Components/Input.vue"),
147 meta: { title: "Components Input" }
148 },
149 {
150 - path: "input-number",
151 - name: "components-input-number",
150 + path: "inputnumber",
151 + name: "Components-InputNumber",
152 component: () => import("@/views/Components/InputNumber.vue"),
153 meta: { title: "Components Input Number" }
154 },
155 {
156 path: "mention",
157 - name: "components-mention",
157 + name: "Components-Mention",
158 component: () => import("@/views/Components/Mention.vue"),
159 meta: { title: "Components Mention" }
160 },
161 {
162 path: "radio",
163 - name: "components-radio",
163 + name: "Components-Radio",
164 component: () => import("@/views/Components/Radio.vue"),
165 meta: { title: "Components Radio" }
166 },
167 {
168 path: "rate",
169 - name: "components-rate",
169 + name: "Components-Rate",
170 component: () => import("@/views/Components/Rate.vue"),
171 meta: { title: "Components Rate" }
172 },
173 {
174 path: "select",
175 - name: "components-select",
175 + name: "Components-Select",
176 component: () => import("@/views/Components/Select.vue"),
177 meta: { title: "Components Select" }
178 },
179 {
180 path: "slider",
181 - name: "components-slider",
181 + name: "Components-Slider",
182 component: () => import("@/views/Components/Slider.vue"),
183 meta: { title: "Components Slider" }
184 },
185 {
186 path: "switch",
187 - name: "components-switch",
187 + name: "Components-Switch",
188 component: () => import("@/views/Components/Switch.vue"),
189 meta: { title: "Components Switch" }
190 },
191 {
192 - path: "time-picker",
193 - name: "components-time-picker",
192 + path: "timepicker",
193 + name: "Components-TimePicker",
194 component: () => import("@/views/Components/TimePicker.vue"),
195 meta: { title: "Components Time Picker" }
196 },
197 {
198 path: "transfer",
199 - name: "components-transfer",
199 + name: "Components-Transfer",
200 component: () => import("@/views/Components/Transfer.vue"),
201 meta: { title: "Components Transfer" }
202 },
203 {
204 - path: "tree-select",
205 - name: "components-tree-select",
204 + path: "treeselect",
205 + name: "Components-TreeSelect",
206 component: () => import("@/views/Components/TreeSelect.vue"),
207 meta: { title: "Components Tree Select" }
208 },
209 {
210 path: "upload",
211 - name: "components-upload",
211 + name: "Components-Upload",
212 component: () => import("@/views/Components/Upload.vue"),
213 meta: { title: "Components Upload" }
214 },
215 {
216 path: "calendar",
217 - name: "components-calendar",
217 + name: "Components-Calendar",
218 component: () => import("@/views/Components/Calendar.vue"),
219 meta: { title: "Components Calendar" }
220 },
221 {
222 path: "countdown",
223 - name: "components-countdown",
223 + name: "Components-Countdown",
224 component: () => import("@/views/Components/Countdown.vue"),
225 meta: { title: "Components Countdown" }
226 },
227 {
228 - path: "data-table",
229 - name: "components-data-table",
228 + path: "datatable",
229 + name: "Components-DataTable",
230 component: () => import("@/views/Components/DataTable.vue"),
231 meta: { title: "Components Data Table" }
232 },
233 {
234 path: "descriptions",
235 - name: "components-descriptions",
235 + name: "Components-Descriptions",
236 component: () => import("@/views/Components/Descriptions.vue"),
237 meta: { title: "Components Descriptions" }
238 },
239 {
240 path: "empty",
241 - name: "components-empty",
241 + name: "Components-Empty",
242 component: () => import("@/views/Components/Empty.vue"),
243 meta: { title: "Components Empty" }
244 },
245 {
246 path: "image",
247 - name: "components-image",
247 + name: "Components-Image",
248 component: () => import("@/views/Components/Image.vue"),
249 meta: { title: "Components Image" }
250 },
251 {
252 path: "list",
253 - name: "components-list",
253 + name: "Components-List",
254 component: () => import("@/views/Components/List.vue"),
255 meta: { title: "Components List" }
256 },
257 {
258 - path: "number-animation",
259 - name: "components-number-animation",
258 + path: "numberanimation",
259 + name: "Components-NumberAnimation",
260 component: () => import("@/views/Components/NumberAnimation.vue"),
261 meta: { title: "Components Number Animation" }
262 },
263 {
264 path: "scrollbar",
265 - name: "components-scrollbar",
265 + name: "Components-Scrollbar",
266 component: () => import("@/views/Components/Scrollbar.vue"),
267 meta: { title: "Components Scrollbar" }
268 },
269 {
270 path: "statistic",
271 - name: "components-statistic",
271 + name: "Components-Statistic",
272 component: () => import("@/views/Components/Statistic.vue"),
273 meta: { title: "Components Statistic" }
274 },
275 {
276 path: "table",
277 - name: "components-table",
277 + name: "Components-Table",
278 component: () => import("@/views/Components/Table.vue"),
279 meta: { title: "Components Table" }
280 },
281 {
282 path: "thing",
283 - name: "components-thing",
283 + name: "Components-Thing",
284 component: () => import("@/views/Components/Thing.vue"),
285 meta: { title: "Components Thing" }
286 },
287 {
288 path: "time",
289 - name: "components-time",
289 + name: "Components-Time",
290 component: () => import("@/views/Components/Time.vue"),
291 meta: { title: "Components Time" }
292 },
293 {
294 path: "timeline",
295 - name: "components-timeline",
295 + name: "Components-Timeline",
296 component: () => import("@/views/Components/Timeline.vue"),
297 meta: { title: "Components Timeline" }
298 },
299 {
300 path: "tree",
301 - name: "components-tree",
301 + name: "Components-Tree",
302 component: () => import("@/views/Components/Tree.vue"),
303 meta: { title: "Components Tree" }
304 },
305 {
306 path: "affix",
307 - name: "components-affix",
307 + name: "Components-Affix",
308 component: () => import("@/views/Components/Affix.vue"),
309 meta: { title: "Components Affix" }
310 },
311 {
312 path: "anchor",
313 - name: "components-anchor",
313 + name: "Components-Anchor",
314 component: () => import("@/views/Components/Anchor.vue"),
315 meta: { title: "Components Anchor" }
316 },
317 {
318 - path: "back-top",
319 - name: "components-back-top",
318 + path: "backtop",
319 + name: "Components-BackTop",
320 component: () => import("@/views/Components/BackTop.vue"),
321 meta: { title: "Components Back Top" }
322 },
323 {
324 path: "breadcrumb",
325 - name: "components-breadcrumb",
325 + name: "Components-Breadcrumb",
326 component: () => import("@/views/Components/Breadcrumb.vue"),
327 meta: { title: "Components Breadcrumb" }
328 },
329 {
330 path: "menu",
331 - name: "components-menu",
331 + name: "Components-Menu",
332 component: () => import("@/views/Components/Menu.vue"),
333 meta: { title: "Components Menu" }
334 },
335 {
336 path: "pagination",
337 - name: "components-pagination",
337 + name: "Components-Pagination",
338 component: () => import("@/views/Components/Pagination.vue"),
339 meta: { title: "Components Pagination" }
340 },
341 {
342 path: "steps",
343 - name: "components-steps",
343 + name: "Components-Steps",
344 component: () => import("@/views/Components/Steps.vue"),
345 meta: { title: "Components Steps" }
346 },
347 {
348 path: "tabs",
349 - name: "components-tabs",
349 + name: "Components-Tabs",
350 component: () => import("@/views/Components/Tabs.vue"),
351 meta: { title: "Components Tabs" }
352 },
353 {
354 path: "alert",
355 - name: "components-alert",
355 + name: "Components-Alert",
356 component: () => import("@/views/Components/Alert.vue"),
357 meta: { title: "Components Alert" }
358 },
359 {
360 path: "badge",
361 - name: "components-badge",
361 + name: "Components-Badge",
362 component: () => import("@/views/Components/Badge.vue"),
363 meta: { title: "Components Badge" }
364 },
365 {
366 path: "dialog",
367 - name: "components-dialog",
367 + name: "Components-Dialog",
368 component: () => import("@/views/Components/Dialog.vue"),
369 meta: { title: "Components Dialog" }
370 },
371 {
372 path: "drawer",
373 - name: "components-drawer",
373 + name: "Components-Drawer",
374 component: () => import("@/views/Components/Drawer.vue"),
375 meta: { title: "Components Drawer" }
376 },
377 {
378 path: "message",
379 - name: "components-message",
379 + name: "Components-Message",
380 component: () => import("@/views/Components/Message.vue"),
381 meta: { title: "Components Message" }
382 },
383 {
384 path: "modal",
385 - name: "components-modal",
385 + name: "Components-Modal",
386 component: () => import("@/views/Components/Modal.vue"),
387 meta: { title: "Components Modal" }
388 },
389 {
390 path: "notification",
391 - name: "components-notification",
391 + name: "Components-Notification",
392 component: () => import("@/views/Components/Notification.vue"),
393 meta: { title: "Components Notification" }
394 },
395 {
396 path: "popconfirm",
397 - name: "components-popconfirm",
397 + name: "Components-Popconfirm",
398 component: () => import("@/views/Components/Popconfirm.vue"),
399 meta: { title: "Components Popconfirm" }
400 },
401 {
402 path: "popover",
403 - name: "components-popover",
403 + name: "Components-Popover",
404 component: () => import("@/views/Components/Popover.vue"),
405 meta: { title: "Components Popover" }
406 },
407 {
408 path: "popselect",
409 - name: "components-popselect",
409 + name: "Components-Popselect",
410 component: () => import("@/views/Components/Popselect.vue"),
411 meta: { title: "Components Popselect" }
412 },
413 {
414 path: "progress",
415 - name: "components-progress",
415 + name: "Components-Progress",
416 component: () => import("@/views/Components/Progress.vue"),
417 meta: { title: "Components Progress" }
418 },
419 {
420 path: "result",
421 - name: "components-result",
421 + name: "Components-Result",
422 component: () => import("@/views/Components/Result.vue"),
423 meta: { title: "Components Result" }
424 },
425 {
426 path: "skeleton",
427 - name: "components-skeleton",
427 + name: "Components-Skeleton",
428 component: () => import("@/views/Components/Skeleton.vue"),
429 meta: { title: "Components Skeleton" }
430 },
431 {
432 path: "spin",
433 - name: "components-spin",
433 + name: "Components-Spin",
434 component: () => import("@/views/Components/Spin.vue"),
435 meta: { title: "Components Spin" }
436 },
437 {
438 path: "tooltip",
439 - name: "components-tooltip",
439 + name: "Components-Tooltip",
440 component: () => import("@/views/Components/Tooltip.vue"),
441 meta: { title: "Components Tooltip" }
442 },
443 {
444 path: "layout",
445 - name: "components-layout",
445 + name: "Components-Layout",
446 component: () => import("@/views/Components/Layout.vue"),
447 meta: { title: "Components Layout" }
448 },
449 {
450 - path: "legacy-grid",
451 - name: "components-legacy-grid",
450 + path: "legacygrid",
451 + name: "Components-LegacyGrid",
452 component: () => import("@/views/Components/LegacyGrid.vue"),
453 meta: { title: "Components Legacy Grid" }
454 },
455 {
456 path: "grid",
457 - name: "components-grid",
457 + name: "Components-Grid",
458 component: () => import("@/views/Components/Grid.vue"),
459 meta: { title: "Components Grid" }
460 },
461 {
462 path: "space",
463 - name: "components-space",
463 + name: "Components-Space",
464 component: () => import("@/views/Components/Space.vue"),
465 meta: { title: "Components Space" }
466 }
src/router/index.ts
+72 -60
@@ -1,41 +1,69 @@
1 -import { UserRole } from "@/types/auth.d"
1 import { createRouter, createWebHistory } from "vue-router"
2 import Analytics from "@/views/Dashboard/Analytics.vue"
4 -import { authCheck } from "@/utils/auth"
3 +import { UserRole } from "@/types/auth.d"
4 import components from "./components"
5 import { Layout } from "@/types/theme.d"
6 +import { authCheck } from "@/utils/auth"
7
8 const router = createRouter({
9 history: createWebHistory(import.meta.env.BASE_URL),
10 routes: [
11 {
12 path: "/",
13 - redirect: "/dashboard/analytics"
13 + redirect: "/indices"
14 },
15 {
16 path: "/indices",
17 - name: "indices",
17 + name: "Indices",
18 component: () => import("@/views/socfortress/Indices.vue"),
19 meta: { title: "Indices", auth: true, roles: UserRole.All }
20 },
21 {
22 path: "/connectors",
23 - name: "connectors",
23 + name: "Connectors",
24 component: () => import("@/views/socfortress/Connectors.vue"),
25 meta: { title: "Connectors", auth: true, roles: UserRole.All }
26 },
27 {
28 path: "/agents",
29 - name: "agents",
29 + name: "Agents",
30 component: () => import("@/views/socfortress/Agents.vue"),
31 meta: { title: "Agents", auth: true, roles: UserRole.All }
32 },
33 {
34 path: "/agent/:id?",
35 - name: "agent",
35 + name: "Agent",
36 component: () => import("@/views/socfortress/AgentOverview.vue"),
37 meta: { title: "Agent", auth: true, roles: UserRole.All }
38 },
39 + {
40 + path: "/graylog",
41 + redirect: "/graylog/management",
42 + meta: {
43 + auth: true,
44 + roles: UserRole.All
45 + },
46 + children: [
47 + {
48 + path: "management",
49 + name: "Graylog-Management",
50 + component: () => import("@/views/socfortress/graylog/Management.vue"),
51 + meta: { title: "Management" }
52 + },
53 + {
54 + path: "metrics",
55 + name: "Graylog-Metrics",
56 + component: () => import("@/views/socfortress/graylog/Metrics.vue"),
57 + meta: { title: "Metrics" }
58 + },
59 + {
60 + path: "pipelines",
61 + name: "Graylog-Pipelines",
62 + component: () => import("@/views/socfortress/graylog/Pipelines.vue"),
63 + meta: { title: "Pipelines" }
64 + }
65 + ]
66 + },
67
68 {
69 path: "/dashboard",
@@ -47,13 +75,13 @@ const router = createRouter({
75 children: [
76 {
77 path: "analytics",
50 - name: "analytics",
78 + name: "Dashboard-Analytics",
79 component: Analytics,
80 meta: { title: "Analytics" }
81 },
82 {
83 path: "ecommerce",
56 - name: "ecommerce",
84 + name: "Dashboard-eCommerce",
85 component: () => import("@/views/Dashboard/eCommerce.vue"),
86 meta: { title: "eCommerce" }
87 }
@@ -61,37 +89,37 @@ const router = createRouter({
89 },
90 {
91 path: "/calendar",
64 - name: "calendar",
92 + name: "Apps-Calendars-FullCalendar",
93 component: () => import("@/views/Apps/Calendars/FullCalendar.vue"),
94 meta: { title: "Calendar", auth: true, roles: UserRole.All }
95 },
96 {
97 path: "/email",
70 - name: "email",
98 + name: "Apps-Mailbox",
99 component: () => import("@/views/Apps/Mailbox.vue"),
100 meta: { title: "Email", auth: true, roles: UserRole.All }
101 },
102 {
103 path: "/chat",
76 - name: "chat",
104 + name: "Apps-Chat",
105 component: () => import("@/views/Apps/Chat.vue"),
106 meta: { title: "Chat", auth: true, roles: UserRole.All }
107 },
108 {
109 path: "/kanban",
82 - name: "kanban",
110 + name: "Apps-Kanban",
111 component: () => import("@/views/Apps/Kanban.vue"),
112 meta: { title: "Kanban", auth: true, roles: UserRole.All }
113 },
114 {
115 path: "/notes",
88 - name: "notes",
116 + name: "Apps-Notes",
117 component: () => import("@/views/Apps/Notes.vue"),
118 meta: { title: "Notes", auth: true, roles: UserRole.All }
119 },
120 {
121 path: "/typography",
94 - name: "typography",
122 + name: "Typography",
123 component: () => import("@/views/Typography.vue"),
124 meta: { title: "Typography", auth: true, roles: UserRole.All }
125 },
@@ -105,31 +133,31 @@ const router = createRouter({
133 children: [
134 {
135 path: "basic",
108 - name: "cards-basic",
136 + name: "Cards-Basic",
137 component: () => import("@/views/Cards/Basic.vue"),
138 meta: { title: "Cards Basic" }
139 },
140 {
141 path: "ecommerce",
114 - name: "cards-ecommerce",
142 + name: "Cards-Ecommerce",
143 component: () => import("@/views/Cards/Ecommerce.vue"),
144 meta: { title: "Cards Ecommerce" }
145 },
146 {
147 path: "list",
120 - name: "cards-list",
148 + name: "Cards-List",
149 component: () => import("@/views/Cards/List.vue"),
150 meta: { title: "Cards List" }
151 },
152 {
153 path: "extra",
126 - name: "cards-extra",
154 + name: "Cards-Extra",
155 component: () => import("@/views/Cards/Extra.vue"),
156 meta: { title: "Cards Extra" }
157 },
158 {
159 path: "combo",
132 - name: "cards-combo",
160 + name: "Cards-Combo",
161 component: () => import("@/views/Cards/Combo.vue"),
162 meta: { title: "Cards Combo" }
163 }
@@ -146,7 +174,7 @@ const router = createRouter({
174 children: [
175 {
176 path: "refresh-tool",
149 - name: "toolbox-refresh-tool",
177 + name: "Toolbox-RefreshTool",
178 // route level code-splitting
179 // this generates a separate chunk (About.[hash].js) for this route
180 // which is lazy-loaded when the route is visited.
@@ -155,7 +183,7 @@ const router = createRouter({
183 },
184 {
185 path: "tour",
158 - name: "toolbox-tour",
186 + name: "Toolbox-Tour",
187 component: () => import("@/views/Toolbox/Tour.vue"),
188 meta: { title: "Tour" }
189 }
@@ -171,19 +199,19 @@ const router = createRouter({
199 children: [
200 {
201 path: "left-sidebar",
174 - name: "layout-left-sidebar",
202 + name: "Layout-LeftSidebar",
203 component: () => import("@/views/Layout/LeftSidebar.vue"),
204 meta: { title: "Left Sidebar" }
205 },
206 {
207 path: "right-sidebar",
180 - name: "layout-right-sidebar",
208 + name: "Layout-RightSidebar",
209 component: () => import("@/views/Layout/RightSidebar.vue"),
210 meta: { title: "Right Sidebar" }
211 },
212 {
213 path: "full-width",
186 - name: "layout-full-width",
214 + name: "Layout-FullWidth",
215 component: () => import("@/views/Layout/FullWidth.vue"),
216 meta: { title: "Full Width" }
217 }
@@ -199,25 +227,25 @@ const router = createRouter({
227 children: [
228 {
229 path: "google-maps",
202 - name: "maps-google-maps",
230 + name: "Maps-GoogleMaps",
231 component: () => import("@/views/Maps/GoogleMaps.vue"),
232 meta: { title: "Google maps" }
233 },
234 {
235 path: "maplibre",
208 - name: "maps-maplibre",
236 + name: "Maps-MapLibre",
237 component: () => import("@/views/Maps/MapLibre.vue"),
238 meta: { title: "MapLibre" }
239 },
240 {
241 path: "leaflet",
214 - name: "maps-leaflet",
242 + name: "Maps-Leaflet",
243 component: () => import("@/views/Maps/Leaflet.vue"),
244 meta: { title: "Leaflet" }
245 },
246 {
247 path: "vectormap",
220 - name: "maps-vectormap",
248 + name: "Maps-VectorMap",
249 component: () => import("@/views/Maps/VectorMap.vue"),
250 meta: { title: "Vector Map" }
251 }
@@ -233,19 +261,19 @@ const router = createRouter({
261 children: [
262 {
263 path: "quill",
236 - name: "editors-quill",
264 + name: "Editors-Quill",
265 component: () => import("@/views/Editors/Quill.vue"),
266 meta: { title: "Quill" }
267 },
268 {
269 path: "tiptap",
242 - name: "editors-tiptap",
270 + name: "Editors-Tiptap",
271 component: () => import("@/views/Editors/Tiptap.vue"),
272 meta: { title: "Tiptap" }
273 },
274 {
275 path: "milkdown",
248 - name: "editors-milkdown",
276 + name: "Editors-Milkdown",
277 component: () => import("@/views/Editors/Milkdown.vue"),
278 meta: { title: "Milkdown" }
279 }
@@ -261,13 +289,13 @@ const router = createRouter({
289 children: [
290 {
291 path: "apexcharts",
264 - name: "charts-apexcharts",
292 + name: "Charts-ApexCharts",
293 component: () => import("@/views/Charts/ApexCharts.vue"),
294 meta: { title: "ApexCharts" }
295 },
296 {
297 path: "chartjs",
270 - name: "charts-chartjs",
298 + name: "Charts-ChartJS",
299 component: () => import("@/views/Charts/ChartJS.vue"),
300 meta: { title: "ChartJS" }
301 }
@@ -275,31 +303,15 @@ const router = createRouter({
303 },
304 {
305 path: "/multi-language",
278 - name: "multi-language",
306 + name: "MultiLanguage",
307 component: () => import("@/views/MultiLanguage.vue"),
308 meta: { title: "Multi Language", auth: true, roles: UserRole.All }
309 },
310 {
311 path: "/icons",
284 - redirect: "/icons/xicons",
285 - meta: {
286 - auth: true,
287 - roles: UserRole.All
288 - },
289 - children: [
290 - {
291 - path: "xicons",
292 - name: "icons-xicons",
293 - component: () => import("@/views/Icons/Xicons.vue"),
294 - meta: { title: "xIcons" }
295 - },
296 - {
297 - path: "flag",
298 - name: "icons-flag",
299 - component: () => import("@/views/Icons/Flag.vue"),
300 - meta: { title: "Flag Icons" }
301 - }
302 - ]
312 + name: "Icons",
313 + component: () => import("@/views/Icons.vue"),
314 + meta: { title: "Icons", auth: true, roles: UserRole.All }
315 },
316 {
317 path: "/tables",
@@ -311,13 +323,13 @@ const router = createRouter({
323 children: [
324 {
325 path: "base",
314 - name: "tables-base",
326 + name: "Tables-Base",
327 component: () => import("@/views/Tables/Base.vue"),
328 meta: { title: "Tables Base" }
329 },
330 {
331 path: "data-table",
320 - name: "tables-data-table",
332 + name: "Tables-DataTable",
333 component: () => import("@/views/Tables/DataTable.vue"),
334 meta: { title: "Data Table" }
335 }
@@ -326,25 +338,25 @@ const router = createRouter({
338
339 {
340 path: "/profile",
329 - name: "profile",
341 + name: "Profile",
342 component: () => import("@/views/Profile.vue"),
343 meta: { title: "Profile", auth: true, roles: UserRole.All }
344 },
345
346 {
347 path: "/login",
336 - name: "login",
348 + name: "Login",
349 component: () => import("@/views/Auth/Login.vue"),
350 meta: { title: "Login", forceLayout: Layout.Blank, checkAuth: true }
351 },
352 {
353 path: "/logout",
342 - name: "logout",
354 + name: "Logout",
355 redirect: "/login"
356 },
357 {
358 path: "/:pathMatch(.*)*",
347 - name: "not-found",
359 + name: "NotFound",
360 component: () => import("@/views/NotFound.vue"),
361 meta: { forceLayout: Layout.Blank }
362 }
src/stores/auth.ts
+21 -1
@@ -39,7 +39,24 @@ export const useAuthStore = defineStore("auth", {
39 .login(payload)
40 .then(res => {
41 if (res.data.access_token) {
42 - useAuthStore().setLogged(res.data.access_token)
42 + this.setLogged(res.data.access_token)
43 + resolve(res.data)
44 + } else {
45 + reject(res.data)
46 + }
47 + })
48 + .catch(err => {
49 + reject(err.response?.data)
50 + })
51 + })
52 + },
53 + refreshToken() {
54 + return new Promise((resolve, reject) => {
55 + Api.auth
56 + .refresh()
57 + .then(res => {
58 + if (res.data.access_token) {
59 + this.setToken(res.data.access_token)
60 resolve(res.data)
61 } else {
62 reject(res.data)
@@ -61,6 +78,9 @@ export const useAuthStore = defineStore("auth", {
78 userRole(state): UserRole {
79 return state.user?.role
80 },
81 + userRoleName(state): string {
82 + return UserRole[(state.user?.role || 0) as number]
83 + },
84 isRoleGranted() {
85 return (roles?: UserRole | UserRole[]) => {
86 if (!roles) {
src/stores/main.ts
-2
@@ -1,10 +1,8 @@
1 import { defineStore, acceptHMRUpdate } from "pinia"
2 -const API_URL = import.meta.env.VITE_API_URL
2 import { type LoadingBarInst } from "naive-ui/es/loading-bar/src/LoadingBarProvider"
3
4 export const useMainStore = defineStore("main", {
5 state: () => ({
7 - API_URL,
6 forceRefresh: new Date().getTime(),
7 loadingBar: null as LoadingBarInst | null
8 }),
src/stores/settings.ts new
+49
@@ -0,0 +1,49 @@
1 +import { defineStore, acceptHMRUpdate } from "pinia"
2 +
3 +export const useSettingsStore = defineStore("settings", {
4 + state: () => ({
5 + settings: {
6 + dateFormat: "MM/DD/YYYY",
7 + hours24: true
8 + },
9 + dateFormats: ["MM/DD/YYYY", "DD/MM/YYYY"]
10 + }),
11 + actions: {
12 + setDateFormat(format: string) {
13 + this.settings.dateFormat = format
14 + },
15 + setHours24(hours24: boolean) {
16 + this.settings.hours24 = hours24
17 + }
18 + },
19 + getters: {
20 + dateFormatsAvailables(state) {
21 + return state.dateFormats
22 + },
23 + hours24(state) {
24 + return state.settings.hours24
25 + },
26 + rawDateFormat(state) {
27 + return state.settings.dateFormat
28 + },
29 + dateFormat(state) {
30 + const separator = " "
31 + const date = state.settings.dateFormat
32 + const time = state.settings.hours24 ? "HH:mm" : "h:mm a"
33 + const timesec = state.settings.hours24 ? "HH:mm:ss" : "h:mm:ss a"
34 +
35 + return {
36 + date: `${date}`,
37 + datetime: `${date}${separator}${time}`,
38 + datetimesec: `${date}${separator}${timesec}`
39 + }
40 + }
41 + },
42 + persist: {
43 + paths: ["settings"]
44 + }
45 +})
46 +
47 +if (import.meta.hot) {
48 + import.meta.hot.accept(acceptHMRUpdate(useSettingsStore, import.meta.hot))
49 +}
src/stores/theme.ts
+160 -39
@@ -3,16 +3,17 @@ import {
3 type ColorAction,
4 type ColorKey,
5 type ColorType,
6 + type ThemeColor,
7 + type ThemeName,
8 Layout,
9 RouterTransition,
8 - type ThemeColor,
9 - ThemeEnum,
10 - type ThemeName
10 + ThemeEnum
11 } from "@/types/theme.d"
12 import { type GlobalThemeOverrides, type ThemeCommonVars, darkTheme, lightTheme, useOsTheme } from "naive-ui"
13 -import { exposure, hex2hsl, hex2rgb } from "@/utils"
13 +import { exportPrimaryShades, exposure, getTypeValue, hex2rgb, type PrimaryShade } from "@/utils/theme"
14 import _get from "lodash/get"
15 import _set from "lodash/set"
16 +import _pick from "lodash/pick"
17 import { type BuiltInGlobalTheme } from "naive-ui/es/themes/interface"
18 import tokens from "@/design-tokens.json"
19 const osTheme = useOsTheme()
@@ -22,6 +23,7 @@ export const useThemeStore = defineStore("theme", {
23 layout: Layout.VerticalNav,
24 themeName: osTheme.value || ThemeEnum.Light,
25 routerTransition: RouterTransition.FadeUp,
26 + routerTransitionDuration: 0.3,
27 boxed: {
28 enabled: true,
29 toolbar: true,
@@ -59,7 +61,8 @@ export const useThemeStore = defineStore("theme", {
61 borderRadius: tokens["borderRadius"],
62 lineHeight: tokens["lineHeight"],
63 fontSize: tokens["fontSize"],
62 - fontFamily: tokens["fontFamily"]
64 + fontFamily: tokens["fontFamily"],
65 + typography: tokens["typography"]
66 }),
67 actions: {
68 setLayout(layout: Layout): void {
@@ -88,6 +91,17 @@ export const useThemeStore = defineStore("theme", {
91 },
92 setColor(theme: ThemeName, colorType: ColorType, color: string): void {
93 this.colors[theme][colorType] = color
94 +
95 + if (colorType === "primary") {
96 + const primaryShades = exportPrimaryShades(color)
97 +
98 + for (const k in primaryShades) {
99 + const name = k as PrimaryShade
100 + const shade = primaryShades[name]
101 + // @ts-ignore
102 + this.colors[theme][colorType + name] = shade
103 + }
104 + }
105 },
106 toggleTheme(): void {
107 if (this.isThemeDark) {
@@ -138,8 +152,19 @@ export const useThemeStore = defineStore("theme", {
152 return state.themeName === ThemeEnum.Dark ? darkTheme : lightTheme
153 },
154 themeOverrides(state): GlobalThemeOverrides {
141 - const { primary, success, warning, error, info, background, bodyBackground, text, textSecondary } =
142 - state.colors[state.themeName]
155 + const {
156 + primary,
157 + success,
158 + warning,
159 + error,
160 + info,
161 + background,
162 + bodyBackground,
163 + text,
164 + textSecondary,
165 + divider005,
166 + hover010
167 + } = state.colors[state.themeName]
168
169 const themeColors = getThemeColors({ primary, success, warning, error, info })
170
@@ -164,7 +189,9 @@ export const useThemeStore = defineStore("theme", {
189 borderRadiusSmall,
190 fontSize: state.fontSize.base,
191 fontFamily: state.fontFamily.base,
167 - fontFamilyMono: state.fontFamily.mono
192 + fontFamilyMono: state.fontFamily.mono,
193 + dividerColor: divider005,
194 + hoverColor: hover010
195 },
196 Card: {
197 color: background,
@@ -175,6 +202,14 @@ export const useThemeStore = defineStore("theme", {
202 },
203 LoadingBar: {
204 colorLoading: primary
205 + },
206 + Typography: {
207 + headerFontSize1: getTypeValue(state, state.typography.h1.fontSize),
208 + headerFontSize2: getTypeValue(state, state.typography.h2.fontSize),
209 + headerFontSize3: getTypeValue(state, state.typography.h3.fontSize),
210 + headerFontSize4: getTypeValue(state, state.typography.h4.fontSize),
211 + headerFontSize5: getTypeValue(state, state.typography.h5.fontSize),
212 + headerFontSize6: getTypeValue(state, state.typography.h6.fontSize)
213 }
214 }
215 },
@@ -193,20 +228,54 @@ export const useThemeStore = defineStore("theme", {
228 bodyBackground(state): string {
229 return state.colors[state.themeName].bodyBackground
230 },
196 - secondaryColors(state): { [key: string]: string } {
197 - const { secondary1, secondary2, secondary3, secondary4 } = state.colors[state.themeName]
198 - return {
199 - secondary1,
200 - secondary2,
201 - secondary3,
202 - secondary4
203 - }
231 + backgroundSecondaryColor(state): string {
232 + return state.colors[state.themeName].backgroundSecondary
233 },
205 - shadeColors(state): { [key: string]: string } {
206 - const { shade1 } = state.colors[state.themeName]
207 - return {
208 - shade1
209 - }
234 + secondaryColors(state): { [key: string]: string } {
235 + const pick = ["secondary1", "secondary2", "secondary3", "secondary4"]
236 + return _pick(state.colors[state.themeName], pick)
237 + },
238 + secondaryOpacityColors(state): { [key: string]: string } {
239 + const pick = [
240 + "secondary1Opacity005",
241 + "secondary1Opacity010",
242 + "secondary1Opacity020",
243 + "secondary1Opacity030",
244 + "secondary2Opacity005",
245 + "secondary2Opacity010",
246 + "secondary2Opacity020",
247 + "secondary2Opacity030",
248 + "secondary3Opacity005",
249 + "secondary3Opacity010",
250 + "secondary3Opacity020",
251 + "secondary3Opacity030",
252 + "secondary4Opacity005",
253 + "secondary4Opacity010",
254 + "secondary4Opacity020",
255 + "secondary4Opacity030"
256 + ]
257 + return _pick(state.colors[state.themeName], pick)
258 + },
259 + dividerColors(state): { [key: string]: string } {
260 + const pick = ["divider005", "divider010", "divider020"]
261 + return _pick(state.colors[state.themeName], pick)
262 + },
263 + hoverColors(state): { [key: string]: string } {
264 + const pick = ["hover005", "hover010", "hover050"]
265 + return _pick(state.colors[state.themeName], pick)
266 + },
267 + primaryColors(state): { [key: string]: string } {
268 + const pick = [
269 + "primary005",
270 + "primary010",
271 + "primary015",
272 + "primary020",
273 + "primary030",
274 + "primary040",
275 + "primary050",
276 + "primary060"
277 + ]
278 + return _pick(state.colors[state.themeName], pick)
279 },
280 naiveCommon(): ThemeCommonVars {
281 return { ...this.naiveTheme.common, ...this.themeOverrides.common }
@@ -216,15 +285,13 @@ export const useThemeStore = defineStore("theme", {
285
286 const bgColor = naive.baseColor
287 const bgColorRGB = hex2rgb(bgColor).join(", ")
288 + const bgSecondaryColor = this.backgroundSecondaryColor
289 const fgColor = naive.textColorBase
220 - const fgColorRGB = hex2rgb(fgColor).join(", ")
290 const fgSecondaryColor = naive.textColor3
222 - const fgSecondaryColorRGB = hex2rgb(fgSecondaryColor).join(", ")
291 +
292 const tabFgColorActive = naive.textColor2
293 const borderColor = naive.dividerColor
294 const primaryColor = naive.primaryColor
226 - const primaryColorRGB = hex2rgb(primaryColor).join(", ")
227 - const primaryColorHS = [hex2hsl(primaryColor)[0], hex2hsl(primaryColor)[1] + "%"].join(" ")
295
296 const successColor = naive.successColor
297 const errorColor = naive.errorColor
@@ -244,8 +311,9 @@ export const useThemeStore = defineStore("theme", {
311
312 const bgSidebar = this.sidebarBackground
313 const bgBody = this.bodyBackground
247 - const bgBodyRGB = hex2rgb(bgBody).join(", ")
314 +
315 const boxedWidth = state.boxed.width
316 + const routerTransitionDuration = state.routerTransitionDuration
317 const sidebarAnimEase = state.sidebar.animEase
318 const sidebarAnimDuration = state.sidebar.animDuration
319 const sidebarOpenWidth = state.sidebar.openWidth
@@ -260,27 +328,49 @@ export const useThemeStore = defineStore("theme", {
328 const borderRadius = state.borderRadius.base
329 const borderRadiusSmall = state.borderRadius.small
330
331 + const { divider005, divider010, divider020 } = this.dividerColors
332 + const { hover005, hover010, hover050 } = this.hoverColors
333 + const { primary005, primary010, primary015, primary020, primary030, primary040, primary050, primary060 } =
334 + this.primaryColors
335 + const {
336 + secondary1Opacity005,
337 + secondary1Opacity010,
338 + secondary1Opacity020,
339 + secondary1Opacity030,
340 + secondary2Opacity005,
341 + secondary2Opacity010,
342 + secondary2Opacity020,
343 + secondary2Opacity030,
344 + secondary3Opacity005,
345 + secondary3Opacity010,
346 + secondary3Opacity020,
347 + secondary3Opacity030,
348 + secondary4Opacity005,
349 + secondary4Opacity010,
350 + secondary4Opacity020,
351 + secondary4Opacity030
352 + } = this.secondaryOpacityColors
353 +
354 const { secondary1, secondary2, secondary3, secondary4 } = this.secondaryColors
355 const secondary1RGB = hex2rgb(secondary1).join(", ")
356 const secondary2RGB = hex2rgb(secondary2).join(", ")
357 const secondary3RGB = hex2rgb(secondary3).join(", ")
358 const secondary4RGB = hex2rgb(secondary4).join(", ")
359
269 - const { shade1 } = this.shadeColors
270 - const shade1RGB = hex2rgb(shade1).join(", ")
271 -
360 return {
361 + "--bg-sidebar": `${bgSidebar}`,
362 + "--bg-body": `${bgBody}`,
363 +
364 "--fg-color": `${fgColor}`,
274 - "--fg-color-rgb": `${fgColorRGB}`,
365 "--fg-secondary-color": `${fgSecondaryColor}`,
276 - "--fg-secondary-color-rgb": `${fgSecondaryColorRGB}`,
366 "--bg-color": `${bgColor}`,
367 + "--bg-secondary-color": `${bgSecondaryColor}`,
368 +
369 "--bg-color-rgb": `${bgColorRGB}`,
279 - "--bg-sidebar": `${bgSidebar}`,
280 - "--bg-body": `${bgBody}`,
281 - "--bg-body-rgb": `${bgBodyRGB}`,
370 +
371 "--border-color": `${borderColor}`,
372 "--bezier-ease": `${bezierEase}`,
373 + "--router-transition-duration": `${routerTransitionDuration}s`,
374 "--sidebar-anim-ease": `${sidebarAnimEase}`,
375 "--sidebar-anim-duration": `${sidebarAnimDuration}s`,
376 "--sidebar-open-width": `${sidebarOpenWidth}px`,
@@ -296,8 +386,6 @@ export const useThemeStore = defineStore("theme", {
386 "--font-family-mono": `${fontFamilyMono}`,
387 "--code-color": `${codeColor}`,
388 "--primary-color": `${primaryColor}`,
299 - "--primary-color-rgb": `${primaryColorRGB}`,
300 - "--primary-color-hs": `${primaryColorHS}`,
389 "--tab-color": `${tabColor}`,
390 "--tab-color-active": `${tabColorActive}`,
391 "--tab-fg-color-active": `${tabFgColorActive}`,
@@ -308,10 +396,28 @@ export const useThemeStore = defineStore("theme", {
396 "--button-color-secondary-hover": `${buttonColorSecondaryHover}`,
397 "--button-color-secondary-pressed": `${buttonColorSecondaryPressed}`,
398
399 + "--primary-005-color": `${primary005}`,
400 + "--primary-010-color": `${primary010}`,
401 + "--primary-015-color": `${primary015}`,
402 + "--primary-020-color": `${primary020}`,
403 + "--primary-030-color": `${primary030}`,
404 + "--primary-040-color": `${primary040}`,
405 + "--primary-050-color": `${primary050}`,
406 + "--primary-060-color": `${primary060}`,
407 +
408 + "--hover-005-color": `${hover005}`,
409 + "--hover-010-color": `${hover010}`,
410 + "--hover-050-color": `${hover050}`,
411 +
412 + "--divider-005-color": `${divider005}`,
413 + "--divider-010-color": `${divider010}`,
414 + "--divider-020-color": `${divider020}`,
415 +
416 "--success-color": `${successColor}`,
417 "--error-color": `${errorColor}`,
418 "--warning-color": `${warningColor}`,
419 "--info-color": `${infoColor}`,
420 +
421 "--secondary1-color": `${secondary1}`,
422 "--secondary1-color-rgb": `${secondary1RGB}`,
423 "--secondary2-color": `${secondary2}`,
@@ -320,8 +426,23 @@ export const useThemeStore = defineStore("theme", {
426 "--secondary3-color-rgb": `${secondary3RGB}`,
427 "--secondary4-color": `${secondary4}`,
428 "--secondary4-color-rgb": `${secondary4RGB}`,
323 - "--shade1-color": `${shade1}`,
324 - "--shade1-color-rgb": `${shade1RGB}`
429 +
430 + "--secondary1-opacity-005-color": `${secondary1Opacity005}`,
431 + "--secondary1-opacity-010-color": `${secondary1Opacity010}`,
432 + "--secondary1-opacity-020-color": `${secondary1Opacity020}`,
433 + "--secondary1-opacity-030-color": `${secondary1Opacity030}`,
434 + "--secondary2-opacity-005-color": `${secondary2Opacity005}`,
435 + "--secondary2-opacity-010-color": `${secondary2Opacity010}`,
436 + "--secondary2-opacity-020-color": `${secondary2Opacity020}`,
437 + "--secondary2-opacity-030-color": `${secondary2Opacity030}`,
438 + "--secondary3-opacity-005-color": `${secondary3Opacity005}`,
439 + "--secondary3-opacity-010-color": `${secondary3Opacity010}`,
440 + "--secondary3-opacity-020-color": `${secondary3Opacity020}`,
441 + "--secondary3-opacity-030-color": `${secondary3Opacity030}`,
442 + "--secondary4-opacity-005-color": `${secondary4Opacity005}`,
443 + "--secondary4-opacity-010-color": `${secondary4Opacity010}`,
444 + "--secondary4-opacity-020-color": `${secondary4Opacity020}`,
445 + "--secondary4-opacity-030-color": `${secondary4Opacity030}`
446 } as unknown as CSSStyleDeclaration
447 },
448 isThemeDark(state): boolean {
@@ -341,7 +462,7 @@ export const useThemeStore = defineStore("theme", {
462 }
463 },
464 persist: {
344 - paths: ["layout", "themeName", "routerTransition", "boxed", "sidebar", "colors"]
465 + paths: ["layout", "themeName", "routerTransition", "boxed", "sidebar"]
466 }
467 })
468
src/types/graylog/alerts.d.ts new
+80
@@ -0,0 +1,80 @@
1 +export interface AlertsQuery {
2 + query: string
3 + page: number
4 + per_page: number
5 + filter: {
6 + alerts: "only"
7 + event_definitions: any[]
8 + }
9 + timerange: {
10 + range: number // seconds
11 + type: "relative" | "absolute"
12 + }
13 +}
14 +
15 +export interface Alerts {
16 + context: AlertsContext
17 + duration: number
18 + events: AlertsEventElement[]
19 + parameters: AlertsParameters
20 + total_events: number
21 + used_indices: string[]
22 +}
23 +
24 +export interface AlertsContext {
25 + event_definitions: { [key: string]: AlertsEventDefinition }
26 + streams: { [key: string]: AlertsEventDefinition }
27 +}
28 +
29 +export interface AlertsEventDefinition {
30 + description: string
31 + id: string
32 + title: string
33 +}
34 +
35 +export interface AlertsEventElement {
36 + event: AlertsEvent
37 + index_name: string
38 + index_type: string
39 +}
40 +
41 +export interface AlertsEvent {
42 + alert: boolean
43 + event_definition_id: string
44 + event_definition_type: string
45 + fields: { [key: string]: string }
46 + group_by_fields: any
47 + id: string
48 + key: null
49 + key_tuple: any[]
50 + message: string
51 + origin_context: string
52 + priority: number
53 + source: string
54 + source_streams: string[]
55 + streams: string[]
56 + timerange_end: null
57 + timerange_start: null
58 + timestamp: string
59 + timestamp_processing: string
60 +}
61 +
62 +export interface AlertsParameters {
63 + page: number
64 + per_page: number
65 + query: string
66 + sort_by: string
67 + sort_direction: string
68 + timerange: AlertsParametersTimerange
69 + filter: AlertsParametersFilter
70 +}
71 +
72 +export interface AlertsParametersFilter {
73 + alerts: string
74 + event_definitions: any[]
75 +}
76 +
77 +export interface AlertsParametersTimerange {
78 + range: number
79 + type: string
80 +}
src/types/graylog/event-definition.d.ts new
+51
@@ -0,0 +1,51 @@
1 +export interface EventDefinition {
2 + alert: boolean
3 + config: EventDefinitionConfig
4 + description: string
5 + field_spec: { [key: string]: EventDefinitionFieldSpec }
6 + id: string
7 + key_spec: any[]
8 + notification_settings: {
9 + backlog_size: number
10 + grace_period_ms: number
11 + }
12 + notifications: EventDefinitionNotification[]
13 + priority: number
14 + storage: EventDefinitionStorage[]
15 + title: string
16 +}
17 +
18 +export interface EventDefinitionConfig {
19 + conditions: {
20 + expression: string | null
21 + }
22 + execute_every_ms: number
23 + group_by: any[]
24 + query: string
25 + query_parameters: any[]
26 + search_within_ms: number
27 + series: any[]
28 + streams: string[]
29 + type: string
30 +}
31 +
32 +export interface EventDefinitionFieldSpec {
33 + data_type: string
34 + providers: EventDefinitionProvider[]
35 +}
36 +
37 +export interface EventDefinitionProvider {
38 + require_values: boolean
39 + template: string
40 + type: string
41 +}
42 +
43 +export interface EventDefinitionNotification {
44 + notification_id: string
45 + notification_parameters: null
46 +}
47 +
48 +export interface EventDefinitionStorage {
49 + streams: string[]
50 + type: string
51 +}
src/types/graylog/index.d.ts renamed
+6 -67
@@ -5,11 +5,17 @@ export interface Message {
5 timestamp: string
6 }
7
8 +export interface MessageExtended extends Message {
9 + id?: string
10 +}
11 +
12 export interface ThroughputMetric {
13 metric: string
14 value: number
15 }
16
17 +// TODO: review --------------------------------------------------------------------
18 +
19 export interface Documents {
20 count: number
21 deleted: number
@@ -62,70 +68,3 @@ export interface IndexData {
68 message: string
69 success: boolean
70 }
65 -
66 -// Graylog Inputs
67 -
68 -export enum InputState {
69 - RUNNING = "RUNNING",
70 - STOPPED = "STOPPED"
71 -}
72 -
73 -export interface ConfiguredInput {
74 - port: number
75 - title: string
76 -}
77 -
78 -export interface RunningInput {
79 - port: number
80 - state: string
81 - title: string
82 -}
83 -
84 -export interface ConfiguredInputsData {
85 - configured_inputs: ConfiguredInput[]
86 - message: string
87 - success: boolean
88 -}
89 -
90 -export interface RunningInputsData {
91 - inputs: RunningInput[]
92 - message: string
93 - success: boolean
94 -}
95 -
96 -export interface Inputs {
97 - configured_inputs: ConfiguredInputsData
98 - running_inputs: RunningInputsData
99 -}
100 -
101 -// Stream Rule
102 -export interface StreamRule {
103 - description: null | string
104 - field: string
105 - id: string
106 - inverted: boolean
107 - stream_id: string
108 - type: number
109 - value: string
110 -}
111 -
112 -// Stream
113 -export interface Stream {
114 - content_pack: null | string
115 - created_at: string
116 - creator_user_id: string
117 - description: string
118 - disabled: boolean
119 - id: string
120 - index_set_id: string
121 - is_default: boolean
122 - is_editable: boolean
123 - matching_type: string
124 - outputs: any[] // Replace with the appropriate type if known
125 - remove_matches_from_default_stream: boolean
126 - rules: StreamRule[]
127 - title: string
128 -}
129 -
130 -// Streams Array
131 -export interface Streams extends Array<Stream> {}
src/types/graylog/inputs.d.ts new
+81
@@ -0,0 +1,81 @@
1 +export interface ConfiguredInput {
2 + title: string
3 + global: boolean
4 + name: string
5 + content_pack: null | string
6 + created_at: string
7 + type: string
8 + creator_user_id: string
9 + attributes: ConfiguredInputAttributes
10 + static_fields: StaticFields
11 + node: string
12 + id: string
13 +}
14 +
15 +export interface ConfiguredInputAttributes {
16 + recv_buffer_size: number
17 + tcp_keepalive: boolean
18 + use_null_delimiter: boolean
19 + number_worker_threads: number
20 + tls_client_auth_cert_file: string
21 + force_rdns: null | boolean
22 + bind_address: string
23 + tls_cert_file: string
24 + store_full_message: null | boolean
25 + expand_structured_data: null | boolean
26 + port: number
27 + tls_key_file: string
28 + tls_enable: boolean
29 + tls_key_password: string
30 + max_message_size: number
31 + tls_client_auth: string
32 + override_source: null | string
33 + charset_name: null | string
34 + allow_override_date: null | boolean
35 +}
36 +
37 +export interface RunningInput {
38 + id: string
39 + state: "RUNNING" | string
40 + started_at: string
41 + detailed_message: null | string
42 + message_input: MessageInput
43 +}
44 +
45 +export interface MessageInput {
46 + title: string
47 + global: boolean
48 + name: string
49 + content_pack: null | string
50 + created_at: string
51 + type: string
52 + creator_user_id: string
53 + attributes: MessageInputAttributes
54 + static_fields: StaticFields
55 + node: string
56 + id: string
57 +}
58 +
59 +export interface MessageInputAttributes {
60 + recv_buffer_size: number
61 + tcp_keepalive: boolean
62 + use_null_delimiter: boolean
63 + number_worker_threads: number
64 + tls_client_auth_cert_file: string
65 + bind_address: string
66 + tls_cert_file: string
67 + port: number
68 + tls_key_file: string
69 + tls_enable: boolean
70 + tls_key_password: string
71 + max_message_size: number
72 + tls_client_auth: string
73 +}
74 +
75 +export interface StaticFields {
76 + [key: string]: string
77 +}
78 +
79 +export interface InputExtended
80 + extends ConfiguredInput,
81 + Pick<RunningInput, "state" | "started_at" | "detailed_message"> {}
src/types/graylog/pipelines.d.ts new
+37
@@ -0,0 +1,37 @@
1 +export interface Pipeline {
2 + created_at: string
3 + description: string
4 + errors: null | string
5 + id: string
6 + modified_at: string
7 + source: string
8 + stages: PipelineStage[]
9 + title: string
10 +}
11 +
12 +export interface PipelineFull extends Pipeline {
13 + stages: PipelineFullStage[]
14 +}
15 +
16 +export interface PipelineStage {
17 + match: "EITHER" | "PASS"
18 + rules: string[]
19 + stage: number
20 +}
21 +
22 +export interface PipelineFullStage {
23 + match: "EITHER" | "PASS"
24 + rules: string[]
25 + rule_ids: string[]
26 + stage: number
27 +}
28 +
29 +export interface PipelineRule {
30 + created_at: string
31 + description: string
32 + errors: null | string
33 + id: string
34 + modified_at: string
35 + source: string
36 + title: string
37 +}
src/types/graylog/stream.d.ts new
+26
@@ -0,0 +1,26 @@
1 +export interface Stream {
2 + content_pack: string | null
3 + created_at: string
4 + creator_user_id: string
5 + description: string
6 + disabled: boolean
7 + id: string
8 + index_set_id: string
9 + is_default: boolean
10 + is_editable: boolean
11 + matching_type: "AND" | "OR" | string
12 + outputs: string[]
13 + remove_matches_from_default_stream: boolean
14 + rules: StreamRule[]
15 + title: string
16 +}
17 +
18 +export interface StreamRule {
19 + description: string | null
20 + field: string
21 + id: string
22 + inverted: boolean
23 + stream_id: string
24 + type: number
25 + value: string
26 +}
src/utils/auth.ts
+13 -7
@@ -2,8 +2,20 @@ import { useAuthStore } from "@/stores/auth"
2 import { type RouteMetaAuth, UserRole } from "@/types/auth.d"
3 import { type RouteLocationNormalized } from "vue-router"
4 import _castArray from "lodash/castArray"
5 +import _toNumber from "lodash/toNumber"
6 import * as jose from "jose"
7
8 +const TOKEN_DEBOUNCE_TIME = import.meta.env.VITE_TOKEN_DEBOUNCE_TIME // seconds
9 +
10 +export function isDebounceTimeOver(lastCheck: Date | null) {
11 + if (!lastCheck) return true
12 +
13 + const timeOver = lastCheck.getTime() + _toNumber(TOKEN_DEBOUNCE_TIME) * 1000
14 + const now = new Date().getTime()
15 +
16 + return timeOver < now
17 +}
18 +
19 /**
20 * @param token jwt token
21 * @param threshold in seconds
@@ -18,12 +30,6 @@ export function isJwtExpiring(token: string, threshold: number): boolean {
30 const now = new Date().getTime() / 1000
31 const delta = (exp || 0) - threshold
32
21 - /*
22 - console.log("exp", new Date((exp || 0) * 1000))
23 - console.log("now", new Date())
24 - console.log("del", new Date(delta * 1000))
25 - */
26 -
33 if (!exp) {
34 return true
35 }
@@ -38,7 +44,7 @@ export function authCheck(route: RouteLocationNormalized) {
44 const meta: RouteMetaAuth = route.meta
45 const { checkAuth, authRedirect, auth, roles } = meta
46
41 - if (route?.redirectedFrom?.name === "logout") {
47 + if (route?.redirectedFrom?.name === "Logout") {
48 useAuthStore().setLogout()
49 }
50
src/utils/i18n.ts deleted
-15
@@ -1,15 +0,0 @@
1 -import { useLocalesStore } from "@/stores/i18n"
2 -
3 -export function initLocale(): string {
4 - return useLocalesStore().locale
5 -}
6 -export function getAvailableLocales(): string[] {
7 - return useLocalesStore().available
8 -}
9 -export function getLocale(): string {
10 - return useLocalesStore().locale
11 -}
12 -export function setLocale(newLocale: string): string {
13 - useLocalesStore().setLocale(newLocale)
14 - return newLocale
15 -}
src/utils/index.ts
+7 -21
@@ -1,6 +1,5 @@
1 -import { NIcon } from "naive-ui"
1 +import Icon from "@/components/common/Icon.vue"
2 import { type Component, h } from "vue"
3 -import { colord } from "colord"
3 import { isMobile as detectMobile } from "detect-touch-device"
4
5 export type OS = "Unknown" | "Windows" | "MacOS" | "UNIX" | "Linux"
@@ -28,27 +27,14 @@ export const isMobile = () => {
27 return detectMobile
28 }
29
31 -export function renderIcon(icon: Component) {
32 - return () => h(NIcon, null, { default: () => h(icon) })
33 -}
34 -export function hex2rgb(hex: string): number[] {
35 - const rgba = colord(hex).toRgb()
36 - return [rgba.r, rgba.g, rgba.b]
37 -}
38 -export function hex2hsl(hex: string): number[] {
39 - const hsl = colord(hex).toHsl()
40 - return [hsl.h, hsl.s, hsl.l]
41 -}
42 -
43 -export function exposure(color: string, amount: number): string {
44 - if (amount >= 0) {
45 - return colord(color).lighten(amount).desaturate(amount).toHex()
30 +export function renderIcon(icon: Component | string) {
31 + if (typeof icon === "string") {
32 + return () => h(Icon, { name: icon })
33 + } else {
34 + return () => h(Icon, null, { default: () => h(icon) })
35 }
47 - return colord(color)
48 - .lighten(amount)
49 - .desaturate(amount * -1)
50 - .toHex()
36 }
37 +
38 export function getOS(): OS {
39 let os: OS = "Unknown"
40 if (navigator.userAgent.indexOf("Win") != -1) os = "Windows"
src/utils/theme.ts
+47
@@ -1,3 +1,8 @@
1 +import { colord } from "colord"
2 +import _get from "lodash/get"
3 +
4 +export type PrimaryShade = "005" | "010" | "015" | "020" | "030" | "040" | "050" | "060"
5 +
6 export function toggleSidebarClass(
7 sidebarCollapsed: boolean,
8 elementId: string,
@@ -13,3 +18,45 @@ export function toggleSidebarClass(
18 el && el.classList.remove(classClose)
19 }
20 }
21 +
22 +export function hex2rgb(hex: string): number[] {
23 + const rgba = colord(hex).toRgb()
24 + return [rgba.r, rgba.g, rgba.b]
25 +}
26 +export function hex2hsl(hex: string): number[] {
27 + const hsl = colord(hex).toHsl()
28 + return [hsl.h, hsl.s, hsl.l]
29 +}
30 +
31 +export function exposure(color: string, amount: number): string {
32 + if (amount >= 0) {
33 + return colord(color).lighten(amount).desaturate(amount).toHex()
34 + }
35 + return colord(color)
36 + .lighten(amount)
37 + .desaturate(amount * -1)
38 + .toHex()
39 +}
40 +
41 +export function exportPrimaryShades(color: string): { [key: string]: string } {
42 + const rgba = colord(color).toRgb()
43 + return {
44 + "005": colord({ r: rgba.r, g: rgba.g, b: rgba.b, a: 0.05 }).toRgbString(),
45 + "010": colord({ r: rgba.r, g: rgba.g, b: rgba.b, a: 0.1 }).toRgbString(),
46 + "015": colord({ r: rgba.r, g: rgba.g, b: rgba.b, a: 0.15 }).toRgbString(),
47 + "020": colord({ r: rgba.r, g: rgba.g, b: rgba.b, a: 0.2 }).toRgbString(),
48 + "030": colord({ r: rgba.r, g: rgba.g, b: rgba.b, a: 0.3 }).toRgbString(),
49 + "040": colord({ r: rgba.r, g: rgba.g, b: rgba.b, a: 0.4 }).toRgbString(),
50 + "050": colord({ r: rgba.r, g: rgba.g, b: rgba.b, a: 0.5 }).toRgbString(),
51 + "060": colord({ r: rgba.r, g: rgba.g, b: rgba.b, a: 0.6 }).toRgbString()
52 + }
53 +}
54 +
55 +export function getTypeValue(origin: any, val: string) {
56 + if (val && val.indexOf("{") === 0) {
57 + const path = val.replace("{", "").replace("}", "")
58 + return _get(origin, path)
59 + }
60 +
61 + return val
62 +}
src/views/Apps/Calendars/FullCalendar.vue
+54 -45
@@ -40,9 +40,7 @@
40 class="flex items-center"
41 rel="nofollow noopener noreferrer"
42 >
43 - <n-icon :size="16">
44 - <ExternalIcon />
45 - </n-icon>
43 + <Icon :name="ExternalIcon" :size="16" />
44 <span class="ml-2">docs</span>
45 </a>
46 <a
@@ -52,17 +50,16 @@
50 class="flex items-center"
51 rel="nofollow noopener noreferrer"
52 >
55 - <n-icon :size="16">
56 - <ExternalIcon />
57 - </n-icon>
53 + <Icon :name="ExternalIcon" :size="16" />
54 <span class="ml-2">VCalendar</span>
55 </a>
56 </div>
57 </div>
58 </div>
59 </n-scrollbar>
64 - <div class="main flex-grow">
65 - <FullCalendar ref="refCalendar" :options="calendarOptions" />
60 + <div class="main flex-grow scrollbar-styled">
61 + <FullCalendar ref="refCalendar" :options="calendarOptions" v-if="ready" />
62 + <n-spin v-else class="w-full h-full"></n-spin>
63 </div>
64 </div>
65
@@ -100,9 +97,12 @@
97 </template>
98
99 <script setup lang="ts">
103 -import { computed, onMounted, ref, watch } from "vue"
104 -import { NCheckbox, NCheckboxGroup, NIcon, NScrollbar, NModal, NButton } from "naive-ui"
105 -import ExternalIcon from "@vicons/tabler/ExternalLink"
100 +import { computed, onMounted, ref, watch, nextTick } from "vue"
101 +import { NCheckbox, NCheckboxGroup, NScrollbar, NModal, NButton, NSpin } from "naive-ui"
102 +
103 +import Icon from "@/components/common/Icon.vue"
104 +const ExternalIcon = "tabler:external-link"
105 +
106 import FullCalendar from "@fullcalendar/vue3"
107 import type { CalendarApi, EventInput, CalendarOptions } from "@fullcalendar/core"
108 import type { DateMarker, EventImpl } from "@fullcalendar/core/internal"
@@ -110,7 +110,7 @@ import dayGridPlugin from "@fullcalendar/daygrid"
110 import interactionPlugin from "@fullcalendar/interaction"
111 import listPlugin from "@fullcalendar/list"
112 import timeGridPlugin from "@fullcalendar/timegrid"
113 -import type { CalendarEvent } from "@/mock/fullcalendar"
113 +import type { CalendarEvent, CalendarEditEvent } from "@/mock/fullcalendar"
114 import { useFullCalendarStore } from "@/stores/apps/useFullCalendarStore"
115 import { DatePicker } from "v-calendar"
116 import "v-calendar/style.css"
@@ -119,21 +119,28 @@ import { useThemeStore } from "@/stores/theme"
119 import EventEditor from "@/components/apps/FullCalendar/EventEditor.vue"
120 import { useHideLayoutFooter } from "@/composables/useHideLayoutFooter"
121
122 -// Store
122 const store = useFullCalendarStore()
123 +const themeStore = useThemeStore()
124
125 +const ready = ref(false)
126 const refCalendar = ref()
127 const calendarApi = ref<null | CalendarApi>(null)
128
129 onMounted(() => {
129 - calendarApi.value = refCalendar.value.getApi()
130 + nextTick(() => {
131 + const duration = 1000 * themeStore.routerTransitionDuration
132 + const gap = 500
133 +
134 + // TIMEOUT REQUIRED BY PAGE ANIMATION
135 + setTimeout(() => {
136 + ready.value = true
137 + nextTick(() => {
138 + calendarApi.value = refCalendar.value?.getApi()
139 + })
140 + }, duration + gap)
141 + })
142 })
143
132 -export interface CalendarEditEvent extends Omit<CalendarEvent, "start" | "end"> {
133 - start: number
134 - end: number
135 -}
136 -
144 const newEvent: CalendarEditEvent = {
145 title: "",
146 start: new Date().getTime(),
@@ -339,7 +346,8 @@ useHideLayoutFooter()
346 :deep() {
347 .sidebar-scroll {
348 width: 310px;
342 - background-color: var(--bg-sidebar);
349 + min-width: 290px;
350 + background-color: var(--bg-secondary-color);
351
352 @media (max-width: 1200px) {
353 display: none;
@@ -348,6 +356,7 @@ useHideLayoutFooter()
356 }
357 .sidebar {
358 padding: 36px 20px;
359 + min-width: 290px;
360 }
361
362 .main {
@@ -359,16 +368,16 @@ useHideLayoutFooter()
368 height: 100%;
369
370 :deep() {
362 - --fc-today-bg-color: rgba(var(--primary-color-rgb), 0.05);
363 - --fc-border-color: rgba(var(--fg-color-rgb), 0.1);
364 - --fc-button-bg-color: rgba(var(--bg-color-rgb), 0.7);
365 -
366 - --fc-button-border-color: rgba(var(--primary-color-rgb), 0.05);
367 - --fc-button-text-color: rgba(var(--primary-color-rgb), 1);
368 - --fc-button-active-bg-color: rgba(var(--primary-color-rgb), 0.15);
369 - --fc-button-active-border-color: rgba(var(--primary-color-rgb), 0.05);
370 - --fc-button-hover-bg-color: rgba(var(--primary-color-rgb), 0.05);
371 - --fc-button-hover-border-color: rgba(var(--primary-color-rgb), 0.05);
371 + --fc-today-bg-color: var(--primary-005-color);
372 + --fc-border-color: var(--border-color);
373 + --fc-button-bg-color: var(--bg-secondary-color);
374 +
375 + --fc-button-border-color: var(--primary-005-color);
376 + --fc-button-text-color: var(--primary-color);
377 + --fc-button-active-bg-color: var(--primary-010-color);
378 + --fc-button-active-border-color: var(--primary-005-color);
379 + --fc-button-hover-bg-color: var(--primary-005-color);
380 + --fc-button-hover-border-color: var(--primary-005-color);
381 .fc-header-toolbar {
382 flex-wrap: wrap;
383 gap: 10px;
@@ -417,7 +426,7 @@ useHideLayoutFooter()
426 }
427
428 .fc-drawerToggler-button {
420 - background-color: rgba(var(--primary-color-rgb), 0.1);
429 + background-color: var(--primary-010-color);
430 border-radius: var(--border-radius);
431 border: none;
432 padding: 7px 18px;
@@ -475,7 +484,7 @@ useHideLayoutFooter()
484
485 .fc-list-event:hover {
486 td {
478 - background-color: rgba(var(--primary-color-rgb), 0.05);
487 + background-color: var(--primary-005-color);
488 }
489 }
490 .fc-timegrid-event-harness-inset .fc-timegrid-event,
@@ -490,41 +499,41 @@ useHideLayoutFooter()
499 }
500
501 .fc-event-time {
493 - color: rgba(var(--fg-color-rgb), 0.5);
502 + color: var(--fg-secondary-color);
503 margin-left: 5px;
504 }
505
506 &.c-Personal {
498 - background-color: rgba(var(--secondary1-color-rgb), 0.06);
499 - border-color: rgba(var(--secondary1-color-rgb), 0.1);
507 + background-color: var(--secondary1-opacity-005-color);
508 + border-color: var(--secondary1-opacity-010-color);
509 .fc-event-title {
510 color: var(--secondary1-color);
511 }
512 }
513 &.c-Business {
505 - background-color: rgba(var(--secondary2-color-rgb), 0.06);
506 - border-color: rgba(var(--secondary2-color-rgb), 0.1);
514 + background-color: var(--secondary2-opacity-005-color);
515 + border-color: var(--secondary2-opacity-010-color);
516 .fc-event-title {
517 color: var(--secondary2-color);
518 }
519 }
520 &.c-Family {
512 - background-color: rgba(var(--secondary3-color-rgb), 0.06);
513 - border-color: rgba(var(--secondary3-color-rgb), 0.1);
521 + background-color: var(--secondary3-opacity-005-color);
522 + border-color: var(--secondary3-opacity-010-color);
523 .fc-event-title {
524 color: var(--secondary3-color);
525 }
526 }
527 &.c-Holiday {
519 - background-color: rgba(var(--secondary4-color-rgb), 0.06);
520 - border-color: rgba(var(--secondary4-color-rgb), 0.1);
528 + background-color: var(--secondary4-opacity-005-color);
529 + border-color: var(--secondary4-opacity-010-color);
530 .fc-event-title {
531 color: var(--secondary4-color);
532 }
533 }
534 &.c-Other {
526 - background-color: rgba(var(--primary-color-rgb), 0.06);
527 - border-color: rgba(var(--primary-color-rgb), 0.05);
535 + background-color: var(--primary-005-color);
536 + border-color: var(--primary-005-color);
537 .fc-event-title {
538 color: var(--primary-color);
539 }
@@ -532,11 +541,11 @@ useHideLayoutFooter()
541 }
542
543 .fc-popover {
535 - border-color: rgba(var(--primary-color-rgb), 0.2);
544 + border-color: var(--primary-020-color);
545 border: none;
546 .fc-popover-header {
547 color: var(--fg-color);
539 - background-color: rgba(var(--bg-color-rgb), 0.7);
548 + background-color: var(--bg-secondary-color);
549 }
550
551 .fc-event-time {
src/views/Apps/Calendars/VueCal.vue
+16 -19
@@ -9,11 +9,8 @@
9 target="_blank"
10 alt="docs"
11 rel="nofollow noopener noreferrer"
12 - class="ml-4"
12 >
14 - <n-icon :size="20">
15 - <ExternalIcon />
16 - </n-icon>
13 + <Icon :name="ExternalIcon" :size="20" />
14 docs
15 </a>
16 <a
@@ -22,9 +19,7 @@
19 alt="docs"
20 rel="nofollow noopener noreferrer"
21 >
25 - <n-icon :size="20">
26 - <ExternalIcon />
27 - </n-icon>
22 + <Icon :name="ExternalIcon" :size="20" />
23 examples
24 </a>
25 </div>
@@ -60,8 +55,9 @@
55
56 <script lang="ts">
57 import { defineComponent } from "vue"
63 -import ExternalIcon from "@vicons/tabler/ExternalLink"
64 -import { NCheckbox, NIcon } from "naive-ui"
58 +import { NCheckbox } from "naive-ui"
59 +import Icon from "@/components/common/Icon.vue"
60 +// @ts-ignore
61 import VueCal from "vue-cal"
62 import "vue-cal/dist/vuecal.css"
63 import dayjs from "@/utils/dayjs"
@@ -93,6 +89,7 @@ export default defineComponent({
89 demoExample,
90 split: false,
91 monthEvents: false,
92 + ExternalIcon: "tabler:external-link",
93 selectedDate: new Date(),
94 previousFirstDayOfWeek: dayjs(new Date().setDate(new Date().getDate() - ((new Date().getDay() + 6) % 7)))
95 }),
@@ -170,19 +167,19 @@ export default defineComponent({
167 this.addEvents()
168 }
169 },
173 - components: { VueCal, NCheckbox, NIcon, ExternalIcon }
170 + components: { VueCal, NCheckbox, Icon }
171 })
172 </script>
173
174 <style lang="scss" scoped>
175 .mini-card {
179 - background: var(--bg-sidebar);
176 + background: var(--bg-secondary-color);
177 border-radius: var(--border-radius);
178 padding: 10px 20px;
179
180 a {
181 text-decoration: underline;
185 - text-decoration-color: rgba(var(--primary-color-rgb), 0.6);
182 + text-decoration-color: var(--primary-060-color);
183 }
184 }
185 .vuecal {
@@ -250,18 +247,18 @@ export default defineComponent({
247
248 &.vuecal__cell--today,
249 &.vuecal__cell--current {
253 - background: rgba(var(--bg-color-rgb), 0.8);
250 + background: var(--bg-secondary-color);
251 background: repeating-linear-gradient(
252 45deg,
256 - rgba(var(--bg-color-rgb), 0.8),
257 - rgba(var(--bg-color-rgb), 0.8) 10px,
258 - rgba(var(--primary-color-rgb), 0.02) 10px,
259 - rgba(var(--primary-color-rgb), 0.02) 20px
253 + var(--bg-secondary-color),
254 + var(--bg-secondary-color) 10px,
255 + var(--primary-005-color) 10px,
256 + var(--primary-005-color) 20px
257 );
258 }
259
260 &.vuecal__cell--selected {
264 - background: rgba(var(--primary-color-rgb), 0.05);
261 + background: var(--primary-005-color);
262 }
263 }
264
@@ -310,7 +307,7 @@ export default defineComponent({
307
308 color: var(--primary-color);
309 border: 1px solid var(--primary-color);
313 - background: rgba(var(--primary-color-rgb), 0.15);
310 + background: var(--primary-010-color);
311
312 @media (max-height: 1000px) {
313 margin-bottom: 10px;
src/views/Apps/Chat.vue
+51 -51
@@ -5,7 +5,7 @@
5 <div class="sidebar-toolbar flex items-center">
6 <n-input placeholder="Search..." clearable size="medium">
7 <template #prefix>
8 - <n-icon :component="SearchIcon" />
8 + <Icon :name="SearchIcon" />
9 </template>
10 </n-input>
11 </div>
@@ -44,9 +44,7 @@
44 <div class="main-toolbar flex items-center" v-if="store.activeChat">
45 <div class="menu-btn flex justify-center opacity-50">
46 <n-button text @click="sidebarOpen = true">
47 - <n-icon :size="24">
48 - <MenuIcon />
49 - </n-icon>
47 + <Icon :name="MenuIcon" :size="24" />
48 </n-button>
49 </div>
50
@@ -65,22 +63,16 @@
63
64 <div class="actions-btns flex items-center gap-4 opacity-50">
65 <n-button text>
68 - <n-icon :size="20">
69 - <VideoIcon />
70 - </n-icon>
66 + <Icon :name="VideoIcon" :size="20" />
67 </n-button>
68 <n-button text>
73 - <n-icon :size="20">
74 - <PhoneIcon />
75 - </n-icon>
69 + <Icon :name="PhoneIcon" :size="20" />
70 </n-button>
71 </div>
72 <div class="new-btn flex justify-center opacity-50">
73 <n-dropdown :options="menuOptions">
74 <n-button text>
81 - <n-icon :size="24">
82 - <MenuHorizontalIcon />
83 - </n-icon>
75 + <Icon :name="MenuHorizontalIcon" :size="24" />
76 </n-button>
77 </n-dropdown>
78 </div>
@@ -122,26 +114,19 @@
114 </div>
115 <div class="actions-group flex items-center">
116 <n-button text>
125 - <n-icon :size="20">
126 - <MicrophoneIcon />
127 - </n-icon>
117 + <Icon :name="MicrophoneIcon" :size="20" />
118 </n-button>
119 <n-button text>
130 - <n-icon :size="20">
131 - <AttachmentIcon />
132 - </n-icon>
120 + <Icon :name="AttachmentIcon" :size="20" />
121 </n-button>
122 <n-button strong ghost circle type="primary">
135 - <n-icon :size="20">
136 - <SendIcon />
137 - </n-icon>
123 + <Icon :name="SendIcon" :size="20" />
124 </n-button>
125 </div>
126 </div>
127 <div class="empty-view grow flex flex-col items-center justify-center" v-if="!store.activeChat">
142 - <n-icon :size="48">
143 - <ChatIcon />
144 - </n-icon>
128 + <Icon :name="ChatIcon" :size="48" />
129 +
130 <div class="text-xl mt-4">Select a Contact</div>
131 </div>
132 </div>
@@ -150,35 +135,39 @@
135 </template>
136
137 <script setup lang="ts">
153 -import { NIcon, NScrollbar, NAvatar, NInput, NButton, NDropdown, NTime } from "naive-ui"
154 -import ChatIcon from "@vicons/carbon/Chat"
155 -import TrashIcon from "@vicons/carbon/TrashCan"
156 -import MenuIcon from "@vicons/ionicons5/MenuSharp"
157 -import MenuHorizontalIcon from "@vicons/carbon/OverflowMenuHorizontal"
158 -import SearchIcon from "@vicons/carbon/Search"
159 -import VideoIcon from "@vicons/carbon/Video"
160 -import PhoneIcon from "@vicons/carbon/Phone"
161 -import InfoIcon from "@vicons/carbon/Information"
162 -import MuteIcon from "@vicons/fluent/AlertOff16Regular"
163 -import BlockUserIcon from "@vicons/tabler/UserOff"
164 -import MicrophoneIcon from "@vicons/carbon/Microphone"
165 -import AttachmentIcon from "@vicons/carbon/Attachment"
166 -import SendIcon from "@vicons/carbon/Send"
167 -import { ref, type VNode, type RendererNode, type RendererElement } from "vue"
138 +import { NScrollbar, NAvatar, NInput, NButton, NDropdown, NTime } from "naive-ui"
139 +import Icon from "@/components/common/Icon.vue"
140 +import { ref, type VNode, type RendererNode, type RendererElement, nextTick } from "vue"
141 import { onClickOutside, useResizeObserver } from "@vueuse/core"
142 import { renderIcon } from "@/utils"
143 import "@vueup/vue-quill/dist/vue-quill.snow.css"
144 import { useChatStore } from "@/stores/apps/useChatStore"
145 import { type Contact } from "@/mock/chat"
146 +import { useThemeStore } from "@/stores/theme"
147 import { onMounted } from "vue"
148 import { useHideLayoutFooter } from "@/composables/useHideLayoutFooter"
149
150 +const ChatIcon = "carbon:chat"
151 +const TrashIcon = "carbon:trash-can"
152 +const MenuIcon = "ion:menu-sharp"
153 +const MenuHorizontalIcon = "carbon:overflow-menu-horizontal"
154 +const SearchIcon = "carbon:search"
155 +const VideoIcon = "carbon:video"
156 +const PhoneIcon = "carbon:phone"
157 +const InfoIcon = "carbon:information"
158 +const MuteIcon = "fluent:alert-off-16-regular"
159 +const BlockUserIcon = "tabler:user-off"
160 +const MicrophoneIcon = "carbon:microphone"
161 +const AttachmentIcon = "carbon:attachment"
162 +const SendIcon = "carbon:send"
163 +
164 interface MenuItem {
165 label: string
166 key: string
167 icon: () => VNode<RendererNode, RendererElement, { [key: string]: any }>
168 }
169
170 +const themeStore = useThemeStore()
171 const store = useChatStore()
172 const sidebarOpen = ref(false)
173 const sidebar = ref(null)
@@ -252,6 +241,16 @@ function resetWindowScroll() {
241
242 onMounted(() => {
243 resetChatScroll()
244 +
245 + nextTick(() => {
246 + const duration = 1000 * themeStore.routerTransitionDuration
247 + const gap = 500
248 +
249 + // TIMEOUT REQUIRED BY PAGE ANIMATION
250 + setTimeout(() => {
251 + resetChatScroll()
252 + }, duration + gap)
253 + })
254 })
255
256 // :has() CSS relational pseudo-class not yet supported by Firefox
@@ -272,9 +271,10 @@ useHideLayoutFooter()
271 overflow: hidden;
272 border-radius: var(--border-radius);
273 border: 1px solid var(--border-color);
274 + background-color: var(--bg-color);
275
276 .sidebar {
277 - background-color: var(--bg-sidebar);
277 + background-color: var(--bg-secondary-color);
278 min-width: 250px;
279 width: 40%;
280 max-width: 350px;
@@ -309,7 +309,7 @@ useHideLayoutFooter()
309
310 .u-avatar {
311 border-radius: 50%;
312 - border: 2px solid rgba(var(--fg-color-rgb), 0.2);
312 + border: 2px solid var(--divider-020-color);
313 position: relative;
314
315 &::after {
@@ -321,7 +321,7 @@ useHideLayoutFooter()
321 right: 0;
322 bottom: 0;
323 background-color: #b8b8b8;
324 - border: 2px solid rgba(var(--fg-color-rgb), 0.2);
324 + border: 2px solid var(--divider-020-color);
325 border-radius: 50%;
326 }
327 &.u-online {
@@ -355,12 +355,12 @@ useHideLayoutFooter()
355 }
356
357 &:hover {
358 - background-color: rgba(var(--fg-color-rgb), 0.05);
358 + background-color: var(--hover-005-color);
359 }
360
361 &.u-active {
362 - background-color: rgba(var(--primary-color-rgb), 0.05);
363 - color: rgba(var(--primary-color-rgb), 0.9);
362 + background-color: var(--primary-005-color);
363 + color: var(--primary-color);
364 }
365 }
366 }
@@ -403,7 +403,7 @@ useHideLayoutFooter()
403
404 .u-avatar {
405 border-radius: 50%;
406 - border: 2px solid rgba(var(--fg-color-rgb), 0.2);
406 + border: 2px solid var(--divider-020-color);
407 position: relative;
408
409 &::after {
@@ -415,7 +415,7 @@ useHideLayoutFooter()
415 right: 0;
416 bottom: 0;
417 background-color: #b8b8b8;
418 - border: 2px solid rgba(var(--fg-color-rgb), 0.2);
418 + border: 2px solid var(--divider-020-color);
419 border-radius: 50%;
420 }
421 &.u-online {
@@ -466,7 +466,7 @@ useHideLayoutFooter()
466 width: fit-content;
467 max-width: 60%;
468 .message {
469 - background-color: var(--bg-sidebar);
469 + background-color: var(--bg-secondary-color);
470 margin-bottom: 5px;
471 padding: 5px 10px;
472 border-radius: var(--border-radius);
@@ -544,7 +544,7 @@ useHideLayoutFooter()
544 content: "";
545 width: 100vw;
546 display: block;
547 - background-color: rgba(var(--bg-body-rgb), 0.4);
547 + background-color: var(--bg-body);
548 position: absolute;
549 top: 0;
550 left: 0;
@@ -591,7 +591,7 @@ useHideLayoutFooter()
591 &.sidebar-open {
592 &::before {
593 transform: translateX(0);
594 - opacity: 1;
594 + opacity: 0.4;
595 transition:
596 opacity 0.25s ease-in-out,
597 transform 0s linear 0s;
src/views/Apps/Kanban.vue
+32 -35
@@ -29,15 +29,11 @@
29 <span>
30 {{ column.title }}
31
32 - <n-icon :size="14">
33 - <EditIcon />
34 - </n-icon>
32 + <Icon :name="EditIcon" :size="12"></Icon>
33 </span>
34 <span class="opacity-40">{{ column.tasks.length || 0 }}</span>
35 </span>
38 - <n-icon :size="20" class="pan-area">
39 - <PanIcon />
40 - </n-icon>
36 + <Icon :name="PanIcon" :size="20" class="pan-area"></Icon>
37 </div>
38 </template>
39 <template #item="{ element: task }">
@@ -48,9 +44,7 @@
44 class="add-task-btn flex items-center justify-center"
45 @click="addTask(column)"
46 >
51 - <n-icon :size="20">
52 - <AddIcon />
53 - </n-icon>
47 + <Icon :name="AddIcon" :size="20"></Icon>
48 <span>Add card</span>
49 </button>
50 </template>
@@ -63,9 +57,7 @@
57 class="add-task-btn flex items-center justify-center !mt-0"
58 @click="addColumn()"
59 >
66 - <n-icon :size="20">
67 - <AddIcon />
68 - </n-icon>
60 + <Icon :name="AddIcon" :size="20"></Icon>
61 <span>Add column</span>
62 </button>
63 </div>
@@ -92,10 +84,7 @@
84 </template>
85
86 <script lang="ts" setup>
95 -import { NScrollbar, NIcon, NModal } from "naive-ui"
96 -import AddIcon from "@vicons/carbon/AddAlt"
97 -import PanIcon from "@vicons/carbon/PanHorizontal"
98 -import EditIcon from "@vicons/fluent/Edit16Filled"
87 +import { NScrollbar, NModal } from "naive-ui"
88 import draggable from "vuedraggable"
89 import TaskCard from "@/components/apps/Kanban/TaskCard.vue"
90 import TaskEditor from "@/components/apps/Kanban/TaskEditor.vue"
@@ -105,6 +94,11 @@ import { getTask, type Column, type Task } from "@/mock/kanban"
94 import dayjs from "@/utils/dayjs"
95 import { isMobile } from "@/utils"
96 import { useHideLayoutFooter } from "@/composables/useHideLayoutFooter"
97 +import Icon from "@/components/common/Icon.vue"
98 +
99 +const AddIcon = "carbon:add-alt"
100 +const PanIcon = "carbon:pan-horizontal"
101 +const EditIcon = "uil:edit-alt"
102
103 const selectedTask = ref<Task | null>(null)
104 const selectedColumn = ref<Column | null>(null)
@@ -157,7 +151,7 @@ useHideLayoutFooter()
151 }
152
153 .column {
160 - background-color: var(--bg-sidebar);
154 + background-color: var(--bg-secondary-color);
155 margin-left: 14px;
156 width: 70vw;
157 max-width: 320px;
@@ -177,7 +171,7 @@ useHideLayoutFooter()
171 margin-right: var(--view-padding);
172 }
173 &:hover {
180 - transform: translateY(-1px);
174 + border-color: var(--primary-color);
175 }
176
177 .column-header {
@@ -205,7 +199,7 @@ useHideLayoutFooter()
199 }
200
201 .add-task-btn {
208 - background-color: rgba(var(--primary-color-rgb), 0.1);
202 + background-color: var(--primary-010-color);
203 width: 100%;
204 height: 50px;
205 border-radius: var(--border-radius-small);
@@ -235,27 +229,30 @@ useHideLayoutFooter()
229 .layout {
230 .main {
231 // .view:has(.kanban-app) when will firefox have full compatibility
238 - .view.route-kanban {
239 - padding-left: 0;
240 - padding-right: 0;
232 + .view {
233 + &.route-kanban,
234 + &.route-Apps-Kanban {
235 + padding-left: 0;
236 + padding-right: 0;
237
242 - .page {
243 - .columns-scroll {
244 - padding: 0 var(--view-padding);
238 + .page {
239 + .columns-scroll {
240 + padding: 0 var(--view-padding);
241 + }
242 }
246 - }
243
248 - &.boxed {
249 - max-width: initial;
244 + &.boxed {
245 + max-width: initial;
246
251 - .page {
252 - .columns-scroll {
253 - max-width: var(--boxed-width);
254 - margin: 0 auto;
255 - padding: 0;
247 + .page {
248 + .columns-scroll {
249 + max-width: var(--boxed-width);
250 + margin: 0 auto;
251 + padding: 0;
252
257 - .columns-wrap {
258 - padding: 0 var(--view-padding);
253 + .columns-wrap {
254 + padding: 0 var(--view-padding);
255 + }
256 }
257 }
258 }
src/views/Apps/Mailbox.vue
+40 -52
@@ -17,14 +17,14 @@
17 :class="[`f-${folder.id}`, folder.id === store.activeFolder ? 'f-active' : '']"
18 >
19 <div class="f-icon">
20 - <n-icon :size="18">
21 - <InboxIcon v-if="folder.id === 'inbox'" />
22 - <SentIcon v-if="folder.id === 'sent'" />
23 - <DraftIcon v-if="folder.id === 'draft'" />
24 - <StarredIcon v-if="folder.id === 'starred'" />
25 - <SpamIcon v-if="folder.id === 'spam'" />
26 - <TrashIcon v-if="folder.id === 'trash'" />
27 - </n-icon>
20 + <Icon :size="18">
21 + <Iconify :icon="InboxIcon" v-if="folder.id === 'inbox'" />
22 + <Iconify :icon="SentIcon" v-if="folder.id === 'sent'" />
23 + <Iconify :icon="DraftIcon" v-if="folder.id === 'draft'" />
24 + <Iconify :icon="StarredIcon" v-if="folder.id === 'starred'" />
25 + <Iconify :icon="SpamIcon" v-if="folder.id === 'spam'" />
26 + <Iconify :icon="TrashIcon" v-if="folder.id === 'trash'" />
27 + </Icon>
28 </div>
29 <div class="f-title">
30 {{ folder.title }}
@@ -42,9 +42,7 @@
42 :class="[`l-${label.id}`, label.id === store.activeLabel ? 'l-active' : '']"
43 >
44 <div class="l-icon flex">
45 - <n-icon :size="14">
46 - <LabelIcon :color="labelsColors[label.id]" />
47 - </n-icon>
45 + <Icon :size="14" :name="LabelIcon" :color="labelsColors[label.id]"></Icon>
46 </div>
47 <div class="l-title">
48 {{ label.title }}
@@ -68,9 +66,7 @@
66 <n-tooltip>
67 <template #trigger>
68 <n-button text>
71 - <n-icon :size="20">
72 - <TrashIcon />
73 - </n-icon>
69 + <Icon :size="20" :name="TrashIcon"></Icon>
70 </n-button>
71 </template>
72 <span>Delete</span>
@@ -79,9 +75,7 @@
75 <n-tooltip>
76 <template #trigger>
77 <n-button text>
82 - <n-icon :size="20">
83 - <LabelOutIcon />
84 - </n-icon>
78 + <Icon :size="20" :name="LabelOutIcon"></Icon>
79 </n-button>
80 </template>
81 <span>Add label</span>
@@ -89,9 +83,7 @@
83 <n-tooltip>
84 <template #trigger>
85 <n-button text>
92 - <n-icon :size="20">
93 - <FolderIcon />
94 - </n-icon>
86 + <Icon :size="20" :name="FolderIcon"></Icon>
87 </n-button>
88 </template>
89 <span>Move to folder</span>
@@ -99,9 +91,7 @@
91 <n-tooltip>
92 <template #trigger>
93 <n-button text>
102 - <n-icon :size="20">
103 - <StarredIcon />
104 - </n-icon>
94 + <Icon :size="20" :name="StarredIcon"></Icon>
95 </n-button>
96 </template>
97 <span>Star</span>
@@ -110,29 +100,23 @@
100 <div class="flex grow search-box" v-if="!checkControl">
101 <n-input placeholder="Search..." clearable size="medium" v-model:value="search">
102 <template #prefix>
113 - <n-icon :component="SearchIcon" />
103 + <Icon :name="SearchIcon" />
104 </template>
105 </n-input>
106 </div>
107 <div class="flex justify-center opacity-50" v-if="!checkControl">
108 <n-button text>
119 - <n-icon :size="18">
120 - <RefreshIcon />
121 - </n-icon>
109 + <Icon :size="18" :name="RefreshIcon"></Icon>
110 </n-button>
111 </div>
112 <div class="menu-btn flex justify-center opacity-50" v-if="!checkControl">
113 <n-button text @click="sidebarOpen = true">
126 - <n-icon :size="24">
127 - <MenuIcon />
128 - </n-icon>
114 + <Icon :size="24" :name="MenuIcon"></Icon>
115 </n-button>
116 </div>
117 <div class="new-btn flex justify-center opacity-50" v-if="!checkControl">
118 <n-button text @click="newEmail()">
133 - <n-icon :size="20">
134 - <PenIcon />
135 - </n-icon>
119 + <Icon :size="20" :name="PenIcon"></Icon>
120 </n-button>
121 </div>
122 </div>
@@ -154,20 +138,10 @@
138 </template>
139
140 <script setup lang="ts">
157 -import { NIcon, NScrollbar, NCheckbox, NInput, NButton, NTooltip } from "naive-ui"
158 -import InboxIcon from "@vicons/carbon/Email"
159 -import SentIcon from "@vicons/carbon/Send"
160 -import DraftIcon from "@vicons/carbon/Edit"
161 -import StarredIcon from "@vicons/carbon/Star"
162 -import SpamIcon from "@vicons/ionicons5/AlertCircleOutline"
163 -import TrashIcon from "@vicons/carbon/TrashCan"
164 -import LabelIcon from "@vicons/carbon/BookmarkFilled"
165 -import LabelOutIcon from "@vicons/carbon/Bookmark"
166 -import MenuIcon from "@vicons/ionicons5/MenuSharp"
167 -import SearchIcon from "@vicons/carbon/Search"
168 -import FolderIcon from "@vicons/carbon/FolderMoveTo"
169 -import RefreshIcon from "@vicons/ionicons5/Reload"
170 -import PenIcon from "@vicons/carbon/Pen"
141 +import { NScrollbar, NCheckbox, NInput, NButton, NTooltip } from "naive-ui"
142 +import Icon from "@/components/common/Icon.vue"
143 +import { Icon as Iconify } from "@iconify/vue"
144 +
145 import { useMailboxStore } from "@/stores/apps/useMailboxStore"
146 import { ref, computed, type ComputedRef, onMounted } from "vue"
147 import { onClickOutside } from "@vueuse/core"
@@ -179,6 +153,20 @@ import ComposeView from "@/components/apps/Mailbox/ComposeView.vue"
153 import { useThemeStore } from "@/stores/theme"
154 import { useHideLayoutFooter } from "@/composables/useHideLayoutFooter"
155
156 +const InboxIcon = "carbon:email"
157 +const SentIcon = "carbon:send"
158 +const DraftIcon = "carbon:edit"
159 +const StarredIcon = "carbon:star"
160 +const SpamIcon = "ion:alert-circle-outline"
161 +const TrashIcon = "carbon:trash-can"
162 +const LabelIcon = "carbon:bookmark-filled"
163 +const LabelOutIcon = "carbon:bookmark"
164 +const MenuIcon = "ion:menu-sharp"
165 +const SearchIcon = "carbon:search"
166 +const FolderIcon = "carbon:folder-move-to"
167 +const RefreshIcon = "ion:reload"
168 +const PenIcon = "carbon:pen"
169 +
170 const store = useMailboxStore()
171 const sidebarOpen = ref(false)
172
@@ -303,7 +291,7 @@ useHideLayoutFooter()
291 width: 100%;
292 display: flex;
293 align-items: center;
306 - background-color: rgba(var(--primary-color-rgb), 0.1);
294 + background-color: var(--primary-010-color);
295 .n-button__content {
296 gap: 14px;
297 }
@@ -330,7 +318,7 @@ useHideLayoutFooter()
318 }
319
320 &:hover {
333 - background-color: rgba(var(--fg-color-rgb), 0.05);
321 + background-color: var(--hover-005-color);
322 }
323
324 &.f-active {
@@ -411,7 +399,7 @@ useHideLayoutFooter()
399 .search-box {
400 margin: 0px 12px;
401 .n-input {
414 - background-color: var(--bg-sidebar);
402 + background-color: var(--bg-secondary-color);
403
404 :deep() {
405 .n-input__border,
@@ -444,7 +432,7 @@ useHideLayoutFooter()
432 content: "";
433 width: 100vw;
434 display: block;
447 - background-color: rgba(var(--bg-body-rgb), 0.4);
435 + background-color: var(--bg-body);
436 position: absolute;
437 top: 0;
438 left: 0;
@@ -496,7 +484,7 @@ useHideLayoutFooter()
484 &.sidebar-open {
485 &::before {
486 transform: translateX(0);
499 - opacity: 1;
487 + opacity: 0.4;
488 transition:
489 opacity 0.25s ease-in-out,
490 transform 0s linear 0s;
src/views/Apps/Notes.vue
+10 -23
@@ -2,9 +2,7 @@
2 <div class="page">
3 <div class="toolbar flex items-center mb-6 gap-4">
4 <n-button type="primary" size="large" @click="newNote()">
5 - <n-icon class="mr-2">
6 - <AddIcon />
7 - </n-icon>
5 + <Icon :name="AddIcon" class="mr-2"></Icon>
6 Add notes
7 </n-button>
8
@@ -54,9 +52,7 @@
52 <n-upload class="mb-2" :max="1" v-else>
53 <n-upload-dragger>
54 <div style="margin-bottom: 12px">
57 - <n-icon size="48" :depth="3">
58 - <ImageIcon />
59 - </n-icon>
55 + <Icon :name="ImageIcon" :size="48" :depth="3"></Icon>
56 </div>
57 <n-text style="font-size: 16px">Click or drag a file to this area to upload</n-text>
58 <n-p depth="3" style="margin: 8px 0 0 0">
@@ -112,27 +108,18 @@
108 </template>
109
110 <script lang="ts" setup>
115 -import {
116 - NButton,
117 - NIcon,
118 - NImage,
119 - NImageGroup,
120 - NSelect,
121 - NModal,
122 - NInput,
123 - NUpload,
124 - NUploadDragger,
125 - NText,
126 - NP
127 -} from "naive-ui"
128 -import AddIcon from "@vicons/fluent/NotebookAdd24Regular"
129 -import ImageIcon from "@vicons/carbon/Image"
111 +import { NButton, NImage, NImageGroup, NSelect, NModal, NInput, NUpload, NUploadDragger, NText, NP } from "naive-ui"
112 +import Icon from "@/components/common/Icon.vue"
113 +
114 import { type Note, getNotes, labels } from "@/mock/notes"
115 import { type Ref, ref, computed } from "vue"
116 import _clone from "lodash/cloneDeep"
117 import dayjs from "@/utils/dayjs"
118 import { useThemeStore } from "@/stores/theme"
119
120 +const AddIcon = "fluent:notebook-add-24-regular"
121 +const ImageIcon = "carbon:image"
122 +
123 const notes: Ref<Note[]> = ref(getNotes())
124 const options = labels.map(l => ({
125 label: l.title,
@@ -203,10 +190,10 @@ function save(note: Note) {
190
191 .n-select {
192 :deep(.n-base-selection__border) {
206 - border-color: rgba(var(--fg-color-rgb), 0.2);
193 + border-color: var(--divider-020-color);
194 }
195 :deep(.n-base-selection-tags) {
209 - background-color: rgba(var(--bg-color-rgb), 0.7);
196 + background-color: var(--bg-secondary-color);
197 }
198 }
199 }
src/views/Auth/Login.vue
+42 -39
@@ -1,55 +1,55 @@
1 <template>
2 <div class="page">
3 - <div class="settings flex items-center justify-between">
3 + <div class="settings flex items-center justify-between" v-if="!isLogged">
4 <div class="layout">
5 <n-button quaternary circle @click="align = 'left'">
6 <template #icon>
7 - <n-icon>
8 - <AlignLeftActive v-if="align === 'left'" />
9 - <AlignLeft v-else />
10 - </n-icon>
7 + <Icon>
8 + <Iconify :icon="AlignLeftActive" v-if="align === 'left'" />
9 + <Iconify :icon="AlignLeft" v-else />
10 + </Icon>
11 </template>
12 </n-button>
13 <n-button quaternary circle @click="align = 'center'">
14 <template #icon>
15 - <n-icon>
16 - <AlignCenterActive v-if="align === 'center'" />
17 - <AlignCenter v-else />
18 - </n-icon>
15 + <Icon>
16 + <Iconify :icon="AlignCenterActive" v-if="align === 'center'" />
17 + <Iconify :icon="AlignCenter" v-else />
18 + </Icon>
19 </template>
20 </n-button>
21 <n-button quaternary circle @click="align = 'right'">
22 <template #icon>
23 - <n-icon>
24 - <AlignRightActive v-if="align === 'right'" />
25 - <AlignRight v-else />
26 - </n-icon>
23 + <Icon>
24 + <Iconify :icon="AlignRightActive" v-if="align === 'right'" />
25 + <Iconify :icon="AlignRight" v-else />
26 + </Icon>
27 </template>
28 </n-button>
29 </div>
30 <div class="colors">
31 <n-button quaternary circle v-for="color of colors" :key="color" @click="activeColor = color">
32 <template #icon>
33 - <n-icon :color="color">
34 - <SquareActive v-if="activeColor === color" />
35 - <Square v-else />
36 - </n-icon>
33 + <Icon :color="color">
34 + <Iconify :icon="SquareActive" v-if="activeColor === color" />
35 + <Iconify :icon="Square" v-else />
36 + </Icon>
37 </template>
38 </n-button>
39 <n-button quaternary circle @click="activeColor = primaryColor">
40 <template #icon>
41 - <n-icon :color="primaryColor">
42 - <SquareActive v-if="activeColor === primaryColor" />
43 - <Square v-else />
44 - </n-icon>
41 + <Icon :color="primaryColor">
42 + <Iconify :icon="SquareActive" v-if="activeColor === primaryColor" />
43 + <Iconify :icon="Square" v-else />
44 + </Icon>
45 </template>
46 </n-button>
47 </div>
48 </div>
49 - <div class="flex wrapper justify-center">
49 + <div class="flex wrapper justify-center" v-if="!isLogged">
50 <div class="image-box basis-2/3" v-if="align === 'right'"></div>
51 <div class="form-box basis-1/3 flex items-center justify-center" :class="{ centered: align === 'center' }">
52 - <SignInUp :type="type" />
52 + <AuthForm :type="type" />
53 </div>
54 <div class="image-box basis-2/3" v-if="align === 'left'"></div>
55 </div>
@@ -57,24 +57,25 @@
57 </template>
58
59 <script lang="ts" setup>
60 -import { NButton, NIcon } from "naive-ui"
61 -import SignInUp from "@/components/SignInUp/index.vue"
62 -import AlignLeft from "@vicons/fluent/TextboxAlignBottomRotate9024Regular"
63 -import AlignCenter from "@vicons/fluent/TextboxAlignMiddleRotate9024Regular"
64 -import AlignRight from "@vicons/fluent/TextboxAlignTopRotate9024Regular"
65 -import AlignLeftActive from "@vicons/fluent/TextboxAlignBottomRotate9024Filled"
66 -import AlignCenterActive from "@vicons/fluent/TextboxAlignMiddleRotate9024Filled"
67 -import AlignRightActive from "@vicons/fluent/TextboxAlignTopRotate9024Filled"
68 -import Square from "@vicons/fluent/Square24Filled"
69 -import SquareActive from "@vicons/fluent/CheckboxIndeterminate24Regular"
60 +import { NButton } from "naive-ui"
61 +import Icon from "@/components/common/Icon.vue"
62 +import { Icon as Iconify } from "@iconify/vue"
63 +
64 +import AuthForm from "@/components/AuthForm/index.vue"
65 import { ref, computed, onBeforeMount } from "vue"
66 import { useRoute } from "vue-router"
67 import { useThemeStore } from "@/stores/theme"
73 -import { type FormType } from "@/components/SignInUp/index.vue"
68 +import { useAuthStore } from "@/stores/auth"
69 +import type { FormType } from "@/components/AuthForm/index.vue"
70
75 -defineOptions({
76 - name: "Login"
77 -})
71 +const AlignLeft = "fluent:textbox-align-bottom-rotate-90-24-regular"
72 +const AlignCenter = "fluent:textbox-align-middle-rotate-90-24-regular"
73 +const AlignRight = "fluent:textbox-align-top-rotate-90-24-regular"
74 +const AlignLeftActive = "fluent:textbox-align-bottom-rotate-90-24-filled"
75 +const AlignCenterActive = "fluent:textbox-align-middle-rotate-90-24-filled"
76 +const AlignRightActive = "fluent:textbox-align-top-rotate-90-24-filled"
77 +const Square = "fluent:square-24-filled"
78 +const SquareActive = "fluent:checkbox-indeterminate-24-regular"
79
80 type Align = "left" | "center" | "right"
81
@@ -85,6 +86,7 @@ const type = ref<FormType | undefined>(undefined)
86
87 const colors = computed(() => useThemeStore().secondaryColors)
88 const primaryColor = computed(() => useThemeStore().primaryColor)
89 +const isLogged = computed(() => useAuthStore().isLogged)
90
91 onBeforeMount(() => {
92 if (route.query.step) {
@@ -96,6 +98,8 @@ onBeforeMount(() => {
98 </script>
99
100 <style lang="scss" scoped>
101 +@import "@/assets/scss/common.scss";
102 +
103 .page {
104 min-height: 100vh;
105
@@ -104,8 +108,7 @@ onBeforeMount(() => {
108 top: 10px;
109 left: 50%;
110 transform: translateX(-50%);
107 - backdrop-filter: blur(10px);
108 - background-color: rgba(var(--bg-color-rgb), 0.4);
111 + background-color: var(--bg-secondary-color);
112 height: 44px;
113 width: 300px;
114 border-radius: 50px;
src/views/Cards/Combo.vue
+46 -52
@@ -4,18 +4,18 @@
4 <div class="card-wrap md:basis-1/2 basis-full">
5 <CardCombo1 title="Sales" class="h-full">
6 <template #icon>
7 - <CardComboIcon boxed>
8 - <SalesIcon />
9 - </CardComboIcon>
7 + <CardComboIcon :iconName="SalesIcon" boxed></CardComboIcon>
8 </template>
9 </CardCombo1>
10 </div>
11 <div class="card-wrap md:basis-1/2 basis-full">
12 <CardCombo1 title="Subscribers" type="bar" class="h-full" :chartColor="style['--secondary1-color']">
13 <template #icon>
16 - <CardComboIcon boxed :color="style['--secondary1-color']">
17 - <SubscribersIcon />
18 - </CardComboIcon>
14 + <CardComboIcon
15 + :iconName="SubscribersIcon"
16 + boxed
17 + :color="style['--secondary1-color']"
18 + ></CardComboIcon>
19 </template>
20 </CardCombo1>
21 </div>
@@ -30,9 +30,7 @@
30 :style="`background-color: ${style['--secondary2-color']}`"
31 >
32 <template #icon>
33 - <CardComboIcon boxed :boxSize="50" :color="'white'">
34 - <ReportsIcon />
35 - </CardComboIcon>
33 + <CardComboIcon :iconName="ReportsIcon" boxed :boxSize="50" :color="'white'"></CardComboIcon>
34 </template>
35 </CardCombo2>
36 </div>
@@ -40,16 +38,17 @@
38 <div class="flex flex-col gap-5 w-full">
39 <CardCombo2 title="Issues" horizontal>
40 <template #icon>
43 - <CardComboIcon boxed :boxSize="50" :color="style['--secondary4-color']">
44 - <ErrorIcon />
45 - </CardComboIcon>
41 + <CardComboIcon
42 + :iconName="ErrorIcon"
43 + boxed
44 + :boxSize="50"
45 + :color="style['--secondary4-color']"
46 + ></CardComboIcon>
47 </template>
48 </CardCombo2>
49 <CardCombo2 title="Completed" horizontal>
50 <template #icon>
50 - <CardComboIcon boxed :boxSize="50">
51 - <CompletedIcon />
52 - </CardComboIcon>
51 + <CardComboIcon :iconName="CompletedIcon" boxed :boxSize="50"></CardComboIcon>
52 </template>
53 </CardCombo2>
54 </div>
@@ -60,16 +59,20 @@
59 <div class="flex flex-col gap-5 w-full">
60 <CardCombo2 title="Pending" horizontal>
61 <template #icon>
63 - <CardComboIcon :boxSize="50" :color="style['--secondary3-color']">
64 - <PendingIcon />
65 - </CardComboIcon>
62 + <CardComboIcon
63 + :iconName="PendingIcon"
64 + :boxSize="50"
65 + :color="style['--secondary3-color']"
66 + ></CardComboIcon>
67 </template>
68 </CardCombo2>
69 <CardCombo2 title="Shipped" horizontal>
70 <template #icon>
70 - <CardComboIcon :boxSize="50" :color="style['--secondary1-color']">
71 - <ShippedIcon />
72 - </CardComboIcon>
71 + <CardComboIcon
72 + :iconName="ShippedIcon"
73 + :boxSize="50"
74 + :color="style['--secondary1-color']"
75 + ></CardComboIcon>
76 </template>
77 </CardCombo2>
78 </div>
@@ -77,9 +80,7 @@
80 <div class="flex lg:basis-1/2 basis-full">
81 <CardCombo2 title="Earned" centered class="h-full" currency="USD">
82 <template #icon>
80 - <CardComboIcon :boxSize="50">
81 - <RevenueIcon />
82 - </CardComboIcon>
83 + <CardComboIcon :iconName="RevenueIcon" :boxSize="50"></CardComboIcon>
84 </template>
85 </CardCombo2>
86 </div>
@@ -94,14 +95,10 @@
95 <div class="flex flex-col gap-5 md:flex-row lg:flex-col xl:flex-row">
96 <CardCombo6 cardWrap titleLeft="Computer" titleRight="Tablet" valueLeft="75.6k" valueRight="143.7k">
97 <template #iconLeft>
97 - <CardComboIcon boxed :boxSize="30">
98 - <ComputerIcon />
99 - </CardComboIcon>
98 + <CardComboIcon :iconName="ComputerIcon" boxed :boxSize="30"></CardComboIcon>
99 </template>
100 <template #iconRight>
102 - <CardComboIcon boxed :boxSize="30">
103 - <TabletIcon />
104 - </CardComboIcon>
101 + <CardComboIcon :iconName="TabletIcon" boxed :boxSize="30"></CardComboIcon>
102 </template>
103 </CardCombo6>
104
@@ -112,9 +109,7 @@
109 <div class="flex flex-col gap-5 sm:flex-row">
110 <CardCombo7 cardWrap>
111 <template #icon>
115 - <CardComboIcon boxed>
116 - <SalesIcon />
117 - </CardComboIcon>
112 + <CardComboIcon :iconName="SalesIcon" boxed></CardComboIcon>
113 </template>
114 </CardCombo7>
115
@@ -130,9 +125,10 @@
125 }"
126 >
127 <template #icon>
133 - <CardComboIcon :color="style['--secondary2-color']">
134 - <ActivityIcon />
135 - </CardComboIcon>
128 + <CardComboIcon
129 + :iconName="ActivityIcon"
130 + :color="style['--secondary2-color']"
131 + ></CardComboIcon>
132 </template>
133 </CardCombo4>
134 </div>
@@ -151,9 +147,7 @@
147 }"
148 >
149 <template #icon>
154 - <CardComboIcon boxed>
155 - <UploadsIcon />
156 - </CardComboIcon>
150 + <CardComboIcon :iconName="UploadsIcon" boxed></CardComboIcon>
151 </template>
152 </CardCombo4>
153
@@ -180,20 +174,20 @@
174 </template>
175
176 <script lang="ts" setup>
183 -import RevenueIcon from "@vicons/carbon/Money"
184 -import SubscribersIcon from "@vicons/carbon/UserMultiple"
185 -import SalesIcon from "@vicons/carbon/ShoppingCart"
186 -import ReportsIcon from "@vicons/carbon/Report"
187 -import ErrorIcon from "@vicons/carbon/Debug"
188 -import ActivityIcon from "@vicons/carbon/Activity"
189 -import UploadsIcon from "@vicons/carbon/CloudUpload"
190 -import CompletedIcon from "@vicons/carbon/CheckmarkOutline"
191 -import PendingIcon from "@vicons/carbon/Hourglass"
192 -import ShippedIcon from "@vicons/carbon/Send"
193 -import TabletIcon from "@vicons/carbon/Tablet"
194 -import ComputerIcon from "@vicons/carbon/Screen"
177 +const RevenueIcon = "carbon:money"
178 +const SubscribersIcon = "carbon:user-multiple"
179 +const SalesIcon = "carbon:shopping-cart"
180 +const ReportsIcon = "carbon:report"
181 +const ErrorIcon = "carbon:debug"
182 +const ActivityIcon = "carbon:activity"
183 +const UploadsIcon = "carbon:cloud-upload"
184 +const CompletedIcon = "carbon:checkmark-outline"
185 +const PendingIcon = "carbon:hourglass"
186 +const ShippedIcon = "carbon:send"
187 +const TabletIcon = "carbon:tablet"
188 +const ComputerIcon = "carbon:screen"
189 import { computed } from "vue"
190 import { useThemeStore } from "@/stores/theme"
191
198 -const style: { [key: string]: any } = computed(() => useThemeStore().style)
192 +const style = computed<{ [key: string]: any }>(() => useThemeStore().style)
193 </script>
src/views/Cards/List.vue
+3 -4
@@ -21,12 +21,11 @@
21 </CardActions>
22 </CardWrapper>
23
24 - <CardWrapper v-slot="{ expand, isExpand, reload, getState }">
24 + <CardWrapper v-slot="{ expand, isExpand, reload }">
25 <CardActions
26 :expand="expand"
27 :isExpand="isExpand"
28 :reload="reload"
29 - :getState="getState"
29 :segmented="true"
30 :hideSubtitle="true"
31 >
@@ -168,11 +167,11 @@
167 <script setup lang="ts">
168 import { NButton } from "naive-ui"
169 import { computed } from "vue"
171 -import DemoChart from "@/components/charts/Apex.vue"
170 +import DemoChart from "@/components/charts/DemoApex.vue"
171 import DemoList from "@/components/list/List.vue"
172 import { useThemeStore } from "@/stores/theme"
173
175 -const style: { [key: string]: any } = computed(() => useThemeStore().style)
174 +const style = computed<{ [key: string]: any }>(() => useThemeStore().style)
175 const textColor = computed<string>(() => style.value["--fg-color"])
176 const textSecondaryColor = computed<string>(() => style.value["--fg-secondary-color"])
177 </script>
src/views/Charts/ApexCharts.vue
+38 -26
@@ -3,33 +3,26 @@
3 <div class="page-header">
4 <div class="title">ApexCharts</div>
5 <div class="links">
6 - <a
7 - href="https://apexcharts.com/"
8 - target="_blank"
9 - alt="docs"
10 - rel="nofollow noopener noreferrer"
11 - class="ml-4"
12 - >
13 - <n-icon :size="16">
14 - <ExternalIcon />
15 - </n-icon>
6 + <a href="https://apexcharts.com/" target="_blank" alt="docs" rel="nofollow noopener noreferrer">
7 + <Icon :name="ExternalIcon" :size="16" />
8 docs
9 </a>
10 </div>
11 </div>
12
13 <div class="components-list">
22 - <Brush />
23 - <Realtime />
24 - <Sync />
25 - <Column />
26 - <Bar />
14 + <n-spin v-if="!mounted" class="w-full h-full"></n-spin>
15 + <Brush v-if="mounted" />
16 + <Realtime v-if="mounted" />
17 + <Sync v-if="mounted" />
18 + <Column v-if="mounted" />
19 + <Bar v-if="mounted" />
20 <div class="flex lg:flex-row flex-col gap-5">
21 <div class="lg:basis-1/2 basis-full lg:min-h-full">
29 - <Pie />
22 + <Pie v-if="mounted" />
23 </div>
24 <div class="lg:basis-1/2 basis-full lg:min-h-full">
32 - <Radar />
25 + <Radar v-if="mounted" />
26 </div>
27 </div>
28 </div>
@@ -37,15 +30,34 @@
30 </template>
31
32 <script lang="ts" setup>
40 -import { NIcon } from "naive-ui"
41 -import ExternalIcon from "@vicons/tabler/ExternalLink"
42 -import Brush from "./apex-charts-components/Brush.vue"
43 -import Realtime from "./apex-charts-components/Realtime.vue"
44 -import Sync from "./apex-charts-components/Sync.vue"
45 -import Column from "./apex-charts-components/Column.vue"
46 -import Bar from "./apex-charts-components/Bar.vue"
47 -import Pie from "./apex-charts-components/Pie.vue"
48 -import Radar from "./apex-charts-components/Radar.vue"
33 +import { NSpin } from "naive-ui"
34 +import { ref, defineAsyncComponent, onMounted } from "vue"
35 +
36 +import Icon from "@/components/common/Icon.vue"
37 +const ExternalIcon = "tabler:external-link"
38 +
39 +import { useThemeStore } from "@/stores/theme"
40 +const Brush = defineAsyncComponent(() => import("@/components/charts/demo-pages/apex-charts-components/Brush.vue"))
41 +const Realtime = defineAsyncComponent(
42 + () => import("@/components/charts/demo-pages/apex-charts-components/Realtime.vue")
43 +)
44 +const Sync = defineAsyncComponent(() => import("@/components/charts/demo-pages/apex-charts-components/Sync.vue"))
45 +const Column = defineAsyncComponent(() => import("@/components/charts/demo-pages/apex-charts-components/Column.vue"))
46 +const Bar = defineAsyncComponent(() => import("@/components/charts/demo-pages/apex-charts-components/Bar.vue"))
47 +const Pie = defineAsyncComponent(() => import("@/components/charts/demo-pages/apex-charts-components/Pie.vue"))
48 +const Radar = defineAsyncComponent(() => import("@/components/charts/demo-pages/apex-charts-components/Radar.vue"))
49 +const mounted = ref(false)
50 +const themeStore = useThemeStore()
51 +
52 +onMounted(() => {
53 + const duration = 1000 * themeStore.routerTransitionDuration
54 + const gap = 500
55 +
56 + // TIMEOUT REQUIRED BY PAGE ANIMATION
57 + setTimeout(() => {
58 + mounted.value = true
59 + }, duration + gap)
60 +})
61 </script>
62
63 <style scoped lang="scss">
src/views/Charts/ChartJS.vue
+7 -14
@@ -3,16 +3,8 @@
3 <div class="page-header">
4 <div class="title">ChartJS</div>
5 <div class="links">
6 - <a
7 - href="https://vue-chartjs.org/"
8 - target="_blank"
9 - alt="docs"
10 - rel="nofollow noopener noreferrer"
11 - class="ml-4"
12 - >
13 - <n-icon :size="16">
14 - <ExternalIcon />
15 - </n-icon>
6 + <a href="https://vue-chartjs.org/" target="_blank" alt="docs" rel="nofollow noopener noreferrer">
7 + <Icon :name="ExternalIcon" :size="16" />
8 docs
9 </a>
10 </div>
@@ -26,10 +18,11 @@
18 </template>
19
20 <script lang="ts" setup>
29 -import { NIcon } from "naive-ui"
30 -import ExternalIcon from "@vicons/tabler/ExternalLink"
31 -import Bar from "./chartjs-components/Bar.vue"
32 -import Line from "./chartjs-components/Line.vue"
21 +import Icon from "@/components/common/Icon.vue"
22 +const ExternalIcon = "tabler:external-link"
23 +
24 +import Bar from "@/components/charts/demo-pages/chartjs-components/Bar.vue"
25 +import Line from "@/components/charts/demo-pages/chartjs-components/Line.vue"
26 </script>
27
28 <style scoped lang="scss">
src/views/Components/Affix.vue
+4 -6
@@ -8,11 +8,8 @@
8 target="_blank"
9 alt="docs"
10 rel="nofollow noopener noreferrer"
11 - class="ml-4"
11 >
13 - <n-icon :size="16">
14 - <ExternalIcon />
15 - </n-icon>
12 + <Icon :name="ExternalIcon" :size="16" />
13 docs
14 </a>
15 </div>
@@ -101,8 +98,9 @@
98 </template>
99
100 <script lang="ts" setup>
104 -import { NIcon, NAffix, NText, NTag } from "naive-ui"
105 -import ExternalIcon from "@vicons/tabler/ExternalLink"
101 +import { NAffix, NText, NTag } from "naive-ui"
102 +import Icon from "@/components/common/Icon.vue"
103 +const ExternalIcon = "tabler:external-link"
104 import { ref } from "vue"
105
106 const containerRef = ref<HTMLElement | undefined>(undefined)
src/views/Components/Alert.vue
+6 -10
@@ -8,11 +8,8 @@
8 target="_blank"
9 alt="docs"
10 rel="nofollow noopener noreferrer"
11 - class="ml-4"
11 >
13 - <n-icon :size="16">
14 - <ExternalIcon />
15 - </n-icon>
12 + <Icon :name="ExternalIcon" :size="16" />
13 docs
14 </a>
15 </div>
@@ -23,9 +20,7 @@
20 <n-space vertical :size="12">
21 <n-alert title="Default Text" type="default">
22 <template #icon>
26 - <n-icon>
27 - <Airplane />
28 - </n-icon>
23 + <Icon :name="Airplane" />
24 </template>
25 Gee it's good to be back home
26 </n-alert>
@@ -62,8 +57,9 @@
57 </template>
58
59 <script lang="ts" setup>
65 -import { NIcon, NSpace, NAlert } from "naive-ui"
66 -import ExternalIcon from "@vicons/tabler/ExternalLink"
60 +import { NSpace, NAlert } from "naive-ui"
61 +import Icon from "@/components/common/Icon.vue"
62 +const ExternalIcon = "tabler:external-link"
63
68 -import { Airplane } from "@vicons/ionicons5"
64 +const Airplane = "ion:airplane"
65 </script>
src/views/Components/Anchor.vue
+4 -6
@@ -8,11 +8,8 @@
8 target="_blank"
9 alt="docs"
10 rel="nofollow noopener noreferrer"
11 - class="ml-4"
11 >
13 - <n-icon :size="16">
14 - <ExternalIcon />
15 - </n-icon>
12 + <Icon :name="ExternalIcon" :size="16" />
13 docs
14 </a>
15 </div>
@@ -67,8 +64,9 @@
64 </template>
65
66 <script lang="ts" setup>
70 -import { NIcon, NSpace, NSwitch, NAnchor, NAnchorLink } from "naive-ui"
71 -import ExternalIcon from "@vicons/tabler/ExternalLink"
67 +import { NSpace, NSwitch, NAnchor, NAnchorLink } from "naive-ui"
68 +import Icon from "@/components/common/Icon.vue"
69 +const ExternalIcon = "tabler:external-link"
70 import { ref } from "vue"
71
72 const showRail = ref(true)
src/views/Components/AutoComplete.vue
+4 -6
@@ -8,11 +8,8 @@
8 target="_blank"
9 alt="docs"
10 rel="nofollow noopener noreferrer"
11 - class="ml-4"
11 >
13 - <n-icon :size="16">
14 - <ExternalIcon />
15 - </n-icon>
12 + <Icon :name="ExternalIcon" :size="16" />
13 docs
14 </a>
15 </div>
@@ -105,8 +102,9 @@
102 </template>
103
104 <script lang="ts" setup>
108 -import { NIcon, NAutoComplete } from "naive-ui"
109 -import ExternalIcon from "@vicons/tabler/ExternalLink"
105 +import { NAutoComplete } from "naive-ui"
106 +import Icon from "@/components/common/Icon.vue"
107 +const ExternalIcon = "tabler:external-link"
108 import { computed, ref } from "vue"
109
110 const value = ref("")
src/views/Components/Avatar.vue
+4 -6
@@ -8,11 +8,8 @@
8 target="_blank"
9 alt="docs"
10 rel="nofollow noopener noreferrer"
11 - class="ml-4"
11 >
13 - <n-icon :size="16">
14 - <ExternalIcon />
15 - </n-icon>
12 + <Icon :name="ExternalIcon" :size="16" />
13 docs
14 </a>
15 </div>
@@ -145,8 +142,9 @@
142 </template>
143
144 <script lang="ts" setup>
148 -import { NIcon, NAvatar, NBadge, NAvatarGroup, NText } from "naive-ui"
149 -import ExternalIcon from "@vicons/tabler/ExternalLink"
145 +import { NAvatar, NBadge, NAvatarGroup, NText } from "naive-ui"
146 +import Icon from "@/components/common/Icon.vue"
147 +const ExternalIcon = "tabler:external-link"
148
149 const options = [
150 {
src/views/Components/BackTop.vue
+4 -6
@@ -8,11 +8,8 @@
8 target="_blank"
9 alt="docs"
10 rel="nofollow noopener noreferrer"
11 - class="ml-4"
11 >
13 - <n-icon :size="16">
14 - <ExternalIcon />
15 - </n-icon>
12 + <Icon :name="ExternalIcon" :size="16" />
13 docs
14 </a>
15 </div>
@@ -35,6 +32,7 @@
32 </template>
33
34 <script lang="ts" setup>
38 -import { NIcon, NBackTop } from "naive-ui"
39 -import ExternalIcon from "@vicons/tabler/ExternalLink"
35 +import { NBackTop } from "naive-ui"
36 +import Icon from "@/components/common/Icon.vue"
37 +const ExternalIcon = "tabler:external-link"
38 </script>
src/views/Components/Badge.vue
+9 -9
@@ -8,11 +8,8 @@
8 target="_blank"
9 alt="docs"
10 rel="nofollow noopener noreferrer"
11 - class="ml-4"
11 >
13 - <n-icon :size="16">
14 - <ExternalIcon />
15 - </n-icon>
12 + <Icon :name="ExternalIcon" :size="16" />
13 docs
14 </a>
15 </div>
@@ -30,12 +27,12 @@
27 <n-button-group>
28 <n-button @click="value = Math.min(16, value + 1)">
29 <template #icon>
33 - <n-icon><add /></n-icon>
30 + <Icon :name="Add" />
31 </template>
32 </n-button>
33 <n-button @click="value = Math.max(0, value - 1)">
34 <template #icon>
38 - <n-icon><remove /></n-icon>
35 + <Icon :name="Remove" />
36 </template>
37 </n-button>
38 </n-button-group>
@@ -137,11 +134,14 @@
134 </template>
135
136 <script lang="ts" setup>
140 -import { NIcon, NSpace, NBadge, NAvatar, NButtonGroup, NButton, NText, NCheckbox } from "naive-ui"
141 -import ExternalIcon from "@vicons/tabler/ExternalLink"
137 +import { NSpace, NBadge, NAvatar, NButtonGroup, NButton, NText, NCheckbox } from "naive-ui"
138 +import Icon from "@/components/common/Icon.vue"
139 +const ExternalIcon = "tabler:external-link"
140 import { ref } from "vue"
141
144 -import { Add, Remove } from "@vicons/ionicons5"
142 +const Add = "ion:add"
143 +const Remove = "ion:remove"
144 +
145 const value = ref(5)
146 const raw = ref(false)
147 const processing = ref(false)
src/views/Components/Breadcrumb.vue
+8 -10
@@ -8,11 +8,8 @@
8 target="_blank"
9 alt="docs"
10 rel="nofollow noopener noreferrer"
11 - class="ml-4"
11 >
13 - <n-icon :size="16">
14 - <ExternalIcon />
15 - </n-icon>
12 + <Icon :name="ExternalIcon" :size="16" />
13 docs
14 </a>
15 </div>
@@ -22,15 +19,15 @@
19 <CardCodeExample title="Basic">
20 <n-breadcrumb>
21 <n-breadcrumb-item>
25 - <n-icon :component="Cash" />
22 + <Icon :name="Cash" />
23 Home
24 </n-breadcrumb-item>
25 <n-breadcrumb-item>
29 - <n-icon :component="Cash" />
26 + <Icon :name="Cash" />
27 Account
28 </n-breadcrumb-item>
29 <n-breadcrumb-item>
33 - <n-icon :component="Cash" />
30 + <Icon :name="Cash" />
31 Category
32 </n-breadcrumb-item>
33 </n-breadcrumb>
@@ -128,10 +125,11 @@
125 </template>
126
127 <script lang="ts" setup>
131 -import { NIcon, NBreadcrumb, NBreadcrumbItem, NText, NDropdown } from "naive-ui"
132 -import ExternalIcon from "@vicons/tabler/ExternalLink"
128 +import { NBreadcrumb, NBreadcrumbItem, NText, NDropdown } from "naive-ui"
129 +import Icon from "@/components/common/Icon.vue"
130 +const ExternalIcon = "tabler:external-link"
131
134 -import { Cash } from "@vicons/ionicons5"
132 +const Cash = "ion:cash"
133
134 const options1 = [
135 {
src/views/Components/Button.vue
+28 -30
@@ -8,11 +8,8 @@
8 target="_blank"
9 alt="docs"
10 rel="nofollow noopener noreferrer"
11 - class="ml-4"
11 >
13 - <n-icon :size="16">
14 - <ExternalIcon />
15 - </n-icon>
12 + <Icon :name="ExternalIcon" :size="16" />
13 docs
14 </a>
15 </div>
@@ -77,32 +74,32 @@
74 <n-button strong secondary round type="error">Error</n-button>
75 <n-button strong secondary circle>
76 <template #icon>
80 - <n-icon><cash-icon /></n-icon>
77 + <Icon :name="CashIcon" />
78 </template>
79 </n-button>
80 <n-button strong secondary circle type="primary">
81 <template #icon>
85 - <n-icon><cash-icon /></n-icon>
82 + <Icon :name="CashIcon" />
83 </template>
84 </n-button>
85 <n-button strong secondary circle type="info">
86 <template #icon>
90 - <n-icon><cash-icon /></n-icon>
87 + <Icon :name="CashIcon" />
88 </template>
89 </n-button>
90 <n-button strong secondary circle type="success">
91 <template #icon>
95 - <n-icon><cash-icon /></n-icon>
92 + <Icon :name="CashIcon" />
93 </template>
94 </n-button>
95 <n-button strong secondary circle type="warning">
96 <template #icon>
100 - <n-icon><cash-icon /></n-icon>
97 + <Icon :name="CashIcon" />
98 </template>
99 </n-button>
100 <n-button strong secondary circle type="error">
101 <template #icon>
105 - <n-icon><cash-icon /></n-icon>
102 + <Icon :name="CashIcon" />
103 </template>
104 </n-button>
105 </n-space>
@@ -214,7 +211,7 @@
211 <n-space>
212 <n-button circle>
213 <template #icon>
217 - <n-icon><cash-icon /></n-icon>
214 + <Icon :name="CashIcon" />
215 </template>
216 </n-button>
217 <n-button round>Round</n-button>
@@ -238,9 +235,7 @@
235 <n-space>
236 <n-button :loading="loading" @click="handleClick">
237 <template #icon>
241 - <n-icon>
242 - <cash-icon />
243 - </n-icon>
238 + <Icon :name="CashIcon" />
239 </template>
240 Click Me
241 </n-button>
@@ -278,19 +273,19 @@
273 <n-button-group vertical>
274 <n-button round>
275 <template #icon>
281 - <n-icon><log-in-icon /></n-icon>
276 + <Icon :name="LogInIcon" />
277 </template>
278 Live a
279 </n-button>
280 <n-button ghost>
281 <template #icon>
287 - <n-icon><log-in-icon /></n-icon>
282 + <Icon :name="LogInIcon" />
283 </template>
284 Sufficient
285 </n-button>
286 <n-button>
287 <template #icon>
293 - <n-icon><log-in-icon /></n-icon>
288 + <Icon :name="LogInIcon" />
289 </template>
290 Life
291 </n-button>
@@ -298,19 +293,19 @@
293 <n-button-group vertical size="large">
294 <n-button>
295 <template #icon>
301 - <n-icon><log-in-icon /></n-icon>
296 + <Icon :name="LogInIcon" />
297 </template>
298 With
299 </n-button>
300 <n-button>
301 <template #icon>
307 - <n-icon><log-in-icon /></n-icon>
302 + <Icon :name="LogInIcon" />
303 </template>
304 Enough
305 </n-button>
306 <n-button ghost round>
307 <template #icon>
313 - <n-icon><log-in-icon /></n-icon>
308 + <Icon :name="LogInIcon" />
309 </template>
310 Happiness
311 </n-button>
@@ -318,19 +313,19 @@
313 <n-button-group size="small">
314 <n-button round>
315 <template #icon>
321 - <n-icon><log-in-icon /></n-icon>
316 + <Icon :name="LogInIcon" />
317 </template>
318 Life
319 </n-button>
320 <n-button>
321 <template #icon>
327 - <n-icon><log-in-icon /></n-icon>
322 + <Icon :name="LogInIcon" />
323 </template>
324 Is
325 </n-button>
326 <n-button>
327 <template #icon>
333 - <n-icon><log-in-icon /></n-icon>
328 + <Icon :name="LogInIcon" />
329 </template>
330 Good
331 </n-button>
@@ -338,19 +333,19 @@
333 <n-button-group>
334 <n-button ghost>
335 <template #icon>
341 - <n-icon><log-in-icon /></n-icon>
336 + <Icon :name="LogInIcon" />
337 </template>
338 Eat
339 </n-button>
340 <n-button ghost>
341 <template #icon>
347 - <n-icon><log-in-icon /></n-icon>
342 + <Icon :name="LogInIcon" />
343 </template>
344 One More
345 </n-button>
346 <n-button round>
347 <template #icon>
353 - <n-icon><log-in-icon /></n-icon>
348 + <Icon :name="LogInIcon" />
349 </template>
350 Apple
351 </n-button>
@@ -446,10 +441,13 @@
441 </template>
442
443 <script lang="ts" setup>
449 -import { NIcon, NButton, NButtonGroup, NSpace, NText } from "naive-ui"
450 -import ExternalIcon from "@vicons/tabler/ExternalLink"
451 -import { LogInOutline as LogInIcon } from "@vicons/ionicons5"
452 -import { CashOutline as CashIcon } from "@vicons/ionicons5"
444 +import { NButton, NButtonGroup, NSpace, NText } from "naive-ui"
445 +import Icon from "@/components/common/Icon.vue"
446 +const ExternalIcon = "tabler:external-link"
447 +
448 +const LogInIcon = "ion:log-in-outline"
449 +const CashIcon = "ion:cash-outline"
450 +
451 import { ref } from "vue"
452
453 const loading = ref(false)
src/views/Components/Calendar.vue
+4 -6
@@ -8,11 +8,8 @@
8 target="_blank"
9 alt="docs"
10 rel="nofollow noopener noreferrer"
11 - class="ml-4"
11 >
13 - <n-icon :size="16">
14 - <ExternalIcon />
15 - </n-icon>
12 + <Icon :name="ExternalIcon" :size="16" />
13 docs
14 </a>
15 </div>
@@ -51,8 +48,9 @@
48 </template>
49
50 <script lang="ts" setup>
54 -import { NIcon, NCalendar, useMessage } from "naive-ui"
55 -import ExternalIcon from "@vicons/tabler/ExternalLink"
51 +import { NCalendar, useMessage } from "naive-ui"
52 +import Icon from "@/components/common/Icon.vue"
53 +const ExternalIcon = "tabler:external-link"
54
55 const message = useMessage()
56
src/views/Components/Card.vue
+7 -11
@@ -8,17 +8,12 @@
8 target="_blank"
9 alt="docs"
10 rel="nofollow noopener noreferrer"
11 - class="ml-4"
11 >
13 - <n-icon :size="16">
14 - <ExternalIcon />
15 - </n-icon>
12 + <Icon :name="ExternalIcon" :size="16" />
13 docs
14 </a>
18 - <router-link :to="{ name: 'cards-basic' }">
19 - <n-icon :size="16">
20 - <LinkIcon />
21 - </n-icon>
15 + <router-link :to="{ name: 'Cards-Basic' }">
16 + <Icon :name="LinkIcon" :size="16" />
17 more cards
18 </router-link>
19 </div>
@@ -98,8 +93,9 @@
93 </template>
94
95 <script lang="ts" setup>
101 -import { NIcon, NCard, NSpace, NText } from "naive-ui"
102 -import ExternalIcon from "@vicons/tabler/ExternalLink"
103 -import LinkIcon from "@vicons/carbon/Link"
96 +import { NCard, NSpace, NText } from "naive-ui"
97 +import Icon from "@/components/common/Icon.vue"
98 +const ExternalIcon = "tabler:external-link"
99 +const LinkIcon = "carbon:link"
100 import { RouterLink } from "vue-router"
101 </script>
src/views/Components/Carousel.vue
+4 -6
@@ -8,11 +8,8 @@
8 target="_blank"
9 alt="docs"
10 rel="nofollow noopener noreferrer"
11 - class="ml-4"
11 >
13 - <n-icon :size="16">
14 - <ExternalIcon />
15 - </n-icon>
12 + <Icon :name="ExternalIcon" :size="16" />
13 docs
14 </a>
15 </div>
@@ -180,8 +177,9 @@
177 </template>
178
179 <script lang="ts" setup>
183 -import { NIcon, NCarousel, NText } from "naive-ui"
184 -import ExternalIcon from "@vicons/tabler/ExternalLink"
180 +import { NCarousel, NText } from "naive-ui"
181 +import Icon from "@/components/common/Icon.vue"
182 +const ExternalIcon = "tabler:external-link"
183 </script>
184
185 <style scoped>
src/views/Components/Cascader.vue
+4 -6
@@ -8,11 +8,8 @@
8 target="_blank"
9 alt="docs"
10 rel="nofollow noopener noreferrer"
11 - class="ml-4"
11 >
13 - <n-icon :size="16">
14 - <ExternalIcon />
15 - </n-icon>
12 + <Icon :name="ExternalIcon" :size="16" />
13 docs
14 </a>
15 </div>
@@ -179,8 +176,9 @@
176 </template>
177
178 <script lang="ts" setup>
182 -import { NIcon, NSpace, NSwitch, NCascader, type CascaderOption } from "naive-ui"
183 -import ExternalIcon from "@vicons/tabler/ExternalLink"
179 +import { NSpace, NSwitch, NCascader, type CascaderOption } from "naive-ui"
180 +import Icon from "@/components/common/Icon.vue"
181 +const ExternalIcon = "tabler:external-link"
182 import { ref } from "vue"
183
184 function getOptions(depth = 3, iterator = 1, prefix = "") {
src/views/Components/Checkbox.vue
+4 -6
@@ -8,11 +8,8 @@
8 target="_blank"
9 alt="docs"
10 rel="nofollow noopener noreferrer"
11 - class="ml-4"
11 >
13 - <n-icon :size="16">
14 - <ExternalIcon />
15 - </n-icon>
12 + <Icon :name="ExternalIcon" :size="16" />
13 docs
14 </a>
15 </div>
@@ -99,8 +96,9 @@
96 </template>
97
98 <script lang="ts" setup>
102 -import { NIcon, NCheckbox, NSpace, NButton, NCheckboxGroup } from "naive-ui"
103 -import ExternalIcon from "@vicons/tabler/ExternalLink"
99 +import { NCheckbox, NSpace, NButton, NCheckboxGroup } from "naive-ui"
100 +import Icon from "@/components/common/Icon.vue"
101 +const ExternalIcon = "tabler:external-link"
102 import { ref } from "vue"
103
104 const value = ref(false)
src/views/Components/Collapse.vue
+7 -11
@@ -8,11 +8,8 @@
8 target="_blank"
9 alt="docs"
10 rel="nofollow noopener noreferrer"
11 - class="ml-4"
11 >
13 - <n-icon :size="16">
14 - <ExternalIcon />
15 - </n-icon>
12 + <Icon :name="ExternalIcon" :size="16" />
13 docs
14 </a>
15 </div>
@@ -85,12 +82,10 @@
82 <CardCodeExample title="Customize icon">
83 <n-collapse>
84 <template #header-extra>
88 - <n-icon><cash-icon /></n-icon>
85 + <Icon :name="CashIcon" />
86 </template>
87 <template #arrow>
91 - <n-icon>
92 - <cash-icon />
93 - </n-icon>
88 + <Icon :name="CashIcon" />
89 </template>
90 <n-collapse-item title="Bronze" name="1">
91 <div>good</div>
@@ -167,7 +162,8 @@
162 </template>
163
164 <script lang="ts" setup>
170 -import { NIcon, NCollapse, NCollapseItem, NText } from "naive-ui"
171 -import { CashOutline as CashIcon } from "@vicons/ionicons5"
172 -import ExternalIcon from "@vicons/tabler/ExternalLink"
165 +import { NCollapse, NCollapseItem, NText } from "naive-ui"
166 +const CashIcon = "ion:cash-outline"
167 +import Icon from "@/components/common/Icon.vue"
168 +const ExternalIcon = "tabler:external-link"
169 </script>
src/views/Components/ColorPicker.vue
+4 -6
@@ -8,11 +8,8 @@
8 target="_blank"
9 alt="docs"
10 rel="nofollow noopener noreferrer"
11 - class="ml-4"
11 >
13 - <n-icon :size="16">
14 - <ExternalIcon />
15 - </n-icon>
12 + <Icon :name="ExternalIcon" :size="16" />
13 docs
14 </a>
15 </div>
@@ -38,6 +35,7 @@
35 </template>
36
37 <script lang="ts" setup>
41 -import { NIcon, NColorPicker } from "naive-ui"
42 -import ExternalIcon from "@vicons/tabler/ExternalLink"
38 +import { NColorPicker } from "naive-ui"
39 +import Icon from "@/components/common/Icon.vue"
40 +const ExternalIcon = "tabler:external-link"
41 </script>
src/views/Components/Countdown.vue
+4 -6
@@ -8,11 +8,8 @@
8 target="_blank"
9 alt="docs"
10 rel="nofollow noopener noreferrer"
11 - class="ml-4"
11 >
13 - <n-icon :size="16">
14 - <ExternalIcon />
15 - </n-icon>
12 + <Icon :name="ExternalIcon" :size="16" />
13 docs
14 </a>
15 </div>
@@ -49,8 +46,9 @@
46 </template>
47
48 <script lang="ts" setup>
52 -import { NIcon, NSpace, NSwitch, NCountdown } from "naive-ui"
53 -import ExternalIcon from "@vicons/tabler/ExternalLink"
49 +import { NSpace, NSwitch, NCountdown } from "naive-ui"
50 +import Icon from "@/components/common/Icon.vue"
51 +const ExternalIcon = "tabler:external-link"
52 import { ref } from "vue"
53
54 const active = ref(false)
src/views/Components/DataTable.vue
+3 -6
@@ -8,11 +8,8 @@
8 target="_blank"
9 alt="docs"
10 rel="nofollow noopener noreferrer"
11 - class="ml-4"
11 >
13 - <n-icon :size="16">
14 - <ExternalIcon />
15 - </n-icon>
12 + <Icon :name="ExternalIcon" :size="16" />
13 docs
14 </a>
15 </div>
@@ -29,13 +26,13 @@
26 </template>
27
28 <script lang="ts" setup>
32 -import { NIcon } from "naive-ui"
29 import Basic from "./data-table-components/Basic.vue"
30 import Merge from "./data-table-components/Merge.vue"
31 import Sorting from "./data-table-components/Sorting.vue"
32 import Draggable from "./data-table-components/Draggable.vue"
33 import Selection from "./data-table-components/Selection.vue"
38 -import ExternalIcon from "@vicons/tabler/ExternalLink"
34 +import Icon from "@/components/common/Icon.vue"
35 +const ExternalIcon = "tabler:external-link"
36 </script>
37
38 <style lang="scss" scoped>
src/views/Components/DatePicker.vue
+4 -6
@@ -8,11 +8,8 @@
8 target="_blank"
9 alt="docs"
10 rel="nofollow noopener noreferrer"
11 - class="ml-4"
11 >
13 - <n-icon :size="16">
14 - <ExternalIcon />
15 - </n-icon>
12 + <Icon :name="ExternalIcon" :size="16" />
13 docs
14 </a>
15 </div>
@@ -165,8 +162,9 @@
162 </template>
163
164 <script lang="ts" setup>
168 -import { NIcon, NDatePicker, NSelect } from "naive-ui"
169 -import ExternalIcon from "@vicons/tabler/ExternalLink"
165 +import { NDatePicker, NSelect } from "naive-ui"
166 +import Icon from "@/components/common/Icon.vue"
167 +const ExternalIcon = "tabler:external-link"
168 import { ref, watch } from "vue"
169 import { type DatePickerType } from "naive-ui/es/date-picker/src/config"
170
src/views/Components/Descriptions.vue
+4 -6
@@ -8,11 +8,8 @@
8 target="_blank"
9 alt="docs"
10 rel="nofollow noopener noreferrer"
11 - class="ml-4"
11 >
13 - <n-icon :size="16">
14 - <ExternalIcon />
15 - </n-icon>
12 + <Icon :name="ExternalIcon" :size="16" />
13 docs
14 </a>
15 </div>
@@ -58,6 +55,7 @@
55 </template>
56
57 <script lang="ts" setup>
61 -import { NIcon, NDescriptions, NDescriptionsItem } from "naive-ui"
62 -import ExternalIcon from "@vicons/tabler/ExternalLink"
58 +import { NDescriptions, NDescriptionsItem } from "naive-ui"
59 +import Icon from "@/components/common/Icon.vue"
60 +const ExternalIcon = "tabler:external-link"
61 </script>
src/views/Components/Dialog.vue
+47 -7
@@ -8,11 +8,8 @@
8 target="_blank"
9 alt="docs"
10 rel="nofollow noopener noreferrer"
11 - class="ml-4"
11 >
13 - <n-icon :size="16">
14 - <ExternalIcon />
15 - </n-icon>
12 + <Icon :name="ExternalIcon" :size="16" />
13 docs
14 </a>
15 </div>
@@ -25,7 +22,7 @@
22 <n-button @click="handleSuccess">Success</n-button>
23 <n-button @click="handleError">Error</n-button>
24 </n-space>
28 - <template #code="{ html }">
25 + <template #code="{ html, js }">
26 {{ html(`
27 <n-space>
28 <n-button @click="handleConfirm">Confirm</n-button>
@@ -33,6 +30,48 @@
30 <n-button @click="handleError">Error</n-button>
31 </n-space>
32 `) }}
33 +
34 + {{
35 + js(`
36 + const message = useMessage()
37 + const dialog = useDialog()
38 +
39 + function handleConfirm() {
40 + dialog.warning({
41 + title: "Confirm",
42 + content: "Are you sure?",
43 + positiveText: "Sure",
44 + negativeText: "Not Sure",
45 + onPositiveClick: () => {
46 + message.success("Sure")
47 + },
48 + onNegativeClick: () => {
49 + message.error("Not Sure")
50 + }
51 + })
52 + }
53 + function handleSuccess() {
54 + dialog.success({
55 + title: "Success",
56 + content: "Cool",
57 + positiveText: "Wow!",
58 + onPositiveClick: () => {
59 + message.success("Great!")
60 + }
61 + })
62 + }
63 + function handleError() {
64 + dialog.error({
65 + title: "Error",
66 + content: "A mistake.",
67 + positiveText: "Ahhh!",
68 + onPositiveClick: () => {
69 + message.success("I knew it...")
70 + }
71 + })
72 + }
73 + `)
74 + }}
75 </template>
76 </CardCodeExample>
77 </div>
@@ -40,8 +79,9 @@
79 </template>
80
81 <script lang="ts" setup>
43 -import { NIcon, NSpace, NButton, useDialog, useMessage } from "naive-ui"
44 -import ExternalIcon from "@vicons/tabler/ExternalLink"
82 +import { NSpace, NButton, useDialog, useMessage } from "naive-ui"
83 +import Icon from "@/components/common/Icon.vue"
84 +const ExternalIcon = "tabler:external-link"
85
86 const message = useMessage()
87 const dialog = useDialog()
src/views/Components/Divider.vue
+4 -6
@@ -8,11 +8,8 @@
8 target="_blank"
9 alt="docs"
10 rel="nofollow noopener noreferrer"
11 - class="ml-4"
11 >
13 - <n-icon :size="16">
14 - <ExternalIcon />
15 - </n-icon>
12 + <Icon :name="ExternalIcon" :size="16" />
13 docs
14 </a>
15 </div>
@@ -76,6 +73,7 @@
73 </template>
74
75 <script lang="ts" setup>
79 -import { NIcon, NDivider } from "naive-ui"
80 -import ExternalIcon from "@vicons/tabler/ExternalLink"
76 +import { NDivider } from "naive-ui"
77 +import Icon from "@/components/common/Icon.vue"
78 +const ExternalIcon = "tabler:external-link"
79 </script>
src/views/Components/Drawer.vue
+4 -6
@@ -8,11 +8,8 @@
8 target="_blank"
9 alt="docs"
10 rel="nofollow noopener noreferrer"
11 - class="ml-4"
11 >
13 - <n-icon :size="16">
14 - <ExternalIcon />
15 - </n-icon>
12 + <Icon :name="ExternalIcon" :size="16" />
13 docs
14 </a>
15 </div>
@@ -276,8 +273,9 @@
273 </template>
274
275 <script lang="ts" setup>
279 -import { NIcon, NButtonGroup, NDrawer, NDrawerContent, NButton, type DrawerPlacement, NText } from "naive-ui"
280 -import ExternalIcon from "@vicons/tabler/ExternalLink"
276 +import { NButtonGroup, NDrawer, NDrawerContent, NButton, type DrawerPlacement, NText } from "naive-ui"
277 +import Icon from "@/components/common/Icon.vue"
278 +const ExternalIcon = "tabler:external-link"
279 import { ref } from "vue"
280
281 const active = ref(false)
src/views/Components/Dropdown.vue
+14 -24
@@ -8,11 +8,8 @@
8 target="_blank"
9 alt="docs"
10 rel="nofollow noopener noreferrer"
11 - class="ml-4"
11 >
13 - <n-icon :size="16">
14 - <ExternalIcon />
15 - </n-icon>
12 + <Icon :name="ExternalIcon" :size="16" />
13 docs
14 </a>
15 </div>
@@ -36,7 +33,7 @@
33
34 const renderIcon = (icon: Component) => {
35 return () => {
39 - return h(NIcon, null, {
36 + return h( null, {
37 default: () => h(icon)
38 })
39 }
@@ -108,7 +105,7 @@
105
106 const renderIcon = (icon: Component) => {
107 return () => {
111 - return h(NIcon, null, {
108 + return h( null, {
109 default: () => h(icon)
110 })
111 }
@@ -181,7 +178,7 @@
178
179 const renderIcon = (icon: Component) => {
180 return () => {
184 - return h(NIcon, null, {
181 + return h( null, {
182 default: () => h(icon)
183 })
184 }
@@ -309,7 +306,7 @@
306
307 const renderIcon = (icon: Component) => {
308 return () => {
312 - return h(NIcon, null, {
309 + return h( null, {
310 default: () => h(icon)
311 })
312 }
@@ -397,24 +394,17 @@
394 </template>
395
396 <script lang="ts" setup>
400 -import { NIcon, NDropdown, NButton, useMessage, NSpace, NText } from "naive-ui"
401 -import ExternalIcon from "@vicons/tabler/ExternalLink"
402 -import { type Component, h, nextTick, ref } from "vue"
397 +import { NDropdown, NButton, useMessage, NSpace, NText } from "naive-ui"
398 +import { nextTick, ref } from "vue"
399 +import { renderIcon } from "@/utils"
400
404 -import {
405 - PersonCircleOutline as UserIcon,
406 - Pencil as EditIcon,
407 - LogOutOutline as LogoutIcon,
408 - ArrowRedoOutline
409 -} from "@vicons/ionicons5"
401 +import Icon from "@/components/common/Icon.vue"
402 +const ExternalIcon = "tabler:external-link"
403
411 -const renderIcon = (icon: Component) => {
412 - return () => {
413 - return h(NIcon, null, {
414 - default: () => h(icon)
415 - })
416 - }
417 -}
404 +const UserIcon = "ion:person-circle-outline"
405 +const EditIcon = "ion:pencil"
406 +const LogoutIcon = "ion:log-out-outline"
407 +const ArrowRedoOutline = "ion:arrow-redo-outline"
408
409 const xRef = ref(0)
410 const yRef = ref(0)
src/views/Components/DynamicInput.vue
+4 -6
@@ -8,11 +8,8 @@
8 target="_blank"
9 alt="docs"
10 rel="nofollow noopener noreferrer"
11 - class="ml-4"
11 >
13 - <n-icon :size="16">
14 - <ExternalIcon />
15 - </n-icon>
12 + <Icon :name="ExternalIcon" :size="16" />
13 docs
14 </a>
15 </div>
@@ -131,8 +128,9 @@
128 </template>
129
130 <script lang="ts" setup>
134 -import { NIcon, NDynamicInput, NText, NSelect, NCheckbox, NInputNumber, NInput } from "naive-ui"
135 -import ExternalIcon from "@vicons/tabler/ExternalLink"
131 +import { NDynamicInput, NText, NSelect, NCheckbox, NInputNumber, NInput } from "naive-ui"
132 +import Icon from "@/components/common/Icon.vue"
133 +const ExternalIcon = "tabler:external-link"
134 import { ref } from "vue"
135 import { watch } from "vue"
136
src/views/Components/DynamicTags.vue
+4 -6
@@ -8,11 +8,8 @@
8 target="_blank"
9 alt="docs"
10 rel="nofollow noopener noreferrer"
11 - class="ml-4"
11 >
13 - <n-icon :size="16">
14 - <ExternalIcon />
15 - </n-icon>
12 + <Icon :name="ExternalIcon" :size="16" />
13 docs
14 </a>
15 </div>
@@ -34,8 +31,9 @@
31 </template>
32
33 <script lang="ts" setup>
37 -import { NIcon, NDynamicTags } from "naive-ui"
38 -import ExternalIcon from "@vicons/tabler/ExternalLink"
34 +import { NDynamicTags } from "naive-ui"
35 +import Icon from "@/components/common/Icon.vue"
36 +const ExternalIcon = "tabler:external-link"
37 import { ref } from "vue"
38
39 const tags = ref(["teacher", "programmer"])
src/views/Components/Ellipsis.vue
+4 -6
@@ -8,11 +8,8 @@
8 target="_blank"
9 alt="docs"
10 rel="nofollow noopener noreferrer"
11 - class="ml-4"
11 >
13 - <n-icon :size="16">
14 - <ExternalIcon />
15 - </n-icon>
12 + <Icon :name="ExternalIcon" :size="16" />
13 docs
14 </a>
15 </div>
@@ -119,6 +116,7 @@
116 </template>
117
118 <script lang="ts" setup>
122 -import { NIcon, NEllipsis, NText } from "naive-ui"
123 -import ExternalIcon from "@vicons/tabler/ExternalLink"
119 +import { NEllipsis, NText } from "naive-ui"
120 +import Icon from "@/components/common/Icon.vue"
121 +const ExternalIcon = "tabler:external-link"
122 </script>
src/views/Components/Empty.vue
+4 -6
@@ -8,11 +8,8 @@
8 target="_blank"
9 alt="docs"
10 rel="nofollow noopener noreferrer"
11 - class="ml-4"
11 >
13 - <n-icon :size="16">
14 - <ExternalIcon />
15 - </n-icon>
12 + <Icon :name="ExternalIcon" :size="16" />
13 docs
14 </a>
15 </div>
@@ -40,6 +37,7 @@
37 </template>
38
39 <script lang="ts" setup>
43 -import { NIcon, NEmpty, NButton } from "naive-ui"
44 -import ExternalIcon from "@vicons/tabler/ExternalLink"
40 +import { NEmpty, NButton } from "naive-ui"
41 +import Icon from "@/components/common/Icon.vue"
42 +const ExternalIcon = "tabler:external-link"
43 </script>
src/views/Components/Form.vue
+4 -7
@@ -9,11 +9,8 @@
9 target="_blank"
10 alt="docs"
11 rel="nofollow noopener noreferrer"
12 - class="ml-4"
12 >
14 - <n-icon :size="20">
15 - <ExternalIcon />
16 - </n-icon>
13 + <Icon :name="ExternalIcon" :size="16" />
14 docs
15 </a>
16 </div>
@@ -206,7 +203,6 @@
203
204 <script lang="ts" setup>
205 import {
209 - NIcon,
206 NRadioGroup,
207 NRadioButton,
208 NForm,
@@ -218,9 +214,10 @@ import {
214 type FormInst,
215 useMessage
216 } from "naive-ui"
221 -import ExternalIcon from "@vicons/tabler/ExternalLink"
217 +import Icon from "@/components/common/Icon.vue"
218 +const ExternalIcon = "tabler:external-link"
219 import { ref } from "vue"
223 -import { type LabelPlacement, type Size } from "naive-ui/es/form/src/interface"
220 +import { type LabelPlacement, type Size } from "naive-ui/es/form/src/interface"
221
222 const size = ref<Size | undefined>("medium")
223 const inline = ref(false)
src/views/Components/GradientText.vue
+4 -6
@@ -8,11 +8,8 @@
8 target="_blank"
9 alt="docs"
10 rel="nofollow noopener noreferrer"
11 - class="ml-4"
11 >
13 - <n-icon :size="16">
14 - <ExternalIcon />
15 - </n-icon>
12 + <Icon :name="ExternalIcon" :size="16" />
13 docs
14 </a>
15 </div>
@@ -59,6 +56,7 @@
56 </template>
57
58 <script lang="ts" setup>
62 -import { NIcon, NGradientText } from "naive-ui"
63 -import ExternalIcon from "@vicons/tabler/ExternalLink"
59 +import { NGradientText } from "naive-ui"
60 +import Icon from "@/components/common/Icon.vue"
61 +const ExternalIcon = "tabler:external-link"
62 </script>
src/views/Components/Grid.vue
+4 -6
@@ -8,11 +8,8 @@
8 target="_blank"
9 alt="docs"
10 rel="nofollow noopener noreferrer"
11 - class="ml-4"
11 >
13 - <n-icon :size="16">
14 - <ExternalIcon />
15 - </n-icon>
12 + <Icon :name="ExternalIcon" :size="16" />
13 docs
14 </a>
15 </div>
@@ -95,8 +92,9 @@
92 </template>
93
94 <script lang="ts" setup>
98 -import { NIcon, NGrid, NGridItem } from "naive-ui"
99 -import ExternalIcon from "@vicons/tabler/ExternalLink"
95 +import { NGrid, NGridItem } from "naive-ui"
96 +import Icon from "@/components/common/Icon.vue"
97 +const ExternalIcon = "tabler:external-link"
98 </script>
99
100 <style scoped>
src/views/Components/Icon.vue
+18 -18
@@ -8,11 +8,8 @@
8 target="_blank"
9 alt="docs"
10 rel="nofollow noopener noreferrer"
11 - class="ml-4"
11 >
13 - <n-icon :size="16">
14 - <ExternalIcon />
15 - </n-icon>
12 + <Icon :name="ExternalIcon" :size="16" />
13 docs
14 </a>
15 </div>
@@ -20,13 +17,9 @@
17
18 <div class="components-list">
19 <CardCodeExample title="Basic">
23 - <n-icon size="40">
24 - <game-controller-outline />
25 - </n-icon>
26 - <n-icon size="40" color="#0e7a0d">
27 - <game-controller />
28 - </n-icon>
29 - <n-icon size="40" :component="GameController" />
20 + <Icon :name="GameControllerOutline" :size="40" />
21 + <Icon :name="GameController" :size="40" color="#0e7a0d" />
22 + <Icon :name="GameController" :size="40" />
23 <template #code="{ html, js }">
24 {{ html(`
25 <n-icon size="40">
@@ -47,9 +40,15 @@
40
41 <CardCodeExample title="Icon with background">
42 <template #description>Sometime it looks better with a background.</template>
50 - <n-icon-wrapper :size="24" :border-radius="10">
51 - <n-icon :size="18" :component="Checkmark16Filled" />
52 - </n-icon-wrapper>
43 + <Icon
44 + :name="Checkmark16Filled"
45 + :bgSize="24"
46 + :size="18"
47 + :border-radius="10"
48 + bgColor="#0e7a0d"
49 + color="#fff"
50 + />
51 +
52 <template #code="{ html, js }">
53 {{ html(`
54 <n-icon-wrapper :size="24" :border-radius="10">
@@ -65,8 +64,9 @@
64 </template>
65
66 <script lang="ts" setup>
68 -import { NIcon, NIconWrapper } from "naive-ui"
69 -import ExternalIcon from "@vicons/tabler/ExternalLink"
70 -import { GameControllerOutline, GameController } from "@vicons/ionicons5"
71 -import Checkmark16Filled from "@vicons/fluent/Checkmark16Filled"
67 +import Icon from "@/components/common/Icon.vue"
68 +const ExternalIcon = "tabler:external-link"
69 +const GameControllerOutline = "ion:game-controller-outline"
70 +const GameController = "ion:game-controller"
71 +const Checkmark16Filled = "fluent:checkmark-16-filled"
72 </script>
src/views/Components/Image.vue
+4 -6
@@ -8,11 +8,8 @@
8 target="_blank"
9 alt="docs"
10 rel="nofollow noopener noreferrer"
11 - class="ml-4"
11 >
13 - <n-icon :size="16">
14 - <ExternalIcon />
15 - </n-icon>
12 + <Icon :name="ExternalIcon" :size="16" />
13 docs
14 </a>
15 </div>
@@ -53,6 +50,7 @@
50 </template>
51
52 <script lang="ts" setup>
56 -import { NIcon, NImage, NImageGroup, NSpace } from "naive-ui"
57 -import ExternalIcon from "@vicons/tabler/ExternalLink"
53 +import { NImage, NImageGroup, NSpace } from "naive-ui"
54 +import Icon from "@/components/common/Icon.vue"
55 +const ExternalIcon = "tabler:external-link"
56 </script>
src/views/Components/Input.vue
+11 -12
@@ -8,11 +8,8 @@
8 target="_blank"
9 alt="docs"
10 rel="nofollow noopener noreferrer"
11 - class="ml-4"
11 >
13 - <n-icon :size="16">
14 - <ExternalIcon />
15 - </n-icon>
12 + <Icon :name="ExternalIcon" :size="16" />
13 docs
14 </a>
15 </div>
@@ -68,7 +65,7 @@
65 <n-space vertical>
66 <n-input placeholder="Flash">
67 <template #prefix>
71 - <n-icon :component="FlashOutline" />
68 + <Icon :name="FlashOutline" />
69 </template>
70 </n-input>
71 <n-input round placeholder="1,400,000">
@@ -76,7 +73,7 @@
73 </n-input>
74 <n-input round placeholder="Flash">
75 <template #suffix>
79 - <n-icon :component="FlashOutline" />
76 + <Icon :name="FlashOutline" />
77 </template>
78 </n-input>
79 <n-input type="text" placeholder="Basic Input" loading />
@@ -118,10 +115,10 @@
115 :maxlength="8"
116 >
117 <template #password-visible-icon>
121 - <n-icon :size="16" :component="GlassesOutline" />
118 + <Icon :size="16" :name="GlassesOutline" />
119 </template>
120 <template #password-invisible-icon>
124 - <n-icon :size="16" :component="Glasses" />
121 + <Icon :size="16" :name="Glasses" />
122 </template>
123 </n-input>
124 <n-input type="text" placeholder="Content is clearable" clearable />
@@ -330,7 +327,6 @@
327
328 <script lang="ts" setup>
329 import {
333 - NIcon,
330 NInput,
331 NSpace,
332 NText,
@@ -343,10 +339,13 @@ import {
339 NDatePicker,
340 NTimePicker
341 } from "naive-ui"
346 -import ExternalIcon from "@vicons/tabler/ExternalLink"
342 +import Icon from "@/components/common/Icon.vue"
343 import { ref } from "vue"
348 -import { FlashOutline } from "@vicons/ionicons5"
349 -import { GlassesOutline, Glasses } from "@vicons/ionicons5"
344 +
345 +const ExternalIcon = "tabler:external-link"
346 +const FlashOutline = "ion:flash-outline"
347 +const GlassesOutline = "ion:glasses-outline"
348 +const Glasses = "ion:glasses"
349
350 const value = ref(null)
351
src/views/Components/InputNumber.vue
+4 -6
@@ -8,11 +8,8 @@
8 target="_blank"
9 alt="docs"
10 rel="nofollow noopener noreferrer"
11 - class="ml-4"
11 >
13 - <n-icon :size="16">
14 - <ExternalIcon />
15 - </n-icon>
12 + <Icon :name="ExternalIcon" :size="16" />
13 docs
14 </a>
15 </div>
@@ -127,8 +124,9 @@
124 </template>
125
126 <script lang="ts" setup>
130 -import { NIcon, NInputNumber, NText, NSpace } from "naive-ui"
131 -import ExternalIcon from "@vicons/tabler/ExternalLink"
127 +import { NInputNumber, NText, NSpace } from "naive-ui"
128 +import Icon from "@/components/common/Icon.vue"
129 +const ExternalIcon = "tabler:external-link"
130 import { ref } from "vue"
131
132 const value = ref(0)
src/views/Components/Layout.vue
+4 -6
@@ -8,11 +8,8 @@
8 target="_blank"
9 alt="docs"
10 rel="nofollow noopener noreferrer"
11 - class="ml-4"
11 >
13 - <n-icon :size="16">
14 - <ExternalIcon />
15 - </n-icon>
12 + <Icon :name="ExternalIcon" :size="16" />
13 docs
14 </a>
15 </div>
@@ -118,6 +115,7 @@
115 </template>
116
117 <script lang="ts" setup>
121 -import { NIcon, NLayout, NLayoutHeader, NLayoutSider, NLayoutFooter, NH2 } from "naive-ui"
122 -import ExternalIcon from "@vicons/tabler/ExternalLink"
118 +import { NLayout, NLayoutHeader, NLayoutSider, NLayoutFooter, NH2 } from "naive-ui"
119 +import Icon from "@/components/common/Icon.vue"
120 +const ExternalIcon = "tabler:external-link"
121 </script>
src/views/Components/LegacyGrid.vue
+4 -6
@@ -8,11 +8,8 @@
8 target="_blank"
9 alt="docs"
10 rel="nofollow noopener noreferrer"
11 - class="ml-4"
11 >
13 - <n-icon :size="16">
14 - <ExternalIcon />
15 - </n-icon>
12 + <Icon :name="ExternalIcon" :size="16" />
13 docs
14 </a>
15 </div>
@@ -95,8 +92,9 @@
92 </template>
93
94 <script lang="ts" setup>
98 -import { NIcon, NRow, NCol } from "naive-ui"
99 -import ExternalIcon from "@vicons/tabler/ExternalLink"
95 +import { NRow, NCol } from "naive-ui"
96 +import Icon from "@/components/common/Icon.vue"
97 +const ExternalIcon = "tabler:external-link"
98 </script>
99
100 <style scoped>
src/views/Components/List.vue
+4 -6
@@ -8,11 +8,8 @@
8 target="_blank"
9 alt="docs"
10 rel="nofollow noopener noreferrer"
11 - class="ml-4"
11 >
13 - <n-icon :size="16">
14 - <ExternalIcon />
15 - </n-icon>
12 + <Icon :name="ExternalIcon" :size="16" />
13 docs
14 </a>
15 </div>
@@ -76,6 +73,7 @@
73 </template>
74
75 <script lang="ts" setup>
79 -import { NIcon, NList, NListItem, NThing, NButton } from "naive-ui"
80 -import ExternalIcon from "@vicons/tabler/ExternalLink"
76 +import { NList, NListItem, NThing, NButton } from "naive-ui"
77 +import Icon from "@/components/common/Icon.vue"
78 +const ExternalIcon = "tabler:external-link"
79 </script>
src/views/Components/Mention.vue
+4 -6
@@ -8,11 +8,8 @@
8 target="_blank"
9 alt="docs"
10 rel="nofollow noopener noreferrer"
11 - class="ml-4"
11 >
13 - <n-icon :size="16">
14 - <ExternalIcon />
15 - </n-icon>
12 + <Icon :name="ExternalIcon" :size="16" />
13 docs
14 </a>
15 </div>
@@ -89,8 +86,9 @@
86 </template>
87
88 <script lang="ts" setup>
92 -import { NIcon, type MentionOption, NAvatar, NMention } from "naive-ui"
93 -import ExternalIcon from "@vicons/tabler/ExternalLink"
89 +import { type MentionOption, NAvatar, NMention } from "naive-ui"
90 +import Icon from "@/components/common/Icon.vue"
91 +const ExternalIcon = "tabler:external-link"
92 import { type VNodeChild, h } from "vue"
93
94 const options = [
src/views/Components/Menu.vue
+13 -16
@@ -8,11 +8,8 @@
8 target="_blank"
9 alt="docs"
10 rel="nofollow noopener noreferrer"
11 - class="ml-4"
11 >
13 - <n-icon :size="16">
14 - <ExternalIcon />
15 - </n-icon>
12 + <Icon :name="ExternalIcon" :size="16" />
13 docs
14 </a>
15 </div>
@@ -80,10 +77,10 @@
77
78 {{
79 js(`
83 - import { BookOutline as BookIcon, PersonOutline as PersonIcon, WineOutline as WineIcon } from "@vicons/ionicons5"
80 + import { BookOutline as BookIcon, PersonOutline as Perso WineOutline as WineIcon } from "@vicons/ionicons5"
81
82 function renderIcon(icon: Component) {
86 - return () => h(NIcon, null, { default: () => h(icon) })
83 + return () => h( null, { default: () => h(icon) })
84 }
85
86 const menuOptions: MenuOption[] = [
@@ -177,10 +174,10 @@
174
175 {{
176 js(`
180 - import { BookOutline as BookIcon, PersonOutline as PersonIcon, WineOutline as WineIcon } from "@vicons/ionicons5"
177 + import { BookOutline as BookIcon, PersonOutline as Perso WineOutline as WineIcon } from "@vicons/ionicons5"
178
179 function renderIcon(icon: Component) {
183 - return () => h(NIcon, null, { default: () => h(icon) })
180 + return () => h( null, { default: () => h(icon) })
181 }
182
183 const menuOptions: MenuOption[] = [
@@ -266,15 +263,15 @@
263 </template>
264
265 <script lang="ts" setup>
269 -import { NIcon, NSpace, NSwitch, NLayout, NLayoutSider, NMenu, type MenuOption } from "naive-ui"
270 -import ExternalIcon from "@vicons/tabler/ExternalLink"
266 +import { NSpace, NSwitch, NLayout, NLayoutSider, NMenu, type MenuOption } from "naive-ui"
267 +import { ref } from "vue"
268 +import { renderIcon } from "@/utils"
269 +import Icon from "@/components/common/Icon.vue"
270
272 -import { BookOutline as BookIcon, PersonOutline as PersonIcon, WineOutline as WineIcon } from "@vicons/ionicons5"
273 -import { ref, h, type Component } from "vue"
274 -
275 -function renderIcon(icon: Component) {
276 - return () => h(NIcon, null, { default: () => h(icon) })
277 -}
271 +const ExternalIcon = "tabler:external-link"
272 +const BookIcon = "ion:book-outline"
273 +const PersonIcon = "ion:person-outline"
274 +const WineIcon = "ion:wine-outline"
275
276 const menuOptions: MenuOption[] = [
277 {
src/views/Components/Message.vue
+4 -6
@@ -8,11 +8,8 @@
8 target="_blank"
9 alt="docs"
10 rel="nofollow noopener noreferrer"
11 - class="ml-4"
11 >
13 - <n-icon :size="16">
14 - <ExternalIcon />
15 - </n-icon>
12 + <Icon :name="ExternalIcon" :size="16" />
13 docs
14 </a>
15 </div>
@@ -69,8 +66,9 @@
66 </template>
67
68 <script lang="ts" setup>
72 -import { NIcon, NSpace, NButton, useMessage } from "naive-ui"
73 -import ExternalIcon from "@vicons/tabler/ExternalLink"
69 +import { NSpace, NButton, useMessage } from "naive-ui"
70 +import Icon from "@/components/common/Icon.vue"
71 +const ExternalIcon = "tabler:external-link"
72
73 const message = useMessage()
74
src/views/Components/Modal.vue
+4 -6
@@ -8,11 +8,8 @@
8 target="_blank"
9 alt="docs"
10 rel="nofollow noopener noreferrer"
11 - class="ml-4"
11 >
13 - <n-icon :size="16">
14 - <ExternalIcon />
15 - </n-icon>
12 + <Icon :name="ExternalIcon" :size="16" />
13 docs
14 </a>
15 </div>
@@ -69,8 +66,9 @@
66 </template>
67
68 <script lang="ts" setup>
72 -import { NIcon, NModal, NCard, NButton } from "naive-ui"
73 -import ExternalIcon from "@vicons/tabler/ExternalLink"
69 +import { NModal, NCard, NButton } from "naive-ui"
70 +import Icon from "@/components/common/Icon.vue"
71 +const ExternalIcon = "tabler:external-link"
72 import { ref } from "vue"
73
74 const showModal = ref(false)
src/views/Components/Notification.vue
+4 -6
@@ -8,11 +8,8 @@
8 target="_blank"
9 alt="docs"
10 rel="nofollow noopener noreferrer"
11 - class="ml-4"
11 >
13 - <n-icon :size="16">
14 - <ExternalIcon />
15 - </n-icon>
12 + <Icon :name="ExternalIcon" :size="16" />
13 docs
14 </a>
15 </div>
@@ -121,8 +118,9 @@
118 </template>
119
120 <script lang="ts" setup>
124 -import { NIcon, NSpace, NButton, NAvatar, useMessage, useNotification, type NotificationType } from "naive-ui"
125 -import ExternalIcon from "@vicons/tabler/ExternalLink"
121 +import { NSpace, NButton, NAvatar, useMessage, useNotification, type NotificationType } from "naive-ui"
122 +import Icon from "@/components/common/Icon.vue"
123 +const ExternalIcon = "tabler:external-link"
124 import { h } from "vue"
125
126 const message = useMessage()
src/views/Components/NumberAnimation.vue
+4 -6
@@ -8,11 +8,8 @@
8 target="_blank"
9 alt="docs"
10 rel="nofollow noopener noreferrer"
11 - class="ml-4"
11 >
13 - <n-icon :size="16">
14 - <ExternalIcon />
15 - </n-icon>
12 + <Icon :name="ExternalIcon" :size="16" />
13 docs
14 </a>
15 </div>
@@ -62,8 +59,9 @@
59 </template>
60
61 <script lang="ts" setup>
65 -import { NIcon, NStatistic, NNumberAnimation, NButton, type NumberAnimationInst } from "naive-ui"
66 -import ExternalIcon from "@vicons/tabler/ExternalLink"
62 +import { NStatistic, NNumberAnimation, NButton, type NumberAnimationInst } from "naive-ui"
63 +import Icon from "@/components/common/Icon.vue"
64 +const ExternalIcon = "tabler:external-link"
65 import { ref } from "vue"
66
67 const numberAnimationInstRef = ref<NumberAnimationInst | null>(null)
src/views/Components/PageHeader.vue
+3 -6
@@ -8,11 +8,8 @@
8 target="_blank"
9 alt="docs"
10 rel="nofollow noopener noreferrer"
11 - class="ml-4"
11 >
13 - <n-icon :size="16">
14 - <ExternalIcon />
15 - </n-icon>
12 + <Icon :name="ExternalIcon" :size="16" />
13 docs
14 </a>
15 </div>
@@ -137,7 +134,6 @@
134
135 <script lang="ts" setup>
136 import {
140 - NIcon,
137 NPageHeader,
138 NGrid,
139 NGi,
@@ -150,7 +146,8 @@ import {
146 NButton,
147 NDropdown
148 } from "naive-ui"
153 -import ExternalIcon from "@vicons/tabler/ExternalLink"
149 +import Icon from "@/components/common/Icon.vue"
150 +const ExternalIcon = "tabler:external-link"
151
152 const message = useMessage()
153 function handleBack() {
src/views/Components/Pagination.vue
+4 -6
@@ -8,11 +8,8 @@
8 target="_blank"
9 alt="docs"
10 rel="nofollow noopener noreferrer"
11 - class="ml-4"
11 >
13 - <n-icon :size="16">
14 - <ExternalIcon />
15 - </n-icon>
12 + <Icon :name="ExternalIcon" :size="16" />
13 docs
14 </a>
15 </div>
@@ -78,8 +75,9 @@
75 </template>
76
77 <script lang="ts" setup>
81 -import { NIcon, NSpace, NPagination } from "naive-ui"
82 -import ExternalIcon from "@vicons/tabler/ExternalLink"
78 +import { NSpace, NPagination } from "naive-ui"
79 +import Icon from "@/components/common/Icon.vue"
80 +const ExternalIcon = "tabler:external-link"
81 import { ref } from "vue"
82
83 const page = ref(2)
src/views/Components/Popconfirm.vue
+4 -6
@@ -8,11 +8,8 @@
8 target="_blank"
9 alt="docs"
10 rel="nofollow noopener noreferrer"
11 - class="ml-4"
11 >
13 - <n-icon :size="16">
14 - <ExternalIcon />
15 - </n-icon>
12 + <Icon :name="ExternalIcon" :size="16" />
13 docs
14 </a>
15 </div>
@@ -54,8 +51,9 @@
51 </template>
52
53 <script lang="ts" setup>
57 -import { NIcon, NPopconfirm, NButton, useMessage } from "naive-ui"
58 -import ExternalIcon from "@vicons/tabler/ExternalLink"
54 +import { NPopconfirm, NButton, useMessage } from "naive-ui"
55 +import Icon from "@/components/common/Icon.vue"
56 +const ExternalIcon = "tabler:external-link"
57
58 const message = useMessage()
59 function handlePositiveClick() {
src/views/Components/Popover.vue
+4 -6
@@ -8,11 +8,8 @@
8 target="_blank"
9 alt="docs"
10 rel="nofollow noopener noreferrer"
11 - class="ml-4"
11 >
13 - <n-icon :size="16">
14 - <ExternalIcon />
15 - </n-icon>
12 + <Icon :name="ExternalIcon" :size="16" />
13 docs
14 </a>
15 </div>
@@ -282,8 +279,9 @@
279 </template>
280
281 <script lang="ts" setup>
285 -import { NIcon, NSpace, NPopover, NButton, NDivider, NSwitch } from "naive-ui"
286 -import ExternalIcon from "@vicons/tabler/ExternalLink"
282 +import { NSpace, NPopover, NButton, NDivider, NSwitch } from "naive-ui"
283 +import Icon from "@/components/common/Icon.vue"
284 +const ExternalIcon = "tabler:external-link"
285 import { ref } from "vue"
286
287 const showPopover = ref(false)
src/views/Components/Popselect.vue
+4 -6
@@ -8,11 +8,8 @@
8 target="_blank"
9 alt="docs"
10 rel="nofollow noopener noreferrer"
11 - class="ml-4"
11 >
13 - <n-icon :size="16">
14 - <ExternalIcon />
15 - </n-icon>
12 + <Icon :name="ExternalIcon" :size="16" />
13 docs
14 </a>
15 </div>
@@ -165,8 +162,9 @@
162 </template>
163
164 <script lang="ts" setup>
168 -import { NIcon, NPopselect, NButton } from "naive-ui"
169 -import ExternalIcon from "@vicons/tabler/ExternalLink"
165 +import { NPopselect, NButton } from "naive-ui"
166 +import Icon from "@/components/common/Icon.vue"
167 +const ExternalIcon = "tabler:external-link"
168 import { ref } from "vue"
169
170 const value = ref("Drive My Car")
src/views/Components/Progress.vue
+4 -6
@@ -8,11 +8,8 @@
8 target="_blank"
9 alt="docs"
10 rel="nofollow noopener noreferrer"
11 - class="ml-4"
11 >
13 - <n-icon :size="16">
14 - <ExternalIcon />
15 - </n-icon>
12 + <Icon :name="ExternalIcon" :size="16" />
13 docs
14 </a>
15 </div>
@@ -304,8 +301,9 @@
301 </template>
302
303 <script lang="ts" setup>
307 -import { NIcon, NSpace, NProgress, NButton, NEl, NText } from "naive-ui"
308 -import ExternalIcon from "@vicons/tabler/ExternalLink"
304 +import { NSpace, NProgress, NButton, NEl, NText } from "naive-ui"
305 +import Icon from "@/components/common/Icon.vue"
306 +const ExternalIcon = "tabler:external-link"
307 import { ref } from "vue"
308
309 const percentage = ref(0)
src/views/Components/Radio.vue
+4 -6
@@ -8,11 +8,8 @@
8 target="_blank"
9 alt="docs"
10 rel="nofollow noopener noreferrer"
11 - class="ml-4"
11 >
13 - <n-icon :size="16">
14 - <ExternalIcon />
15 - </n-icon>
12 + <Icon :name="ExternalIcon" :size="16" />
13 docs
14 </a>
15 </div>
@@ -136,8 +133,9 @@
133 </template>
134
135 <script lang="ts" setup>
139 -import { NIcon, NSpace, NRadio, NRadioGroup, NRadioButton } from "naive-ui"
140 -import ExternalIcon from "@vicons/tabler/ExternalLink"
136 +import { NSpace, NRadio, NRadioGroup, NRadioButton } from "naive-ui"
137 +import Icon from "@/components/common/Icon.vue"
138 +const ExternalIcon = "tabler:external-link"
139 import { ref } from "vue"
140
141 const checkedValue = ref<string | null>(null)
src/views/Components/Rate.vue
+4 -6
@@ -8,11 +8,8 @@
8 target="_blank"
9 alt="docs"
10 rel="nofollow noopener noreferrer"
11 - class="ml-4"
11 >
13 - <n-icon :size="16">
14 - <ExternalIcon />
15 - </n-icon>
12 + <Icon :name="ExternalIcon" :size="16" />
13 docs
14 </a>
15 </div>
@@ -41,6 +38,7 @@
38 </template>
39
40 <script lang="ts" setup>
44 -import { NIcon, NRate } from "naive-ui"
45 -import ExternalIcon from "@vicons/tabler/ExternalLink"
41 +import { NRate } from "naive-ui"
42 +import Icon from "@/components/common/Icon.vue"
43 +const ExternalIcon = "tabler:external-link"
44 </script>
src/views/Components/Result.vue
+4 -6
@@ -8,11 +8,8 @@
8 target="_blank"
9 alt="docs"
10 rel="nofollow noopener noreferrer"
11 - class="ml-4"
11 >
13 - <n-icon :size="16">
14 - <ExternalIcon />
15 - </n-icon>
12 + <Icon :name="ExternalIcon" :size="16" />
13 docs
14 </a>
15 </div>
@@ -187,6 +184,7 @@
184 </template>
185
186 <script lang="ts" setup>
190 -import { NIcon, NResult, NButton } from "naive-ui"
191 -import ExternalIcon from "@vicons/tabler/ExternalLink"
187 +import { NResult, NButton } from "naive-ui"
188 +import Icon from "@/components/common/Icon.vue"
189 +const ExternalIcon = "tabler:external-link"
190 </script>
src/views/Components/Scrollbar.vue
+4 -6
@@ -8,11 +8,8 @@
8 target="_blank"
9 alt="docs"
10 rel="nofollow noopener noreferrer"
11 - class="ml-4"
11 >
13 - <n-icon :size="16">
14 - <ExternalIcon />
15 - </n-icon>
12 + <Icon :name="ExternalIcon" :size="16" />
13 docs
14 </a>
15 </div>
@@ -96,6 +93,7 @@
93 </template>
94
95 <script lang="ts" setup>
99 -import { NIcon, NScrollbar } from "naive-ui"
100 -import ExternalIcon from "@vicons/tabler/ExternalLink"
96 +import { NScrollbar } from "naive-ui"
97 +import Icon from "@/components/common/Icon.vue"
98 +const ExternalIcon = "tabler:external-link"
99 </script>
src/views/Components/Select.vue
+4 -6
@@ -8,11 +8,8 @@
8 target="_blank"
9 alt="docs"
10 rel="nofollow noopener noreferrer"
11 - class="ml-4"
11 >
13 - <n-icon :size="16">
14 - <ExternalIcon />
15 - </n-icon>
12 + <Icon :name="ExternalIcon" :size="16" />
13 docs
14 </a>
15 </div>
@@ -180,8 +177,9 @@
177 </template>
178
179 <script lang="ts" setup>
183 -import { NIcon, NSpace, NSelect } from "naive-ui"
184 -import ExternalIcon from "@vicons/tabler/ExternalLink"
180 +import { NSpace, NSelect } from "naive-ui"
181 +import Icon from "@/components/common/Icon.vue"
182 +const ExternalIcon = "tabler:external-link"
183 import { ref } from "vue"
184
185 const value = ref(null)
src/views/Components/Skeleton.vue
+4 -6
@@ -8,11 +8,8 @@
8 target="_blank"
9 alt="docs"
10 rel="nofollow noopener noreferrer"
11 - class="ml-4"
11 >
13 - <n-icon :size="16">
14 - <ExternalIcon />
15 - </n-icon>
12 + <Icon :name="ExternalIcon" :size="16" />
13 docs
14 </a>
15 </div>
@@ -90,8 +87,9 @@
87 </template>
88
89 <script lang="ts" setup>
93 -import { NIcon, NSpace, NSwitch, NSkeleton, NButton } from "naive-ui"
94 -import ExternalIcon from "@vicons/tabler/ExternalLink"
90 +import { NSpace, NSwitch, NSkeleton, NButton } from "naive-ui"
91 +import Icon from "@/components/common/Icon.vue"
92 +const ExternalIcon = "tabler:external-link"
93 import { ref } from "vue"
94
95 const loading = ref(true)
src/views/Components/Slider.vue
+4 -6
@@ -8,11 +8,8 @@
8 target="_blank"
9 alt="docs"
10 rel="nofollow noopener noreferrer"
11 - class="ml-4"
11 >
13 - <n-icon :size="16">
14 - <ExternalIcon />
15 - </n-icon>
12 + <Icon :name="ExternalIcon" :size="16" />
13 docs
14 </a>
15 </div>
@@ -86,8 +83,9 @@
83 </template>
84
85 <script lang="ts" setup>
89 -import { NIcon, NSlider, NText, NSpace } from "naive-ui"
90 -import ExternalIcon from "@vicons/tabler/ExternalLink"
86 +import { NSlider, NText, NSpace } from "naive-ui"
87 +import Icon from "@/components/common/Icon.vue"
88 +const ExternalIcon = "tabler:external-link"
89 import { ref } from "vue"
90
91 const value = ref(50)
src/views/Components/Space.vue
+4 -6
@@ -8,11 +8,8 @@
8 target="_blank"
9 alt="docs"
10 rel="nofollow noopener noreferrer"
11 - class="ml-4"
11 >
13 - <n-icon :size="16">
14 - <ExternalIcon />
15 - </n-icon>
12 + <Icon :name="ExternalIcon" :size="16" />
13 docs
14 </a>
15 </div>
@@ -127,6 +124,7 @@
124 </template>
125
126 <script lang="ts" setup>
130 -import { NIcon, NSpace, NButton } from "naive-ui"
131 -import ExternalIcon from "@vicons/tabler/ExternalLink"
127 +import { NSpace, NButton } from "naive-ui"
128 +import Icon from "@/components/common/Icon.vue"
129 +const ExternalIcon = "tabler:external-link"
130 </script>
src/views/Components/Spin.vue
+4 -6
@@ -8,11 +8,8 @@
8 target="_blank"
9 alt="docs"
10 rel="nofollow noopener noreferrer"
11 - class="ml-4"
11 >
13 - <n-icon :size="16">
14 - <ExternalIcon />
15 - </n-icon>
12 + <Icon :name="ExternalIcon" :size="16" />
13 docs
14 </a>
15 </div>
@@ -75,8 +72,9 @@
72 </template>
73
74 <script lang="ts" setup>
78 -import { NIcon, NSpace, NText, NSpin, NButton, NAlert } from "naive-ui"
79 -import ExternalIcon from "@vicons/tabler/ExternalLink"
75 +import { NSpace, NText, NSpin, NButton, NAlert } from "naive-ui"
76 +import Icon from "@/components/common/Icon.vue"
77 +const ExternalIcon = "tabler:external-link"
78 import { ref } from "vue"
79
80 const show = ref(false)
src/views/Components/Statistic.vue
+6 -10
@@ -8,11 +8,8 @@
8 target="_blank"
9 alt="docs"
10 rel="nofollow noopener noreferrer"
11 - class="ml-4"
11 >
13 - <n-icon :size="16">
14 - <ExternalIcon />
15 - </n-icon>
12 + <Icon :name="ExternalIcon" :size="16" />
13 docs
14 </a>
15 </div>
@@ -24,9 +21,7 @@
21 <n-col :span="12">
22 <n-statistic label="Statistic" :value="99">
23 <template #prefix>
27 - <n-icon>
28 - <Save />
29 - </n-icon>
24 + <Icon :name="Save" />
25 </template>
26 <template #suffix>/ 100</template>
27 </n-statistic>
@@ -60,7 +55,8 @@
55 </template>
56
57 <script lang="ts" setup>
63 -import { NIcon, NRow, NCol, NStatistic } from "naive-ui"
64 -import ExternalIcon from "@vicons/tabler/ExternalLink"
65 -import { Save } from "@vicons/carbon"
58 +import { NRow, NCol, NStatistic } from "naive-ui"
59 +import Icon from "@/components/common/Icon.vue"
60 +const ExternalIcon = "tabler:external-link"
61 +const Save = "carbon:save"
62 </script>
src/views/Components/Steps.vue
+10 -29
@@ -8,11 +8,8 @@
8 target="_blank"
9 alt="docs"
10 rel="nofollow noopener noreferrer"
11 - class="ml-4"
11 >
13 - <n-icon :size="16">
14 - <ExternalIcon />
15 - </n-icon>
12 + <Icon :name="ExternalIcon" :size="16" />
13 docs
14 </a>
15 </div>
@@ -25,16 +22,12 @@
22 <n-button-group>
23 <n-button @click="prev">
24 <template #icon>
28 - <n-icon>
29 - <arrow-back />
30 - </n-icon>
25 + <Icon :name="ArrowBack" />
26 </template>
27 </n-button>
28 <n-button @click="next">
29 <template #icon>
35 - <n-icon>
36 - <arrow-forward />
37 - </n-icon>
30 + <Icon :name="ArrowForward" />
31 </template>
32 </n-button>
33 </n-button-group>
@@ -149,16 +142,12 @@
142 <n-button-group>
143 <n-button @click="prev">
144 <template #icon>
152 - <n-icon>
153 - <arrow-back />
154 - </n-icon>
145 + <Icon :name="ArrowBack" />
146 </template>
147 </n-button>
148 <n-button @click="next">
149 <template #icon>
159 - <n-icon>
160 - <arrow-forward />
161 - </n-icon>
150 + <Icon :name="ArrowForward" />
151 </template>
152 </n-button>
153 </n-button-group>
@@ -245,20 +234,12 @@
234 </template>
235
236 <script lang="ts" setup>
248 -import {
249 - NIcon,
250 - NSpace,
251 - NSteps,
252 - NStep,
253 - NButtonGroup,
254 - NRadioGroup,
255 - NRadioButton,
256 - NButton,
257 - type StepsProps
258 -} from "naive-ui"
259 -import ExternalIcon from "@vicons/tabler/ExternalLink"
237 +import { NSpace, NSteps, NStep, NButtonGroup, NRadioGroup, NRadioButton, NButton, type StepsProps } from "naive-ui"
238 +import Icon from "@/components/common/Icon.vue"
239 +const ExternalIcon = "tabler:external-link"
240 +const ArrowBack = "ion:arrow-back"
241 +const ArrowForward = "ion:arrow-forward"
242
261 -import { ArrowBack, ArrowForward } from "@vicons/ionicons5"
243 import { ref } from "vue"
244
245 const currentRef = ref<number | null>(1)
src/views/Components/Switch.vue
+4 -6
@@ -8,11 +8,8 @@
8 target="_blank"
9 alt="docs"
10 rel="nofollow noopener noreferrer"
11 - class="ml-4"
11 >
13 - <n-icon :size="16">
14 - <ExternalIcon />
15 - </n-icon>
12 + <Icon :name="ExternalIcon" :size="16" />
13 docs
14 </a>
15 </div>
@@ -94,6 +91,7 @@
91 </template>
92
93 <script lang="ts" setup>
97 -import { NIcon, NSwitch, NSpace } from "naive-ui"
98 -import ExternalIcon from "@vicons/tabler/ExternalLink"
94 +import { NSwitch, NSpace } from "naive-ui"
95 +import Icon from "@/components/common/Icon.vue"
96 +const ExternalIcon = "tabler:external-link"
97 </script>
src/views/Components/Table.vue
+4 -6
@@ -8,11 +8,8 @@
8 target="_blank"
9 alt="docs"
10 rel="nofollow noopener noreferrer"
11 - class="ml-4"
11 >
13 - <n-icon :size="16">
14 - <ExternalIcon />
15 - </n-icon>
12 + <Icon :name="ExternalIcon" :size="16" />
13 docs
14 </a>
15 </div>
@@ -84,6 +81,7 @@
81 </template>
82
83 <script lang="ts" setup>
87 -import { NIcon, NTable } from "naive-ui"
88 -import ExternalIcon from "@vicons/tabler/ExternalLink"
84 +import { NTable } from "naive-ui"
85 +import Icon from "@/components/common/Icon.vue"
86 +const ExternalIcon = "tabler:external-link"
87 </script>
src/views/Components/Tabs.vue
+4 -6
@@ -8,11 +8,8 @@
8 target="_blank"
9 alt="docs"
10 rel="nofollow noopener noreferrer"
11 - class="ml-4"
11 >
13 - <n-icon :size="16">
14 - <ExternalIcon />
15 - </n-icon>
12 + <Icon :name="ExternalIcon" :size="16" />
13 docs
14 </a>
15 </div>
@@ -201,8 +198,9 @@
198 </template>
199
200 <script lang="ts" setup>
204 -import { NIcon, NSpace, NRadioGroup, NRadio, NTabs, NTabPane, NText } from "naive-ui"
205 -import ExternalIcon from "@vicons/tabler/ExternalLink"
201 +import { NSpace, NRadioGroup, NRadio, NTabs, NTabPane, NText } from "naive-ui"
202 +import Icon from "@/components/common/Icon.vue"
203 +const ExternalIcon = "tabler:external-link"
204
205 import { computed, ref } from "vue"
206 import type { TabsProps } from "naive-ui"
src/views/Components/Tag.vue
+7 -9
@@ -8,11 +8,8 @@
8 target="_blank"
9 alt="docs"
10 rel="nofollow noopener noreferrer"
11 - class="ml-4"
11 >
13 - <n-icon :size="16">
14 - <ExternalIcon />
15 - </n-icon>
12 + <Icon :name="ExternalIcon" :size="16" />
13 docs
14 </a>
15 </div>
@@ -94,13 +91,13 @@
91 <n-tag type="success">
92 Checked
93 <template #icon>
97 - <n-icon :component="CheckmarkCircle" />
94 + <Icon :name="CheckmarkCircle" />
95 </template>
96 </n-tag>
97 <n-tag round :bordered="false" type="success">
98 Checked
99 <template #icon>
103 - <n-icon :component="CheckmarkCircle" />
100 + <Icon :name="CheckmarkCircle" />
101 </template>
102 </n-tag>
103 </n-space>
@@ -134,9 +131,10 @@
131 </template>
132
133 <script lang="ts" setup>
137 -import { NIcon, NSpace, NTag, useMessage } from "naive-ui"
138 -import ExternalIcon from "@vicons/tabler/ExternalLink"
139 -import { CheckmarkCircle } from "@vicons/ionicons5"
134 +import { NSpace, NTag, useMessage } from "naive-ui"
135 +import Icon from "@/components/common/Icon.vue"
136 +const ExternalIcon = "tabler:external-link"
137 +const CheckmarkCircle = "ion:checkmark-circle"
138
139 const message = useMessage()
140 function handleClose() {
src/views/Components/Thing.vue
+11 -20
@@ -8,11 +8,8 @@
8 target="_blank"
9 alt="docs"
10 rel="nofollow noopener noreferrer"
11 - class="ml-4"
11 >
13 - <n-icon :size="16">
14 - <ExternalIcon />
15 - </n-icon>
12 + <Icon :name="ExternalIcon" :size="16" />
13 docs
14 </a>
15 </div>
@@ -53,16 +50,14 @@
50 <n-thing :content-indented="indented">
51 <template v-if="avatar" #avatar>
52 <n-avatar>
56 - <n-icon>
57 - <cash-icon />
58 - </n-icon>
53 + <Icon :name="CashIcon" />
54 </n-avatar>
55 </template>
56 <template v-if="header" #header>Money</template>
57 <template v-if="headerExtra" #header-extra>
58 <n-button circle size="small">
59 <template #icon>
65 - <cash-icon />
60 + <Icon :name="CashIcon" />
61 </template>
62 </n-button>
63 </template>
@@ -74,25 +69,19 @@
69 <n-space>
70 <n-button size="small">
71 <template #icon>
77 - <n-icon>
78 - <cash-icon />
79 - </n-icon>
72 + <Icon :name="CashIcon" />
73 </template>
74 1$
75 </n-button>
76 <n-button size="small">
77 <template #icon>
85 - <n-icon>
86 - <cash-icon />
87 - </n-icon>
78 + <Icon :name="CashIcon" />
79 </template>
80 10$
81 </n-button>
82 <n-button size="small">
83 <template #icon>
93 - <n-icon>
94 - <cash-icon />
95 - </n-icon>
84 + <Icon :name="CashIcon" />
85 </template>
86 100$
87 </n-button>
@@ -202,11 +191,13 @@
191 </template>
192
193 <script lang="ts" setup>
205 -import { NIcon, NRow, NCol, NCheckbox, NDivider, NThing, NAvatar, NSpace, NButton } from "naive-ui"
206 -import ExternalIcon from "@vicons/tabler/ExternalLink"
194 +import { NRow, NCol, NCheckbox, NDivider, NThing, NAvatar, NSpace, NButton } from "naive-ui"
195 +import Icon from "@/components/common/Icon.vue"
196 import { ref } from "vue"
197
209 -import { CashOutline as CashIcon } from "@vicons/ionicons5"
198 +const ExternalIcon = "tabler:external-link"
199 +const CashIcon = "ion:cash-outline"
200 +
201 const avatar = ref(true)
202 const header = ref(true)
203 const headerExtra = ref(true)
src/views/Components/Time.vue
+4 -6
@@ -8,11 +8,8 @@
8 target="_blank"
9 alt="docs"
10 rel="nofollow noopener noreferrer"
11 - class="ml-4"
11 >
13 - <n-icon :size="16">
14 - <ExternalIcon />
15 - </n-icon>
12 + <Icon :name="ExternalIcon" :size="16" />
13 docs
14 </a>
15 </div>
@@ -38,8 +35,9 @@
35 </template>
36
37 <script lang="ts" setup>
41 -import { NIcon, NTime } from "naive-ui"
42 -import ExternalIcon from "@vicons/tabler/ExternalLink"
38 +import { NTime } from "naive-ui"
39 +import Icon from "@/components/common/Icon.vue"
40 +const ExternalIcon = "tabler:external-link"
41
42 const time = new Date()
43 </script>
src/views/Components/TimePicker.vue
+4 -6
@@ -8,11 +8,8 @@
8 target="_blank"
9 alt="docs"
10 rel="nofollow noopener noreferrer"
11 - class="ml-4"
11 >
13 - <n-icon :size="16">
14 - <ExternalIcon />
15 - </n-icon>
12 + <Icon :name="ExternalIcon" :size="16" />
13 docs
14 </a>
15 </div>
@@ -89,8 +86,9 @@
86 </template>
87
88 <script lang="ts" setup>
92 -import { NIcon, NTimePicker, NSpace } from "naive-ui"
93 -import ExternalIcon from "@vicons/tabler/ExternalLink"
89 +import { NTimePicker, NSpace } from "naive-ui"
90 +import Icon from "@/components/common/Icon.vue"
91 +const ExternalIcon = "tabler:external-link"
92 import { ref } from "vue"
93
94 const time0 = ref(null)
src/views/Components/Timeline.vue
+4 -6
@@ -8,11 +8,8 @@
8 target="_blank"
9 alt="docs"
10 rel="nofollow noopener noreferrer"
11 - class="ml-4"
11 >
13 - <n-icon :size="16">
14 - <ExternalIcon />
15 - </n-icon>
12 + <Icon :name="ExternalIcon" :size="16" />
13 docs
14 </a>
15 </div>
@@ -113,6 +110,7 @@
110 </template>
111
112 <script lang="ts" setup>
116 -import { NIcon, NTimeline, NTimelineItem } from "naive-ui"
117 -import ExternalIcon from "@vicons/tabler/ExternalLink"
113 +import { NTimeline, NTimelineItem } from "naive-ui"
114 +import Icon from "@/components/common/Icon.vue"
115 +const ExternalIcon = "tabler:external-link"
116 </script>
src/views/Components/Tooltip.vue
+4 -6
@@ -8,11 +8,8 @@
8 target="_blank"
9 alt="docs"
10 rel="nofollow noopener noreferrer"
11 - class="ml-4"
11 >
13 - <n-icon :size="16">
14 - <ExternalIcon />
15 - </n-icon>
12 + <Icon :name="ExternalIcon" :size="16" />
13 docs
14 </a>
15 </div>
@@ -72,8 +69,9 @@
69 </template>
70
71 <script lang="ts" setup>
75 -import { NIcon, NSpace, NTooltip, NButton } from "naive-ui"
76 -import ExternalIcon from "@vicons/tabler/ExternalLink"
72 +import { NSpace, NTooltip, NButton } from "naive-ui"
73 +import Icon from "@/components/common/Icon.vue"
74 +const ExternalIcon = "tabler:external-link"
75 import { ref } from "vue"
76
77 const showPopover = ref(false)
src/views/Components/Transfer.vue
+4 -6
@@ -8,11 +8,8 @@
8 target="_blank"
9 alt="docs"
10 rel="nofollow noopener noreferrer"
11 - class="ml-4"
11 >
13 - <n-icon :size="16">
14 - <ExternalIcon />
15 - </n-icon>
12 + <Icon :name="ExternalIcon" :size="16" />
13 docs
14 </a>
15 </div>
@@ -66,8 +63,9 @@
63 </template>
64
65 <script lang="ts" setup>
69 -import { NIcon, NTransfer } from "naive-ui"
70 -import ExternalIcon from "@vicons/tabler/ExternalLink"
66 +import { NTransfer } from "naive-ui"
67 +import Icon from "@/components/common/Icon.vue"
68 +const ExternalIcon = "tabler:external-link"
69 import { ref } from "vue"
70
71 function createOptions() {
src/views/Components/Tree.vue
+4 -6
@@ -8,11 +8,8 @@
8 target="_blank"
9 alt="docs"
10 rel="nofollow noopener noreferrer"
11 - class="ml-4"
11 >
13 - <n-icon :size="16">
14 - <ExternalIcon />
15 - </n-icon>
12 + <Icon :name="ExternalIcon" :size="16" />
13 docs
14 </a>
15 </div>
@@ -204,8 +201,9 @@
201 </template>
202
203 <script lang="ts" setup>
207 -import { NIcon, NTree, type TreeOption, NSpace, NInput, NSwitch } from "naive-ui"
208 -import ExternalIcon from "@vicons/tabler/ExternalLink"
204 +import { NTree, type TreeOption, NSpace, NInput, NSwitch } from "naive-ui"
205 +import Icon from "@/components/common/Icon.vue"
206 +const ExternalIcon = "tabler:external-link"
207 import { ref } from "vue"
208
209 function createData(level = 4, baseKey = ""): TreeOption[] | undefined {
src/views/Components/TreeSelect.vue
+4 -6
@@ -8,11 +8,8 @@
8 target="_blank"
9 alt="docs"
10 rel="nofollow noopener noreferrer"
11 - class="ml-4"
11 >
13 - <n-icon :size="16">
14 - <ExternalIcon />
15 - </n-icon>
12 + <Icon :name="ExternalIcon" :size="16" />
13 docs
14 </a>
15 </div>
@@ -313,8 +310,9 @@
310 </template>
311
312 <script lang="ts" setup>
316 -import { NIcon, type TreeSelectOption, NTreeSelect } from "naive-ui"
317 -import ExternalIcon from "@vicons/tabler/ExternalLink"
313 +import { type TreeSelectOption, NTreeSelect } from "naive-ui"
314 +import Icon from "@/components/common/Icon.vue"
315 +const ExternalIcon = "tabler:external-link"
316
317 function handleUpdateValue(
318 value: string | number | Array<string | number> | null,
src/views/Components/Typography.vue
+4 -6
@@ -8,11 +8,8 @@
8 target="_blank"
9 alt="docs"
10 rel="nofollow noopener noreferrer"
11 - class="ml-4"
11 >
13 - <n-icon :size="16">
14 - <ExternalIcon />
15 - </n-icon>
12 + <Icon :name="ExternalIcon" :size="16" />
13 docs
14 </a>
15 </div>
@@ -203,6 +200,7 @@
200 </template>
201
202 <script lang="ts" setup>
206 -import { NIcon, NA, NH1, NH2, NH3, NH4, NH5, NH6, NP, NUl, NLi, NOl, NHr, NBlockquote } from "naive-ui"
207 -import ExternalIcon from "@vicons/tabler/ExternalLink"
203 +import { NA, NH1, NH2, NH3, NH4, NH5, NH6, NP, NUl, NLi, NOl, NHr, NBlockquote } from "naive-ui"
204 +import Icon from "@/components/common/Icon.vue"
205 +const ExternalIcon = "tabler:external-link"
206 </script>
src/views/Components/Upload.vue
+6 -10
@@ -8,11 +8,8 @@
8 target="_blank"
9 alt="docs"
10 rel="nofollow noopener noreferrer"
11 - class="ml-4"
11 >
13 - <n-icon :size="16">
14 - <ExternalIcon />
15 - </n-icon>
12 + <Icon :name="ExternalIcon" :size="16" />
13 docs
14 </a>
15 </div>
@@ -59,9 +56,7 @@
56 <n-upload multiple directory-dnd action="https://www.mocky.io/v2/5e4bafc63100007100d8b70f" :max="5">
57 <n-upload-dragger>
58 <div style="margin-bottom: 12px">
62 - <n-icon size="48" :depth="3">
63 - <archive-icon />
64 - </n-icon>
59 + <Icon :name="ArchiveIcon" :size="48" :depth="3" />
60 </div>
61 <n-text style="font-size: 16px">Click or drag a file to this area to upload</n-text>
62 <n-p depth="3" style="margin: 8px 0 0 0">
@@ -159,10 +154,11 @@
154 </template>
155
156 <script lang="ts" setup>
162 -import { NIcon, NUpload, NButton, NText, NUploadDragger, NP, type UploadFileInfo } from "naive-ui"
163 -import ExternalIcon from "@vicons/tabler/ExternalLink"
157 +import { NUpload, NButton, NText, NUploadDragger, NP, type UploadFileInfo } from "naive-ui"
158 +import Icon from "@/components/common/Icon.vue"
159 +const ExternalIcon = "tabler:external-link"
160
165 -import { ArchiveOutline as ArchiveIcon } from "@vicons/ionicons5"
161 +const ArchiveIcon = "ion:archive-outline"
162 import { ref } from "vue"
163
164 const fileList = ref<UploadFileInfo[]>([
src/views/Components/Watermark.vue
+4 -6
@@ -9,11 +9,8 @@
9 target="_blank"
10 alt="docs"
11 rel="nofollow noopener noreferrer"
12 - class="ml-4"
12 >
14 - <n-icon :size="20">
15 - <ExternalIcon />
16 - </n-icon>
13 + <Icon :name="ExternalIcon" :size="16" />
14 docs
15 </a>
16 </div>
@@ -154,8 +151,9 @@
151 </template>
152
153 <script lang="ts" setup>
157 -import { NIcon, NWatermark, NTable, NSwitch, NScrollbar } from "naive-ui"
158 -import ExternalIcon from "@vicons/tabler/ExternalLink"
154 +import { NWatermark, NTable, NSwitch, NScrollbar } from "naive-ui"
155 +import Icon from "@/components/common/Icon.vue"
156 +const ExternalIcon = "tabler:external-link"
157 import { ref } from "vue"
158
159 const show = ref(false)
src/views/Components/data-table-components/Draggable.vue
+1 -2
@@ -87,8 +87,7 @@
87
88 <script lang="ts">
89 import { h, defineComponent } from "vue"
90 -import { NButton, useMessage, NDataTable } from "naive-ui"
91 -import type { DataTableColumns } from "naive-ui"
90 +import { NButton, useMessage, NDataTable, type DataTableColumns } from "naive-ui"
91
92 type Song = {
93 no: number
src/views/Dashboard/Analytics.vue
+26 -34
@@ -23,9 +23,7 @@
23 }"
24 >
25 <template #icon>
26 - <CardComboIcon boxed>
27 - <UsersIcon />
28 - </CardComboIcon>
26 + <CardComboIcon :iconName="UsersIcon" boxed></CardComboIcon>
27 </template>
28 </CardCombo4>
29 <CardCombo4
@@ -39,9 +37,7 @@
37 }"
38 >
39 <template #icon>
42 - <CardComboIcon boxed>
43 - <ViewsIcon />
44 - </CardComboIcon>
40 + <CardComboIcon :iconName="ViewsIcon" boxed></CardComboIcon>
41 </template>
42 </CardCombo4>
43 <CardCombo4
@@ -55,9 +51,7 @@
51 }"
52 >
53 <template #icon>
58 - <CardComboIcon boxed>
59 - <ActivityIcon />
60 - </CardComboIcon>
54 + <CardComboIcon :iconName="ActivityIcon" boxed></CardComboIcon>
55 </template>
56 </CardCombo4>
57 <CardCombo4
@@ -71,9 +65,7 @@
65 }"
66 >
67 <template #icon>
74 - <CardComboIcon boxed>
75 - <UploadsIcon />
76 - </CardComboIcon>
68 + <CardComboIcon :iconName="UploadsIcon" boxed></CardComboIcon>
69 </template>
70 </CardCombo4>
71 </div>
@@ -122,9 +114,7 @@
114 chartColor="#ffffff"
115 >
116 <template #icon>
125 - <CardComboIcon boxed color="white">
126 - <SessionsIcon />
127 - </CardComboIcon>
117 + <CardComboIcon :iconName="SessionsIcon" boxed color="white"></CardComboIcon>
118 </template>
119 </CardCombo1>
120 </div>
@@ -133,16 +123,22 @@
123 <div class="flex gap-5">
124 <CardCombo2 title="Reports" centered class="basis-1/2">
125 <template #icon>
136 - <CardComboIcon boxed :boxSize="50" :color="style['--secondary3-color']">
137 - <ReportsIcon />
138 - </CardComboIcon>
126 + <CardComboIcon
127 + :iconName="ReportsIcon"
128 + boxed
129 + :boxSize="50"
130 + :color="style['--secondary3-color']"
131 + ></CardComboIcon>
132 </template>
133 </CardCombo2>
134 <CardCombo2 title="Issues" centered class="basis-1/2">
135 <template #icon>
143 - <CardComboIcon boxed :boxSize="50" :color="style['--secondary4-color']">
144 - <ErrorIcon />
145 - </CardComboIcon>
136 + <CardComboIcon
137 + :iconName="ErrorIcon"
138 + boxed
139 + :boxSize="50"
140 + :color="style['--secondary4-color']"
141 + ></CardComboIcon>
142 </template>
143 </CardCombo2>
144 </div>
@@ -177,24 +173,20 @@
173 </template>
174
175 <script lang="ts" setup>
180 -import DemoChart from "@/components/charts/Apex.vue"
176 +import DemoChart from "@/components/charts/DemoApex.vue"
177 import DemoList from "@/components/list/List.vue"
182 -import SessionsIcon from "@vicons/carbon/UserMultiple"
183 -import UsersIcon from "@vicons/carbon/User"
184 -import ReportsIcon from "@vicons/carbon/Report"
185 -import ErrorIcon from "@vicons/carbon/Debug"
186 -import ViewsIcon from "@vicons/carbon/View"
187 -import ActivityIcon from "@vicons/carbon/Activity"
188 -import UploadsIcon from "@vicons/carbon/CloudUpload"
178 import { useThemeStore } from "@/stores/theme"
190 -
179 import { computed } from "vue"
180
193 -defineOptions({
194 - name: "Analytics"
195 -})
181 +const SessionsIcon = "carbon:user-multiple"
182 +const UsersIcon = "carbon:user"
183 +const ReportsIcon = "carbon:report"
184 +const ErrorIcon = "carbon:debug"
185 +const ViewsIcon = "carbon:view"
186 +const ActivityIcon = "carbon:activity"
187 +const UploadsIcon = "carbon:cloud-upload"
188
197 -const style: { [key: string]: any } = computed(() => useThemeStore().style)
189 +const style = computed<{ [key: string]: any }>(() => useThemeStore().style)
190 const textSecondaryColor = computed<string>(() => style.value["--fg-secondary-color"])
191
192 const chartBg = computed<string>(() =>
src/views/Dashboard/eCommerce.vue
+30 -31
@@ -4,9 +4,7 @@
4 <div class="box-card-1">
5 <CardCombo1 title="Sales" class="h-full">
6 <template #icon>
7 - <CardComboIcon boxed>
8 - <SalesIcon />
9 - </CardComboIcon>
7 + <CardComboIcon :iconName="SalesIcon" boxed></CardComboIcon>
8 </template>
9 </CardCombo1>
10 </div>
@@ -22,9 +20,7 @@
20 <div class="box-card-3">
21 <CardCombo1 title="Orders" class="h-full">
22 <template #icon>
25 - <CardComboIcon boxed>
26 - <OrdersIcon />
27 - </CardComboIcon>
23 + <CardComboIcon :iconName="OrdersIcon" boxed></CardComboIcon>
24 </template>
25 </CardCombo1>
26 </div>
@@ -75,23 +71,32 @@
71 <div class="flex gap-5 w-full" :class="{ 'flex-col': isCardHorizontal }" ref="cardsContainer">
72 <CardCombo2 title="Completed" centered :horizontal="isCardHorizontal" class="basis-1/3">
73 <template #icon>
78 - <CardComboIcon boxed :boxSize="50" :color="style['--primary-color']">
79 - <CompletedIcon />
80 - </CardComboIcon>
74 + <CardComboIcon
75 + :iconName="CompletedIcon"
76 + boxed
77 + :boxSize="50"
78 + :color="style['--primary-color']"
79 + ></CardComboIcon>
80 </template>
81 </CardCombo2>
82 <CardCombo2 title="Pending" centered :horizontal="isCardHorizontal" class="basis-1/3">
83 <template #icon>
85 - <CardComboIcon boxed :boxSize="50" :color="style['--secondary3-color']">
86 - <PendingIcon />
87 - </CardComboIcon>
84 + <CardComboIcon
85 + :iconName="PendingIcon"
86 + boxed
87 + :boxSize="50"
88 + :color="style['--secondary3-color']"
89 + ></CardComboIcon>
90 </template>
91 </CardCombo2>
92 <CardCombo2 title="Shipped" centered :horizontal="isCardHorizontal" class="basis-1/3">
93 <template #icon>
92 - <CardComboIcon boxed :boxSize="50" :color="style['--secondary1-color']">
93 - <ShippedIcon />
94 - </CardComboIcon>
94 + <CardComboIcon
95 + :iconName="ShippedIcon"
96 + boxed
97 + :boxSize="50"
98 + :color="style['--secondary1-color']"
99 + ></CardComboIcon>
100 </template>
101 </CardCombo2>
102 </div>
@@ -114,9 +119,7 @@
119 chartColor="#ffffff"
120 >
121 <template #icon>
117 - <CardComboIcon boxed color="white">
118 - <RevenueIcon />
119 - </CardComboIcon>
122 + <CardComboIcon :iconName="RevenueIcon" boxed color="white"></CardComboIcon>
123 </template>
124 </CardCombo1>
125 </div>
@@ -150,25 +153,21 @@
153 </template>
154
155 <script lang="ts" setup>
153 -import DemoChart from "@/components/charts/Apex.vue"
154 -import CompletedIcon from "@vicons/carbon/CheckmarkOutline"
155 -import PendingIcon from "@vicons/carbon/Hourglass"
156 -import ShippedIcon from "@vicons/carbon/Send"
156 +import DemoChart from "@/components/charts/DemoApex.vue"
157 import DemoList from "@/components/list/List.vue"
158 -import SalesIcon from "@vicons/carbon/WirelessCheckout"
159 -import OrdersIcon from "@vicons/carbon/ShoppingCart"
160 -import RevenueIcon from "@vicons/carbon/Money"
161 -import { useWindowSize } from "@vueuse/core"
158 +import { useWindowSize, useElementSize } from "@vueuse/core"
159 import { computed, ref } from "vue"
160 import { useThemeStore } from "@/stores/theme"
164 -import { useElementSize } from "@vueuse/core"
161
166 -defineOptions({
167 - name: "eCommerce"
168 -})
162 +const CompletedIcon = "carbon:checkmark-outline"
163 +const PendingIcon = "carbon:hourglass"
164 +const ShippedIcon = "carbon:send"
165 +const SalesIcon = "carbon:wireless-checkout"
166 +const OrdersIcon = "carbon:shopping-cart"
167 +const RevenueIcon = "carbon:money"
168
169 const { width } = useWindowSize()
171 -const style: { [key: string]: any } = computed(() => useThemeStore().style)
170 +const style = computed<{ [key: string]: any }>(() => useThemeStore().style)
171 const textSecondaryColor = computed<string>(() => style.value["--fg-secondary-color"])
172 const cardsContainer = ref(null)
173 const { width: widthCardsContainer } = useElementSize(cardsContainer)
src/views/Editors/Milkdown.vue
+8 -13
@@ -3,16 +3,8 @@
3 <div class="page-header">
4 <div class="title">Milkdown</div>
5 <div class="links">
6 - <a
7 - href="https://milkdown.dev/"
8 - target="_blank"
9 - alt="docs"
10 - rel="nofollow noopener noreferrer"
11 - class="ml-4"
12 - >
13 - <n-icon :size="16">
14 - <ExternalIcon />
15 - </n-icon>
6 + <a href="https://milkdown.dev/" target="_blank" alt="docs" rel="nofollow noopener noreferrer">
7 + <Icon :name="ExternalIcon" :size="16" />
8 docs
9 </a>
10 </div>
@@ -23,7 +15,10 @@
15 </template>
16
17 <script setup lang="ts">
26 -import { NIcon, NCard } from "naive-ui"
27 -import ExternalIcon from "@vicons/tabler/ExternalLink"
28 -import Milkdown from "@/components/editors/Milkdown"
18 +import { NCard } from "naive-ui"
19 +
20 +import Icon from "@/components/common/Icon.vue"
21 +const ExternalIcon = "tabler:external-link"
22 +
23 +import Milkdown from "@/components/editors/Milkdown/index.vue"
24 </script>
src/views/Editors/Quill.vue
+27 -8
@@ -8,29 +8,48 @@
8 target="_blank"
9 alt="docs"
10 rel="nofollow noopener noreferrer"
11 - class="ml-4"
11 >
13 - <n-icon :size="16">
14 - <ExternalIcon />
15 - </n-icon>
12 + <Icon :name="ExternalIcon" :size="16" />
13 docs
14 </a>
15 </div>
16 </div>
17
18 <n-card>
22 - <QuillEditor theme="snow" toolbar="minimal" @blur="resetScroll()" style="width: 100%; height: 60vh" />
19 + <QuillEditor
20 + v-if="mounted"
21 + theme="snow"
22 + toolbar="minimal"
23 + @blur="resetScroll()"
24 + style="width: 100%; height: 60vh"
25 + />
26 </n-card>
27 </div>
28 </template>
29
30 <script setup lang="ts">
28 -import { NIcon, NCard } from "naive-ui"
29 -import ExternalIcon from "@vicons/tabler/ExternalLink"
30 -import { QuillEditor } from "@vueup/vue-quill"
31 +import { NCard } from "naive-ui"
32 +import { ref, defineAsyncComponent, type Component, onMounted } from "vue"
33 +
34 +import Icon from "@/components/common/Icon.vue"
35 +const ExternalIcon = "tabler:external-link"
36 +
37 import "@/assets/scss/quill-override.scss"
38
39 +const mounted = ref(false)
40 +
41 +const QuillEditor = defineAsyncComponent<Component>(() => {
42 + return (async () => {
43 + const { QuillEditor } = await import("@vueup/vue-quill")
44 + return QuillEditor
45 + })()
46 +})
47 +
48 function resetScroll() {
49 window.scrollTo(0, 0)
50 }
51 +
52 +onMounted(() => {
53 + mounted.value = true
54 +})
55 </script>
src/views/Editors/Tiptap.vue
+4 -7
@@ -8,11 +8,8 @@
8 target="_blank"
9 alt="docs"
10 rel="nofollow noopener noreferrer"
11 - class="ml-4"
11 >
13 - <n-icon :size="16">
14 - <ExternalIcon />
15 - </n-icon>
12 + <Icon :name="ExternalIcon" :size="16" />
13 docs
14 </a>
15 </div>
@@ -25,11 +22,11 @@
22 </template>
23
24 <script setup lang="ts">
28 -import { NIcon } from "naive-ui"
29 -import ExternalIcon from "@vicons/tabler/ExternalLink"
30 -import Tiptap from "@/components/editors/Tiptap"
25 import { ref } from "vue"
26 +import Tiptap from "@/components/editors/Tiptap/index.vue"
27 +import Icon from "@/components/common/Icon.vue"
28
29 +const ExternalIcon = "tabler:external-link"
30 const text = ref("<p>I’m running Tiptap with Vue.js. 🎉</p>")
31 </script>
32
src/views/Icons.vue new
+137
@@ -0,0 +1,137 @@
1 +<template>
2 + <div class="page">
3 + <div class="page-header">
4 + <div class="title">Icons</div>
5 + </div>
6 + <div class="main">
7 + <strong>Pinx</strong>
8 + uses the
9 + <a href="https://iconify.design/" target="_blank" alt="docs" rel="nofollow noopener noreferrer">iconify</a>
10 + library which gives the possibility to integrate, many icon sets. You can find
11 + <a href="https://icones.js.org/">here</a>
12 + all sets available. Here, instead, are some examples:
13 +
14 + <div class="groups grid gap-5 mt-8">
15 + <n-card class="group" title="Carbon">
16 + <div class="list">
17 + <div v-for="icon of Carbon" :key="icon" class="icon-box">
18 + <Icon :size="20" :name="icon"></Icon>
19 + </div>
20 + </div>
21 + <template #footer>
22 + <div class="flex justify-end">
23 + <n-button tag="a" href="https://iconify.design/" target="_blank" type="primary" alt="docs">
24 + See more
25 + </n-button>
26 + </div>
27 + </template>
28 + </n-card>
29 +
30 + <n-card class="group" title="Flags">
31 + <div class="list">
32 + <div v-for="icon of Flags" :key="icon" class="icon-box">
33 + <Icon :size="20" :name="icon"></Icon>
34 + </div>
35 + </div>
36 + <template #footer>
37 + <div class="flex justify-end">
38 + <n-button tag="a" href="https://iconify.design/" target="_blank" type="primary" alt="docs">
39 + See more
40 + </n-button>
41 + </div>
42 + </template>
43 + </n-card>
44 + </div>
45 + </div>
46 + </div>
47 +</template>
48 +
49 +<script setup lang="ts">
50 +import { NCard, NButton } from "naive-ui"
51 +import Icon from "@/components/common/Icon.vue"
52 +
53 +const Carbon = [
54 + "carbon:3d-cursor",
55 + "carbon:accessibility-alt",
56 + "carbon:audio-console",
57 + "carbon:bicycle",
58 + "carbon:business-processes",
59 + "carbon:basketball",
60 + "carbon:chart-histogram",
61 + "carbon:cloud-satellite-config",
62 + "carbon:content-delivery-network",
63 + "carbon:dashboard",
64 + "carbon:corn",
65 + "carbon:cube-view",
66 + "carbon:data-categorical",
67 + "carbon:data-volume",
68 + "carbon:decision-tree",
69 + "carbon:debug",
70 + "carbon:delivery-add",
71 + "carbon:cut-out",
72 + "carbon:cyclist",
73 + "carbon:earth-filled",
74 + "carbon:directory-domain",
75 + "carbon:direct-link",
76 + "carbon:fish",
77 + "carbon:forecast-hail-30"
78 +]
79 +const Flags = [
80 + "circle-flags:it",
81 + "circle-flags:ca",
82 + "circle-flags:br",
83 + "circle-flags:us",
84 + "circle-flags:jp",
85 + "circle-flags:uk",
86 + "circle-flags:be",
87 + "circle-flags:fr-cp",
88 + "circle-flags:cg",
89 + "circle-flags:fi",
90 + "circle-flags:in-mn",
91 + "circle-flags:be",
92 + "circle-flags:gr",
93 + "circle-flags:kw",
94 + "circle-flags:at",
95 + "circle-flags:ch",
96 + "circle-flags:sy",
97 + "circle-flags:ml",
98 + "circle-flags:hu",
99 + "circle-flags:de",
100 + "circle-flags:fx",
101 + "circle-flags:kr",
102 + "circle-flags:es-variant",
103 + "circle-flags:cz"
104 +]
105 +</script>
106 +
107 +<style lang="scss" scoped>
108 +.page {
109 + .main {
110 + .groups {
111 + grid-template-columns: repeat(auto-fit, minmax(380px, 1fr));
112 +
113 + @media (max-width: 450px) {
114 + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
115 + }
116 + .group {
117 + .list {
118 + margin-top: 10px;
119 + display: grid;
120 + gap: 12px;
121 + align-items: start;
122 + grid-template-columns: repeat(auto-fit, minmax(45px, 1fr));
123 +
124 + .icon-box {
125 + background-color: var(--bg-secondary-color);
126 + border-radius: 10px;
127 + aspect-ratio: 1;
128 + display: flex;
129 + align-items: center;
130 + justify-content: center;
131 + }
132 + }
133 + }
134 + }
135 + }
136 +}
137 +</style>
src/views/Icons/Flag.vue deleted
-152
@@ -1,152 +0,0 @@
1 -<template>
2 - <div class="page">
3 - <div class="page-header">
4 - <div class="title">Flag Icons</div>
5 - </div>
6 - <div class="main">
7 - <strong>Pinx</strong>
8 - uses the
9 - <a href="https://flagicons.lipis.dev/" target="_blank" alt="docs" rel="nofollow noopener noreferrer">
10 - flag-icons
11 - </a>
12 - as its svg flag library, so you can import only the flags you need
13 - <div class="groups grid gap-5 mt-8">
14 - <n-card class="group">
15 - <div class="list">
16 - <div v-for="icon of Flags" :key="icon.name" class="icon-box">
17 - <n-icon :size="20" :color="'#000'">
18 - <component :is="icon.name" />
19 - </n-icon>
20 - </div>
21 - </div>
22 - <template #footer>
23 - <div class="flex justify-end">
24 - <n-button
25 - tag="a"
26 - href="https://flagicons.lipis.dev/"
27 - target="_blank"
28 - type="primary"
29 - alt="docs"
30 - >
31 - See more
32 - </n-button>
33 - </div>
34 - </template>
35 - </n-card>
36 - </div>
37 - </div>
38 - </div>
39 -</template>
40 -
41 -<script lang="ts">
42 -import { NIcon, NCard, NButton } from "naive-ui"
43 -import Flags from "./icons-set/flags"
44 -
45 -import nz from "flag-icons/flags/4x3/nz.svg"
46 -import md from "flag-icons/flags/4x3/md.svg"
47 -import sn from "flag-icons/flags/4x3/sn.svg"
48 -import it from "flag-icons/flags/4x3/it.svg"
49 -import az from "flag-icons/flags/4x3/az.svg"
50 -import ws from "flag-icons/flags/4x3/ws.svg"
51 -import us from "flag-icons/flags/4x3/us.svg"
52 -import nc from "flag-icons/flags/4x3/nc.svg"
53 -import fr from "flag-icons/flags/4x3/fr.svg"
54 -import by from "flag-icons/flags/4x3/by.svg"
55 -import gq from "flag-icons/flags/4x3/gq.svg"
56 -import es from "flag-icons/flags/4x3/es.svg"
57 -import lu from "flag-icons/flags/4x3/lu.svg"
58 -import mg from "flag-icons/flags/4x3/mg.svg"
59 -import de from "flag-icons/flags/4x3/de.svg"
60 -import vg from "flag-icons/flags/4x3/vg.svg"
61 -import st from "flag-icons/flags/4x3/st.svg"
62 -import cg from "flag-icons/flags/4x3/cg.svg"
63 -import gb from "flag-icons/flags/4x3/gb.svg"
64 -import sa from "flag-icons/flags/4x3/sa.svg"
65 -import gd from "flag-icons/flags/4x3/gd.svg"
66 -import tv from "flag-icons/flags/4x3/tv.svg"
67 -import jp from "flag-icons/flags/4x3/jp.svg"
68 -import ky from "flag-icons/flags/4x3/ky.svg"
69 -import ne from "flag-icons/flags/4x3/ne.svg"
70 -import sl from "flag-icons/flags/4x3/sl.svg"
71 -import ge from "flag-icons/flags/4x3/ge.svg"
72 -import as from "flag-icons/flags/4x3/as.svg"
73 -import sg from "flag-icons/flags/4x3/sg.svg"
74 -import pa from "flag-icons/flags/4x3/pa.svg"
75 -
76 -import { type Component, defineComponent } from "vue"
77 -
78 -export default defineComponent({
79 - name: "FlagIcons",
80 - data: () => ({
81 - Flags
82 - }),
83 - components: {
84 - NIcon,
85 - NCard,
86 - NButton,
87 - nz: nz as unknown as Component,
88 - md: md as unknown as Component,
89 - sn: sn as unknown as Component,
90 - it: it as unknown as Component,
91 - az: az as unknown as Component,
92 - ws: ws as unknown as Component,
93 - us: us as unknown as Component,
94 - nc: nc as unknown as Component,
95 - fr: fr as unknown as Component,
96 - by: by as unknown as Component,
97 - gq: gq as unknown as Component,
98 - es: es as unknown as Component,
99 - lu: lu as unknown as Component,
100 - mg: mg as unknown as Component,
101 - de: de as unknown as Component,
102 - vg: vg as unknown as Component,
103 - st: st as unknown as Component,
104 - cg: cg as unknown as Component,
105 - gb: gb as unknown as Component,
106 - sa: sa as unknown as Component,
107 - gd: gd as unknown as Component,
108 - tv: tv as unknown as Component,
109 - jp: jp as unknown as Component,
110 - ky: ky as unknown as Component,
111 - ne: ne as unknown as Component,
112 - sl: sl as unknown as Component,
113 - ge: ge as unknown as Component,
114 - as: as as unknown as Component,
115 - sg: sg as unknown as Component,
116 - pa: pa as unknown as Component
117 - }
118 -})
119 -</script>
120 -
121 -<style lang="scss" scoped>
122 -.page {
123 - .main {
124 - .groups {
125 - grid-template-columns: repeat(auto-fit, minmax(380px, 1fr));
126 -
127 - @media (max-width: 450px) {
128 - grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
129 - }
130 - .group {
131 - max-width: 500px;
132 - .list {
133 - margin-top: 10px;
134 - display: grid;
135 - gap: 12px;
136 - align-items: start;
137 - grid-template-columns: repeat(auto-fit, minmax(45px, 1fr));
138 -
139 - .icon-box {
140 - background-color: rgba(var(--bg-body-rgb), 0.5);
141 - border-radius: 10px;
142 - aspect-ratio: 1;
143 - display: flex;
144 - align-items: center;
145 - justify-content: center;
146 - }
147 - }
148 - }
149 - }
150 - }
151 -}
152 -</style>
src/views/Icons/Xicons.vue deleted
-586
@@ -1,586 +0,0 @@
1 -<template>
2 - <div class="page">
3 - <div class="page-header">
4 - <div class="title">Icons</div>
5 - </div>
6 - <div class="main">
7 - <strong>Pinx</strong>
8 - uses the
9 - <a href="https://www.xicons.org/#/" target="_blank" alt="docs" rel="nofollow noopener noreferrer">xicons</a>
10 - library which gives the possibility to integrate, with the use of the NIcon component, many icon sets. Here
11 - are some examples:
12 -
13 - <div class="groups grid gap-5 mt-8">
14 - <n-card class="group" title="Carbon">
15 - <div class="list">
16 - <div v-for="icon of Carbon" :key="icon.name" class="icon-box">
17 - <n-icon :size="20">
18 - <component :is="icon.name" />
19 - </n-icon>
20 - </div>
21 - </div>
22 - <template #footer>
23 - <div class="flex justify-end">
24 - <n-button
25 - tag="a"
26 - href="https://www.xicons.org/#/"
27 - target="_blank"
28 - type="primary"
29 - alt="docs"
30 - >
31 - See more
32 - </n-button>
33 - </div>
34 - </template>
35 - </n-card>
36 -
37 - <n-card class="group" title="Tabler">
38 - <div class="list">
39 - <div v-for="icon of Tabler" :key="icon.name" class="icon-box">
40 - <n-icon :size="20">
41 - <component :is="icon.name" />
42 - </n-icon>
43 - </div>
44 - </div>
45 - <template #footer>
46 - <div class="flex justify-end">
47 - <n-button
48 - tag="a"
49 - href="https://www.xicons.org/#/"
50 - target="_blank"
51 - type="primary"
52 - alt="docs"
53 - >
54 - See more
55 - </n-button>
56 - </div>
57 - </template>
58 - </n-card>
59 -
60 - <n-card class="group" title="Font Awesome">
61 - <div class="list">
62 - <div v-for="icon of Fa" :key="icon.name" class="icon-box">
63 - <n-icon :size="20">
64 - <component :is="icon.name" />
65 - </n-icon>
66 - </div>
67 - </div>
68 - <template #footer>
69 - <div class="flex justify-end">
70 - <n-button
71 - tag="a"
72 - href="https://www.xicons.org/#/"
73 - target="_blank"
74 - type="primary"
75 - alt="docs"
76 - >
77 - See more
78 - </n-button>
79 - </div>
80 - </template>
81 - </n-card>
82 -
83 - <n-card class="group" title="Fluent">
84 - <div class="list">
85 - <div v-for="icon of Fluent" :key="icon.name" class="icon-box">
86 - <n-icon :size="20">
87 - <component :is="icon.name" />
88 - </n-icon>
89 - </div>
90 - </div>
91 - <template #footer>
92 - <div class="flex justify-end">
93 - <n-button
94 - tag="a"
95 - href="https://www.xicons.org/#/"
96 - target="_blank"
97 - type="primary"
98 - alt="docs"
99 - >
100 - See more
101 - </n-button>
102 - </div>
103 - </template>
104 - </n-card>
105 -
106 - <n-card class="group" title="Ionicons 5">
107 - <div class="list">
108 - <div v-for="icon of Ionicons5" :key="icon.name" class="icon-box">
109 - <n-icon :size="20">
110 - <component :is="icon.name" />
111 - </n-icon>
112 - </div>
113 - </div>
114 - <template #footer>
115 - <div class="flex justify-end">
116 - <n-button
117 - tag="a"
118 - href="https://www.xicons.org/#/"
119 - target="_blank"
120 - type="primary"
121 - alt="docs"
122 - >
123 - See more
124 - </n-button>
125 - </div>
126 - </template>
127 - </n-card>
128 -
129 - <n-card class="group" title="Material">
130 - <div class="list">
131 - <div v-for="icon of Material" :key="icon.name" class="icon-box">
132 - <n-icon :size="20">
133 - <component :is="icon.name" />
134 - </n-icon>
135 - </div>
136 - </div>
137 - <template #footer>
138 - <div class="flex justify-end">
139 - <n-button
140 - tag="a"
141 - href="https://www.xicons.org/#/"
142 - target="_blank"
143 - type="primary"
144 - alt="docs"
145 - >
146 - See more
147 - </n-button>
148 - </div>
149 - </template>
150 - </n-card>
151 - </div>
152 - </div>
153 - </div>
154 -</template>
155 -
156 -<script lang="ts">
157 -import { NIcon, NCard, NButton } from "naive-ui"
158 -import Carbon from "./icons-set/carbon"
159 -import Fa from "./icons-set/fa"
160 -import Fluent from "./icons-set/fluent"
161 -import Ionicons5 from "./icons-set/ionicons5"
162 -import Material from "./icons-set/material"
163 -import Tabler from "./icons-set/tabler"
164 -import {
165 - StringInteger,
166 - DocumentWordProcessorReference,
167 - CategoryNewEach,
168 - WarningAlt,
169 - Stethoscope,
170 - KeepDry,
171 - NoImage,
172 - GroupObjects,
173 - Information,
174 - Warning,
175 - AccessibilityAlt,
176 - VirtualColumnKey,
177 - OpenPanelFilledRight,
178 - DataBase,
179 - AlignVerticalTop,
180 - ConnectionSignal,
181 - ConnectionSend,
182 - PhoneIncomingFilled,
183 - TemperatureFahrenheitAlt,
184 - PhoneVoice,
185 - Network1,
186 - WintryMix,
187 - PlayFilled,
188 - MicrophoneOffFilled,
189 - FilterRemove,
190 - Moonset,
191 - AlignBoxMiddleRight,
192 - TaskTools,
193 - FaceWinkFilled,
194 - AlignBoxMiddleCenter
195 -} from "@vicons/carbon"
196 -import {
197 - SortAlphaDown,
198 - Chrome,
199 - Envira,
200 - Shopware,
201 - Snapchat,
202 - CommentAlt,
203 - RedditAlien,
204 - Comment,
205 - Mailchimp,
206 - Bone,
207 - Quran,
208 - Download,
209 - Draft2Digital,
210 - Blackberry,
211 - Digg,
212 - Film,
213 - IdBadgeRegular,
214 - FileMedicalAlt,
215 - SortAlphaUpAlt,
216 - CodeBranch,
217 - MehRegular,
218 - DiceThree,
219 - Beer,
220 - Js,
221 - CottonBureau,
222 - CreativeCommonsShare,
223 - Vest,
224 - DiceFive,
225 - StoreAltSlash,
226 - Unlink
227 -} from "@vicons/fa"
228 -import {
229 - ChevronUp24Filled,
230 - SquareMultiple20Regular,
231 - PresenceUnknown10Regular,
232 - Gift20Regular,
233 - CheckmarkUnderlineCircle20Filled,
234 - ArrowFit16Regular,
235 - Fingerprint24Filled,
236 - TextClearFormatting16Regular,
237 - ReadingListAdd16Regular,
238 - DrinkCoffee20Filled,
239 - FolderLink20Filled,
240 - History24Filled,
241 - DataBarHorizontal20Regular,
242 - News28Regular,
243 - BookLetter24Filled,
244 - SportHockey24Regular,
245 - Oval32Regular,
246 - ArrowUpLeft16Filled,
247 - Home48Filled,
248 - LineDashes20Regular,
249 - MailArrowDoubleBack16Regular,
250 - TabInPrivate16Filled,
251 - ChatHelp20Filled,
252 - EditOff20Filled,
253 - PanelRightContract16Regular,
254 - WeatherMoonOff24Regular,
255 - Dentist12Regular,
256 - Handshake16Filled,
257 - DeleteArrowBack16Regular,
258 - TableMoveLeft16Regular
259 -} from "@vicons/fluent"
260 -import {
261 - LogoWindows,
262 - EarthSharp,
263 - CaretForwardCircleSharp,
264 - GitBranchSharp,
265 - LogoEuro,
266 - EllipsisHorizontalSharp,
267 - PlayForward,
268 - FootstepsSharp,
269 - BagAddSharp,
270 - SendOutline,
271 - BackspaceSharp,
272 - BagCheck,
273 - BatteryDead,
274 - LogoAppleAppstore,
275 - RefreshCircleSharp,
276 - ShirtSharp,
277 - InvertModeOutline,
278 - PulseOutline,
279 - PawOutline,
280 - SwapHorizontal,
281 - PinOutline,
282 - CheckmarkDoneCircle,
283 - CloudOfflineSharp,
284 - CodeDownloadSharp,
285 - Barcode,
286 - CheckmarkDoneSharp,
287 - CaretUpSharp,
288 - LaptopOutline,
289 - LocationOutline,
290 - PlaySkipBackCircleOutline
291 -} from "@vicons/ionicons5"
292 -import {
293 - PinOffTwotone,
294 - CategoryFilled,
295 - MoneyRound,
296 - FiberSmartRecordRound,
297 - HourglassBottomTwotone,
298 - AddToDriveRound,
299 - LocalCarWashFilled,
300 - KeyboardArrowRightRound,
301 - MonitorHeartFilled,
302 - KeyboardOptionKeySharp,
303 - ChurchSharp,
304 - Md123Round,
305 - FilterTiltShiftTwotone,
306 - KeyboardArrowDownRound,
307 - VpnKeyRound,
308 - ShieldFilled,
309 - ExploreOffRound,
310 - SupportAgentRound,
311 - NotStartedTwotone,
312 - LocalOfferSharp,
313 - AudioFileFilled,
314 - ArrowForwardIosSharp,
315 - DoorBackFilled,
316 - AirlineSeatReclineNormalOutlined,
317 - CurrencyBitcoinTwotone,
318 - VideogameAssetOutlined,
319 - TocOutlined,
320 - Md7KOutlined,
321 - SensorsFilled,
322 - WifiProtectedSetupOutlined
323 -} from "@vicons/material"
324 -import {
325 - ArrowBigUpLine,
326 - FileDiff,
327 - Smoking,
328 - ArrowsDoubleSwNe,
329 - Atom,
330 - Messages,
331 - Space,
332 - Separator,
333 - Home,
334 - Stars,
335 - Trash,
336 - SquareForbid2,
337 - BellMinus,
338 - SoccerField,
339 - Ruler2,
340 - BrandOpera,
341 - PlaneArrival,
342 - FileMusic,
343 - Barbell,
344 - PictureInPicture,
345 - Anchor,
346 - Calendar,
347 - BoxMultiple3,
348 - CurrencyDollarAustralian,
349 - HandLittleFinger,
350 - LetterY,
351 - TrashX,
352 - HandOff,
353 - Pizza,
354 - AntennaBars5
355 -} from "@vicons/tabler"
356 -import { defineComponent } from "vue"
357 -
358 -export default defineComponent({
359 - name: "Xicons",
360 - data: () => ({
361 - Carbon,
362 - Fa,
363 - Fluent,
364 - Ionicons5,
365 - Material,
366 - Tabler
367 - }),
368 - components: {
369 - NIcon,
370 - NCard,
371 - NButton,
372 - StringInteger,
373 - DocumentWordProcessorReference,
374 - CategoryNewEach,
375 - WarningAlt,
376 - Stethoscope,
377 - KeepDry,
378 - NoImage,
379 - GroupObjects,
380 - Information,
381 - Warning,
382 - AccessibilityAlt,
383 - VirtualColumnKey,
384 - OpenPanelFilledRight,
385 - DataBase,
386 - AlignVerticalTop,
387 - ConnectionSignal,
388 - ConnectionSend,
389 - PhoneIncomingFilled,
390 - TemperatureFahrenheitAlt,
391 - PhoneVoice,
392 - Network1,
393 - WintryMix,
394 - PlayFilled,
395 - MicrophoneOffFilled,
396 - FilterRemove,
397 - Moonset,
398 - AlignBoxMiddleRight,
399 - TaskTools,
400 - FaceWinkFilled,
401 - AlignBoxMiddleCenter,
402 - SortAlphaDown,
403 - Chrome,
404 - Envira,
405 - Shopware,
406 - Snapchat,
407 - CommentAlt,
408 - RedditAlien,
409 - Comment,
410 - Mailchimp,
411 - Bone,
412 - Quran,
413 - Download,
414 - Draft2Digital,
415 - Blackberry,
416 - Digg,
417 - Film,
418 - IdBadgeRegular,
419 - FileMedicalAlt,
420 - SortAlphaUpAlt,
421 - CodeBranch,
422 - MehRegular,
423 - DiceThree,
424 - Beer,
425 - Js,
426 - CottonBureau,
427 - CreativeCommonsShare,
428 - Vest,
429 - DiceFive,
430 - StoreAltSlash,
431 - Unlink,
432 - ChevronUp24Filled,
433 - SquareMultiple20Regular,
434 - PresenceUnknown10Regular,
435 - Gift20Regular,
436 - CheckmarkUnderlineCircle20Filled,
437 - ArrowFit16Regular,
438 - Fingerprint24Filled,
439 - TextClearFormatting16Regular,
440 - ReadingListAdd16Regular,
441 - DrinkCoffee20Filled,
442 - FolderLink20Filled,
443 - History24Filled,
444 - DataBarHorizontal20Regular,
445 - News28Regular,
446 - BookLetter24Filled,
447 - SportHockey24Regular,
448 - Oval32Regular,
449 - ArrowUpLeft16Filled,
450 - Home48Filled,
451 - LineDashes20Regular,
452 - MailArrowDoubleBack16Regular,
453 - TabInPrivate16Filled,
454 - ChatHelp20Filled,
455 - EditOff20Filled,
456 - PanelRightContract16Regular,
457 - WeatherMoonOff24Regular,
458 - Dentist12Regular,
459 - Handshake16Filled,
460 - DeleteArrowBack16Regular,
461 - TableMoveLeft16Regular,
462 - LogoWindows,
463 - EarthSharp,
464 - CaretForwardCircleSharp,
465 - GitBranchSharp,
466 - LogoEuro,
467 - EllipsisHorizontalSharp,
468 - PlayForward,
469 - FootstepsSharp,
470 - BagAddSharp,
471 - SendOutline,
472 - BackspaceSharp,
473 - BagCheck,
474 - BatteryDead,
475 - LogoAppleAppstore,
476 - RefreshCircleSharp,
477 - ShirtSharp,
478 - InvertModeOutline,
479 - PulseOutline,
480 - PawOutline,
481 - SwapHorizontal,
482 - PinOutline,
483 - CheckmarkDoneCircle,
484 - CloudOfflineSharp,
485 - CodeDownloadSharp,
486 - Barcode,
487 - CheckmarkDoneSharp,
488 - CaretUpSharp,
489 - LaptopOutline,
490 - LocationOutline,
491 - PlaySkipBackCircleOutline,
492 - PinOffTwotone,
493 - CategoryFilled,
494 - MoneyRound,
495 - FiberSmartRecordRound,
496 - HourglassBottomTwotone,
497 - AddToDriveRound,
498 - LocalCarWashFilled,
499 - KeyboardArrowRightRound,
500 - MonitorHeartFilled,
501 - KeyboardOptionKeySharp,
502 - ChurchSharp,
503 - Md123Round,
504 - FilterTiltShiftTwotone,
505 - KeyboardArrowDownRound,
506 - VpnKeyRound,
507 - ShieldFilled,
508 - ExploreOffRound,
509 - SupportAgentRound,
510 - NotStartedTwotone,
511 - LocalOfferSharp,
512 - AudioFileFilled,
513 - ArrowForwardIosSharp,
514 - DoorBackFilled,
515 - AirlineSeatReclineNormalOutlined,
516 - CurrencyBitcoinTwotone,
517 - VideogameAssetOutlined,
518 - TocOutlined,
519 - Md7KOutlined,
520 - SensorsFilled,
521 - WifiProtectedSetupOutlined,
522 - ArrowBigUpLine,
523 - FileDiff,
524 - Smoking,
525 - ArrowsDoubleSwNe,
526 - Atom,
527 - Messages,
528 - Space,
529 - Separator,
530 - Home,
531 - Stars,
532 - Trash,
533 - SquareForbid2,
534 - BellMinus,
535 - SoccerField,
536 - Ruler2,
537 - BrandOpera,
538 - PlaneArrival,
539 - FileMusic,
540 - Barbell,
541 - PictureInPicture,
542 - Anchor,
543 - Calendar,
544 - BoxMultiple3,
545 - CurrencyDollarAustralian,
546 - HandLittleFinger,
547 - LetterY,
548 - TrashX,
549 - HandOff,
550 - Pizza,
551 - AntennaBars5
552 - }
553 -})
554 -</script>
555 -
556 -<style lang="scss" scoped>
557 -.page {
558 - .main {
559 - .groups {
560 - grid-template-columns: repeat(auto-fit, minmax(380px, 1fr));
561 -
562 - @media (max-width: 450px) {
563 - grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
564 - }
565 - .group {
566 - .list {
567 - margin-top: 10px;
568 - display: grid;
569 - gap: 12px;
570 - align-items: start;
571 - grid-template-columns: repeat(auto-fit, minmax(45px, 1fr));
572 -
573 - .icon-box {
574 - background-color: rgba(var(--bg-body-rgb), 0.5);
575 - border-radius: 10px;
576 - aspect-ratio: 1;
577 - display: flex;
578 - align-items: center;
579 - justify-content: center;
580 - }
581 - }
582 - }
583 - }
584 - }
585 -}
586 -</style>
src/views/Icons/icons-set/carbon.ts deleted
-92
@@ -1,92 +0,0 @@
1 -export default [
2 - {
3 - name: "StringInteger"
4 - },
5 - {
6 - name: "DocumentWordProcessorReference"
7 - },
8 - {
9 - name: "CategoryNewEach"
10 - },
11 - {
12 - name: "WarningAlt"
13 - },
14 - {
15 - name: "Stethoscope"
16 - },
17 - {
18 - name: "KeepDry"
19 - },
20 - {
21 - name: "NoImage"
22 - },
23 - {
24 - name: "GroupObjects"
25 - },
26 - {
27 - name: "Information"
28 - },
29 - {
30 - name: "Warning"
31 - },
32 - {
33 - name: "AccessibilityAlt"
34 - },
35 - {
36 - name: "VirtualColumnKey"
37 - },
38 - {
39 - name: "OpenPanelFilledRight"
40 - },
41 - {
42 - name: "DataBase"
43 - },
44 - {
45 - name: "AlignVerticalTop"
46 - },
47 - {
48 - name: "ConnectionSignal"
49 - },
50 - {
51 - name: "ConnectionSend"
52 - },
53 - {
54 - name: "PhoneIncomingFilled"
55 - },
56 - {
57 - name: "TemperatureFahrenheitAlt"
58 - },
59 - {
60 - name: "PhoneVoice"
61 - },
62 - {
63 - name: "Network1"
64 - },
65 - {
66 - name: "WintryMix"
67 - },
68 - {
69 - name: "PlayFilled"
70 - },
71 - {
72 - name: "MicrophoneOffFilled"
73 - },
74 - {
75 - name: "FilterRemove"
76 - },
77 - {
78 - name: "Moonset"
79 - },
80 - {
81 - name: "AlignBoxMiddleRight"
82 - },
83 - {
84 - name: "TaskTools"
85 - },
86 - {
87 - name: "FaceWinkFilled"
88 - },
89 - {
90 - name: "AlignBoxMiddleCenter"
91 - }
92 -]
src/views/Icons/icons-set/fa.ts deleted
-92
@@ -1,92 +0,0 @@
1 -export default [
2 - {
3 - name: "SortAlphaDown"
4 - },
5 - {
6 - name: "Chrome"
7 - },
8 - {
9 - name: "Envira"
10 - },
11 - {
12 - name: "Shopware"
13 - },
14 - {
15 - name: "Snapchat"
16 - },
17 - {
18 - name: "CommentAlt"
19 - },
20 - {
21 - name: "RedditAlien"
22 - },
23 - {
24 - name: "Comment"
25 - },
26 - {
27 - name: "Mailchimp"
28 - },
29 - {
30 - name: "Bone"
31 - },
32 - {
33 - name: "Quran"
34 - },
35 - {
36 - name: "Download"
37 - },
38 - {
39 - name: "Draft2Digital"
40 - },
41 - {
42 - name: "Blackberry"
43 - },
44 - {
45 - name: "Digg"
46 - },
47 - {
48 - name: "Film"
49 - },
50 - {
51 - name: "IdBadgeRegular"
52 - },
53 - {
54 - name: "FileMedicalAlt"
55 - },
56 - {
57 - name: "SortAlphaUpAlt"
58 - },
59 - {
60 - name: "CodeBranch"
61 - },
62 - {
63 - name: "MehRegular"
64 - },
65 - {
66 - name: "DiceThree"
67 - },
68 - {
69 - name: "Beer"
70 - },
71 - {
72 - name: "Js"
73 - },
74 - {
75 - name: "CottonBureau"
76 - },
77 - {
78 - name: "CreativeCommonsShare"
79 - },
80 - {
81 - name: "Vest"
82 - },
83 - {
84 - name: "DiceFive"
85 - },
86 - {
87 - name: "StoreAltSlash"
88 - },
89 - {
90 - name: "Unlink"
91 - }
92 -]
src/views/Icons/icons-set/flags.ts deleted
-92
@@ -1,92 +0,0 @@
1 -export default [
2 - {
3 - name: "nz"
4 - },
5 - {
6 - name: "md"
7 - },
8 - {
9 - name: "sn"
10 - },
11 - {
12 - name: "it"
13 - },
14 - {
15 - name: "az"
16 - },
17 - {
18 - name: "ws"
19 - },
20 - {
21 - name: "us"
22 - },
23 - {
24 - name: "nc"
25 - },
26 - {
27 - name: "fr"
28 - },
29 - {
30 - name: "by"
31 - },
32 - {
33 - name: "gq"
34 - },
35 - {
36 - name: "es"
37 - },
38 - {
39 - name: "lu"
40 - },
41 - {
42 - name: "mg"
43 - },
44 - {
45 - name: "de"
46 - },
47 - {
48 - name: "vg"
49 - },
50 - {
51 - name: "st"
52 - },
53 - {
54 - name: "cg"
55 - },
56 - {
57 - name: "gb"
58 - },
59 - {
60 - name: "sa"
61 - },
62 - {
63 - name: "gd"
64 - },
65 - {
66 - name: "tv"
67 - },
68 - {
69 - name: "jp"
70 - },
71 - {
72 - name: "ky"
73 - },
74 - {
75 - name: "ne"
76 - },
77 - {
78 - name: "sl"
79 - },
80 - {
81 - name: "ge"
82 - },
83 - {
84 - name: "as"
85 - },
86 - {
87 - name: "sg"
88 - },
89 - {
90 - name: "pa"
91 - }
92 -]
src/views/Icons/icons-set/fluent.ts deleted
-92
@@ -1,92 +0,0 @@
1 -export default [
2 - {
3 - name: "ChevronUp24Filled"
4 - },
5 - {
6 - name: "SquareMultiple20Regular"
7 - },
8 - {
9 - name: "PresenceUnknown10Regular"
10 - },
11 - {
12 - name: "Gift20Regular"
13 - },
14 - {
15 - name: "CheckmarkUnderlineCircle20Filled"
16 - },
17 - {
18 - name: "ArrowFit16Regular"
19 - },
20 - {
21 - name: "Fingerprint24Filled"
22 - },
23 - {
24 - name: "TextClearFormatting16Regular"
25 - },
26 - {
27 - name: "ReadingListAdd16Regular"
28 - },
29 - {
30 - name: "DrinkCoffee20Filled"
31 - },
32 - {
33 - name: "FolderLink20Filled"
34 - },
35 - {
36 - name: "History24Filled"
37 - },
38 - {
39 - name: "DataBarHorizontal20Regular"
40 - },
41 - {
42 - name: "News28Regular"
43 - },
44 - {
45 - name: "BookLetter24Filled"
46 - },
47 - {
48 - name: "SportHockey24Regular"
49 - },
50 - {
51 - name: "Oval32Regular"
52 - },
53 - {
54 - name: "ArrowUpLeft16Filled"
55 - },
56 - {
57 - name: "Home48Filled"
58 - },
59 - {
60 - name: "LineDashes20Regular"
61 - },
62 - {
63 - name: "MailArrowDoubleBack16Regular"
64 - },
65 - {
66 - name: "TabInPrivate16Filled"
67 - },
68 - {
69 - name: "ChatHelp20Filled"
70 - },
71 - {
72 - name: "EditOff20Filled"
73 - },
74 - {
75 - name: "PanelRightContract16Regular"
76 - },
77 - {
78 - name: "WeatherMoonOff24Regular"
79 - },
80 - {
81 - name: "Dentist12Regular"
82 - },
83 - {
84 - name: "Handshake16Filled"
85 - },
86 - {
87 - name: "DeleteArrowBack16Regular"
88 - },
89 - {
90 - name: "TableMoveLeft16Regular"
91 - }
92 -]
src/views/Icons/icons-set/ionicons5.ts deleted
-92
@@ -1,92 +0,0 @@
1 -export default [
2 - {
3 - name: "LogoWindows"
4 - },
5 - {
6 - name: "EarthSharp"
7 - },
8 - {
9 - name: "CaretForwardCircleSharp"
10 - },
11 - {
12 - name: "GitBranchSharp"
13 - },
14 - {
15 - name: "LogoEuro"
16 - },
17 - {
18 - name: "EllipsisHorizontalSharp"
19 - },
20 - {
21 - name: "PlayForward"
22 - },
23 - {
24 - name: "FootstepsSharp"
25 - },
26 - {
27 - name: "BagAddSharp"
28 - },
29 - {
30 - name: "SendOutline"
31 - },
32 - {
33 - name: "BackspaceSharp"
34 - },
35 - {
36 - name: "BagCheck"
37 - },
38 - {
39 - name: "BatteryDead"
40 - },
41 - {
42 - name: "LogoAppleAppstore"
43 - },
44 - {
45 - name: "RefreshCircleSharp"
46 - },
47 - {
48 - name: "ShirtSharp"
49 - },
50 - {
51 - name: "InvertModeOutline"
52 - },
53 - {
54 - name: "PulseOutline"
55 - },
56 - {
57 - name: "PawOutline"
58 - },
59 - {
60 - name: "SwapHorizontal"
61 - },
62 - {
63 - name: "PinOutline"
64 - },
65 - {
66 - name: "CheckmarkDoneCircle"
67 - },
68 - {
69 - name: "CloudOfflineSharp"
70 - },
71 - {
72 - name: "CodeDownloadSharp"
73 - },
74 - {
75 - name: "Barcode"
76 - },
77 - {
78 - name: "CheckmarkDoneSharp"
79 - },
80 - {
81 - name: "CaretUpSharp"
82 - },
83 - {
84 - name: "LaptopOutline"
85 - },
86 - {
87 - name: "LocationOutline"
88 - },
89 - {
90 - name: "PlaySkipBackCircleOutline"
91 - }
92 -]
src/views/Icons/icons-set/material.ts deleted
-92
@@ -1,92 +0,0 @@
1 -export default [
2 - {
3 - name: "PinOffTwotone"
4 - },
5 - {
6 - name: "CategoryFilled"
7 - },
8 - {
9 - name: "MoneyRound"
10 - },
11 - {
12 - name: "FiberSmartRecordRound"
13 - },
14 - {
15 - name: "HourglassBottomTwotone"
16 - },
17 - {
18 - name: "AddToDriveRound"
19 - },
20 - {
21 - name: "LocalCarWashFilled"
22 - },
23 - {
24 - name: "KeyboardArrowRightRound"
25 - },
26 - {
27 - name: "MonitorHeartFilled"
28 - },
29 - {
30 - name: "KeyboardOptionKeySharp"
31 - },
32 - {
33 - name: "ChurchSharp"
34 - },
35 - {
36 - name: "Md123Round"
37 - },
38 - {
39 - name: "FilterTiltShiftTwotone"
40 - },
41 - {
42 - name: "KeyboardArrowDownRound"
43 - },
44 - {
45 - name: "VpnKeyRound"
46 - },
47 - {
48 - name: "ShieldFilled"
49 - },
50 - {
51 - name: "ExploreOffRound"
52 - },
53 - {
54 - name: "SupportAgentRound"
55 - },
56 - {
57 - name: "NotStartedTwotone"
58 - },
59 - {
60 - name: "LocalOfferSharp"
61 - },
62 - {
63 - name: "AudioFileFilled"
64 - },
65 - {
66 - name: "ArrowForwardIosSharp"
67 - },
68 - {
69 - name: "DoorBackFilled"
70 - },
71 - {
72 - name: "AirlineSeatReclineNormalOutlined"
73 - },
74 - {
75 - name: "CurrencyBitcoinTwotone"
76 - },
77 - {
78 - name: "VideogameAssetOutlined"
79 - },
80 - {
81 - name: "TocOutlined"
82 - },
83 - {
84 - name: "Md7KOutlined"
85 - },
86 - {
87 - name: "SensorsFilled"
88 - },
89 - {
90 - name: "WifiProtectedSetupOutlined"
91 - }
92 -]
src/views/Icons/icons-set/tabler.ts deleted
-92
@@ -1,92 +0,0 @@
1 -export default [
2 - {
3 - name: "ArrowBigUpLine"
4 - },
5 - {
6 - name: "FileDiff"
7 - },
8 - {
9 - name: "Smoking"
10 - },
11 - {
12 - name: "ArrowsDoubleSwNe"
13 - },
14 - {
15 - name: "Atom"
16 - },
17 - {
18 - name: "Messages"
19 - },
20 - {
21 - name: "Space"
22 - },
23 - {
24 - name: "Separator"
25 - },
26 - {
27 - name: "Home"
28 - },
29 - {
30 - name: "Stars"
31 - },
32 - {
33 - name: "Trash"
34 - },
35 - {
36 - name: "SquareForbid2"
37 - },
38 - {
39 - name: "BellMinus"
40 - },
41 - {
42 - name: "SoccerField"
43 - },
44 - {
45 - name: "Ruler2"
46 - },
47 - {
48 - name: "BrandOpera"
49 - },
50 - {
51 - name: "PlaneArrival"
52 - },
53 - {
54 - name: "FileMusic"
55 - },
56 - {
57 - name: "Barbell"
58 - },
59 - {
60 - name: "PictureInPicture"
61 - },
62 - {
63 - name: "Anchor"
64 - },
65 - {
66 - name: "Calendar"
67 - },
68 - {
69 - name: "BoxMultiple3"
70 - },
71 - {
72 - name: "CurrencyDollarAustralian"
73 - },
74 - {
75 - name: "HandLittleFinger"
76 - },
77 - {
78 - name: "LetterY"
79 - },
80 - {
81 - name: "TrashX"
82 - },
83 - {
84 - name: "HandOff"
85 - },
86 - {
87 - name: "Pizza"
88 - },
89 - {
90 - name: "AntennaBars5"
91 - }
92 -]
src/views/Layout/FullWidth.vue
+4 -4
@@ -6,18 +6,18 @@
6
7 <style lang="scss" scoped>
8 .spacer {
9 - background: rgba(var(--fg-color-rgb), 0.05);
9 + background: var(--divider-005-color);
10 background: repeating-linear-gradient(
11 -45deg,
12 - rgba(var(--fg-color-rgb), 0.05),
13 - rgba(var(--fg-color-rgb), 0.05) 1px,
12 + var(--divider-005-color),
13 + var(--divider-005-color) 1px,
14 transparent 1px,
15 transparent 20px
16 );
17 width: 100%;
18 height: 200vh;
19 border-radius: 14px;
20 - border: 4px dashed rgba(var(--fg-color-rgb), 0.05);
20 + border: 4px dashed var(--divider-005-color);
21 padding: 30px;
22 }
23 </style>
src/views/Layout/LeftSidebar.vue
+7 -7
@@ -1,6 +1,6 @@
1 <template>
2 <div class="page page-wrapped flex flex-col page-without-footer">
3 - <CardWIthSiderbar>
3 + <PageSplitted>
4 <template #main-toolbar>Main toolbar</template>
5 <template #main-content>
6 <div>Main content scrollable</div>
@@ -13,12 +13,12 @@
13 <div class="spacer mt-5"></div>
14 </template>
15 <template #sidebar-footer>Sidebar footer</template>
16 - </CardWIthSiderbar>
16 + </PageSplitted>
17 </div>
18 </template>
19
20 <script setup lang="ts">
21 -import CardWIthSiderbar from "@/components/CardWIthSiderbar.vue"
21 +import PageSplitted from "@/components/common/PageSplitted.vue"
22 import { useHideLayoutFooter } from "@/composables/useHideLayoutFooter"
23
24 // :has() CSS relational pseudo-class not yet supported by Firefox
@@ -36,18 +36,18 @@ useHideLayoutFooter()
36 }
37
38 .spacer {
39 - background: rgba(var(--fg-color-rgb), 0.05);
39 + background: var(--divider-005-color);
40 background: repeating-linear-gradient(
41 -45deg,
42 - rgba(var(--fg-color-rgb), 0.05),
43 - rgba(var(--fg-color-rgb), 0.05) 1px,
42 + var(--divider-005-color),
43 + var(--divider-005-color) 1px,
44 transparent 1px,
45 transparent 20px
46 );
47 width: 100%;
48 height: 200vh;
49 border-radius: 14px;
50 - border: 4px dashed rgba(var(--fg-color-rgb), 0.05);
50 + border: 4px dashed var(--divider-005-color);
51 opacity: 0.5;
52 }
53 }
src/views/Layout/RightSidebar.vue
+7 -7
@@ -1,6 +1,6 @@
1 <template>
2 <div class="page page-wrapped flex flex-col page-without-footer">
3 - <CardWIthSiderbar sidebar-position="right">
3 + <PageSplitted sidebar-position="right">
4 <template #main-toolbar>Main toolbar</template>
5 <template #main-content>
6 <div>Main content scrollable</div>
@@ -13,12 +13,12 @@
13 <div class="spacer mt-5"></div>
14 </template>
15 <template #sidebar-footer>Sidebar footer</template>
16 - </CardWIthSiderbar>
16 + </PageSplitted>
17 </div>
18 </template>
19
20 <script setup lang="ts">
21 -import CardWIthSiderbar from "@/components/CardWIthSiderbar.vue"
21 +import PageSplitted from "@/components/common/PageSplitted.vue"
22 import { useHideLayoutFooter } from "@/composables/useHideLayoutFooter"
23
24 // :has() CSS relational pseudo-class not yet supported by Firefox
@@ -35,18 +35,18 @@ useHideLayoutFooter()
35 }
36
37 .spacer {
38 - background: rgba(var(--fg-color-rgb), 0.05);
38 + background: var(--divider-005-color);
39 background: repeating-linear-gradient(
40 -45deg,
41 - rgba(var(--fg-color-rgb), 0.05),
42 - rgba(var(--fg-color-rgb), 0.05) 1px,
41 + var(--divider-005-color),
42 + var(--divider-005-color) 1px,
43 transparent 1px,
44 transparent 20px
45 );
46 width: 100%;
47 height: 200vh;
48 border-radius: 14px;
49 - border: 4px dashed rgba(var(--fg-color-rgb), 0.05);
49 + border: 4px dashed var(--divider-005-color);
50 opacity: 0.5;
51 }
52 }
src/views/Maps/GoogleMaps.vue
+5 -12
@@ -3,16 +3,8 @@
3 <div class="page-header">
4 <div class="title">Google Maps</div>
5 <div class="links">
6 - <a
7 - href="https://vue-map.netlify.app/"
8 - target="_blank"
9 - alt="docs"
10 - rel="nofollow noopener noreferrer"
11 - class="ml-4"
12 - >
13 - <n-icon :size="16">
14 - <ExternalIcon />
15 - </n-icon>
6 + <a href="https://vue-map.netlify.app/" target="_blank" alt="docs" rel="nofollow noopener noreferrer">
7 + <Icon :name="ExternalIcon" :size="16" />
8 docs
9 </a>
10 </div>
@@ -33,9 +25,10 @@
25 </div>
26 </template>
27 <script setup lang="ts">
36 -import { NIcon, NCard } from "naive-ui"
37 -import ExternalIcon from "@vicons/tabler/ExternalLink"
28 +import { NCard } from "naive-ui"
29
30 +import Icon from "@/components/common/Icon.vue"
31 +const ExternalIcon = "tabler:external-link"
32 import { ref } from "vue"
33
34 const center = ref({ lat: 42.50974755936583, lng: 11.917505449320428 })
src/views/Maps/Leaflet.vue
+20 -133
@@ -8,11 +8,8 @@
8 target="_blank"
9 alt="docs"
10 rel="nofollow noopener noreferrer"
11 - class="ml-4"
11 >
13 - <n-icon :size="16">
14 - <ExternalIcon />
15 - </n-icon>
12 + <Icon :name="ExternalIcon" :size="16" />
13 docs
14 </a>
15 </div>
@@ -20,144 +17,34 @@
17
18 <n-card>
19 <div style="height: 60vh; width: 100%">
23 - <l-map ref="map" v-model:zoom="zoom" :center="[47.41322, -1.219482]" :useGlobalLeaflet="false">
24 - <!--
25 - <l-tile-layer
26 - url="http://tile.stamen.com/watercolor/{z}/{x}/{y}.jpg"
27 - layer-type="base"
28 - name="Stamen Watercolor"
29 - attribution="Map tiles by <a href='http://stamen.com'>Stamen Design</a>, under <a href='http://creativecommons.org/licenses/by/3.0'>CC BY 3.0</a>. Data by <a href='http://openstreetmap.org'>OpenStreetMap</a>, under <a href='http://creativecommons.org/licenses/by-sa/3.0'>CC BY SA</a>."
30 - />
31 - -->
32 - <l-tile-layer
33 - url="https://stamen-tiles.a.ssl.fastly.net/toner/{z}/{x}/{y}.png"
34 - layer-type="base"
35 - name="Stamen Toner"
36 - attribution="Map tiles by <a href='http://stamen.com'>Stamen Design</a>, under <a href='http://creativecommons.org/licenses/by/3.0'>CC BY 3.0</a>. Data by <a href='http://openstreetmap.org'>OpenStreetMap</a>, under <a href='http://creativecommons.org/licenses/by-sa/3.0'>CC BY SA</a>."
37 - ></l-tile-layer>
38 - <l-tile-layer
39 - url="https://stamen-tiles.a.ssl.fastly.net/terrain/{z}/{x}/{y}.png"
40 - layer-type="base"
41 - name="Stamen Terrain"
42 - attribution="Map tiles by <a href='http://stamen.com'>Stamen Design</a>, under <a href='http://creativecommons.org/licenses/by/3.0'>CC BY 3.0</a>. Data by <a href='http://openstreetmap.org'>OpenStreetMap</a>, under <a href='http://creativecommons.org/licenses/by-sa/3.0'>CC BY SA</a>."
43 - ></l-tile-layer>
44 - <l-tile-layer
45 - url="https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png"
46 - layer-type="base"
47 - name="OpenTopoMap"
48 - attribution="Map data: &copy; <a href='https://www.openstreetmap.org/copyright'>OpenStreetMap</a> contributors, <a href='http://viewfinderpanoramas.org'>SRTM</a> | Map style: &copy; <a href='https://opentopomap.org'>OpenTopoMap</a> (<a href='https://creativecommons.org/licenses/by-sa/3.0/'>CC-BY-SA</a>)"
49 - ></l-tile-layer>
50 - <l-tile-layer
51 - url="https://map1.vis.earthdata.nasa.gov/wmts-webmerc/VIIRS_CityLights_2012/default//GoogleMapsCompatible_Level{maxZoom}/{z}/{y}/{x}.jpg"
52 - layer-type="base"
53 - name="NASA/GSFC/Earth"
54 - attribution="Imagery provided by services from the Global Imagery Browse Services (GIBS), operated by the NASA/GSFC/Earth Science Data and Information System (<a href='https://earthdata.nasa.gov'>ESDIS</a>) with funding provided by NASA/HQ."
55 - :bounds="[
56 - [-85.0511287776, -179.999999975],
57 - [85.0511287776, 179.999999975]
58 - ]"
59 - :minZoom="1"
60 - :maxZoom="8"
61 - ></l-tile-layer>
62 - <l-tile-layer
63 - url="https://cartodb-basemaps-{s}.global.ssl.fastly.net/light_all/{z}/{x}/{y}.png"
64 - layer-type="base"
65 - name="Positron"
66 - attribution="&copy; <a href='https://www.openstreetmap.org/copyright'>OpenStreetMap</a> contributors &copy; <a href='https://carto.com/attributions'>CARTO</a>"
67 - ></l-tile-layer>
68 - <l-tile-layer
69 - url="https://cartodb-basemaps-{s}.global.ssl.fastly.net/dark_all/{z}/{x}/{y}.png"
70 - layer-type="base"
71 - name="Dark Matter"
72 - attribution="&copy; <a href='https://www.openstreetmap.org/copyright'>OpenStreetMap</a> contributors &copy; <a href='https://carto.com/attributions'>CARTO</a>"
73 - ></l-tile-layer>
74 - <l-tile-layer
75 - url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
76 - layer-type="base"
77 - name="OpenStreetMap"
78 - attribution="&copy; <a href='https://www.openstreetmap.org/copyright'>OpenStreetMap</a> contributors"
79 - ></l-tile-layer>
80 -
81 - <l-control-layers />
82 - <l-marker :lat-lng="[47.85549887088562, 10.087190790477521]" draggable>
83 - <l-tooltip>tooltip</l-tooltip>
84 - </l-marker>
85 -
86 - <l-marker :lat-lng="[45.39799982105989, 9.05183645038641]">
87 - <l-icon :icon-url="logo" :icon-size="iconSize" />
88 - </l-marker>
89 -
90 - <l-marker :lat-lng="[46.731739550358135, -1.3987144591730958]" draggable>
91 - <l-popup>popup</l-popup>
92 - </l-marker>
93 -
94 - <l-polyline
95 - :lat-lngs="[
96 - [47.334852, -1.509485],
97 - [47.342596, -1.328731],
98 - [47.241487, -1.190568],
99 - [47.234787, -1.358337]
100 - ]"
101 - color="green"
102 - ></l-polyline>
103 - <l-polygon
104 - :lat-lngs="[
105 - [46.334852, -1.509485],
106 - [46.342596, -1.328731],
107 - [46.241487, -1.190568],
108 - [46.234787, -1.358337]
109 - ]"
110 - color="#41b782"
111 - :fill="true"
112 - :fillOpacity="0.5"
113 - fillColor="#41b782"
114 - />
115 - <l-rectangle
116 - :lat-lngs="[
117 - [46.334852, -1.509485],
118 - [46.342596, -1.328731],
119 - [46.241487, -1.190568],
120 - [46.234787, -1.358337]
121 - ]"
122 - :fill="true"
123 - color="#35495d"
124 - />
125 - <l-rectangle
126 - :bounds="[
127 - [46.334852, -1.190568],
128 - [46.241487, -1.090357]
129 - ]"
130 - >
131 - <l-popup>lol</l-popup>
132 - </l-rectangle>
133 - </l-map>
20 + <Map v-if="mounted" />
21 + <n-spin v-else class="w-full h-full"></n-spin>
22 </div>
23 </n-card>
24 </div>
25 </template>
26 <script setup lang="ts">
139 -import { NIcon, NCard } from "naive-ui"
140 -import ExternalIcon from "@vicons/tabler/ExternalLink"
141 -import logo from "@/assets/images/brand-logo_light.svg?url"
27 +import { NCard, NSpin } from "naive-ui"
28 +import { defineAsyncComponent, onMounted, type Component } from "vue"
29 +import { useThemeStore } from "@/stores/theme"
30
31 +import Icon from "@/components/common/Icon.vue"
32 +const ExternalIcon = "tabler:external-link"
33 import { ref } from "vue"
34
145 -import "leaflet/dist/leaflet.css"
146 -import {
147 - LMap,
148 - LIcon,
149 - LTileLayer,
150 - LMarker,
151 - LControlLayers,
152 - LTooltip,
153 - LPopup,
154 - LPolyline,
155 - LPolygon,
156 - LRectangle
157 -} from "@vue-leaflet/vue-leaflet"
35 +const Map = defineAsyncComponent<Component>(() => import("@/components/maps/leaflet/Map.vue"))
36 +const mounted = ref(false)
37 +const themeStore = useThemeStore()
38 +
39 +onMounted(() => {
40 + const duration = 1000 * themeStore.routerTransitionDuration
41 + const gap = 500
42
159 -const zoom = ref(4)
160 -const iconSize = ref([50, 50])
43 + // TIMEOUT REQUIRED BY PAGE ANIMATION
44 + setTimeout(() => {
45 + mounted.value = true
46 + }, duration + gap)
47 +})
48 </script>
49
50 <style lang="scss" scoped>
src/views/Maps/MapLibre.vue
+17 -189
@@ -8,11 +8,8 @@
8 target="_blank"
9 alt="docs"
10 rel="nofollow noopener noreferrer"
11 - class="ml-4"
11 >
13 - <n-icon :size="16">
14 - <ExternalIcon />
15 - </n-icon>
12 + <Icon :name="ExternalIcon" :size="16" />
13 docs
14 </a>
15 </div>
@@ -20,201 +17,32 @@
17
18 <n-card>
19 <div style="height: 60vh; width: 100%">
23 - <mgl-map ref="map" :center="center" :zoom="zoom" :attribution-control="false" :language="currentLocale">
24 - <mgl-frame-rate-control />
25 - <mgl-fullscreen-control />
26 - <mgl-attribution-control />
27 - <mgl-navigation-control />
28 - <mgl-scale-control />
29 - <mgl-geolocation-control />
30 - <mgl-style-switch-control :map-styles="mapStyles" :position="controlPosition" />
31 - <mgl-marker :coordinates="markerCoordinates" color="#cc0000" :scale="0.5" />
32 - <mgl-geo-json-source source-id="geojson" :data="geoJsonSource">
33 - <mgl-line-layer layer-id="geojson" :layout="layout" :paint="paint" />
34 - </mgl-geo-json-source>
35 -
36 - <mgl-vector-source source-id="libraries" :tiles="librariesSourceTiles">
37 - <mgl-circle-layer
38 - layer-id="libraries"
39 - source-layer="libraries"
40 - :paint="librariesLayerCirclesPaint"
41 - />
42 - </mgl-vector-source>
43 - </mgl-map>
20 + <Map v-if="mounted" />
21 + <n-spin v-else class="w-full h-full"></n-spin>
22 </div>
45 -
46 - <LocaleSelect class="mt-4" />
23 </n-card>
24 </div>
25 </template>
26
27 <script lang="ts" setup>
52 -import { NIcon, NCard } from "naive-ui"
53 -import ExternalIcon from "@vicons/tabler/ExternalLink"
54 -import { computed, ref, onMounted } from "vue"
55 -
56 -import LocaleSelect from "@/components/common/LocaleSelect.vue"
57 -
58 -/*eslint @typescript-eslint/no-unused-vars: "off"*/
59 -import {
60 - MglMap,
61 - MglDefaults,
62 - useMap,
63 - MglCircleLayer,
64 - MglVectorSource,
65 - MglLineLayer,
66 - MglGeoJsonSource,
67 - MglMarker,
68 - MglStyleSwitchControl,
69 - MglButton,
70 - MglCustomControl,
71 - MglGeolocationControl,
72 - MglScaleControl,
73 - MglNavigationControl,
74 - MglAttributionControl,
75 - MglFullscreenControl,
76 - MglFrameRateControl
77 -} from "vue-maplibre-gl"
78 -import type { ValidLanguages, StyleSwitchItem } from "vue-maplibre-gl"
79 -import { type LngLatLike, type LineLayerSpecification, type CircleLayerSpecification } from "maplibre-gl"
80 -import { type Feature } from "geojson"
81 -import { useLocalesStore } from "@/stores/i18n"
82 -
83 -enum Position {
84 - TOP_LEFT = "top-left",
85 - TOP_RIGHT = "top-right",
86 - BOTTOM_LEFT = "bottom-left",
87 - BOTTOM_RIGHT = "bottom-right"
88 -}
89 -
90 -MglDefaults.style = "https://api.maptiler.com/maps/streets/style.json?key=cQX2iET1gmOW38bedbUh"
91 -
92 -const currentLocale = computed(() => useLocalesStore().locale as ValidLanguages)
28 +import { NCard, NSpin } from "naive-ui"
29 +import { useThemeStore } from "@/stores/theme"
30
94 -const mapStyles = [
95 - {
96 - name: "Streets",
97 - label: "Streets",
98 - style: "https://api.maptiler.com/maps/streets/style.json?key=cQX2iET1gmOW38bedbUh"
99 - },
100 - { name: "Basic", label: "Basic", style: "https://api.maptiler.com/maps/basic/style.json?key=cQX2iET1gmOW38bedbUh" },
101 - {
102 - name: "Bright",
103 - label: "Bright",
104 - style: "https://api.maptiler.com/maps/bright/style.json?key=cQX2iET1gmOW38bedbUh"
105 - },
106 - {
107 - name: "Satellite",
108 - label: "Satellite",
109 - style: "https://api.maptiler.com/maps/hybrid/style.json?key=cQX2iET1gmOW38bedbUh"
110 - },
111 - {
112 - name: "Voyager",
113 - label: "Voyager",
114 - style: "https://api.maptiler.com/maps/voyager/style.json?key=cQX2iET1gmOW38bedbUh"
115 - },
116 - {
117 - name: "watercolor",
118 - label: "Water color",
119 - style: {
120 - version: 8,
121 - sources: {
122 - "raster-tiles": {
123 - type: "raster",
124 - tiles: ["https://stamen-tiles.a.ssl.fastly.net/watercolor/{z}/{x}/{y}.jpg"],
125 - tileSize: 256,
126 - attribution:
127 - 'Map tiles by <a target="_top" rel="noopener" href="http://stamen.com">Stamen Design</a>, under <a target="_top" rel="noopener" href="http://creativecommons.org/licenses/by/3.0">CC BY 3.0</a>. Data by <a target="_top" rel="noopener" href="http://openstreetmap.org">OpenStreetMap</a>, under <a target="_top" rel="noopener" href="http://creativecommons.org/licenses/by-sa/3.0">CC BY SA</a>'
128 - }
129 - },
130 - layers: [
131 - {
132 - id: "simple-tiles",
133 - type: "raster",
134 - source: "raster-tiles",
135 - minzoom: 0,
136 - maxzoom: 22
137 - }
138 - ]
139 - }
140 - }
141 -] as StyleSwitchItem[]
31 +import Icon from "@/components/common/Icon.vue"
32 +const ExternalIcon = "tabler:external-link"
33 +import { ref, onMounted, defineAsyncComponent, type Component } from "vue"
34
143 -const geoJsonSource = ref({
144 - type: "Feature",
145 - geometry: {
146 - type: "Polygon",
147 - coordinates: [
148 - [
149 - [-67.13734351262877, 45.137451890638886],
150 - [-66.96466, 44.8097],
151 - [-68.03252, 44.3252],
152 - [-69.06, 43.98],
153 - [-70.11617, 43.68405],
154 - [-70.64573401557249, 43.090083319667144],
155 - [-70.75102474636725, 43.08003225358635],
156 - [-70.79761105007827, 43.21973948828747],
157 - [-70.98176001655037, 43.36789581966826],
158 - [-70.94416541205806, 43.46633942318431],
159 - [-71.08482, 45.3052400000002],
160 - [-70.6600225491012, 45.46022288673396],
161 - [-70.30495378282376, 45.914794623389355],
162 - [-70.00014034695016, 46.69317088478567],
163 - [-69.23708614772835, 47.44777598732787],
164 - [-68.90478084987546, 47.184794623394396],
165 - [-68.23430497910454, 47.35462921812177],
166 - [-67.79035274928509, 47.066248887716995],
167 - [-67.79141211614706, 45.702585354182816],
168 - [-67.13734351262877, 45.137451890638886]
169 - ]
170 - ]
171 - }
172 -} as Feature)
173 -
174 -const librariesSourceTiles = ["https://api.librarydata.uk/libraries/{z}/{x}/{y}.mvt"]
175 -const librariesLayerCirclesPaint = {
176 - "circle-radius": 5,
177 - "circle-color": "#1b5e20"
178 -} as CircleLayerSpecification["paint"]
179 -
180 -const controlPosition = ref(Position.TOP_LEFT)
181 -const markerCoordinates = ref<LngLatLike>([13.377507, 52.516267])
182 -const map = useMap()
183 -
184 -const layout = {
185 - "line-join": "round",
186 - "line-cap": "round"
187 -} as LineLayerSpecification["layout"]
188 -const paint = {
189 - "line-color": "#FF0000",
190 - "line-width": 8
191 -} as LineLayerSpecification["paint"]
192 -
193 -const center = ref<LngLatLike>([10.288107, 49.405078])
194 -const zoom = ref(3)
35 +const Map = defineAsyncComponent<Component>(() => import("@/components/maps/maplibre/Map.vue"))
36 +const mounted = ref(false)
37 +const themeStore = useThemeStore()
38
39 onMounted(() => {
40 + const duration = 1000 * themeStore.routerTransitionDuration
41 + const gap = 500
42 +
43 + // TIMEOUT REQUIRED BY PAGE ANIMATION
44 setTimeout(() => {
198 - if (map.language) {
199 - map.language = "en"
200 - }
201 - setTimeout(() => {
202 - if (map.language) {
203 - map.language = currentLocale.value
204 - }
205 - }, 500)
206 - }, 500)
45 + mounted.value = true
46 + }, duration + gap)
47 })
48 </script>
209 -
210 -<style lang="scss">
211 -@import "maplibre-gl/dist/maplibre-gl.css";
212 -@import "vue-maplibre-gl/src/lib/css/maplibre.scss";
213 -
214 -.maplibregl-ctrl .maplibregl-ctrl-icon svg {
215 - margin: 0 auto;
216 - path {
217 - fill: #333333;
218 - }
219 -}
220 -</style>
src/views/Maps/VectorMap.vue
+7 -16
@@ -3,16 +3,8 @@
3 <div class="page-header">
4 <div class="title">Vector Map</div>
5 <div class="links">
6 - <a
7 - href="https://jvm-docs.vercel.app/"
8 - target="_blank"
9 - alt="docs"
10 - rel="nofollow noopener noreferrer"
11 - class="ml-4"
12 - >
13 - <n-icon :size="16">
14 - <ExternalIcon />
15 - </n-icon>
6 + <a href="https://jvm-docs.vercel.app/" target="_blank" alt="docs" rel="nofollow noopener noreferrer">
7 + <Icon :name="ExternalIcon" :size="16" />
8 docs
9 </a>
10 </div>
@@ -37,16 +29,15 @@
29 </div>
30 </template>
31 <script setup lang="ts">
40 -import { NIcon, NCard, NSpin } from "naive-ui"
41 -import ExternalIcon from "@vicons/tabler/ExternalLink"
42 -// Import your preferred map
43 -import "jsvectormap/dist/maps/world-merc"
32 +import { NCard, NSpin } from "naive-ui"
33
34 +import Icon from "@/components/common/Icon.vue"
35 +const ExternalIcon = "tabler:external-link"
36 import { computed, ref, watchEffect, watch } from "vue"
37 import { useResizeObserver, useWindowSize } from "@vueuse/core"
38 import { useThemeStore } from "@/stores/theme"
39
49 -const style: { [key: string]: any } = computed(() => useThemeStore().style)
40 +const style = computed<{ [key: string]: any }>(() => useThemeStore().style)
41
42 function getOption() {
43 return {
@@ -146,7 +137,7 @@ watchEffect(() => {
137 padding: 0;
138 color: var(--fg-color);
139 background: var(--bg-body);
149 - border: 1px solid rgba(var(--fg-color-rgb), 0.1);
140 + border: 1px solid var(--border-color);
141
142 &.jvm-zoomout {
143 top: 60px;
src/views/MultiLanguage.vue
+1 -23
@@ -15,7 +15,7 @@
15 <a href="#" target="_blank" alt="docs" rel="nofollow noopener noreferrer">template documentation</a>
16 you can find out how it was implemented.
17
18 - <n-card class="demo">
18 + <n-card class="demo mt-6">
19 <LocaleSelect class="mb-5" />
20
21 <p>
@@ -29,25 +29,3 @@
29 import { NCard } from "naive-ui"
30 import LocaleSelect from "@/components/common/LocaleSelect.vue"
31 </script>
32 -
33 -<style lang="scss" scoped>
34 -.page {
35 - .header {
36 - font-family: var(--font-family-display);
37 - margin-bottom: 20px;
38 -
39 - span {
40 - font-weight: bold;
41 - font-size: 30px;
42 - }
43 - }
44 -
45 - a {
46 - font-weight: 500;
47 - }
48 -
49 - .demo {
50 - margin-top: 24px;
51 - }
52 -}
53 -</style>
src/views/NotFound.vue
+12 -5
@@ -1,15 +1,22 @@
1 <template>
2 <div class="page page-wrapped flex items-center justify-center">
3 - <n-result status="404" title="404 Not Found" size="huge"></n-result>
3 + <n-result status="404" title="404 Not Found" size="huge">
4 + <div class="flex justify-center">
5 + <n-button @click="redirect()">Go to home</n-button>
6 + </div>
7 + </n-result>
8 </div>
9 </template>
10
11 <script lang="ts" setup>
8 -import { NResult } from "naive-ui"
12 +import { NResult, NButton } from "naive-ui"
13 +import { useRouter } from "vue-router"
14
10 -defineOptions({
11 - name: "NotFound"
12 -})
15 +const router = useRouter()
16 +
17 +function redirect() {
18 + router.push({ path: "/" })
19 +}
20 </script>
21
22 <style lang="scss" scoped>
src/views/Profile.vue
+12 -25
@@ -9,9 +9,7 @@
9 @crop="setCroppedImage"
10 :placeholder="'Select your profile picture'"
11 >
12 - <n-icon :size="16" class="edit" @click="openCropper()">
13 - <EditIcon />
14 - </n-icon>
12 + <Icon :name="EditIcon" :size="16" class="edit" @click="openCropper()"></Icon>
13 </ImageCropper>
14 </div>
15 <div class="info grow flex flex-col justify-center">
@@ -23,8 +21,8 @@
21 <n-tooltip placement="top">
22 <template #trigger>
23 <div class="tooltip-wrap">
26 - <n-icon><RoleIcon /></n-icon>
27 - <span>Editor</span>
24 + <Icon :name="RoleIcon"></Icon>
25 + <span>{{ userRole }}</span>
26 </div>
27 </template>
28 <span>Role</span>
@@ -34,18 +32,7 @@
32 <n-tooltip placement="top">
33 <template #trigger>
34 <div class="tooltip-wrap">
37 - <n-icon><LocationIcon /></n-icon>
38 - <span>New York No. 1 Lake Park</span>
39 - </div>
40 - </template>
41 - <span>Location</span>
42 - </n-tooltip>
43 - </div>
44 - <div class="item">
45 - <n-tooltip placement="top">
46 - <template #trigger>
47 - <div class="tooltip-wrap">
48 - <n-icon><MailIcon /></n-icon>
35 + <Icon :name="MailIcon"></Icon>
36 <span>sigmund67@gmail.com</span>
37 </div>
38 </template>
@@ -85,23 +72,23 @@
72 </template>
73
74 <script lang="ts" setup>
88 -import { NAvatar, NIcon, NButton, NTooltip, NTab, NTabs, NTabPane, NCard } from "naive-ui"
75 +import { NAvatar, NButton, NTooltip, NTab, NTabs, NTabPane, NCard } from "naive-ui"
76 import { ref } from "vue"
90 -import RoleIcon from "@vicons/tabler/User"
91 -import LocationIcon from "@vicons/tabler/MapPin"
92 -import EditIcon from "@vicons/fluent/Edit16Filled"
93 -import MailIcon from "@vicons/tabler/Mail"
77 import ImageCropper, { type ImageCropperResult } from "@/components/common/ImageCropper.vue"
78 import ProfileActivity from "@/components/profile/ProfileActivity.vue"
79 import ProfileSettings from "@/components/profile/ProfileSettings.vue"
80 +import Icon from "@/components/common/Icon.vue"
81 +import { useAuthStore } from "@/stores/auth"
82
98 -defineOptions({
99 - name: "Profile"
100 -})
83 +const RoleIcon = "tabler:user"
84 +const EditIcon = "uil:image-edit"
85 +const MailIcon = "tabler:mail"
86
87 const tabActive = ref("activity")
88 const propic = ref("/images/avatar-200.jpg")
89
90 +const userRole = useAuthStore().userRoleName
91 +
92 function setCroppedImage(result: ImageCropperResult) {
93 const canvas = result.canvas as HTMLCanvasElement
94 propic.value = canvas.toDataURL()
src/views/Tables/Base.vue
+4 -6
@@ -8,11 +8,8 @@
8 target="_blank"
9 alt="docs"
10 rel="nofollow noopener noreferrer"
11 - class="ml-4"
11 >
13 - <n-icon :size="16">
14 - <ExternalIcon />
15 - </n-icon>
12 + <Icon :name="ExternalIcon" :size="16" />
13 docs
14 </a>
15 </div>
@@ -60,8 +57,9 @@
57 </template>
58
59 <script lang="ts" setup>
63 -import { NIcon, NScrollbar } from "naive-ui"
64 -import ExternalIcon from "@vicons/tabler/ExternalLink"
60 +import { NScrollbar } from "naive-ui"
61 +import Icon from "@/components/common/Icon.vue"
62 +const ExternalIcon = "tabler:external-link"
63 import TableBase from "@/components/tables/Base.vue"
64 </script>
65
src/views/Tables/DataTable.vue
+3 -6
@@ -8,11 +8,8 @@
8 target="_blank"
9 alt="docs"
10 rel="nofollow noopener noreferrer"
11 - class="ml-4"
11 >
13 - <n-icon :size="16">
14 - <ExternalIcon />
15 - </n-icon>
12 + <Icon :name="ExternalIcon" :size="16" />
13 docs
14 </a>
15 </div>
@@ -33,8 +30,8 @@
30 </template>
31
32 <script lang="ts" setup>
36 -import { NIcon } from "naive-ui"
37 -import ExternalIcon from "@vicons/tabler/ExternalLink"
33 +import Icon from "@/components/common/Icon.vue"
34 +const ExternalIcon = "tabler:external-link"
35 import Basic from "./data-tables-components/Basic.vue"
36 import Merge from "./data-tables-components/Merge.vue"
37 import Sorting from "./data-tables-components/Sorting.vue"
src/views/Tables/Grid.vue new
+156
@@ -0,0 +1,156 @@
1 +<template>
2 + <div class="page page-wrapped flex flex-col">
3 + <div class="page-header">
4 + <div class="title">RevoGrid</div>
5 + <div class="links">
6 + <a
7 + href="https://revolist.github.io/revogrid/"
8 + target="_blank"
9 + alt="docs"
10 + rel="nofollow noopener noreferrer"
11 + >
12 + <Icon :name="ExternalIcon" :size="16" />
13 + docs
14 + </a>
15 + </div>
16 + </div>
17 +
18 + <div class="components-list grow scrollbar-styled">
19 + <n-card class="card" content-style="padding: 0;">
20 + <v-grid
21 + class="grid-component"
22 + :autoSizeColumn="true"
23 + :source="source"
24 + :columns="columns"
25 + :columnTypes="columnTypes"
26 + :pinnedTopRows="pinnedTopRows"
27 + :pinnedBottomRows="pinnedBottomRows"
28 + :filter="true"
29 + :theme="theme"
30 + :resize="true"
31 + :range="true"
32 + v-if="mounted"
33 + rowClass="highlighted"
34 + />
35 + <n-spin v-else class="w-full h-full"></n-spin>
36 + </n-card>
37 + </div>
38 + </div>
39 +</template>
40 +
41 +<script setup lang="js">
42 +import { NCard, NSpin } from "naive-ui"
43 +import Icon from "@/components/common/Icon.vue"
44 +const ExternalIcon = "tabler:external-link"
45 +import { generateFakeDataDemo } from "./grid-assets/dataGenerate"
46 +import people from "./grid-assets/peopleSample"
47 +import { computed, ref } from "vue"
48 +
49 +import PluginDate from "./grid-assets/plugin-date"
50 +import PluginSelect from "./grid-assets/plugin-select"
51 +import { useThemeStore } from "@/stores/theme"
52 +
53 +const mounted = ref(false)
54 +const VGrid = defineAsyncComponent(() => import("@revolist/vue3-datagrid"))
55 +const data = generateFakeDataDemo(people, 50, window.innerWidth > 700)
56 +const dataSource = data.source
57 +const dataColumns = data.columns
58 +
59 +const columnTypes = ref({})
60 +const themeStore = useThemeStore()
61 +
62 +const source = ref(dataSource)
63 +const pinnedBottomRows = ref([])
64 +const columns = ref(dataColumns)
65 +const pinnedTopRows = ref([])
66 +
67 +const theme = computed(() => (useThemeStore().isThemeDark ? "darkMaterial" : "material"))
68 +
69 +onMounted(async () => {
70 + const importPluginNumeral = () => import("@revolist/revogrid-column-numeral")
71 +
72 + const PluginNumeral = (await importPluginNumeral()).default
73 +
74 + const select = new PluginSelect()
75 + const numeric = new PluginNumeral()
76 + const date = new PluginDate()
77 +
78 + columnTypes.value = {
79 + select,
80 + numeric,
81 + date
82 + }
83 +
84 + const duration = 1000 * themeStore.routerTransitionDuration
85 + const gap = 500
86 +
87 + // TIMEOUT REQUIRED BY PAGE ANIMATION
88 + setTimeout(() => {
89 + mounted.value = true
90 + }, duration + gap)
91 +})
92 +</script>
93 +
94 +<style scoped lang="scss">
95 +.page {
96 + .components-list {
97 + grid-template-columns: none;
98 +
99 + .card {
100 + height: 100%;
101 + width: 100%;
102 + overflow: hidden;
103 + }
104 +
105 + :deep() {
106 + revo-grid {
107 + height: 100%;
108 + }
109 +
110 + .temp-bg-range {
111 + display: initial !important;
112 + }
113 +
114 + .draggable-wrapper {
115 + background: #fff;
116 + color: black;
117 + }
118 +
119 + revogr-edit {
120 + background: #fff;
121 + color: black;
122 + border: 2px dashed var(--primary-050-color);
123 +
124 + input {
125 + margin: 0px 15px;
126 + width: calc(100% - 30px);
127 + height: 100%;
128 + }
129 + }
130 +
131 + .bubble {
132 + color: #fff;
133 + border: none;
134 + cursor: default;
135 + height: 32px;
136 + display: inline-flex;
137 + outline: 0;
138 + padding: 0 10px;
139 + font-size: 0.8125rem;
140 + box-sizing: border-box;
141 + transition:
142 + background-color 0.3s cubic-bezier(0.4, 0, 0.2, 1) 0ms,
143 + box-shadow 0.3s cubic-bezier(0.4, 0, 0.2, 1) 0ms;
144 + align-items: center;
145 + white-space: nowrap;
146 + border-radius: 16px;
147 + vertical-align: middle;
148 + justify-content: center;
149 + text-decoration: none;
150 + background-color: #e0e0e0;
151 + opacity: 0.7;
152 + }
153 + }
154 + }
155 +}
156 +</style>
src/views/Tables/data-tables-components/LargeData.vue
+1 -2
@@ -6,8 +6,7 @@
6
7 <script lang="ts">
8 import { h, defineComponent } from "vue"
9 -import type { DataTableColumns } from "naive-ui"
10 -import { NDataTable } from "naive-ui"
9 +import { NDataTable, type DataTableColumns } from "naive-ui"
10
11 type RowData = {
12 key: number
src/views/Tables/data-tables-components/Selection.vue
+1 -2
@@ -22,8 +22,7 @@
22
23 <script lang="tsx" setup>
24 import { computed, reactive, ref } from "vue"
25 -import type { DataTableColumns, DataTableRowKey } from "naive-ui"
26 -import { NP, NDataTable, NText, NButton } from "naive-ui"
25 +import { NP, NDataTable, NText, NButton, type DataTableColumns, type DataTableRowKey } from "naive-ui"
26 import { useResizeObserver } from "@vueuse/core"
27 import { faker } from "@faker-js/faker"
28
src/views/Tables/grid-assets/dataGenerate.js new
+178
@@ -0,0 +1,178 @@
1 +function generateHeader(index) {
2 + const asciiFirstLetter = 65
3 + const lettersCount = 26
4 + let div = index + 1
5 + let label = ""
6 + let pos
7 + while (div > 0) {
8 + pos = (div - 1) % lettersCount
9 + label = String.fromCharCode(asciiFirstLetter + pos) + label
10 + div = parseInt(((div - pos) / lettersCount).toString(), 10)
11 + }
12 + return label
13 +}
14 +
15 +function getRandomArbitrary(min, max) {
16 + return parseInt(Math.random() * (max - min) + min)
17 +}
18 +
19 +export default function generateFakeDataObject(rows, colsNumber) {
20 + const defColumns = [
21 + {
22 + name: "Name",
23 + prop: "name",
24 + sortable: true,
25 + order: "asc",
26 + size: 200
27 + },
28 + {
29 + name: "Personal",
30 + children: [
31 + {
32 + name: "Age",
33 + prop: "age",
34 + size: 100
35 + },
36 + {
37 + name: "Eyes",
38 + prop: "eyeColor",
39 + sortable: true,
40 + cellTemplate: (createElement, props) =>
41 + createElement(
42 + "span",
43 + {
44 + class: "bubble",
45 + style: {
46 + backgroundColor: props.model[props.prop]
47 + }
48 + },
49 + props.model[props.prop]
50 + )
51 + }
52 + ]
53 + }
54 + ]
55 + const result = [...rows]
56 + const columns = [...defColumns]
57 +
58 + for (let j = 0; j < colsNumber; j++) {
59 + columns.push({
60 + name: generateHeader(j),
61 + prop: j,
62 + size: 50
63 + })
64 + }
65 +
66 + for (let i in result) {
67 + result[i]["highlighted"] = result[i]["eyeColor"]
68 + for (let j = 0; j < colsNumber; j++) {
69 + result[i][j] = `${i}:${j}`
70 + }
71 + }
72 + const pinnedTopRows = (result[10] && [result[10]]) || []
73 + const pinnedBottomRows = (result[1] && [result[1]]) || []
74 +
75 + return {
76 + source: result,
77 + pinnedTopRows,
78 + pinnedBottomRows,
79 + columns
80 + }
81 +}
82 +
83 +export function generateFakeDataDemo(rows, colsNumber, pinColumn) {
84 + const defColumns = [
85 + {
86 + name: "Name",
87 + prop: "name",
88 + rowDrag: true,
89 + sortable: true,
90 + order: "asc",
91 + pin: "colPinStart",
92 + size: 200
93 + },
94 + {
95 + name: "Personal",
96 + children: [
97 + {
98 + sortable: true,
99 + name: "Age",
100 + prop: "age",
101 + pin: pinColumn ? "colPinEnd" : undefined
102 + },
103 + {
104 + sortable: true,
105 + name: "Company",
106 + prop: "company",
107 + size: 200
108 + },
109 + {
110 + name: "Eyes",
111 + prop: "eyeColor",
112 + sortable: true,
113 + cellTemplate: (createElement, props) =>
114 + createElement(
115 + "span",
116 + {
117 + class: "bubble",
118 + style: {
119 + backgroundColor: props.model[props.prop]
120 + }
121 + },
122 + props.model[props.prop]
123 + )
124 + }
125 + ]
126 + }
127 + ]
128 +
129 + const result = [...rows]
130 + const columns = [...defColumns]
131 + const nameColumn = columns[0]
132 + nameColumn.autoSize = true
133 + nameColumn.name = "Name(autosize)"
134 +
135 + const companies = Object.keys(
136 + rows.reduce((r, p) => {
137 + r[p.company] = p.company
138 + return r
139 + }, {})
140 + )
141 + const companyColumn = columns[1].children[1]
142 + columns[1].children[1] = {
143 + ...companyColumn,
144 + columnType: "select",
145 + source: companies
146 + }
147 +
148 + columns.push({
149 + name: "Birth date",
150 + prop: "date",
151 + columnType: "date",
152 + size: 150
153 + })
154 +
155 + for (let j = 0; j < colsNumber; j++) {
156 + columns.push({
157 + name: generateHeader(j),
158 + prop: j,
159 + columnType: "numeric"
160 + })
161 + }
162 +
163 + for (let i in result) {
164 + result[i]["highlighted"] = result[i]["eyeColor"]
165 + result[i]["date"] = `${getRandomArbitrary(1950, 2020)}-0${getRandomArbitrary(1, 9)}-${getRandomArbitrary(
166 + 10,
167 + 28
168 + )}`
169 + for (let j = 0; j < colsNumber; j++) {
170 + result[i][j] = getRandomArbitrary(0, 10000)
171 + }
172 + }
173 +
174 + return {
175 + source: result,
176 + columns
177 + }
178 +}
src/views/Tables/grid-assets/peopleSample.js new
+4738
@@ -0,0 +1,4738 @@
1 +export default [
2 + {
3 + isActive: true,
4 + age: 37,
5 + eyeColor: "green",
6 + name: "Samantha Molina",
7 + company: "VALPREAL",
8 + email: "samanthamolina@valpreal.com"
9 + },
10 + {
11 + isActive: true,
12 + age: 30,
13 + eyeColor: "green",
14 + name: "Weber Henderson",
15 + company: "ORBALIX",
16 + email: "weberhenderson@orbalix.com"
17 + },
18 + {
19 + isActive: true,
20 + age: 22,
21 + eyeColor: "blue",
22 + name: "Fernandez Young",
23 + company: "IMAGEFLOW",
24 + email: "fernandezyoung@imageflow.com"
25 + },
26 + {
27 + isActive: false,
28 + age: 26,
29 + eyeColor: "brown",
30 + name: "Roxanne Horn",
31 + company: "MEDALERT",
32 + email: "roxannehorn@medalert.com"
33 + },
34 + {
35 + isActive: false,
36 + age: 36,
37 + eyeColor: "green",
38 + name: "Evangelina Landry",
39 + company: "MEDCOM",
40 + email: "evangelinalandry@medcom.com"
41 + },
42 + {
43 + isActive: true,
44 + age: 39,
45 + eyeColor: "green",
46 + name: "Liz Sanchez",
47 + company: "MANTRO",
48 + email: "lizsanchez@mantro.com"
49 + },
50 + {
51 + isActive: true,
52 + age: 40,
53 + eyeColor: "brown",
54 + name: "Parks Heath",
55 + company: "PANZENT",
56 + email: "parksheath@panzent.com"
57 + },
58 + {
59 + isActive: true,
60 + age: 40,
61 + eyeColor: "blue",
62 + name: "Dennis Hunter",
63 + company: "COASH",
64 + email: "dennishunter@coash.com"
65 + },
66 + {
67 + isActive: true,
68 + age: 30,
69 + eyeColor: "green",
70 + name: "Denise Strickland",
71 + company: "CYTREX",
72 + email: "denisestrickland@cytrex.com"
73 + },
74 + {
75 + isActive: true,
76 + age: 30,
77 + eyeColor: "green",
78 + name: "Mcdaniel Donaldson",
79 + company: "ISOTRACK",
80 + email: "mcdanieldonaldson@isotrack.com"
81 + },
82 + {
83 + isActive: true,
84 + age: 25,
85 + eyeColor: "brown",
86 + name: "Dixon Hudson",
87 + company: "ESSENSIA",
88 + email: "dixonhudson@essensia.com"
89 + },
90 + {
91 + isActive: false,
92 + age: 32,
93 + eyeColor: "brown",
94 + name: "Campos Elliott",
95 + company: "ACCIDENCY",
96 + email: "camposelliott@accidency.com"
97 + },
98 + {
99 + isActive: false,
100 + age: 24,
101 + eyeColor: "brown",
102 + name: "Park Rivera",
103 + company: "TOURMANIA",
104 + email: "parkrivera@tourmania.com"
105 + },
106 + {
107 + isActive: true,
108 + age: 28,
109 + eyeColor: "brown",
110 + name: "Price Schmidt",
111 + company: "DIGIPRINT",
112 + email: "priceschmidt@digiprint.com"
113 + },
114 + {
115 + isActive: true,
116 + age: 30,
117 + eyeColor: "green",
118 + name: "Branch Finley",
119 + company: "GEEKOL",
120 + email: "branchfinley@geekol.com"
121 + },
122 + {
123 + isActive: true,
124 + age: 34,
125 + eyeColor: "blue",
126 + name: "Hogan Justice",
127 + company: "SILODYNE",
128 + email: "hoganjustice@silodyne.com"
129 + },
130 + {
131 + isActive: false,
132 + age: 25,
133 + eyeColor: "green",
134 + name: "Carroll Cote",
135 + company: "FIBRODYNE",
136 + email: "carrollcote@fibrodyne.com"
137 + },
138 + {
139 + isActive: true,
140 + age: 21,
141 + eyeColor: "brown",
142 + name: "Hodges Kline",
143 + company: "COMVEYOR",
144 + email: "hodgeskline@comveyor.com"
145 + },
146 + {
147 + isActive: true,
148 + age: 25,
149 + eyeColor: "blue",
150 + name: "Janis Bradshaw",
151 + company: "PARCOE",
152 + email: "janisbradshaw@parcoe.com"
153 + },
154 + {
155 + isActive: false,
156 + age: 35,
157 + eyeColor: "brown",
158 + name: "Ila Harper",
159 + company: "OCEANICA",
160 + email: "ilaharper@oceanica.com"
161 + },
162 + {
163 + isActive: false,
164 + age: 22,
165 + eyeColor: "brown",
166 + name: "Juliette Aguirre",
167 + company: "WAAB",
168 + email: "julietteaguirre@waab.com"
169 + },
170 + {
171 + isActive: false,
172 + age: 20,
173 + eyeColor: "brown",
174 + name: "Dalton Lopez",
175 + company: "SNOWPOKE",
176 + email: "daltonlopez@snowpoke.com"
177 + },
178 + {
179 + isActive: true,
180 + age: 33,
181 + eyeColor: "brown",
182 + name: "Brandi Petersen",
183 + company: "CYTRAK",
184 + email: "brandipetersen@cytrak.com"
185 + },
186 + {
187 + isActive: true,
188 + age: 29,
189 + eyeColor: "green",
190 + name: "Rosalinda Clarke",
191 + company: "VETRON",
192 + email: "rosalindaclarke@vetron.com"
193 + },
194 + {
195 + isActive: true,
196 + age: 20,
197 + eyeColor: "green",
198 + name: "Cheri Potter",
199 + company: "ISOLOGICA",
200 + email: "cheripotter@isologica.com"
201 + },
202 + {
203 + isActive: true,
204 + age: 22,
205 + eyeColor: "blue",
206 + name: "Elnora Alvarado",
207 + company: "EPLOSION",
208 + email: "elnoraalvarado@eplosion.com"
209 + },
210 + {
211 + isActive: false,
212 + age: 27,
213 + eyeColor: "blue",
214 + name: "Pugh Ward",
215 + company: "HONOTRON",
216 + email: "pughward@honotron.com"
217 + },
218 + {
219 + isActive: true,
220 + age: 40,
221 + eyeColor: "brown",
222 + name: "Katie Norman",
223 + company: "PYRAMI",
224 + email: "katienorman@pyrami.com"
225 + },
226 + {
227 + isActive: true,
228 + age: 36,
229 + eyeColor: "brown",
230 + name: "Atkins Mcmillan",
231 + company: "SURETECH",
232 + email: "atkinsmcmillan@suretech.com"
233 + },
234 + {
235 + isActive: false,
236 + age: 20,
237 + eyeColor: "blue",
238 + name: "Daphne Leon",
239 + company: "MEGALL",
240 + email: "daphneleon@megall.com"
241 + },
242 + {
243 + isActive: true,
244 + age: 37,
245 + eyeColor: "blue",
246 + name: "Kelli Mckee",
247 + company: "CEMENTION",
248 + email: "kellimckee@cemention.com"
249 + },
250 + {
251 + isActive: true,
252 + age: 35,
253 + eyeColor: "brown",
254 + name: "Nora Mclean",
255 + company: "OPTIQUE",
256 + email: "noramclean@optique.com"
257 + },
258 + {
259 + isActive: false,
260 + age: 28,
261 + eyeColor: "blue",
262 + name: "Brandy Odonnell",
263 + company: "XURBAN",
264 + email: "brandyodonnell@xurban.com"
265 + },
266 + {
267 + isActive: false,
268 + age: 30,
269 + eyeColor: "green",
270 + name: "Terra Nieves",
271 + company: "STRALOY",
272 + email: "terranieves@straloy.com"
273 + },
274 + {
275 + isActive: true,
276 + age: 28,
277 + eyeColor: "brown",
278 + name: "Gibson Spencer",
279 + company: "NAXDIS",
280 + email: "gibsonspencer@naxdis.com"
281 + },
282 + {
283 + isActive: true,
284 + age: 39,
285 + eyeColor: "green",
286 + name: "Klein Byers",
287 + company: "GINKLE",
288 + email: "kleinbyers@ginkle.com"
289 + },
290 + {
291 + isActive: true,
292 + age: 33,
293 + eyeColor: "brown",
294 + name: "Rhea Carson",
295 + company: "VIOCULAR",
296 + email: "rheacarson@viocular.com"
297 + },
298 + {
299 + isActive: true,
300 + age: 40,
301 + eyeColor: "blue",
302 + name: "Alberta Craig",
303 + company: "HOTCAKES",
304 + email: "albertacraig@hotcakes.com"
305 + },
306 + {
307 + isActive: false,
308 + age: 32,
309 + eyeColor: "blue",
310 + name: "Puckett Gomez",
311 + company: "PROTODYNE",
312 + email: "puckettgomez@protodyne.com"
313 + },
314 + {
315 + isActive: false,
316 + age: 30,
317 + eyeColor: "blue",
318 + name: "Greer Hatfield",
319 + company: "SHEPARD",
320 + email: "greerhatfield@shepard.com"
321 + },
322 + {
323 + isActive: true,
324 + age: 30,
325 + eyeColor: "brown",
326 + name: "Gay Rodgers",
327 + company: "OMATOM",
328 + email: "gayrodgers@omatom.com"
329 + },
330 + {
331 + isActive: true,
332 + age: 38,
333 + eyeColor: "green",
334 + name: "Langley Stark",
335 + company: "AQUAFIRE",
336 + email: "langleystark@aquafire.com"
337 + },
338 + {
339 + isActive: true,
340 + age: 22,
341 + eyeColor: "green",
342 + name: "Yang Campbell",
343 + company: "ZYTREK",
344 + email: "yangcampbell@zytrek.com"
345 + },
346 + {
347 + isActive: true,
348 + age: 28,
349 + eyeColor: "green",
350 + name: "Clemons Crawford",
351 + company: "CAXT",
352 + email: "clemonscrawford@caxt.com"
353 + },
354 + {
355 + isActive: true,
356 + age: 24,
357 + eyeColor: "blue",
358 + name: "Camacho Goodman",
359 + company: "FIREWAX",
360 + email: "camachogoodman@firewax.com"
361 + },
362 + {
363 + isActive: true,
364 + age: 20,
365 + eyeColor: "blue",
366 + name: "Herminia Preston",
367 + company: "SYBIXTEX",
368 + email: "herminiapreston@sybixtex.com"
369 + },
370 + {
371 + isActive: true,
372 + age: 35,
373 + eyeColor: "blue",
374 + name: "Hollie Turner",
375 + company: "CRUSTATIA",
376 + email: "hollieturner@crustatia.com"
377 + },
378 + {
379 + isActive: false,
380 + age: 26,
381 + eyeColor: "blue",
382 + name: "Melinda Kelley",
383 + company: "KINDALOO",
384 + email: "melindakelley@kindaloo.com"
385 + },
386 + {
387 + isActive: false,
388 + age: 38,
389 + eyeColor: "brown",
390 + name: "Gladys Dunn",
391 + company: "DANCERITY",
392 + email: "gladysdunn@dancerity.com"
393 + },
394 + {
395 + isActive: false,
396 + age: 23,
397 + eyeColor: "brown",
398 + name: "Deleon Marsh",
399 + company: "ROUGHIES",
400 + email: "deleonmarsh@roughies.com"
401 + },
402 + {
403 + isActive: true,
404 + age: 37,
405 + eyeColor: "green",
406 + name: "Lauri Osborn",
407 + company: "EQUITOX",
408 + email: "lauriosborn@equitox.com"
409 + },
410 + {
411 + isActive: true,
412 + age: 29,
413 + eyeColor: "brown",
414 + name: "Blair Cooper",
415 + company: "VERBUS",
416 + email: "blaircooper@verbus.com"
417 + },
418 + {
419 + isActive: false,
420 + age: 21,
421 + eyeColor: "brown",
422 + name: "Valenzuela Myers",
423 + company: "HALAP",
424 + email: "valenzuelamyers@halap.com"
425 + },
426 + {
427 + isActive: true,
428 + age: 29,
429 + eyeColor: "blue",
430 + name: "Sykes Giles",
431 + company: "NETPLODE",
432 + email: "sykesgiles@netplode.com"
433 + },
434 + {
435 + isActive: false,
436 + age: 25,
437 + eyeColor: "green",
438 + name: "Middleton Scott",
439 + company: "PAPRICUT",
440 + email: "middletonscott@papricut.com"
441 + },
442 + {
443 + isActive: true,
444 + age: 30,
445 + eyeColor: "blue",
446 + name: "Maxine Wynn",
447 + company: "XYQAG",
448 + email: "maxinewynn@xyqag.com"
449 + },
450 + {
451 + isActive: true,
452 + age: 24,
453 + eyeColor: "blue",
454 + name: "Lorna Duke",
455 + company: "YOGASM",
456 + email: "lornaduke@yogasm.com"
457 + },
458 + {
459 + isActive: false,
460 + age: 25,
461 + eyeColor: "brown",
462 + name: "Isabelle Shelton",
463 + company: "QUALITEX",
464 + email: "isabelleshelton@qualitex.com"
465 + },
466 + {
467 + isActive: true,
468 + age: 21,
469 + eyeColor: "brown",
470 + name: "Ida Shepherd",
471 + company: "LYRICHORD",
472 + email: "idashepherd@lyrichord.com"
473 + },
474 + {
475 + isActive: false,
476 + age: 37,
477 + eyeColor: "brown",
478 + name: "Sanders Delgado",
479 + company: "AQUASURE",
480 + email: "sandersdelgado@aquasure.com"
481 + },
482 + {
483 + isActive: true,
484 + age: 29,
485 + eyeColor: "brown",
486 + name: "Vasquez Figueroa",
487 + company: "PHORMULA",
488 + email: "vasquezfigueroa@phormula.com"
489 + },
490 + {
491 + isActive: true,
492 + age: 37,
493 + eyeColor: "green",
494 + name: "Bauer Hartman",
495 + company: "ZAPHIRE",
496 + email: "bauerhartman@zaphire.com"
497 + },
498 + {
499 + isActive: false,
500 + age: 27,
501 + eyeColor: "green",
502 + name: "Mills Taylor",
503 + company: "DADABASE",
504 + email: "millstaylor@dadabase.com"
505 + },
506 + {
507 + isActive: true,
508 + age: 22,
509 + eyeColor: "green",
510 + name: "Salinas Middleton",
511 + company: "DIGIAL",
512 + email: "salinasmiddleton@digial.com"
513 + },
514 + {
515 + isActive: false,
516 + age: 21,
517 + eyeColor: "green",
518 + name: "Heidi Flores",
519 + company: "SENSATE",
520 + email: "heidiflores@sensate.com"
521 + },
522 + {
523 + isActive: false,
524 + age: 21,
525 + eyeColor: "brown",
526 + name: "Gloria Langley",
527 + company: "MAGNEATO",
528 + email: "glorialangley@magneato.com"
529 + },
530 + {
531 + isActive: true,
532 + age: 35,
533 + eyeColor: "blue",
534 + name: "Ophelia Sexton",
535 + company: "COWTOWN",
536 + email: "opheliasexton@cowtown.com"
537 + },
538 + {
539 + isActive: false,
540 + age: 39,
541 + eyeColor: "green",
542 + name: "Elvia Keith",
543 + company: "TWIGGERY",
544 + email: "elviakeith@twiggery.com"
545 + },
546 + {
547 + isActive: true,
548 + age: 29,
549 + eyeColor: "blue",
550 + name: "Holt Sullivan",
551 + company: "COLAIRE",
552 + email: "holtsullivan@colaire.com"
553 + },
554 + {
555 + isActive: false,
556 + age: 37,
557 + eyeColor: "brown",
558 + name: "Hewitt Adams",
559 + company: "MUSIX",
560 + email: "hewittadams@musix.com"
561 + },
562 + {
563 + isActive: true,
564 + age: 31,
565 + eyeColor: "blue",
566 + name: "Arnold Foreman",
567 + company: "HINWAY",
568 + email: "arnoldforeman@hinway.com"
569 + },
570 + {
571 + isActive: true,
572 + age: 27,
573 + eyeColor: "green",
574 + name: "Elizabeth Kaufman",
575 + company: "XERONK",
576 + email: "elizabethkaufman@xeronk.com"
577 + },
578 + {
579 + isActive: true,
580 + age: 34,
581 + eyeColor: "green",
582 + name: "Holder Riley",
583 + company: "COMBOGEN",
584 + email: "holderriley@combogen.com"
585 + },
586 + {
587 + isActive: true,
588 + age: 25,
589 + eyeColor: "green",
590 + name: "Deloris Dickerson",
591 + company: "PROWASTE",
592 + email: "delorisdickerson@prowaste.com"
593 + },
594 + {
595 + isActive: false,
596 + age: 22,
597 + eyeColor: "blue",
598 + name: "Melva Schwartz",
599 + company: "QUINEX",
600 + email: "melvaschwartz@quinex.com"
601 + },
602 + {
603 + isActive: false,
604 + age: 23,
605 + eyeColor: "blue",
606 + name: "Natalia Decker",
607 + company: "ASSISTIA",
608 + email: "nataliadecker@assistia.com"
609 + },
610 + {
611 + isActive: true,
612 + age: 40,
613 + eyeColor: "brown",
614 + name: "Hooper Whitfield",
615 + company: "PLASMOS",
616 + email: "hooperwhitfield@plasmos.com"
617 + },
618 + {
619 + isActive: true,
620 + age: 26,
621 + eyeColor: "green",
622 + name: "Cathryn Galloway",
623 + company: "ZILLACTIC",
624 + email: "cathryngalloway@zillactic.com"
625 + },
626 + {
627 + isActive: false,
628 + age: 28,
629 + eyeColor: "brown",
630 + name: "Rosanna Hurley",
631 + company: "SOLAREN",
632 + email: "rosannahurley@solaren.com"
633 + },
634 + {
635 + isActive: true,
636 + age: 31,
637 + eyeColor: "green",
638 + name: "Pauline Sharpe",
639 + company: "TERRASYS",
640 + email: "paulinesharpe@terrasys.com"
641 + },
642 + {
643 + isActive: false,
644 + age: 21,
645 + eyeColor: "green",
646 + name: "Villarreal Vasquez",
647 + company: "PLEXIA",
648 + email: "villarrealvasquez@plexia.com"
649 + },
650 + {
651 + isActive: false,
652 + age: 28,
653 + eyeColor: "blue",
654 + name: "Dollie Lott",
655 + company: "SUREPLEX",
656 + email: "dollielott@sureplex.com"
657 + },
658 + {
659 + isActive: true,
660 + age: 38,
661 + eyeColor: "brown",
662 + name: "Snyder Patel",
663 + company: "XIXAN",
664 + email: "snyderpatel@xixan.com"
665 + },
666 + {
667 + isActive: false,
668 + age: 31,
669 + eyeColor: "brown",
670 + name: "Wilkinson Wilder",
671 + company: "KONGENE",
672 + email: "wilkinsonwilder@kongene.com"
673 + },
674 + {
675 + isActive: true,
676 + age: 26,
677 + eyeColor: "green",
678 + name: "Acosta Haley",
679 + company: "FARMAGE",
680 + email: "acostahaley@farmage.com"
681 + },
682 + {
683 + isActive: true,
684 + age: 24,
685 + eyeColor: "blue",
686 + name: "Sasha Savage",
687 + company: "DIGIQUE",
688 + email: "sashasavage@digique.com"
689 + },
690 + {
691 + isActive: false,
692 + age: 23,
693 + eyeColor: "green",
694 + name: "Richards Richmond",
695 + company: "ENERFORCE",
696 + email: "richardsrichmond@enerforce.com"
697 + },
698 + {
699 + isActive: true,
700 + age: 29,
701 + eyeColor: "green",
702 + name: "Jenifer Brown",
703 + company: "ZYTRAC",
704 + email: "jeniferbrown@zytrac.com"
705 + },
706 + {
707 + isActive: false,
708 + age: 38,
709 + eyeColor: "blue",
710 + name: "Lavonne Fields",
711 + company: "VANTAGE",
712 + email: "lavonnefields@vantage.com"
713 + },
714 + {
715 + isActive: false,
716 + age: 29,
717 + eyeColor: "blue",
718 + name: "Kathy Macdonald",
719 + company: "HOMELUX",
720 + email: "kathymacdonald@homelux.com"
721 + },
722 + {
723 + isActive: false,
724 + age: 24,
725 + eyeColor: "green",
726 + name: "Mccoy Solis",
727 + company: "ROCKABYE",
728 + email: "mccoysolis@rockabye.com"
729 + },
730 + {
731 + isActive: true,
732 + age: 22,
733 + eyeColor: "blue",
734 + name: "Mckee Dominguez",
735 + company: "MICRONAUT",
736 + email: "mckeedominguez@micronaut.com"
737 + },
738 + {
739 + isActive: true,
740 + age: 22,
741 + eyeColor: "brown",
742 + name: "Bentley Herring",
743 + company: "CENTREGY",
744 + email: "bentleyherring@centregy.com"
745 + },
746 + {
747 + isActive: true,
748 + age: 24,
749 + eyeColor: "blue",
750 + name: "Patterson Cleveland",
751 + company: "BOILICON",
752 + email: "pattersoncleveland@boilicon.com"
753 + },
754 + {
755 + isActive: false,
756 + age: 20,
757 + eyeColor: "brown",
758 + name: "Mollie Mcneil",
759 + company: "KRAG",
760 + email: "molliemcneil@krag.com"
761 + },
762 + {
763 + isActive: false,
764 + age: 30,
765 + eyeColor: "blue",
766 + name: "Elinor Shepard",
767 + company: "ZOUNDS",
768 + email: "elinorshepard@zounds.com"
769 + },
770 + {
771 + isActive: true,
772 + age: 28,
773 + eyeColor: "green",
774 + name: "Cain Carter",
775 + company: "SQUISH",
776 + email: "caincarter@squish.com"
777 + },
778 + {
779 + isActive: false,
780 + age: 39,
781 + eyeColor: "blue",
782 + name: "Albert Velasquez",
783 + company: "GINKOGENE",
784 + email: "albertvelasquez@ginkogene.com"
785 + },
786 + {
787 + isActive: true,
788 + age: 28,
789 + eyeColor: "brown",
790 + name: "Berg Chapman",
791 + company: "EARWAX",
792 + email: "bergchapman@earwax.com"
793 + },
794 + {
795 + isActive: false,
796 + age: 40,
797 + eyeColor: "blue",
798 + name: "Clare Owens",
799 + company: "XSPORTS",
800 + email: "clareowens@xsports.com"
801 + },
802 + {
803 + isActive: false,
804 + age: 29,
805 + eyeColor: "blue",
806 + name: "Ward Chen",
807 + company: "WATERBABY",
808 + email: "wardchen@waterbaby.com"
809 + },
810 + {
811 + isActive: true,
812 + age: 29,
813 + eyeColor: "green",
814 + name: "Smith Oneil",
815 + company: "BITTOR",
816 + email: "smithoneil@bittor.com"
817 + },
818 + {
819 + isActive: true,
820 + age: 33,
821 + eyeColor: "brown",
822 + name: "Jenna Santos",
823 + company: "GYNK",
824 + email: "jennasantos@gynk.com"
825 + },
826 + {
827 + isActive: true,
828 + age: 21,
829 + eyeColor: "blue",
830 + name: "Sullivan Morris",
831 + company: "POWERNET",
832 + email: "sullivanmorris@powernet.com"
833 + },
834 + {
835 + isActive: true,
836 + age: 23,
837 + eyeColor: "green",
838 + name: "Moore Page",
839 + company: "GONKLE",
840 + email: "moorepage@gonkle.com"
841 + },
842 + {
843 + isActive: true,
844 + age: 37,
845 + eyeColor: "blue",
846 + name: "Reyna Pope",
847 + company: "FRANSCENE",
848 + email: "reynapope@franscene.com"
849 + },
850 + {
851 + isActive: false,
852 + age: 25,
853 + eyeColor: "green",
854 + name: "Janine Potts",
855 + company: "COMVEYER",
856 + email: "janinepotts@comveyer.com"
857 + },
858 + {
859 + isActive: true,
860 + age: 27,
861 + eyeColor: "brown",
862 + name: "Kelsey Wade",
863 + company: "ZINCA",
864 + email: "kelseywade@zinca.com"
865 + },
866 + {
867 + isActive: false,
868 + age: 20,
869 + eyeColor: "brown",
870 + name: "Mullins Huff",
871 + company: "XTH",
872 + email: "mullinshuff@xth.com"
873 + },
874 + {
875 + isActive: true,
876 + age: 21,
877 + eyeColor: "green",
878 + name: "Rosetta Silva",
879 + company: "ACIUM",
880 + email: "rosettasilva@acium.com"
881 + },
882 + {
883 + isActive: false,
884 + age: 26,
885 + eyeColor: "blue",
886 + name: "Letitia Wood",
887 + company: "APEXIA",
888 + email: "letitiawood@apexia.com"
889 + },
890 + {
891 + isActive: false,
892 + age: 32,
893 + eyeColor: "blue",
894 + name: "Annabelle Jensen",
895 + company: "ARTWORLDS",
896 + email: "annabellejensen@artworlds.com"
897 + },
898 + {
899 + isActive: true,
900 + age: 40,
901 + eyeColor: "brown",
902 + name: "Ashlee Moon",
903 + company: "ENTALITY",
904 + email: "ashleemoon@entality.com"
905 + },
906 + {
907 + isActive: false,
908 + age: 21,
909 + eyeColor: "green",
910 + name: "Candy Rush",
911 + company: "EXOTERIC",
912 + email: "candyrush@exoteric.com"
913 + },
914 + {
915 + isActive: false,
916 + age: 20,
917 + eyeColor: "brown",
918 + name: "Young Bass",
919 + company: "KYAGURU",
920 + email: "youngbass@kyaguru.com"
921 + },
922 + {
923 + isActive: false,
924 + age: 31,
925 + eyeColor: "brown",
926 + name: "Luz Edwards",
927 + company: "ENTROFLEX",
928 + email: "luzedwards@entroflex.com"
929 + },
930 + {
931 + isActive: true,
932 + age: 31,
933 + eyeColor: "green",
934 + name: "Brianna Deleon",
935 + company: "ACCUPHARM",
936 + email: "briannadeleon@accupharm.com"
937 + },
938 + {
939 + isActive: true,
940 + age: 32,
941 + eyeColor: "green",
942 + name: "Terrie Hurst",
943 + company: "BEZAL",
944 + email: "terriehurst@bezal.com"
945 + },
946 + {
947 + isActive: false,
948 + age: 21,
949 + eyeColor: "green",
950 + name: "Fowler Allison",
951 + company: "ZAGGLES",
952 + email: "fowlerallison@zaggles.com"
953 + },
954 + {
955 + isActive: true,
956 + age: 31,
957 + eyeColor: "blue",
958 + name: "Rosa Walsh",
959 + company: "FLEXIGEN",
960 + email: "rosawalsh@flexigen.com"
961 + },
962 + {
963 + isActive: true,
964 + age: 37,
965 + eyeColor: "blue",
966 + name: "Burks Blackwell",
967 + company: "JIMBIES",
968 + email: "burksblackwell@jimbies.com"
969 + },
970 + {
971 + isActive: false,
972 + age: 36,
973 + eyeColor: "green",
974 + name: "Gracie Kelly",
975 + company: "IMANT",
976 + email: "graciekelly@imant.com"
977 + },
978 + {
979 + isActive: true,
980 + age: 37,
981 + eyeColor: "brown",
982 + name: "Willie Beasley",
983 + company: "QUILM",
984 + email: "williebeasley@quilm.com"
985 + },
986 + {
987 + isActive: false,
988 + age: 30,
989 + eyeColor: "green",
990 + name: "Shawna Sheppard",
991 + company: "ISOSWITCH",
992 + email: "shawnasheppard@isoswitch.com"
993 + },
994 + {
995 + isActive: false,
996 + age: 32,
997 + eyeColor: "blue",
998 + name: "Ann George",
999 + company: "GOLOGY",
1000 + email: "anngeorge@gology.com"
1001 + },
1002 + {
1003 + isActive: true,
1004 + age: 21,
1005 + eyeColor: "brown",
1006 + name: "Holmes Ortega",
1007 + company: "GEOSTELE",
1008 + email: "holmesortega@geostele.com"
1009 + },
1010 + {
1011 + isActive: false,
1012 + age: 23,
1013 + eyeColor: "green",
1014 + name: "Barr Miranda",
1015 + company: "POLARIUM",
1016 + email: "barrmiranda@polarium.com"
1017 + },
1018 + {
1019 + isActive: false,
1020 + age: 21,
1021 + eyeColor: "blue",
1022 + name: "English Mccall",
1023 + company: "FARMEX",
1024 + email: "englishmccall@farmex.com"
1025 + },
1026 + {
1027 + isActive: false,
1028 + age: 32,
1029 + eyeColor: "blue",
1030 + name: "Jessica Tanner",
1031 + company: "PHEAST",
1032 + email: "jessicatanner@pheast.com"
1033 + },
1034 + {
1035 + isActive: false,
1036 + age: 37,
1037 + eyeColor: "brown",
1038 + name: "Consuelo Terrell",
1039 + company: "MONDICIL",
1040 + email: "consueloterrell@mondicil.com"
1041 + },
1042 + {
1043 + isActive: false,
1044 + age: 21,
1045 + eyeColor: "blue",
1046 + name: "Freda Martinez",
1047 + company: "ORBEAN",
1048 + email: "fredamartinez@orbean.com"
1049 + },
1050 + {
1051 + isActive: false,
1052 + age: 29,
1053 + eyeColor: "brown",
1054 + name: "Spencer Floyd",
1055 + company: "REMOLD",
1056 + email: "spencerfloyd@remold.com"
1057 + },
1058 + {
1059 + isActive: true,
1060 + age: 22,
1061 + eyeColor: "brown",
1062 + name: "Peggy Hoover",
1063 + company: "XELEGYL",
1064 + email: "peggyhoover@xelegyl.com"
1065 + },
1066 + {
1067 + isActive: true,
1068 + age: 26,
1069 + eyeColor: "brown",
1070 + name: "Cora Rocha",
1071 + company: "ICOLOGY",
1072 + email: "corarocha@icology.com"
1073 + },
1074 + {
1075 + isActive: false,
1076 + age: 24,
1077 + eyeColor: "brown",
1078 + name: "Meyers Bradley",
1079 + company: "ZIGGLES",
1080 + email: "meyersbradley@ziggles.com"
1081 + },
1082 + {
1083 + isActive: true,
1084 + age: 23,
1085 + eyeColor: "green",
1086 + name: "Aileen Lawrence",
1087 + company: "MEMORA",
1088 + email: "aileenlawrence@memora.com"
1089 + },
1090 + {
1091 + isActive: true,
1092 + age: 25,
1093 + eyeColor: "blue",
1094 + name: "Jerry Terry",
1095 + company: "THREDZ",
1096 + email: "jerryterry@thredz.com"
1097 + },
1098 + {
1099 + isActive: false,
1100 + age: 21,
1101 + eyeColor: "brown",
1102 + name: "Guerra Mathews",
1103 + company: "KIDGREASE",
1104 + email: "guerramathews@kidgrease.com"
1105 + },
1106 + {
1107 + isActive: true,
1108 + age: 35,
1109 + eyeColor: "green",
1110 + name: "Cooper Estrada",
1111 + company: "KANGLE",
1112 + email: "cooperestrada@kangle.com"
1113 + },
1114 + {
1115 + isActive: false,
1116 + age: 22,
1117 + eyeColor: "blue",
1118 + name: "Brittany Hogan",
1119 + company: "PAPRIKUT",
1120 + email: "brittanyhogan@paprikut.com"
1121 + },
1122 + {
1123 + isActive: false,
1124 + age: 28,
1125 + eyeColor: "brown",
1126 + name: "Belinda May",
1127 + company: "SPEEDBOLT",
1128 + email: "belindamay@speedbolt.com"
1129 + },
1130 + {
1131 + isActive: true,
1132 + age: 35,
1133 + eyeColor: "green",
1134 + name: "Dominguez Newman",
1135 + company: "AMTAS",
1136 + email: "domingueznewman@amtas.com"
1137 + },
1138 + {
1139 + isActive: true,
1140 + age: 29,
1141 + eyeColor: "green",
1142 + name: "Margo Cabrera",
1143 + company: "ISOLOGIA",
1144 + email: "margocabrera@isologia.com"
1145 + },
1146 + {
1147 + isActive: true,
1148 + age: 29,
1149 + eyeColor: "blue",
1150 + name: "Lourdes Nash",
1151 + company: "MOTOVATE",
1152 + email: "lourdesnash@motovate.com"
1153 + },
1154 + {
1155 + isActive: false,
1156 + age: 39,
1157 + eyeColor: "blue",
1158 + name: "Thornton Harris",
1159 + company: "COMCUR",
1160 + email: "thorntonharris@comcur.com"
1161 + },
1162 + {
1163 + isActive: false,
1164 + age: 28,
1165 + eyeColor: "blue",
1166 + name: "Ladonna Leach",
1167 + company: "XIIX",
1168 + email: "ladonnaleach@xiix.com"
1169 + },
1170 + {
1171 + isActive: false,
1172 + age: 20,
1173 + eyeColor: "blue",
1174 + name: "Dickerson Vazquez",
1175 + company: "LIMAGE",
1176 + email: "dickersonvazquez@limage.com"
1177 + },
1178 + {
1179 + isActive: true,
1180 + age: 35,
1181 + eyeColor: "green",
1182 + name: "Lamb Good",
1183 + company: "JASPER",
1184 + email: "lambgood@jasper.com"
1185 + },
1186 + {
1187 + isActive: true,
1188 + age: 34,
1189 + eyeColor: "green",
1190 + name: "Nannie Dotson",
1191 + company: "AUTOMON",
1192 + email: "nanniedotson@automon.com"
1193 + },
1194 + {
1195 + isActive: true,
1196 + age: 23,
1197 + eyeColor: "brown",
1198 + name: "Williams Reilly",
1199 + company: "ZANITY",
1200 + email: "williamsreilly@zanity.com"
1201 + },
1202 + {
1203 + isActive: false,
1204 + age: 30,
1205 + eyeColor: "brown",
1206 + name: "Mitzi Key",
1207 + company: "EARTHPLEX",
1208 + email: "mitzikey@earthplex.com"
1209 + },
1210 + {
1211 + isActive: true,
1212 + age: 38,
1213 + eyeColor: "brown",
1214 + name: "Cooke Pace",
1215 + company: "ENERSOL",
1216 + email: "cookepace@enersol.com"
1217 + },
1218 + {
1219 + isActive: false,
1220 + age: 24,
1221 + eyeColor: "brown",
1222 + name: "Mckinney Torres",
1223 + company: "INTERODEO",
1224 + email: "mckinneytorres@interodeo.com"
1225 + },
1226 + {
1227 + isActive: true,
1228 + age: 20,
1229 + eyeColor: "blue",
1230 + name: "Cook Espinoza",
1231 + company: "ANIXANG",
1232 + email: "cookespinoza@anixang.com"
1233 + },
1234 + {
1235 + isActive: true,
1236 + age: 27,
1237 + eyeColor: "brown",
1238 + name: "Craft Stein",
1239 + company: "AQUAMATE",
1240 + email: "craftstein@aquamate.com"
1241 + },
1242 + {
1243 + isActive: true,
1244 + age: 24,
1245 + eyeColor: "brown",
1246 + name: "Baker Armstrong",
1247 + company: "ADORNICA",
1248 + email: "bakerarmstrong@adornica.com"
1249 + },
1250 + {
1251 + isActive: false,
1252 + age: 32,
1253 + eyeColor: "brown",
1254 + name: "Gabrielle Hawkins",
1255 + company: "RUGSTARS",
1256 + email: "gabriellehawkins@rugstars.com"
1257 + },
1258 + {
1259 + isActive: false,
1260 + age: 29,
1261 + eyeColor: "brown",
1262 + name: "Day Hayes",
1263 + company: "CENTICE",
1264 + email: "dayhayes@centice.com"
1265 + },
1266 + {
1267 + isActive: false,
1268 + age: 35,
1269 + eyeColor: "brown",
1270 + name: "Terry Ray",
1271 + company: "SULFAX",
1272 + email: "terryray@sulfax.com"
1273 + },
1274 + {
1275 + isActive: true,
1276 + age: 38,
1277 + eyeColor: "blue",
1278 + name: "Austin Clay",
1279 + company: "VIXO",
1280 + email: "austinclay@vixo.com"
1281 + },
1282 + {
1283 + isActive: true,
1284 + age: 38,
1285 + eyeColor: "blue",
1286 + name: "Wood Mooney",
1287 + company: "NIPAZ",
1288 + email: "woodmooney@nipaz.com"
1289 + },
1290 + {
1291 + isActive: true,
1292 + age: 20,
1293 + eyeColor: "blue",
1294 + name: "Regina Dejesus",
1295 + company: "BEADZZA",
1296 + email: "reginadejesus@beadzza.com"
1297 + },
1298 + {
1299 + isActive: false,
1300 + age: 23,
1301 + eyeColor: "green",
1302 + name: "Lynn Bird",
1303 + company: "EXTRAGENE",
1304 + email: "lynnbird@extragene.com"
1305 + },
1306 + {
1307 + isActive: false,
1308 + age: 30,
1309 + eyeColor: "green",
1310 + name: "Bethany Mccarty",
1311 + company: "MACRONAUT",
1312 + email: "bethanymccarty@macronaut.com"
1313 + },
1314 + {
1315 + isActive: true,
1316 + age: 28,
1317 + eyeColor: "blue",
1318 + name: "Vargas Beach",
1319 + company: "ULTRIMAX",
1320 + email: "vargasbeach@ultrimax.com"
1321 + },
1322 + {
1323 + isActive: false,
1324 + age: 27,
1325 + eyeColor: "blue",
1326 + name: "Carver Lynn",
1327 + company: "QUAREX",
1328 + email: "carverlynn@quarex.com"
1329 + },
1330 + {
1331 + isActive: true,
1332 + age: 32,
1333 + eyeColor: "green",
1334 + name: "Berger Stevens",
1335 + company: "XPLOR",
1336 + email: "bergerstevens@xplor.com"
1337 + },
1338 + {
1339 + isActive: true,
1340 + age: 21,
1341 + eyeColor: "green",
1342 + name: "Hayden Moore",
1343 + company: "NETROPIC",
1344 + email: "haydenmoore@netropic.com"
1345 + },
1346 + {
1347 + isActive: false,
1348 + age: 38,
1349 + eyeColor: "brown",
1350 + name: "Mattie Simpson",
1351 + company: "GEOFORMA",
1352 + email: "mattiesimpson@geoforma.com"
1353 + },
1354 + {
1355 + isActive: true,
1356 + age: 31,
1357 + eyeColor: "brown",
1358 + name: "Lester Strong",
1359 + company: "VERTIDE",
1360 + email: "lesterstrong@vertide.com"
1361 + },
1362 + {
1363 + isActive: true,
1364 + age: 39,
1365 + eyeColor: "brown",
1366 + name: "Claire Norton",
1367 + company: "ECLIPTO",
1368 + email: "clairenorton@eclipto.com"
1369 + },
1370 + {
1371 + isActive: true,
1372 + age: 38,
1373 + eyeColor: "brown",
1374 + name: "Sarah Wilkinson",
1375 + company: "XANIDE",
1376 + email: "sarahwilkinson@xanide.com"
1377 + },
1378 + {
1379 + isActive: true,
1380 + age: 38,
1381 + eyeColor: "blue",
1382 + name: "Aguilar Lloyd",
1383 + company: "BRAINCLIP",
1384 + email: "aguilarlloyd@brainclip.com"
1385 + },
1386 + {
1387 + isActive: true,
1388 + age: 25,
1389 + eyeColor: "blue",
1390 + name: "Sandra Hall",
1391 + company: "EXTRAGEN",
1392 + email: "sandrahall@extragen.com"
1393 + },
1394 + {
1395 + isActive: false,
1396 + age: 27,
1397 + eyeColor: "blue",
1398 + name: "Blanche Michael",
1399 + company: "GOLISTIC",
1400 + email: "blanchemichael@golistic.com"
1401 + },
1402 + {
1403 + isActive: true,
1404 + age: 23,
1405 + eyeColor: "brown",
1406 + name: "Judith Greer",
1407 + company: "SENMAO",
1408 + email: "judithgreer@senmao.com"
1409 + },
1410 + {
1411 + isActive: false,
1412 + age: 39,
1413 + eyeColor: "green",
1414 + name: "Tanner Mason",
1415 + company: "ZILLAR",
1416 + email: "tannermason@zillar.com"
1417 + },
1418 + {
1419 + isActive: false,
1420 + age: 33,
1421 + eyeColor: "brown",
1422 + name: "Petersen Conway",
1423 + company: "LYRIA",
1424 + email: "petersenconway@lyria.com"
1425 + },
1426 + {
1427 + isActive: true,
1428 + age: 39,
1429 + eyeColor: "brown",
1430 + name: "Noreen Workman",
1431 + company: "RETROTEX",
1432 + email: "noreenworkman@retrotex.com"
1433 + },
1434 + {
1435 + isActive: true,
1436 + age: 25,
1437 + eyeColor: "green",
1438 + name: "Hamilton Wilson",
1439 + company: "GEEKKO",
1440 + email: "hamiltonwilson@geekko.com"
1441 + },
1442 + {
1443 + isActive: true,
1444 + age: 25,
1445 + eyeColor: "blue",
1446 + name: "Sampson Mcintosh",
1447 + company: "VENOFLEX",
1448 + email: "sampsonmcintosh@venoflex.com"
1449 + },
1450 + {
1451 + isActive: true,
1452 + age: 32,
1453 + eyeColor: "green",
1454 + name: "Chaney Frost",
1455 + company: "TALKOLA",
1456 + email: "chaneyfrost@talkola.com"
1457 + },
1458 + {
1459 + isActive: false,
1460 + age: 29,
1461 + eyeColor: "green",
1462 + name: "Marylou Yang",
1463 + company: "IMAGINART",
1464 + email: "marylouyang@imaginart.com"
1465 + },
1466 + {
1467 + isActive: true,
1468 + age: 37,
1469 + eyeColor: "brown",
1470 + name: "Dorthy Sellers",
1471 + company: "PHARMACON",
1472 + email: "dorthysellers@pharmacon.com"
1473 + },
1474 + {
1475 + isActive: true,
1476 + age: 40,
1477 + eyeColor: "green",
1478 + name: "Leona Mcconnell",
1479 + company: "QABOOS",
1480 + email: "leonamcconnell@qaboos.com"
1481 + },
1482 + {
1483 + isActive: true,
1484 + age: 28,
1485 + eyeColor: "brown",
1486 + name: "Kim Harrington",
1487 + company: "GREEKER",
1488 + email: "kimharrington@greeker.com"
1489 + },
1490 + {
1491 + isActive: false,
1492 + age: 38,
1493 + eyeColor: "blue",
1494 + name: "Cantu Mathis",
1495 + company: "ZENSOR",
1496 + email: "cantumathis@zensor.com"
1497 + },
1498 + {
1499 + isActive: false,
1500 + age: 32,
1501 + eyeColor: "brown",
1502 + name: "Alford Brock",
1503 + company: "CORIANDER",
1504 + email: "alfordbrock@coriander.com"
1505 + },
1506 + {
1507 + isActive: false,
1508 + age: 34,
1509 + eyeColor: "blue",
1510 + name: "Christine Mckay",
1511 + company: "EARTHPURE",
1512 + email: "christinemckay@earthpure.com"
1513 + },
1514 + {
1515 + isActive: false,
1516 + age: 30,
1517 + eyeColor: "blue",
1518 + name: "Winifred Melton",
1519 + company: "INJOY",
1520 + email: "winifredmelton@injoy.com"
1521 + },
1522 + {
1523 + isActive: false,
1524 + age: 32,
1525 + eyeColor: "brown",
1526 + name: "Fletcher Castillo",
1527 + company: "IMKAN",
1528 + email: "fletchercastillo@imkan.com"
1529 + },
1530 + {
1531 + isActive: false,
1532 + age: 37,
1533 + eyeColor: "green",
1534 + name: "Verna Maddox",
1535 + company: "ECOLIGHT",
1536 + email: "vernamaddox@ecolight.com"
1537 + },
1538 + {
1539 + isActive: true,
1540 + age: 24,
1541 + eyeColor: "brown",
1542 + name: "Nichols Haney",
1543 + company: "COMTRACT",
1544 + email: "nicholshaney@comtract.com"
1545 + },
1546 + {
1547 + isActive: true,
1548 + age: 35,
1549 + eyeColor: "green",
1550 + name: "Leanna Rose",
1551 + company: "ZIORE",
1552 + email: "leannarose@ziore.com"
1553 + },
1554 + {
1555 + isActive: true,
1556 + age: 25,
1557 + eyeColor: "brown",
1558 + name: "Dudley Rice",
1559 + company: "MOMENTIA",
1560 + email: "dudleyrice@momentia.com"
1561 + },
1562 + {
1563 + isActive: false,
1564 + age: 22,
1565 + eyeColor: "green",
1566 + name: "Janet Farley",
1567 + company: "BOILCAT",
1568 + email: "janetfarley@boilcat.com"
1569 + },
1570 + {
1571 + isActive: true,
1572 + age: 23,
1573 + eyeColor: "brown",
1574 + name: "Benita Slater",
1575 + company: "MEDIFAX",
1576 + email: "benitaslater@medifax.com"
1577 + },
1578 + {
1579 + isActive: false,
1580 + age: 29,
1581 + eyeColor: "green",
1582 + name: "Wilder Riggs",
1583 + company: "KINETICA",
1584 + email: "wilderriggs@kinetica.com"
1585 + },
1586 + {
1587 + isActive: false,
1588 + age: 30,
1589 + eyeColor: "brown",
1590 + name: "Barker Reyes",
1591 + company: "ZOID",
1592 + email: "barkerreyes@zoid.com"
1593 + },
1594 + {
1595 + isActive: false,
1596 + age: 21,
1597 + eyeColor: "blue",
1598 + name: "Marisa Ratliff",
1599 + company: "PROSURE",
1600 + email: "marisaratliff@prosure.com"
1601 + },
1602 + {
1603 + isActive: true,
1604 + age: 31,
1605 + eyeColor: "blue",
1606 + name: "Mccarthy Daugherty",
1607 + company: "LOTRON",
1608 + email: "mccarthydaugherty@lotron.com"
1609 + },
1610 + {
1611 + isActive: true,
1612 + age: 34,
1613 + eyeColor: "brown",
1614 + name: "Sheri Welch",
1615 + company: "EXOTECHNO",
1616 + email: "sheriwelch@exotechno.com"
1617 + },
1618 + {
1619 + isActive: true,
1620 + age: 31,
1621 + eyeColor: "blue",
1622 + name: "Workman Mccray",
1623 + company: "LIMOZEN",
1624 + email: "workmanmccray@limozen.com"
1625 + },
1626 + {
1627 + isActive: true,
1628 + age: 28,
1629 + eyeColor: "brown",
1630 + name: "Valdez Eaton",
1631 + company: "UPDAT",
1632 + email: "valdezeaton@updat.com"
1633 + },
1634 + {
1635 + isActive: true,
1636 + age: 26,
1637 + eyeColor: "blue",
1638 + name: "Millie Petty",
1639 + company: "QUADEEBO",
1640 + email: "milliepetty@quadeebo.com"
1641 + },
1642 + {
1643 + isActive: false,
1644 + age: 24,
1645 + eyeColor: "brown",
1646 + name: "Richard Baird",
1647 + company: "SKINSERVE",
1648 + email: "richardbaird@skinserve.com"
1649 + },
1650 + {
1651 + isActive: false,
1652 + age: 32,
1653 + eyeColor: "blue",
1654 + name: "Rose Boyle",
1655 + company: "KROG",
1656 + email: "roseboyle@krog.com"
1657 + },
1658 + {
1659 + isActive: true,
1660 + age: 28,
1661 + eyeColor: "green",
1662 + name: "Byrd Dyer",
1663 + company: "CANDECOR",
1664 + email: "byrddyer@candecor.com"
1665 + },
1666 + {
1667 + isActive: true,
1668 + age: 24,
1669 + eyeColor: "green",
1670 + name: "Maureen Hinton",
1671 + company: "ANARCO",
1672 + email: "maureenhinton@anarco.com"
1673 + },
1674 + {
1675 + isActive: false,
1676 + age: 22,
1677 + eyeColor: "green",
1678 + name: "Sharron Ryan",
1679 + company: "ELPRO",
1680 + email: "sharronryan@elpro.com"
1681 + },
1682 + {
1683 + isActive: true,
1684 + age: 34,
1685 + eyeColor: "blue",
1686 + name: "Merrill Hopkins",
1687 + company: "KRAGGLE",
1688 + email: "merrillhopkins@kraggle.com"
1689 + },
1690 + {
1691 + isActive: false,
1692 + age: 22,
1693 + eyeColor: "green",
1694 + name: "Bates Day",
1695 + company: "COMBOT",
1696 + email: "batesday@combot.com"
1697 + },
1698 + {
1699 + isActive: true,
1700 + age: 31,
1701 + eyeColor: "green",
1702 + name: "Mullen Mcknight",
1703 + company: "MAROPTIC",
1704 + email: "mullenmcknight@maroptic.com"
1705 + },
1706 + {
1707 + isActive: true,
1708 + age: 26,
1709 + eyeColor: "green",
1710 + name: "Katheryn Mcleod",
1711 + company: "UNI",
1712 + email: "katherynmcleod@uni.com"
1713 + },
1714 + {
1715 + isActive: false,
1716 + age: 25,
1717 + eyeColor: "brown",
1718 + name: "Sweeney Fitzgerald",
1719 + company: "NIXELT",
1720 + email: "sweeneyfitzgerald@nixelt.com"
1721 + },
1722 + {
1723 + isActive: true,
1724 + age: 20,
1725 + eyeColor: "green",
1726 + name: "Christy Crane",
1727 + company: "CONFRENZY",
1728 + email: "christycrane@confrenzy.com"
1729 + },
1730 + {
1731 + isActive: false,
1732 + age: 30,
1733 + eyeColor: "green",
1734 + name: "Janice Burgess",
1735 + company: "QNEKT",
1736 + email: "janiceburgess@qnekt.com"
1737 + },
1738 + {
1739 + isActive: true,
1740 + age: 38,
1741 + eyeColor: "brown",
1742 + name: "Mayo Raymond",
1743 + company: "NITRACYR",
1744 + email: "mayoraymond@nitracyr.com"
1745 + },
1746 + {
1747 + isActive: true,
1748 + age: 34,
1749 + eyeColor: "green",
1750 + name: "Aida Fleming",
1751 + company: "ZOXY",
1752 + email: "aidafleming@zoxy.com"
1753 + },
1754 + {
1755 + isActive: false,
1756 + age: 37,
1757 + eyeColor: "blue",
1758 + name: "Jimenez Mcfarland",
1759 + company: "TETRATREX",
1760 + email: "jimenezmcfarland@tetratrex.com"
1761 + },
1762 + {
1763 + isActive: false,
1764 + age: 38,
1765 + eyeColor: "brown",
1766 + name: "Mcfarland Beard",
1767 + company: "COMFIRM",
1768 + email: "mcfarlandbeard@comfirm.com"
1769 + },
1770 + {
1771 + isActive: true,
1772 + age: 39,
1773 + eyeColor: "brown",
1774 + name: "Douglas Reed",
1775 + company: "DYNO",
1776 + email: "douglasreed@dyno.com"
1777 + },
1778 + {
1779 + isActive: true,
1780 + age: 32,
1781 + eyeColor: "brown",
1782 + name: "Graciela Humphrey",
1783 + company: "MANUFACT",
1784 + email: "gracielahumphrey@manufact.com"
1785 + },
1786 + {
1787 + isActive: true,
1788 + age: 24,
1789 + eyeColor: "blue",
1790 + name: "Perry Chandler",
1791 + company: "TERRAGEN",
1792 + email: "perrychandler@terragen.com"
1793 + },
1794 + {
1795 + isActive: true,
1796 + age: 40,
1797 + eyeColor: "brown",
1798 + name: "Le Calhoun",
1799 + company: "CUJO",
1800 + email: "lecalhoun@cujo.com"
1801 + },
1802 + {
1803 + isActive: true,
1804 + age: 33,
1805 + eyeColor: "green",
1806 + name: "Barrett Mckinney",
1807 + company: "ENQUILITY",
1808 + email: "barrettmckinney@enquility.com"
1809 + },
1810 + {
1811 + isActive: true,
1812 + age: 39,
1813 + eyeColor: "green",
1814 + name: "Dean Craft",
1815 + company: "MARKETOID",
1816 + email: "deancraft@marketoid.com"
1817 + },
1818 + {
1819 + isActive: true,
1820 + age: 28,
1821 + eyeColor: "blue",
1822 + name: "Shelton Tucker",
1823 + company: "OPTICALL",
1824 + email: "sheltontucker@opticall.com"
1825 + },
1826 + {
1827 + isActive: false,
1828 + age: 27,
1829 + eyeColor: "blue",
1830 + name: "Clarke Hopper",
1831 + company: "SPRINGBEE",
1832 + email: "clarkehopper@springbee.com"
1833 + },
1834 + {
1835 + isActive: true,
1836 + age: 24,
1837 + eyeColor: "blue",
1838 + name: "Vance Tate",
1839 + company: "EXIAND",
1840 + email: "vancetate@exiand.com"
1841 + },
1842 + {
1843 + isActive: false,
1844 + age: 27,
1845 + eyeColor: "blue",
1846 + name: "Mamie Holden",
1847 + company: "ISOPOP",
1848 + email: "mamieholden@isopop.com"
1849 + },
1850 + {
1851 + isActive: true,
1852 + age: 21,
1853 + eyeColor: "brown",
1854 + name: "Latonya Baldwin",
1855 + company: "ZENTIX",
1856 + email: "latonyabaldwin@zentix.com"
1857 + },
1858 + {
1859 + isActive: true,
1860 + age: 37,
1861 + eyeColor: "brown",
1862 + name: "Rosalind Burks",
1863 + company: "SLOFAST",
1864 + email: "rosalindburks@slofast.com"
1865 + },
1866 + {
1867 + isActive: false,
1868 + age: 20,
1869 + eyeColor: "blue",
1870 + name: "Kimberley Porter",
1871 + company: "MAXEMIA",
1872 + email: "kimberleyporter@maxemia.com"
1873 + },
1874 + {
1875 + isActive: true,
1876 + age: 21,
1877 + eyeColor: "brown",
1878 + name: "Joyner Kirk",
1879 + company: "EVIDENDS",
1880 + email: "joynerkirk@evidends.com"
1881 + },
1882 + {
1883 + isActive: true,
1884 + age: 30,
1885 + eyeColor: "brown",
1886 + name: "Noemi Dudley",
1887 + company: "CENTREE",
1888 + email: "noemidudley@centree.com"
1889 + },
1890 + {
1891 + isActive: false,
1892 + age: 29,
1893 + eyeColor: "blue",
1894 + name: "Hebert Grant",
1895 + company: "CENTURIA",
1896 + email: "hebertgrant@centuria.com"
1897 + },
1898 + {
1899 + isActive: true,
1900 + age: 23,
1901 + eyeColor: "green",
1902 + name: "Adams Blake",
1903 + company: "BIZMATIC",
1904 + email: "adamsblake@bizmatic.com"
1905 + },
1906 + {
1907 + isActive: true,
1908 + age: 37,
1909 + eyeColor: "green",
1910 + name: "Peterson Lester",
1911 + company: "EXOBLUE",
1912 + email: "petersonlester@exoblue.com"
1913 + },
1914 + {
1915 + isActive: true,
1916 + age: 37,
1917 + eyeColor: "green",
1918 + name: "Burke Gibson",
1919 + company: "ISOSPHERE",
1920 + email: "burkegibson@isosphere.com"
1921 + },
1922 + {
1923 + isActive: false,
1924 + age: 34,
1925 + eyeColor: "blue",
1926 + name: "Rosemary Koch",
1927 + company: "MAGNEMO",
1928 + email: "rosemarykoch@magnemo.com"
1929 + },
1930 + {
1931 + isActive: false,
1932 + age: 26,
1933 + eyeColor: "green",
1934 + name: "Jodi Marquez",
1935 + company: "INQUALA",
1936 + email: "jodimarquez@inquala.com"
1937 + },
1938 + {
1939 + isActive: true,
1940 + age: 22,
1941 + eyeColor: "green",
1942 + name: "Bishop Atkins",
1943 + company: "QUARX",
1944 + email: "bishopatkins@quarx.com"
1945 + },
1946 + {
1947 + isActive: false,
1948 + age: 28,
1949 + eyeColor: "blue",
1950 + name: "Kennedy Rosa",
1951 + company: "TRIBALOG",
1952 + email: "kennedyrosa@tribalog.com"
1953 + },
1954 + {
1955 + isActive: false,
1956 + age: 30,
1957 + eyeColor: "green",
1958 + name: "Margaret Carroll",
1959 + company: "NIKUDA",
1960 + email: "margaretcarroll@nikuda.com"
1961 + },
1962 + {
1963 + isActive: true,
1964 + age: 30,
1965 + eyeColor: "brown",
1966 + name: "Tonia Mcmahon",
1967 + company: "PIGZART",
1968 + email: "toniamcmahon@pigzart.com"
1969 + },
1970 + {
1971 + isActive: true,
1972 + age: 36,
1973 + eyeColor: "blue",
1974 + name: "Stanton Osborne",
1975 + company: "UNEEQ",
1976 + email: "stantonosborne@uneeq.com"
1977 + },
1978 + {
1979 + isActive: true,
1980 + age: 35,
1981 + eyeColor: "green",
1982 + name: "Antonia Berger",
1983 + company: "IMPERIUM",
1984 + email: "antoniaberger@imperium.com"
1985 + },
1986 + {
1987 + isActive: true,
1988 + age: 31,
1989 + eyeColor: "green",
1990 + name: "Robinson Foster",
1991 + company: "KENEGY",
1992 + email: "robinsonfoster@kenegy.com"
1993 + },
1994 + {
1995 + isActive: true,
1996 + age: 34,
1997 + eyeColor: "green",
1998 + name: "Sparks Gross",
1999 + company: "FRENEX",
2000 + email: "sparksgross@frenex.com"
2001 + },
2002 + {
2003 + isActive: false,
2004 + age: 27,
2005 + eyeColor: "green",
2006 + name: "Alfreda Boone",
2007 + company: "AQUOAVO",
2008 + email: "alfredaboone@aquoavo.com"
2009 + },
2010 + {
2011 + isActive: true,
2012 + age: 25,
2013 + eyeColor: "blue",
2014 + name: "Dianne Huber",
2015 + company: "APPLIDECK",
2016 + email: "diannehuber@applideck.com"
2017 + },
2018 + {
2019 + isActive: true,
2020 + age: 25,
2021 + eyeColor: "brown",
2022 + name: "Edith Alston",
2023 + company: "VELOS",
2024 + email: "edithalston@velos.com"
2025 + },
2026 + {
2027 + isActive: true,
2028 + age: 38,
2029 + eyeColor: "blue",
2030 + name: "Lena Chambers",
2031 + company: "PYRAMIA",
2032 + email: "lenachambers@pyramia.com"
2033 + },
2034 + {
2035 + isActive: false,
2036 + age: 37,
2037 + eyeColor: "brown",
2038 + name: "Mandy Clemons",
2039 + company: "TRIPSCH",
2040 + email: "mandyclemons@tripsch.com"
2041 + },
2042 + {
2043 + isActive: false,
2044 + age: 35,
2045 + eyeColor: "brown",
2046 + name: "Blackwell Russo",
2047 + company: "KOG",
2048 + email: "blackwellrusso@kog.com"
2049 + },
2050 + {
2051 + isActive: false,
2052 + age: 36,
2053 + eyeColor: "brown",
2054 + name: "Ayala Sanford",
2055 + company: "ACRODANCE",
2056 + email: "ayalasanford@acrodance.com"
2057 + },
2058 + {
2059 + isActive: true,
2060 + age: 38,
2061 + eyeColor: "green",
2062 + name: "Catherine Lucas",
2063 + company: "CANOPOLY",
2064 + email: "catherinelucas@canopoly.com"
2065 + },
2066 + {
2067 + isActive: false,
2068 + age: 30,
2069 + eyeColor: "brown",
2070 + name: "Constance Cannon",
2071 + company: "PLAYCE",
2072 + email: "constancecannon@playce.com"
2073 + },
2074 + {
2075 + isActive: true,
2076 + age: 40,
2077 + eyeColor: "blue",
2078 + name: "Lorie Barlow",
2079 + company: "NETUR",
2080 + email: "loriebarlow@netur.com"
2081 + },
2082 + {
2083 + isActive: true,
2084 + age: 36,
2085 + eyeColor: "brown",
2086 + name: "Doyle Roberts",
2087 + company: "EMTRAC",
2088 + email: "doyleroberts@emtrac.com"
2089 + },
2090 + {
2091 + isActive: false,
2092 + age: 26,
2093 + eyeColor: "green",
2094 + name: "Pansy Barnett",
2095 + company: "PETICULAR",
2096 + email: "pansybarnett@peticular.com"
2097 + },
2098 + {
2099 + isActive: false,
2100 + age: 20,
2101 + eyeColor: "brown",
2102 + name: "Helga Guerra",
2103 + company: "SLAMBDA",
2104 + email: "helgaguerra@slambda.com"
2105 + },
2106 + {
2107 + isActive: true,
2108 + age: 27,
2109 + eyeColor: "green",
2110 + name: "Alexis Casey",
2111 + company: "GYNKO",
2112 + email: "alexiscasey@gynko.com"
2113 + },
2114 + {
2115 + isActive: false,
2116 + age: 31,
2117 + eyeColor: "brown",
2118 + name: "Bird Patterson",
2119 + company: "VENDBLEND",
2120 + email: "birdpatterson@vendblend.com"
2121 + },
2122 + {
2123 + isActive: true,
2124 + age: 20,
2125 + eyeColor: "blue",
2126 + name: "Ada Ochoa",
2127 + company: "GENMOM",
2128 + email: "adaochoa@genmom.com"
2129 + },
2130 + {
2131 + isActive: false,
2132 + age: 38,
2133 + eyeColor: "blue",
2134 + name: "Gonzales Meadows",
2135 + company: "STEELTAB",
2136 + email: "gonzalesmeadows@steeltab.com"
2137 + },
2138 + {
2139 + isActive: true,
2140 + age: 22,
2141 + eyeColor: "green",
2142 + name: "Paige Huffman",
2143 + company: "ATOMICA",
2144 + email: "paigehuffman@atomica.com"
2145 + },
2146 + {
2147 + isActive: false,
2148 + age: 29,
2149 + eyeColor: "brown",
2150 + name: "Wendi Levine",
2151 + company: "COSMOSIS",
2152 + email: "wendilevine@cosmosis.com"
2153 + },
2154 + {
2155 + isActive: true,
2156 + age: 31,
2157 + eyeColor: "green",
2158 + name: "Jackie Harvey",
2159 + company: "LOVEPAD",
2160 + email: "jackieharvey@lovepad.com"
2161 + },
2162 + {
2163 + isActive: true,
2164 + age: 25,
2165 + eyeColor: "brown",
2166 + name: "Francis Stokes",
2167 + company: "KOFFEE",
2168 + email: "francisstokes@koffee.com"
2169 + },
2170 + {
2171 + isActive: false,
2172 + age: 24,
2173 + eyeColor: "brown",
2174 + name: "Beatrice Hobbs",
2175 + company: "COMVOY",
2176 + email: "beatricehobbs@comvoy.com"
2177 + },
2178 + {
2179 + isActive: false,
2180 + age: 21,
2181 + eyeColor: "brown",
2182 + name: "Hopper Daniels",
2183 + company: "GAPTEC",
2184 + email: "hopperdaniels@gaptec.com"
2185 + },
2186 + {
2187 + isActive: true,
2188 + age: 39,
2189 + eyeColor: "blue",
2190 + name: "Chase Mcdaniel",
2191 + company: "MUSAPHICS",
2192 + email: "chasemcdaniel@musaphics.com"
2193 + },
2194 + {
2195 + isActive: false,
2196 + age: 40,
2197 + eyeColor: "green",
2198 + name: "Gibbs Zimmerman",
2199 + company: "COMTEST",
2200 + email: "gibbszimmerman@comtest.com"
2201 + },
2202 + {
2203 + isActive: true,
2204 + age: 29,
2205 + eyeColor: "blue",
2206 + name: "Weiss Ayala",
2207 + company: "INTRAWEAR",
2208 + email: "weissayala@intrawear.com"
2209 + },
2210 + {
2211 + isActive: true,
2212 + age: 21,
2213 + eyeColor: "green",
2214 + name: "Bartlett Whitney",
2215 + company: "ZOMBOID",
2216 + email: "bartlettwhitney@zomboid.com"
2217 + },
2218 + {
2219 + isActive: false,
2220 + age: 26,
2221 + eyeColor: "brown",
2222 + name: "Tricia Santiago",
2223 + company: "RAMJOB",
2224 + email: "triciasantiago@ramjob.com"
2225 + },
2226 + {
2227 + isActive: true,
2228 + age: 23,
2229 + eyeColor: "green",
2230 + name: "Deana Rollins",
2231 + company: "LUNCHPAD",
2232 + email: "deanarollins@lunchpad.com"
2233 + },
2234 + {
2235 + isActive: true,
2236 + age: 40,
2237 + eyeColor: "blue",
2238 + name: "Lynne Singleton",
2239 + company: "CHORIZON",
2240 + email: "lynnesingleton@chorizon.com"
2241 + },
2242 + {
2243 + isActive: false,
2244 + age: 30,
2245 + eyeColor: "brown",
2246 + name: "Arlene Ellis",
2247 + company: "FROLIX",
2248 + email: "arleneellis@frolix.com"
2249 + },
2250 + {
2251 + isActive: true,
2252 + age: 32,
2253 + eyeColor: "brown",
2254 + name: "Schultz Cook",
2255 + company: "GRACKER",
2256 + email: "schultzcook@gracker.com"
2257 + },
2258 + {
2259 + isActive: true,
2260 + age: 25,
2261 + eyeColor: "green",
2262 + name: "Simon Hodges",
2263 + company: "MANGELICA",
2264 + email: "simonhodges@mangelica.com"
2265 + },
2266 + {
2267 + isActive: true,
2268 + age: 40,
2269 + eyeColor: "green",
2270 + name: "Luella Palmer",
2271 + company: "LUXURIA",
2272 + email: "luellapalmer@luxuria.com"
2273 + },
2274 + {
2275 + isActive: true,
2276 + age: 40,
2277 + eyeColor: "green",
2278 + name: "Vega James",
2279 + company: "MITROC",
2280 + email: "vegajames@mitroc.com"
2281 + },
2282 + {
2283 + isActive: true,
2284 + age: 32,
2285 + eyeColor: "brown",
2286 + name: "Mercer Flynn",
2287 + company: "ZENTIME",
2288 + email: "mercerflynn@zentime.com"
2289 + },
2290 + {
2291 + isActive: true,
2292 + age: 35,
2293 + eyeColor: "blue",
2294 + name: "Nina Barrera",
2295 + company: "ORBIFLEX",
2296 + email: "ninabarrera@orbiflex.com"
2297 + },
2298 + {
2299 + isActive: false,
2300 + age: 23,
2301 + eyeColor: "green",
2302 + name: "William Sanders",
2303 + company: "FLYBOYZ",
2304 + email: "williamsanders@flyboyz.com"
2305 + },
2306 + {
2307 + isActive: true,
2308 + age: 21,
2309 + eyeColor: "green",
2310 + name: "Essie Cantu",
2311 + company: "BITREX",
2312 + email: "essiecantu@bitrex.com"
2313 + },
2314 + {
2315 + isActive: false,
2316 + age: 35,
2317 + eyeColor: "blue",
2318 + name: "Welch Phelps",
2319 + company: "UNDERTAP",
2320 + email: "welchphelps@undertap.com"
2321 + },
2322 + {
2323 + isActive: false,
2324 + age: 37,
2325 + eyeColor: "blue",
2326 + name: "Kate Bryant",
2327 + company: "EMPIRICA",
2328 + email: "katebryant@empirica.com"
2329 + },
2330 + {
2331 + isActive: true,
2332 + age: 35,
2333 + eyeColor: "brown",
2334 + name: "Burnett Francis",
2335 + company: "TECHMANIA",
2336 + email: "burnettfrancis@techmania.com"
2337 + },
2338 + {
2339 + isActive: false,
2340 + age: 33,
2341 + eyeColor: "blue",
2342 + name: "Wells Willis",
2343 + company: "ZAYA",
2344 + email: "wellswillis@zaya.com"
2345 + },
2346 + {
2347 + isActive: true,
2348 + age: 31,
2349 + eyeColor: "green",
2350 + name: "Eve Higgins",
2351 + company: "CEDWARD",
2352 + email: "evehiggins@cedward.com"
2353 + },
2354 + {
2355 + isActive: true,
2356 + age: 40,
2357 + eyeColor: "blue",
2358 + name: "Robbins Farrell",
2359 + company: "QUIZMO",
2360 + email: "robbinsfarrell@quizmo.com"
2361 + },
2362 + {
2363 + isActive: true,
2364 + age: 23,
2365 + eyeColor: "brown",
2366 + name: "Queen Lambert",
2367 + company: "ZIDOX",
2368 + email: "queenlambert@zidox.com"
2369 + },
2370 + {
2371 + isActive: false,
2372 + age: 36,
2373 + eyeColor: "blue",
2374 + name: "Freeman Contreras",
2375 + company: "HELIXO",
2376 + email: "freemancontreras@helixo.com"
2377 + },
2378 + {
2379 + isActive: false,
2380 + age: 21,
2381 + eyeColor: "green",
2382 + name: "Hood Conner",
2383 + company: "ZILLATIDE",
2384 + email: "hoodconner@zillatide.com"
2385 + },
2386 + {
2387 + isActive: true,
2388 + age: 38,
2389 + eyeColor: "blue",
2390 + name: "Burgess Peters",
2391 + company: "COMTRAIL",
2392 + email: "burgesspeters@comtrail.com"
2393 + },
2394 + {
2395 + isActive: false,
2396 + age: 22,
2397 + eyeColor: "blue",
2398 + name: "Christie Boyd",
2399 + company: "QOT",
2400 + email: "christieboyd@qot.com"
2401 + },
2402 + {
2403 + isActive: false,
2404 + age: 28,
2405 + eyeColor: "green",
2406 + name: "Cobb Fuentes",
2407 + company: "NETAGY",
2408 + email: "cobbfuentes@netagy.com"
2409 + },
2410 + {
2411 + isActive: false,
2412 + age: 33,
2413 + eyeColor: "brown",
2414 + name: "Gina Caldwell",
2415 + company: "COLUMELLA",
2416 + email: "ginacaldwell@columella.com"
2417 + },
2418 + {
2419 + isActive: false,
2420 + age: 33,
2421 + eyeColor: "blue",
2422 + name: "Ayers Ellison",
2423 + company: "ENOMEN",
2424 + email: "ayersellison@enomen.com"
2425 + },
2426 + {
2427 + isActive: true,
2428 + age: 29,
2429 + eyeColor: "green",
2430 + name: "Debora Neal",
2431 + company: "SPLINX",
2432 + email: "deboraneal@splinx.com"
2433 + },
2434 + {
2435 + isActive: false,
2436 + age: 27,
2437 + eyeColor: "brown",
2438 + name: "Lee Bruce",
2439 + company: "CALLFLEX",
2440 + email: "leebruce@callflex.com"
2441 + },
2442 + {
2443 + isActive: false,
2444 + age: 23,
2445 + eyeColor: "blue",
2446 + name: "Cara Tran",
2447 + company: "EVENTIX",
2448 + email: "caratran@eventix.com"
2449 + },
2450 + {
2451 + isActive: true,
2452 + age: 27,
2453 + eyeColor: "brown",
2454 + name: "Nona Acevedo",
2455 + company: "BUZZNESS",
2456 + email: "nonaacevedo@buzzness.com"
2457 + },
2458 + {
2459 + isActive: true,
2460 + age: 30,
2461 + eyeColor: "brown",
2462 + name: "Matthews Bullock",
2463 + company: "VOIPA",
2464 + email: "matthewsbullock@voipa.com"
2465 + },
2466 + {
2467 + isActive: false,
2468 + age: 29,
2469 + eyeColor: "green",
2470 + name: "Virginia Foley",
2471 + company: "BITENDREX",
2472 + email: "virginiafoley@bitendrex.com"
2473 + },
2474 + {
2475 + isActive: false,
2476 + age: 22,
2477 + eyeColor: "brown",
2478 + name: "Kari Gilbert",
2479 + company: "APPLIDEC",
2480 + email: "karigilbert@applidec.com"
2481 + },
2482 + {
2483 + isActive: true,
2484 + age: 24,
2485 + eyeColor: "brown",
2486 + name: "Meghan Reynolds",
2487 + company: "PORTICO",
2488 + email: "meghanreynolds@portico.com"
2489 + },
2490 + {
2491 + isActive: true,
2492 + age: 29,
2493 + eyeColor: "blue",
2494 + name: "Miranda Saunders",
2495 + company: "AVIT",
2496 + email: "mirandasaunders@avit.com"
2497 + },
2498 + {
2499 + isActive: true,
2500 + age: 23,
2501 + eyeColor: "brown",
2502 + name: "Bowen Mcgowan",
2503 + company: "ARCTIQ",
2504 + email: "bowenmcgowan@arctiq.com"
2505 + },
2506 + {
2507 + isActive: false,
2508 + age: 21,
2509 + eyeColor: "green",
2510 + name: "Keri Copeland",
2511 + company: "CORPULSE",
2512 + email: "kericopeland@corpulse.com"
2513 + },
2514 + {
2515 + isActive: false,
2516 + age: 21,
2517 + eyeColor: "blue",
2518 + name: "David Knox",
2519 + company: "ZOGAK",
2520 + email: "davidknox@zogak.com"
2521 + },
2522 + {
2523 + isActive: false,
2524 + age: 35,
2525 + eyeColor: "green",
2526 + name: "Dolores Stephenson",
2527 + company: "OPTYK",
2528 + email: "doloresstephenson@optyk.com"
2529 + },
2530 + {
2531 + isActive: true,
2532 + age: 28,
2533 + eyeColor: "blue",
2534 + name: "Lucille Salas",
2535 + company: "FROSNEX",
2536 + email: "lucillesalas@frosnex.com"
2537 + },
2538 + {
2539 + isActive: true,
2540 + age: 25,
2541 + eyeColor: "brown",
2542 + name: "Jody Anderson",
2543 + company: "HANDSHAKE",
2544 + email: "jodyanderson@handshake.com"
2545 + },
2546 + {
2547 + isActive: false,
2548 + age: 33,
2549 + eyeColor: "green",
2550 + name: "Teri Weaver",
2551 + company: "STUCCO",
2552 + email: "teriweaver@stucco.com"
2553 + },
2554 + {
2555 + isActive: false,
2556 + age: 31,
2557 + eyeColor: "green",
2558 + name: "Kirk Herman",
2559 + company: "ZENTRY",
2560 + email: "kirkherman@zentry.com"
2561 + },
2562 + {
2563 + isActive: false,
2564 + age: 21,
2565 + eyeColor: "blue",
2566 + name: "Savannah Bolton",
2567 + company: "ORBIN",
2568 + email: "savannahbolton@orbin.com"
2569 + },
2570 + {
2571 + isActive: true,
2572 + age: 38,
2573 + eyeColor: "blue",
2574 + name: "Meadows Durham",
2575 + company: "UNCORP",
2576 + email: "meadowsdurham@uncorp.com"
2577 + },
2578 + {
2579 + isActive: true,
2580 + age: 36,
2581 + eyeColor: "brown",
2582 + name: "Mcdonald Juarez",
2583 + company: "BIFLEX",
2584 + email: "mcdonaldjuarez@biflex.com"
2585 + },
2586 + {
2587 + isActive: false,
2588 + age: 27,
2589 + eyeColor: "blue",
2590 + name: "Trevino Rios",
2591 + company: "LUMBREX",
2592 + email: "trevinorios@lumbrex.com"
2593 + },
2594 + {
2595 + isActive: false,
2596 + age: 34,
2597 + eyeColor: "brown",
2598 + name: "Cardenas Avery",
2599 + company: "GOGOL",
2600 + email: "cardenasavery@gogol.com"
2601 + },
2602 + {
2603 + isActive: true,
2604 + age: 31,
2605 + eyeColor: "brown",
2606 + name: "Chandra Montgomery",
2607 + company: "STOCKPOST",
2608 + email: "chandramontgomery@stockpost.com"
2609 + },
2610 + {
2611 + isActive: false,
2612 + age: 20,
2613 + eyeColor: "blue",
2614 + name: "Elisabeth King",
2615 + company: "ETERNIS",
2616 + email: "elisabethking@eternis.com"
2617 + },
2618 + {
2619 + isActive: false,
2620 + age: 23,
2621 + eyeColor: "blue",
2622 + name: "Whitfield Lowery",
2623 + company: "GEEKWAGON",
2624 + email: "whitfieldlowery@geekwagon.com"
2625 + },
2626 + {
2627 + isActive: false,
2628 + age: 36,
2629 + eyeColor: "green",
2630 + name: "Mcclain Nolan",
2631 + company: "PREMIANT",
2632 + email: "mcclainnolan@premiant.com"
2633 + },
2634 + {
2635 + isActive: true,
2636 + age: 28,
2637 + eyeColor: "blue",
2638 + name: "Myrna Gaines",
2639 + company: "TROPOLI",
2640 + email: "myrnagaines@tropoli.com"
2641 + },
2642 + {
2643 + isActive: true,
2644 + age: 20,
2645 + eyeColor: "brown",
2646 + name: "Oneill Jackson",
2647 + company: "LINGOAGE",
2648 + email: "oneilljackson@lingoage.com"
2649 + },
2650 + {
2651 + isActive: false,
2652 + age: 29,
2653 + eyeColor: "green",
2654 + name: "Stone Maxwell",
2655 + company: "MAINELAND",
2656 + email: "stonemaxwell@maineland.com"
2657 + },
2658 + {
2659 + isActive: true,
2660 + age: 20,
2661 + eyeColor: "blue",
2662 + name: "Woodward Mays",
2663 + company: "WAZZU",
2664 + email: "woodwardmays@wazzu.com"
2665 + },
2666 + {
2667 + isActive: true,
2668 + age: 21,
2669 + eyeColor: "green",
2670 + name: "Josephine Whitaker",
2671 + company: "BARKARAMA",
2672 + email: "josephinewhitaker@barkarama.com"
2673 + },
2674 + {
2675 + isActive: true,
2676 + age: 38,
2677 + eyeColor: "blue",
2678 + name: "Terrell Conley",
2679 + company: "QUANTALIA",
2680 + email: "terrellconley@quantalia.com"
2681 + },
2682 + {
2683 + isActive: true,
2684 + age: 37,
2685 + eyeColor: "brown",
2686 + name: "Aurora Payne",
2687 + company: "DEVILTOE",
2688 + email: "aurorapayne@deviltoe.com"
2689 + },
2690 + {
2691 + isActive: false,
2692 + age: 31,
2693 + eyeColor: "blue",
2694 + name: "Fleming Mercer",
2695 + company: "BEDDER",
2696 + email: "flemingmercer@bedder.com"
2697 + },
2698 + {
2699 + isActive: true,
2700 + age: 27,
2701 + eyeColor: "blue",
2702 + name: "Molina Hendricks",
2703 + company: "IDETICA",
2704 + email: "molinahendricks@idetica.com"
2705 + },
2706 + {
2707 + isActive: true,
2708 + age: 22,
2709 + eyeColor: "blue",
2710 + name: "Tamara Cantrell",
2711 + company: "ZENOLUX",
2712 + email: "tamaracantrell@zenolux.com"
2713 + },
2714 + {
2715 + isActive: true,
2716 + age: 21,
2717 + eyeColor: "brown",
2718 + name: "Macias Cruz",
2719 + company: "MAKINGWAY",
2720 + email: "maciascruz@makingway.com"
2721 + },
2722 + {
2723 + isActive: true,
2724 + age: 33,
2725 + eyeColor: "blue",
2726 + name: "Mooney Carpenter",
2727 + company: "ILLUMITY",
2728 + email: "mooneycarpenter@illumity.com"
2729 + },
2730 + {
2731 + isActive: true,
2732 + age: 33,
2733 + eyeColor: "blue",
2734 + name: "Benton Phillips",
2735 + company: "SUREMAX",
2736 + email: "bentonphillips@suremax.com"
2737 + },
2738 + {
2739 + isActive: false,
2740 + age: 34,
2741 + eyeColor: "blue",
2742 + name: "Bonnie Todd",
2743 + company: "KNEEDLES",
2744 + email: "bonnietodd@kneedles.com"
2745 + },
2746 + {
2747 + isActive: true,
2748 + age: 24,
2749 + eyeColor: "brown",
2750 + name: "Cherry Castaneda",
2751 + company: "ISOLOGIX",
2752 + email: "cherrycastaneda@isologix.com"
2753 + },
2754 + {
2755 + isActive: true,
2756 + age: 21,
2757 + eyeColor: "brown",
2758 + name: "Jewell Kinney",
2759 + company: "RODEOMAD",
2760 + email: "jewellkinney@rodeomad.com"
2761 + },
2762 + {
2763 + isActive: false,
2764 + age: 34,
2765 + eyeColor: "brown",
2766 + name: "Owens Wiggins",
2767 + company: "OBLIQ",
2768 + email: "owenswiggins@obliq.com"
2769 + },
2770 + {
2771 + isActive: false,
2772 + age: 37,
2773 + eyeColor: "green",
2774 + name: "Little Burton",
2775 + company: "UPLINX",
2776 + email: "littleburton@uplinx.com"
2777 + },
2778 + {
2779 + isActive: true,
2780 + age: 28,
2781 + eyeColor: "brown",
2782 + name: "Velasquez Atkinson",
2783 + company: "KAGE",
2784 + email: "velasquezatkinson@kage.com"
2785 + },
2786 + {
2787 + isActive: true,
2788 + age: 22,
2789 + eyeColor: "blue",
2790 + name: "Shannon Vincent",
2791 + company: "ZILODYNE",
2792 + email: "shannonvincent@zilodyne.com"
2793 + },
2794 + {
2795 + isActive: true,
2796 + age: 32,
2797 + eyeColor: "green",
2798 + name: "Franks Camacho",
2799 + company: "KEENGEN",
2800 + email: "frankscamacho@keengen.com"
2801 + },
2802 + {
2803 + isActive: true,
2804 + age: 37,
2805 + eyeColor: "green",
2806 + name: "Morin Burns",
2807 + company: "STRALUM",
2808 + email: "morinburns@stralum.com"
2809 + },
2810 + {
2811 + isActive: true,
2812 + age: 25,
2813 + eyeColor: "green",
2814 + name: "Donovan Pearson",
2815 + company: "COMSTRUCT",
2816 + email: "donovanpearson@comstruct.com"
2817 + },
2818 + {
2819 + isActive: false,
2820 + age: 34,
2821 + eyeColor: "brown",
2822 + name: "Juana Cooley",
2823 + company: "BIOSPAN",
2824 + email: "juanacooley@biospan.com"
2825 + },
2826 + {
2827 + isActive: true,
2828 + age: 34,
2829 + eyeColor: "blue",
2830 + name: "Mercedes Holmes",
2831 + company: "DIGIGEN",
2832 + email: "mercedesholmes@digigen.com"
2833 + },
2834 + {
2835 + isActive: true,
2836 + age: 25,
2837 + eyeColor: "green",
2838 + name: "Hester Rivers",
2839 + company: "GEOLOGIX",
2840 + email: "hesterrivers@geologix.com"
2841 + },
2842 + {
2843 + isActive: true,
2844 + age: 25,
2845 + eyeColor: "brown",
2846 + name: "Kitty Thomas",
2847 + company: "COLLAIRE",
2848 + email: "kittythomas@collaire.com"
2849 + },
2850 + {
2851 + isActive: true,
2852 + age: 22,
2853 + eyeColor: "green",
2854 + name: "Casey Charles",
2855 + company: "PARLEYNET",
2856 + email: "caseycharles@parleynet.com"
2857 + },
2858 + {
2859 + isActive: false,
2860 + age: 40,
2861 + eyeColor: "blue",
2862 + name: "Katrina Gates",
2863 + company: "EXERTA",
2864 + email: "katrinagates@exerta.com"
2865 + },
2866 + {
2867 + isActive: false,
2868 + age: 38,
2869 + eyeColor: "green",
2870 + name: "Beatriz Wiley",
2871 + company: "UNISURE",
2872 + email: "beatrizwiley@unisure.com"
2873 + },
2874 + {
2875 + isActive: false,
2876 + age: 24,
2877 + eyeColor: "brown",
2878 + name: "Manning Ayers",
2879 + company: "GRUPOLI",
2880 + email: "manningayers@grupoli.com"
2881 + },
2882 + {
2883 + isActive: true,
2884 + age: 34,
2885 + eyeColor: "green",
2886 + name: "Riley Barry",
2887 + company: "SENMEI",
2888 + email: "rileybarry@senmei.com"
2889 + },
2890 + {
2891 + isActive: true,
2892 + age: 25,
2893 + eyeColor: "blue",
2894 + name: "Hines Everett",
2895 + company: "GEEKUS",
2896 + email: "hineseverett@geekus.com"
2897 + },
2898 + {
2899 + isActive: true,
2900 + age: 30,
2901 + eyeColor: "blue",
2902 + name: "Sadie Fulton",
2903 + company: "TALKALOT",
2904 + email: "sadiefulton@talkalot.com"
2905 + },
2906 + {
2907 + isActive: false,
2908 + age: 30,
2909 + eyeColor: "brown",
2910 + name: "Mariana Reese",
2911 + company: "CORMORAN",
2912 + email: "marianareese@cormoran.com"
2913 + },
2914 + {
2915 + isActive: false,
2916 + age: 30,
2917 + eyeColor: "green",
2918 + name: "Stella House",
2919 + company: "EXOSPEED",
2920 + email: "stellahouse@exospeed.com"
2921 + },
2922 + {
2923 + isActive: false,
2924 + age: 35,
2925 + eyeColor: "brown",
2926 + name: "Serena Watts",
2927 + company: "ZILPHUR",
2928 + email: "serenawatts@zilphur.com"
2929 + },
2930 + {
2931 + isActive: true,
2932 + age: 28,
2933 + eyeColor: "brown",
2934 + name: "Valentine Dillon",
2935 + company: "BRAINQUIL",
2936 + email: "valentinedillon@brainquil.com"
2937 + },
2938 + {
2939 + isActive: true,
2940 + age: 28,
2941 + eyeColor: "green",
2942 + name: "Clayton Joyner",
2943 + company: "JUNIPOOR",
2944 + email: "claytonjoyner@junipoor.com"
2945 + },
2946 + {
2947 + isActive: false,
2948 + age: 32,
2949 + eyeColor: "blue",
2950 + name: "Amie Cotton",
2951 + company: "SENTIA",
2952 + email: "amiecotton@sentia.com"
2953 + },
2954 + {
2955 + isActive: false,
2956 + age: 34,
2957 + eyeColor: "brown",
2958 + name: "Marian Barton",
2959 + company: "TERSANKI",
2960 + email: "marianbarton@tersanki.com"
2961 + },
2962 + {
2963 + isActive: true,
2964 + age: 25,
2965 + eyeColor: "blue",
2966 + name: "Mallory Cummings",
2967 + company: "QUARMONY",
2968 + email: "mallorycummings@quarmony.com"
2969 + },
2970 + {
2971 + isActive: false,
2972 + age: 24,
2973 + eyeColor: "blue",
2974 + name: "Boyd Hayden",
2975 + company: "APEX",
2976 + email: "boydhayden@apex.com"
2977 + },
2978 + {
2979 + isActive: true,
2980 + age: 20,
2981 + eyeColor: "brown",
2982 + name: "Davenport Grimes",
2983 + company: "CENTREXIN",
2984 + email: "davenportgrimes@centrexin.com"
2985 + },
2986 + {
2987 + isActive: true,
2988 + age: 34,
2989 + eyeColor: "blue",
2990 + name: "Brown Johnson",
2991 + company: "PETIGEMS",
2992 + email: "brownjohnson@petigems.com"
2993 + },
2994 + {
2995 + isActive: true,
2996 + age: 39,
2997 + eyeColor: "green",
2998 + name: "Vaughan Holland",
2999 + company: "UTARA",
3000 + email: "vaughanholland@utara.com"
3001 + },
3002 + {
3003 + isActive: false,
3004 + age: 20,
3005 + eyeColor: "blue",
3006 + name: "Case Hancock",
3007 + company: "NIMON",
3008 + email: "casehancock@nimon.com"
3009 + },
3010 + {
3011 + isActive: true,
3012 + age: 39,
3013 + eyeColor: "green",
3014 + name: "Sellers Wolfe",
3015 + company: "STEELFAB",
3016 + email: "sellerswolfe@steelfab.com"
3017 + },
3018 + {
3019 + isActive: true,
3020 + age: 37,
3021 + eyeColor: "brown",
3022 + name: "Mays Rowe",
3023 + company: "CUIZINE",
3024 + email: "maysrowe@cuizine.com"
3025 + },
3026 + {
3027 + isActive: false,
3028 + age: 24,
3029 + eyeColor: "green",
3030 + name: "Cynthia Banks",
3031 + company: "QUORDATE",
3032 + email: "cynthiabanks@quordate.com"
3033 + },
3034 + {
3035 + isActive: false,
3036 + age: 32,
3037 + eyeColor: "brown",
3038 + name: "Selma Rhodes",
3039 + company: "JAMNATION",
3040 + email: "selmarhodes@jamnation.com"
3041 + },
3042 + {
3043 + isActive: true,
3044 + age: 25,
3045 + eyeColor: "blue",
3046 + name: "Burris Curry",
3047 + company: "NEBULEAN",
3048 + email: "burriscurry@nebulean.com"
3049 + },
3050 + {
3051 + isActive: false,
3052 + age: 20,
3053 + eyeColor: "blue",
3054 + name: "Estelle Fitzpatrick",
3055 + company: "HAIRPORT",
3056 + email: "estellefitzpatrick@hairport.com"
3057 + },
3058 + {
3059 + isActive: false,
3060 + age: 27,
3061 + eyeColor: "brown",
3062 + name: "Lori Callahan",
3063 + company: "BOSTONIC",
3064 + email: "loricallahan@bostonic.com"
3065 + },
3066 + {
3067 + isActive: true,
3068 + age: 27,
3069 + eyeColor: "blue",
3070 + name: "Leigh Fuller",
3071 + company: "SKYPLEX",
3072 + email: "leighfuller@skyplex.com"
3073 + },
3074 + {
3075 + isActive: true,
3076 + age: 37,
3077 + eyeColor: "green",
3078 + name: "Harris Stephens",
3079 + company: "PROSELY",
3080 + email: "harrisstephens@prosely.com"
3081 + },
3082 + {
3083 + isActive: false,
3084 + age: 31,
3085 + eyeColor: "blue",
3086 + name: "Humphrey Powell",
3087 + company: "SEALOUD",
3088 + email: "humphreypowell@sealoud.com"
3089 + },
3090 + {
3091 + isActive: true,
3092 + age: 27,
3093 + eyeColor: "green",
3094 + name: "Roth Hodge",
3095 + company: "OTHERSIDE",
3096 + email: "rothhodge@otherside.com"
3097 + },
3098 + {
3099 + isActive: true,
3100 + age: 37,
3101 + eyeColor: "brown",
3102 + name: "Kelley Hernandez",
3103 + company: "TURNABOUT",
3104 + email: "kelleyhernandez@turnabout.com"
3105 + },
3106 + {
3107 + isActive: false,
3108 + age: 27,
3109 + eyeColor: "brown",
3110 + name: "Petty Glenn",
3111 + company: "PASTURIA",
3112 + email: "pettyglenn@pasturia.com"
3113 + },
3114 + {
3115 + isActive: false,
3116 + age: 27,
3117 + eyeColor: "green",
3118 + name: "Ashley Walton",
3119 + company: "WEBIOTIC",
3120 + email: "ashleywalton@webiotic.com"
3121 + },
3122 + {
3123 + isActive: false,
3124 + age: 23,
3125 + eyeColor: "green",
3126 + name: "Acevedo Burch",
3127 + company: "EMTRAK",
3128 + email: "acevedoburch@emtrak.com"
3129 + },
3130 + {
3131 + isActive: true,
3132 + age: 33,
3133 + eyeColor: "brown",
3134 + name: "Hicks Best",
3135 + company: "GEOFORM",
3136 + email: "hicksbest@geoform.com"
3137 + },
3138 + {
3139 + isActive: true,
3140 + age: 39,
3141 + eyeColor: "brown",
3142 + name: "Donna Pacheco",
3143 + company: "DECRATEX",
3144 + email: "donnapacheco@decratex.com"
3145 + },
3146 + {
3147 + isActive: false,
3148 + age: 40,
3149 + eyeColor: "green",
3150 + name: "Hardy Trujillo",
3151 + company: "COMBOGENE",
3152 + email: "hardytrujillo@combogene.com"
3153 + },
3154 + {
3155 + isActive: true,
3156 + age: 35,
3157 + eyeColor: "green",
3158 + name: "Misty Hughes",
3159 + company: "OATFARM",
3160 + email: "mistyhughes@oatfarm.com"
3161 + },
3162 + {
3163 + isActive: false,
3164 + age: 33,
3165 + eyeColor: "brown",
3166 + name: "Glenn Oneal",
3167 + company: "ROBOID",
3168 + email: "glennoneal@roboid.com"
3169 + },
3170 + {
3171 + isActive: false,
3172 + age: 37,
3173 + eyeColor: "brown",
3174 + name: "Phillips Vaughan",
3175 + company: "OLUCORE",
3176 + email: "phillipsvaughan@olucore.com"
3177 + },
3178 + {
3179 + isActive: true,
3180 + age: 23,
3181 + eyeColor: "brown",
3182 + name: "Araceli Acosta",
3183 + company: "KNOWLYSIS",
3184 + email: "araceliacosta@knowlysis.com"
3185 + },
3186 + {
3187 + isActive: false,
3188 + age: 33,
3189 + eyeColor: "blue",
3190 + name: "Rojas Brennan",
3191 + company: "ZENSUS",
3192 + email: "rojasbrennan@zensus.com"
3193 + },
3194 + {
3195 + isActive: true,
3196 + age: 35,
3197 + eyeColor: "green",
3198 + name: "Rosa Hicks",
3199 + company: "GEOFARM",
3200 + email: "rosahicks@geofarm.com"
3201 + },
3202 + {
3203 + isActive: false,
3204 + age: 24,
3205 + eyeColor: "brown",
3206 + name: "Cochran Griffith",
3207 + company: "TECHTRIX",
3208 + email: "cochrangriffith@techtrix.com"
3209 + },
3210 + {
3211 + isActive: false,
3212 + age: 24,
3213 + eyeColor: "green",
3214 + name: "Diaz Warren",
3215 + company: "DEEPENDS",
3216 + email: "diazwarren@deepends.com"
3217 + },
3218 + {
3219 + isActive: true,
3220 + age: 28,
3221 + eyeColor: "green",
3222 + name: "Tyson Hamilton",
3223 + company: "BILLMED",
3224 + email: "tysonhamilton@billmed.com"
3225 + },
3226 + {
3227 + isActive: false,
3228 + age: 24,
3229 + eyeColor: "blue",
3230 + name: "Parker Peterson",
3231 + company: "DOGNOST",
3232 + email: "parkerpeterson@dognost.com"
3233 + },
3234 + {
3235 + isActive: true,
3236 + age: 29,
3237 + eyeColor: "green",
3238 + name: "Kelley Dean",
3239 + company: "ENTROPIX",
3240 + email: "kelleydean@entropix.com"
3241 + },
3242 + {
3243 + isActive: false,
3244 + age: 35,
3245 + eyeColor: "brown",
3246 + name: "Tran Wells",
3247 + company: "UNIWORLD",
3248 + email: "tranwells@uniworld.com"
3249 + },
3250 + {
3251 + isActive: true,
3252 + age: 21,
3253 + eyeColor: "brown",
3254 + name: "Charmaine Collins",
3255 + company: "INFOTRIPS",
3256 + email: "charmainecollins@infotrips.com"
3257 + },
3258 + {
3259 + isActive: true,
3260 + age: 26,
3261 + eyeColor: "green",
3262 + name: "Jefferson Hardy",
3263 + company: "HOPELI",
3264 + email: "jeffersonhardy@hopeli.com"
3265 + },
3266 + {
3267 + isActive: true,
3268 + age: 29,
3269 + eyeColor: "green",
3270 + name: "Buckner Ashley",
3271 + company: "EXODOC",
3272 + email: "bucknerashley@exodoc.com"
3273 + },
3274 + {
3275 + isActive: true,
3276 + age: 36,
3277 + eyeColor: "blue",
3278 + name: "Shelby Hardin",
3279 + company: "TROLLERY",
3280 + email: "shelbyhardin@trollery.com"
3281 + },
3282 + {
3283 + isActive: true,
3284 + age: 32,
3285 + eyeColor: "green",
3286 + name: "Ochoa Bray",
3287 + company: "ORONOKO",
3288 + email: "ochoabray@oronoko.com"
3289 + },
3290 + {
3291 + isActive: true,
3292 + age: 28,
3293 + eyeColor: "green",
3294 + name: "Battle Summers",
3295 + company: "PATHWAYS",
3296 + email: "battlesummers@pathways.com"
3297 + },
3298 + {
3299 + isActive: false,
3300 + age: 37,
3301 + eyeColor: "blue",
3302 + name: "Sloan Murray",
3303 + company: "ENORMO",
3304 + email: "sloanmurray@enormo.com"
3305 + },
3306 + {
3307 + isActive: false,
3308 + age: 32,
3309 + eyeColor: "blue",
3310 + name: "Dorothea Campos",
3311 + company: "PLASMOSIS",
3312 + email: "dorotheacampos@plasmosis.com"
3313 + },
3314 + {
3315 + isActive: true,
3316 + age: 22,
3317 + eyeColor: "blue",
3318 + name: "Lauren Vaughn",
3319 + company: "INRT",
3320 + email: "laurenvaughn@inrt.com"
3321 + },
3322 + {
3323 + isActive: true,
3324 + age: 24,
3325 + eyeColor: "blue",
3326 + name: "Annette Emerson",
3327 + company: "FISHLAND",
3328 + email: "annetteemerson@fishland.com"
3329 + },
3330 + {
3331 + isActive: true,
3332 + age: 39,
3333 + eyeColor: "green",
3334 + name: "Angelica Oneill",
3335 + company: "CORPORANA",
3336 + email: "angelicaoneill@corporana.com"
3337 + },
3338 + {
3339 + isActive: false,
3340 + age: 27,
3341 + eyeColor: "green",
3342 + name: "Alyce Mckenzie",
3343 + company: "EWAVES",
3344 + email: "alycemckenzie@ewaves.com"
3345 + },
3346 + {
3347 + isActive: false,
3348 + age: 36,
3349 + eyeColor: "blue",
3350 + name: "Tamera Newton",
3351 + company: "CINESANCT",
3352 + email: "tameranewton@cinesanct.com"
3353 + },
3354 + {
3355 + isActive: true,
3356 + age: 32,
3357 + eyeColor: "green",
3358 + name: "Heath Mcintyre",
3359 + company: "NAMEGEN",
3360 + email: "heathmcintyre@namegen.com"
3361 + },
3362 + {
3363 + isActive: false,
3364 + age: 33,
3365 + eyeColor: "green",
3366 + name: "Wiley Hebert",
3367 + company: "TERAPRENE",
3368 + email: "wileyhebert@teraprene.com"
3369 + },
3370 + {
3371 + isActive: false,
3372 + age: 35,
3373 + eyeColor: "brown",
3374 + name: "Gates Bailey",
3375 + company: "PHARMEX",
3376 + email: "gatesbailey@pharmex.com"
3377 + },
3378 + {
3379 + isActive: true,
3380 + age: 24,
3381 + eyeColor: "green",
3382 + name: "Mccormick Norris",
3383 + company: "KOOGLE",
3384 + email: "mccormicknorris@koogle.com"
3385 + },
3386 + {
3387 + isActive: true,
3388 + age: 28,
3389 + eyeColor: "blue",
3390 + name: "Julianne Dickson",
3391 + company: "VERTON",
3392 + email: "juliannedickson@verton.com"
3393 + },
3394 + {
3395 + isActive: false,
3396 + age: 26,
3397 + eyeColor: "brown",
3398 + name: "Graves Price",
3399 + company: "STELAECOR",
3400 + email: "gravesprice@stelaecor.com"
3401 + },
3402 + {
3403 + isActive: true,
3404 + age: 31,
3405 + eyeColor: "brown",
3406 + name: "Jean Bean",
3407 + company: "NETBOOK",
3408 + email: "jeanbean@netbook.com"
3409 + },
3410 + {
3411 + isActive: true,
3412 + age: 26,
3413 + eyeColor: "green",
3414 + name: "Howard Gillespie",
3415 + company: "PLUTORQUE",
3416 + email: "howardgillespie@plutorque.com"
3417 + },
3418 + {
3419 + isActive: false,
3420 + age: 39,
3421 + eyeColor: "blue",
3422 + name: "Robin England",
3423 + company: "RONBERT",
3424 + email: "robinengland@ronbert.com"
3425 + },
3426 + {
3427 + isActive: true,
3428 + age: 23,
3429 + eyeColor: "brown",
3430 + name: "Liliana Davidson",
3431 + company: "RUBADUB",
3432 + email: "lilianadavidson@rubadub.com"
3433 + },
3434 + {
3435 + isActive: false,
3436 + age: 39,
3437 + eyeColor: "green",
3438 + name: "Mindy Wooten",
3439 + company: "MARTGO",
3440 + email: "mindywooten@martgo.com"
3441 + },
3442 + {
3443 + isActive: false,
3444 + age: 33,
3445 + eyeColor: "brown",
3446 + name: "Joanne Spears",
3447 + company: "SUPPORTAL",
3448 + email: "joannespears@supportal.com"
3449 + },
3450 + {
3451 + isActive: false,
3452 + age: 23,
3453 + eyeColor: "blue",
3454 + name: "Mendez Ferrell",
3455 + company: "TALENDULA",
3456 + email: "mendezferrell@talendula.com"
3457 + },
3458 + {
3459 + isActive: false,
3460 + age: 35,
3461 + eyeColor: "blue",
3462 + name: "Justine Wilkins",
3463 + company: "LIQUIDOC",
3464 + email: "justinewilkins@liquidoc.com"
3465 + },
3466 + {
3467 + isActive: true,
3468 + age: 33,
3469 + eyeColor: "brown",
3470 + name: "Christa Mullen",
3471 + company: "COGENTRY",
3472 + email: "christamullen@cogentry.com"
3473 + },
3474 + {
3475 + isActive: false,
3476 + age: 21,
3477 + eyeColor: "green",
3478 + name: "Santos Pratt",
3479 + company: "RETRACK",
3480 + email: "santospratt@retrack.com"
3481 + },
3482 + {
3483 + isActive: false,
3484 + age: 24,
3485 + eyeColor: "blue",
3486 + name: "Lang Benson",
3487 + company: "NORALI",
3488 + email: "langbenson@norali.com"
3489 + },
3490 + {
3491 + isActive: false,
3492 + age: 39,
3493 + eyeColor: "green",
3494 + name: "Carlson Andrews",
3495 + company: "ZANYMAX",
3496 + email: "carlsonandrews@zanymax.com"
3497 + },
3498 + {
3499 + isActive: false,
3500 + age: 30,
3501 + eyeColor: "green",
3502 + name: "Lynette Underwood",
3503 + company: "RAMEON",
3504 + email: "lynetteunderwood@rameon.com"
3505 + },
3506 + {
3507 + isActive: false,
3508 + age: 21,
3509 + eyeColor: "blue",
3510 + name: "Schwartz Dillard",
3511 + company: "SLUMBERIA",
3512 + email: "schwartzdillard@slumberia.com"
3513 + },
3514 + {
3515 + isActive: true,
3516 + age: 35,
3517 + eyeColor: "blue",
3518 + name: "Carmen Dorsey",
3519 + company: "SLOGANAUT",
3520 + email: "carmendorsey@sloganaut.com"
3521 + },
3522 + {
3523 + isActive: true,
3524 + age: 39,
3525 + eyeColor: "blue",
3526 + name: "Bradley Adkins",
3527 + company: "ECLIPSENT",
3528 + email: "bradleyadkins@eclipsent.com"
3529 + },
3530 + {
3531 + isActive: false,
3532 + age: 37,
3533 + eyeColor: "green",
3534 + name: "Crosby Morgan",
3535 + company: "QUILITY",
3536 + email: "crosbymorgan@quility.com"
3537 + },
3538 + {
3539 + isActive: false,
3540 + age: 38,
3541 + eyeColor: "blue",
3542 + name: "Melba Rutledge",
3543 + company: "MULTRON",
3544 + email: "melbarutledge@multron.com"
3545 + },
3546 + {
3547 + isActive: true,
3548 + age: 25,
3549 + eyeColor: "green",
3550 + name: "Emilia Byrd",
3551 + company: "COMVEY",
3552 + email: "emiliabyrd@comvey.com"
3553 + },
3554 + {
3555 + isActive: false,
3556 + age: 27,
3557 + eyeColor: "blue",
3558 + name: "Wilson Moreno",
3559 + company: "ZAJ",
3560 + email: "wilsonmoreno@zaj.com"
3561 + },
3562 + {
3563 + isActive: true,
3564 + age: 29,
3565 + eyeColor: "green",
3566 + name: "Patricia Meyer",
3567 + company: "ROTODYNE",
3568 + email: "patriciameyer@rotodyne.com"
3569 + },
3570 + {
3571 + isActive: true,
3572 + age: 37,
3573 + eyeColor: "green",
3574 + name: "Richardson Pruitt",
3575 + company: "ACLIMA",
3576 + email: "richardsonpruitt@aclima.com"
3577 + },
3578 + {
3579 + isActive: false,
3580 + age: 20,
3581 + eyeColor: "brown",
3582 + name: "Alice Kent",
3583 + company: "TOYLETRY",
3584 + email: "alicekent@toyletry.com"
3585 + },
3586 + {
3587 + isActive: false,
3588 + age: 22,
3589 + eyeColor: "green",
3590 + name: "Nash Harding",
3591 + company: "MOREGANIC",
3592 + email: "nashharding@moreganic.com"
3593 + },
3594 + {
3595 + isActive: true,
3596 + age: 33,
3597 + eyeColor: "blue",
3598 + name: "Stephanie Spence",
3599 + company: "QUALITERN",
3600 + email: "stephaniespence@qualitern.com"
3601 + },
3602 + {
3603 + isActive: true,
3604 + age: 33,
3605 + eyeColor: "blue",
3606 + name: "Ryan Buckley",
3607 + company: "SPACEWAX",
3608 + email: "ryanbuckley@spacewax.com"
3609 + },
3610 + {
3611 + isActive: false,
3612 + age: 38,
3613 + eyeColor: "brown",
3614 + name: "Esther Rosales",
3615 + company: "VINCH",
3616 + email: "estherrosales@vinch.com"
3617 + },
3618 + {
3619 + isActive: true,
3620 + age: 28,
3621 + eyeColor: "blue",
3622 + name: "Lou Morse",
3623 + company: "ISOLOGICS",
3624 + email: "loumorse@isologics.com"
3625 + },
3626 + {
3627 + isActive: true,
3628 + age: 32,
3629 + eyeColor: "blue",
3630 + name: "Tucker Holder",
3631 + company: "GEEKULAR",
3632 + email: "tuckerholder@geekular.com"
3633 + },
3634 + {
3635 + isActive: false,
3636 + age: 23,
3637 + eyeColor: "blue",
3638 + name: "Pratt Abbott",
3639 + company: "KIOSK",
3640 + email: "prattabbott@kiosk.com"
3641 + },
3642 + {
3643 + isActive: false,
3644 + age: 38,
3645 + eyeColor: "brown",
3646 + name: "Patsy Whitehead",
3647 + company: "POOCHIES",
3648 + email: "patsywhitehead@poochies.com"
3649 + },
3650 + {
3651 + isActive: true,
3652 + age: 28,
3653 + eyeColor: "brown",
3654 + name: "Foreman Mcbride",
3655 + company: "GORGANIC",
3656 + email: "foremanmcbride@gorganic.com"
3657 + },
3658 + {
3659 + isActive: false,
3660 + age: 32,
3661 + eyeColor: "green",
3662 + name: "Lowe Horne",
3663 + company: "ZENTILITY",
3664 + email: "lowehorne@zentility.com"
3665 + },
3666 + {
3667 + isActive: true,
3668 + age: 28,
3669 + eyeColor: "brown",
3670 + name: "Lessie Stanton",
3671 + company: "QUILK",
3672 + email: "lessiestanton@quilk.com"
3673 + },
3674 + {
3675 + isActive: true,
3676 + age: 36,
3677 + eyeColor: "green",
3678 + name: "Malone Miles",
3679 + company: "ORBAXTER",
3680 + email: "malonemiles@orbaxter.com"
3681 + },
3682 + {
3683 + isActive: true,
3684 + age: 31,
3685 + eyeColor: "brown",
3686 + name: "Odom Barker",
3687 + company: "EMOLTRA",
3688 + email: "odombarker@emoltra.com"
3689 + },
3690 + {
3691 + isActive: true,
3692 + age: 35,
3693 + eyeColor: "brown",
3694 + name: "Trisha Bryan",
3695 + company: "ENJOLA",
3696 + email: "trishabryan@enjola.com"
3697 + },
3698 + {
3699 + isActive: true,
3700 + age: 36,
3701 + eyeColor: "blue",
3702 + name: "Lynnette Duffy",
3703 + company: "CHILLIUM",
3704 + email: "lynnetteduffy@chillium.com"
3705 + },
3706 + {
3707 + isActive: true,
3708 + age: 25,
3709 + eyeColor: "brown",
3710 + name: "Francesca Puckett",
3711 + company: "ZOLAVO",
3712 + email: "francescapuckett@zolavo.com"
3713 + },
3714 + {
3715 + isActive: false,
3716 + age: 32,
3717 + eyeColor: "brown",
3718 + name: "Tasha Gonzalez",
3719 + company: "ACCUSAGE",
3720 + email: "tashagonzalez@accusage.com"
3721 + },
3722 + {
3723 + isActive: true,
3724 + age: 32,
3725 + eyeColor: "green",
3726 + name: "Leanne Meyers",
3727 + company: "KONNECT",
3728 + email: "leannemeyers@konnect.com"
3729 + },
3730 + {
3731 + isActive: false,
3732 + age: 38,
3733 + eyeColor: "brown",
3734 + name: "Buchanan Ware",
3735 + company: "ZOARERE",
3736 + email: "buchananware@zoarere.com"
3737 + },
3738 + {
3739 + isActive: false,
3740 + age: 35,
3741 + eyeColor: "blue",
3742 + name: "Newman Bell",
3743 + company: "ELITA",
3744 + email: "newmanbell@elita.com"
3745 + },
3746 + {
3747 + isActive: false,
3748 + age: 36,
3749 + eyeColor: "blue",
3750 + name: "Beach Graham",
3751 + company: "VELITY",
3752 + email: "beachgraham@velity.com"
3753 + },
3754 + {
3755 + isActive: true,
3756 + age: 22,
3757 + eyeColor: "green",
3758 + name: "Erickson Maynard",
3759 + company: "EQUICOM",
3760 + email: "ericksonmaynard@equicom.com"
3761 + },
3762 + {
3763 + isActive: false,
3764 + age: 26,
3765 + eyeColor: "blue",
3766 + name: "Flynn Haynes",
3767 + company: "EXOSWITCH",
3768 + email: "flynnhaynes@exoswitch.com"
3769 + },
3770 + {
3771 + isActive: true,
3772 + age: 25,
3773 + eyeColor: "blue",
3774 + name: "Faye Henson",
3775 + company: "ISONUS",
3776 + email: "fayehenson@isonus.com"
3777 + },
3778 + {
3779 + isActive: true,
3780 + age: 38,
3781 + eyeColor: "green",
3782 + name: "Ginger Johnston",
3783 + company: "IPLAX",
3784 + email: "gingerjohnston@iplax.com"
3785 + },
3786 + {
3787 + isActive: true,
3788 + age: 30,
3789 + eyeColor: "blue",
3790 + name: "Ruiz Noel",
3791 + company: "INSURITY",
3792 + email: "ruiznoel@insurity.com"
3793 + },
3794 + {
3795 + isActive: true,
3796 + age: 21,
3797 + eyeColor: "brown",
3798 + name: "Strickland Miller",
3799 + company: "DIGIRANG",
3800 + email: "stricklandmiller@digirang.com"
3801 + },
3802 + {
3803 + isActive: true,
3804 + age: 37,
3805 + eyeColor: "blue",
3806 + name: "Joanna Mclaughlin",
3807 + company: "HATOLOGY",
3808 + email: "joannamclaughlin@hatology.com"
3809 + },
3810 + {
3811 + isActive: true,
3812 + age: 24,
3813 + eyeColor: "green",
3814 + name: "Helena Gould",
3815 + company: "NORALEX",
3816 + email: "helenagould@noralex.com"
3817 + },
3818 + {
3819 + isActive: true,
3820 + age: 36,
3821 + eyeColor: "brown",
3822 + name: "Irma Pickett",
3823 + company: "COMVERGES",
3824 + email: "irmapickett@comverges.com"
3825 + },
3826 + {
3827 + isActive: false,
3828 + age: 36,
3829 + eyeColor: "green",
3830 + name: "Avis Steele",
3831 + company: "BOLAX",
3832 + email: "avissteele@bolax.com"
3833 + },
3834 + {
3835 + isActive: true,
3836 + age: 22,
3837 + eyeColor: "green",
3838 + name: "Heather Patrick",
3839 + company: "REALMO",
3840 + email: "heatherpatrick@realmo.com"
3841 + },
3842 + {
3843 + isActive: true,
3844 + age: 24,
3845 + eyeColor: "green",
3846 + name: "Lott Ballard",
3847 + company: "EXTRAWEAR",
3848 + email: "lottballard@extrawear.com"
3849 + },
3850 + {
3851 + isActive: true,
3852 + age: 31,
3853 + eyeColor: "green",
3854 + name: "Hutchinson Garcia",
3855 + company: "ARTIQ",
3856 + email: "hutchinsongarcia@artiq.com"
3857 + },
3858 + {
3859 + isActive: true,
3860 + age: 32,
3861 + eyeColor: "blue",
3862 + name: "Waller Benton",
3863 + company: "MICROLUXE",
3864 + email: "wallerbenton@microluxe.com"
3865 + },
3866 + {
3867 + isActive: false,
3868 + age: 24,
3869 + eyeColor: "brown",
3870 + name: "Hansen Pena",
3871 + company: "QUONATA",
3872 + email: "hansenpena@quonata.com"
3873 + },
3874 + {
3875 + isActive: true,
3876 + age: 33,
3877 + eyeColor: "green",
3878 + name: "Woods Hensley",
3879 + company: "EXOZENT",
3880 + email: "woodshensley@exozent.com"
3881 + },
3882 + {
3883 + isActive: true,
3884 + age: 29,
3885 + eyeColor: "green",
3886 + name: "Castaneda Blair",
3887 + company: "ECRATER",
3888 + email: "castanedablair@ecrater.com"
3889 + },
3890 + {
3891 + isActive: false,
3892 + age: 32,
3893 + eyeColor: "green",
3894 + name: "Vickie Rogers",
3895 + company: "DOGNOSIS",
3896 + email: "vickierogers@dognosis.com"
3897 + },
3898 + {
3899 + isActive: false,
3900 + age: 37,
3901 + eyeColor: "brown",
3902 + name: "Horton Tyson",
3903 + company: "NEXGENE",
3904 + email: "hortontyson@nexgene.com"
3905 + },
3906 + {
3907 + isActive: true,
3908 + age: 38,
3909 + eyeColor: "blue",
3910 + name: "Conrad Salazar",
3911 + company: "DAISU",
3912 + email: "conradsalazar@daisu.com"
3913 + },
3914 + {
3915 + isActive: false,
3916 + age: 33,
3917 + eyeColor: "green",
3918 + name: "Corinne Mills",
3919 + company: "TUBALUM",
3920 + email: "corinnemills@tubalum.com"
3921 + },
3922 + {
3923 + isActive: false,
3924 + age: 28,
3925 + eyeColor: "brown",
3926 + name: "Estella Bauer",
3927 + company: "INDEXIA",
3928 + email: "estellabauer@indexia.com"
3929 + },
3930 + {
3931 + isActive: false,
3932 + age: 27,
3933 + eyeColor: "green",
3934 + name: "Cline Chaney",
3935 + company: "KIDSTOCK",
3936 + email: "clinechaney@kidstock.com"
3937 + },
3938 + {
3939 + isActive: false,
3940 + age: 24,
3941 + eyeColor: "brown",
3942 + name: "Jacobs Johns",
3943 + company: "TELPOD",
3944 + email: "jacobsjohns@telpod.com"
3945 + },
3946 + {
3947 + isActive: false,
3948 + age: 40,
3949 + eyeColor: "blue",
3950 + name: "Sosa Allen",
3951 + company: "DEMINIMUM",
3952 + email: "sosaallen@deminimum.com"
3953 + },
3954 + {
3955 + isActive: false,
3956 + age: 29,
3957 + eyeColor: "green",
3958 + name: "Ivy Larson",
3959 + company: "FURNIGEER",
3960 + email: "ivylarson@furnigeer.com"
3961 + },
3962 + {
3963 + isActive: true,
3964 + age: 38,
3965 + eyeColor: "green",
3966 + name: "Lynch Watson",
3967 + company: "RODEOCEAN",
3968 + email: "lynchwatson@rodeocean.com"
3969 + },
3970 + {
3971 + isActive: true,
3972 + age: 32,
3973 + eyeColor: "green",
3974 + name: "Santana Chan",
3975 + company: "NEPTIDE",
3976 + email: "santanachan@neptide.com"
3977 + },
3978 + {
3979 + isActive: true,
3980 + age: 39,
3981 + eyeColor: "green",
3982 + name: "Marjorie Blevins",
3983 + company: "ISOSURE",
3984 + email: "marjorieblevins@isosure.com"
3985 + },
3986 + {
3987 + isActive: false,
3988 + age: 24,
3989 + eyeColor: "green",
3990 + name: "Rosemarie Franklin",
3991 + company: "LEXICONDO",
3992 + email: "rosemariefranklin@lexicondo.com"
3993 + },
3994 + {
3995 + isActive: true,
3996 + age: 20,
3997 + eyeColor: "green",
3998 + name: "Robertson Smith",
3999 + company: "GENMY",
4000 + email: "robertsonsmith@genmy.com"
4001 + },
4002 + {
4003 + isActive: false,
4004 + age: 34,
4005 + eyeColor: "green",
4006 + name: "Randall Olsen",
4007 + company: "MAXIMIND",
4008 + email: "randallolsen@maximind.com"
4009 + },
4010 + {
4011 + isActive: true,
4012 + age: 24,
4013 + eyeColor: "brown",
4014 + name: "Quinn Christian",
4015 + company: "RODEOLOGY",
4016 + email: "quinnchristian@rodeology.com"
4017 + },
4018 + {
4019 + isActive: false,
4020 + age: 23,
4021 + eyeColor: "brown",
4022 + name: "Miles Solomon",
4023 + company: "QUONK",
4024 + email: "milessolomon@quonk.com"
4025 + },
4026 + {
4027 + isActive: false,
4028 + age: 30,
4029 + eyeColor: "brown",
4030 + name: "Joyce Cameron",
4031 + company: "ACCUPRINT",
4032 + email: "joycecameron@accuprint.com"
4033 + },
4034 + {
4035 + isActive: false,
4036 + age: 20,
4037 + eyeColor: "green",
4038 + name: "Diann Bishop",
4039 + company: "FITCORE",
4040 + email: "diannbishop@fitcore.com"
4041 + },
4042 + {
4043 + isActive: false,
4044 + age: 30,
4045 + eyeColor: "green",
4046 + name: "Marisol Curtis",
4047 + company: "KATAKANA",
4048 + email: "marisolcurtis@katakana.com"
4049 + },
4050 + {
4051 + isActive: false,
4052 + age: 33,
4053 + eyeColor: "blue",
4054 + name: "Lakisha Hays",
4055 + company: "DYMI",
4056 + email: "lakishahays@dymi.com"
4057 + },
4058 + {
4059 + isActive: true,
4060 + age: 33,
4061 + eyeColor: "blue",
4062 + name: "Russo Winters",
4063 + company: "NORSUL",
4064 + email: "russowinters@norsul.com"
4065 + },
4066 + {
4067 + isActive: true,
4068 + age: 25,
4069 + eyeColor: "blue",
4070 + name: "Debra Wong",
4071 + company: "SOLGAN",
4072 + email: "debrawong@solgan.com"
4073 + },
4074 + {
4075 + isActive: true,
4076 + age: 38,
4077 + eyeColor: "green",
4078 + name: "Cleo Medina",
4079 + company: "FORTEAN",
4080 + email: "cleomedina@fortean.com"
4081 + },
4082 + {
4083 + isActive: false,
4084 + age: 26,
4085 + eyeColor: "blue",
4086 + name: "Jolene Guy",
4087 + company: "VIASIA",
4088 + email: "joleneguy@viasia.com"
4089 + },
4090 + {
4091 + isActive: true,
4092 + age: 24,
4093 + eyeColor: "blue",
4094 + name: "Schneider Gray",
4095 + company: "PORTALIS",
4096 + email: "schneidergray@portalis.com"
4097 + },
4098 + {
4099 + isActive: true,
4100 + age: 33,
4101 + eyeColor: "brown",
4102 + name: "White Colon",
4103 + company: "ZILIDIUM",
4104 + email: "whitecolon@zilidium.com"
4105 + },
4106 + {
4107 + isActive: false,
4108 + age: 36,
4109 + eyeColor: "brown",
4110 + name: "Bonita Webb",
4111 + company: "AQUAZURE",
4112 + email: "bonitawebb@aquazure.com"
4113 + },
4114 + {
4115 + isActive: true,
4116 + age: 22,
4117 + eyeColor: "brown",
4118 + name: "Eva Greene",
4119 + company: "BOVIS",
4120 + email: "evagreene@bovis.com"
4121 + },
4122 + {
4123 + isActive: true,
4124 + age: 24,
4125 + eyeColor: "green",
4126 + name: "Rochelle Mueller",
4127 + company: "TELEPARK",
4128 + email: "rochellemueller@telepark.com"
4129 + },
4130 + {
4131 + isActive: false,
4132 + age: 33,
4133 + eyeColor: "green",
4134 + name: "Addie Baker",
4135 + company: "FLUMBO",
4136 + email: "addiebaker@flumbo.com"
4137 + },
4138 + {
4139 + isActive: false,
4140 + age: 24,
4141 + eyeColor: "blue",
4142 + name: "Phoebe Wall",
4143 + company: "EZENTIA",
4144 + email: "phoebewall@ezentia.com"
4145 + },
4146 + {
4147 + isActive: true,
4148 + age: 39,
4149 + eyeColor: "green",
4150 + name: "Ferrell Kane",
4151 + company: "COREPAN",
4152 + email: "ferrellkane@corepan.com"
4153 + },
4154 + {
4155 + isActive: false,
4156 + age: 31,
4157 + eyeColor: "blue",
4158 + name: "Susan Lindsay",
4159 + company: "FLEETMIX",
4160 + email: "susanlindsay@fleetmix.com"
4161 + },
4162 + {
4163 + isActive: true,
4164 + age: 25,
4165 + eyeColor: "blue",
4166 + name: "Tameka Jacobson",
4167 + company: "ZIDANT",
4168 + email: "tamekajacobson@zidant.com"
4169 + },
4170 + {
4171 + isActive: true,
4172 + age: 26,
4173 + eyeColor: "blue",
4174 + name: "Sara Bennett",
4175 + company: "XYLAR",
4176 + email: "sarabennett@xylar.com"
4177 + },
4178 + {
4179 + isActive: false,
4180 + age: 29,
4181 + eyeColor: "green",
4182 + name: "Karina Franco",
4183 + company: "FIBEROX",
4184 + email: "karinafranco@fiberox.com"
4185 + },
4186 + {
4187 + isActive: false,
4188 + age: 26,
4189 + eyeColor: "green",
4190 + name: "Mcguire Obrien",
4191 + company: "BUZZOPIA",
4192 + email: "mcguireobrien@buzzopia.com"
4193 + },
4194 + {
4195 + isActive: false,
4196 + age: 30,
4197 + eyeColor: "green",
4198 + name: "Stafford Delacruz",
4199 + company: "ROCKYARD",
4200 + email: "stafforddelacruz@rockyard.com"
4201 + },
4202 + {
4203 + isActive: false,
4204 + age: 31,
4205 + eyeColor: "blue",
4206 + name: "Rene Kemp",
4207 + company: "KOZGENE",
4208 + email: "renekemp@kozgene.com"
4209 + },
4210 + {
4211 + isActive: true,
4212 + age: 25,
4213 + eyeColor: "green",
4214 + name: "Bessie Gill",
4215 + company: "VICON",
4216 + email: "bessiegill@zicon.com"
4217 + },
4218 + {
4219 + isActive: true,
4220 + age: 34,
4221 + eyeColor: "brown",
4222 + name: "Graham Berry",
4223 + company: "ESCHOIR",
4224 + email: "grahamberry@eschoir.com"
4225 + },
4226 + {
4227 + isActive: false,
4228 + age: 24,
4229 + eyeColor: "blue",
4230 + name: "Margie Bentley",
4231 + company: "ZIZZLE",
4232 + email: "margiebentley@zizzle.com"
4233 + },
4234 + {
4235 + isActive: false,
4236 + age: 34,
4237 + eyeColor: "blue",
4238 + name: "Chapman Le",
4239 + company: "AUSTEX",
4240 + email: "chapmanle@austex.com"
4241 + },
4242 + {
4243 + isActive: true,
4244 + age: 39,
4245 + eyeColor: "green",
4246 + name: "Ramsey Nguyen",
4247 + company: "ISOTRONIC",
4248 + email: "ramseynguyen@isotronic.com"
4249 + },
4250 + {
4251 + isActive: true,
4252 + age: 36,
4253 + eyeColor: "green",
4254 + name: "Sexton Robinson",
4255 + company: "ENERSAVE",
4256 + email: "sextonrobinson@enersave.com"
4257 + },
4258 + {
4259 + isActive: true,
4260 + age: 37,
4261 + eyeColor: "brown",
4262 + name: "Bridgett David",
4263 + company: "ATGEN",
4264 + email: "bridgettdavid@atgen.com"
4265 + },
4266 + {
4267 + isActive: false,
4268 + age: 27,
4269 + eyeColor: "brown",
4270 + name: "Lillie Castro",
4271 + company: "DANJA",
4272 + email: "lilliecastro@danja.com"
4273 + },
4274 + {
4275 + isActive: false,
4276 + age: 27,
4277 + eyeColor: "brown",
4278 + name: "Sonia Larsen",
4279 + company: "HOUSEDOWN",
4280 + email: "sonialarsen@housedown.com"
4281 + },
4282 + {
4283 + isActive: false,
4284 + age: 28,
4285 + eyeColor: "green",
4286 + name: "Jessie Bright",
4287 + company: "MIXERS",
4288 + email: "jessiebright@mixers.com"
4289 + },
4290 + {
4291 + isActive: true,
4292 + age: 21,
4293 + eyeColor: "brown",
4294 + name: "Claudette Weber",
4295 + company: "PHOLIO",
4296 + email: "claudetteweber@pholio.com"
4297 + },
4298 + {
4299 + isActive: false,
4300 + age: 32,
4301 + eyeColor: "brown",
4302 + name: "Pittman William",
4303 + company: "MALATHION",
4304 + email: "pittmanwilliam@malathion.com"
4305 + },
4306 + {
4307 + isActive: false,
4308 + age: 33,
4309 + eyeColor: "green",
4310 + name: "Crystal Britt",
4311 + company: "AMRIL",
4312 + email: "crystalbritt@amril.com"
4313 + },
4314 + {
4315 + isActive: true,
4316 + age: 25,
4317 + eyeColor: "green",
4318 + name: "Vaughn Pitts",
4319 + company: "BEDLAM",
4320 + email: "vaughnpitts@bedlam.com"
4321 + },
4322 + {
4323 + isActive: true,
4324 + age: 25,
4325 + eyeColor: "brown",
4326 + name: "Shaw Rich",
4327 + company: "INSECTUS",
4328 + email: "shawrich@insectus.com"
4329 + },
4330 + {
4331 + isActive: true,
4332 + age: 36,
4333 + eyeColor: "green",
4334 + name: "Geneva Harmon",
4335 + company: "BLUEGRAIN",
4336 + email: "genevaharmon@bluegrain.com"
4337 + },
4338 + {
4339 + isActive: true,
4340 + age: 31,
4341 + eyeColor: "green",
4342 + name: "Josie Mcclure",
4343 + company: "CABLAM",
4344 + email: "josiemcclure@cablam.com"
4345 + },
4346 + {
4347 + isActive: false,
4348 + age: 21,
4349 + eyeColor: "blue",
4350 + name: "Lorrie Waters",
4351 + company: "CAPSCREEN",
4352 + email: "lorriewaters@capscreen.com"
4353 + },
4354 + {
4355 + isActive: false,
4356 + age: 36,
4357 + eyeColor: "brown",
4358 + name: "Desiree Bartlett",
4359 + company: "FLUM",
4360 + email: "desireebartlett@flum.com"
4361 + },
4362 + {
4363 + isActive: true,
4364 + age: 36,
4365 + eyeColor: "green",
4366 + name: "Sharp Fernandez",
4367 + company: "MARVANE",
4368 + email: "sharpfernandez@marvane.com"
4369 + },
4370 + {
4371 + isActive: true,
4372 + age: 32,
4373 + eyeColor: "brown",
4374 + name: "Carly Cole",
4375 + company: "BICOL",
4376 + email: "carlycole@bicol.com"
4377 + },
4378 + {
4379 + isActive: true,
4380 + age: 34,
4381 + eyeColor: "blue",
4382 + name: "Shelly Nielsen",
4383 + company: "ZILENCIO",
4384 + email: "shellynielsen@zilencio.com"
4385 + },
4386 + {
4387 + isActive: true,
4388 + age: 30,
4389 + eyeColor: "green",
4390 + name: "Powers Gordon",
4391 + company: "BLEENDOT",
4392 + email: "powersgordon@bleendot.com"
4393 + },
4394 + {
4395 + isActive: true,
4396 + age: 39,
4397 + eyeColor: "blue",
4398 + name: "Becky Sosa",
4399 + company: "EXOSPACE",
4400 + email: "beckysosa@exospace.com"
4401 + },
4402 + {
4403 + isActive: false,
4404 + age: 32,
4405 + eyeColor: "blue",
4406 + name: "Delacruz Browning",
4407 + company: "GRAINSPOT",
4408 + email: "delacruzbrowning@grainspot.com"
4409 + },
4410 + {
4411 + isActive: true,
4412 + age: 23,
4413 + eyeColor: "green",
4414 + name: "Jane Buckner",
4415 + company: "VIDTO",
4416 + email: "janebuckner@vidto.com"
4417 + },
4418 + {
4419 + isActive: true,
4420 + age: 39,
4421 + eyeColor: "green",
4422 + name: "Leslie Stewart",
4423 + company: "SNORUS",
4424 + email: "lesliestewart@snorus.com"
4425 + },
4426 + {
4427 + isActive: true,
4428 + age: 20,
4429 + eyeColor: "blue",
4430 + name: "Banks Clayton",
4431 + company: "EARTHMARK",
4432 + email: "banksclayton@earthmark.com"
4433 + },
4434 + {
4435 + isActive: false,
4436 + age: 30,
4437 + eyeColor: "blue",
4438 + name: "Moran Ingram",
4439 + company: "ARCHITAX",
4440 + email: "moraningram@architax.com"
4441 + },
4442 + {
4443 + isActive: true,
4444 + age: 23,
4445 + eyeColor: "green",
4446 + name: "Keith Garza",
4447 + company: "EBIDCO",
4448 + email: "keithgarza@ebidco.com"
4449 + },
4450 + {
4451 + isActive: false,
4452 + age: 27,
4453 + eyeColor: "blue",
4454 + name: "Martin Hendrix",
4455 + company: "QUANTASIS",
4456 + email: "martinhendrix@quantasis.com"
4457 + },
4458 + {
4459 + isActive: true,
4460 + age: 30,
4461 + eyeColor: "brown",
4462 + name: "Caroline Schneider",
4463 + company: "HOMETOWN",
4464 + email: "carolineschneider@hometown.com"
4465 + },
4466 + {
4467 + isActive: false,
4468 + age: 24,
4469 + eyeColor: "brown",
4470 + name: "Lesa Hanson",
4471 + company: "BUZZWORKS",
4472 + email: "lesahanson@buzzworks.com"
4473 + },
4474 + {
4475 + isActive: true,
4476 + age: 21,
4477 + eyeColor: "blue",
4478 + name: "Frost Dale",
4479 + company: "ZENTIA",
4480 + email: "frostdale@zentia.com"
4481 + },
4482 + {
4483 + isActive: false,
4484 + age: 26,
4485 + eyeColor: "green",
4486 + name: "Lewis Lawson",
4487 + company: "ANACHO",
4488 + email: "lewislawson@anacho.com"
4489 + },
4490 + {
4491 + isActive: false,
4492 + age: 31,
4493 + eyeColor: "green",
4494 + name: "Mejia Rosario",
4495 + company: "KEGULAR",
4496 + email: "mejiarosario@kegular.com"
4497 + },
4498 + {
4499 + isActive: false,
4500 + age: 36,
4501 + eyeColor: "green",
4502 + name: "Lora Sawyer",
4503 + company: "PULZE",
4504 + email: "lorasawyer@pulze.com"
4505 + },
4506 + {
4507 + isActive: true,
4508 + age: 37,
4509 + eyeColor: "brown",
4510 + name: "Durham Navarro",
4511 + company: "VORTEXACO",
4512 + email: "durhamnavarro@vortexaco.com"
4513 + },
4514 + {
4515 + isActive: false,
4516 + age: 39,
4517 + eyeColor: "green",
4518 + name: "Hull Gamble",
4519 + company: "BLUPLANET",
4520 + email: "hullgamble@bluplanet.com"
4521 + },
4522 + {
4523 + isActive: false,
4524 + age: 22,
4525 + eyeColor: "green",
4526 + name: "Sheryl Wyatt",
4527 + company: "ISOTERNIA",
4528 + email: "sherylwyatt@isoternia.com"
4529 + },
4530 + {
4531 + isActive: false,
4532 + age: 34,
4533 + eyeColor: "brown",
4534 + name: "Spears Simmons",
4535 + company: "ZIPAK",
4536 + email: "spearssimmons@zipak.com"
4537 + },
4538 + {
4539 + isActive: true,
4540 + age: 30,
4541 + eyeColor: "brown",
4542 + name: "Elba Stanley",
4543 + company: "PHOTOBIN",
4544 + email: "elbastanley@photobin.com"
4545 + },
4546 + {
4547 + isActive: false,
4548 + age: 38,
4549 + eyeColor: "green",
4550 + name: "Marquita Griffin",
4551 + company: "BUNGA",
4552 + email: "marquitagriffin@bunga.com"
4553 + },
4554 + {
4555 + isActive: false,
4556 + age: 24,
4557 + eyeColor: "green",
4558 + name: "Renee Zamora",
4559 + company: "EXOSTREAM",
4560 + email: "reneezamora@exostream.com"
4561 + },
4562 + {
4563 + isActive: true,
4564 + age: 27,
4565 + eyeColor: "blue",
4566 + name: "Margarita Howard",
4567 + company: "ANOCHA",
4568 + email: "margaritahoward@anocha.com"
4569 + },
4570 + {
4571 + isActive: true,
4572 + age: 25,
4573 + eyeColor: "blue",
4574 + name: "Riggs Levy",
4575 + company: "FLOTONIC",
4576 + email: "riggslevy@flotonic.com"
4577 + },
4578 + {
4579 + isActive: true,
4580 + age: 32,
4581 + eyeColor: "brown",
4582 + name: "Elena Mccoy",
4583 + company: "REVERSUS",
4584 + email: "elenamccoy@reversus.com"
4585 + },
4586 + {
4587 + isActive: false,
4588 + age: 37,
4589 + eyeColor: "green",
4590 + name: "Chan Carver",
4591 + company: "GINK",
4592 + email: "chancarver@gink.com"
4593 + },
4594 + {
4595 + isActive: false,
4596 + age: 37,
4597 + eyeColor: "brown",
4598 + name: "Cornelia Davenport",
4599 + company: "MARQET",
4600 + email: "corneliadavenport@marqet.com"
4601 + },
4602 + {
4603 + isActive: true,
4604 + age: 25,
4605 + eyeColor: "blue",
4606 + name: "Myers Gallagher",
4607 + company: "ENDIPIN",
4608 + email: "myersgallagher@endipin.com"
4609 + },
4610 + {
4611 + isActive: true,
4612 + age: 23,
4613 + eyeColor: "blue",
4614 + name: "Ines Knowles",
4615 + company: "EQUITAX",
4616 + email: "inesknowles@equitax.com"
4617 + },
4618 + {
4619 + isActive: true,
4620 + age: 27,
4621 + eyeColor: "blue",
4622 + name: "Kerri Mayer",
4623 + company: "SEQUITUR",
4624 + email: "kerrimayer@sequitur.com"
4625 + },
4626 + {
4627 + isActive: true,
4628 + age: 36,
4629 + eyeColor: "green",
4630 + name: "Mcconnell Soto",
4631 + company: "OCTOCORE",
4632 + email: "mcconnellsoto@octocore.com"
4633 + },
4634 + {
4635 + isActive: true,
4636 + age: 35,
4637 + eyeColor: "brown",
4638 + name: "Molly Mcdowell",
4639 + company: "PUSHCART",
4640 + email: "mollymcdowell@pushcart.com"
4641 + },
4642 + {
4643 + isActive: false,
4644 + age: 39,
4645 + eyeColor: "brown",
4646 + name: "Marci Webster",
4647 + company: "CALCULA",
4648 + email: "marciwebster@calcula.com"
4649 + },
4650 + {
4651 + isActive: true,
4652 + age: 25,
4653 + eyeColor: "blue",
4654 + name: "Jones Hutchinson",
4655 + company: "INSURESYS",
4656 + email: "joneshutchinson@insuresys.com"
4657 + },
4658 + {
4659 + isActive: true,
4660 + age: 20,
4661 + eyeColor: "green",
4662 + name: "Gilda Moses",
4663 + company: "PYRAMIS",
4664 + email: "gildamoses@pyramis.com"
4665 + },
4666 + {
4667 + isActive: false,
4668 + age: 30,
4669 + eyeColor: "brown",
4670 + name: "Melton Monroe",
4671 + company: "GROK",
4672 + email: "meltonmonroe@grok.com"
4673 + },
4674 + {
4675 + isActive: true,
4676 + age: 30,
4677 + eyeColor: "green",
4678 + name: "Nicholson Head",
4679 + company: "VALREDA",
4680 + email: "nicholsonhead@valreda.com"
4681 + },
4682 + {
4683 + isActive: true,
4684 + age: 26,
4685 + eyeColor: "brown",
4686 + name: "Bernadine Hubbard",
4687 + company: "EVENTAGE",
4688 + email: "bernadinehubbard@eventage.com"
4689 + },
4690 + {
4691 + isActive: false,
4692 + age: 34,
4693 + eyeColor: "green",
4694 + name: "Skinner Massey",
4695 + company: "EVEREST",
4696 + email: "skinnermassey@everest.com"
4697 + },
4698 + {
4699 + isActive: false,
4700 + age: 23,
4701 + eyeColor: "blue",
4702 + name: "Maggie Love",
4703 + company: "KEEG",
4704 + email: "maggielove@keeg.com"
4705 + },
4706 + {
4707 + isActive: false,
4708 + age: 28,
4709 + eyeColor: "blue",
4710 + name: "Zimmerman Burris",
4711 + company: "SAVVY",
4712 + email: "zimmermanburris@savvy.com"
4713 + },
4714 + {
4715 + isActive: true,
4716 + age: 28,
4717 + eyeColor: "brown",
4718 + name: "Florence Reeves",
4719 + company: "ISBOL",
4720 + email: "florencereeves@isbol.com"
4721 + },
4722 + {
4723 + isActive: false,
4724 + age: 29,
4725 + eyeColor: "brown",
4726 + name: "Berry Valencia",
4727 + company: "SNIPS",
4728 + email: "berryvalencia@snips.com"
4729 + },
4730 + {
4731 + isActive: true,
4732 + age: 21,
4733 + eyeColor: "green",
4734 + name: "Maricela Parker",
4735 + company: "IMMUNICS",
4736 + email: "maricelaparker@immunics.com"
4737 + }
4738 +]
src/views/Tables/grid-assets/plugin-date/calendar.svg new
+14
@@ -0,0 +1,14 @@
1 +<svg aria-hidden="true" height="24" viewBox="0 0 21 21" width="24" xmlns="http://www.w3.org/2000/svg">
2 + <g fill="none" fill-rule="evenodd" transform="translate(2 2)">
3 + <path d="m2.5.5h12c1.1045695 0 2 .8954305 2 2v12c0 1.1045695-.8954305 2-2 2h-12c-1.1045695 0-2-.8954305-2-2v-12c0-1.1045695.8954305-2 2-2z" stroke="grey" stroke-linecap="round" stroke-linejoin="round"></path>
4 + <path d="m.5 4.5h16" stroke="grey" stroke-linecap="round" stroke-linejoin="round"></path>
5 + <g fill="grey">
6 + <circle cx="8.5" cy="8.5" r="1"></circle>
7 + <circle cx="4.5" cy="8.5" r="1"></circle>
8 + <circle cx="12.5" cy="8.5" r="1"></circle>
9 + <circle cx="8.5" cy="12.5" r="1"></circle>
10 + <circle cx="4.5" cy="12.5" r="1"></circle>
11 + <circle cx="12.5" cy="12.5" r="1"></circle>
12 + </g>
13 + </g>
14 +</svg>
src/views/Tables/grid-assets/plugin-date/index.ts new
+74
@@ -0,0 +1,74 @@
1 +import type { HyperFunc, ColumnRegular } from "@revolist/revogrid/dist/types/types/interfaces.d.ts"
2 +import type { EditorBase, EditCell } from "@revolist/revogrid/dist/types/types/selection.d.ts"
3 +import { type VNode } from "@revolist/revogrid/dist/types/stencil-public-runtime.d"
4 +import dayjs from "@/utils/dayjs"
5 +import icon from "./calendar.svg?url"
6 +
7 +const ColumnRenderer = (h: any, { model, prop }: any): any[] => {
8 + const val = model[prop]
9 + const isValid = dayjs(val).isValid()
10 + return h(
11 + "span",
12 + {
13 + style: {
14 + display: "flex",
15 + justifyContent: "space-between",
16 + alignItems: "center"
17 + }
18 + },
19 + [
20 + isValid ? dayjs(val).format("DD/MM/YYYY") : val,
21 + h("img", {
22 + width: 14,
23 + src: icon
24 + })
25 + ]
26 + )
27 +}
28 +
29 +class DateEditor implements EditorBase {
30 + public element: Element | null = null
31 + public editCell: EditCell | undefined
32 +
33 + constructor(
34 + public column: ColumnRegular,
35 + private saveCallback: (value: any) => void,
36 + private closeCallback: () => void
37 + ) {}
38 +
39 + // optional, called after editor rendered
40 + componentDidRender() {}
41 +
42 + // optional, called after editor destroyed
43 + disconnectedCallback() {}
44 +
45 + render(createComponent: HyperFunc<VNode>) {
46 + let val = ""
47 + if (this?.editCell) {
48 + const model = this?.editCell.model || {}
49 + val = model[this?.editCell?.prop] || ""
50 + }
51 +
52 + return createComponent("input", {
53 + type: "date",
54 + value: val,
55 + style: {
56 + margin: "0 15px",
57 + width: " calc(100% - 30px)",
58 + height: "100%"
59 + },
60 + onChange: (event: Event) => {
61 + const inputElement = event.target as HTMLInputElement
62 + this.saveCallback(inputElement?.value)
63 + }
64 + })
65 + }
66 +}
67 +
68 +export default class ColumnType {
69 + constructor() {}
70 +
71 + readonly editor = DateEditor
72 +
73 + readonly cellTemplate = ColumnRenderer
74 +}
src/views/Tables/grid-assets/plugin-select/arrow.svg new
+1
@@ -0,0 +1 @@
1 +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 20 20"><g fill="none"><path d="M5.019 8.628A1 1 0 0 1 5.797 7h8.407a1 1 0 0 1 .778 1.628l-3.815 4.723a1.5 1.5 0 0 1-2.334 0L5.02 8.628zM14.204 8H5.797l3.814 4.723a.5.5 0 0 0 .778 0L14.204 8z" fill="grey"></path></g></svg>
src/views/Tables/grid-assets/plugin-select/index.ts new
+91
@@ -0,0 +1,91 @@
1 +import type { HyperFunc, ColumnRegular } from "@revolist/revogrid/dist/types/types/interfaces.d.ts"
2 +import type { EditorBase, EditCell } from "@revolist/revogrid/dist/types/types/selection.d.ts"
3 +import { type VNode } from "@revolist/revogrid/dist/types/stencil-public-runtime.d"
4 +import icon from "./arrow.svg?url"
5 +
6 +const ColumnRenderer = (h: any, { model, prop }: any): any[] => {
7 + const val = model[prop]
8 + return h(
9 + "span",
10 + {
11 + style: {
12 + display: "flex",
13 + justifyContent: "space-between",
14 + alignItems: "center"
15 + }
16 + },
17 + [
18 + val,
19 + h("img", {
20 + width: 18,
21 + src: icon
22 + })
23 + ]
24 + )
25 +}
26 +
27 +class SelectEditor implements EditorBase {
28 + public element: Element | null = null
29 + public editCell: EditCell | undefined
30 +
31 + constructor(
32 + public column: ColumnRegular,
33 + private saveCallback: (value: any) => void,
34 + private closeCallback: () => void
35 + ) {}
36 +
37 + // optional, called after editor rendered
38 + componentDidRender() {}
39 +
40 + // optional, called after editor destroyed
41 + disconnectedCallback() {}
42 +
43 + render(createComponent: HyperFunc<VNode>) {
44 + let val = ""
45 + if (this?.editCell) {
46 + const model = this?.editCell.model || {}
47 + val = model[this?.editCell?.prop] || ""
48 + }
49 +
50 + const options = []
51 + if (this.column?.source?.length) {
52 + for (const source of this.column.source) {
53 + options.push(
54 + createComponent(
55 + "option",
56 + {
57 + value: source,
58 + selected: source === val
59 + },
60 + source
61 + )
62 + )
63 + }
64 + }
65 +
66 + return createComponent(
67 + "select",
68 + {
69 + value: val,
70 + style: {
71 + margin: "0 15px",
72 + width: " calc(100% - 30px)",
73 + height: "100%"
74 + },
75 + onChange: (event: Event) => {
76 + const inputElement = event.target as HTMLInputElement
77 + this.saveCallback(inputElement?.value)
78 + }
79 + },
80 + options
81 + )
82 + }
83 +}
84 +
85 +export default class ColumnType {
86 + constructor() {}
87 +
88 + readonly editor = SelectEditor
89 +
90 + readonly cellTemplate = ColumnRenderer
91 +}
src/views/socfortress/AgentOverview.vue
+7 -5
@@ -2,7 +2,7 @@
2 <div class="page">
3 <div class="agent-toolbar">
4 <div class="back-btn" @click="gotoAgents()">
5 - <n-icon :size="16"><ArrowIcon /></n-icon>
5 + <Icon :name="ArrowIcon" :size="16"></Icon>
6 <span>Agents list</span>
7 </div>
8 <div class="delete-btn" @click.stop="handleDelete" v-if="agent">Delete Agent</div>
@@ -24,7 +24,7 @@
24 @click.stop="toggleCritical(agent.agent_id, agent.critical_asset)"
25 >
26 <template #icon>
27 - <n-icon><StarIcon /></n-icon>
27 + <Icon :name="StarIcon"></Icon>
28 </template>
29 </n-button>
30 </template>
@@ -65,12 +65,14 @@ import { useRoute } from "vue-router"
65 import Api from "@/api"
66 import { type Agent } from "@/types/agents.d"
67 import { handleDeleteAgent, isAgentOnline, toggleAgentCritical } from "@/components/agents/utils"
68 -import StarIcon from "@vicons/carbon/Star"
68 import { useRouter } from "vue-router"
69 import VulnerabilitiesSection from "@/components/agents/VulnerabilitiesSection.vue"
70 import OverviewSection from "@/components/agents/OverviewSection.vue"
72 -import { useMessage, NSpin, NTooltip, NButton, NIcon, NTabs, NTabPane, NCard, useDialog } from "naive-ui"
73 -import ArrowIcon from "@vicons/carbon/ArrowLeft"
71 +import { useMessage, NSpin, NTooltip, NButton, NTabs, NTabPane, NCard, useDialog } from "naive-ui"
72 +import Icon from "@/components/common/Icon.vue"
73 +
74 +const StarIcon = "carbon:star"
75 +const ArrowIcon = "carbon:arrow-left"
76
77 const message = useMessage()
78 const router = useRouter()
src/views/socfortress/graylog/Management.vue new
+41
@@ -0,0 +1,41 @@
1 +<template>
2 + <div class="page">
3 + <n-tabs type="line" animated>
4 + <n-tab-pane name="messages" tab="Messages" display-directive="show:lazy">
5 + <Messages />
6 + </n-tab-pane>
7 + <n-tab-pane name="alerts" tab="Alerts" display-directive="show:lazy">
8 + <Alerts />
9 + </n-tab-pane>
10 + <n-tab-pane name="events" tab="Events" display-directive="show:lazy">
11 + <Events />
12 + </n-tab-pane>
13 + <n-tab-pane name="streams" tab="Streams" display-directive="show:lazy">
14 + <Streams />
15 + </n-tab-pane>
16 + <template #suffix>
17 + <n-button ghost type="primary" size="small" @click="showInputDrawer = true">Inputs</n-button>
18 + </template>
19 + </n-tabs>
20 +
21 + <n-drawer v-model:show="showInputDrawer" :width="700" style="max-width: 90vw" :trap-focus="false">
22 + <n-drawer-content title="Inputs" closable body-content-style="padding:0">
23 + <Inputs />
24 + </n-drawer-content>
25 + </n-drawer>
26 + </div>
27 +</template>
28 +
29 +<script setup>
30 +import { ref } from "vue"
31 +import { NTabs, NTabPane, NButton, NDrawer, NDrawerContent } from "naive-ui"
32 +import Messages from "@/components/graylog/Messages/List.vue"
33 +import Alerts from "@/components/graylog/Alerts/List.vue"
34 +import Events from "@/components/graylog/Events/List.vue"
35 +import Streams from "@/components/graylog/Streams/List.vue"
36 +import Inputs from "@/components/graylog/Inputs/List.vue"
37 +
38 +const showInputDrawer = ref(false)
39 +</script>
40 +
41 +<style lang="scss" scoped></style>
src/views/socfortress/graylog/Metrics.vue new
+138
@@ -0,0 +1,138 @@
1 +<template>
2 + <div class="page">
3 + <div class="header flex flex-wrap justify-between items-center gap-4">
4 + <div class="info flex items-center gap-3">
5 + <n-button size="small" @click="getData()" type="primary" secondary :loading="loading">
6 + <template #icon><Icon :name="UpdatedIcon" :size="15"></Icon></template>
7 + </n-button>
8 + <span>Last check:</span>
9 + <strong>{{ lastCheck ? formatDate(lastCheck) : "..." }}</strong>
10 + </div>
11 +
12 + <div class="toolbar flex items-center gap-3">
13 + <n-button size="small" @click="start()" v-if="!isRunning" type="primary" class="!w-24">
14 + <template #icon><Icon :name="StartIcon"></Icon></template>
15 + Start
16 + </n-button>
17 + <n-button size="small" @click="stop()" v-if="isRunning" type="error" ghost class="!w-24">
18 + <template #icon><Icon :name="StopIcon"></Icon></template>
19 + Stop
20 + </n-button>
21 + <n-select size="small" v-model:value="intervalSelected" :options="intervalOptions" class="!w-36" />
22 + </div>
23 + </div>
24 +
25 + <div class="my-6">
26 + <UncommittedEntries :value="uncommittedJournalEntries" />
27 + </div>
28 +
29 + <div>
30 + <MetricsList :throughput-metrics="throughputMetrics" />
31 + </div>
32 + </div>
33 +</template>
34 +
35 +<script setup lang="ts">
36 +import { ref, onBeforeMount, computed, watch, nextTick, onBeforeUnmount } from "vue"
37 +import { useMessage, NButton, NSelect } from "naive-ui"
38 +import Api from "@/api"
39 +import type { ThroughputMetric } from "@/types/graylog/index.d"
40 +import Icon from "@/components/common/Icon.vue"
41 +import UncommittedEntries from "@/components/graylog/Metrics/UncommittedEntries.vue"
42 +import MetricsList from "@/components/graylog/Metrics/List.vue"
43 +import dayjs from "@/utils/dayjs"
44 +import { useSettingsStore } from "@/stores/settings"
45 +import { useStorage } from "@vueuse/core"
46 +
47 +const UpdatedIcon = "carbon:update-now"
48 +const StopIcon = "carbon:stop"
49 +const StartIcon = "carbon:play"
50 +
51 +const message = useMessage()
52 +const loading = ref(false)
53 +const uncommittedJournalEntries = ref(0)
54 +const throughputMetrics = ref<ThroughputMetric[]>([])
55 +const lastCheck = ref<null | Date>(null)
56 +const getDataTimer = ref<NodeJS.Timeout | null>(null)
57 +const dFormats = useSettingsStore().dateFormat
58 +const intervalOptions = [
59 + {
60 + label: "1 Second",
61 + value: 1000
62 + },
63 + {
64 + label: "5 Seconds",
65 + value: 5000
66 + },
67 + {
68 + label: "10 Seconds",
69 + value: 10000
70 + },
71 + {
72 + label: "30 Seconds",
73 + value: 30000
74 + },
75 + {
76 + label: "1 Minute",
77 + value: 60000
78 + }
79 +]
80 +const intervalSelected = useStorage<number>("metrics-interval", 5000, localStorage)
81 +
82 +const isRunning = computed<boolean>(() => {
83 + return !!getDataTimer.value
84 +})
85 +
86 +function getData() {
87 + loading.value = true
88 +
89 + Api.graylog
90 + .getMetrics()
91 + .then(res => {
92 + if (res.data.success) {
93 + throughputMetrics.value = res.data.throughput_metrics || []
94 + uncommittedJournalEntries.value = res.data.uncommitted_journal_entries || 0
95 + lastCheck.value = new Date()
96 + } else {
97 + message.warning(res.data?.message || "An error occurred. Please try again later.")
98 + }
99 + })
100 + .catch(err => {
101 + message.error(err.response?.data?.message || "An error occurred. Please try again later.")
102 + })
103 + .finally(() => {
104 + loading.value = false
105 + })
106 +}
107 +
108 +function stop() {
109 + if (getDataTimer.value !== null) {
110 + clearInterval(getDataTimer.value)
111 + getDataTimer.value = null
112 + }
113 +}
114 +
115 +function start() {
116 + getDataTimer.value = setInterval(getData, intervalSelected.value)
117 +}
118 +
119 +function formatDate(timestamp: string | Date): string {
120 + return dayjs(timestamp).format(dFormats.datetimesec)
121 +}
122 +
123 +watch(intervalSelected, () => {
124 + stop()
125 + nextTick(() => {
126 + start()
127 + })
128 +})
129 +
130 +onBeforeMount(() => {
131 + getData()
132 + start()
133 +})
134 +
135 +onBeforeUnmount(() => {
136 + stop()
137 +})
138 +</script>
src/views/socfortress/graylog/Pipelines.vue new
+134
@@ -0,0 +1,134 @@
1 +<template>
2 + <div class="page">
3 + <div class="mb-4">
4 + <n-button secondary type="primary" @click="showRulesDrawer = true">
5 + <template #icon>
6 + <Icon :name="RulesIcon" :size="22"></Icon>
7 + </template>
8 + View All Rules
9 + </n-button>
10 + </div>
11 + <n-card>
12 + <n-spin :show="loading">
13 + <n-collapse v-model:expanded-names="selectedPipeline" accordion>
14 + <n-collapse-item :title="pipe.title" :name="pipe.id" v-for="pipe of pipelines" :key="pipe.id">
15 + <template #header>
16 + <PipeTitle :pipeline="pipe" />
17 + </template>
18 + <template #header-extra>
19 + <n-button size="small" @click.stop="openModal(pipe)">
20 + <template #icon>
21 + <Icon :name="InfoIcon"></Icon>
22 + </template>
23 + </n-button>
24 + </template>
25 + <div class="overflow-hidden">
26 + <PipeDetails :pipeline="pipe" @click-rule="openRule($event)" />
27 + </div>
28 + </n-collapse-item>
29 + </n-collapse>
30 + </n-spin>
31 + </n-card>
32 +
33 + <n-modal
34 + v-model:show="showDetails"
35 + preset="card"
36 + content-style="padding:0px"
37 + :style="{ maxWidth: 'min(600px, 90vw)', overflow: 'hidden' }"
38 + :title="highlightPipe?.title"
39 + :bordered="false"
40 + segmented
41 + >
42 + <PipeInfo :pipeline="highlightPipe" />
43 + </n-modal>
44 +
45 + <n-drawer
46 + v-model:show="showRulesDrawer"
47 + :width="700"
48 + style="max-width: 90vw"
49 + :trap-focus="false"
50 + display-directive="show"
51 + >
52 + <n-drawer-content closable body-content-style="padding:0">
53 + <template #header>
54 + <span>Rules list</span>
55 + <span class="font-mono ml-2 opacity-60" v-if="rulesTotal !== null">{{ rulesTotal }}</span>
56 + </template>
57 + <RulesList @loaded="rulesTotal = $event.total" :highlight="highlightRule" />
58 + </n-drawer-content>
59 + </n-drawer>
60 + </div>
61 +</template>
62 +
63 +<script setup lang="ts">
64 +import { useMessage, NCollapse, NCollapseItem, NSpin, NButton, NModal, NCard, NDrawer, NDrawerContent } from "naive-ui"
65 +import { onBeforeMount, ref } from "vue"
66 +import type { PipelineFull } from "@/types/graylog/pipelines.d"
67 +import Api from "@/api"
68 +import Icon from "@/components/common/Icon.vue"
69 +import PipeDetails from "@/components/graylog/Pipelines/PipeDetails.vue"
70 +import PipeInfo from "@/components/graylog/Pipelines/PipeInfo.vue"
71 +import PipeTitle from "@/components/graylog/Pipelines/PipeTitle.vue"
72 +import RulesList from "@/components/graylog/Pipelines/RulesList.vue"
73 +import { watch } from "vue"
74 +
75 +const RulesIcon = "ic:outline-swipe-right-alt"
76 +const InfoIcon = "carbon:information"
77 +
78 +const message = useMessage()
79 +const showDetails = ref(false)
80 +const loading = ref(false)
81 +const pipelines = ref<PipelineFull[]>([])
82 +const selectedPipeline = ref<string | null>(null)
83 +const highlightPipe = ref<PipelineFull | undefined>(undefined)
84 +const highlightRule = ref<string | null>(null)
85 +const showRulesDrawer = ref(false)
86 +const rulesTotal = ref<null | number>(null)
87 +
88 +function openRule(id: string) {
89 + highlightRule.value = id
90 + showRulesDrawer.value = true
91 +}
92 +
93 +function setHighlightPipe(pipeline: PipelineFull) {
94 + highlightPipe.value = pipeline
95 +}
96 +
97 +function openModal(pipeline: PipelineFull) {
98 + setHighlightPipe(pipeline)
99 + showDetails.value = true
100 +}
101 +
102 +function getPipelines() {
103 + loading.value = true
104 +
105 + Api.graylog
106 + .getPipelinesFull()
107 + .then(res => {
108 + if (res.data.success) {
109 + pipelines.value = res.data.pipelines || []
110 + if (pipelines.value.length && !selectedPipeline.value) {
111 + selectedPipeline.value = pipelines.value[0].id
112 + }
113 + } else {
114 + message.warning(res.data?.message || "An error occurred. Please try again later.")
115 + }
116 + })
117 + .catch(err => {
118 + message.error(err.response?.data?.message || "An error occurred. Please try again later.")
119 + })
120 + .finally(() => {
121 + loading.value = false
122 + })
123 +}
124 +
125 +watch(showRulesDrawer, val => {
126 + if (!val) {
127 + highlightRule.value = null
128 + }
129 +})
130 +
131 +onBeforeMount(() => {
132 + getPipelines()
133 +})
134 +</script>
tailwind.config.js
+40 -11
@@ -1,5 +1,16 @@
1 /** @type {import('tailwindcss').Config} */
2 const plugin = require("tailwindcss/plugin")
3 +const tokens = require("./src/design-tokens.json")
4 +const _ = require("lodash")
5 +
6 +function getValue(origin, val) {
7 + if (val && val.indexOf("{") === 0) {
8 + const path = val.replace("{", "").replace("}", "")
9 + return _.get(origin, path)
10 + }
11 +
12 + return val
13 +}
14
15 module.exports = {
16 content: ["./index.html", "./src/**/*.{vue,js,ts,jsx,tsx}"],
@@ -15,27 +26,45 @@ module.exports = {
26 plugin(function ({ addBase, theme }) {
27 addBase({
28 h1: {
18 - fontWeight: theme("fontWeight.bold"),
19 - letterSpacing: theme("letterSpacing.tight")
29 + fontFamily: getValue(tokens, tokens?.typography?.h1?.fontFamily),
30 + fontWeight: getValue(tokens, tokens?.typography?.h1?.fontWeight) || theme("fontWeight.bold"),
31 + fontSize: getValue(tokens, tokens?.typography?.h1?.fontSize),
32 + letterSpacing:
33 + getValue(tokens, tokens?.typography?.h1?.letterSpacing) || theme("letterSpacing.tight")
34 },
35 h2: {
22 - fontWeight: theme("fontWeight.bold"),
23 - letterSpacing: theme("letterSpacing.tight")
36 + fontFamily: getValue(tokens, tokens?.typography?.h2?.fontFamily),
37 + fontWeight: getValue(tokens, tokens?.typography?.h2?.fontWeight) || theme("fontWeight.bold"),
38 + fontSize: getValue(tokens, tokens?.typography?.h2?.fontSize),
39 + letterSpacing:
40 + getValue(tokens, tokens?.typography?.h2?.letterSpacing) || theme("letterSpacing.tight")
41 },
42 h3: {
26 - fontWeight: theme("fontWeight.bold"),
27 - letterSpacing: theme("letterSpacing.tight")
43 + fontFamily: getValue(tokens, tokens?.typography?.h3?.fontFamily),
44 + fontWeight: getValue(tokens, tokens?.typography?.h3?.fontWeight) || theme("fontWeight.bold"),
45 + fontSize: getValue(tokens, tokens?.typography?.h3?.fontSize),
46 + letterSpacing:
47 + getValue(tokens, tokens?.typography?.h3?.letterSpacing) || theme("letterSpacing.tight")
48 },
49 h4: {
30 - fontWeight: theme("fontWeight.medium"),
31 - letterSpacing: theme("letterSpacing.tight")
50 + fontFamily: getValue(tokens, tokens?.typography?.h4?.fontFamily),
51 + fontWeight: getValue(tokens, tokens?.typography?.h4?.fontWeight) || theme("fontWeight.medium"),
52 + fontSize: getValue(tokens, tokens?.typography?.h4?.fontSize),
53 + letterSpacing:
54 + getValue(tokens, tokens?.typography?.h4?.letterSpacing) || theme("letterSpacing.tight")
55 },
56 h5: {
34 - fontWeight: theme("fontWeight.bold"),
35 - letterSpacing: theme("letterSpacing.tight")
57 + fontFamily: getValue(tokens, tokens?.typography?.h5?.fontFamily),
58 + fontWeight: getValue(tokens, tokens?.typography?.h5?.fontWeight) || theme("fontWeight.bold"),
59 + fontSize: getValue(tokens, tokens?.typography?.h5?.fontSize),
60 + letterSpacing:
61 + getValue(tokens, tokens?.typography?.h5?.letterSpacing) || theme("letterSpacing.tight")
62 },
63 h6: {
38 - fontWeight: theme("fontWeight.medium")
64 + fontFamily: getValue(tokens, tokens?.typography?.h6?.fontFamily),
65 + fontWeight: getValue(tokens, tokens?.typography?.h6?.fontWeight) || theme("fontWeight.bold"),
66 + fontSize: getValue(tokens, tokens?.typography?.h6?.fontSize),
67 + letterSpacing: getValue(tokens, tokens?.typography?.h6?.letterSpacing)
68 }
69 })
70 })
vite.config.ts
+1 -1
@@ -28,6 +28,6 @@ export default defineConfig({
28 }
29 },
30 optimizeDeps: {
31 - include: ["@fawmi/vue-google-maps", "fast-deep-equal", "@vicons/fluent"]
31 + include: ["@fawmi/vue-google-maps", "fast-deep-equal"]
32 }
33 })