From f1dc058bdb8284f38d875f20451e1b270b81318b Mon Sep 17 00:00:00 2001
From: jakub-tldr <78603704+jakub-tldr@users.noreply.github.com>
Date: Wed, 14 Jan 2026 19:09:36 +0100
Subject: [PATCH 01/12] Clickable modal
---
web/messages/en/modal.json | 4 ++
.../SettingsActivityStreamingTab.tsx | 45 +++++++++++++
.../AddLogStreamingModal.tsx | 67 +++++++++++++++++++
.../modals/AddDestinationModal/style.scss | 0
.../useAddDestinationModal.tsx | 31 +++++++++
.../style.scss | 5 ++
.../SettingsIndexPage/SettingsIndexPage.tsx | 5 +-
web/src/pages/settings/shared/consts.ts | 7 ++
web/src/shared/defguard-ui | 2 +-
.../shared/hooks/modalControls/modalTypes.ts | 4 ++
10 files changed, 166 insertions(+), 4 deletions(-)
create mode 100644 web/src/pages/settings/SettingsActivityLogStreamingPage/SettingsActivityStreamingTab.tsx
create mode 100644 web/src/pages/settings/SettingsActivityLogStreamingPage/modals/AddDestinationModal/AddLogStreamingModal.tsx
create mode 100644 web/src/pages/settings/SettingsActivityLogStreamingPage/modals/AddDestinationModal/style.scss
create mode 100644 web/src/pages/settings/SettingsActivityLogStreamingPage/modals/AddDestinationModal/useAddDestinationModal.tsx
create mode 100644 web/src/pages/settings/SettingsActivityLogStreamingPage/style.scss
diff --git a/web/messages/en/modal.json b/web/messages/en/modal.json
index 21237ecf70..56864af12d 100644
--- a/web/messages/en/modal.json
+++ b/web/messages/en/modal.json
@@ -74,12 +74,16 @@
"modal_add_api_token_copy_warning": "Please copy and save the API token below now. You won't be able to see it again.",
"modal_rename_api_title": "Rename API token",
"modal_add_user_title": "Add new user",
+ "modal_add_vector_destination_title": "Add Vector destination",
+ "modal_add_logstash_destination_title": "Add Logstash destination",
"modal_add_user_groups_title": "Assign user to group(s)",
"modal_add_user_enroll_title": "Start enrollment for user",
"modal_add_user_choice_enroll_title": "Add user with self-enrollment option",
"modal_add_user_choice_enroll_content": "User will be able to configure their account during the setup of their desktop client (e.g., set their own password, etc.).",
"modal_add_user_choice_manual_title": "Add user manually",
"modal_add_user_choice_manual_content": "The admin creates the user account by providing the details and assigning a password on their behalf.",
+ "modal_add_logstash_destination": "Logstash is an activity log provider that collects, processes, and streams event data from multiple sources in real time.",
+ "modal_add_vector_destination": "Vector is a high-performance observability data pipeline that collects, transforms, and routes logs, metrics, and events.",
"modal_add_user_form_enable_enroll_label": "Enable user secure remote self-enrollment.",
"modal_add_user_form_enable_enroll_help": "By enabling this option, the user will be able to configure their account during the setup of their desktop client (e.g., set their own password, etc.). ",
"modal_add_user_submit": "Add user",
diff --git a/web/src/pages/settings/SettingsActivityLogStreamingPage/SettingsActivityStreamingTab.tsx b/web/src/pages/settings/SettingsActivityLogStreamingPage/SettingsActivityStreamingTab.tsx
new file mode 100644
index 0000000000..45b96cecd3
--- /dev/null
+++ b/web/src/pages/settings/SettingsActivityLogStreamingPage/SettingsActivityStreamingTab.tsx
@@ -0,0 +1,45 @@
+import { useMemo } from 'react';
+import { SettingsHeader } from '../../../shared/components/SettingsHeader/SettingsHeader';
+import { SettingsLayout } from '../../../shared/components/SettingsLayout/SettingsLayout';
+import { EmptyState } from '../../../shared/defguard-ui/components/EmptyState/EmptyState';
+import { businessPlanBadgeProps } from '../shared/consts';
+import './style.scss';
+import type { ButtonProps } from '../../../shared/defguard-ui/components/Button/types';
+import { openModal } from '../../../shared/hooks/modalControls/modalsSubjects';
+import { ModalName } from '../../../shared/hooks/modalControls/modalTypes';
+import { AddLogStreamingModal } from './modals/AddDestinationModal/AddLogStreamingModal';
+
+export const SettingsActivityLogStreamingPage = () => {
+ const isEmpty = false;
+
+ const addButtonProps = useMemo(
+ (): ButtonProps => ({
+ text: 'Add log streaming',
+ iconLeft: 'file-add',
+ testId: 'add-activity-stream',
+ onClick: () => {
+ openModal(ModalName.AddLogStreaming);
+ },
+ }),
+ [],
+ );
+
+ return (
+
+
+
+
+
+ );
+};
diff --git a/web/src/pages/settings/SettingsActivityLogStreamingPage/modals/AddDestinationModal/AddLogStreamingModal.tsx b/web/src/pages/settings/SettingsActivityLogStreamingPage/modals/AddDestinationModal/AddLogStreamingModal.tsx
new file mode 100644
index 0000000000..4972f68e05
--- /dev/null
+++ b/web/src/pages/settings/SettingsActivityLogStreamingPage/modals/AddDestinationModal/AddLogStreamingModal.tsx
@@ -0,0 +1,67 @@
+import { ModalName } from '../../../../../shared/hooks/modalControls/modalTypes';
+import { useEffect, useState } from 'react';
+import {
+ subscribeCloseModal,
+ subscribeOpenModal,
+} from '../../../../../shared/hooks/modalControls/modalsSubjects';
+import { Modal } from '../../../../../shared/defguard-ui/components/Modal/Modal';
+import { SectionSelect } from '../../../../../shared/defguard-ui/components/SectionSelect/SectionSelect';
+import { useAddDestinationModal } from './useAddDestinationModal';
+import { SizedBox } from '../../../../../shared/defguard-ui/components/SizedBox/SizedBox';
+import { ThemeSpacing } from '../../../../../shared/defguard-ui/types';
+import { m } from '../../../../../paraglide/messages';
+
+const modalNameValue = ModalName.AddLogStreaming;
+
+export const AddLogStreamingModal = () => {
+ const [isOpen, setOpen] = useState(false);
+
+ useEffect(() => {
+ const openSub = subscribeOpenModal(modalNameValue, () => {
+ setOpen(true);
+ });
+ const closeSub = subscribeCloseModal(modalNameValue, () => setOpen(false));
+ return () => {
+ openSub.unsubscribe();
+ closeSub.unsubscribe();
+ };
+ }, []);
+
+ return (
+ setOpen(false)}>
+
+
+ );
+};
+
+const ModalContent = () => {
+ return (
+ <>
+ {
+ // useAddUserModal.setState({
+ // step: 'user',
+ // enrollUser: false,
+ // });
+ useAddDestinationModal.setState({
+ destination: 'logstash',
+ });
+ }}
+ />
+
+ {
+ //todo
+ }}
+ />
+ >
+ );
+};
diff --git a/web/src/pages/settings/SettingsActivityLogStreamingPage/modals/AddDestinationModal/style.scss b/web/src/pages/settings/SettingsActivityLogStreamingPage/modals/AddDestinationModal/style.scss
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/web/src/pages/settings/SettingsActivityLogStreamingPage/modals/AddDestinationModal/useAddDestinationModal.tsx b/web/src/pages/settings/SettingsActivityLogStreamingPage/modals/AddDestinationModal/useAddDestinationModal.tsx
new file mode 100644
index 0000000000..a745b93592
--- /dev/null
+++ b/web/src/pages/settings/SettingsActivityLogStreamingPage/modals/AddDestinationModal/useAddDestinationModal.tsx
@@ -0,0 +1,31 @@
+import { create } from 'zustand';
+
+interface StoreValues {
+ step: 'choice' | 'destination';
+ destination?: 'logstash' | 'vector';
+ isOpen: boolean;
+ name: string;
+ url: string;
+ username?: string;
+ password?: string;
+ certificate?: string;
+}
+
+const defaults: StoreValues = {
+ isOpen: false,
+ step: 'choice',
+ name: '',
+ url: '',
+ username: '',
+ password: '',
+ certificate: '',
+};
+
+interface Store extends StoreValues {
+ reset: () => void;
+}
+
+export const useAddDestinationModal = create((set) => ({
+ ...defaults,
+ reset: () => set(defaults),
+}));
diff --git a/web/src/pages/settings/SettingsActivityLogStreamingPage/style.scss b/web/src/pages/settings/SettingsActivityLogStreamingPage/style.scss
new file mode 100644
index 0000000000..664b1516ff
--- /dev/null
+++ b/web/src/pages/settings/SettingsActivityLogStreamingPage/style.scss
@@ -0,0 +1,5 @@
+#settings-activity-log-streaming-tab {
+ .empty-state {
+ margin-top: 120px;
+ }
+}
diff --git a/web/src/pages/settings/SettingsIndexPage/SettingsIndexPage.tsx b/web/src/pages/settings/SettingsIndexPage/SettingsIndexPage.tsx
index 014e22a31a..6c1d64b3a8 100644
--- a/web/src/pages/settings/SettingsIndexPage/SettingsIndexPage.tsx
+++ b/web/src/pages/settings/SettingsIndexPage/SettingsIndexPage.tsx
@@ -10,14 +10,13 @@ import { SettingsGeneralTab } from './tabs/SettingsGeneralTab';
import { SettingsLicenseTab } from './tabs/SettingsLicenseTab/SettingsLicenseTab';
import { SettingsNotificationsTab } from './tabs/SettingsNotificationsTab';
import { type SettingsTabValue, settingsTabsSchema } from './types';
-
-const ActivityTab = () => null;
+import { SettingsActivityLogStreamingPage } from '../SettingsActivityLogStreamingPage/SettingsActivityStreamingTab';
const tabComponent: Record = {
general: ,
notifications: ,
openid: ,
- activity: ,
+ activity: ,
license: ,
};
diff --git a/web/src/pages/settings/shared/consts.ts b/web/src/pages/settings/shared/consts.ts
index 2c910c5ce3..b6098b1a27 100644
--- a/web/src/pages/settings/shared/consts.ts
+++ b/web/src/pages/settings/shared/consts.ts
@@ -6,3 +6,10 @@ export const higherPlanBadgeProps: BadgeProps = {
showIcon: true,
variant: 'warning',
};
+
+export const businessPlanBadgeProps: BadgeProps = {
+ text: 'Business feature',
+ icon: 'status-premium',
+ showIcon: true,
+ variant: 'additional',
+};
diff --git a/web/src/shared/defguard-ui b/web/src/shared/defguard-ui
index 64bbbc159b..fa1f515221 160000
--- a/web/src/shared/defguard-ui
+++ b/web/src/shared/defguard-ui
@@ -1 +1 @@
-Subproject commit 64bbbc159b9e8c4bbb4ff7b4f08f40690708a1fb
+Subproject commit fa1f515221717eb816b4df67cfaaef73cd02afa9
diff --git a/web/src/shared/hooks/modalControls/modalTypes.ts b/web/src/shared/hooks/modalControls/modalTypes.ts
index 815d4a3e17..6472cd3c43 100644
--- a/web/src/shared/hooks/modalControls/modalTypes.ts
+++ b/web/src/shared/hooks/modalControls/modalTypes.ts
@@ -45,6 +45,7 @@ export const ModalName = {
NetworkDeviceConfig: 'networkDeviceConfig',
NetworkDeviceToken: 'networkDeviceToken',
AddLocation: 'addLocation',
+ AddLogStreaming: 'addLogStreaming',
} as const;
export type ModalNameValue = (typeof ModalName)[keyof typeof ModalName];
@@ -138,6 +139,9 @@ const modalOpenArgsSchema = z.discriminatedUnion('name', [
z.object({
name: z.literal(ModalName.AddLocation),
}),
+ z.object({
+ name: z.literal(ModalName.AddLogStreaming),
+ }),
z.object({
name: z.literal(ModalName.SendTestMail),
}),
From 0db42868bc4ea8b064097d5f54fbc3ebe9abd462 Mon Sep 17 00:00:00 2001
From: jakub-tldr <78603704+jakub-tldr@users.noreply.github.com>
Date: Wed, 14 Jan 2026 20:15:10 +0100
Subject: [PATCH 02/12] creating log stream
---
web/messages/en/common.json | 1 +
.../SettingsActivityStreamingTab.tsx | 27 ++-
.../AddLogStreamingModal.tsx | 167 ++++++++++++++++--
.../useAddDestinationModal.tsx | 15 +-
web/src/shared/api/api.ts | 10 ++
web/src/shared/api/types.ts | 36 +++-
6 files changed, 218 insertions(+), 38 deletions(-)
diff --git a/web/messages/en/common.json b/web/messages/en/common.json
index 299403d007..95fc8c9d37 100644
--- a/web/messages/en/common.json
+++ b/web/messages/en/common.json
@@ -27,6 +27,7 @@
"controls_keep": "Keep",
"controls_submit": "Submit",
"controls_cancel": "Cancel",
+ "controls_add_destination": "Add destination",
"controls_save": "Save",
"controls_save_changes": "Save changes",
"controls_sign_in": "Sign in",
diff --git a/web/src/pages/settings/SettingsActivityLogStreamingPage/SettingsActivityStreamingTab.tsx b/web/src/pages/settings/SettingsActivityLogStreamingPage/SettingsActivityStreamingTab.tsx
index 45b96cecd3..9636d5887a 100644
--- a/web/src/pages/settings/SettingsActivityLogStreamingPage/SettingsActivityStreamingTab.tsx
+++ b/web/src/pages/settings/SettingsActivityLogStreamingPage/SettingsActivityStreamingTab.tsx
@@ -10,7 +10,7 @@ import { ModalName } from '../../../shared/hooks/modalControls/modalTypes';
import { AddLogStreamingModal } from './modals/AddDestinationModal/AddLogStreamingModal';
export const SettingsActivityLogStreamingPage = () => {
- const isEmpty = false;
+ const isEmpty = true;
const addButtonProps = useMemo(
(): ButtonProps => ({
@@ -30,15 +30,24 @@ export const SettingsActivityLogStreamingPage = () => {
badgeProps={businessPlanBadgeProps}
icon="activity"
title="Activity log streaming"
- subtitle="Monitor and export real-time activity logs from your Defguard instance. Stream events to external systems for auditing, analytics, or security monitoring."
- />
-
+ {isEmpty ? (
+
+ ) : (
+
+ )}
);
diff --git a/web/src/pages/settings/SettingsActivityLogStreamingPage/modals/AddDestinationModal/AddLogStreamingModal.tsx b/web/src/pages/settings/SettingsActivityLogStreamingPage/modals/AddDestinationModal/AddLogStreamingModal.tsx
index 4972f68e05..7045f48331 100644
--- a/web/src/pages/settings/SettingsActivityLogStreamingPage/modals/AddDestinationModal/AddLogStreamingModal.tsx
+++ b/web/src/pages/settings/SettingsActivityLogStreamingPage/modals/AddDestinationModal/AddLogStreamingModal.tsx
@@ -1,5 +1,5 @@
import { ModalName } from '../../../../../shared/hooks/modalControls/modalTypes';
-import { useEffect, useState } from 'react';
+import { useEffect, useMemo } from 'react';
import {
subscribeCloseModal,
subscribeOpenModal,
@@ -10,17 +10,42 @@ import { useAddDestinationModal } from './useAddDestinationModal';
import { SizedBox } from '../../../../../shared/defguard-ui/components/SizedBox/SizedBox';
import { ThemeSpacing } from '../../../../../shared/defguard-ui/types';
import { m } from '../../../../../paraglide/messages';
+import { useStore } from '@tanstack/react-form';
+import { useAppForm } from '../../../../../shared/form';
+import { formChangeLogic } from '../../../../../shared/formLogic';
+import z from 'zod';
+import { ModalControls } from '../../../../../shared/defguard-ui/components/ModalControls/ModalControls';
+import { Button } from '../../../../../shared/defguard-ui/components/Button/Button';
+import api from '../../../../../shared/api/api';
+import {
+ ActivityLogStreamType,
+ type CreateActivityLogStreamRequest,
+} from '../../../../../shared/api/types';
const modalNameValue = ModalName.AddLogStreaming;
export const AddLogStreamingModal = () => {
- const [isOpen, setOpen] = useState(false);
+ const isOpen = useAddDestinationModal((s) => s.isOpen);
+ const step = useAddDestinationModal((s) => s.step);
+ const destination = useAddDestinationModal((s) => s.destination);
+
+ const modalTitle = useMemo(() => {
+ if (step === 'choice') return 'Select destination';
+ switch (destination) {
+ case 'logstash':
+ return 'Add Logstash destination';
+ case 'vector':
+ return 'Add Vector destination';
+ }
+ }, [step, destination]);
useEffect(() => {
const openSub = subscribeOpenModal(modalNameValue, () => {
- setOpen(true);
+ useAddDestinationModal.setState({ isOpen: true });
+ });
+ const closeSub = subscribeCloseModal(modalNameValue, () => {
+ useAddDestinationModal.setState({ isOpen: false });
});
- const closeSub = subscribeCloseModal(modalNameValue, () => setOpen(false));
return () => {
openSub.unsubscribe();
closeSub.unsubscribe();
@@ -28,13 +53,19 @@ export const AddLogStreamingModal = () => {
}, []);
return (
- setOpen(false)}>
-
+ useAddDestinationModal.setState({ isOpen: false })}
+ afterClose={() => useAddDestinationModal.getState().reset()}
+ >
+ {step === 'choice' && }
+ {step === 'form' && }
);
};
-const ModalContent = () => {
+const ChoiceStep = () => {
return (
<>
{
content={m.modal_add_logstash_destination()}
data-testid="add-logstash"
onClick={() => {
- // useAddUserModal.setState({
- // step: 'user',
- // enrollUser: false,
- // });
useAddDestinationModal.setState({
+ step: 'form',
destination: 'logstash',
});
}}
@@ -59,9 +87,124 @@ const ModalContent = () => {
title="Vector"
data-testid="add-vector"
onClick={() => {
- //todo
+ useAddDestinationModal.setState({
+ step: 'form',
+ destination: 'vector',
+ });
}}
/>
>
);
};
+
+const FormStep = () => {
+ const destination = useAddDestinationModal((s) => s.destination);
+
+ const formSchema = useMemo(
+ () =>
+ z.object({
+ name: z.string().trim().min(1, m.form_error_required()),
+ url: z.string().trim().min(1, m.form_error_required()),
+ username: z.string().optional(),
+ password: z.string().optional(),
+ certificate: z.string().optional(),
+ }),
+ [],
+ );
+
+ type FormFields = z.infer;
+
+ const defaultValues = useMemo(
+ (): FormFields => ({
+ name: '',
+ url: '',
+ username: '',
+ password: '',
+ certificate: '',
+ }),
+ [],
+ );
+
+ const form = useAppForm({
+ defaultValues,
+ validationLogic: formChangeLogic,
+ validators: {
+ onSubmit: formSchema,
+ onChange: formSchema,
+ },
+ onSubmit: async ({ value }) => {
+ const requestData: CreateActivityLogStreamRequest = {
+ name: value.name,
+ stream_type:
+ destination === 'logstash'
+ ? ActivityLogStreamType.LogstashHttp
+ : ActivityLogStreamType.VectorHttp,
+ stream_config: {
+ url: value.url,
+ username: value.username || undefined,
+ password: value.password || undefined,
+ cert: value.certificate || undefined,
+ },
+ };
+
+ await api.activityLogStream.createStream(requestData);
+ useAddDestinationModal.setState({ isOpen: false });
+ },
+ });
+
+ return (
+ <>
+
+
+ {(field) => }
+
+
+
+
+
+ {(field) => }
+
+
+
+
+
+ {(field) => }
+
+
+
+
+
+ {(field) => }
+
+
+
+
+
+ {(field) => }
+
+
+
+
+ form.handleSubmit(),
+ }}
+ cancelProps={{
+ text: m.controls_cancel(),
+ onClick: () => {
+ useAddDestinationModal.setState({ isOpen: false });
+ },
+ }}
+ >
+ >
+ );
+};
diff --git a/web/src/pages/settings/SettingsActivityLogStreamingPage/modals/AddDestinationModal/useAddDestinationModal.tsx b/web/src/pages/settings/SettingsActivityLogStreamingPage/modals/AddDestinationModal/useAddDestinationModal.tsx
index a745b93592..197e1d859d 100644
--- a/web/src/pages/settings/SettingsActivityLogStreamingPage/modals/AddDestinationModal/useAddDestinationModal.tsx
+++ b/web/src/pages/settings/SettingsActivityLogStreamingPage/modals/AddDestinationModal/useAddDestinationModal.tsx
@@ -1,24 +1,15 @@
import { create } from 'zustand';
interface StoreValues {
- step: 'choice' | 'destination';
- destination?: 'logstash' | 'vector';
+ step: 'choice' | 'form';
+ destination: 'logstash' | 'vector';
isOpen: boolean;
- name: string;
- url: string;
- username?: string;
- password?: string;
- certificate?: string;
}
const defaults: StoreValues = {
isOpen: false,
+ destination: 'logstash',
step: 'choice',
- name: '',
- url: '',
- username: '',
- password: '',
- certificate: '',
};
interface Store extends StoreValues {
diff --git a/web/src/shared/api/api.ts b/web/src/shared/api/api.ts
index 04430a1158..7f402a3bcf 100644
--- a/web/src/shared/api/api.ts
+++ b/web/src/shared/api/api.ts
@@ -7,6 +7,7 @@ import type {
AclRule,
ActivityLogEvent,
ActivityLogRequestParams,
+ ActivityLogStream,
AddAclAliasRequest,
AddAclRuleRequest,
AddApiTokenRequest,
@@ -29,6 +30,7 @@ import type {
AvailableLocationIpResponse,
ChangeAccountActiveRequest,
ChangeWebhookStateRequest,
+ CreateActivityLogStreamRequest,
CreateGroupRequest,
DeleteApiTokenRequest,
DeleteAuthKeyRequest,
@@ -376,6 +378,14 @@ const api = {
deleteRule: (ruleId: number | string) => client.delete(`/acl/rule/${ruleId}`),
},
},
+ activityLogStream: {
+ getStreams: () => client.get('/activity_log_stream'),
+ createStream: (data: CreateActivityLogStreamRequest) =>
+ client.post('/activity_log_stream', data),
+ updateStream: (id: number, data: CreateActivityLogStreamRequest) =>
+ client.put(`/activity_log_stream/${id}`, data),
+ deleteStream: (id: number) => client.delete(`/activity_log_stream/${id}`),
+ },
getActivityLog: (data?: ActivityLogRequestParams) =>
client
.get>(`/activity_log`, {
diff --git a/web/src/shared/api/types.ts b/web/src/shared/api/types.ts
index 029e7f1385..5136e9b563 100644
--- a/web/src/shared/api/types.ts
+++ b/web/src/shared/api/types.ts
@@ -493,11 +493,10 @@ export interface NetworkLocation {
service_location_mode: LocationServiceModeValue;
}
-export interface EditNetworkLocation
- extends Omit<
- NetworkLocation,
- 'gateways' | 'connected_at' | 'id' | 'connected' | 'allowed_ips' | 'address'
- > {
+export interface EditNetworkLocation extends Omit<
+ NetworkLocation,
+ 'gateways' | 'connected_at' | 'id' | 'connected' | 'allowed_ips' | 'address'
+> {
allowed_ips: string;
address: string;
}
@@ -856,6 +855,33 @@ export interface ActivityLogEvent {
description?: string;
}
+export const ActivityLogStreamType = {
+ VectorHttp: 'vector_http',
+ LogstashHttp: 'logstash_http',
+} as const;
+
+export type ActivityLogStreamTypeValue =
+ (typeof ActivityLogStreamType)[keyof typeof ActivityLogStreamType];
+
+export interface ActivityLogStream {
+ id: number;
+ name: string;
+ stream_type: ActivityLogStreamTypeValue;
+ config: ActivityLogStreamConfig;
+}
+export interface CreateActivityLogStreamRequest {
+ name: string;
+ stream_type: ActivityLogStreamTypeValue;
+ stream_config: ActivityLogStreamConfig;
+}
+
+export interface ActivityLogStreamConfig {
+ url: string;
+ username?: string;
+ password?: string;
+ cert?: string;
+}
+
export type ActivityLogSortKey =
| 'timestamp'
| 'username'
From 66064ff016dde003e4e1a6cc773b6cca306fc443 Mon Sep 17 00:00:00 2001
From: jakub-tldr <78603704+jakub-tldr@users.noreply.github.com>
Date: Fri, 16 Jan 2026 11:59:17 +0100
Subject: [PATCH 03/12] log streaming table
---
web/src/pages/AliasesPage/AliasTable.tsx | 1 +
.../ActivityLogStreamTable.tsx | 114 ++++++++++++++++++
.../SettingsActivityStreamingTab.tsx | 23 ++--
.../style.scss | 11 ++
web/src/shared/query.ts | 6 +
5 files changed, 147 insertions(+), 8 deletions(-)
create mode 100644 web/src/pages/settings/SettingsActivityLogStreamingPage/ActivityLogStreamTable.tsx
diff --git a/web/src/pages/AliasesPage/AliasTable.tsx b/web/src/pages/AliasesPage/AliasTable.tsx
index bbfcc1af7b..ab4b97da89 100644
--- a/web/src/pages/AliasesPage/AliasTable.tsx
+++ b/web/src/pages/AliasesPage/AliasTable.tsx
@@ -177,6 +177,7 @@ export const AliasTable = ({ data: rowData }: Props) => {
enableRowSelection: false,
enableExpanding: false,
enableSorting: true,
+ columnResizeMode: 'onChange',
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(),
});
diff --git a/web/src/pages/settings/SettingsActivityLogStreamingPage/ActivityLogStreamTable.tsx b/web/src/pages/settings/SettingsActivityLogStreamingPage/ActivityLogStreamTable.tsx
new file mode 100644
index 0000000000..bca8fff689
--- /dev/null
+++ b/web/src/pages/settings/SettingsActivityLogStreamingPage/ActivityLogStreamTable.tsx
@@ -0,0 +1,114 @@
+import { useMutation } from '@tanstack/react-query';
+import {
+ createColumnHelper,
+ getCoreRowModel,
+ getSortedRowModel,
+ useReactTable,
+} from '@tanstack/react-table';
+import { useMemo } from 'react';
+import { m } from '../../../paraglide/messages';
+import api from '../../../shared/api/api';
+import type { ActivityLogStream } from '../../../shared/api/types';
+import { IconButtonMenu } from '../../../shared/defguard-ui/components/IconButtonMenu/IconButtonMenu';
+import type { MenuItemsGroup } from '../../../shared/defguard-ui/components/Menu/types';
+import { tableEditColumnSize } from '../../../shared/defguard-ui/components/table/consts';
+import { TableBody } from '../../../shared/defguard-ui/components/table/TableBody/TableBody';
+import { TableCell } from '../../../shared/defguard-ui/components/table/TableCell/TableCell';
+import { openModal } from '../../../shared/hooks/modalControls/modalsSubjects';
+import { ModalName } from '../../../shared/hooks/modalControls/modalTypes';
+
+type RowData = ActivityLogStream;
+
+const columnHelper = createColumnHelper();
+
+type Props = {
+ data: RowData[];
+};
+
+export const ActivityLogStreamTable = ({ data: rowData }: Props) => {
+ const { mutate: deleteStream } = useMutation({
+ mutationFn: api.activityLogStream.deleteStream, //TODO
+ meta: {
+ invalidate: ['activity_log_stream'],
+ },
+ });
+
+ const columns = useMemo(
+ () => [
+ columnHelper.accessor('name', {
+ header: 'Name',
+ minSize: 484,
+ enableSorting: true,
+ sortingFn: 'text',
+ cell: (info) => (
+
+ {info.getValue()}
+
+ ),
+ }),
+ columnHelper.accessor('stream_type', {
+ header: 'Destination',
+ size: 200,
+ minSize: 100,
+ cell: (info) => {
+ const value = info.getValue();
+ const displayValue = value === 'vector_http' ? 'Vector' : 'Logstash';
+ return (
+
+ {displayValue}
+
+ );
+ },
+ }),
+ columnHelper.display({
+ id: 'edit',
+ enableResizing: false,
+ header: '',
+ enableSorting: false,
+ size: tableEditColumnSize,
+ cell: (info) => {
+ const row = info.row.original;
+ const menuItems: MenuItemsGroup[] = [
+ {
+ items: [
+ {
+ text: m.controls_edit(),
+ icon: 'edit',
+ onClick: () => {
+ // TODO: edit modal
+ openModal(ModalName.AddLogStreaming);
+ },
+ },
+ {
+ text: m.controls_delete(),
+ icon: 'delete',
+ variant: 'danger',
+ onClick: () => {
+ deleteStream(row.id);
+ },
+ },
+ ],
+ },
+ ];
+ return (
+
+
+
+ );
+ },
+ }),
+ ],
+ [deleteStream],
+ );
+
+ const table = useReactTable({
+ columns,
+ data: rowData,
+ enableRowSelection: false,
+ columnResizeMode: 'onChange',
+ getCoreRowModel: getCoreRowModel(),
+ getSortedRowModel: getSortedRowModel(),
+ });
+
+ return ;
+};
diff --git a/web/src/pages/settings/SettingsActivityLogStreamingPage/SettingsActivityStreamingTab.tsx b/web/src/pages/settings/SettingsActivityLogStreamingPage/SettingsActivityStreamingTab.tsx
index 9636d5887a..6fbd6b9f32 100644
--- a/web/src/pages/settings/SettingsActivityLogStreamingPage/SettingsActivityStreamingTab.tsx
+++ b/web/src/pages/settings/SettingsActivityLogStreamingPage/SettingsActivityStreamingTab.tsx
@@ -1,3 +1,4 @@
+import { useQuery } from '@tanstack/react-query';
import { useMemo } from 'react';
import { SettingsHeader } from '../../../shared/components/SettingsHeader/SettingsHeader';
import { SettingsLayout } from '../../../shared/components/SettingsLayout/SettingsLayout';
@@ -8,9 +9,15 @@ import type { ButtonProps } from '../../../shared/defguard-ui/components/Button/
import { openModal } from '../../../shared/hooks/modalControls/modalsSubjects';
import { ModalName } from '../../../shared/hooks/modalControls/modalTypes';
import { AddLogStreamingModal } from './modals/AddDestinationModal/AddLogStreamingModal';
+import { getActivityLogStreamsQueryOptions } from '../../../shared/query';
+import { ActivityLogStreamTable } from './ActivityLogStreamTable';
+import { Button } from '../../../shared/defguard-ui/components/Button/Button';
+import { TableTop } from '../../../shared/defguard-ui/components/table/TableTop/TableTop';
export const SettingsActivityLogStreamingPage = () => {
- const isEmpty = true;
+ const { data: streams } = useQuery(getActivityLogStreamsQueryOptions);
+
+ const isEmpty = !streams || streams.length === 0;
const addButtonProps = useMemo(
(): ButtonProps => ({
@@ -30,7 +37,7 @@ export const SettingsActivityLogStreamingPage = () => {
badgeProps={businessPlanBadgeProps}
icon="activity"
title="Activity log streaming"
- subtitle="Monitor and export real-time activity logs from your Defguard instance. Stream events to external systems for auditing, analytics, or security monitoring." // TODO: add this and rest to common.json
+ subtitle="Monitor and export real-time activity logs from your Defguard instance. Stream events to external systems for auditing, analytics, or security monitoring."
/>
{isEmpty ? (
{
primaryAction={addButtonProps}
/>
) : (
-
+ <>
+
+
+
+
+ >
)}
diff --git a/web/src/pages/settings/SettingsActivityLogStreamingPage/style.scss b/web/src/pages/settings/SettingsActivityLogStreamingPage/style.scss
index 664b1516ff..32d4aba551 100644
--- a/web/src/pages/settings/SettingsActivityLogStreamingPage/style.scss
+++ b/web/src/pages/settings/SettingsActivityLogStreamingPage/style.scss
@@ -1,5 +1,16 @@
#settings-activity-log-streaming-tab {
+ width: 784px;
+
+ .table-top {
+ margin-right: 10px;
+ margin-left: -20px;
+ }
+ .table {
+ margin-left: -20px;
+ }
+
.empty-state {
margin-top: 120px;
}
}
+// temporarily
diff --git a/web/src/shared/query.ts b/web/src/shared/query.ts
index 5821ee2001..0e6a1f6d1a 100644
--- a/web/src/shared/query.ts
+++ b/web/src/shared/query.ts
@@ -149,3 +149,9 @@ export const getLicenseInfoQueryOptions = queryOptions({
queryKey: ['enterprise_info'],
select: (response) => response.data.license_info,
});
+
+export const getActivityLogStreamsQueryOptions = queryOptions({
+ queryFn: api.activityLogStream.getStreams,
+ queryKey: ['activity_log_stream'],
+ select: (resp) => resp.data,
+});
From 08e5ff2e2bd708739950f19f43c61360bd7d5fee Mon Sep 17 00:00:00 2001
From: jakub-tldr <78603704+jakub-tldr@users.noreply.github.com>
Date: Fri, 16 Jan 2026 13:21:08 +0100
Subject: [PATCH 04/12] refactor, add delete modal
---
web/messages/en/modal.json | 1 +
.../ActivityLogStreamTable.tsx | 18 +-
.../SettingsActivityStreamingTab.tsx | 4 +
.../AddLogStreamingModal.tsx | 108 +++++++----
.../modals/AddDestinationModal/style.scss | 0
.../useAddDestinationModal.tsx | 22 ---
.../DeleteLogStreamingModal.tsx | 78 ++++++++
.../EditLogStreamingModal.tsx | 182 ++++++++++++++++++
.../style.scss | 8 +-
.../shared/hooks/modalControls/modalTypes.ts | 12 +-
10 files changed, 351 insertions(+), 82 deletions(-)
delete mode 100644 web/src/pages/settings/SettingsActivityLogStreamingPage/modals/AddDestinationModal/style.scss
delete mode 100644 web/src/pages/settings/SettingsActivityLogStreamingPage/modals/AddDestinationModal/useAddDestinationModal.tsx
create mode 100644 web/src/pages/settings/SettingsActivityLogStreamingPage/modals/DeleteDestinationModal/DeleteLogStreamingModal.tsx
create mode 100644 web/src/pages/settings/SettingsActivityLogStreamingPage/modals/EditDestinationModal/EditLogStreamingModal.tsx
diff --git a/web/messages/en/modal.json b/web/messages/en/modal.json
index 56864af12d..f347185abf 100644
--- a/web/messages/en/modal.json
+++ b/web/messages/en/modal.json
@@ -5,6 +5,7 @@
"modal_delete_authorized_app_title": "Delete authorized app",
"modal_delete_authorized_app_content_1": "Are you sure you want to remove this application? If you do, you won't be able to use it to log in with defguard anymore.",
"modal_delete_authorized_app_content_2": "Please note: until you log out from this application, it will remain signed in with DefGuard.",
+ "modal_delete_logstream_destination": " Are you sure you want to delete this activity log destination? All related logs will stop being sent to this location. This action cannot be undone.",
"modal_mfa_enable_email_title": "Email MFA setup",
"modal_mfa_enable_email_verification": "Email verification",
"modal_mfa_enable_email_content": "To setup your MFA enter the code that was sent to your account email: {email}",
diff --git a/web/src/pages/settings/SettingsActivityLogStreamingPage/ActivityLogStreamTable.tsx b/web/src/pages/settings/SettingsActivityLogStreamingPage/ActivityLogStreamTable.tsx
index bca8fff689..769a091de5 100644
--- a/web/src/pages/settings/SettingsActivityLogStreamingPage/ActivityLogStreamTable.tsx
+++ b/web/src/pages/settings/SettingsActivityLogStreamingPage/ActivityLogStreamTable.tsx
@@ -1,4 +1,3 @@
-import { useMutation } from '@tanstack/react-query';
import {
createColumnHelper,
getCoreRowModel,
@@ -7,7 +6,6 @@ import {
} from '@tanstack/react-table';
import { useMemo } from 'react';
import { m } from '../../../paraglide/messages';
-import api from '../../../shared/api/api';
import type { ActivityLogStream } from '../../../shared/api/types';
import { IconButtonMenu } from '../../../shared/defguard-ui/components/IconButtonMenu/IconButtonMenu';
import type { MenuItemsGroup } from '../../../shared/defguard-ui/components/Menu/types';
@@ -26,13 +24,6 @@ type Props = {
};
export const ActivityLogStreamTable = ({ data: rowData }: Props) => {
- const { mutate: deleteStream } = useMutation({
- mutationFn: api.activityLogStream.deleteStream, //TODO
- meta: {
- invalidate: ['activity_log_stream'],
- },
- });
-
const columns = useMemo(
() => [
columnHelper.accessor('name', {
@@ -48,7 +39,7 @@ export const ActivityLogStreamTable = ({ data: rowData }: Props) => {
}),
columnHelper.accessor('stream_type', {
header: 'Destination',
- size: 200,
+ size: 220,
minSize: 100,
cell: (info) => {
const value = info.getValue();
@@ -75,8 +66,7 @@ export const ActivityLogStreamTable = ({ data: rowData }: Props) => {
text: m.controls_edit(),
icon: 'edit',
onClick: () => {
- // TODO: edit modal
- openModal(ModalName.AddLogStreaming);
+ openModal(ModalName.EditLogStreaming, row);
},
},
{
@@ -84,7 +74,7 @@ export const ActivityLogStreamTable = ({ data: rowData }: Props) => {
icon: 'delete',
variant: 'danger',
onClick: () => {
- deleteStream(row.id);
+ openModal(ModalName.DeleteLogStreaming, row);
},
},
],
@@ -98,7 +88,7 @@ export const ActivityLogStreamTable = ({ data: rowData }: Props) => {
},
}),
],
- [deleteStream],
+ [],
);
const table = useReactTable({
diff --git a/web/src/pages/settings/SettingsActivityLogStreamingPage/SettingsActivityStreamingTab.tsx b/web/src/pages/settings/SettingsActivityLogStreamingPage/SettingsActivityStreamingTab.tsx
index 6fbd6b9f32..103434d421 100644
--- a/web/src/pages/settings/SettingsActivityLogStreamingPage/SettingsActivityStreamingTab.tsx
+++ b/web/src/pages/settings/SettingsActivityLogStreamingPage/SettingsActivityStreamingTab.tsx
@@ -9,6 +9,8 @@ import type { ButtonProps } from '../../../shared/defguard-ui/components/Button/
import { openModal } from '../../../shared/hooks/modalControls/modalsSubjects';
import { ModalName } from '../../../shared/hooks/modalControls/modalTypes';
import { AddLogStreamingModal } from './modals/AddDestinationModal/AddLogStreamingModal';
+import { EditLogStreamingModal } from './modals/EditDestinationModal/EditLogStreamingModal';
+import { DeleteLogStreamingModal } from './modals/DeleteDestinationModal/DeleteLogStreamingModal';
import { getActivityLogStreamsQueryOptions } from '../../../shared/query';
import { ActivityLogStreamTable } from './ActivityLogStreamTable';
import { Button } from '../../../shared/defguard-ui/components/Button/Button';
@@ -56,6 +58,8 @@ export const SettingsActivityLogStreamingPage = () => {
>
)}
+
+
);
};
diff --git a/web/src/pages/settings/SettingsActivityLogStreamingPage/modals/AddDestinationModal/AddLogStreamingModal.tsx b/web/src/pages/settings/SettingsActivityLogStreamingPage/modals/AddDestinationModal/AddLogStreamingModal.tsx
index 7045f48331..0a3242d5f8 100644
--- a/web/src/pages/settings/SettingsActivityLogStreamingPage/modals/AddDestinationModal/AddLogStreamingModal.tsx
+++ b/web/src/pages/settings/SettingsActivityLogStreamingPage/modals/AddDestinationModal/AddLogStreamingModal.tsx
@@ -1,51 +1,58 @@
-import { ModalName } from '../../../../../shared/hooks/modalControls/modalTypes';
-import { useEffect, useMemo } from 'react';
+import { useMutation } from '@tanstack/react-query';
+import { useEffect, useMemo, useState } from 'react';
+import z from 'zod';
+import { m } from '../../../../../paraglide/messages';
+import api from '../../../../../shared/api/api';
import {
- subscribeCloseModal,
- subscribeOpenModal,
-} from '../../../../../shared/hooks/modalControls/modalsSubjects';
+ ActivityLogStreamType,
+ type CreateActivityLogStreamRequest,
+} from '../../../../../shared/api/types';
import { Modal } from '../../../../../shared/defguard-ui/components/Modal/Modal';
+import { ModalControls } from '../../../../../shared/defguard-ui/components/ModalControls/ModalControls';
import { SectionSelect } from '../../../../../shared/defguard-ui/components/SectionSelect/SectionSelect';
-import { useAddDestinationModal } from './useAddDestinationModal';
import { SizedBox } from '../../../../../shared/defguard-ui/components/SizedBox/SizedBox';
import { ThemeSpacing } from '../../../../../shared/defguard-ui/types';
-import { m } from '../../../../../paraglide/messages';
-import { useStore } from '@tanstack/react-form';
import { useAppForm } from '../../../../../shared/form';
import { formChangeLogic } from '../../../../../shared/formLogic';
-import z from 'zod';
-import { ModalControls } from '../../../../../shared/defguard-ui/components/ModalControls/ModalControls';
-import { Button } from '../../../../../shared/defguard-ui/components/Button/Button';
-import api from '../../../../../shared/api/api';
+import { ModalName } from '../../../../../shared/hooks/modalControls/modalTypes';
import {
- ActivityLogStreamType,
- type CreateActivityLogStreamRequest,
-} from '../../../../../shared/api/types';
+ subscribeCloseModal,
+ subscribeOpenModal,
+} from '../../../../../shared/hooks/modalControls/modalsSubjects';
const modalNameValue = ModalName.AddLogStreaming;
+type ModalState = {
+ step: 'choice' | 'form';
+ destination: 'logstash' | 'vector' | null;
+};
+
+const defaultState: ModalState = {
+ step: 'choice',
+ destination: null,
+};
+
export const AddLogStreamingModal = () => {
- const isOpen = useAddDestinationModal((s) => s.isOpen);
- const step = useAddDestinationModal((s) => s.step);
- const destination = useAddDestinationModal((s) => s.destination);
+ const [isOpen, setOpen] = useState(false);
+ const [modalState, setModalState] = useState(defaultState);
const modalTitle = useMemo(() => {
- if (step === 'choice') return 'Select destination';
- switch (destination) {
+ if (modalState.step === 'choice') return 'Select destination';
+ switch (modalState.destination) {
case 'logstash':
return 'Add Logstash destination';
case 'vector':
return 'Add Vector destination';
+ default:
+ return 'Add destination';
}
- }, [step, destination]);
+ }, [modalState]);
useEffect(() => {
const openSub = subscribeOpenModal(modalNameValue, () => {
- useAddDestinationModal.setState({ isOpen: true });
- });
- const closeSub = subscribeCloseModal(modalNameValue, () => {
- useAddDestinationModal.setState({ isOpen: false });
+ setOpen(true);
});
+ const closeSub = subscribeCloseModal(modalNameValue, () => setOpen(false));
return () => {
openSub.unsubscribe();
closeSub.unsubscribe();
@@ -56,16 +63,26 @@ export const AddLogStreamingModal = () => {
useAddDestinationModal.setState({ isOpen: false })}
- afterClose={() => useAddDestinationModal.getState().reset()}
+ onClose={() => setOpen(false)}
+ afterClose={() => setModalState(defaultState)}
>
- {step === 'choice' && }
- {step === 'form' && }
+ {modalState.step === 'choice' && }
+ {modalState.step === 'form' && (
+
+ )}
);
};
-const ChoiceStep = () => {
+type ChoiceStepProps = {
+ setModalState: (state: ModalState) => void;
+};
+
+const ChoiceStep = ({ setModalState }: ChoiceStepProps) => {
return (
<>
{
content={m.modal_add_logstash_destination()}
data-testid="add-logstash"
onClick={() => {
- useAddDestinationModal.setState({
+ setModalState({
step: 'form',
destination: 'logstash',
});
@@ -87,7 +104,7 @@ const ChoiceStep = () => {
title="Vector"
data-testid="add-vector"
onClick={() => {
- useAddDestinationModal.setState({
+ setModalState({
step: 'form',
destination: 'vector',
});
@@ -97,8 +114,19 @@ const ChoiceStep = () => {
);
};
-const FormStep = () => {
- const destination = useAddDestinationModal((s) => s.destination);
+type FormStepProps = {
+ destination: 'logstash' | 'vector';
+ setOpen: (open: boolean) => void;
+ setModalState: (state: ModalState) => void;
+};
+
+const FormStep = ({ destination, setOpen }: FormStepProps) => {
+ const { mutateAsync: createStream } = useMutation({
+ mutationFn: api.activityLogStream.createStream,
+ meta: {
+ invalidate: ['activity_log_stream'],
+ },
+ });
const formSchema = useMemo(
() =>
@@ -147,8 +175,8 @@ const FormStep = () => {
},
};
- await api.activityLogStream.createStream(requestData);
- useAddDestinationModal.setState({ isOpen: false });
+ await createStream(requestData);
+ setOpen(false);
},
});
@@ -169,7 +197,7 @@ const FormStep = () => {
- {(field) => }
+ {(field) => }
@@ -200,11 +228,9 @@ const FormStep = () => {
}}
cancelProps={{
text: m.controls_cancel(),
- onClick: () => {
- useAddDestinationModal.setState({ isOpen: false });
- },
+ onClick: () => setOpen(false),
}}
- >
+ />
>
);
};
diff --git a/web/src/pages/settings/SettingsActivityLogStreamingPage/modals/AddDestinationModal/style.scss b/web/src/pages/settings/SettingsActivityLogStreamingPage/modals/AddDestinationModal/style.scss
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/web/src/pages/settings/SettingsActivityLogStreamingPage/modals/AddDestinationModal/useAddDestinationModal.tsx b/web/src/pages/settings/SettingsActivityLogStreamingPage/modals/AddDestinationModal/useAddDestinationModal.tsx
deleted file mode 100644
index 197e1d859d..0000000000
--- a/web/src/pages/settings/SettingsActivityLogStreamingPage/modals/AddDestinationModal/useAddDestinationModal.tsx
+++ /dev/null
@@ -1,22 +0,0 @@
-import { create } from 'zustand';
-
-interface StoreValues {
- step: 'choice' | 'form';
- destination: 'logstash' | 'vector';
- isOpen: boolean;
-}
-
-const defaults: StoreValues = {
- isOpen: false,
- destination: 'logstash',
- step: 'choice',
-};
-
-interface Store extends StoreValues {
- reset: () => void;
-}
-
-export const useAddDestinationModal = create((set) => ({
- ...defaults,
- reset: () => set(defaults),
-}));
diff --git a/web/src/pages/settings/SettingsActivityLogStreamingPage/modals/DeleteDestinationModal/DeleteLogStreamingModal.tsx b/web/src/pages/settings/SettingsActivityLogStreamingPage/modals/DeleteDestinationModal/DeleteLogStreamingModal.tsx
new file mode 100644
index 0000000000..6dd4f15f35
--- /dev/null
+++ b/web/src/pages/settings/SettingsActivityLogStreamingPage/modals/DeleteDestinationModal/DeleteLogStreamingModal.tsx
@@ -0,0 +1,78 @@
+import { useMutation } from '@tanstack/react-query';
+import { useEffect, useState } from 'react';
+import { m } from '../../../../../paraglide/messages';
+import api from '../../../../../shared/api/api';
+import type { ActivityLogStream } from '../../../../../shared/api/types';
+import { AppText } from '../../../../../shared/defguard-ui/components/AppText/AppText';
+import { Modal } from '../../../../../shared/defguard-ui/components/Modal/Modal';
+import { ModalControls } from '../../../../../shared/defguard-ui/components/ModalControls/ModalControls';
+import { TextStyle } from '../../../../../shared/defguard-ui/types';
+import { ModalName } from '../../../../../shared/hooks/modalControls/modalTypes';
+import {
+ subscribeCloseModal,
+ subscribeOpenModal,
+} from '../../../../../shared/hooks/modalControls/modalsSubjects';
+
+const modalNameValue = ModalName.DeleteLogStreaming;
+
+type ModalData = ActivityLogStream;
+
+export const DeleteLogStreamingModal = () => {
+ const [isOpen, setOpen] = useState(false);
+ const [modalData, setModalData] = useState(null);
+
+ const { mutateAsync: deleteStream, isPending } = useMutation({
+ mutationFn: api.activityLogStream.deleteStream,
+ meta: {
+ invalidate: ['activity_log_stream'],
+ },
+ });
+
+ useEffect(() => {
+ const openSub = subscribeOpenModal(modalNameValue, (data) => {
+ setModalData(data);
+ setOpen(true);
+ });
+ const closeSub = subscribeCloseModal(modalNameValue, () => setOpen(false));
+ return () => {
+ openSub.unsubscribe();
+ closeSub.unsubscribe();
+ };
+ }, []);
+
+ const handleDelete = async () => {
+ if (!modalData) return;
+ await deleteStream(modalData.id);
+ setOpen(false);
+ };
+
+ return (
+ setOpen(false)}
+ afterClose={() => setModalData(null)}
+ >
+
+ {m.modal_delete_logstream_destination()}
+
+ setOpen(false),
+ disabled: isPending,
+ }}
+ />
+
+ );
+};
diff --git a/web/src/pages/settings/SettingsActivityLogStreamingPage/modals/EditDestinationModal/EditLogStreamingModal.tsx b/web/src/pages/settings/SettingsActivityLogStreamingPage/modals/EditDestinationModal/EditLogStreamingModal.tsx
new file mode 100644
index 0000000000..f09b76a5a9
--- /dev/null
+++ b/web/src/pages/settings/SettingsActivityLogStreamingPage/modals/EditDestinationModal/EditLogStreamingModal.tsx
@@ -0,0 +1,182 @@
+import { useMutation } from '@tanstack/react-query';
+import { useEffect, useMemo, useState } from 'react';
+import z from 'zod';
+import { m } from '../../../../../paraglide/messages';
+import api from '../../../../../shared/api/api';
+import {
+ ActivityLogStreamType,
+ type ActivityLogStream,
+ type CreateActivityLogStreamRequest,
+} from '../../../../../shared/api/types';
+import { Modal } from '../../../../../shared/defguard-ui/components/Modal/Modal';
+import { ModalControls } from '../../../../../shared/defguard-ui/components/ModalControls/ModalControls';
+import { SizedBox } from '../../../../../shared/defguard-ui/components/SizedBox/SizedBox';
+import { ThemeSpacing } from '../../../../../shared/defguard-ui/types';
+import { isPresent } from '../../../../../shared/defguard-ui/utils/isPresent';
+import { useAppForm } from '../../../../../shared/form';
+import { formChangeLogic } from '../../../../../shared/formLogic';
+import { ModalName } from '../../../../../shared/hooks/modalControls/modalTypes';
+import {
+ subscribeCloseModal,
+ subscribeOpenModal,
+} from '../../../../../shared/hooks/modalControls/modalsSubjects';
+
+const modalNameValue = ModalName.EditLogStreaming;
+
+type ModalData = ActivityLogStream;
+
+export const EditLogStreamingModal = () => {
+ const [isOpen, setOpen] = useState(false);
+ const [modalData, setModalData] = useState(null);
+
+ const modalTitle = useMemo(() => {
+ if (!modalData) return 'Edit destination';
+ switch (modalData.stream_type) {
+ case ActivityLogStreamType.LogstashHttp:
+ return 'Edit Logstash destination';
+ case ActivityLogStreamType.VectorHttp:
+ return 'Edit Vector destination';
+ }
+ }, [modalData]);
+
+ useEffect(() => {
+ const openSub = subscribeOpenModal(modalNameValue, (data) => {
+ setModalData(data);
+ setOpen(true);
+ });
+ const closeSub = subscribeCloseModal(modalNameValue, () => setOpen(false));
+ return () => {
+ openSub.unsubscribe();
+ closeSub.unsubscribe();
+ };
+ }, []);
+
+ return (
+ setOpen(false)}
+ afterClose={() => setModalData(null)}
+ >
+ {isPresent(modalData) && }
+
+ );
+};
+
+type ModalContentProps = {
+ modalData: ModalData;
+ setOpen: (open: boolean) => void;
+};
+
+const ModalContent = ({ modalData, setOpen }: ModalContentProps) => {
+ const { mutateAsync: updateStream } = useMutation({
+ mutationFn: ({ id, data }: { id: number; data: CreateActivityLogStreamRequest }) =>
+ api.activityLogStream.updateStream(id, data),
+ meta: {
+ invalidate: ['activity_log_stream'],
+ },
+ });
+
+ const formSchema = useMemo(
+ () =>
+ z.object({
+ name: z.string().trim().min(1, m.form_error_required()),
+ url: z.string().trim().min(1, m.form_error_required()),
+ username: z.string().optional(),
+ password: z.string().optional(),
+ certificate: z.string().optional(),
+ }),
+ [],
+ );
+
+ type FormFields = z.infer;
+
+ const defaultValues = useMemo(
+ (): FormFields => ({
+ name: modalData.name,
+ url: modalData.config.url,
+ username: modalData.config.username || '',
+ password: modalData.config.password || '',
+ certificate: modalData.config.cert || '',
+ }),
+ [modalData],
+ );
+
+ const form = useAppForm({
+ defaultValues,
+ validationLogic: formChangeLogic,
+ validators: {
+ onSubmit: formSchema,
+ onChange: formSchema,
+ },
+ onSubmit: async ({ value }) => {
+ const requestData: CreateActivityLogStreamRequest = {
+ name: value.name,
+ stream_type: modalData.stream_type,
+ stream_config: {
+ url: value.url,
+ username: value.username || undefined,
+ password: value.password || undefined,
+ cert: value.certificate || undefined,
+ },
+ };
+
+ await updateStream({ id: modalData.id, data: requestData });
+ setOpen(false);
+ },
+ });
+
+ return (
+ <>
+
+
+ {(field) => }
+
+
+
+
+
+ {(field) => }
+
+
+
+
+
+ {(field) => }
+
+
+
+
+
+ {(field) => }
+
+
+
+
+
+ {(field) => }
+
+
+
+
+ form.handleSubmit(),
+ }}
+ cancelProps={{
+ text: m.controls_cancel(),
+ onClick: () => setOpen(false),
+ }}
+ />
+ >
+ );
+};
diff --git a/web/src/pages/settings/SettingsActivityLogStreamingPage/style.scss b/web/src/pages/settings/SettingsActivityLogStreamingPage/style.scss
index 32d4aba551..68e3680608 100644
--- a/web/src/pages/settings/SettingsActivityLogStreamingPage/style.scss
+++ b/web/src/pages/settings/SettingsActivityLogStreamingPage/style.scss
@@ -2,15 +2,15 @@
width: 784px;
.table-top {
- margin-right: 10px;
- margin-left: -20px;
+ padding-left: 0px;
+ padding-right: 0px;
}
.table {
- margin-left: -20px;
+ padding-left: 0px;
+ padding-right: 0px;
}
.empty-state {
margin-top: 120px;
}
}
-// temporarily
diff --git a/web/src/shared/hooks/modalControls/modalTypes.ts b/web/src/shared/hooks/modalControls/modalTypes.ts
index 6472cd3c43..b341aaa89a 100644
--- a/web/src/shared/hooks/modalControls/modalTypes.ts
+++ b/web/src/shared/hooks/modalControls/modalTypes.ts
@@ -1,5 +1,5 @@
import z from 'zod';
-import type { AddDeviceResponse, User } from '../../api/types';
+import type { ActivityLogStream, AddDeviceResponse, User } from '../../api/types';
import type {
OpenAddApiTokenModal,
OpenAddNetworkDeviceModal,
@@ -46,6 +46,8 @@ export const ModalName = {
NetworkDeviceToken: 'networkDeviceToken',
AddLocation: 'addLocation',
AddLogStreaming: 'addLogStreaming',
+ EditLogStreaming: 'editLogStreaming',
+ DeleteLogStreaming: 'deleteLogStreaming',
} as const;
export type ModalNameValue = (typeof ModalName)[keyof typeof ModalName];
@@ -142,6 +144,14 @@ const modalOpenArgsSchema = z.discriminatedUnion('name', [
z.object({
name: z.literal(ModalName.AddLogStreaming),
}),
+ z.object({
+ name: z.literal(ModalName.EditLogStreaming),
+ data: z.custom(),
+ }),
+ z.object({
+ name: z.literal(ModalName.DeleteLogStreaming),
+ data: z.custom(),
+ }),
z.object({
name: z.literal(ModalName.SendTestMail),
}),
From dbe0432e2b3c2f66cee09e6f08e94f4f246e4b03 Mon Sep 17 00:00:00 2001
From: jakub-tldr <78603704+jakub-tldr@users.noreply.github.com>
Date: Fri, 16 Jan 2026 13:36:23 +0100
Subject: [PATCH 05/12] rename button
---
.../modals/EditDestinationModal/EditLogStreamingModal.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/web/src/pages/settings/SettingsActivityLogStreamingPage/modals/EditDestinationModal/EditLogStreamingModal.tsx b/web/src/pages/settings/SettingsActivityLogStreamingPage/modals/EditDestinationModal/EditLogStreamingModal.tsx
index f09b76a5a9..31087cc19f 100644
--- a/web/src/pages/settings/SettingsActivityLogStreamingPage/modals/EditDestinationModal/EditLogStreamingModal.tsx
+++ b/web/src/pages/settings/SettingsActivityLogStreamingPage/modals/EditDestinationModal/EditLogStreamingModal.tsx
@@ -168,7 +168,7 @@ const ModalContent = ({ modalData, setOpen }: ModalContentProps) => {
form.handleSubmit(),
}}
From 456fb94e09e9b26beffc7215fa0a833708fbe4bd Mon Sep 17 00:00:00 2001
From: jakub-tldr <78603704+jakub-tldr@users.noreply.github.com>
Date: Fri, 16 Jan 2026 14:20:41 +0100
Subject: [PATCH 06/12] add translation
---
web/messages/en/modal.json | 4 ++++
web/messages/en/settings.json | 12 ++++++++++++
web/project.inlang/settings.json | 7 +++----
.../ActivityLogStreamTable.tsx | 4 ++--
.../SettingsActivityStreamingTab.tsx | 15 ++++++++-------
.../AddDestinationModal/AddLogStreamingModal.tsx | 7 ++++---
.../DeleteLogStreamingModal.tsx | 2 +-
.../EditLogStreamingModal.tsx | 6 +++---
8 files changed, 37 insertions(+), 20 deletions(-)
create mode 100644 web/messages/en/settings.json
diff --git a/web/messages/en/modal.json b/web/messages/en/modal.json
index f347185abf..5a3e994537 100644
--- a/web/messages/en/modal.json
+++ b/web/messages/en/modal.json
@@ -77,6 +77,10 @@
"modal_add_user_title": "Add new user",
"modal_add_vector_destination_title": "Add Vector destination",
"modal_add_logstash_destination_title": "Add Logstash destination",
+ "modal_edit_vector_destination_title": "Edit Vector destination",
+ "modal_edit_logstash_destination_title": "Edit Logstash destination",
+ "modal_edit_log_streaming_destination_title": "Edit destination",
+ "modal_select_log_streaming_destination_title": "Select destination",
"modal_add_user_groups_title": "Assign user to group(s)",
"modal_add_user_enroll_title": "Start enrollment for user",
"modal_add_user_choice_enroll_title": "Add user with self-enrollment option",
diff --git a/web/messages/en/settings.json b/web/messages/en/settings.json
new file mode 100644
index 0000000000..d2cdd0f964
--- /dev/null
+++ b/web/messages/en/settings.json
@@ -0,0 +1,12 @@
+{
+ "$schema": "https://inlang.com/schema/inlang-message-format",
+ "settings_activity_log_streaming_title": "Activity log streaming",
+ "settings_activity_log_streaming_description": "Monitor and export real-time activity logs from your Defguard instance. Stream events to external systems for auditing, analytics, or security monitoring.",
+ "settings_activity_log_streaming_no_upstreams": "You don't have any activity log upstreams.",
+ "settings_activity_log_streaming_no_upstreams_subtitle": "Click the button below to add an activity log provider and start streaming events.",
+ "settings_activity_log_streaming_add_log_streaming_button": "Add log streaming.",
+ "settings_activity_log_streaming_delete_log_streaming_title": "Delete destination confirmation",
+ "settings_activity_log_streaming_table_title": "All log streams",
+ "settings_activity_log_streaming_table_header_name": "Name",
+ "settings_activity_log_streaming_table_stream_type_name": "Destination"
+}
diff --git a/web/project.inlang/settings.json b/web/project.inlang/settings.json
index 6206c1b159..a0d9217b71 100644
--- a/web/project.inlang/settings.json
+++ b/web/project.inlang/settings.json
@@ -1,9 +1,7 @@
{
"$schema": "https://inlang.com/schema/project-settings",
"baseLocale": "en",
- "locales": [
- "en"
- ],
+ "locales": ["en"],
"modules": [
"https://cdn.jsdelivr.net/npm/@inlang/plugin-message-format@latest/dist/index.js"
],
@@ -19,7 +17,8 @@
"./messages/{locale}/webhooks.json",
"./messages/{locale}/groups.json",
"./messages/{locale}/openid.json",
- "./messages/{locale}/activity.json"
+ "./messages/{locale}/activity.json",
+ "./messages/{locale}/settings.json"
]
}
}
diff --git a/web/src/pages/settings/SettingsActivityLogStreamingPage/ActivityLogStreamTable.tsx b/web/src/pages/settings/SettingsActivityLogStreamingPage/ActivityLogStreamTable.tsx
index 769a091de5..7aa8e85ba7 100644
--- a/web/src/pages/settings/SettingsActivityLogStreamingPage/ActivityLogStreamTable.tsx
+++ b/web/src/pages/settings/SettingsActivityLogStreamingPage/ActivityLogStreamTable.tsx
@@ -27,7 +27,7 @@ export const ActivityLogStreamTable = ({ data: rowData }: Props) => {
const columns = useMemo(
() => [
columnHelper.accessor('name', {
- header: 'Name',
+ header: m.settings_activity_log_streaming_table_header_name(),
minSize: 484,
enableSorting: true,
sortingFn: 'text',
@@ -38,7 +38,7 @@ export const ActivityLogStreamTable = ({ data: rowData }: Props) => {
),
}),
columnHelper.accessor('stream_type', {
- header: 'Destination',
+ header: m.settings_activity_log_streaming_table_stream_type_name(),
size: 220,
minSize: 100,
cell: (info) => {
diff --git a/web/src/pages/settings/SettingsActivityLogStreamingPage/SettingsActivityStreamingTab.tsx b/web/src/pages/settings/SettingsActivityLogStreamingPage/SettingsActivityStreamingTab.tsx
index 103434d421..5b0db4f141 100644
--- a/web/src/pages/settings/SettingsActivityLogStreamingPage/SettingsActivityStreamingTab.tsx
+++ b/web/src/pages/settings/SettingsActivityLogStreamingPage/SettingsActivityStreamingTab.tsx
@@ -15,6 +15,7 @@ import { getActivityLogStreamsQueryOptions } from '../../../shared/query';
import { ActivityLogStreamTable } from './ActivityLogStreamTable';
import { Button } from '../../../shared/defguard-ui/components/Button/Button';
import { TableTop } from '../../../shared/defguard-ui/components/table/TableTop/TableTop';
+import { m } from '../../../paraglide/messages';
export const SettingsActivityLogStreamingPage = () => {
const { data: streams } = useQuery(getActivityLogStreamsQueryOptions);
@@ -23,7 +24,7 @@ export const SettingsActivityLogStreamingPage = () => {
const addButtonProps = useMemo(
(): ButtonProps => ({
- text: 'Add log streaming',
+ text: m.settings_activity_log_streaming_add_log_streaming_button(),
iconLeft: 'file-add',
testId: 'add-activity-stream',
onClick: () => {
@@ -38,20 +39,20 @@ export const SettingsActivityLogStreamingPage = () => {
{isEmpty ? (
) : (
<>
-
+
diff --git a/web/src/pages/settings/SettingsActivityLogStreamingPage/modals/AddDestinationModal/AddLogStreamingModal.tsx b/web/src/pages/settings/SettingsActivityLogStreamingPage/modals/AddDestinationModal/AddLogStreamingModal.tsx
index 0a3242d5f8..41e06e6aec 100644
--- a/web/src/pages/settings/SettingsActivityLogStreamingPage/modals/AddDestinationModal/AddLogStreamingModal.tsx
+++ b/web/src/pages/settings/SettingsActivityLogStreamingPage/modals/AddDestinationModal/AddLogStreamingModal.tsx
@@ -37,12 +37,13 @@ export const AddLogStreamingModal = () => {
const [modalState, setModalState] = useState(defaultState);
const modalTitle = useMemo(() => {
- if (modalState.step === 'choice') return 'Select destination';
+ if (modalState.step === 'choice')
+ return m.modal_select_log_streaming_destination_title();
switch (modalState.destination) {
case 'logstash':
- return 'Add Logstash destination';
+ return m.modal_add_logstash_destination();
case 'vector':
- return 'Add Vector destination';
+ return m.modal_add_vector_destination();
default:
return 'Add destination';
}
diff --git a/web/src/pages/settings/SettingsActivityLogStreamingPage/modals/DeleteDestinationModal/DeleteLogStreamingModal.tsx b/web/src/pages/settings/SettingsActivityLogStreamingPage/modals/DeleteDestinationModal/DeleteLogStreamingModal.tsx
index 6dd4f15f35..68df0e5322 100644
--- a/web/src/pages/settings/SettingsActivityLogStreamingPage/modals/DeleteDestinationModal/DeleteLogStreamingModal.tsx
+++ b/web/src/pages/settings/SettingsActivityLogStreamingPage/modals/DeleteDestinationModal/DeleteLogStreamingModal.tsx
@@ -50,7 +50,7 @@ export const DeleteLogStreamingModal = () => {
setOpen(false)}
afterClose={() => setModalData(null)}
diff --git a/web/src/pages/settings/SettingsActivityLogStreamingPage/modals/EditDestinationModal/EditLogStreamingModal.tsx b/web/src/pages/settings/SettingsActivityLogStreamingPage/modals/EditDestinationModal/EditLogStreamingModal.tsx
index 31087cc19f..4903bb2eca 100644
--- a/web/src/pages/settings/SettingsActivityLogStreamingPage/modals/EditDestinationModal/EditLogStreamingModal.tsx
+++ b/web/src/pages/settings/SettingsActivityLogStreamingPage/modals/EditDestinationModal/EditLogStreamingModal.tsx
@@ -30,12 +30,12 @@ export const EditLogStreamingModal = () => {
const [modalData, setModalData] = useState(null);
const modalTitle = useMemo(() => {
- if (!modalData) return 'Edit destination';
+ if (!modalData) return m.modal_edit_log_streaming_destination_title();
switch (modalData.stream_type) {
case ActivityLogStreamType.LogstashHttp:
- return 'Edit Logstash destination';
+ return m.modal_edit_logstash_destination_title();
case ActivityLogStreamType.VectorHttp:
- return 'Edit Vector destination';
+ return m.modal_edit_vector_destination_title();
}
}, [modalData]);
From 262c676ce8e0694c5ff7d9a7d4bc4ac29de7fa03 Mon Sep 17 00:00:00 2001
From: jakub-tldr <78603704+jakub-tldr@users.noreply.github.com>
Date: Fri, 16 Jan 2026 14:22:01 +0100
Subject: [PATCH 07/12] fix typo
---
.../modals/AddDestinationModal/AddLogStreamingModal.tsx | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/web/src/pages/settings/SettingsActivityLogStreamingPage/modals/AddDestinationModal/AddLogStreamingModal.tsx b/web/src/pages/settings/SettingsActivityLogStreamingPage/modals/AddDestinationModal/AddLogStreamingModal.tsx
index 41e06e6aec..fbb1d7a363 100644
--- a/web/src/pages/settings/SettingsActivityLogStreamingPage/modals/AddDestinationModal/AddLogStreamingModal.tsx
+++ b/web/src/pages/settings/SettingsActivityLogStreamingPage/modals/AddDestinationModal/AddLogStreamingModal.tsx
@@ -41,9 +41,9 @@ export const AddLogStreamingModal = () => {
return m.modal_select_log_streaming_destination_title();
switch (modalState.destination) {
case 'logstash':
- return m.modal_add_logstash_destination();
+ return m.modal_add_logstash_destination_title();
case 'vector':
- return m.modal_add_vector_destination();
+ return m.modal_add_vector_destination_title();
default:
return 'Add destination';
}
From 59d75868605211225b2e7561c7c6288780b74903 Mon Sep 17 00:00:00 2001
From: jakub-tldr <78603704+jakub-tldr@users.noreply.github.com>
Date: Fri, 16 Jan 2026 15:06:40 +0100
Subject: [PATCH 08/12] add translation
---
web/messages/en/modal.json | 1 +
.../modals/AddDestinationModal/AddLogStreamingModal.tsx | 2 +-
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/web/messages/en/modal.json b/web/messages/en/modal.json
index 5a3e994537..b783e7fc26 100644
--- a/web/messages/en/modal.json
+++ b/web/messages/en/modal.json
@@ -81,6 +81,7 @@
"modal_edit_logstash_destination_title": "Edit Logstash destination",
"modal_edit_log_streaming_destination_title": "Edit destination",
"modal_select_log_streaming_destination_title": "Select destination",
+ "modal_add_log_streaming_destination_title": "Add destination",
"modal_add_user_groups_title": "Assign user to group(s)",
"modal_add_user_enroll_title": "Start enrollment for user",
"modal_add_user_choice_enroll_title": "Add user with self-enrollment option",
diff --git a/web/src/pages/settings/SettingsActivityLogStreamingPage/modals/AddDestinationModal/AddLogStreamingModal.tsx b/web/src/pages/settings/SettingsActivityLogStreamingPage/modals/AddDestinationModal/AddLogStreamingModal.tsx
index fbb1d7a363..9caf62e14b 100644
--- a/web/src/pages/settings/SettingsActivityLogStreamingPage/modals/AddDestinationModal/AddLogStreamingModal.tsx
+++ b/web/src/pages/settings/SettingsActivityLogStreamingPage/modals/AddDestinationModal/AddLogStreamingModal.tsx
@@ -45,7 +45,7 @@ export const AddLogStreamingModal = () => {
case 'vector':
return m.modal_add_vector_destination_title();
default:
- return 'Add destination';
+ return m.modal_add_log_streaming_destination_title();
}
}, [modalState]);
From 09c58b17b8c5ef74e427ab01523eab6f9b28cbf2 Mon Sep 17 00:00:00 2001
From: jakub-tldr <78603704+jakub-tldr@users.noreply.github.com>
Date: Mon, 19 Jan 2026 15:17:51 +0100
Subject: [PATCH 09/12] merge
---
proto | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/proto b/proto
index 161c6c6776..c48340f72b 160000
--- a/proto
+++ b/proto
@@ -1 +1 @@
-Subproject commit 161c6c677662130924e8bac0c16421b8ed085d33
+Subproject commit c48340f72b9de3a69cf71318c75ff1361ebd7897
From 20ae3ca4c8a67719356f46572b90b973072212b2 Mon Sep 17 00:00:00 2001
From: jakub-tldr <78603704+jakub-tldr@users.noreply.github.com>
Date: Mon, 19 Jan 2026 15:19:13 +0100
Subject: [PATCH 10/12] merge
---
proto | 2 +-
web/src/shared/defguard-ui | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/proto b/proto
index c48340f72b..161c6c6776 160000
--- a/proto
+++ b/proto
@@ -1 +1 @@
-Subproject commit c48340f72b9de3a69cf71318c75ff1361ebd7897
+Subproject commit 161c6c677662130924e8bac0c16421b8ed085d33
diff --git a/web/src/shared/defguard-ui b/web/src/shared/defguard-ui
index fa1f515221..86fd6dec23 160000
--- a/web/src/shared/defguard-ui
+++ b/web/src/shared/defguard-ui
@@ -1 +1 @@
-Subproject commit fa1f515221717eb816b4df67cfaaef73cd02afa9
+Subproject commit 86fd6dec2387295cb555e9ab3e472d7375ddf270
From 41ab7d99eb003883cf2d6aa20d87fe14e28750e2 Mon Sep 17 00:00:00 2001
From: jakub-tldr <78603704+jakub-tldr@users.noreply.github.com>
Date: Mon, 19 Jan 2026 15:25:19 +0100
Subject: [PATCH 11/12] linter
---
.../SettingsActivityStreamingTab.tsx | 12 ++++++------
.../AddDestinationModal/AddLogStreamingModal.tsx | 6 +++---
.../DeleteLogStreamingModal.tsx | 2 +-
.../EditDestinationModal/EditLogStreamingModal.tsx | 4 ++--
.../SettingsActivityLogStreamingPage/style.scss | 9 +++++----
.../settings/SettingsIndexPage/SettingsIndexPage.tsx | 2 +-
web/src/shared/api/types.ts | 9 +++++----
web/src/shared/defguard-ui | 2 +-
8 files changed, 24 insertions(+), 22 deletions(-)
diff --git a/web/src/pages/settings/SettingsActivityLogStreamingPage/SettingsActivityStreamingTab.tsx b/web/src/pages/settings/SettingsActivityLogStreamingPage/SettingsActivityStreamingTab.tsx
index 5b0db4f141..76d38a0147 100644
--- a/web/src/pages/settings/SettingsActivityLogStreamingPage/SettingsActivityStreamingTab.tsx
+++ b/web/src/pages/settings/SettingsActivityLogStreamingPage/SettingsActivityStreamingTab.tsx
@@ -5,17 +5,17 @@ import { SettingsLayout } from '../../../shared/components/SettingsLayout/Settin
import { EmptyState } from '../../../shared/defguard-ui/components/EmptyState/EmptyState';
import { businessPlanBadgeProps } from '../shared/consts';
import './style.scss';
+import { m } from '../../../paraglide/messages';
+import { Button } from '../../../shared/defguard-ui/components/Button/Button';
import type { ButtonProps } from '../../../shared/defguard-ui/components/Button/types';
+import { TableTop } from '../../../shared/defguard-ui/components/table/TableTop/TableTop';
import { openModal } from '../../../shared/hooks/modalControls/modalsSubjects';
import { ModalName } from '../../../shared/hooks/modalControls/modalTypes';
-import { AddLogStreamingModal } from './modals/AddDestinationModal/AddLogStreamingModal';
-import { EditLogStreamingModal } from './modals/EditDestinationModal/EditLogStreamingModal';
-import { DeleteLogStreamingModal } from './modals/DeleteDestinationModal/DeleteLogStreamingModal';
import { getActivityLogStreamsQueryOptions } from '../../../shared/query';
import { ActivityLogStreamTable } from './ActivityLogStreamTable';
-import { Button } from '../../../shared/defguard-ui/components/Button/Button';
-import { TableTop } from '../../../shared/defguard-ui/components/table/TableTop/TableTop';
-import { m } from '../../../paraglide/messages';
+import { AddLogStreamingModal } from './modals/AddDestinationModal/AddLogStreamingModal';
+import { DeleteLogStreamingModal } from './modals/DeleteDestinationModal/DeleteLogStreamingModal';
+import { EditLogStreamingModal } from './modals/EditDestinationModal/EditLogStreamingModal';
export const SettingsActivityLogStreamingPage = () => {
const { data: streams } = useQuery(getActivityLogStreamsQueryOptions);
diff --git a/web/src/pages/settings/SettingsActivityLogStreamingPage/modals/AddDestinationModal/AddLogStreamingModal.tsx b/web/src/pages/settings/SettingsActivityLogStreamingPage/modals/AddDestinationModal/AddLogStreamingModal.tsx
index 9caf62e14b..c0964d273f 100644
--- a/web/src/pages/settings/SettingsActivityLogStreamingPage/modals/AddDestinationModal/AddLogStreamingModal.tsx
+++ b/web/src/pages/settings/SettingsActivityLogStreamingPage/modals/AddDestinationModal/AddLogStreamingModal.tsx
@@ -14,11 +14,11 @@ import { SizedBox } from '../../../../../shared/defguard-ui/components/SizedBox/
import { ThemeSpacing } from '../../../../../shared/defguard-ui/types';
import { useAppForm } from '../../../../../shared/form';
import { formChangeLogic } from '../../../../../shared/formLogic';
-import { ModalName } from '../../../../../shared/hooks/modalControls/modalTypes';
import {
subscribeCloseModal,
subscribeOpenModal,
} from '../../../../../shared/hooks/modalControls/modalsSubjects';
+import { ModalName } from '../../../../../shared/hooks/modalControls/modalTypes';
const modalNameValue = ModalName.AddLogStreaming;
@@ -68,9 +68,9 @@ export const AddLogStreamingModal = () => {
afterClose={() => setModalState(defaultState)}
>
{modalState.step === 'choice' && }
- {modalState.step === 'form' && (
+ {modalState.step === 'form' && modalState.destination && (
diff --git a/web/src/pages/settings/SettingsActivityLogStreamingPage/modals/DeleteDestinationModal/DeleteLogStreamingModal.tsx b/web/src/pages/settings/SettingsActivityLogStreamingPage/modals/DeleteDestinationModal/DeleteLogStreamingModal.tsx
index 68df0e5322..b2f5d3a535 100644
--- a/web/src/pages/settings/SettingsActivityLogStreamingPage/modals/DeleteDestinationModal/DeleteLogStreamingModal.tsx
+++ b/web/src/pages/settings/SettingsActivityLogStreamingPage/modals/DeleteDestinationModal/DeleteLogStreamingModal.tsx
@@ -7,11 +7,11 @@ import { AppText } from '../../../../../shared/defguard-ui/components/AppText/Ap
import { Modal } from '../../../../../shared/defguard-ui/components/Modal/Modal';
import { ModalControls } from '../../../../../shared/defguard-ui/components/ModalControls/ModalControls';
import { TextStyle } from '../../../../../shared/defguard-ui/types';
-import { ModalName } from '../../../../../shared/hooks/modalControls/modalTypes';
import {
subscribeCloseModal,
subscribeOpenModal,
} from '../../../../../shared/hooks/modalControls/modalsSubjects';
+import { ModalName } from '../../../../../shared/hooks/modalControls/modalTypes';
const modalNameValue = ModalName.DeleteLogStreaming;
diff --git a/web/src/pages/settings/SettingsActivityLogStreamingPage/modals/EditDestinationModal/EditLogStreamingModal.tsx b/web/src/pages/settings/SettingsActivityLogStreamingPage/modals/EditDestinationModal/EditLogStreamingModal.tsx
index 4903bb2eca..91cf37a25e 100644
--- a/web/src/pages/settings/SettingsActivityLogStreamingPage/modals/EditDestinationModal/EditLogStreamingModal.tsx
+++ b/web/src/pages/settings/SettingsActivityLogStreamingPage/modals/EditDestinationModal/EditLogStreamingModal.tsx
@@ -4,8 +4,8 @@ import z from 'zod';
import { m } from '../../../../../paraglide/messages';
import api from '../../../../../shared/api/api';
import {
- ActivityLogStreamType,
type ActivityLogStream,
+ ActivityLogStreamType,
type CreateActivityLogStreamRequest,
} from '../../../../../shared/api/types';
import { Modal } from '../../../../../shared/defguard-ui/components/Modal/Modal';
@@ -15,11 +15,11 @@ import { ThemeSpacing } from '../../../../../shared/defguard-ui/types';
import { isPresent } from '../../../../../shared/defguard-ui/utils/isPresent';
import { useAppForm } from '../../../../../shared/form';
import { formChangeLogic } from '../../../../../shared/formLogic';
-import { ModalName } from '../../../../../shared/hooks/modalControls/modalTypes';
import {
subscribeCloseModal,
subscribeOpenModal,
} from '../../../../../shared/hooks/modalControls/modalsSubjects';
+import { ModalName } from '../../../../../shared/hooks/modalControls/modalTypes';
const modalNameValue = ModalName.EditLogStreaming;
diff --git a/web/src/pages/settings/SettingsActivityLogStreamingPage/style.scss b/web/src/pages/settings/SettingsActivityLogStreamingPage/style.scss
index 68e3680608..f013dfd3fc 100644
--- a/web/src/pages/settings/SettingsActivityLogStreamingPage/style.scss
+++ b/web/src/pages/settings/SettingsActivityLogStreamingPage/style.scss
@@ -2,12 +2,13 @@
width: 784px;
.table-top {
- padding-left: 0px;
- padding-right: 0px;
+ padding-left: 0;
+ padding-right: 0;
}
+
.table {
- padding-left: 0px;
- padding-right: 0px;
+ padding-left: 0;
+ padding-right: 0;
}
.empty-state {
diff --git a/web/src/pages/settings/SettingsIndexPage/SettingsIndexPage.tsx b/web/src/pages/settings/SettingsIndexPage/SettingsIndexPage.tsx
index 6c1d64b3a8..6da68e1ea9 100644
--- a/web/src/pages/settings/SettingsIndexPage/SettingsIndexPage.tsx
+++ b/web/src/pages/settings/SettingsIndexPage/SettingsIndexPage.tsx
@@ -5,12 +5,12 @@ import { SizedBox } from '../../../shared/defguard-ui/components/SizedBox/SizedB
import { Tabs } from '../../../shared/defguard-ui/components/Tabs/Tabs';
import type { TabProps } from '../../../shared/defguard-ui/components/Tabs/types';
import { ThemeSpacing } from '../../../shared/defguard-ui/types';
+import { SettingsActivityLogStreamingPage } from '../SettingsActivityLogStreamingPage/SettingsActivityStreamingTab';
import { SettingsExternalOpenIdPage } from '../SettingsExternalOpenIdPage/SettingsExternalOpenIdPage';
import { SettingsGeneralTab } from './tabs/SettingsGeneralTab';
import { SettingsLicenseTab } from './tabs/SettingsLicenseTab/SettingsLicenseTab';
import { SettingsNotificationsTab } from './tabs/SettingsNotificationsTab';
import { type SettingsTabValue, settingsTabsSchema } from './types';
-import { SettingsActivityLogStreamingPage } from '../SettingsActivityLogStreamingPage/SettingsActivityStreamingTab';
const tabComponent: Record = {
general: ,
diff --git a/web/src/shared/api/types.ts b/web/src/shared/api/types.ts
index a0bb571846..461171723c 100644
--- a/web/src/shared/api/types.ts
+++ b/web/src/shared/api/types.ts
@@ -494,10 +494,11 @@ export interface NetworkLocation {
service_location_mode: LocationServiceModeValue;
}
-export interface EditNetworkLocation extends Omit<
- NetworkLocation,
- 'gateways' | 'connected_at' | 'id' | 'connected' | 'allowed_ips' | 'address'
-> {
+export interface EditNetworkLocation
+ extends Omit<
+ NetworkLocation,
+ 'gateways' | 'connected_at' | 'id' | 'connected' | 'allowed_ips' | 'address'
+ > {
allowed_ips: string;
address: string;
}
diff --git a/web/src/shared/defguard-ui b/web/src/shared/defguard-ui
index 86fd6dec23..8630c3cf23 160000
--- a/web/src/shared/defguard-ui
+++ b/web/src/shared/defguard-ui
@@ -1 +1 @@
-Subproject commit 86fd6dec2387295cb555e9ab3e472d7375ddf270
+Subproject commit 8630c3cf23a08d7e283a6c2e482e6a78cef7489e
From c9f94e50075b25201bd69b2292892b141ab2d45a Mon Sep 17 00:00:00 2001
From: jakub-tldr <78603704+jakub-tldr@users.noreply.github.com>
Date: Mon, 19 Jan 2026 15:49:52 +0100
Subject: [PATCH 12/12] add translation / simplify statements
---
web/messages/en/modal.json | 2 ++
.../ActivityLogStreamTable.tsx | 18 +++++++++---------
.../AddLogStreamingModal.tsx | 4 ++--
3 files changed, 13 insertions(+), 11 deletions(-)
diff --git a/web/messages/en/modal.json b/web/messages/en/modal.json
index b783e7fc26..58f0504d99 100644
--- a/web/messages/en/modal.json
+++ b/web/messages/en/modal.json
@@ -77,6 +77,8 @@
"modal_add_user_title": "Add new user",
"modal_add_vector_destination_title": "Add Vector destination",
"modal_add_logstash_destination_title": "Add Logstash destination",
+ "modal_logstash_destination_title": "Logstash",
+ "modal_vector_destination_title": "Vector",
"modal_edit_vector_destination_title": "Edit Vector destination",
"modal_edit_logstash_destination_title": "Edit Logstash destination",
"modal_edit_log_streaming_destination_title": "Edit destination",
diff --git a/web/src/pages/settings/SettingsActivityLogStreamingPage/ActivityLogStreamTable.tsx b/web/src/pages/settings/SettingsActivityLogStreamingPage/ActivityLogStreamTable.tsx
index 7aa8e85ba7..9c210c514d 100644
--- a/web/src/pages/settings/SettingsActivityLogStreamingPage/ActivityLogStreamTable.tsx
+++ b/web/src/pages/settings/SettingsActivityLogStreamingPage/ActivityLogStreamTable.tsx
@@ -41,15 +41,15 @@ export const ActivityLogStreamTable = ({ data: rowData }: Props) => {
header: m.settings_activity_log_streaming_table_stream_type_name(),
size: 220,
minSize: 100,
- cell: (info) => {
- const value = info.getValue();
- const displayValue = value === 'vector_http' ? 'Vector' : 'Logstash';
- return (
-
- {displayValue}
-
- );
- },
+ cell: (info) => (
+
+
+ {info.getValue() === 'vector_http'
+ ? m.modal_vector_destination_title()
+ : m.modal_logstash_destination_title()}
+
+
+ ),
}),
columnHelper.display({
id: 'edit',
diff --git a/web/src/pages/settings/SettingsActivityLogStreamingPage/modals/AddDestinationModal/AddLogStreamingModal.tsx b/web/src/pages/settings/SettingsActivityLogStreamingPage/modals/AddDestinationModal/AddLogStreamingModal.tsx
index c0964d273f..e4e5a8e5c0 100644
--- a/web/src/pages/settings/SettingsActivityLogStreamingPage/modals/AddDestinationModal/AddLogStreamingModal.tsx
+++ b/web/src/pages/settings/SettingsActivityLogStreamingPage/modals/AddDestinationModal/AddLogStreamingModal.tsx
@@ -88,7 +88,7 @@ const ChoiceStep = ({ setModalState }: ChoiceStepProps) => {
<>
{
@@ -102,7 +102,7 @@ const ChoiceStep = ({ setModalState }: ChoiceStepProps) => {
{
setModalState({