diff --git a/data/dispatch-sample-data.dump b/data/dispatch-sample-data.dump index 475b49969892..af733b784449 100644 --- a/data/dispatch-sample-data.dump +++ b/data/dispatch-sample-data.dump @@ -2996,7 +2996,7 @@ CREATE TABLE dispatch_organization_default.entity ( name character varying, description character varying, value character varying, - source boolean, + source character varying, entity_type_id integer NOT NULL, search_vector tsvector, project_id integer, @@ -3037,7 +3037,7 @@ CREATE TABLE dispatch_organization_default.entity_type ( id integer NOT NULL, name character varying, description character varying, - jpath character varying NOT NULL, + jpath character varying, regular_expression character varying, enabled boolean, search_vector tsvector, @@ -3967,7 +3967,6 @@ ALTER SEQUENCE dispatch_organization_default.participant_role_id_seq OWNED BY di CREATE TABLE dispatch_organization_default.plugin_instance ( id integer NOT NULL, enabled boolean, - configuration json, plugin_id integer, project_id integer, _configuration character varying @@ -7666,7 +7665,7 @@ COPY dispatch_core.plugin_event (id, name, slug, description, plugin_id, search_ -- COPY dispatch_organization_default.alembic_version (version_num) FROM stdin; -3edb0476365a +928b725d64f6 \. @@ -7724,7 +7723,6 @@ COPY dispatch_organization_default.assoc_document_tags (document_id, tag_id) FRO COPY dispatch_organization_default.assoc_incident_tags (incident_id, tag_id) FROM stdin; 2 1 -7 1 \. @@ -8080,9 +8078,6 @@ COPY dispatch_organization_default.event (id, uuid, started_at, ended_at, source 56 6fe13da1-de96-41dd-ba9a-29a752060b46 2021-07-27 20:11:48.28756 2021-07-27 20:11:48.28756 Dispatch Core App Conversation added to incident null \N 6 'ad':5B 'app':3A 'convers':4B 'core':2A 'dispatch':1A 'incid':7B 2021-07-27 20:11:48.307412 2021-07-27 20:11:48.290557 \N \N \N \N \N 58 d66ec104-af72-46df-80a8-2b32a6fa8944 2021-07-27 20:11:59.021607 2021-07-27 20:11:59.021607 Dispatch Core App Incident notifications sent null \N 6 'app':3A 'core':2A 'dispatch':1A 'incid':4B 'notif':5B 'sent':6B 2021-07-27 20:11:59.04991 2021-07-27 20:11:59.022365 \N \N \N \N \N 59 7e011a9d-9bb9-4770-b5e3-1a21197e60c2 2021-07-28 17:13:49.192243 2021-07-28 17:13:49.192243 Dispatch Core App New incident task created by Kevin Glisson {"weblink": null} \N 4 'app':3A 'core':2A 'creat':7B 'dispatch':1A 'glisson':10B 'incid':5B 'kevin':9B 'new':4B 'task':6B 2021-07-28 17:13:49.218153 2021-07-28 17:13:49.199624 \N \N \N \N \N -60 2627886d-5466-47d8-a702-6596a17561a8 2024-02-04 02:51:26.185354 2024-02-04 02:51:26.185354 Dispatch Core App Incident created {"title": "Incident Test Created by Playwright", "description": "Test description created by Playwright", "type": "Denial of Service", "severity": "Undetermined", "priority": "Low", "status": "Active", "visibility": "Open"} \N 7 'app':3A 'core':2A 'creat':5B 'dispatch':1A 'incid':4B 2024-02-04 02:51:26.366892 2024-02-04 02:51:26.190379 \N \N Other \N t -61 5501e170-a4a6-4c29-a582-9d8e6367c35b 2024-02-04 02:51:26.752647 2024-02-04 02:51:26.752647 Dispatch Core App 3p1t6@example.com added to incident with Reporter role null \N 7 '3p1t6@example.com':4B 'ad':5B 'app':3A 'core':2A 'dispatch':1A 'incid':7B 'report':9B 'role':10B 2024-02-04 02:51:26.940241 2024-02-04 02:51:26.753164 \N \N Participant updated f -62 c1baff75-d46a-4cae-a793-9dab11d749e1 2024-02-04 02:51:27.112502 2024-02-04 02:51:27.112502 Dispatch Core App 3p1t6@example.com added to incident with Incident Commander role null \N 7 '3p1t6@example.com':4B 'ad':5B 'app':3A 'command':10B 'core':2A 'dispatch':1A 'incid':7B,9B 'role':11B 2024-02-04 02:51:27.222527 2024-02-04 02:51:27.113162 \N \N Participant updated f \. @@ -8135,7 +8130,6 @@ COPY dispatch_organization_default.incident (id, name, title, description, statu 4 dispatch-default-default-4 Heartbleed Sad PKI noises Stable Open 2021-07-27 19:52:57.757214 2021-07-27 19:54:03.96021 \N '4':9A 'default':7A,8A 'dispatch':6A 'dispatch-default-default':5A 'heartble':1B 'nois':4C 'pki':3C 'sad':2C 1 1 \N 1 2021-07-27 19:52:57.757221 2021-07-28 17:13:49.216785 Description of the actions taken to resolve the incident. Unknown America/Los_Angeles America/Los_Angeles America/Los_Angeles 2 2 2 \N \N \N 3 4 1 \N \N 5 dispatch-default-default-5 Solarwinds More like a solar tornado. Active Open 2021-07-27 20:06:15.252697 \N \N '5':11A 'default':9A,10A 'dispatch':8A 'dispatch-default-default':7A 'like':3C 'solar':5C 'solarwind':1B 'tornado':6C 2 1 \N 1 2021-07-27 20:06:15.252705 2021-07-27 20:06:41.627061 Description of the actions taken to resolve the incident. Unknown America/Los_Angeles America/Los_Angeles America/Los_Angeles 3 3 3 \N \N \N 5 6 1 \N \N 6 dispatch-default-default-6 Kaseya Those backups are good right? Active Open 2021-07-27 20:11:30.525883 \N \N '6':11A 'backup':3C 'default':9A,10A 'dispatch':8A 'dispatch-default-default':7A 'good':5C 'kaseya':1B 'right':6C 3 1 \N 1 2021-07-27 20:11:30.525893 2021-07-27 20:11:59.048666 Description of the actions taken to resolve the incident. Unknown America/Los_Angeles America/Los_Angeles America/Los_Angeles 4 4 4 \N \N \N 7 8 1 \N \N -7 \N Incident Test Created by Playwright Test description created by Playwright Active Open 2024-02-04 02:51:26.109901 \N \N 'creat':3B,8C 'descript':7C 'incid':1B 'playwright':5B,10C 'test':2B,6C 3 5 \N 1 2024-02-04 02:51:26.109912 2024-02-04 02:51:27.220352 Description of the actions taken to resolve the incident. example.com Unknown Unknown Unknown 5 5 \N \N \N \N \N \N 1 \N \N \. @@ -8272,7 +8266,6 @@ COPY dispatch_organization_default.participant (id, team, department, location, 2 Unknown Unknown America/Los_Angeles \N \N f \N 4 2 \N \N 3 Unknown Unknown America/Los_Angeles \N \N f \N 5 2 \N \N 4 Unknown Unknown America/Los_Angeles \N \N f \N 6 2 \N \N -5 example.com Unknown Unknown \N \N f \N 7 3 \N \N \. @@ -8301,8 +8294,6 @@ COPY dispatch_organization_default.participant_role (id, assumed_at, renounced_a 10 2021-07-27 20:11:32.314039 \N Reporter 4 1 11 2021-07-27 20:11:32.427753 \N Incident Commander 4 1 12 2021-07-27 20:11:32.491482 \N Liaison 4 1 -13 2024-02-04 02:51:26.570736 \N Reporter 5 0 -14 2024-02-04 02:51:27.008425 \N Incident Commander 5 0 \. @@ -8310,20 +8301,19 @@ COPY dispatch_organization_default.participant_role (id, assumed_at, renounced_a -- Data for Name: plugin_instance; Type: TABLE DATA; Schema: dispatch_organization_default; Owner: postgres -- -COPY dispatch_organization_default.plugin_instance (id, enabled, configuration, plugin_id, project_id, _configuration) FROM stdin; -1 t {} 2 1 \N -3 t {} 4 1 \N -9 \N {} 16 1 \N -11 f {} 19 1 \N -8 f {} 18 1 \N -7 f {} 13 1 \N -6 f {} 12 1 \N -14 f {} 10 1 \N -13 f {} 9 1 \N -12 f {} 8 1 \N -5 t {} 6 1 \N -2 t {} 3 1 \N -4 t {} 7 1 \N +COPY dispatch_organization_default.plugin_instance (id, enabled, plugin_id, project_id, _configuration) FROM stdin; +1 t 2 1 \N +9 \N 16 1 \N +11 f 19 1 \N +8 f 18 1 \N +7 f 13 1 \N +6 f 12 1 \N +14 f 10 1 \N +13 f 9 1 \N +12 f 8 1 \N +5 t 6 1 \N +2 t 3 1 \N +4 t 7 1 \N \. @@ -11676,6 +11666,13 @@ CREATE INDEX definition_search_vector_idx ON dispatch_organization_default.defin CREATE INDEX document_search_vector_idx ON dispatch_organization_default.document USING gin (search_vector); +-- +-- Name: entity_search_vector_idx; Type: INDEX; Schema: dispatch_organization_default; Owner: postgres +-- + +CREATE INDEX entity_search_vector_idx ON dispatch_organization_default.entity USING gin (search_vector); + + -- -- Name: entity_type_search_vector_idx; Type: INDEX; Schema: dispatch_organization_default; Owner: postgres -- @@ -11739,13 +11736,6 @@ CREATE INDEX incident_type_search_vector_idx ON dispatch_organization_default.in CREATE INDEX individual_contact_search_vector_idx ON dispatch_organization_default.individual_contact USING gin (search_vector); --- --- Name: ix_entity_search_vector; Type: INDEX; Schema: dispatch_organization_default; Owner: postgres --- - -CREATE INDEX ix_entity_search_vector ON dispatch_organization_default.entity USING gin (search_vector); - - -- -- Name: notification_search_vector_idx; Type: INDEX; Schema: dispatch_organization_default; Owner: postgres -- @@ -13664,14 +13654,6 @@ ALTER TABLE ONLY dispatch_organization_default.project ADD CONSTRAINT project_organization_id_fkey FOREIGN KEY (organization_id) REFERENCES dispatch_core.organization(id); --- --- Name: project project_stable_priority_id_fkey; Type: FK CONSTRAINT; Schema: dispatch_organization_default; Owner: postgres --- - -ALTER TABLE ONLY dispatch_organization_default.project - ADD CONSTRAINT project_stable_priority_id_fkey FOREIGN KEY (stable_priority_id) REFERENCES dispatch_organization_default.incident_priority(id); - - -- -- Name: query query_project_id_fkey; Type: FK CONSTRAINT; Schema: dispatch_organization_default; Owner: postgres -- diff --git a/docs/package-lock.json b/docs/package-lock.json index 5cfa8ef2ec69..976ca1b4c7cc 100644 --- a/docs/package-lock.json +++ b/docs/package-lock.json @@ -5229,9 +5229,9 @@ } }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", diff --git a/playwright.config.ts b/playwright.config.ts index f86fa7ed8b62..47e5d0f5dd2f 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -18,13 +18,13 @@ const config: PlaywrightTestConfig = { screenshot: "on", }, /* Maximum time one test can run for. */ - timeout: 100 * 1000, + timeout: 200 * 1000, expect: { /** * Maximum time expect() should wait for the condition to be met. * For example in `await expect(locator).toHaveText();` */ - timeout: 10000, + timeout: 20000, }, /* Run tests in files in parallel */ fullyParallel: true, diff --git a/requirements-base.txt b/requirements-base.txt index 880bb2f8861c..a32a3b84cf40 100644 --- a/requirements-base.txt +++ b/requirements-base.txt @@ -10,7 +10,7 @@ aiofiles==24.1.0 # via -r requirements-base.in aiohappyeyeballs==2.4.3 # via aiohttp -aiohttp==3.11.0 +aiohttp==3.11.9 # via -r requirements-base.in aiosignal==1.3.1 # via aiohttp @@ -31,7 +31,7 @@ attrs==22.1.0 # jsonschema backoff==2.2.1 # via schemathesis -bcrypt==4.2.0 +bcrypt==4.2.1 # via -r requirements-base.in blis==1.0.1 # via thinc @@ -118,7 +118,7 @@ email-validator==2.2.0 # via -r requirements-base.in emails==0.6 # via -r requirements-base.in -fastapi==0.115.5 +fastapi==0.115.6 # via -r requirements-base.in frozenlist==1.5.0 # via @@ -126,7 +126,7 @@ frozenlist==1.5.0 # aiosignal google-api-core==2.22.0 # via google-api-python-client -google-api-python-client==2.153.0 +google-api-python-client==2.154.0 # via -r requirements-base.in google-auth==2.36.0 # via @@ -154,7 +154,7 @@ httplib2==0.22.0 # google-api-python-client # google-auth-httplib2 # oauth2client -httpx==0.27.2 +httpx==0.28.0 # via # -r requirements-base.in # openai @@ -228,7 +228,7 @@ mdurl==0.1.2 # via markdown-it-py more-itertools==10.5.0 # via cssutils -msal==1.31.0 +msal==1.31.1 # via -r requirements-base.in multidict==6.1.0 # via @@ -256,7 +256,7 @@ oauthlib[signedtoken]==3.2.2 # atlassian-python-api # jira # requests-oauthlib -openai==1.54.4 +openai==1.56.1 # via -r requirements-base.in packaging==24.2 # via @@ -300,7 +300,7 @@ protobuf==4.23.4 # proto-plus psycopg2-binary==2.9.10 # via -r requirements-base.in -pyarrow==18.0.0 +pyarrow==18.1.0 # via -r requirements-base.in pyasn1==0.6.1 # via @@ -353,7 +353,7 @@ python-dateutil==2.9.0.post0 # pandas python-jose==3.3.0 # via -r requirements-base.in -python-multipart==0.0.17 +python-multipart==0.0.19 # via -r requirements-base.in python-slugify==8.0.4 # via -r requirements-base.in @@ -425,7 +425,7 @@ six==1.16.0 # validators slack-bolt==1.21.2 # via -r requirements-base.in -slack-sdk==3.33.3 +slack-sdk==3.33.4 # via # -r requirements-base.in # slack-bolt @@ -436,7 +436,6 @@ smart-open==7.0.5 sniffio==1.3.1 # via # anyio - # httpx # openai sortedcontainers==2.4.0 # via hypothesis @@ -510,7 +509,7 @@ urllib3==2.2.3 # pdpyras # requests # sentry-sdk -uvicorn==0.32.0 +uvicorn==0.32.1 # via -r requirements-base.in uvloop==0.21.0 # via -r requirements-base.in diff --git a/requirements-dev.txt b/requirements-dev.txt index d86c3804c929..51e50fd4f780 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -18,7 +18,7 @@ click==8.1.7 # via # -r requirements-dev.in # black -coverage==7.6.4 +coverage==7.6.8 # via -r requirements-dev.in decorator==5.1.1 # via ipython @@ -32,7 +32,7 @@ executing==2.1.0 # stack-data factory-boy==3.3.1 # via -r requirements-dev.in -faker==32.1.0 +faker==33.1.0 # via # -r requirements-dev.in # factory-boy @@ -42,7 +42,7 @@ identify==2.6.1 # via pre-commit iniconfig==2.0.0 # via pytest -ipython==8.29.0 +ipython==8.30.0 # via -r requirements-dev.in jedi==0.19.1 # via ipython @@ -86,7 +86,7 @@ python-dateutil==2.9.0.post0 # via faker pyyaml==6.0.2 # via pre-commit -ruff==0.7.3 +ruff==0.8.1 # via -r requirements-dev.in six==1.16.0 # via diff --git a/src/dispatch/case/flows.py b/src/dispatch/case/flows.py index 51dee63b5507..1ef8d1fcf6ca 100644 --- a/src/dispatch/case/flows.py +++ b/src/dispatch/case/flows.py @@ -10,6 +10,8 @@ from dispatch.conversation import flows as conversation_flows from dispatch.decorators import background_task from dispatch.document import flows as document_flows +from dispatch.email_templates import service as email_template_service +from dispatch.email_templates.enums import EmailTemplateTypes from dispatch.enums import DocumentResourceTypes, EventType, Visibility from dispatch.event import service as event_service from dispatch.group import flows as group_flows @@ -130,10 +132,19 @@ def case_add_or_reactivate_participant_flow( case, [participant.individual.email], db_session ) - # we send the welcome messages to the participant - send_case_welcome_participant_message( - participant_email=user_email, case=case, db_session=db_session - ) + # check to see if there is an override welcome message template + welcome_template = email_template_service.get_by_type( + db_session=db_session, + project_id=case.project_id, + email_template_type=EmailTemplateTypes.welcome, + ) + + send_case_welcome_participant_message( + participant_email=user_email, + case=case, + db_session=db_session, + welcome_template=welcome_template, + ) return participant @@ -1040,6 +1051,13 @@ def case_create_resources_flow( conversation_target=conversation_target, ) + # check to see if there is an override welcome message template + welcome_template = email_template_service.get_by_type( + db_session=db_session, + project_id=case.project_id, + email_template_type=EmailTemplateTypes.welcome, + ) + for user_email in set(individual_participants): send_participant_announcement_message( db_session=db_session, @@ -1047,6 +1065,13 @@ def case_create_resources_flow( subject=case, ) + send_case_welcome_participant_message( + participant_email=user_email, + case=case, + db_session=db_session, + welcome_template=welcome_template, + ) + event_service.log_case_event( db_session=db_session, source="Dispatch Core App", diff --git a/src/dispatch/case/messaging.py b/src/dispatch/case/messaging.py index d223a03d37b7..02143f19ff2e 100644 --- a/src/dispatch/case/messaging.py +++ b/src/dispatch/case/messaging.py @@ -7,9 +7,12 @@ import logging +from typing import Optional + from sqlalchemy.orm import Session from dispatch.database.core import resolve_attr +from dispatch.document import service as document_service from dispatch.case.models import Case, CaseRead from dispatch.messaging.strings import ( CASE_CLOSE_REMINDER, @@ -26,14 +29,13 @@ CASE_PRIORITY_CHANGE, CASE_CLOSED_RATING_FEEDBACK_NOTIFICATION, MessageType, + generate_welcome_message, ) from dispatch.config import DISPATCH_UI_URL +from dispatch.email_templates.models import EmailTemplates from dispatch.plugin import service as plugin_service from dispatch.event import service as event_service from dispatch.notification import service as notification_service -from dispatch.plugins.dispatch_slack.case.messages import ( - create_welcome_ephemeral_message_to_participant, -) from .enums import CaseStatus @@ -310,6 +312,7 @@ def send_case_welcome_participant_message( participant_email: str, case: Case, db_session: Session, + welcome_template: Optional[EmailTemplates] = None, ): if not case.dedicated_channel: return @@ -322,12 +325,52 @@ def send_case_welcome_participant_message( log.warning("Case participant welcome message not sent. No conversation plugin enabled.") return - welcome_message = create_welcome_ephemeral_message_to_participant(case=case) + # we send the ephemeral message + message_kwargs = { + "name": case.name, + "title": case.title, + "description": case.description, + "visibility": case.visibility, + "status": case.status, + "type": case.case_type.name, + "type_description": case.case_type.description, + "severity": case.case_severity.name, + "severity_description": case.case_severity.description, + "priority": case.case_priority.name, + "priority_description": case.case_priority.description, + "assignee_fullname": case.assignee.individual.name, + "assignee_team": case.assignee.team, + "assignee_weblink": case.assignee.individual.weblink, + "reporter_fullname": case.reporter.individual.name, + "reporter_team": case.reporter.team, + "reporter_weblink": case.reporter.individual.weblink, + "document_weblink": resolve_attr(case, "case_document.weblink"), + "storage_weblink": resolve_attr(case, "storage.weblink"), + "ticket_weblink": resolve_attr(case, "ticket.weblink"), + "conference_weblink": resolve_attr(case, "conference.weblink"), + "conference_challenge": resolve_attr(case, "conference.conference_challenge"), + } + faq_doc = document_service.get_incident_faq_document( + db_session=db_session, project_id=case.project_id + ) + if faq_doc: + message_kwargs.update({"faq_weblink": faq_doc.weblink}) + + conversation_reference = document_service.get_conversation_reference_document( + db_session=db_session, project_id=case.project_id + ) + if conversation_reference: + message_kwargs.update( + {"conversation_commands_reference_document_weblink": conversation_reference.weblink} + ) + plugin.instance.send_ephemeral( conversation_id=case.conversation.channel_id, user=participant_email, text=f"Welcome to {case.name}", - blocks=welcome_message, + message_template=generate_welcome_message(welcome_template, is_incident=False), + notification_type=MessageType.case_participant_welcome, + **message_kwargs, ) log.debug(f"Welcome ephemeral message sent to {participant_email}.") diff --git a/src/dispatch/conversation/flows.py b/src/dispatch/conversation/flows.py index 08eefe7dcd24..a9dd5689ef97 100644 --- a/src/dispatch/conversation/flows.py +++ b/src/dispatch/conversation/flows.py @@ -466,11 +466,14 @@ def add_case_participants( return try: - plugin.instance.add_to_thread( - case.conversation.channel_id, - case.conversation.thread_id, - participant_emails, - ) + if case.has_thread: + plugin.instance.add_to_thread( + case.conversation.channel_id, + case.conversation.thread_id, + participant_emails, + ) + elif case.has_channel: + plugin.instance.add(case.conversation.channel_id, participant_emails) except Exception as e: event_service.log_case_event( db_session=db_session, diff --git a/src/dispatch/database/revisions/tenant/versions/2024-12-05_575ca7d954a8.py b/src/dispatch/database/revisions/tenant/versions/2024-12-05_575ca7d954a8.py new file mode 100644 index 000000000000..8365c4d919cc --- /dev/null +++ b/src/dispatch/database/revisions/tenant/versions/2024-12-05_575ca7d954a8.py @@ -0,0 +1,29 @@ +"""Adds incident summary to the incident table. + +Revision ID: 575ca7d954a8 +Revises: 928b725d64f6 +Create Date: 2024-12-05 15:05:46.932404 + +""" + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = "575ca7d954a8" +down_revision = "928b725d64f6" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column("incident", sa.Column("summary", sa.String(), nullable=True)) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column("incident", "summary") + # ### end Alembic commands ### diff --git a/src/dispatch/incident/flows.py b/src/dispatch/incident/flows.py index f5bc36acf805..e07f5e2efec1 100644 --- a/src/dispatch/incident/flows.py +++ b/src/dispatch/incident/flows.py @@ -552,6 +552,9 @@ def incident_closed_status_flow(incident: Incident, db_session=None): # to rate and provide feedback about the incident send_incident_rating_feedback_message(incident, db_session) + # if an AI plugin is enabled, we send the incident review doc for summary + incident_service.generate_incident_summary(incident, db_session) + def conversation_topic_dispatcher( user_email: str, diff --git a/src/dispatch/incident/metrics.py b/src/dispatch/incident/metrics.py index b5a44b7ae39d..36bf4a9716ab 100644 --- a/src/dispatch/incident/metrics.py +++ b/src/dispatch/incident/metrics.py @@ -1,3 +1,4 @@ +import json import logging import math from calendar import monthrange @@ -31,12 +32,14 @@ def create_incident_metric_query( db_session, end_date: date, start_date: date = None, - filter_spec: List[dict] = None, + filter_spec: List[dict] | str | None = None, ): """Fetches eligible incidents.""" query = db_session.query(Incident) if filter_spec: + if isinstance(filter_spec, str): + filter_spec = json.loads(filter_spec) query = apply_filter_specific_joins(Incident, filter_spec, query) query = apply_filters(query, filter_spec) @@ -73,7 +76,7 @@ def make_forecast(incidents: List[Incident]): dataframe.drop("ds", inplace=True, axis=1) # fill periods without incidents with 0 - idx = pd.date_range(dataframe.index[0], dataframe.index[-1], freq="M") + idx = pd.date_range(dataframe.index[0], dataframe.index[-1], freq="ME") dataframe.index = pd.DatetimeIndex(dataframe.index) dataframe = dataframe.reindex(idx, fill_value=0) diff --git a/src/dispatch/incident/models.py b/src/dispatch/incident/models.py index 5d98f383768e..d5e727d6ecd6 100644 --- a/src/dispatch/incident/models.py +++ b/src/dispatch/incident/models.py @@ -220,6 +220,8 @@ def last_executive_report(self): notifications_group_id = Column(Integer, ForeignKey("group.id")) notifications_group = relationship("Group", foreign_keys=[notifications_group_id]) + summary = Column(String, nullable=True) + @hybrid_property def total_cost(self): total_cost = 0 @@ -323,6 +325,7 @@ class IncidentReadMinimal(IncidentBase): reporters_location: Optional[str] stable_at: Optional[datetime] = None storage: Optional[StorageRead] = None + summary: Optional[str] = None tags: Optional[List[TagRead]] = [] tasks: Optional[List[TaskReadMinimal]] = [] total_cost: Optional[float] @@ -344,6 +347,7 @@ class IncidentUpdate(IncidentBase): reported_at: Optional[datetime] = None reporter: Optional[ParticipantUpdate] stable_at: Optional[datetime] = None + summary: Optional[str] = None tags: Optional[List[TagRead]] = [] terms: Optional[List[TermRead]] = [] @@ -393,6 +397,7 @@ class IncidentRead(IncidentBase): reporters_location: Optional[str] stable_at: Optional[datetime] = None storage: Optional[StorageRead] = None + summary: Optional[str] = None tags: Optional[List[TagRead]] = [] tasks: Optional[List[TaskRead]] = [] terms: Optional[List[TermRead]] = [] diff --git a/src/dispatch/incident/scheduled.py b/src/dispatch/incident/scheduled.py index 301bd1448189..bb13356b3057 100644 --- a/src/dispatch/incident/scheduled.py +++ b/src/dispatch/incident/scheduled.py @@ -286,25 +286,13 @@ def incident_report_weekly(db_session: Session, project: Project): if incident.visibility == Visibility.restricted: continue try: - pir_doc = storage_plugin.instance.get( - file_id=incident.incident_review_document.resource_id, - mime_type="text/plain", - ) - prompt = f""" - Given the text of the security post-incident review document below, - provide answers to the following questions in a paragraph format. - Do not include the questions in your response. - 1. What is the summary of what happened? - 2. What were the overall risk(s)? - 3. How were the risk(s) mitigated? - 4. How was the incident resolved? - 5. What are the follow-up tasks? - - {pir_doc} - """ - - response = ai_plugin.instance.chat_completion(prompt=prompt) - summary = response["choices"][0]["message"]["content"] + # if already summary generated, use that instead + if incident.summary: + summary = incident.summary + else: + summary = incident_service.generate_incident_summary( + db_session=db_session, incident=incident + ) item = { "commander_fullname": incident.commander.individual.name, diff --git a/src/dispatch/incident/service.py b/src/dispatch/incident/service.py index b4ce96f0d88f..5aa2a3f0049f 100644 --- a/src/dispatch/incident/service.py +++ b/src/dispatch/incident/service.py @@ -11,9 +11,11 @@ from typing import List, Optional from pydantic.error_wrappers import ErrorWrapper, ValidationError +from sqlalchemy.orm import Session + from dispatch.decorators import timer from dispatch.case import service as case_service -from dispatch.database.core import SessionLocal +from dispatch.enums import Visibility from dispatch.event import service as event_service from dispatch.exceptions import NotFoundError from dispatch.incident.priority import service as incident_priority_service @@ -35,9 +37,7 @@ log = logging.getLogger(__name__) -def resolve_and_associate_role( - db_session: SessionLocal, incident: Incident, role: ParticipantRoleType -): +def resolve_and_associate_role(db_session: Session, incident: Incident, role: ParticipantRoleType): """For a given role type resolve which individual email should be assigned that role.""" email_address = None service_id = None @@ -65,12 +65,12 @@ def resolve_and_associate_role( @timer -def get(*, db_session, incident_id: int) -> Optional[Incident]: +def get(*, db_session: Session, incident_id: int) -> Optional[Incident]: """Returns an incident based on the given id.""" return db_session.query(Incident).filter(Incident.id == incident_id).first() -def get_by_name(*, db_session, project_id: int, name: str) -> Optional[Incident]: +def get_by_name(*, db_session: Session, project_id: int, name: str) -> Optional[Incident]: """Returns an incident based on the given name.""" return ( db_session.query(Incident) @@ -80,7 +80,9 @@ def get_by_name(*, db_session, project_id: int, name: str) -> Optional[Incident] ) -def get_all_open_by_incident_type(*, db_session, incident_type_id: int) -> List[Optional[Incident]]: +def get_all_open_by_incident_type( + *, db_session: Session, incident_type_id: int +) -> List[Optional[Incident]]: """Returns all non-closed incidents based on the given incident type.""" return ( db_session.query(Incident) @@ -90,7 +92,9 @@ def get_all_open_by_incident_type(*, db_session, incident_type_id: int) -> List[ ) -def get_by_name_or_raise(*, db_session, project_id: int, incident_in: IncidentRead) -> Incident: +def get_by_name_or_raise( + *, db_session: Session, project_id: int, incident_in: IncidentRead +) -> Incident: """Returns an incident based on a given name or raises ValidationError""" incident = get_by_name(db_session=db_session, project_id=project_id, name=incident_in.name) @@ -110,12 +114,14 @@ def get_by_name_or_raise(*, db_session, project_id: int, incident_in: IncidentRe return incident -def get_all(*, db_session, project_id: int) -> List[Optional[Incident]]: +def get_all(*, db_session: Session, project_id: int) -> List[Optional[Incident]]: """Returns all incidents.""" return db_session.query(Incident).filter(Incident.project_id == project_id) -def get_all_by_status(*, db_session, status: str, project_id: int) -> List[Optional[Incident]]: +def get_all_by_status( + *, db_session: Session, status: str, project_id: int +) -> List[Optional[Incident]]: """Returns all incidents based on the given status.""" return ( db_session.query(Incident) @@ -125,7 +131,7 @@ def get_all_by_status(*, db_session, status: str, project_id: int) -> List[Optio ) -def get_all_last_x_hours(*, db_session, hours: int) -> List[Optional[Incident]]: +def get_all_last_x_hours(*, db_session: Session, hours: int) -> List[Optional[Incident]]: """Returns all incidents in the last x hours.""" now = datetime.utcnow() return ( @@ -134,7 +140,7 @@ def get_all_last_x_hours(*, db_session, hours: int) -> List[Optional[Incident]]: def get_all_last_x_hours_by_status( - *, db_session, status: str, hours: int, project_id: int + *, db_session: Session, status: str, hours: int, project_id: int ) -> List[Optional[Incident]]: """Returns all incidents of a given status in the last x hours.""" now = datetime.utcnow() @@ -167,7 +173,7 @@ def get_all_last_x_hours_by_status( ) -def create(*, db_session, incident_in: IncidentCreate) -> Incident: +def create(*, db_session: Session, incident_in: IncidentCreate) -> Incident: """Creates a new incident.""" project = project_service.get_by_name_or_default( db_session=db_session, project_in=incident_in.project @@ -326,7 +332,7 @@ def create(*, db_session, incident_in: IncidentCreate) -> Incident: return incident -def update(*, db_session, incident: Incident, incident_in: IncidentUpdate) -> Incident: +def update(*, db_session: Session, incident: Incident, incident_in: IncidentUpdate) -> Incident: """Updates an existing incident.""" incident_type = incident_type_service.get_by_name_or_default( db_session=db_session, @@ -417,7 +423,72 @@ def update(*, db_session, incident: Incident, incident_in: IncidentUpdate) -> In return incident -def delete(*, db_session, incident_id: int): +def delete(*, db_session: Session, incident_id: int): """Deletes an existing incident.""" db_session.query(Incident).filter(Incident.id == incident_id).delete() db_session.commit() + + +def generate_incident_summary(*, db_session: Session, incident: Incident) -> str: + """Generates a summary of the incident.""" + # Skip summary for restricted incidents + if incident.visibility == Visibility.restricted: + return "Incident summary not generated for restricted incident." + + # Skip if no incident review document + if not incident.incident_review_document or not incident.incident_review_document.resource_id: + log.info( + f"Incident summary not generated for incident {incident.id}. No review document found." + ) + return "Incident summary not generated. No review document found." + + # Don't generate if no enabled ai plugin or storage plugin + ai_plugin = plugin_service.get_active_instance( + db_session=db_session, plugin_type="artificial-intelligence", project_id=incident.project.id + ) + if not ai_plugin: + log.info( + f"Incident summary not generated for incident {incident.id}. No AI plugin enabled." + ) + return "Incident summary not generated. No AI plugin enabled." + + storage_plugin = plugin_service.get_active_instance( + db_session=db_session, plugin_type="storage", project_id=incident.project.id + ) + + if not storage_plugin: + log.info( + f"Incident summary not generated for incident {incident.id}. No storage plugin enabled." + ) + return "Incident summary not generated. No storage plugin enabled." + + try: + pir_doc = storage_plugin.instance.get( + file_id=incident.incident_review_document.resource_id, + mime_type="text/plain", + ) + prompt = f""" + Given the text of the security post-incident review document below, + provide answers to the following questions in a paragraph format. + Do not include the questions in your response. + 1. What is the summary of what happened? + 2. What were the overall risk(s)? + 3. How were the risk(s) mitigated? + 4. How was the incident resolved? + 5. What are the follow-up tasks? + + {pir_doc} + """ + + response = ai_plugin.instance.chat_completion(prompt=prompt) + summary = response["choices"][0]["message"]["content"] + + incident.summary = summary + db_session.add(incident) + db_session.commit() + + return summary + + except Exception as e: + log.exception(f"Error trying to generate summary for incident {incident.id}: {e}") + return "Incident summary not generated. An error occurred." diff --git a/src/dispatch/incident/views.py b/src/dispatch/incident/views.py index cdeaf24bbadc..8bab7317001c 100644 --- a/src/dispatch/incident/views.py +++ b/src/dispatch/incident/views.py @@ -47,7 +47,7 @@ IncidentRead, IncidentUpdate, ) -from .service import create, delete, get, update +from .service import create, delete, get, update, generate_incident_summary log = logging.getLogger(__name__) @@ -497,3 +497,18 @@ def get_incident_forecast( {"name": "Actual", "data": actual[1:]}, ], } + + +@router.get( + "/{incident_id}/regenerate", + summary="Regenerates incident sumamary", + dependencies=[Depends(PermissionsDependency([IncidentEventPermission]))], +) +def generate_summary( + db_session: DbSession, + current_incident: CurrentIncident, +): + return generate_incident_summary( + db_session=db_session, + incident=current_incident, + ) diff --git a/src/dispatch/messaging/email/utils.py b/src/dispatch/messaging/email/utils.py index 19aed7dfebaa..81f2a96dbdfe 100644 --- a/src/dispatch/messaging/email/utils.py +++ b/src/dispatch/messaging/email/utils.py @@ -31,6 +31,7 @@ def get_template(message_type: MessageType, project_id: int): MessageType.case_notification: ("notification.mjml", None), MessageType.incident_participant_welcome: ("notification.mjml", None), MessageType.incident_tactical_report: ("tactical_report.mjml", None), + MessageType.case_participant_welcome: ("notification.mjml", None), MessageType.incident_task_reminder: ( "notification_list.mjml", INCIDENT_TASK_REMINDER_DESCRIPTION, diff --git a/src/dispatch/messaging/strings.py b/src/dispatch/messaging/strings.py index b98cfc7e26a9..e8240612a6ef 100644 --- a/src/dispatch/messaging/strings.py +++ b/src/dispatch/messaging/strings.py @@ -44,6 +44,7 @@ class MessageType(DispatchEnum): task_add_to_incident = "task-add-to-incident" case_rating_feedback = "case-rating-feedback" case_feedback_daily_report = "case-feedback-daily-report" + case_participant_welcome = "case-participant-welcome" INCIDENT_STATUS_DESCRIPTIONS = { @@ -99,7 +100,7 @@ class MessageType(DispatchEnum): ).strip() INCIDENT_WEEKLY_REPORT_NO_INCIDENTS_DESCRIPTION = """ -No open incidents have been closed in the last week.""".replace( +No open visibility incidents have been closed in the last week.""".replace( "\n", " " ).strip() @@ -118,6 +119,7 @@ class MessageType(DispatchEnum): "\n", " " ).strip() + INCIDENT_REPORTER_DESCRIPTION = """ The person who reported the incident. Contact them if the report details need clarification.""".replace( "\n", " " @@ -157,12 +159,24 @@ class MessageType(DispatchEnum): "\n", " " ).strip() +CASE_CONVERSATION_REFERENCE_DOCUMENT_DESCRIPTION = """ +Document containing the list of slash commands available to the Assignee +and participants in the case conversation.""".replace( + "\n", " " +).strip() + INCIDENT_CONVERSATION_REFERENCE_DOCUMENT_DESCRIPTION = """ Document containing the list of slash commands available to the Incident Commander (IC) and participants in the incident conversation.""".replace( "\n", " " ).strip() +CASE_CONFERENCE_DESCRIPTION = """ +Video conference and phone bridge to be used throughout the case. Password: {{conference_challenge if conference_challenge else 'N/A'}} +""".replace( + "\n", "" +).strip() + INCIDENT_CONFERENCE_DESCRIPTION = """ Video conference and phone bridge to be used throughout the incident. Password: {{conference_challenge if conference_challenge else 'N/A'}} """.replace( @@ -197,6 +211,13 @@ class MessageType(DispatchEnum): "\n", " " ).strip() +CASE_FAQ_DOCUMENT_DESCRIPTION = """ +First time responding to a case? This +document answers common questions encountered when +helping us respond to a case.""".replace( + "\n", " " +).strip() + INCIDENT_FAQ_DOCUMENT_DESCRIPTION = """ First time responding to an incident? This document answers common questions encountered when @@ -246,6 +267,13 @@ class MessageType(DispatchEnum): "\n", " " ).strip() +CASE_PARTICIPANT_WELCOME_DESCRIPTION = """ +You\'ve been added to this case, because we think you may +be able to help resolve it. Please review the case details below and +reach out to the assignee if you have any questions.""".replace( + "\n", " " +).strip() + INCIDENT_PARTICIPANT_WELCOME_DESCRIPTION = """ You\'ve been added to this incident, because we think you may be able to help resolve it. Please review the incident details below and @@ -804,6 +832,24 @@ class MessageType(DispatchEnum): INCIDENT_STATUS, ] +CASE_DESCRIPTION = {"title": "Description", "text": "{{description}}"} + +CASE_VISIBILITY = { + "title": "Visibility - {{visibility}}", + "visibility_mapping": CASE_VISIBILITY_DESCRIPTIONS, +} + +CASE_TYPE = {"title": "Type - {{type}}", "text": "{{type_description}}"} + +CASE_SEVERITY = { + "title": "Severity - {{severity}}", + "text": "{{severity_description}}", +} + +CASE_PRIORITY = { + "title": "Priority - {{priority}}", + "text": "{{priority_description}}", +} CASE_CLOSE_REMINDER = [ { @@ -849,6 +895,43 @@ class MessageType(DispatchEnum): "text": CASE_ASSIGNEE_DESCRIPTION, } +CASE_CONFERENCE = { + "title": "Conference", + "title_link": "{{conference_weblink}}", + "text": CASE_CONFERENCE_DESCRIPTION, +} + +CASE_STORAGE = { + "title": "Storage", + "title_link": "{{storage_weblink}}", + "text": STORAGE_DESCRIPTION, +} + +CASE_CONVERSATION_COMMANDS_REFERENCE_DOCUMENT = { + "title": "Incident Conversation Commands Reference Document", + "title_link": "{{conversation_commands_reference_document_weblink}}", + "text": CASE_CONVERSATION_REFERENCE_DOCUMENT_DESCRIPTION, +} + +CASE_INVESTIGATION_DOCUMENT = { + "title": "Investigation Document", + "title_link": "{{document_weblink}}", + "text": CASE_INVESTIGATION_DOCUMENT_DESCRIPTION, +} + + +CASE_FAQ_DOCUMENT = { + "title": "FAQ Document", + "title_link": "{{faq_weblink}}", + "text": CASE_FAQ_DOCUMENT_DESCRIPTION, +} + +CASE_PARTICIPANT_WELCOME = { + "title": "Welcome to {{name}}", + "title_link": "{{ticket_weblink}}", + "text": CASE_PARTICIPANT_WELCOME_DESCRIPTION, +} + CASE_NOTIFICATION_COMMON = [CASE_TITLE] CASE_NOTIFICATION = CASE_NOTIFICATION_COMMON.copy() @@ -864,6 +947,24 @@ class MessageType(DispatchEnum): ] ) +CASE_PARTICIPANT_WELCOME_MESSAGE = [ + CASE_PARTICIPANT_WELCOME, + CASE_TITLE, + CASE_DESCRIPTION, + CASE_VISIBILITY, + CASE_STATUS, + CASE_TYPE, + CASE_SEVERITY, + CASE_PRIORITY, + CASE_REPORTER, + CASE_ASSIGNEE, + CASE_INVESTIGATION_DOCUMENT, + CASE_STORAGE, + CASE_CONFERENCE, + CASE_CONVERSATION_COMMANDS_REFERENCE_DOCUMENT, + CASE_FAQ_DOCUMENT, +] + INCIDENT_TASK_REMINDER = [ {"title": "Incident - {{ name }}", "text": "{{ title }}"}, @@ -1198,10 +1299,15 @@ def render_message_template(message_template: List[dict], **kwargs): return data -def generate_welcome_message(welcome_message: EmailTemplates) -> Optional[List[dict]]: +def generate_welcome_message( + welcome_message: EmailTemplates, is_incident: bool = True +) -> Optional[List[dict]]: """Generates the welcome message.""" if welcome_message is None: - return INCIDENT_PARTICIPANT_WELCOME_MESSAGE + if is_incident: + return INCIDENT_PARTICIPANT_WELCOME_MESSAGE + else: + return CASE_PARTICIPANT_WELCOME_MESSAGE participant_welcome = { "title": welcome_message.welcome_text, @@ -1210,20 +1316,26 @@ def generate_welcome_message(welcome_message: EmailTemplates) -> Optional[List[d } component_mapping = { - "Title": INCIDENT_TITLE, - "Description": INCIDENT_DESCRIPTION, - "Visibility": INCIDENT_VISIBILITY, - "Status": INCIDENT_STATUS, - "Type": INCIDENT_TYPE, - "Severity": INCIDENT_SEVERITY, - "Priority": INCIDENT_PRIORITY, - "Reporter": INCIDENT_REPORTER, - "Commander": INCIDENT_COMMANDER, - "Investigation Document": INCIDENT_INVESTIGATION_DOCUMENT, - "Storage": INCIDENT_STORAGE, - "Conference": INCIDENT_CONFERENCE, - "Slack Commands": INCIDENT_CONVERSATION_COMMANDS_REFERENCE_DOCUMENT, - "FAQ Document": INCIDENT_FAQ_DOCUMENT, + "Title": INCIDENT_TITLE if is_incident else CASE_TITLE, + "Description": INCIDENT_DESCRIPTION if is_incident else CASE_DESCRIPTION, + "Visibility": INCIDENT_VISIBILITY if is_incident else CASE_VISIBILITY, + "Status": INCIDENT_STATUS if is_incident else CASE_STATUS, + "Type": INCIDENT_TYPE if is_incident else CASE_TYPE, + "Severity": INCIDENT_SEVERITY if is_incident else CASE_SEVERITY, + "Priority": INCIDENT_PRIORITY if is_incident else CASE_PRIORITY, + "Reporter": INCIDENT_REPORTER if is_incident else CASE_REPORTER, + "Commander": INCIDENT_COMMANDER if is_incident else CASE_ASSIGNEE, + "Investigation Document": ( + INCIDENT_INVESTIGATION_DOCUMENT if is_incident else CASE_INVESTIGATION_DOCUMENT + ), + "Storage": INCIDENT_STORAGE if is_incident else CASE_STORAGE, + "Conference": INCIDENT_CONFERENCE if is_incident else CASE_CONFERENCE, + "Slack Commands": ( + INCIDENT_CONVERSATION_COMMANDS_REFERENCE_DOCUMENT + if is_incident + else CASE_CONVERSATION_COMMANDS_REFERENCE_DOCUMENT + ), + "FAQ Document": INCIDENT_FAQ_DOCUMENT if is_incident else CASE_FAQ_DOCUMENT, } message = [participant_welcome] diff --git a/src/dispatch/plugins/dispatch_google/groups/plugin.py b/src/dispatch/plugins/dispatch_google/groups/plugin.py index 2d42c51d4ce3..74c502885e68 100644 --- a/src/dispatch/plugins/dispatch_google/groups/plugin.py +++ b/src/dispatch/plugins/dispatch_google/groups/plugin.py @@ -141,7 +141,8 @@ def create( ): """Creates a new Google Group.""" client = get_service(self.configuration, "admin", "directory_v1", self.scopes) - group_key = f"{name.lower()}@{self.configuration.google_domain}" + # note: group username is limited to 60 characters + group_key = f"{name.lower()[:60]}@{self.configuration.google_domain}" if not description: description = "Group automatically created by Dispatch." @@ -170,11 +171,16 @@ def remove(self, email: str, participants: List[str]): for p in participants: remove_member(client, email, p) - def list(self, email: str): + def list(self, email: str) -> list[str]: """Lists members from an existing Google Group.""" client = get_service(self.configuration, "admin", "directory_v1", self.scopes) - members = list_members(client, email) - return [m["email"] for m in members.get("members", [])] + try: + members = list_members(client, email) + return [m["email"] for m in members.get("members", [])] + except HttpError as e: + if e.resp.status == 404: + log.warning(f"Group does not exist. GroupKey={email} Trying to list members.") + return [] def delete(self, email: str): """Deletes an existing Google group.""" diff --git a/src/dispatch/plugins/dispatch_slack/case/enums.py b/src/dispatch/plugins/dispatch_slack/case/enums.py index 5f27b195078f..ee88ee22e310 100644 --- a/src/dispatch/plugins/dispatch_slack/case/enums.py +++ b/src/dispatch/plugins/dispatch_slack/case/enums.py @@ -12,6 +12,8 @@ class CaseNotificationActions(DispatchEnum): triage = "case-notification-triage" user_mfa = "case-notification-user-mfa" invite_user_case = ConversationButtonActions.invite_user_case + do_nothing = "case-do-not-add-user" + add_user = "case-add-user" class CasePaginateActions(DispatchEnum): diff --git a/src/dispatch/plugins/dispatch_slack/case/interactive.py b/src/dispatch/plugins/dispatch_slack/case/interactive.py index 0e87c13a0fce..5343a4b51b8c 100644 --- a/src/dispatch/plugins/dispatch_slack/case/interactive.py +++ b/src/dispatch/plugins/dispatch_slack/case/interactive.py @@ -1,5 +1,6 @@ import json import logging +import re from datetime import datetime, timedelta, timezone from functools import partial from uuid import UUID @@ -12,6 +13,7 @@ Divider, Input, MarkdownText, + Message, Modal, Section, UsersSelect, @@ -34,6 +36,7 @@ from dispatch.event import service as event_service from dispatch.exceptions import ExistsError from dispatch.individual.models import IndividualContactRead +from dispatch.participant import flows as participant_flows from dispatch.participant import service as participant_service from dispatch.participant.models import ParticipantUpdate from dispatch.participant_role import service as participant_role_service @@ -72,7 +75,6 @@ entity_select, incident_priority_select, incident_type_select, - participant_select, project_select, relative_date_picker_input, resolution_input, @@ -80,6 +82,7 @@ ) from dispatch.plugins.dispatch_slack.middleware import ( action_context_middleware, + add_user_middleware, button_context_middleware, command_context_middleware, configuration_middleware, @@ -92,6 +95,7 @@ ) from dispatch.plugins.dispatch_slack.modals.common import send_success_modal from dispatch.plugins.dispatch_slack.models import ( + AddUserMetadata, CaseSubjects, FormData, FormMetadata, @@ -353,7 +357,6 @@ def handle_engage_user_command( """Handles engage user command.""" ack() - case = case_service.get(db_session=db_session, case_id=context["subject"].id) default_engagement = "We'd like to verify your identity. Can you please confirm this is you?" blocks = [ @@ -364,7 +367,7 @@ def handle_engage_user_command( ) ] ), - participant_select(label="Person to engage", participants=case.participants), + assignee_select(label="Person to engage", placeholder="Select user"), description_input(label="Engagement text", initial_value=default_engagement), ] @@ -402,14 +405,26 @@ def engage( """Handles the engage user action.""" ack() - if form_data.get(DefaultBlockIds.participant_select): - participant_id = form_data[DefaultBlockIds.participant_select]["value"] - participant = participant_service.get(db_session=db_session, participant_id=participant_id) - if participant: - user_email = participant.individual.email - else: - log.error(f"Participant not found for id {participant_id} when trying to engage user") - return + case = case_service.get(db_session=db_session, case_id=context["subject"].id) + if not case: + log.error("Case not found when trying to engage user") + return + + if form_data.get(DefaultBlockIds.case_assignee_select): + user_email = client.users_info( + user=form_data[DefaultBlockIds.case_assignee_select]["value"] + )["user"]["profile"]["email"] + conversation_flows.add_case_participants(case, [user_email], db_session) + participant = participant_service.get_by_case_id_and_email( + db_session=db_session, case_id=case.id, email=user_email + ) + if not participant: + participant_flows.add_participant( + user_email, + case, + db_session, + roles=[ParticipantRoleType.participant], + ) else: return @@ -419,8 +434,6 @@ def engage( log.warning("Engagement text not found") return - case = case_service.get(db_session=db_session, case_id=context["subject"].id) - user = client.users_lookupByEmail(email=user_email) result = client.chat_postMessage( @@ -1643,6 +1656,141 @@ def handle_create_channel_event( ) +def extract_mentioned_users(text: str) -> list[str]: + """Extracts mentioned users from a message.""" + return re.findall(r"<@(\w+)>", text) + + +def format_emails(emails: list[str]) -> str: + """Format a list of names into a string with commas and 'and' before the last name.""" + usernames = [email.split("@")[0] for email in emails] + + if not usernames: + return "" + elif len(usernames) == 1: + return f"@{usernames[0]}" + elif len(usernames) == 2: + return f"@{usernames[0]} and @{usernames[1]}" + else: + return ", ".join(f"@{username}" for username in usernames[:-1]) + f", and @{usernames[-1]}" + + +@message_dispatcher.add( + subject=CaseSubjects.case, exclude={"subtype": ["channel_join", "group_join"]} +) # we ignore user channel and group join messages +def handle_user_mention( + ack: Ack, + context: BoltContext, + client: WebClient, + db_session: Session, + payload: dict, +) -> None: + """Handles user posted message events.""" + ack() + + case = case_service.get(db_session=db_session, case_id=context["subject"].id) + if not case or case.dedicated_channel: + # we do not need to handle mentions for cases with dedicated channels + return + + mentioned_users = extract_mentioned_users(payload["text"]) + users_not_in_case = [] + for user_id in mentioned_users: + user_email = dispatch_slack_service.get_user_email(client, user_id) + if not participant_service.get_by_case_id_and_email( + db_session=db_session, case_id=context["subject"].id, email=user_email + ): + users_not_in_case.append(user_email) + + if users_not_in_case: + # send a private message to the user who posted the message to see + # if they want to add the mentioned user(s) to the case + button_metadata = AddUserMetadata( + **dict(context["subject"]), + users=users_not_in_case, + ).json() + blocks = [ + Section( + text=f"You mentioned {format_emails(users_not_in_case)}, but they're not in this case." + ), + Actions( + block_id=DefaultBlockIds.add_user_actions, + elements=[ + Button( + text="Add Them", + style="primary", + action_id=CaseNotificationActions.add_user, + value=button_metadata, + ), + Button( + text="Do Nothing", + action_id=CaseNotificationActions.do_nothing, + ), + ], + ), + ] + blocks = Message(blocks=blocks).build()["blocks"] + client.chat_postEphemeral( + channel=payload["channel"], + thread_ts=payload.get("thread_ts"), + user=payload["user"], + blocks=blocks, + ) + + +@app.action( + CaseNotificationActions.add_user, + middleware=[add_user_middleware, button_context_middleware, db_middleware, user_middleware], +) +def add_users_to_case( + ack: Ack, + db_session: Session, + context: BoltContext, + respond: Respond, +): + ack() + + case_id = context["subject"].id + + case = case_service.get(db_session=db_session, case_id=case_id) + if not case: + log.error(f"Could not find case with id: {case_id}") + return + + users = context["users"] + if users: + for user_email in users: + conversation_flows.add_case_participants(case, [user_email], db_session) + participant = participant_service.get_by_case_id_and_email( + db_session=db_session, case_id=case.id, email=user_email + ) + if not participant: + participant_flows.add_participant( + user_email, + case, + db_session, + roles=[ParticipantRoleType.participant], + ) + + # Delete the ephemeral message + respond(delete_original=True) + + +@app.action(CaseNotificationActions.do_nothing) +def handle_do_nothing_button( + ack: Ack, + respond: Respond, +): + # Acknowledge the action + ack() + + try: + # Delete the ephemeral message + respond(delete_original=True) + except SlackApiError as e: + log.error(f"Error deleting ephemeral message: {e.response['error']}") + + @app.action( CaseNotificationActions.join_incident, middleware=[button_context_middleware, db_middleware, user_middleware], @@ -1906,12 +2054,12 @@ def handle_resolve_submission_event( case_id=updated_case.id, previous_case=previous_case, db_session=db_session, - reporter_email=updated_case.reporter.individual.email - if updated_case.reporter - else None, - assignee_email=updated_case.assignee.individual.email - if updated_case.assignee - else None, + reporter_email=( + updated_case.reporter.individual.email if updated_case.reporter else None + ), + assignee_email=( + updated_case.assignee.individual.email if updated_case.assignee else None + ), organization_slug=context["subject"].organization_slug, ) except Exception as e: @@ -2363,28 +2511,49 @@ def ack_mfa_required_submission_event( ) -> None: """Handles the add engagement submission event acknowledgement.""" + blocks = [] + if mfa_enabled: - mfa_text = ( - "🔐 To complete this action, you need to verify your identity through Multi-Factor Authentication (MFA).\n\n" - f"Please <{challenge_url}|*click here*> to open the MFA verification page." + blocks.extend( + [ + Section( + text="To complete this action, you need to verify your identity through Multi-Factor Authentication (MFA).\n\n" + "Please click the verify button to open the MFA verification page." + ), + Actions( + elements=[ + Button( + text="🔐 Verify", + action_id="button-link", + style="primary", + url=challenge_url, + ) + ] + ), + ] ) else: - mfa_text = "✅ No additional verification required. You can proceed with the confirmation." + blocks.append( + Section( + text="✅ No additional verification required. You can proceed with the confirmation." + ) + ) - blocks = [ - Section(text=mfa_text), - Divider(), - Context( - elements=[ - MarkdownText( - text="💡 This step protects against unauthorized confirmation if your account is compromised." - ) - ] - ), - ] + blocks.extend( + [ + Divider(), + Context( + elements=[ + MarkdownText( + text="💡 This step protects against unauthorized confirmation if your account is compromised." + ) + ] + ), + ] + ) modal = Modal( - title="Confirm Your Identity", + title="Verify Your Identity", close="Cancel", blocks=blocks, ).build() @@ -2423,6 +2592,9 @@ def handle_engagement_submission_event( mfa_plugin = plugin_service.get_active_instance( db_session=db_session, project_id=context["subject"].project_id, plugin_type="auth-mfa" ) + if not mfa_plugin: + log.error("Unable to engage user. No enabled MFA plugin found.") + return require_mfa = engagement.require_mfa if engagement else True mfa_enabled = True if mfa_plugin and require_mfa else False @@ -2532,14 +2704,23 @@ def send_engagement_response( if response == MfaChallengeStatus.APPROVED: # We only update engagement message (which removes Confirm/Deny button) for success # this allows the user to retry the confirmation if the MFA check failed - blocks = create_signal_engagement_message( - case=case, - channel_id=case.conversation.channel_id, - engagement=engagement, - signal_instance=signal_instance, - user_email=engaged_user, - engagement_status=engagement_status, - ) + if not engagement: + # assume the message is from a manual MFA challenge + blocks = create_manual_engagement_message( + case=case, + channel_id=case.conversation.channel_id, + user_email=engaged_user, + engagement_status=engagement_status, + ) + else: + blocks = create_signal_engagement_message( + case=case, + channel_id=case.conversation.channel_id, + engagement=engagement, + signal_instance=signal_instance, + user_email=engaged_user, + engagement_status=engagement_status, + ) if signal_instance: client.chat_update( blocks=blocks, diff --git a/src/dispatch/plugins/dispatch_slack/case/messages.py b/src/dispatch/plugins/dispatch_slack/case/messages.py index 554b0f717068..963b0f3f6cc6 100644 --- a/src/dispatch/plugins/dispatch_slack/case/messages.py +++ b/src/dispatch/plugins/dispatch_slack/case/messages.py @@ -18,7 +18,6 @@ from dispatch.case.enums import CaseStatus from dispatch.case.models import Case from dispatch.config import DISPATCH_UI_URL -from dispatch.messaging.strings import CASE_STATUS_DESCRIPTIONS, CASE_VISIBILITY_DESCRIPTIONS from dispatch.plugins.dispatch_slack.case.enums import ( CaseNotificationActions, SignalEngagementActions, @@ -257,6 +256,13 @@ def create_action_buttons_message( project_id=project_id, channel_id=channel_id, ).json() + mfa_button_metadata = SubjectMetadata( + type=CaseSubjects.case, + organization_slug=organization_slug, + id=case.id, + project_id=project_id, + channel_id=channel_id, + ).json() # we create the response plan and the snooze buttons elements = [] @@ -280,7 +286,7 @@ def create_action_buttons_message( Button( text="👤 User MFA Challenge", action_id=CaseNotificationActions.user_mfa, - value=button_metadata, + value=mfa_button_metadata, ), ] ) @@ -448,21 +454,20 @@ def create_signal_engagement_message( def create_manual_engagement_message( case: Case, channel_id: str, - engagement: str, user_email: str, - user_id: str, engagement_status: SignalEngagementStatus = SignalEngagementStatus.new, + user_id: str = "", + engagement: str = "", thread_ts: str = None, ) -> list[Block]: """ - Generate a list of blocks for a signal engagement message. + Generate a list of blocks for a manual engagement message. Args: - case (Case): The case object related to the signal instance. + case (Case): The case object related to the engagement. channel_id (str): The ID of the Slack channel where the message will be sent. - message (str): Additional context information to include in the message. + engagement_message (str): The engagement text. user_email (str): The email of the user being engaged. - engagement (str): The engagement text. Returns: list[Block]: A list of blocks representing the message structure for the engagement message. @@ -480,11 +485,12 @@ def create_manual_engagement_message( ).json() username, _ = user_email.split("@") - blocks = [ - Section( - text=f"<@{user_id}>: {engagement if engagement else 'No context provided for this alert.'}" - ), - ] + if engagement: + blocks = [ + Section(text=f"<@{user_id}>: {engagement}"), + ] + else: + blocks = [] if engagement_status == SignalEngagementStatus.new: blocks.extend( @@ -526,42 +532,6 @@ def create_manual_engagement_message( return Message(blocks=blocks).build()["blocks"] -def create_welcome_ephemeral_message_to_participant(case: Case) -> list[Block]: - blocks = [ - Section( - text="You've been added to this case, because we think you may be able to help resolve it. Please, review the case details below and reach out to the case assignee if you have any questions.", - ), - Section( - text=f"*Title* \n {case.title}", - ), - Section( - text=f"*Description* \n {case.description}", - ), - Section( - text=f"*Visibility - {case.visibility}* \n {CASE_VISIBILITY_DESCRIPTIONS[case.visibility]}", - ), - Section( - text=f"*Status - {case.status}* \n {CASE_STATUS_DESCRIPTIONS[case.status]}", - ), - Section( - text=f"*Type - {case.case_type.name}* \n {case.case_type.description}", - ), - Section( - text=f"*Severity - {case.case_severity.name}* \n {case.case_severity.description}", - ), - Section( - text=f"*Priority - {case.case_priority.name}* \n {case.case_priority.description}", - ), - Section( - text=f"*Assignee - {case.assignee.individual.name}*", - ), - Section( - text=f"*Reporter - {case.reporter.individual.name}*", - ), - ] - return Message(blocks=blocks).build()["blocks"] - - def create_case_thread_migration_message(channel_weblink: str) -> list[Block]: blocks = [ Context( diff --git a/src/dispatch/plugins/dispatch_slack/events.py b/src/dispatch/plugins/dispatch_slack/events.py index 81c4d63e8cf8..18a943af46c3 100644 --- a/src/dispatch/plugins/dispatch_slack/events.py +++ b/src/dispatch/plugins/dispatch_slack/events.py @@ -31,6 +31,10 @@ def fetch_activity(client: WebClient, subject: None, oldest: str = "0") -> List: log.warning("No conversation provided. Cannot fetch channel activity.") elif not subject.conversation.channel_id: log.warning("No channel id provided. Cannot fetch channel activity.") + elif subject.conversation.thread_id: + log.warning( + "Subject is a thread, not a channel. Fetching channel activity is not applicable for threads." + ) else: return get_channel_activity( client, conversation_id=subject.conversation.channel_id, oldest=oldest diff --git a/src/dispatch/plugins/dispatch_slack/fields.py b/src/dispatch/plugins/dispatch_slack/fields.py index 58de4eeadd6c..2c4541db4c12 100644 --- a/src/dispatch/plugins/dispatch_slack/fields.py +++ b/src/dispatch/plugins/dispatch_slack/fields.py @@ -29,6 +29,7 @@ class DefaultBlockIds(DispatchEnum): + add_user_actions = "add-user-actions" date_picker_input = "date-picker-input" description_input = "description-input" hour_picker_input = "hour-picker-input" diff --git a/src/dispatch/plugins/dispatch_slack/incident/interactive.py b/src/dispatch/plugins/dispatch_slack/incident/interactive.py index 92a4651a7b05..a27253e04600 100644 --- a/src/dispatch/plugins/dispatch_slack/incident/interactive.py +++ b/src/dispatch/plugins/dispatch_slack/incident/interactive.py @@ -249,6 +249,11 @@ def handle_tag_search_action( } if "/" in query_str: + # first check to make sure there's only one slash + if query_str.count("/") > 1: + ack() + return + tag_type, query_str = query_str.split("/") filter_spec["and"].append( {"model": "TagType", "op": "==", "field": "name", "value": tag_type} diff --git a/src/dispatch/plugins/dispatch_slack/middleware.py b/src/dispatch/plugins/dispatch_slack/middleware.py index 94e65f21f5a8..f391930989df 100644 --- a/src/dispatch/plugins/dispatch_slack/middleware.py +++ b/src/dispatch/plugins/dispatch_slack/middleware.py @@ -374,6 +374,14 @@ def command_context_middleware( next() +def add_user_middleware(payload: dict, context: BoltContext, next: Callable): + """Attempts to determine the user to add to the incident.""" + value = payload.get("value") + if value: + context["users"] = json.loads(value).get("users") + next() + + def db_middleware(context: BoltContext, next: Callable): if not context.get("subject"): slug = get_default_org_slug() diff --git a/src/dispatch/plugins/dispatch_slack/models.py b/src/dispatch/plugins/dispatch_slack/models.py index 3b1b3c853f0f..c982305cd2ff 100644 --- a/src/dispatch/plugins/dispatch_slack/models.py +++ b/src/dispatch/plugins/dispatch_slack/models.py @@ -50,6 +50,10 @@ class SubjectMetadata(BaseModel): thread_id: Optional[str] +class AddUserMetadata(SubjectMetadata): + users: list[str] + + class EngagementMetadata(SubjectMetadata): signal_instance_id: str engagement_id: int diff --git a/src/dispatch/plugins/dispatch_slack/service.py b/src/dispatch/plugins/dispatch_slack/service.py index 69557994e4f8..15437b630c06 100644 --- a/src/dispatch/plugins/dispatch_slack/service.py +++ b/src/dispatch/plugins/dispatch_slack/service.py @@ -441,6 +441,8 @@ def add_users_to_conversation(client: WebClient, conversation_id: str, user_ids: # that result in folks already existing in the channel. if e.response["error"] == SlackAPIErrorCode.USER_IN_CHANNEL: pass + elif e.response["error"] == SlackAPIErrorCode.ALREADY_IN_CHANNEL: + pass def get_message_permalink(client: WebClient, conversation_id: str, ts: str) -> str: diff --git a/src/dispatch/static/dispatch/package-lock.json b/src/dispatch/static/dispatch/package-lock.json index facc2bc652e5..1e47aa3001a4 100644 --- a/src/dispatch/static/dispatch/package-lock.json +++ b/src/dispatch/static/dispatch/package-lock.json @@ -32,6 +32,7 @@ "@vue-flow/minimap": "^1.2.0", "@vueuse/core": "^10.5.0", "@vueuse/integrations": "^10.6.1", + "@wdns/vuetify-resize-drawer": "^3.2.0", "apexcharts": "^3.44.0", "axios": "^0.21.4", "d3-force": "^3.0.0", @@ -49,6 +50,7 @@ "monaco-editor": "0.43.0", "register-service-worker": "^1.7.2", "roboto-fontface": "^0.10.0", + "sass-embedded": "^1.81.0", "sortablejs": "^1.15.0", "swrv": "^1.0.4", "vue": "^3.4.12", @@ -56,7 +58,6 @@ "vue3-apexcharts": "^1.4.4", "vue3-markdown-it": "^1.0.10", "vuetify": "^3.4.3", - "vuetify3-resize-drawer": "^2.1.1", "vuex": "^4.1.0", "vuex-map-fields": "^1.4.1" }, @@ -162,6 +163,11 @@ "node": ">=6.9.0" } }, + "node_modules/@bufbuild/protobuf": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.2.2.tgz", + "integrity": "sha512-UNtPCbrwrenpmrXuRwn9jYpPoweNXj8X5sMvYgsqYyaH8jQ6LfUJSk3dJLnBK+6sfYPrF4iAIo5sd5HQ+tg75A==" + }, "node_modules/@date-io/core": { "version": "2.17.0", "resolved": "https://registry.npmjs.org/@date-io/core/-/core-2.17.0.tgz", @@ -624,20 +630,20 @@ } }, "node_modules/@formkit/core": { - "version": "1.6.8", - "resolved": "https://registry.npmjs.org/@formkit/core/-/core-1.6.8.tgz", - "integrity": "sha512-Row9er9GaWPJUEfPhVw3OAUilo53KmkI+/Dxhz/bRw0ztsPqDJPKvr9GxDvDxjz7GD5baAR43KXde4iaq64NIg==", + "version": "1.6.9", + "resolved": "https://registry.npmjs.org/@formkit/core/-/core-1.6.9.tgz", + "integrity": "sha512-Zb5OkYKMf7Rp1pd4iUMv0TJQvfgl1PdKtRRQoGiTA0XIFLB/7tcRMr1wc5isA2JS+hllfxMTh3RWF8N+64fTMg==", "dependencies": { - "@formkit/utils": "1.6.8" + "@formkit/utils": "1.6.9" } }, "node_modules/@formkit/dev": { - "version": "1.6.8", - "resolved": "https://registry.npmjs.org/@formkit/dev/-/dev-1.6.8.tgz", - "integrity": "sha512-YhIdz8H2CopajQyHGk/xNg2rXz71ZLliU69liZaFDcUUl38TzR0aCswgGkgEIbV7ISXC6xaBWNwZU3P6URykcQ==", + "version": "1.6.9", + "resolved": "https://registry.npmjs.org/@formkit/dev/-/dev-1.6.9.tgz", + "integrity": "sha512-4ueBpZAOiKr8/LZnq3mNePCX4ZB1j1JuJscBEwugWMnDeDwCNo5XWBrng1ER/LlitTRQ3mtEBNy2Qpm0yAHlwA==", "dependencies": { - "@formkit/core": "1.6.8", - "@formkit/utils": "1.6.8" + "@formkit/core": "1.6.9", + "@formkit/utils": "1.6.9" } }, "node_modules/@formkit/drag-and-drop": { @@ -646,31 +652,31 @@ "integrity": "sha512-kFjA8ucSqy4zOLXo25JHkkdrbMRW+KINDBMzBkwwtkH4YCOGIdqtxkEMUMBRgaxaAZvdxbtl+i4A/agwpv1oBw==" }, "node_modules/@formkit/i18n": { - "version": "1.6.8", - "resolved": "https://registry.npmjs.org/@formkit/i18n/-/i18n-1.6.8.tgz", - "integrity": "sha512-1X291y857FChU2MSs6QbwDrW9lpkf7EPF1s0JXZFGQPwczaE+xut76KxZUSXWCLu6m0iiK/G67DSr8EtJMNKoA==", + "version": "1.6.9", + "resolved": "https://registry.npmjs.org/@formkit/i18n/-/i18n-1.6.9.tgz", + "integrity": "sha512-8NA5bALlspCBEwInuZVgBqgQr0lDfproZdmbs2LciQpGi2B15u74JCjAkEwaKlMs+qgf/ds3QcIgUv2ztyyVEA==", "dependencies": { - "@formkit/core": "1.6.8", - "@formkit/utils": "1.6.8", - "@formkit/validation": "1.6.8" + "@formkit/core": "1.6.9", + "@formkit/utils": "1.6.9", + "@formkit/validation": "1.6.9" } }, "node_modules/@formkit/inputs": { - "version": "1.6.8", - "resolved": "https://registry.npmjs.org/@formkit/inputs/-/inputs-1.6.8.tgz", - "integrity": "sha512-PZKCbeBYgm1G17ONEgWq6R6bSjKrye3vXGIRB6Rw9dN5kS9VcTeKbS94R0uZ9WoWk4pl1dZVT086WAsfyELeRg==", + "version": "1.6.9", + "resolved": "https://registry.npmjs.org/@formkit/inputs/-/inputs-1.6.9.tgz", + "integrity": "sha512-k9gjV1e5F87NxSnu13JtKb30XYt6ndx2KGHZG8Xz0etoP75yJlMaeROHHPvlxdy2gZM6qH7Ex4it51W74Wh2Eg==", "dependencies": { - "@formkit/core": "1.6.8", - "@formkit/utils": "1.6.8" + "@formkit/core": "1.6.9", + "@formkit/utils": "1.6.9" } }, "node_modules/@formkit/observer": { - "version": "1.6.8", - "resolved": "https://registry.npmjs.org/@formkit/observer/-/observer-1.6.8.tgz", - "integrity": "sha512-KGFogNM2kdE5tf08MjIPd0ZPGPQsEuwgSKwJHbMAhCngPgQkpzUqaYBWQO2GxJt8RwozUXDa4m/7c5GZw8+eHA==", + "version": "1.6.9", + "resolved": "https://registry.npmjs.org/@formkit/observer/-/observer-1.6.9.tgz", + "integrity": "sha512-p3MCmzp6jwzXIuV3gI9uTJTJl+sN5689C7qf7gdrS8jb1fbX1snKiTyWA8FXOrBXu+ne5z/sA/yBWqYFTSLy8A==", "dependencies": { - "@formkit/core": "1.6.8", - "@formkit/utils": "1.6.8" + "@formkit/core": "1.6.9", + "@formkit/utils": "1.6.9" } }, "node_modules/@formkit/pro": { @@ -684,21 +690,21 @@ } }, "node_modules/@formkit/rules": { - "version": "1.6.8", - "resolved": "https://registry.npmjs.org/@formkit/rules/-/rules-1.6.8.tgz", - "integrity": "sha512-Yqd1JuQa7HtyTgs8YgF2EG1s2eV1vXvg3n/iT8M60p0gWmEzO7tjWPADnVJII3FNNXlsAPAdS6E01/Jn/o/ZoQ==", + "version": "1.6.9", + "resolved": "https://registry.npmjs.org/@formkit/rules/-/rules-1.6.9.tgz", + "integrity": "sha512-5Vu3JACKyws1kw02qF+024WkS7L9kYZ0lmdSpsaTqg5Wf7+InsxWXFYaG6vCzqIh4Lk9NeffIzq/xyGpGxf5uQ==", "dependencies": { - "@formkit/core": "1.6.8", - "@formkit/utils": "1.6.8", - "@formkit/validation": "1.6.8" + "@formkit/core": "1.6.9", + "@formkit/utils": "1.6.9", + "@formkit/validation": "1.6.9" } }, "node_modules/@formkit/themes": { - "version": "1.6.8", - "resolved": "https://registry.npmjs.org/@formkit/themes/-/themes-1.6.8.tgz", - "integrity": "sha512-GX8HzQw9T4cjp+e/qvl04UU86uWZ+2WBaiSe8JzpFaGmsF7RpMiQVO7mbWR5qaSwa2lwVB/7sCgzl6BY3cEM6w==", + "version": "1.6.9", + "resolved": "https://registry.npmjs.org/@formkit/themes/-/themes-1.6.9.tgz", + "integrity": "sha512-/UD+MehQEdcCEadt73eIBGGAMEK8ODN0yq9r9299WvQxIELCOP2MbcxuWCV/g2Vd15Xhl8YFdn4KCzQi4X7QXA==", "dependencies": { - "@formkit/core": "1.6.8" + "@formkit/core": "1.6.9" }, "peerDependencies": { "tailwindcss": "^3.2.0", @@ -717,77 +723,40 @@ } } }, - "node_modules/@formkit/themes/node_modules/@formkit/core": { - "version": "1.6.8", - "resolved": "https://registry.npmjs.org/@formkit/core/-/core-1.6.8.tgz", - "integrity": "sha512-Row9er9GaWPJUEfPhVw3OAUilo53KmkI+/Dxhz/bRw0ztsPqDJPKvr9GxDvDxjz7GD5baAR43KXde4iaq64NIg==", - "dependencies": { - "@formkit/utils": "1.6.8" - } - }, - "node_modules/@formkit/themes/node_modules/@formkit/utils": { - "version": "1.6.8", - "resolved": "https://registry.npmjs.org/@formkit/utils/-/utils-1.6.8.tgz", - "integrity": "sha512-OsZCwHHmIZPwiAtQ5/ewAR5eGMv/nE91UGWyVKB8BU8BDh0Ao/oOcyESLGwU5GhPMAG8qG8rGrM4Alu0JKM/Yg==" - }, "node_modules/@formkit/utils": { - "version": "1.6.8", - "resolved": "https://registry.npmjs.org/@formkit/utils/-/utils-1.6.8.tgz", - "integrity": "sha512-OsZCwHHmIZPwiAtQ5/ewAR5eGMv/nE91UGWyVKB8BU8BDh0Ao/oOcyESLGwU5GhPMAG8qG8rGrM4Alu0JKM/Yg==" + "version": "1.6.9", + "resolved": "https://registry.npmjs.org/@formkit/utils/-/utils-1.6.9.tgz", + "integrity": "sha512-vSFhB/Sm/A+SdwKdBi4WhJcdbePqSYRaB878Ol9HL8roTmmmgQpThvkv6EjLM6aRRP27Il5rS8XtIAIeh8vdTA==" }, "node_modules/@formkit/validation": { - "version": "1.6.8", - "resolved": "https://registry.npmjs.org/@formkit/validation/-/validation-1.6.8.tgz", - "integrity": "sha512-m2spXFvCzGfvl1m6GJG13bEJWl3L9A4fYwQmEhpcoty7P64psnqAjJygrl/LpUkcVyJ51ifsV3t5Gy1p4Zc+Mw==", + "version": "1.6.9", + "resolved": "https://registry.npmjs.org/@formkit/validation/-/validation-1.6.9.tgz", + "integrity": "sha512-9PGwN0ZDJt3hsrMyaL8KTG3diSQDik1OGogVG6/nFcZhWUycpeamFfXZSQ5pfzmwnvrTHsvyT0FtKitUnWWuPA==", "dependencies": { - "@formkit/core": "1.6.8", - "@formkit/observer": "1.6.8", - "@formkit/utils": "1.6.8" + "@formkit/core": "1.6.9", + "@formkit/observer": "1.6.9", + "@formkit/utils": "1.6.9" } }, "node_modules/@formkit/vue": { - "version": "1.6.8", - "resolved": "https://registry.npmjs.org/@formkit/vue/-/vue-1.6.8.tgz", - "integrity": "sha512-Uy6ElyWw4o+uo5835hZKtMVTcsOIq8eqcDApPOrCgetpAW+mFNr3C9dEbEy3shJaobEW5wmuX7CyalyZlAgAfw==", - "dependencies": { - "@formkit/core": "1.6.8", - "@formkit/dev": "1.6.8", - "@formkit/i18n": "1.6.8", - "@formkit/inputs": "1.6.8", - "@formkit/observer": "1.6.8", - "@formkit/rules": "1.6.8", - "@formkit/themes": "1.6.8", - "@formkit/utils": "1.6.8", - "@formkit/validation": "1.6.8" + "version": "1.6.9", + "resolved": "https://registry.npmjs.org/@formkit/vue/-/vue-1.6.9.tgz", + "integrity": "sha512-WrjAtEsKnFJzxQuATWsWKMpTAyJE15PUmRh9hwEAqgTDy2yMog1gxqxfZv3rEAdIdgXNp08tWmRVnQgDIF3vAQ==", + "dependencies": { + "@formkit/core": "1.6.9", + "@formkit/dev": "1.6.9", + "@formkit/i18n": "1.6.9", + "@formkit/inputs": "1.6.9", + "@formkit/observer": "1.6.9", + "@formkit/rules": "1.6.9", + "@formkit/themes": "1.6.9", + "@formkit/utils": "1.6.9", + "@formkit/validation": "1.6.9" }, "peerDependencies": { "vue": "^3.4.0" } }, - "node_modules/@formkit/vue/node_modules/@formkit/themes": { - "version": "1.6.7", - "resolved": "https://registry.npmjs.org/@formkit/themes/-/themes-1.6.7.tgz", - "integrity": "sha512-TIiWr4TMAFUg1pQz2E4GErfAhBv2Q2VbWlk6pqXPWI8UyPTjmcinEnCSIWDCX6FPPqiYShBnh8123nTO7pyvjA==", - "dependencies": { - "@formkit/core": "1.6.7" - }, - "peerDependencies": { - "tailwindcss": "^3.2.0", - "unocss": "0.x.x", - "windicss": "^3.0.0" - }, - "peerDependenciesMeta": { - "tailwindcss": { - "optional": true - }, - "unocss": { - "optional": true - }, - "windicss": { - "optional": true - } - } - }, "node_modules/@humanwhocodes/config-array": { "version": "0.13.0", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", @@ -1271,12 +1240,12 @@ } }, "node_modules/@playwright/test": { - "version": "1.48.2", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.48.2.tgz", - "integrity": "sha512-54w1xCWfXuax7dz4W2M9uw0gDyh+ti/0K/MxcCUxChFh37kkdxPdfZDw5QBbuPUJHr1CiHJ1hXgSs+GgeQc5Zw==", + "version": "1.49.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.49.0.tgz", + "integrity": "sha512-DMulbwQURa8rNIQrf94+jPJQ4FmOVdpE5ZppRNvWVjvhC+6sOeo28r8MgIpQRYouXRtt/FCCXU7zn20jnHR4Qw==", "dev": true, "dependencies": { - "playwright": "1.48.2" + "playwright": "1.49.0" }, "bin": { "playwright": "cli.js" @@ -1635,9 +1604,9 @@ "integrity": "sha512-+kmRpd+EeTFd3qNt1AoKphJqbAN26ZDsbiwqjBFeoAmdCyiUO19xMXPtYi9vovAj9a7OAJnvWtiHkwwjU2Fx4Q==" }, "node_modules/@tanstack/match-sorter-utils": { - "version": "8.15.1", - "resolved": "https://registry.npmjs.org/@tanstack/match-sorter-utils/-/match-sorter-utils-8.15.1.tgz", - "integrity": "sha512-PnVV3d2poenUM31ZbZi/yXkBu3J7kd5k2u51CGwwNojag451AjTH9N6n41yjXz2fpLeewleyLBmNS6+HcGDlXw==", + "version": "8.19.4", + "resolved": "https://registry.npmjs.org/@tanstack/match-sorter-utils/-/match-sorter-utils-8.19.4.tgz", + "integrity": "sha512-Wo1iKt2b9OT7d+YGhvEPD3DXvPv2etTusIMhMUoG7fbhmxcXCtIjJDEygy91Y2JFlwGyjqiBPRozme7UD8hoqg==", "dependencies": { "remove-accents": "0.5.0" }, @@ -1650,21 +1619,21 @@ } }, "node_modules/@tanstack/query-core": { - "version": "5.59.20", - "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.59.20.tgz", - "integrity": "sha512-e8vw0lf7KwfGe1if4uPFhvZRWULqHjFcz3K8AebtieXvnMOz5FSzlZe3mTLlPuUBcydCnBRqYs2YJ5ys68wwLg==", + "version": "5.62.2", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.62.2.tgz", + "integrity": "sha512-LcwVcC5qpsDpHcqlXUUL5o9SaOBwhNkGeV+B06s0GBoyBr8FqXPuXT29XzYXR36lchhnerp6XO+CWc84/vh7Zg==", "funding": { "type": "github", "url": "https://github.com/sponsors/tannerlinsley" } }, "node_modules/@tanstack/vue-query": { - "version": "5.59.20", - "resolved": "https://registry.npmjs.org/@tanstack/vue-query/-/vue-query-5.59.20.tgz", - "integrity": "sha512-kIs1GfXh7jVLycbnQDghfdrcvrZz5fxnMF7eAAp8O3ZfhHQWfP57DBXbOvww4Y+TI0EvVoh+hihX+LNFBGFKLg==", + "version": "5.62.2", + "resolved": "https://registry.npmjs.org/@tanstack/vue-query/-/vue-query-5.62.2.tgz", + "integrity": "sha512-osmptKF+Ung+UFNgKDhOrQxmlZsRp0zATCz8ARTbcay20CCnv6rvBR/+LJlb/M9YGQU/chrt/nPIi1Ax+j0SUw==", "dependencies": { - "@tanstack/match-sorter-utils": "^8.15.1", - "@tanstack/query-core": "5.59.20", + "@tanstack/match-sorter-utils": "^8.19.4", + "@tanstack/query-core": "5.62.2", "@vue/devtools-api": "^6.6.3", "vue-demi": "^0.14.10" }, @@ -1708,9 +1677,9 @@ } }, "node_modules/@tiptap/core": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-2.9.1.tgz", - "integrity": "sha512-tifnLL/ARzQ6/FGEJjVwj9UT3v+pENdWHdk9x6F3X0mB1y0SeCjV21wpFLYESzwNdBPAj8NMp8Behv7dBnhIfw==", + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-2.10.3.tgz", + "integrity": "sha512-wAG/0/UsLeZLmshWb6rtWNXKJftcmnned91/HLccHVQAuQZ1UWH+wXeQKu/mtodxEO7JcU2mVPR9mLGQkK0McQ==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -1720,9 +1689,9 @@ } }, "node_modules/@tiptap/extension-blockquote": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/@tiptap/extension-blockquote/-/extension-blockquote-2.9.1.tgz", - "integrity": "sha512-Y0jZxc/pdkvcsftmEZFyG+73um8xrx6/DMfgUcNg3JAM63CISedNcr+OEI11L0oFk1KFT7/aQ9996GM6Kubdqg==", + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-blockquote/-/extension-blockquote-2.10.3.tgz", + "integrity": "sha512-u9Mq4r8KzoeGVT8ms6FQDIMN95dTh3TYcT7fZpwcVM96mIl2Oyt+Bk66mL8z4zuFptfRI57Cu9QdnHEeILd//w==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -1732,9 +1701,9 @@ } }, "node_modules/@tiptap/extension-bold": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/@tiptap/extension-bold/-/extension-bold-2.9.1.tgz", - "integrity": "sha512-e2P1zGpnnt4+TyxTC5pX/lPxPasZcuHCYXY0iwQ3bf8qRQQEjDfj3X7EI+cXqILtnhOiviEOcYmeu5op2WhQDg==", + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-bold/-/extension-bold-2.10.3.tgz", + "integrity": "sha512-xnF1tS2BsORenr11qyybW120gHaeHKiKq+ZOP14cGA0MsriKvWDnaCSocXP/xMEYHy7+2uUhJ0MsKkHVj4bPzQ==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -1744,9 +1713,9 @@ } }, "node_modules/@tiptap/extension-bubble-menu": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/@tiptap/extension-bubble-menu/-/extension-bubble-menu-2.9.1.tgz", - "integrity": "sha512-DWUF6NG08/bZDWw0jCeotSTvpkyqZTi4meJPomG9Wzs/Ol7mEwlNCsCViD999g0+IjyXFatBk4DfUq1YDDu++Q==", + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-bubble-menu/-/extension-bubble-menu-2.10.3.tgz", + "integrity": "sha512-e9a4yMjQezuKy0rtyyzxbV2IAE1bm1PY3yoZEFrcaY0o47g1CMUn2Hwe+9As2HdntEjQpWR7NO1mZeKxHlBPYA==", "dependencies": { "tippy.js": "^6.3.7" }, @@ -1760,9 +1729,9 @@ } }, "node_modules/@tiptap/extension-bullet-list": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/@tiptap/extension-bullet-list/-/extension-bullet-list-2.9.1.tgz", - "integrity": "sha512-0hizL/0j9PragJObjAWUVSuGhN1jKjCFnhLQVRxtx4HutcvS/lhoWMvFg6ZF8xqWgIa06n6A7MaknQkqhTdhKA==", + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-bullet-list/-/extension-bullet-list-2.10.3.tgz", + "integrity": "sha512-PTkwJOVlHi4RR4Wrs044tKMceweXwNmWA6EoQ93hPUVtQcwQL990Es5Izp+i88twTPLuGD9dH+o9QDyH9SkWdA==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -1772,9 +1741,9 @@ } }, "node_modules/@tiptap/extension-code": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/@tiptap/extension-code/-/extension-code-2.9.1.tgz", - "integrity": "sha512-WQqcVGe7i/E+yO3wz5XQteU1ETNZ00euUEl4ylVVmH2NM4Dh0KDjEhbhHlCM0iCfLUo7jhjC7dmS+hMdPUb+Tg==", + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-code/-/extension-code-2.10.3.tgz", + "integrity": "sha512-JyLbfyY3cPctq9sVdpcRWTcoUOoq3/MnGE1eP6eBNyMTHyBPcM9TPhOkgj+xkD1zW/884jfelB+wa70RT/AMxQ==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -1784,9 +1753,9 @@ } }, "node_modules/@tiptap/extension-code-block": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/@tiptap/extension-code-block/-/extension-code-block-2.9.1.tgz", - "integrity": "sha512-A/50wPWDqEUUUPhrwRKILP5gXMO5UlQ0F6uBRGYB9CEVOREam9yIgvONOnZVJtszHqOayjIVMXbH/JMBeq11/g==", + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-code-block/-/extension-code-block-2.10.3.tgz", + "integrity": "sha512-yiDVNg22fYkzsFk5kBlDSHcjwVJgajvO/M5fDXA+Hfxwo2oNcG6aJyyHXFe+UaXTVjdkPej0J6kcMKrTMCiFug==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -1797,9 +1766,9 @@ } }, "node_modules/@tiptap/extension-document": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/@tiptap/extension-document/-/extension-document-2.9.1.tgz", - "integrity": "sha512-1a+HCoDPnBttjqExfYLwfABq8MYdiowhy/wp8eCxVb6KGFEENO53KapstISvPzqH7eOi+qRjBB1KtVYb/ZXicg==", + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-document/-/extension-document-2.10.3.tgz", + "integrity": "sha512-6i8+xbS2zB6t8iFzli1O/QB01MmwyI5Hqiiv4m5lOxqavmJwLss2sRhoMC2hB3CyFg5UmeODy/f/RnI6q5Vixg==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -1809,9 +1778,9 @@ } }, "node_modules/@tiptap/extension-dropcursor": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/@tiptap/extension-dropcursor/-/extension-dropcursor-2.9.1.tgz", - "integrity": "sha512-wJZspSmJRkDBtPkzFz1g7gvZOEOayk8s93UHsgbJxcV4VWHYleZ5XhT74sZunSjefNDm3qC6v2BSgLp3vNHVKQ==", + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-dropcursor/-/extension-dropcursor-2.10.3.tgz", + "integrity": "sha512-wzWf82ixWzZQr0hxcf/A0ul8NNxgy1N63O+c56st6OomoLuKUJWOXF+cs9O7V+/5rZKWdbdYYoRB5QLvnDBAlQ==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -1822,9 +1791,9 @@ } }, "node_modules/@tiptap/extension-floating-menu": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/@tiptap/extension-floating-menu/-/extension-floating-menu-2.9.1.tgz", - "integrity": "sha512-MxZ7acNNsoNaKpetxfwi3Z11Bgrh0T2EJlCV77v9N1vWK38+st3H1WJanmLbPNtc2ocvhHJrz+DjDz3CWxQ9rQ==", + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-floating-menu/-/extension-floating-menu-2.10.3.tgz", + "integrity": "sha512-Prg8rYLxeyzHxfzVu1mDkkUWMnD9ZN3y370O/1qy55e+XKVw9jFkTSuz0y0+OhMJG6bulYpDUMtb+N3+2xOWlQ==", "dependencies": { "tippy.js": "^6.3.7" }, @@ -1838,9 +1807,9 @@ } }, "node_modules/@tiptap/extension-gapcursor": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/@tiptap/extension-gapcursor/-/extension-gapcursor-2.9.1.tgz", - "integrity": "sha512-jsRBmX01vr+5H02GljiHMo0n5H1vzoMLmFarxe0Yq2d2l9G/WV2VWX2XnGliqZAYWd1bI0phs7uLQIN3mxGQTw==", + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-gapcursor/-/extension-gapcursor-2.10.3.tgz", + "integrity": "sha512-FskZi2DqDSTH1WkgLF2OLy0xU7qj3AgHsKhVsryeAtld4jAK5EsonneWgaipbz0e/MxuIvc1oyacfZKABpLaNg==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -1851,9 +1820,9 @@ } }, "node_modules/@tiptap/extension-hard-break": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/@tiptap/extension-hard-break/-/extension-hard-break-2.9.1.tgz", - "integrity": "sha512-fCuaOD/b7nDjm47PZ58oanq7y4ccS2wjPh42Qm0B0yipu/1fmC8eS1SmaXmk28F89BLtuL6uOCtR1spe+lZtlQ==", + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-hard-break/-/extension-hard-break-2.10.3.tgz", + "integrity": "sha512-2rFlimUKAgKDwT6nqAMtPBjkrknQY8S7oBNyIcDOUGyFkvbDUl3Jd0PiC929S5F3XStJRppnMqhpNDAlWmvBLA==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -1863,9 +1832,9 @@ } }, "node_modules/@tiptap/extension-heading": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/@tiptap/extension-heading/-/extension-heading-2.9.1.tgz", - "integrity": "sha512-SjZowzLixOFaCrV2cMaWi1mp8REK0zK1b3OcVx7bCZfVSmsOETJyrAIUpCKA8o60NwF7pwhBg0MN8oXlNKMeFw==", + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-heading/-/extension-heading-2.10.3.tgz", + "integrity": "sha512-AlxXXPCWIvw8hQUDFRskasj32iMNB8Sb19VgyFWqwvntGs2/UffNu8VdsVqxD2HpZ0g5rLYCYtSW4wigs9R3og==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -1875,9 +1844,9 @@ } }, "node_modules/@tiptap/extension-history": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/@tiptap/extension-history/-/extension-history-2.9.1.tgz", - "integrity": "sha512-wp9qR1NM+LpvyLZFmdNaAkDq0d4jDJ7z7Fz7icFQPu31NVxfQYO3IXNmvJDCNu8hFAbImpA5aG8MBuwzRo0H9w==", + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-history/-/extension-history-2.10.3.tgz", + "integrity": "sha512-HaSiMdx9Im9Pb9qGlVud7W8bweRDRMez33Uzs5a2x0n1RWkelfH7TwYs41Y3wus8Ujs7kw6qh7jyhvPpQBKaSA==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -1888,9 +1857,9 @@ } }, "node_modules/@tiptap/extension-horizontal-rule": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-2.9.1.tgz", - "integrity": "sha512-ydUhABeaBI1CoJp+/BBqPhXINfesp1qMNL/jiDcMsB66fsD4nOyphpAJT7FaRFZFtQVF06+nttBtFZVkITQVqg==", + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-2.10.3.tgz", + "integrity": "sha512-1a2IWhD00tgUNg/91RLnBvfENL7DLCui5L245+smcaLu+OXOOEpoBHawx59/M4hEpsjqvRRM79TzO9YXfopsPw==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -1901,9 +1870,9 @@ } }, "node_modules/@tiptap/extension-italic": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/@tiptap/extension-italic/-/extension-italic-2.9.1.tgz", - "integrity": "sha512-VkNA6Vz96+/+7uBlsgM7bDXXx4b62T1fDam/3UKifA72aD/fZckeWrbT7KrtdUbzuIniJSbA0lpTs5FY29+86Q==", + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-italic/-/extension-italic-2.10.3.tgz", + "integrity": "sha512-wAiO6ZxoHx2H90phnKttLWGPjPZXrfKxhOCsqYrK8BpRByhr48godOFRuGwYnKaiwoVjpxc63t+kDJDWvqmgMw==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -1913,9 +1882,9 @@ } }, "node_modules/@tiptap/extension-list-item": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/@tiptap/extension-list-item/-/extension-list-item-2.9.1.tgz", - "integrity": "sha512-6O4NtYNR5N2Txi4AC0/4xMRJq9xd4+7ShxCZCDVL0WDVX37IhaqMO7LGQtA6MVlYyNaX4W1swfdJaqrJJ5HIUw==", + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-list-item/-/extension-list-item-2.10.3.tgz", + "integrity": "sha512-9sok81gvZfSta2K1Dwrq5/HSz1jk4zHBpFqCx0oydzodGslx6X1bNxdca+eXJpXZmQIWALK7zEr4X8kg3WZsgw==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -1925,9 +1894,9 @@ } }, "node_modules/@tiptap/extension-ordered-list": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/@tiptap/extension-ordered-list/-/extension-ordered-list-2.9.1.tgz", - "integrity": "sha512-6J9jtv1XP8dW7/JNSH/K4yiOABc92tBJtgCsgP8Ep4+fjfjdj4HbjS1oSPWpgItucF2Fp/VF8qg55HXhjxHjTw==", + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-ordered-list/-/extension-ordered-list-2.10.3.tgz", + "integrity": "sha512-/SFuEDnbJxy3jvi72LeyiPHWkV+uFc0LUHTUHSh20vwyy+tLrzncJfXohGbTIv5YxYhzExQYZDRD4VbSghKdlw==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -1937,9 +1906,9 @@ } }, "node_modules/@tiptap/extension-paragraph": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/@tiptap/extension-paragraph/-/extension-paragraph-2.9.1.tgz", - "integrity": "sha512-JOmT0xd4gd3lIhLwrsjw8lV+ZFROKZdIxLi0Ia05XSu4RLrrvWj0zdKMSB+V87xOWfSB3Epo95zAvnPox5Q16A==", + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-paragraph/-/extension-paragraph-2.10.3.tgz", + "integrity": "sha512-sNkTX/iN+YoleDiTJsrWSBw9D7c4vsYwnW5y/G5ydfuJMIRQMF78pWSIWZFDRNOMkgK5UHkhu9anrbCFYgBfaA==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -1949,9 +1918,9 @@ } }, "node_modules/@tiptap/extension-placeholder": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/@tiptap/extension-placeholder/-/extension-placeholder-2.9.1.tgz", - "integrity": "sha512-Q/w3OOg/C6jGBf4QKEWKF9k+iaCQCgPoaIg2IDTPx8QmaxRfgoVE5Csd+oTOY/brdmSNXOxykZWEci6OJP+MbA==", + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-placeholder/-/extension-placeholder-2.10.3.tgz", + "integrity": "sha512-0OkwnDLguZgoiJM85cfnOySuMmPUF7qqw7DHQ+c3zwTAYnvzpvqrvpupc+2Zi9GfC1sDgr+Ajrp8imBHa6PHfA==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -1962,9 +1931,9 @@ } }, "node_modules/@tiptap/extension-strike": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/@tiptap/extension-strike/-/extension-strike-2.9.1.tgz", - "integrity": "sha512-V5aEXdML+YojlPhastcu7w4biDPwmzy/fWq0T2qjfu5Te/THcqDmGYVBKESBm5x6nBy5OLkanw2O+KHu2quDdg==", + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-strike/-/extension-strike-2.10.3.tgz", + "integrity": "sha512-jYoPy6F6njYp3txF3u23bgdRy/S5ATcWDO9LPZLHSeikwQfJ47nqb+EUNo5M8jIOgFBTn4MEbhuZ6OGyhnxopA==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -1974,9 +1943,9 @@ } }, "node_modules/@tiptap/extension-text": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/@tiptap/extension-text/-/extension-text-2.9.1.tgz", - "integrity": "sha512-3wo9uCrkLVLQFgbw2eFU37QAa1jq1/7oExa+FF/DVxdtHRS9E2rnUZ8s2hat/IWzvPUHXMwo3Zg2XfhoamQpCA==", + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-text/-/extension-text-2.10.3.tgz", + "integrity": "sha512-7p9XiRprsRZm8y9jvF/sS929FCELJ5N9FQnbzikOiyGNUx5mdI+exVZlfvBr9xOD5s7fBLg6jj9Vs0fXPNRkPg==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -1986,9 +1955,9 @@ } }, "node_modules/@tiptap/extension-text-style": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/@tiptap/extension-text-style/-/extension-text-style-2.9.1.tgz", - "integrity": "sha512-LAxc0SeeiPiAVBwksczeA7BJSZb6WtVpYhy5Esvy9K0mK5kttB4KxtnXWeQzMIJZQbza65yftGKfQlexf/Y7yg==", + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-text-style/-/extension-text-style-2.10.3.tgz", + "integrity": "sha512-TalYIdlF7vBA4afFhmido7AORdBbu3sV+HCByda0FiNbM6cjng3Nr9oxHOCVJy+ChqrcgF4m54zDfLmamdyu5Q==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -1998,28 +1967,28 @@ } }, "node_modules/@tiptap/pm": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-2.9.1.tgz", - "integrity": "sha512-mvV86fr7kEuDYEApQ2uMPCKL2uagUE0BsXiyyz3KOkY1zifyVm1fzdkscb24Qy1GmLzWAIIihA+3UHNRgYdOlQ==", + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-2.10.3.tgz", + "integrity": "sha512-771p53aU0KFvujvKpngvq2uAxThlEsjYaXcVVmwrhf0vxSSg+psKQEvqvWvHv/3BwkPVCGwmEKNVJZjaXFKu4g==", "dependencies": { "prosemirror-changeset": "^2.2.1", "prosemirror-collab": "^1.3.1", - "prosemirror-commands": "^1.6.0", + "prosemirror-commands": "^1.6.2", "prosemirror-dropcursor": "^1.8.1", "prosemirror-gapcursor": "^1.3.2", "prosemirror-history": "^1.4.1", "prosemirror-inputrules": "^1.4.0", "prosemirror-keymap": "^1.2.2", - "prosemirror-markdown": "^1.13.0", + "prosemirror-markdown": "^1.13.1", "prosemirror-menu": "^1.2.4", - "prosemirror-model": "^1.22.3", + "prosemirror-model": "^1.23.0", "prosemirror-schema-basic": "^1.2.3", "prosemirror-schema-list": "^1.4.1", "prosemirror-state": "^1.4.3", - "prosemirror-tables": "^1.4.0", + "prosemirror-tables": "^1.6.1", "prosemirror-trailing-node": "^3.0.0", - "prosemirror-transform": "^1.10.0", - "prosemirror-view": "^1.34.3" + "prosemirror-transform": "^1.10.2", + "prosemirror-view": "^1.37.0" }, "funding": { "type": "github", @@ -2027,31 +1996,31 @@ } }, "node_modules/@tiptap/starter-kit": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/@tiptap/starter-kit/-/starter-kit-2.9.1.tgz", - "integrity": "sha512-nsw6UF/7wDpPfHRhtGOwkj1ipIEiWZS1VGw+c14K61vM1CNj0uQ4jogbHwHZqN1dlL5Hh+FCqUHDPxG6ECbijg==", - "dependencies": { - "@tiptap/core": "^2.9.1", - "@tiptap/extension-blockquote": "^2.9.1", - "@tiptap/extension-bold": "^2.9.1", - "@tiptap/extension-bullet-list": "^2.9.1", - "@tiptap/extension-code": "^2.9.1", - "@tiptap/extension-code-block": "^2.9.1", - "@tiptap/extension-document": "^2.9.1", - "@tiptap/extension-dropcursor": "^2.9.1", - "@tiptap/extension-gapcursor": "^2.9.1", - "@tiptap/extension-hard-break": "^2.9.1", - "@tiptap/extension-heading": "^2.9.1", - "@tiptap/extension-history": "^2.9.1", - "@tiptap/extension-horizontal-rule": "^2.9.1", - "@tiptap/extension-italic": "^2.9.1", - "@tiptap/extension-list-item": "^2.9.1", - "@tiptap/extension-ordered-list": "^2.9.1", - "@tiptap/extension-paragraph": "^2.9.1", - "@tiptap/extension-strike": "^2.9.1", - "@tiptap/extension-text": "^2.9.1", - "@tiptap/extension-text-style": "^2.9.1", - "@tiptap/pm": "^2.9.1" + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@tiptap/starter-kit/-/starter-kit-2.10.3.tgz", + "integrity": "sha512-oq8xdVIMqohSs91ofHSr7i5dCp2F56Lb9aYIAI25lZmwNwQJL2geGOYjMSfL0IC4cQHPylIuSKYCg7vRFdZmAA==", + "dependencies": { + "@tiptap/core": "^2.10.3", + "@tiptap/extension-blockquote": "^2.10.3", + "@tiptap/extension-bold": "^2.10.3", + "@tiptap/extension-bullet-list": "^2.10.3", + "@tiptap/extension-code": "^2.10.3", + "@tiptap/extension-code-block": "^2.10.3", + "@tiptap/extension-document": "^2.10.3", + "@tiptap/extension-dropcursor": "^2.10.3", + "@tiptap/extension-gapcursor": "^2.10.3", + "@tiptap/extension-hard-break": "^2.10.3", + "@tiptap/extension-heading": "^2.10.3", + "@tiptap/extension-history": "^2.10.3", + "@tiptap/extension-horizontal-rule": "^2.10.3", + "@tiptap/extension-italic": "^2.10.3", + "@tiptap/extension-list-item": "^2.10.3", + "@tiptap/extension-ordered-list": "^2.10.3", + "@tiptap/extension-paragraph": "^2.10.3", + "@tiptap/extension-strike": "^2.10.3", + "@tiptap/extension-text": "^2.10.3", + "@tiptap/extension-text-style": "^2.10.3", + "@tiptap/pm": "^2.10.3" }, "funding": { "type": "github", @@ -2059,12 +2028,12 @@ } }, "node_modules/@tiptap/vue-3": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/@tiptap/vue-3/-/vue-3-2.9.1.tgz", - "integrity": "sha512-51mKa4C3hdKe+o6G7Pk7d4puZ/VjoHWtTo2WxE249oH+bCkh6FObqNu2wfRK+9obVuTGXQ9dAc988cmwY+2eyw==", + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@tiptap/vue-3/-/vue-3-2.10.3.tgz", + "integrity": "sha512-eJLUpuKq3Yei3+XHba25eFvjAH6q275r+Dkz/ulStOWGwchlN8OSbcn0kBWfhr14RG8yoNvL4rZncxXvqXzvhQ==", "dependencies": { - "@tiptap/extension-bubble-menu": "^2.9.1", - "@tiptap/extension-floating-menu": "^2.9.1" + "@tiptap/extension-bubble-menu": "^2.10.3", + "@tiptap/extension-floating-menu": "^2.10.3" }, "funding": { "type": "github", @@ -2116,26 +2085,23 @@ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==" }, "node_modules/@types/linkify-it": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.5.tgz", - "integrity": "sha512-yg6E+u0/+Zjva+buc3EIb+29XEg4wltq7cSmd4Uc2EE/1nUVmxyzpX6gUXD0V8jIrG0r7YeOGVIbYRkxeooCtw==", - "peer": true + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==" }, "node_modules/@types/markdown-it": { - "version": "13.0.7", - "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-13.0.7.tgz", - "integrity": "sha512-U/CBi2YUUcTHBt5tjO2r5QV/x0Po6nsYwQU4Y04fBS6vfoImaiZ6f8bi3CjTCxBPQSO1LMyUqkByzi8AidyxfA==", - "peer": true, + "version": "14.1.2", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz", + "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==", "dependencies": { - "@types/linkify-it": "*", - "@types/mdurl": "*" + "@types/linkify-it": "^5", + "@types/mdurl": "^2" } }, "node_modules/@types/mdurl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.5.tgz", - "integrity": "sha512-6L6VymKTzYSrEf4Nev4Xa1LCHKrlTlYCBMTlQKFuddo1CvQcE52I0mwfOJayueUC7MJuXOeHTcIU683lzd0cUA==", - "peer": true + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==" }, "node_modules/@types/node": { "version": "20.11.4", @@ -2156,6 +2122,12 @@ "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.8.tgz", "integrity": "sha512-0vWLNK2D5MT9dg0iOo8GlKguPAU02QjmZitPEsXRuJXU/OGIOt9vT9Fc26wtYuavLxtO45v9PGleoL9Z0k1LHg==" }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "optional": true + }, "node_modules/@types/web-bluetooth": { "version": "0.0.20", "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz", @@ -2349,15 +2321,15 @@ "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==" }, "node_modules/@vitejs/plugin-vue": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.0.tgz", - "integrity": "sha512-7n7KdUEtx/7Yl7I/WVAMZ1bEb0eVvXF3ummWTeLcs/9gvo9pJhuLdouSXGjdZ/MKD1acf1I272+X0RMua4/R3g==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.1.tgz", + "integrity": "sha512-cxh314tzaWwOLqVes2gnnCtvBDcM1UMdn+iFR+UjAn411dPT3tOmqrJjbMd7koZpMAmBM/GqeV4n9ge7JSiJJQ==", "dev": true, "engines": { "node": "^18.0.0 || >=20.0.0" }, "peerDependencies": { - "vite": "^5.0.0", + "vite": "^5.0.0 || ^6.0.0", "vue": "^3.2.25" } }, @@ -2514,57 +2486,57 @@ } }, "node_modules/@vue/compiler-core": { - "version": "3.5.12", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.12.tgz", - "integrity": "sha512-ISyBTRMmMYagUxhcpyEH0hpXRd/KqDU4ymofPgl2XAkY9ZhQ+h0ovEZJIiPop13UmR/54oA2cgMDjgroRelaEw==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.13.tgz", + "integrity": "sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==", "dependencies": { "@babel/parser": "^7.25.3", - "@vue/shared": "3.5.12", + "@vue/shared": "3.5.13", "entities": "^4.5.0", "estree-walker": "^2.0.2", "source-map-js": "^1.2.0" } }, "node_modules/@vue/compiler-dom": { - "version": "3.5.12", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.12.tgz", - "integrity": "sha512-9G6PbJ03uwxLHKQ3P42cMTi85lDRvGLB2rSGOiQqtXELat6uI4n8cNz9yjfVHRPIu+MsK6TE418Giruvgptckg==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.13.tgz", + "integrity": "sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA==", "dependencies": { - "@vue/compiler-core": "3.5.12", - "@vue/shared": "3.5.12" + "@vue/compiler-core": "3.5.13", + "@vue/shared": "3.5.13" } }, "node_modules/@vue/compiler-sfc": { - "version": "3.5.12", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.12.tgz", - "integrity": "sha512-2k973OGo2JuAa5+ZlekuQJtitI5CgLMOwgl94BzMCsKZCX/xiqzJYzapl4opFogKHqwJk34vfsaKpfEhd1k5nw==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.13.tgz", + "integrity": "sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ==", "dependencies": { "@babel/parser": "^7.25.3", - "@vue/compiler-core": "3.5.12", - "@vue/compiler-dom": "3.5.12", - "@vue/compiler-ssr": "3.5.12", - "@vue/shared": "3.5.12", + "@vue/compiler-core": "3.5.13", + "@vue/compiler-dom": "3.5.13", + "@vue/compiler-ssr": "3.5.13", + "@vue/shared": "3.5.13", "estree-walker": "^2.0.2", "magic-string": "^0.30.11", - "postcss": "^8.4.47", + "postcss": "^8.4.48", "source-map-js": "^1.2.0" } }, "node_modules/@vue/compiler-sfc/node_modules/magic-string": { - "version": "0.30.12", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.12.tgz", - "integrity": "sha512-Ea8I3sQMVXr8JhN4z+H/d8zwo+tYDgHE9+5G4Wnrwhs0gaK9fXTKx0Tw5Xwsd/bCPTTZNRAdpyzvoeORe9LYpw==", + "version": "0.30.13", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.13.tgz", + "integrity": "sha512-8rYBO+MsWkgjDSOvLomYnzhdwEG51olQ4zL5KXnNJWV5MNmrb4rTZdrtkhxjnD/QyZUqR/Z/XDsUs/4ej2nx0g==", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } }, "node_modules/@vue/compiler-ssr": { - "version": "3.5.12", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.12.tgz", - "integrity": "sha512-eLwc7v6bfGBSM7wZOGPmRavSWzNFF6+PdRhE+VFJhNCgHiF8AM7ccoqcv5kBXA2eWUfigD7byekvf/JsOfKvPA==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.13.tgz", + "integrity": "sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA==", "dependencies": { - "@vue/compiler-dom": "3.5.12", - "@vue/shared": "3.5.12" + "@vue/compiler-dom": "3.5.13", + "@vue/shared": "3.5.13" } }, "node_modules/@vue/devtools-api": { @@ -2573,49 +2545,49 @@ "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==" }, "node_modules/@vue/reactivity": { - "version": "3.5.12", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.12.tgz", - "integrity": "sha512-UzaN3Da7xnJXdz4Okb/BGbAaomRHc3RdoWqTzlvd9+WBR5m3J39J1fGcHes7U3za0ruYn/iYy/a1euhMEHvTAg==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.13.tgz", + "integrity": "sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==", "dependencies": { - "@vue/shared": "3.5.12" + "@vue/shared": "3.5.13" } }, "node_modules/@vue/runtime-core": { - "version": "3.5.12", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.12.tgz", - "integrity": "sha512-hrMUYV6tpocr3TL3Ad8DqxOdpDe4zuQY4HPY3X/VRh+L2myQO8MFXPAMarIOSGNu0bFAjh1yBkMPXZBqCk62Uw==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.13.tgz", + "integrity": "sha512-Fj4YRQ3Az0WTZw1sFe+QDb0aXCerigEpw418pw1HBUKFtnQHWzwojaukAs2X/c9DQz4MQ4bsXTGlcpGxU/RCIw==", "dependencies": { - "@vue/reactivity": "3.5.12", - "@vue/shared": "3.5.12" + "@vue/reactivity": "3.5.13", + "@vue/shared": "3.5.13" } }, "node_modules/@vue/runtime-dom": { - "version": "3.5.12", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.12.tgz", - "integrity": "sha512-q8VFxR9A2MRfBr6/55Q3umyoN7ya836FzRXajPB6/Vvuv0zOPL+qltd9rIMzG/DbRLAIlREmnLsplEF/kotXKA==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.13.tgz", + "integrity": "sha512-dLaj94s93NYLqjLiyFzVs9X6dWhTdAlEAciC3Moq7gzAc13VJUdCnjjRurNM6uTLFATRHexHCTu/Xp3eW6yoog==", "dependencies": { - "@vue/reactivity": "3.5.12", - "@vue/runtime-core": "3.5.12", - "@vue/shared": "3.5.12", + "@vue/reactivity": "3.5.13", + "@vue/runtime-core": "3.5.13", + "@vue/shared": "3.5.13", "csstype": "^3.1.3" } }, "node_modules/@vue/server-renderer": { - "version": "3.5.12", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.12.tgz", - "integrity": "sha512-I3QoeDDeEPZm8yR28JtY+rk880Oqmj43hreIBVTicisFTx/Dl7JpG72g/X7YF8hnQD3IFhkky5i2bPonwrTVPg==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.13.tgz", + "integrity": "sha512-wAi4IRJV/2SAW3htkTlB+dHeRmpTiVIK1OGLWV1yeStVSebSQQOwGwIq0D3ZIoBj2C2qpgz5+vX9iEBkTdk5YA==", "dependencies": { - "@vue/compiler-ssr": "3.5.12", - "@vue/shared": "3.5.12" + "@vue/compiler-ssr": "3.5.13", + "@vue/shared": "3.5.13" }, "peerDependencies": { - "vue": "3.5.12" + "vue": "3.5.13" } }, "node_modules/@vue/shared": { - "version": "3.5.12", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.12.tgz", - "integrity": "sha512-L2RPSAwUFbgZH20etwrXyVyCBu9OxRSi8T/38QsvnkJyvq2LufW2lDCOzm7t/U9C1mkhJGWYfCuFBCmIuNivrg==" + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.13.tgz", + "integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==" }, "node_modules/@vue/test-utils": { "version": "2.4.6", @@ -2846,6 +2818,25 @@ } } }, + "node_modules/@wdns/vuetify-resize-drawer": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@wdns/vuetify-resize-drawer/-/vuetify-resize-drawer-3.2.0.tgz", + "integrity": "sha512-JfPDrV9G/6k6fCLLIurET6jdDIzEVSvjrqxoVeWhxTVUuS+Cs4oJga7wWNRgFTZdqfyZT8Id2aUDCEHYCjcQQg==", + "funding": [ + { + "type": "paypal", + "url": "https://paypal.me/webdevnerdstuff" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/WebDevNerdStuff" + } + ], + "dependencies": { + "vue": "^3.5.12", + "vuetify": "^3.7.2" + } + }, "node_modules/@yr/monotone-cubic-spline": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@yr/monotone-cubic-spline/-/monotone-cubic-spline-1.0.3.tgz", @@ -3059,6 +3050,11 @@ "node": ">=8" } }, + "node_modules/buffer-builder": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/buffer-builder/-/buffer-builder-0.2.0.tgz", + "integrity": "sha512-7VPMEPuYznPSoR21NE1zvd2Xna6c/CloiZCfcMXR1Jny6PjX0N4Nsa38zcBFo/FMK+BlA+FLKbJCQ0i2yxp+Xg==" + }, "node_modules/cac": { "version": "6.7.14", "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", @@ -3274,6 +3270,11 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, + "node_modules/colorjs.io": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/colorjs.io/-/colorjs.io-0.5.2.tgz", + "integrity": "sha512-twmVoizEW7ylZSN32OgKdXRmo1qg+wT5/6C3xu5b9QsWzSFAhHLn2xd8ro0diCsKfCj1RdaTP/nrcW+vAoQPIw==" + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -3339,9 +3340,9 @@ } }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -3668,9 +3669,12 @@ } }, "node_modules/dompurify": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.0.tgz", - "integrity": "sha512-AMdOzK44oFWqHEi0wpOqix/fUNY707OmoeFDnbi3Q5I8uOpy21ufUA5cDJPr0bosxrflOVD/H2DMSvuGKJGfmQ==" + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.2.tgz", + "integrity": "sha512-YMM+erhdZ2nkZ4fTNRTSI94mb7VG7uVF5vj5Zde7tImgnhZE3R6YW/IACGIHb2ux+QkEXMhe591N+5jWOmL4Zw==", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } }, "node_modules/domutils": { "version": "3.1.0", @@ -3686,9 +3690,9 @@ } }, "node_modules/dotenv": { - "version": "16.4.5", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", - "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", + "version": "16.4.7", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", + "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", "engines": { "node": ">=12" }, @@ -3998,9 +4002,9 @@ } }, "node_modules/eslint-plugin-vue": { - "version": "9.31.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.31.0.tgz", - "integrity": "sha512-aYMUCgivhz1o4tLkRHj5oq9YgYPM4/EJc0M7TAKRLCUA5OYxRLAhYEVD2nLtTwLyixEFI+/QXSvKU9ESZFgqjQ==", + "version": "9.32.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.32.0.tgz", + "integrity": "sha512-b/Y05HYmnB/32wqVcjxjHZzNpwxj1onBOvqW89W+V+XNG1dRuaFbNd3vT9CLbr2LXjEoq+3vn8DanWf7XU22Ug==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", @@ -4020,16 +4024,16 @@ } }, "node_modules/eslint-plugin-vuetify": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-vuetify/-/eslint-plugin-vuetify-2.4.0.tgz", - "integrity": "sha512-WAZjnGXPrxqHBzYjxxUT8jf30O69Hitmj+wYhTIEG/XgqfvnPwqVtqrU2FGLsDtfFskKva0vuZemfbiq8yA/fQ==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-vuetify/-/eslint-plugin-vuetify-2.5.1.tgz", + "integrity": "sha512-iTyPkTC7wOP2nlBPXevqbeTIDHQ+btt+Tt8abowMEiDZcFSdUjBCcggJgMF1pLcpWwFpbfOcnqFLf73g5WM2qA==", "dev": true, "dependencies": { "eslint-plugin-vue": "^9.6.0", "requireindex": "^1.2.0" }, "peerDependencies": { - "eslint": "^8.0.0", + "eslint": "^8.0.0 || ^9.0.0", "vuetify": "^3.0.0" } }, @@ -4563,8 +4567,7 @@ "node_modules/immutable": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.0.2.tgz", - "integrity": "sha512-1NU7hWZDkV7hJ4PJ9dur9gTNQ4ePNPN4k9/0YhwjzykTi/+3Q5pF93YU5QoVj8BuOnhLgaY8gs0U2pj4kSYVcw==", - "dev": true + "integrity": "sha512-1NU7hWZDkV7hJ4PJ9dur9gTNQ4ePNPN4k9/0YhwjzykTi/+3Q5pF93YU5QoVj8BuOnhLgaY8gs0U2pj4kSYVcw==" }, "node_modules/import-fresh": { "version": "3.3.0", @@ -5779,9 +5782,9 @@ } }, "node_modules/picocolors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", - "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==" + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" }, "node_modules/picomatch": { "version": "2.3.1", @@ -5806,12 +5809,12 @@ } }, "node_modules/playwright": { - "version": "1.48.2", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.48.2.tgz", - "integrity": "sha512-NjYvYgp4BPmiwfe31j4gHLa3J7bD2WiBz8Lk2RoSsmX38SVIARZ18VYjxLjAcDsAhA+F4iSEXTSGgjua0rrlgQ==", + "version": "1.49.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.49.0.tgz", + "integrity": "sha512-eKpmys0UFDnfNb3vfsf8Vx2LEOtflgRebl0Im2eQQnYMA4Aqd+Zw8bEOB+7ZKvN76901mRnqdsiOGKxzVTbi7A==", "dev": true, "dependencies": { - "playwright-core": "1.48.2" + "playwright-core": "1.49.0" }, "bin": { "playwright": "cli.js" @@ -5824,9 +5827,9 @@ } }, "node_modules/playwright-core": { - "version": "1.48.2", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.48.2.tgz", - "integrity": "sha512-sjjw+qrLFlriJo64du+EK0kJgZzoQPsabGF4lBvsid+3CNIZIYLgnMj9V6JY5VhM2Peh20DJWIVpVljLLnlawA==", + "version": "1.49.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.49.0.tgz", + "integrity": "sha512-R+3KKTQF3npy5GTiKH/T+kdhoJfJojjHESR1YEWhYuEKRVfVaxH3+4+GvXE5xyCngCxhxnykk0Vlah9v8fs3jA==", "dev": true, "bin": { "playwright-core": "cli.js" @@ -5850,9 +5853,9 @@ } }, "node_modules/postcss": { - "version": "8.4.47", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", - "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", + "version": "8.4.49", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", + "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", "funding": [ { "type": "opencollective", @@ -5869,7 +5872,7 @@ ], "dependencies": { "nanoid": "^3.3.7", - "picocolors": "^1.1.0", + "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, "engines": { @@ -5967,13 +5970,13 @@ } }, "node_modules/prosemirror-commands": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.6.0.tgz", - "integrity": "sha512-xn1U/g36OqXn2tn5nGmvnnimAj/g1pUx2ypJJIe8WkVX83WyJVC5LTARaxZa2AtQRwntu9Jc5zXs9gL9svp/mg==", + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.6.2.tgz", + "integrity": "sha512-0nDHH++qcf/BuPLYvmqZTUUsPJUCPBUXt0J1ErTcDIS369CTp773itzLGIgIXG4LJXOlwYCr44+Mh4ii6MP1QA==", "dependencies": { "prosemirror-model": "^1.0.0", "prosemirror-state": "^1.0.0", - "prosemirror-transform": "^1.0.0" + "prosemirror-transform": "^1.10.2" } }, "node_modules/prosemirror-dropcursor": { @@ -6027,10 +6030,11 @@ } }, "node_modules/prosemirror-markdown": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/prosemirror-markdown/-/prosemirror-markdown-1.13.0.tgz", - "integrity": "sha512-UziddX3ZYSYibgx8042hfGKmukq5Aljp2qoBiJRejD/8MH70siQNz5RB1TrdTPheqLMy4aCe4GYNF10/3lQS5g==", + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/prosemirror-markdown/-/prosemirror-markdown-1.13.1.tgz", + "integrity": "sha512-Sl+oMfMtAjWtlcZoj/5L/Q39MpEnVZ840Xo330WJWUvgyhNmLBLN7MsHn07s53nG/KImevWHSE6fEj4q/GihHw==", "dependencies": { + "@types/markdown-it": "^14.0.0", "markdown-it": "^14.0.0", "prosemirror-model": "^1.20.0" } @@ -6047,9 +6051,9 @@ } }, "node_modules/prosemirror-model": { - "version": "1.22.3", - "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.22.3.tgz", - "integrity": "sha512-V4XCysitErI+i0rKFILGt/xClnFJaohe/wrrlT2NSZ+zk8ggQfDH4x2wNK7Gm0Hp4CIoWizvXFP7L9KMaCuI0Q==", + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.23.0.tgz", + "integrity": "sha512-Q/fgsgl/dlOAW9ILu4OOhYWQbc7TQd4BwKH/RwmUjyVf8682Be4zj3rOYdLnYEcGzyg8LL9Q5IWYKD8tdToreQ==", "dependencies": { "orderedmap": "^2.0.0" } @@ -6083,9 +6087,9 @@ } }, "node_modules/prosemirror-tables": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/prosemirror-tables/-/prosemirror-tables-1.4.0.tgz", - "integrity": "sha512-fxryZZkQG12fSCNuZDrYx6Xvo2rLYZTbKLRd8rglOPgNJGMKIS8uvTt6gGC38m7UCu/ENnXIP9pEz5uDaPc+cA==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/prosemirror-tables/-/prosemirror-tables-1.6.1.tgz", + "integrity": "sha512-p8WRJNA96jaNQjhJolmbxTzd6M4huRE5xQ8OxjvMhQUP0Nzpo4zz6TztEiwk6aoqGBhz9lxRWR1yRZLlpQN98w==", "dependencies": { "prosemirror-keymap": "^1.1.2", "prosemirror-model": "^1.8.1", @@ -6109,17 +6113,17 @@ } }, "node_modules/prosemirror-transform": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/prosemirror-transform/-/prosemirror-transform-1.10.0.tgz", - "integrity": "sha512-9UOgFSgN6Gj2ekQH5CTDJ8Rp/fnKR2IkYfGdzzp5zQMFsS4zDllLVx/+jGcX86YlACpG7UR5fwAXiWzxqWtBTg==", + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/prosemirror-transform/-/prosemirror-transform-1.10.2.tgz", + "integrity": "sha512-2iUq0wv2iRoJO/zj5mv8uDUriOHWzXRnOTVgCzSXnktS/2iQRa3UUQwVlkBlYZFtygw6Nh1+X4mGqoYBINn5KQ==", "dependencies": { "prosemirror-model": "^1.21.0" } }, "node_modules/prosemirror-view": { - "version": "1.34.3", - "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.34.3.tgz", - "integrity": "sha512-mKZ54PrX19sSaQye+sef+YjBbNu2voNwLS1ivb6aD2IRmxRGW64HU9B644+7OfJStGLyxvOreKqEgfvXa91WIA==", + "version": "1.37.0", + "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.37.0.tgz", + "integrity": "sha512-z2nkKI1sJzyi7T47Ji/ewBPuIma1RNvQCCYVdV+MqWBV7o4Sa1n94UJCJJ1aQRF/xRkFfyqLGlGFWitIcCOtbg==", "dependencies": { "prosemirror-model": "^1.20.0", "prosemirror-state": "^1.0.0", @@ -6384,15 +6388,28 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/rxjs/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "node_modules/sass": { - "version": "1.80.7", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.80.7.tgz", - "integrity": "sha512-MVWvN0u5meytrSjsU7AWsbhoXi1sc58zADXFllfZzbsBT1GHjjar6JwBINYPRrkx/zqnQ6uqbQuHgE95O+C+eQ==", + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.82.0.tgz", + "integrity": "sha512-j4GMCTa8elGyN9A7x7bEglx0VgSpNUG4W4wNedQ33wSMdnkqQCT8HTwOaVSV4e6yQovcu/3Oc4coJP/l0xhL2Q==", "dev": true, "dependencies": { "chokidar": "^4.0.0", @@ -6409,6 +6426,363 @@ "@parcel/watcher": "^2.4.1" } }, + "node_modules/sass-embedded": { + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/sass-embedded/-/sass-embedded-1.82.0.tgz", + "integrity": "sha512-v13sRVVZtWAQLpAGTz5D8hy+oyNKRHao5tKVc/P6AMqSP+jDM8X6GkEpL0jfbu3MaN2/hAQsd4Qx14GG1u0prQ==", + "dependencies": { + "@bufbuild/protobuf": "^2.0.0", + "buffer-builder": "^0.2.0", + "colorjs.io": "^0.5.0", + "immutable": "^5.0.2", + "rxjs": "^7.4.0", + "supports-color": "^8.1.1", + "sync-child-process": "^1.0.2", + "varint": "^6.0.0" + }, + "bin": { + "sass": "dist/bin/sass.js" + }, + "engines": { + "node": ">=16.0.0" + }, + "optionalDependencies": { + "sass-embedded-android-arm": "1.82.0", + "sass-embedded-android-arm64": "1.82.0", + "sass-embedded-android-ia32": "1.82.0", + "sass-embedded-android-riscv64": "1.82.0", + "sass-embedded-android-x64": "1.82.0", + "sass-embedded-darwin-arm64": "1.82.0", + "sass-embedded-darwin-x64": "1.82.0", + "sass-embedded-linux-arm": "1.82.0", + "sass-embedded-linux-arm64": "1.82.0", + "sass-embedded-linux-ia32": "1.82.0", + "sass-embedded-linux-musl-arm": "1.82.0", + "sass-embedded-linux-musl-arm64": "1.82.0", + "sass-embedded-linux-musl-ia32": "1.82.0", + "sass-embedded-linux-musl-riscv64": "1.82.0", + "sass-embedded-linux-musl-x64": "1.82.0", + "sass-embedded-linux-riscv64": "1.82.0", + "sass-embedded-linux-x64": "1.82.0", + "sass-embedded-win32-arm64": "1.82.0", + "sass-embedded-win32-ia32": "1.82.0", + "sass-embedded-win32-x64": "1.82.0" + } + }, + "node_modules/sass-embedded-android-arm": { + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/sass-embedded-android-arm/-/sass-embedded-android-arm-1.82.0.tgz", + "integrity": "sha512-ttGMvWnA/5TYdZTjr5fWHDbb9nZgKipHKCc9zZQRF5HjUydOYWKNqmAJHQtbFWaq35kd5qn6yiE73IJN6eJ6wA==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-android-arm64": { + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/sass-embedded-android-arm64/-/sass-embedded-android-arm64-1.82.0.tgz", + "integrity": "sha512-bldHMs02QQWXsgHUZRgolNnZdMjN6XHvmUYoRkzmFq7lsvtLU6SJg2S1Wa9IZJs9jRWdTmOgA6YibSf3pROyFQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-android-ia32": { + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/sass-embedded-android-ia32/-/sass-embedded-android-ia32-1.82.0.tgz", + "integrity": "sha512-FUJOnxw8IYKuYuxxiOkk6QXle8/yQFtKjnuSAJuZ5ZpLVMcSZzLc3SWOtuEXYx5iSAfJCO075o2ZoG/pPrJ9aw==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-android-riscv64": { + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/sass-embedded-android-riscv64/-/sass-embedded-android-riscv64-1.82.0.tgz", + "integrity": "sha512-rd+vc+sxJxNnbhaubiIJmnb1b3FvC9wxCIq8spstopbO7o1uufvBBDeRoFSJaN+7oNhamzjlYGdu6aQoQNs3+A==", + "cpu": [ + "riscv64" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-android-x64": { + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/sass-embedded-android-x64/-/sass-embedded-android-x64-1.82.0.tgz", + "integrity": "sha512-EVlybGTgJ8wNLyWj8RUatPXSnmIcvCsx3EfsRfBfhGihLbn4NNpavYO9QsvZzI2XWbJqHLBCd+CvkTcDw/TaSQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-darwin-arm64": { + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/sass-embedded-darwin-arm64/-/sass-embedded-darwin-arm64-1.82.0.tgz", + "integrity": "sha512-LvdJPojjKlNGYOB0nSUR/ZtMDuAF4puspHlwK42aA/qK292bfSkMUKZPPapB2aSRwccc/ieBq5fI7n/WHrOCVw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-darwin-x64": { + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/sass-embedded-darwin-x64/-/sass-embedded-darwin-x64-1.82.0.tgz", + "integrity": "sha512-6LfnD6YmG1aBfd3ReqMOJDb6Pg2Z/hmlJB7nU+Lb3E+hCNjAZAgeUHQxU/Pm1eIqJJTU/h4ib5QP0Pt9O8yVnw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-arm": { + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-arm/-/sass-embedded-linux-arm-1.82.0.tgz", + "integrity": "sha512-ozjdC5rWzyi5Vo300I4tVZzneXOTQUiaxOr7DjtN26HuFaGAGCGmvThh2BRV4RvySg++5H9rdFu+VgyUQ5iukw==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-arm64": { + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-arm64/-/sass-embedded-linux-arm64-1.82.0.tgz", + "integrity": "sha512-590/y0HJr/JiyxaqgR7Xf9P20BIhJ+zhB/afAnVuZe/4lEfCpTyM5xMe2+sKLsqtrVyzs9Zm/M4S4ASUOPCggA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-ia32": { + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-ia32/-/sass-embedded-linux-ia32-1.82.0.tgz", + "integrity": "sha512-hpc4acZ3UTjjJ3Q/GUXqQOCSml6AFKaku0HMawra9bKyRmOpxn8V5hqgXeOWVjK2oQzCmCnJvwKoQUP+S/SIYQ==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-musl-arm": { + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-arm/-/sass-embedded-linux-musl-arm-1.82.0.tgz", + "integrity": "sha512-R5PQmY/I+GSoMtfLo8GgHkvF/q6x6y8VNM7yu/Ac1mJj86n48VFi29W1HfY2496+Q6cpAq7toobDj7YfldIdVA==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-musl-arm64": { + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-arm64/-/sass-embedded-linux-musl-arm64-1.82.0.tgz", + "integrity": "sha512-bc2MUSMv/jabnNGEyKP2jQAYZoEzTT/c633W6QoeSEWETGCuTNjaHvWWE6qSI6/UfRg1EpuV1LQA2jPMzZfv/w==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-musl-ia32": { + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-ia32/-/sass-embedded-linux-musl-ia32-1.82.0.tgz", + "integrity": "sha512-ZQKCFKm5TBcJ19UG6uUQmIKfVCJIWMb7e1a93lGeujSb9gyKF5Fb6MN3tuExoT7iFK8zU0Z9iyHqh93F58lcCw==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-musl-riscv64": { + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-riscv64/-/sass-embedded-linux-musl-riscv64-1.82.0.tgz", + "integrity": "sha512-5meSU8BHFeaT09RWfkuUrikRlC+WZcYb9To7MpfV1d9nlD7CZ2xydPExK+mj3DqRuQvTbvhMPcr7f+pHlgHINQ==", + "cpu": [ + "riscv64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-musl-x64": { + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-x64/-/sass-embedded-linux-musl-x64-1.82.0.tgz", + "integrity": "sha512-ASLAMfjWv7YEPBvEOVlb3zzHq8l4Y9Eh4x3m7B1dNauGVbO11Yng5cPCX/XbwGVf30BtE75pwqvV7oXxBtN15w==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-riscv64": { + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-riscv64/-/sass-embedded-linux-riscv64-1.82.0.tgz", + "integrity": "sha512-qWvRDXCXH3GzD8OcP0ntd8gBTK3kZyUeyXmxQDZyEtMAM4STC2Tn7+5+2JYYHlppzqWnZPFBNESvpKeOtHaBBw==", + "cpu": [ + "riscv64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-x64": { + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-x64/-/sass-embedded-linux-x64-1.82.0.tgz", + "integrity": "sha512-AmRaHqShztwfep+M4NagdGaY7fTyWGSOM3k4Z/dd7q4nZclXbALLqNJtKx8xOM7A41LHYJ9zDpIBVRkrh0PzTA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-win32-arm64": { + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/sass-embedded-win32-arm64/-/sass-embedded-win32-arm64-1.82.0.tgz", + "integrity": "sha512-zL9JDQZHXHSGAZe5DqSrR86wMHbm9QPziU4/3hoIG+99StuS74CuV42+hw/+FXXBkXMWbjKWsyF/HZt+I/wJuw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-win32-ia32": { + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/sass-embedded-win32-ia32/-/sass-embedded-win32-ia32-1.82.0.tgz", + "integrity": "sha512-xE+AzLquCkFPnnpo0NHjQdLRIhG1bVs42xIKx42aUbVLYKkBDvbBGpw6EtTscRMyvcjoOqGH5saRvSFComUQcw==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-win32-x64": { + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/sass-embedded-win32-x64/-/sass-embedded-win32-x64-1.82.0.tgz", + "integrity": "sha512-cEgfOQG5womOzzk16ReTv2dxPq5BG16LgLUold/LH9IZH86u4E/MN7Fspf4RWeEJ2EcLdew9QYSC2YWs1l98dQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, "node_modules/sass/node_modules/chokidar": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.1.tgz", @@ -6501,9 +6875,9 @@ } }, "node_modules/sortablejs": { - "version": "1.15.3", - "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.15.3.tgz", - "integrity": "sha512-zdK3/kwwAK1cJgy1rwl1YtNTbRmc8qW/+vgXf75A7NHag5of4pyI6uK86ktmQETyWRH7IGaE73uZOOBcGxgqZg==" + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.15.6.tgz", + "integrity": "sha512-aNfiuwMEpfBM/CN6LY0ibyhxPfPbyFeBTYJKCvzkJ2GkUpazIt3H+QIPAMHwqQ7tMKaHz1Qj+rJJCqljnf4p3A==" }, "node_modules/source-map": { "version": "0.6.1", @@ -6766,6 +7140,25 @@ "vue": ">=3.2.26 < 4" } }, + "node_modules/sync-child-process": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/sync-child-process/-/sync-child-process-1.0.2.tgz", + "integrity": "sha512-8lD+t2KrrScJ/7KXCSyfhT3/hRq78rC0wBFqNJXv3mZyn6hW2ypM05JmlSvtqRbeq6jqA94oHbxAr2vYsJ8vDA==", + "dependencies": { + "sync-message-port": "^1.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/sync-message-port": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sync-message-port/-/sync-message-port-1.1.3.tgz", + "integrity": "sha512-GTt8rSKje5FilG+wEdfCkOcLL7LWqpMlr2c3LRuKt/YXxcJ52aGSbGBAdI4L3aaqfrBt6y711El53ItyH1NWzg==", + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -7014,6 +7407,11 @@ "node": ">=10" } }, + "node_modules/varint": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/varint/-/varint-6.0.0.tgz", + "integrity": "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==" + }, "node_modules/vite": { "version": "5.4.11", "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.11.tgz", @@ -7620,15 +8018,15 @@ } }, "node_modules/vue": { - "version": "3.5.12", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.12.tgz", - "integrity": "sha512-CLVZtXtn2ItBIi/zHZ0Sg1Xkb7+PU32bJJ8Bmy7ts3jxXTcbfsEfBivFYYWz1Hur+lalqGAh65Coin0r+HRUfg==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.13.tgz", + "integrity": "sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==", "dependencies": { - "@vue/compiler-dom": "3.5.12", - "@vue/compiler-sfc": "3.5.12", - "@vue/runtime-dom": "3.5.12", - "@vue/server-renderer": "3.5.12", - "@vue/shared": "3.5.12" + "@vue/compiler-dom": "3.5.13", + "@vue/compiler-sfc": "3.5.13", + "@vue/runtime-dom": "3.5.13", + "@vue/server-renderer": "3.5.13", + "@vue/shared": "3.5.13" }, "peerDependencies": { "typescript": "*" @@ -7670,9 +8068,9 @@ } }, "node_modules/vue-router": { - "version": "4.4.5", - "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.4.5.tgz", - "integrity": "sha512-4fKZygS8cH1yCyuabAXGUAsyi1b2/o/OKgu/RUb+znIYOxPRxdkytJEx+0wGcpBE1pX6vUgh5jwWOKRGvuA/7Q==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.5.0.tgz", + "integrity": "sha512-HDuk+PuH5monfNuY+ct49mNmkCRK4xJAV9Ts4z9UFc4rzdDnxQLyCMGGc8pKhZhHTVzfanpNwB/lwqevcBwI4w==", "dependencies": { "@vue/devtools-api": "^6.6.4" }, @@ -7754,9 +8152,9 @@ "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==" }, "node_modules/vuetify": { - "version": "3.7.3", - "resolved": "https://registry.npmjs.org/vuetify/-/vuetify-3.7.3.tgz", - "integrity": "sha512-bpuvBpZl1/+nLlXDgdVXekvMNR6W/ciaoa8CYlpeAzAARbY8zUFSoBq05JlLhkIHI58AnzKVy4c09d0OtfYAPg==", + "version": "3.7.5", + "resolved": "https://registry.npmjs.org/vuetify/-/vuetify-3.7.5.tgz", + "integrity": "sha512-5aiSz8WJyGzYe3yfgDbzxsFATwHvKtdvFAaUJEDTx7xRv55s3YiOho/MFhs5iTbmh2VT4ToRgP0imBUP660UOw==", "engines": { "node": "^12.20 || >=14.13" }, @@ -7782,26 +8180,6 @@ } } }, - "node_modules/vuetify3-resize-drawer": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/vuetify3-resize-drawer/-/vuetify3-resize-drawer-2.1.1.tgz", - "integrity": "sha512-CvYce3NAjiALTcK9JClrXeygXY3rZiE3kaNvvVD/JJgHSA6i9S2yxkgr6ryNJLnmtCB5DGtmHY5sHxbF5y534Q==", - "deprecated": "The Vuetify 3 Resize Drawer component has been changed and moved to the WebDevNerdStuff org @wdns. Please update your packages to it's new location. https://www.npmjs.com/package/@wdns/vuetify-resize-drawer", - "funding": [ - { - "type": "paypal", - "url": "https://paypal.me/webdevnerdstuff" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/WebDevNerdStuff" - } - ], - "dependencies": { - "vue": "^3.3.4", - "vuetify": "^3.3.19" - } - }, "node_modules/vuex": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/vuex/-/vuex-4.1.0.tgz", diff --git a/src/dispatch/static/dispatch/package.json b/src/dispatch/static/dispatch/package.json index d2757306b455..9dd2885a78c6 100644 --- a/src/dispatch/static/dispatch/package.json +++ b/src/dispatch/static/dispatch/package.json @@ -51,6 +51,7 @@ "@vue-flow/minimap": "^1.2.0", "@vueuse/core": "^10.5.0", "@vueuse/integrations": "^10.6.1", + "@wdns/vuetify-resize-drawer": "^3.2.0", "apexcharts": "^3.44.0", "axios": "^0.21.4", "d3-force": "^3.0.0", @@ -68,6 +69,7 @@ "monaco-editor": "0.43.0", "register-service-worker": "^1.7.2", "roboto-fontface": "^0.10.0", + "sass-embedded": "^1.81.0", "sortablejs": "^1.15.0", "swrv": "^1.0.4", "vue": "^3.4.12", @@ -75,7 +77,6 @@ "vue3-apexcharts": "^1.4.4", "vue3-markdown-it": "^1.0.10", "vuetify": "^3.4.3", - "vuetify3-resize-drawer": "^2.1.1", "vuex": "^4.1.0", "vuex-map-fields": "^1.4.1" }, diff --git a/src/dispatch/static/dispatch/src/auth/Mfa.vue b/src/dispatch/static/dispatch/src/auth/Mfa.vue index 50f83ca7ec87..f77461f885b1 100644 --- a/src/dispatch/static/dispatch/src/auth/Mfa.vue +++ b/src/dispatch/static/dispatch/src/auth/Mfa.vue @@ -15,7 +15,7 @@ color="primary" size="64" class="mb-4" - > + /> { + this.incidentProject = response.data.items[0] + this.project = response.data.items[0] + }) } }, } diff --git a/src/dispatch/static/dispatch/src/incident/EditSheet.vue b/src/dispatch/static/dispatch/src/incident/EditSheet.vue index ec7239ec69f1..193d89d41c56 100644 --- a/src/dispatch/static/dispatch/src/incident/EditSheet.vue +++ b/src/dispatch/static/dispatch/src/incident/EditSheet.vue @@ -2,6 +2,8 @@ - +
+ + +

Incident Summary

+
+ +

+ This is an AI-generated summary. Please verify the information before relying on it. +

+
+ +
+ + + + Regenerate + +
+ + +

Tactical Reports

+
+
+
+ mdi-alert-circle-outline +

No tactical reports have been created yet.

+
+ (times in UTC) @@ -47,6 +74,7 @@ diff --git a/src/dispatch/static/dispatch/src/incident/api.js b/src/dispatch/static/dispatch/src/incident/api.js index 31f40c500b72..9d1d31089c15 100644 --- a/src/dispatch/static/dispatch/src/incident/api.js +++ b/src/dispatch/static/dispatch/src/incident/api.js @@ -78,4 +78,8 @@ export default { exportTimeline(incidentId, timeline_filters) { return API.post(`/${resource}/${incidentId}/exportTimeline`, timeline_filters) }, + + regenerate(incidentId) { + return API.get(`/${resource}/${incidentId}/regenerate`) + }, } diff --git a/src/dispatch/static/dispatch/src/incident/store.js b/src/dispatch/static/dispatch/src/incident/store.js index 29db4766619d..8b48cd65e8d2 100644 --- a/src/dispatch/static/dispatch/src/incident/store.js +++ b/src/dispatch/static/dispatch/src/incident/store.js @@ -38,6 +38,7 @@ const getDefaultSelectedState = () => { stable_at: null, status: null, storage: null, + summary: null, tags: [], tasks: [], terms: [], @@ -585,6 +586,19 @@ const actions = { ) }) }, + regenerateSummary({ commit }, incidentId) { + IncidentApi.regenerate(incidentId, {}).then((response) => { + commit("SET_SELECTED_SUMMARY", response.data) + commit( + "notification_backend/addBeNotification", + { + text: "Summary has been successfully regenerated.", + type: "success", + }, + { root: true } + ) + }) + }, getEnabledPlugins() { if (!state.selected.project) { return false @@ -680,6 +694,9 @@ const mutations = { SET_CURRENT_USER_ROLE(state, value) { state.current_user_role = value }, + SET_SELECTED_SUMMARY(state, value) { + state.selected.summary = value + }, } export default { diff --git a/src/dispatch/static/dispatch/src/incident/type/IncidentTypeSelect.vue b/src/dispatch/static/dispatch/src/incident/type/IncidentTypeSelect.vue index 0182b9d0d27c..992e3012ce7c 100644 --- a/src/dispatch/static/dispatch/src/incident/type/IncidentTypeSelect.vue +++ b/src/dispatch/static/dispatch/src/incident/type/IncidentTypeSelect.vue @@ -120,12 +120,15 @@ export default { sortBy: ["name"], descending: [false], itemsPerPage: this.numItems, + enabled: ["true"], } if (this.project) { - filterOptions.filters = { - project: [this.project], - enabled: ["true"], + filterOptions = { + ...filterOptions, + filters: { + project: [this.project], + }, } } diff --git a/src/dispatch/static/dispatch/src/main.js b/src/dispatch/static/dispatch/src/main.js index cb2448d2fccc..231f02c376d6 100644 --- a/src/dispatch/static/dispatch/src/main.js +++ b/src/dispatch/static/dispatch/src/main.js @@ -10,7 +10,7 @@ import "@formkit/themes/genesis" // : null import { plugin, defaultConfig } from "@formkit/vue" -import VResizeDrawer from "vuetify3-resize-drawer" +import VResizeDrawer from "@wdns/vuetify-resize-drawer" import "roboto-fontface/css/roboto/roboto-fontface.css" import "font-awesome/css/font-awesome.css" diff --git a/src/dispatch/static/dispatch/src/plugin/store.js b/src/dispatch/static/dispatch/src/plugin/store.js index ccf54148c07c..d18360600f82 100644 --- a/src/dispatch/static/dispatch/src/plugin/store.js +++ b/src/dispatch/static/dispatch/src/plugin/store.js @@ -183,6 +183,22 @@ function convertToFormkit(json_schema) { help: value.description, }, } + } else if (value.allOf) { + const ref = value.allOf[0].$ref + // will be something like "#/definitions/HostingType" + const ref_name = ref.split("/").pop() + const ref_obj = json_schema.definitions[ref_name]["enum"] + obj = { + $formkit: "select", + name: key, + label: value.title, + help: value.description, + options: ref_obj.map((item) => { + return { label: item, value: item } + }), + default: value.default, + validation: "required", + } } formkit_schema.push(obj) } diff --git a/src/dispatch/static/dispatch/src/service/ServiceSelect.vue b/src/dispatch/static/dispatch/src/service/ServiceSelect.vue index dd180aab9a29..820ac94c4efc 100644 --- a/src/dispatch/static/dispatch/src/service/ServiceSelect.vue +++ b/src/dispatch/static/dispatch/src/service/ServiceSelect.vue @@ -113,14 +113,14 @@ export default { } if (this.project) { - filterOptions.filters.project_id = this.project.map((p) => p.id) + filterOptions.filters.project_id = this.project.id } if (this.healthMetrics) { filterOptions.filters.health_metrics = ["true"] } - filterOptions = SearchUtils.createParametersFromTableOptions({ ...filterOptions }) + filterOptions = SearchUtils.createParametersFromTableOptions({ ...filterOptions }, "Service") ServiceApi.getAll(filterOptions).then((response) => { this.items = response.data.items diff --git a/src/dispatch/static/dispatch/src/styles/timeline.css b/src/dispatch/static/dispatch/src/styles/timeline.css index 4085f0061766..d0dfde2de53d 100644 --- a/src/dispatch/static/dispatch/src/styles/timeline.css +++ b/src/dispatch/static/dispatch/src/styles/timeline.css @@ -97,4 +97,26 @@ :deep(.v-timeline-item__body) { justify-self: initial !important; -} \ No newline at end of file +} + +.summary-paragraph > * { + margin: 10px; + text-align: justify; +} + +.summary-paragraph > *:last-child { + margin-bottom: 0px; +} + +.summary-paragraph > *:first-child { + margin-top: 0px; +} + +.caveat-text { + font-size: 0.875rem; + color: #555; + margin-left: 20px; + margin-top: 0px; + margin-bottom: 0px; + font-style: italic; +} diff --git a/src/dispatch/static/dispatch/vite.config.js b/src/dispatch/static/dispatch/vite.config.js index cec4a9c31111..0e5c12678c1d 100644 --- a/src/dispatch/static/dispatch/vite.config.js +++ b/src/dispatch/static/dispatch/vite.config.js @@ -30,6 +30,13 @@ export default defineConfig({ }, }, ], + css: { + preprocessorOptions: { + scss: { + api: "modern", + }, + }, + }, server: { port: 8080, proxy: { diff --git a/src/dispatch/task/service.py b/src/dispatch/task/service.py index cb2d43b6babf..a93201ab2d65 100644 --- a/src/dispatch/task/service.py +++ b/src/dispatch/task/service.py @@ -79,6 +79,12 @@ def create(*, db_session, task_in: TaskCreate) -> Task: db_session=db_session, incident_id=incident.id, email=i.individual.email ) + assignee = incident_flows.incident_add_or_reactivate_participant_flow( + db_session=db_session, + incident_id=incident.id, + user_email=i.individual.email, + ) + if not participant or not participant.active_roles: # send emphemeral message to user about why they are being added to the incident send_task_add_ephemeral_message( @@ -88,12 +94,6 @@ def create(*, db_session, task_in: TaskCreate) -> Task: task=task_in, ) - assignee = incident_flows.incident_add_or_reactivate_participant_flow( - db_session=db_session, - incident_id=incident.id, - user_email=i.individual.email, - ) - # due to the freeform nature of task assignment, we can sometimes pick up other emails # e.g. a google group that we cannot resolve to an individual assignee if assignee: diff --git a/tests/static/e2e/incidents-table.spec.ts b/tests/static/e2e/incidents-table.spec.ts index 0dcd72196cbe..eb21aabf8339 100644 --- a/tests/static/e2e/incidents-table.spec.ts +++ b/tests/static/e2e/incidents-table.spec.ts @@ -5,7 +5,7 @@ test.describe("Authenticated Dispatch App", () => { test.beforeEach(async ({ authPage }) => { await register(authPage) }), - test("The edit list should appear after clicking the incident edit kebab.", async ({ + test("The edit list should appear after clicking the incident edit kebab.", async ({ page, incidentsPage, }) => { await incidentsPage.goto() diff --git a/tests/static/e2e/pages/auth-page.ts b/tests/static/e2e/pages/auth-page.ts index 42cb4bed845e..1fc7d1971bf9 100644 --- a/tests/static/e2e/pages/auth-page.ts +++ b/tests/static/e2e/pages/auth-page.ts @@ -52,6 +52,8 @@ export class AuthPage { } async registerNewUser(email: string, password: string) { + // wait for 2 minutes to let server settle + await new Promise(resolve => setTimeout(resolve, 120000)); await this.gotoRegisterWithLink() await this.emailLabel.first().click() await this.emailLabel.fill(email)