diff --git a/app/assets/css/app.css b/app/assets/css/app.css
index 3bdfa2f8f..afcf49470 100644
--- a/app/assets/css/app.css
+++ b/app/assets/css/app.css
@@ -615,24 +615,6 @@ input[type='checkbox'] {
font-weight: 600;
}
-/* json-tree override */
-json-tree {
- font-size: 13px;
- color: var(--blue-5);
-}
-
-json-tree .key {
- color: var(--blue-3);
- padding-right: 5px;
-}
-
-json-tree .branch-preview {
- font-style: normal;
- font-size: 11px;
- opacity: 0.5;
-}
-/* !json-tree override */
-
/* uib-progressbar override */
.progress-bar {
color: var(--text-progress-bar-color);
diff --git a/app/assets/css/vendor-override.css b/app/assets/css/vendor-override.css
index d927ce52d..248d759cc 100644
--- a/app/assets/css/vendor-override.css
+++ b/app/assets/css/vendor-override.css
@@ -168,17 +168,6 @@ pre {
background-color: var(--bg-pre-color);
color: var(--text-pre-color);
}
-json-tree .key {
- color: var(--text-json-tree-color);
-}
-
-json-tree .leaf-value {
- color: var(--text-json-tree-leaf-color);
-}
-
-json-tree .branch-preview {
- color: var(--text-json-tree-branch-preview-color);
-}
.progress {
background-color: var(--bg-progress-color);
diff --git a/app/portainer/react/views/activity-logs.ts b/app/portainer/react/views/activity-logs.ts
new file mode 100644
index 000000000..e7427673a
--- /dev/null
+++ b/app/portainer/react/views/activity-logs.ts
@@ -0,0 +1,13 @@
+import angular from 'angular';
+
+import { r2a } from '@/react-tools/react2angular';
+import { withUIRouter } from '@/react-tools/withUIRouter';
+import { ActivityLogsView } from '@/react/portainer/logs/ActivityLogsView/ActivityLogsView';
+import { withCurrentUser } from '@/react-tools/withCurrentUser';
+
+export const activityLogsModule = angular
+ .module('portainer.app.react.views.activity-logs', [])
+ .component(
+ 'activityLogsView',
+ r2a(withUIRouter(withCurrentUser(ActivityLogsView)), [])
+ ).name;
diff --git a/app/portainer/react/views/index.ts b/app/portainer/react/views/index.ts
index f3ad06fda..0fbba928e 100644
--- a/app/portainer/react/views/index.ts
+++ b/app/portainer/react/views/index.ts
@@ -18,6 +18,7 @@ import { teamsModule } from './teams';
import { updateSchedulesModule } from './update-schedules';
import { environmentGroupModule } from './env-groups';
import { registriesModule } from './registries';
+import { activityLogsModule } from './activity-logs';
export const viewsModule = angular
.module('portainer.app.react.views', [
@@ -26,6 +27,7 @@ export const viewsModule = angular
updateSchedulesModule,
environmentGroupModule,
registriesModule,
+ activityLogsModule,
])
.component(
'homeView',
diff --git a/app/portainer/user-activity/activity-logs-view/activity-logs-datatable/activity-logs-datatable.controller.js b/app/portainer/user-activity/activity-logs-view/activity-logs-datatable/activity-logs-datatable.controller.js
deleted file mode 100644
index aa3f0c854..000000000
--- a/app/portainer/user-activity/activity-logs-view/activity-logs-datatable/activity-logs-datatable.controller.js
+++ /dev/null
@@ -1,38 +0,0 @@
-export default class ActivityLogsDatatableController {
- /* @ngInject */
- constructor($controller, $scope, PaginationService) {
- this.PaginationService = PaginationService;
-
- this.tableKey = 'authLogs';
-
- const $onInit = this.$onInit;
- angular.extend(this, $controller('GenericDatatableController', { $scope }));
-
- this.changeSort = this.changeSort.bind(this);
- this.handleChangeLimit = this.handleChangeLimit.bind(this);
- this.$onInit = $onInit.bind(this);
- }
-
- changeSort(key) {
- let desc = false;
- if (key === this.sort.key) {
- desc = !this.sort.desc;
- }
-
- this.onChangeSort({ key, desc });
- }
-
- handleChangeLimit(limit) {
- this.PaginationService.setPaginationLimit(this.tableKey, limit);
- this.onChangeLimit(limit);
- }
-
- $onInit() {
- this.$onInitGeneric();
-
- const limit = this.PaginationService.getPaginationLimit(this.tableKey);
- if (limit) {
- this.onChangeLimit(+limit);
- }
- }
-}
diff --git a/app/portainer/user-activity/activity-logs-view/activity-logs-datatable/activity-logs-datatable.css b/app/portainer/user-activity/activity-logs-view/activity-logs-datatable/activity-logs-datatable.css
deleted file mode 100644
index 42eb3b401..000000000
--- a/app/portainer/user-activity/activity-logs-view/activity-logs-datatable/activity-logs-datatable.css
+++ /dev/null
@@ -1,3 +0,0 @@
-.activity-logs-datatable .small-column {
- width: 150px;
-}
diff --git a/app/portainer/user-activity/activity-logs-view/activity-logs-datatable/activity-logs-datatable.html b/app/portainer/user-activity/activity-logs-view/activity-logs-datatable/activity-logs-datatable.html
deleted file mode 100644
index 970090f69..000000000
--- a/app/portainer/user-activity/activity-logs-view/activity-logs-datatable/activity-logs-datatable.html
+++ /dev/null
@@ -1,95 +0,0 @@
-
-
-
-
-
-
-
-
-
-
- |
-
-
- |
-
-
- |
-
-
-
- |
-
-
- |
-
-
-
-
- |
- |
- |
- |
- |
-
-
-
-
- |
-
-
- Loading... |
-
-
- No logs available. |
-
-
-
-
-
-
-
-
diff --git a/app/portainer/user-activity/activity-logs-view/activity-logs-datatable/index.js b/app/portainer/user-activity/activity-logs-view/activity-logs-datatable/index.js
deleted file mode 100644
index 9550fb36e..000000000
--- a/app/portainer/user-activity/activity-logs-view/activity-logs-datatable/index.js
+++ /dev/null
@@ -1,24 +0,0 @@
-import './activity-logs-datatable.css';
-
-import controller from './activity-logs-datatable.controller.js';
-
-export const activityLogsDatatable = {
- templateUrl: './activity-logs-datatable.html',
- controller,
- bindings: {
- logs: '<',
- keyword: '<',
- sort: '<',
- limit: '<',
- totalItems: '<',
- currentPage: '<',
- feature: '@',
-
- onChangeContextFilter: '<',
- onChangeKeyword: '<',
- onChangeSort: '<',
-
- onChangeLimit: '<',
- onChangePage: '<',
- },
-};
diff --git a/app/portainer/user-activity/activity-logs-view/activity-logs-view.controller.js b/app/portainer/user-activity/activity-logs-view/activity-logs-view.controller.js
deleted file mode 100644
index bd4547b71..000000000
--- a/app/portainer/user-activity/activity-logs-view/activity-logs-view.controller.js
+++ /dev/null
@@ -1,89 +0,0 @@
-import moment from 'moment';
-
-import { FeatureId } from '@/react/portainer/feature-flags/enums';
-export default class ActivityLogsViewController {
- /* @ngInject */
- constructor($async, $scope, Notifications) {
- this.$async = $async;
- this.$scope = $scope;
- this.Notifications = Notifications;
-
- this.limitedFeature = FeatureId.ACTIVITY_AUDIT;
-
- this.state = {
- keyword: '',
- date: {
- from: 0,
- to: 0,
- },
- sort: {
- key: 'Timestamp',
- desc: true,
- },
- page: 1,
- limit: 10,
- totalItems: 0,
- logs: null,
- };
-
- this.today = moment().endOf('day');
- this.minValidDate = moment().subtract(7, 'd').startOf('day');
-
- this.onChangeDate = this.onChangeDate.bind(this);
- this.onChangeKeyword = this.onChangeKeyword.bind(this);
- this.onChangeSort = this.onChangeSort.bind(this);
- this.loadLogs = this.loadLogs.bind(this);
- this.onChangePage = this.onChangePage.bind(this);
- this.onChangeLimit = this.onChangeLimit.bind(this);
- }
-
- onChangePage(page) {
- this.state.page = page;
- this.loadLogs();
- }
-
- onChangeLimit(limit) {
- this.state.page = 1;
- this.state.limit = limit;
- this.loadLogs();
- }
-
- onChangeSort(sort) {
- this.state.page = 1;
- this.state.sort = sort;
- this.loadLogs();
- }
-
- onChangeKeyword(keyword) {
- return this.$scope.$evalAsync(() => {
- this.state.page = 1;
- this.state.keyword = keyword;
- this.loadLogs();
- });
- }
-
- onChangeDate({ startDate, endDate }) {
- this.state.page = 1;
- this.state.date = { to: endDate, from: startDate };
- this.loadLogs();
- }
-
- async loadLogs() {
- return this.$async(async () => {
- this.state.logs = null;
- try {
- const { logs, totalCount } = { logs: [{}, {}, {}, {}, {}], totalCount: 5 };
- this.state.logs = logs;
- this.state.totalItems = totalCount;
- } catch (err) {
- this.Notifications.error('Failure', err, 'Failed loading user activity logs');
- }
- });
- }
-
- $onInit() {
- return this.$async(async () => {
- this.loadLogs();
- });
- }
-}
diff --git a/app/portainer/user-activity/activity-logs-view/activity-logs-view.html b/app/portainer/user-activity/activity-logs-view/activity-logs-view.html
deleted file mode 100644
index d0e5801f5..000000000
--- a/app/portainer/user-activity/activity-logs-view/activity-logs-view.html
+++ /dev/null
@@ -1,47 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
- Portainer user activity logs have a maximum retention of 7 days.
-
-
-
-
-
-
-
-
diff --git a/app/portainer/user-activity/activity-logs-view/activity-logs-view.js b/app/portainer/user-activity/activity-logs-view/activity-logs-view.js
deleted file mode 100644
index 52082d363..000000000
--- a/app/portainer/user-activity/activity-logs-view/activity-logs-view.js
+++ /dev/null
@@ -1,6 +0,0 @@
-import controller from './activity-logs-view.controller.js';
-
-export const activityLogsView = {
- templateUrl: './activity-logs-view.html',
- controller,
-};
diff --git a/app/portainer/user-activity/activity-logs-view/index.js b/app/portainer/user-activity/activity-logs-view/index.js
deleted file mode 100644
index da8c69cb4..000000000
--- a/app/portainer/user-activity/activity-logs-view/index.js
+++ /dev/null
@@ -1,9 +0,0 @@
-import angular from 'angular';
-
-import { activityLogsView } from './activity-logs-view';
-import { activityLogsDatatable } from './activity-logs-datatable';
-
-export default angular
- .module('portainer.app.user-activity.activity-logs-view', [])
- .component('activityLogsDatatable', activityLogsDatatable)
- .component('activityLogsView', activityLogsView).name;
diff --git a/app/portainer/user-activity/index.js b/app/portainer/user-activity/index.js
index d6fb1b67c..0a04640e0 100644
--- a/app/portainer/user-activity/index.js
+++ b/app/portainer/user-activity/index.js
@@ -3,9 +3,15 @@ import angular from 'angular';
import { NotificationsViewAngular } from '@/react/portainer/notifications/NotificationsView';
import { AccessHeaders } from '../authorization-guard';
import authLogsViewModule from './auth-logs-view';
-import activityLogsViewModule from './activity-logs-view';
+import { UserActivityService } from './user-activity.service';
+import { UserActivity } from './user-activity.rest';
-export default angular.module('portainer.app.user-activity', [authLogsViewModule, activityLogsViewModule]).component('notifications', NotificationsViewAngular).config(config).name;
+export default angular
+ .module('portainer.app.user-activity', [authLogsViewModule])
+ .service('UserActivity', UserActivity)
+ .service('UserActivityService', UserActivityService)
+ .component('notifications', NotificationsViewAngular)
+ .config(config).name;
/* @ngInject */
function config($stateRegistryProvider) {
diff --git a/app/portainer/user-activity/user-activity.rest.js b/app/portainer/user-activity/user-activity.rest.js
new file mode 100644
index 000000000..4ed527e81
--- /dev/null
+++ b/app/portainer/user-activity/user-activity.rest.js
@@ -0,0 +1,28 @@
+import { baseHref } from '@/portainer/helpers/pathHelper';
+
+/* @ngInject */
+export function UserActivity($resource, $http) {
+ const BASE_URL = baseHref() + 'api/useractivity';
+
+ const resource = $resource(
+ `${BASE_URL}/:action`,
+ {},
+ {
+ authLogs: { method: 'GET', params: { action: 'authlogs' } },
+ }
+ );
+
+ return { authLogsAsCSV, ...resource };
+
+ async function authLogsAsCSV(params) {
+ return $http({
+ method: 'GET',
+ url: `${BASE_URL}/authlogs.csv`,
+ params,
+ responseType: 'blob',
+ headers: {
+ 'Content-type': 'text/csv',
+ },
+ });
+ }
+}
diff --git a/app/portainer/user-activity/user-activity.service.js b/app/portainer/user-activity/user-activity.service.js
new file mode 100644
index 000000000..5c3c454de
--- /dev/null
+++ b/app/portainer/user-activity/user-activity.service.js
@@ -0,0 +1,13 @@
+/* @ngInject */
+export function UserActivityService(FileSaver, UserActivity) {
+ return { authLogs, saveAuthLogsAsCSV };
+
+ function authLogs(offset, limit, sort, keyword, date, contexts, types) {
+ return UserActivity.authLogs({ offset, limit, keyword, before: date.to, after: date.from, sortBy: sort.key, sortDesc: sort.desc, contexts, types }).$promise;
+ }
+
+ async function saveAuthLogsAsCSV(sort, keyword, date, contexts, types) {
+ const response = await UserActivity.authLogsAsCSV({ keyword, before: date.to, after: date.from, sortBy: sort.key, sortDesc: sort.desc, limit: 2000, contexts, types });
+ return FileSaver.saveAs(response.data, 'logs.csv');
+ }
+}
diff --git a/app/react/components/JsonTree.css b/app/react/components/JsonTree.css
new file mode 100644
index 000000000..181d93437
--- /dev/null
+++ b/app/react/components/JsonTree.css
@@ -0,0 +1,30 @@
+/* json-tree override */
+.json-tree,
+json-tree {
+ font-size: 13px;
+ color: var(--blue-5);
+}
+
+.json-tree .key,
+json-tree .key {
+ color: var(--text-json-tree-color);
+}
+
+json-tree .key {
+ padding-right: 5px;
+}
+
+.json-tree .branch-preview,
+json-tree .branch-preview {
+ color: var(--text-json-tree-branch-preview-color);
+ font-style: normal;
+ font-size: 11px;
+ opacity: 0.5;
+}
+
+.json-tree .leaf-value,
+json-tree .leaf-value {
+ color: var(--text-json-tree-leaf-color);
+}
+
+/* !json-tree override */
diff --git a/app/react/components/JsonTree.tsx b/app/react/components/JsonTree.tsx
new file mode 100644
index 000000000..4dfc78687
--- /dev/null
+++ b/app/react/components/JsonTree.tsx
@@ -0,0 +1,41 @@
+import { ComponentProps } from 'react';
+import { JsonView, defaultStyles } from 'react-json-view-lite';
+import 'react-json-view-lite/dist/index.css';
+import clsx from 'clsx';
+
+import './JsonTree.css';
+
+export function JsonTree({ style, ...props }: ComponentProps) {
+ const currentStyle = getCurrentStyle(style);
+ return (
+
+ );
+}
+
+type StyleProps = ComponentProps['style'];
+
+function getCurrentStyle(style: StyleProps | undefined): StyleProps {
+ if (style) {
+ return style;
+ }
+
+ return {
+ ...defaultStyles,
+ container: 'json-tree',
+ booleanValue: 'leaf-value',
+ nullValue: 'leaf-value',
+ otherValue: 'leaf-value',
+ numberValue: 'leaf-value',
+ stringValue: 'leaf-value',
+ undefinedValue: 'leaf-value',
+ label: 'key',
+ punctuation: 'leaf-value',
+ collapseIcon: clsx(defaultStyles.collapseIcon, 'key'),
+ expandIcon: clsx(defaultStyles.expandIcon, 'key'),
+ collapsedContent: clsx(defaultStyles.collapsedContent, 'branch-preview'),
+ };
+}
diff --git a/app/react/portainer/environments/update-schedules/common/ScheduledTimeField.tsx b/app/react/portainer/environments/update-schedules/common/ScheduledTimeField.tsx
index cacb17cb9..61c6dd069 100644
--- a/app/react/portainer/environments/update-schedules/common/ScheduledTimeField.tsx
+++ b/app/react/portainer/environments/update-schedules/common/ScheduledTimeField.tsx
@@ -16,6 +16,9 @@ import { TextTip } from '@@/Tip/TextTip';
import { FormValues } from './types';
+import 'react-datetime-picker/dist/DateTimePicker.css';
+import 'react-calendar/dist/Calendar.css';
+
interface Props {
disabled?: boolean;
}
diff --git a/app/react/portainer/logs/.keep b/app/react/portainer/logs/.keep
deleted file mode 100644
index e69de29bb..000000000
diff --git a/app/react/portainer/logs/ActivityLogsView/.keep b/app/react/portainer/logs/ActivityLogsView/.keep
deleted file mode 100644
index e69de29bb..000000000
diff --git a/app/react/portainer/logs/ActivityLogsView/ActivityLogsTable.tsx b/app/react/portainer/logs/ActivityLogsView/ActivityLogsTable.tsx
new file mode 100644
index 000000000..086f99427
--- /dev/null
+++ b/app/react/portainer/logs/ActivityLogsView/ActivityLogsTable.tsx
@@ -0,0 +1,110 @@
+import { createColumnHelper } from '@tanstack/react-table';
+import { History, Search } from 'lucide-react';
+
+import { isoDateFromTimestamp } from '@/portainer/filters/filters';
+
+import { ExpandableDatatable } from '@@/datatables/ExpandableDatatable';
+import { Button } from '@@/buttons';
+import { JsonTree } from '@@/JsonTree';
+
+import { ActivityLog } from './types';
+import { getSortType } from './useActivityLogs';
+
+const columnHelper = createColumnHelper();
+
+const columns = [
+ columnHelper.accessor('timestamp', {
+ id: 'Timestamp',
+ header: 'Time',
+ cell: ({ getValue }) => {
+ const value = getValue();
+ return value ? isoDateFromTimestamp(value) : '';
+ },
+ }),
+ columnHelper.accessor('username', {
+ id: 'Username',
+ header: 'User',
+ }),
+ columnHelper.accessor('context', {
+ id: 'Context',
+ header: 'Environment',
+ }),
+ columnHelper.accessor('action', {
+ id: 'Action',
+ header: 'Action',
+ }),
+ columnHelper.accessor('payload', {
+ header: 'Payload',
+ enableSorting: false,
+ cell: ({ row, getValue }) =>
+ getValue() ? (
+
+ ) : null,
+ }),
+];
+
+export function ActivityLogsTable({
+ dataset,
+ currentPage,
+ keyword,
+ limit,
+ onChangeKeyword,
+ onChangeLimit,
+ onChangePage,
+ onChangeSort,
+ sort,
+ totalItems,
+}: {
+ keyword: string;
+ onChangeKeyword(keyword: string): void;
+ sort: { id: string; desc: boolean } | undefined;
+ onChangeSort(sort: { id: string; desc: boolean } | undefined): void;
+ limit: number;
+ onChangeLimit(limit: number): void;
+ currentPage: number;
+ onChangePage(page: number): void;
+ totalItems: number;
+ dataset?: Array;
+}) {
+ return (
+
+ title="Activity Logs"
+ titleIcon={History}
+ columns={columns}
+ dataset={dataset || []}
+ isLoading={!dataset}
+ settingsManager={{
+ pageSize: limit,
+ search: keyword,
+ setPageSize: onChangeLimit,
+ setSearch: onChangeKeyword,
+ setSortBy: (id, desc) =>
+ onChangeSort({ id: getSortType(id) || 'Timestamp', desc }),
+ sortBy: sort
+ ? {
+ id: sort.id,
+ desc: sort.desc,
+ }
+ : undefined,
+ }}
+ page={currentPage}
+ onPageChange={onChangePage}
+ isServerSidePagination
+ totalCount={totalItems}
+ disableSelect
+ renderSubRow={(row) => }
+ />
+ );
+}
+
+function SubRow({ item }: { item: ActivityLog }) {
+ return (
+
+
+
+ |
+
+ );
+}
diff --git a/app/react/portainer/logs/ActivityLogsView/ActivityLogsView.tsx b/app/react/portainer/logs/ActivityLogsView/ActivityLogsView.tsx
new file mode 100644
index 000000000..59f18f7d0
--- /dev/null
+++ b/app/react/portainer/logs/ActivityLogsView/ActivityLogsView.tsx
@@ -0,0 +1,83 @@
+import { useState } from 'react';
+
+import { PageHeader } from '@@/PageHeader';
+import { useTableStateWithoutStorage } from '@@/datatables/useTableState';
+import { BEOverlay } from '@@/BEFeatureIndicator/BEOverlay';
+
+import { FeatureId } from '../../feature-flags/enums';
+
+import { ActivityLogsTable } from './ActivityLogsTable';
+import { useActivityLogs, getSortType } from './useActivityLogs';
+import { useExportMutation } from './useExportMutation';
+import { FilterBar } from './FilterBar';
+
+export function ActivityLogsView() {
+ const exportMutation = useExportMutation();
+ const [range, setRange] = useState<
+ { start: Date; end: Date | null } | undefined
+ >(undefined);
+ const [page, setPage] = useState(0);
+ const tableState = useTableStateWithoutStorage('Timestamp');
+ const offset = page * tableState.pageSize;
+
+ const query = {
+ offset,
+ limit: tableState.pageSize,
+ sortBy: getSortType(tableState.sortBy?.id),
+ desc: tableState.sortBy?.desc,
+ search: tableState.search,
+ ...(range
+ ? {
+ after: seconds(range?.start?.valueOf()),
+ before: seconds(range?.end?.valueOf()),
+ }
+ : undefined),
+ };
+
+ const logsQuery = useActivityLogs(query);
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+ tableState.setSortBy(value?.id, value?.desc || false)
+ }
+ limit={tableState.pageSize}
+ onChangeLimit={tableState.setPageSize}
+ keyword={tableState.search}
+ onChangeKeyword={tableState.setSearch}
+ currentPage={page}
+ onChangePage={setPage}
+ totalItems={logsQuery.data?.totalCount || 0}
+ dataset={logsQuery.data?.logs}
+ />
+
+
+
+ >
+ );
+
+ function handleExport() {
+ exportMutation.mutate(query);
+ }
+}
+
+function seconds(ms?: number) {
+ if (!ms) {
+ return undefined;
+ }
+
+ return Math.floor(ms / 1000);
+}
diff --git a/app/react/portainer/logs/ActivityLogsView/FilterBar.tsx b/app/react/portainer/logs/ActivityLogsView/FilterBar.tsx
new file mode 100644
index 000000000..822372701
--- /dev/null
+++ b/app/react/portainer/logs/ActivityLogsView/FilterBar.tsx
@@ -0,0 +1,45 @@
+import { DownloadIcon } from 'lucide-react';
+
+import { Widget } from '@@/Widget';
+import { TextTip } from '@@/Tip/TextTip';
+import { Button } from '@@/buttons';
+import { BEFeatureIndicator } from '@@/BEFeatureIndicator';
+
+import { FeatureId } from '../../feature-flags/enums';
+import { DateRangePicker } from '../components/DateRangePicker';
+
+export function FilterBar({
+ value,
+ onChange,
+ onExport,
+}: {
+ value: { start: Date; end: Date | null } | undefined;
+ onChange: (value?: { start: Date; end: Date | null }) => void;
+ onExport: () => void;
+}) {
+ return (
+
+
+
+
+
+ );
+}
diff --git a/app/react/portainer/logs/ActivityLogsView/types.ts b/app/react/portainer/logs/ActivityLogsView/types.ts
new file mode 100644
index 000000000..237c8a257
--- /dev/null
+++ b/app/react/portainer/logs/ActivityLogsView/types.ts
@@ -0,0 +1,8 @@
+export interface ActivityLog {
+ timestamp: number;
+ action: string;
+ context: string;
+ id: number;
+ payload: object;
+ username: string;
+}
diff --git a/app/react/portainer/logs/ActivityLogsView/useActivityLogs.ts b/app/react/portainer/logs/ActivityLogsView/useActivityLogs.ts
new file mode 100644
index 000000000..3883e9d2a
--- /dev/null
+++ b/app/react/portainer/logs/ActivityLogsView/useActivityLogs.ts
@@ -0,0 +1,58 @@
+import { useQuery } from 'react-query';
+
+import axios, { parseAxiosError } from '@/portainer/services/axios';
+
+import { isBE } from '../../feature-flags/feature-flags.service';
+
+import { ActivityLog } from './types';
+
+export const sortKeys = ['Context', 'Action', 'Timestamp', 'Username'] as const;
+export type SortKey = (typeof sortKeys)[number];
+export function isSortKey(value?: string): value is SortKey {
+ return !!value && sortKeys.includes(value as SortKey);
+}
+export function getSortType(value?: string): SortKey | undefined {
+ return isSortKey(value) ? value : undefined;
+}
+
+export interface Query {
+ offset: number;
+ limit: number;
+ sortBy?: SortKey;
+ desc?: boolean;
+ search: string;
+ after?: number;
+ before?: number;
+}
+
+export function useActivityLogs(query: Query) {
+ return useQuery({
+ queryKey: ['activityLogs', query] as const,
+ queryFn: () => fetchActivityLogs(query),
+ keepPreviousData: true,
+ });
+}
+
+interface ActivityLogsResponse {
+ logs: Array;
+ totalCount: number;
+}
+
+async function fetchActivityLogs(query: Query): Promise {
+ try {
+ if (!isBE) {
+ return {
+ logs: [{}, {}, {}, {}, {}] as Array,
+ totalCount: 5,
+ };
+ }
+
+ const { data } = await axios.get(
+ '/useractivity/logs',
+ { params: query }
+ );
+ return data;
+ } catch (err) {
+ throw parseAxiosError(err, 'Failed loading user activity logs csv');
+ }
+}
diff --git a/app/react/portainer/logs/ActivityLogsView/useExportMutation.ts b/app/react/portainer/logs/ActivityLogsView/useExportMutation.ts
new file mode 100644
index 000000000..c9bcfe87e
--- /dev/null
+++ b/app/react/portainer/logs/ActivityLogsView/useExportMutation.ts
@@ -0,0 +1,32 @@
+import { useMutation } from 'react-query';
+import { saveAs } from 'file-saver';
+
+import axios, { parseAxiosError } from '@/portainer/services/axios';
+
+import { Query } from './useActivityLogs';
+
+export function useExportMutation() {
+ return useMutation({
+ mutationFn: exportActivityLogs,
+ });
+}
+
+async function exportActivityLogs(query: Omit) {
+ try {
+ const { data, headers } = await axios.get('/useractivity/logs.csv', {
+ params: { ...query, limit: 2000 },
+ responseType: 'blob',
+ headers: {
+ 'Content-type': 'text/csv',
+ },
+ });
+
+ const contentDispositionHeader = headers['content-disposition'] || '';
+ const filename =
+ contentDispositionHeader.replace('attachment; filename=', '').trim() ||
+ 'logs.csv';
+ saveAs(data, filename);
+ } catch (err) {
+ throw parseAxiosError(err, 'Failed loading user activity logs csv');
+ }
+}
diff --git a/app/react/portainer/logs/components/DateRangePicker.tsx b/app/react/portainer/logs/components/DateRangePicker.tsx
new file mode 100644
index 000000000..4ad27e6f6
--- /dev/null
+++ b/app/react/portainer/logs/components/DateRangePicker.tsx
@@ -0,0 +1,65 @@
+import WojtekmajRangePicker from '@wojtekmaj/react-daterange-picker';
+import { Calendar, X } from 'lucide-react';
+import { date, object, SchemaOf } from 'yup';
+import { FormikErrors } from 'formik';
+
+import '@wojtekmaj/react-daterange-picker/dist/DateRangePicker.css';
+import 'react-calendar/dist/Calendar.css';
+
+import { FormControl } from '@@/form-components/FormControl';
+
+import 'react-datetime-picker/dist/DateTimePicker.css';
+
+type Value = { start: Date; end: Date | null };
+
+export function DateRangePicker({
+ value,
+ onChange,
+ name,
+ error,
+}: {
+ value: Value | undefined;
+ onChange: (value?: Value) => void;
+ name?: string;
+ error?: FormikErrors;
+}) {
+ return (
+
+
+ {
+ if (!date) {
+ onChange(undefined);
+ return;
+ }
+ if (Array.isArray(date)) {
+ if (date.length === 2 && date[0] && date[1]) {
+ onChange({
+ start: date[0],
+ end: date[1],
+ });
+ return;
+ }
+ onChange(undefined);
+ return;
+ }
+ onChange({ start: date, end: null });
+ }}
+ name={name}
+ calendarIcon={}
+ clearIcon={}
+ />
+
+
+ );
+}
+
+export function dateRangePickerValidation(): SchemaOf {
+ return object({
+ start: date().required(),
+ end: date().nullable().default(null).required(),
+ });
+}
diff --git a/package.json b/package.json
index 384a686fd..45e8e6af0 100644
--- a/package.json
+++ b/package.json
@@ -57,6 +57,7 @@
"@uirouter/react-hybrid": "^1.0.4",
"@uiw/codemirror-themes": "^4.19.9",
"@uiw/react-codemirror": "^4.19.5",
+ "@wojtekmaj/react-daterange-picker": "^5.5.0",
"angular": "1.8.2",
"angular-clipboard": "^1.6.2",
"angular-file-saver": "^1.1.3",
@@ -108,10 +109,12 @@
"parse-duration": "^1.0.2",
"rc-slider": "^10.0.0",
"react": "^17.0.2",
- "react-datetime-picker": "^4.2.0",
+ "react-calendar": "^4.8.0",
+ "react-datetime-picker": "^5.6.0",
"react-dom": "^17.0.2",
"react-i18next": "^11.12.0",
"react-is": "^17.0.2",
+ "react-json-view-lite": "^1.2.1",
"react-query": "^3.33.4",
"react-select": "^5.2.1",
"sanitize-html": "^2.8.1",
diff --git a/yarn.lock b/yarn.lock
index 9f812ce2d..a9f2a697f 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5944,6 +5944,18 @@
"@types/fined" "*"
"@types/node" "*"
+"@types/lodash.memoize@^4.1.7":
+ version "4.1.9"
+ resolved "https://registry.yarnpkg.com/@types/lodash.memoize/-/lodash.memoize-4.1.9.tgz#9f8912d39b6e450c0d342a2b74c99d331bf2016b"
+ integrity sha512-glY1nQuoqX4Ft8Uk+KfJudOD7DQbbEDF6k9XpGncaohW3RW4eSWBlx6AA0fZCrh40tZcQNH4jS/Oc59J6Eq+aw==
+ dependencies:
+ "@types/lodash" "*"
+
+"@types/lodash@*":
+ version "4.14.175"
+ resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.175.tgz#b78dfa959192b01fae0ad90e166478769b215f45"
+ integrity sha512-XmdEOrKQ8a1Y/yxQFOMbC47G/V2VDO1GvMRnl4O75M4GW/abC5tnfzadQYkqEveqRM1dEJGFFegfPNA2vvx2iw==
+
"@types/lodash@^4.14.167":
version "4.14.194"
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.194.tgz#b71eb6f7a0ff11bff59fc987134a093029258a76"
@@ -6039,13 +6051,6 @@
resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc"
integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==
-"@types/react-calendar@^3.0.0":
- version "3.5.2"
- resolved "https://registry.yarnpkg.com/@types/react-calendar/-/react-calendar-3.5.2.tgz#e401034e4bb82f4510ba87aa490e98b5746e16e0"
- integrity sha512-8gkU9KaE33VVbu3YWvxXjEk4BsalgSYR3c/5XF9XNJiQ/2MKxiGkTg/PfOHUX/BvcADykRBMAEJiCi6jFPEE3A==
- dependencies:
- "@types/react" "*"
-
"@types/react-datetime-picker@^3.4.1":
version "3.4.1"
resolved "https://registry.yarnpkg.com/@types/react-datetime-picker/-/react-datetime-picker-3.4.1.tgz#8acbc3e6f4e69fac0f91be4e920c3efdc28f3ed7"
@@ -6679,10 +6684,22 @@
resolved "https://registry.yarnpkg.com/@webpack-cli/serve/-/serve-2.0.5.tgz#325db42395cd49fe6c14057f9a900e427df8810e"
integrity sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ==
-"@wojtekmaj/date-utils@^1.0.0", "@wojtekmaj/date-utils@^1.0.2", "@wojtekmaj/date-utils@^1.0.3":
- version "1.0.3"
- resolved "https://registry.yarnpkg.com/@wojtekmaj/date-utils/-/date-utils-1.0.3.tgz#2dcfd92881425c5923e429c2aec86fb3609032a1"
- integrity sha512-1VPkkTBk07gMR1fjpBtse4G+oJqpmE+0gUFB0dg3VIL7qJmUVaBoD/vlzMm/jNeOPfvlmerl1lpnsZyBUFIRuw==
+"@wojtekmaj/date-utils@^1.1.3", "@wojtekmaj/date-utils@^1.5.0":
+ version "1.5.1"
+ resolved "https://registry.yarnpkg.com/@wojtekmaj/date-utils/-/date-utils-1.5.1.tgz#c3cd67177ac781cfa5736219d702a55a2aea5f2b"
+ integrity sha512-+i7+JmNiE/3c9FKxzWFi2IjRJ+KzZl1QPu6QNrsgaa2MuBgXvUy4gA1TVzf/JMdIIloB76xSKikTWuyYAIVLww==
+
+"@wojtekmaj/react-daterange-picker@^5.5.0":
+ version "5.5.0"
+ resolved "https://registry.yarnpkg.com/@wojtekmaj/react-daterange-picker/-/react-daterange-picker-5.5.0.tgz#634daf8874a6f704dc5bbe45279e10b826bb41e6"
+ integrity sha512-xW0J5akOO0pmnPyStEndcHj3gQKTYrZue7HSfUp1F7pDgn9vAJD7AfwOBIA3iqUDUnIBl+jgrl1eP1+/EuTn7g==
+ dependencies:
+ clsx "^2.0.0"
+ make-event-props "^1.6.0"
+ prop-types "^15.6.0"
+ react-calendar "^4.6.0"
+ react-date-picker "^10.5.0"
+ react-fit "^1.7.0"
"@xtuc/ieee754@^1.2.0":
version "1.2.0"
@@ -8152,10 +8169,10 @@ clsx@^1.1.1:
resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.1.1.tgz#98b3134f9abbdf23b2663491ace13c5c03a73188"
integrity sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA==
-clsx@^1.2.1:
- version "1.2.1"
- resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12"
- integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==
+clsx@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.0.tgz#e851283bcb5c80ee7608db18487433f7b23f77cb"
+ integrity sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==
codemirror@^6.0.0, codemirror@^6.0.1:
version "6.0.1"
@@ -8958,10 +8975,10 @@ destroy@1.2.0:
resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015"
integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==
-detect-element-overflow@^1.2.0:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/detect-element-overflow/-/detect-element-overflow-1.2.0.tgz#86e504292ffedc3aef813395fbdf0261aaf6afa9"
- integrity sha512-Jtr9ivYPhpd9OJux+hjL0QjUKiS1Ghgy8tvIufUjFslQgIWvgGr4mn57H190APbKkiOmXnmtMI6ytaKzMusecg==
+detect-element-overflow@^1.4.0:
+ version "1.4.2"
+ resolved "https://registry.yarnpkg.com/detect-element-overflow/-/detect-element-overflow-1.4.2.tgz#2e48509e5aa07647f4335b5f4f52c146b92f99c5"
+ integrity sha512-4m6cVOtvm/GJLjo7WFkPfwXoEIIbM7GQwIh4WEa4g7IsNi1YzwUsGL5ApNLrrHL29bHeNeQ+/iZhw+YHqgE2Fw==
detect-file@^1.0.0:
version "1.0.0"
@@ -10649,11 +10666,12 @@ get-tsconfig@^4.5.0:
dependencies:
resolve-pkg-maps "^1.0.0"
-get-user-locale@^1.2.0, get-user-locale@^1.4.0:
- version "1.5.1"
- resolved "https://registry.yarnpkg.com/get-user-locale/-/get-user-locale-1.5.1.tgz#18a9ba2cfeed0e713ea00968efa75d620523a5ea"
- integrity sha512-WiNpoFRcHn1qxP9VabQljzGwkAQDrcpqUtaP0rNBEkFxJdh4f3tik6MfZsMYZc+UgQJdGCxWEjL9wnCUlRQXag==
+get-user-locale@^2.2.1:
+ version "2.3.1"
+ resolved "https://registry.yarnpkg.com/get-user-locale/-/get-user-locale-2.3.1.tgz#fc7319429c8a70fac01b3b2a0b08b0c71c1d3fe2"
+ integrity sha512-VEvcsqKYx7zhZYC1CjecrDC5ziPSpl1gSm0qFFJhHSGDrSC+x4+p1KojWC/83QX//j476gFhkVXP/kNUc9q+bQ==
dependencies:
+ "@types/lodash.memoize" "^4.1.7"
lodash.memoize "^4.1.1"
giget@^1.0.0:
@@ -12557,10 +12575,10 @@ make-dir@^4.0.0:
dependencies:
semver "^7.5.3"
-make-event-props@^1.1.0:
- version "1.3.0"
- resolved "https://registry.yarnpkg.com/make-event-props/-/make-event-props-1.3.0.tgz#2434cb390d58bcf40898d009ef5b1f936de9671b"
- integrity sha512-oWiDZMcVB1/A487251hEWza1xzgCzl6MXxe9aF24l5Bt9N9UEbqTqKumEfuuLhmlhRZYnc+suVvW4vUs8bwO7Q==
+make-event-props@^1.6.0:
+ version "1.6.2"
+ resolved "https://registry.yarnpkg.com/make-event-props/-/make-event-props-1.6.2.tgz#c8e0e48eb28b9b808730de38359f6341de7ec5a2"
+ integrity sha512-iDwf7mA03WPiR8QxvcVHmVWEPfMY1RZXerDVNCRYW7dUr2ppH3J58Rwb39/WG39yTZdRSxr3x+2v22tvI0VEvA==
make-iterator@^1.0.0:
version "1.0.1"
@@ -14438,15 +14456,16 @@ rc-util@^5.27.0:
"@babel/runtime" "^7.18.3"
react-is "^16.12.0"
-react-calendar@^4.0.0:
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/react-calendar/-/react-calendar-4.0.0.tgz#99ad73dd0c7c5b25aa535a5fdeee3d71bfe45faa"
- integrity sha512-y9Q5Oo3Mq869KExbOCP3aJ3hEnRZKZ0TqUa9QU1wJGgDZFrW1qTaWp5v52oZpmxTTrpAMTUcUGaC0QJcO1f8Nw==
+react-calendar@^4.6.0, react-calendar@^4.8.0:
+ version "4.8.0"
+ resolved "https://registry.yarnpkg.com/react-calendar/-/react-calendar-4.8.0.tgz#61edbba6d17e7ef8a8012de9143b5e5ff41104c8"
+ integrity sha512-qFgwo+p58sgv1QYMI1oGNaop90eJVKuHTZ3ZgBfrrpUb+9cAexxsKat0sAszgsizPMVo7vOXedV7Lqa0GQGMvA==
dependencies:
- "@wojtekmaj/date-utils" "^1.0.2"
- clsx "^1.2.1"
- get-user-locale "^1.2.0"
+ "@wojtekmaj/date-utils" "^1.1.3"
+ clsx "^2.0.0"
+ get-user-locale "^2.2.1"
prop-types "^15.6.0"
+ warning "^4.0.0"
react-clientside-effect@^1.2.6:
version "1.2.6"
@@ -14455,14 +14474,14 @@ react-clientside-effect@^1.2.6:
dependencies:
"@babel/runtime" "^7.12.13"
-react-clock@^4.0.0:
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/react-clock/-/react-clock-4.0.0.tgz#29d087159154d789c6c93048ae47534b7a7b3fbb"
- integrity sha512-CBevN5B40TDUegSWzXk6bSwXhYzyerL9JGTme8GMAY0zO4FiEhVTGN1uzgC0rn/oSAMJw3M5wSf/OJpp9vcN2Q==
+react-clock@^4.5.0:
+ version "4.6.0"
+ resolved "https://registry.yarnpkg.com/react-clock/-/react-clock-4.6.0.tgz#61aea8af2b63883e79d258f723abd77d3183a413"
+ integrity sha512-Yz+vwrwrfVRSBw3BdmX/Mc7mVdQYJQ5Pi00qDzGLyLNWQuEmp5PC2oYjQAsDalLjekeDwBIGD7OLcKnkAp1kcw==
dependencies:
- "@wojtekmaj/date-utils" "^1.0.0"
- clsx "^1.2.1"
- get-user-locale "^1.4.0"
+ "@wojtekmaj/date-utils" "^1.5.0"
+ clsx "^2.0.0"
+ get-user-locale "^2.2.1"
prop-types "^15.6.0"
react-colorful@^5.1.2:
@@ -14470,36 +14489,35 @@ react-colorful@^5.1.2:
resolved "https://registry.yarnpkg.com/react-colorful/-/react-colorful-5.6.1.tgz#7dc2aed2d7c72fac89694e834d179e32f3da563b"
integrity sha512-1exovf0uGTGyq5mXQT0zgQ80uvj2PCwvF8zY1RN9/vbJVSjSo3fsB/4L3ObbF7u70NduSiK4xu4Y6q1MHoUGEw==
-react-date-picker@^9.2.0:
- version "9.2.0"
- resolved "https://registry.yarnpkg.com/react-date-picker/-/react-date-picker-9.2.0.tgz#ee194a694fa9891d93e4d40e76fbcdae7eafbe86"
- integrity sha512-kAE7HFLq1ic4pS0Pk9SyPTjejIfjTyPov04a2eZzLxfZh8ss8EPaaaX7bBUP4RUCkbxHpR0P4UHloD0/fFDCZw==
+react-date-picker@^10.5.0:
+ version "10.6.0"
+ resolved "https://registry.yarnpkg.com/react-date-picker/-/react-date-picker-10.6.0.tgz#b49ad556cff7009255a8dcbd0f59f4d9e9fdeab1"
+ integrity sha512-db5lcmU/52X8ur8SU1QU3PYBiaDG5SbzZDlqWk3YruPx5Ti9w6UpqCRsd1TXycVla9Ut2I3Qb4BUe27jxSwHeg==
dependencies:
- "@types/react-calendar" "^3.0.0"
- "@wojtekmaj/date-utils" "^1.0.3"
- clsx "^1.2.1"
- get-user-locale "^1.2.0"
- make-event-props "^1.1.0"
+ "@wojtekmaj/date-utils" "^1.1.3"
+ clsx "^2.0.0"
+ get-user-locale "^2.2.1"
+ make-event-props "^1.6.0"
prop-types "^15.6.0"
- react-calendar "^4.0.0"
- react-fit "^1.4.0"
- update-input-width "^1.2.2"
+ react-calendar "^4.6.0"
+ react-fit "^1.7.0"
+ update-input-width "^1.4.0"
-react-datetime-picker@^4.2.0:
- version "4.2.0"
- resolved "https://registry.yarnpkg.com/react-datetime-picker/-/react-datetime-picker-4.2.0.tgz#747b86013fa59ce6f9d201317f0df486a343769a"
- integrity sha512-5K7s4yVpG7e/Y3HspF2iHdaa2OYymqnoV2aUho5J6fQOtVfkOEkMJOwSG4PbSHisq0Xz3CXgOjn88X0GscZoAw==
+react-datetime-picker@^5.6.0:
+ version "5.6.0"
+ resolved "https://registry.yarnpkg.com/react-datetime-picker/-/react-datetime-picker-5.6.0.tgz#2a0bfa041f3333cc9afca349eb8661287aa9abfe"
+ integrity sha512-zbYSuYuiRj4/6lR9xGjAgw7V4gpTGtzOwZIfw1TONj6K6OKuaDreczS+6ijJjwLYlMcx8V+Sw1IMP+K059wnnA==
dependencies:
- "@wojtekmaj/date-utils" "^1.0.3"
- clsx "^1.2.1"
- get-user-locale "^1.2.0"
- make-event-props "^1.1.0"
+ "@wojtekmaj/date-utils" "^1.1.3"
+ clsx "^2.0.0"
+ get-user-locale "^2.2.1"
+ make-event-props "^1.6.0"
prop-types "^15.6.0"
- react-calendar "^4.0.0"
- react-clock "^4.0.0"
- react-date-picker "^9.2.0"
- react-fit "^1.4.0"
- react-time-picker "^5.2.0"
+ react-calendar "^4.6.0"
+ react-clock "^4.5.0"
+ react-date-picker "^10.5.0"
+ react-fit "^1.7.0"
+ react-time-picker "^6.5.0"
react-docgen-typescript-plugin@^1.0.5:
version "1.0.5"
@@ -14558,12 +14576,12 @@ react-fast-compare@^2.0.1:
resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-2.0.4.tgz#e84b4d455b0fec113e0402c329352715196f81f9"
integrity sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==
-react-fit@^1.4.0:
- version "1.4.0"
- resolved "https://registry.yarnpkg.com/react-fit/-/react-fit-1.4.0.tgz#6b6e3c75215561cc3cfb9854a6811b4347628666"
- integrity sha512-cf9sFKbr1rlTB9fNIKE5Uy4NCMUOqrX2mdJ69V4RtmV4KubPdtnbIP1tEar16GXaToCRr7I7c9d2wkTNk9TV5g==
+react-fit@^1.7.0:
+ version "1.7.1"
+ resolved "https://registry.yarnpkg.com/react-fit/-/react-fit-1.7.1.tgz#95259e90cfa9c4d243a8013d03ea59c9c5c51a6f"
+ integrity sha512-y/TYovCCBzfIwRJsbLj0rH4Es40wPQhU5GPPq9GlbdF09b0OdzTdMSkBza0QixSlgFzTm6dkM7oTFzaVvaBx+w==
dependencies:
- detect-element-overflow "^1.2.0"
+ detect-element-overflow "^1.4.0"
prop-types "^15.6.0"
tiny-warning "^1.0.0"
@@ -14616,6 +14634,11 @@ react-is@^18.0.0:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b"
integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==
+react-json-view-lite@^1.2.1:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/react-json-view-lite/-/react-json-view-lite-1.2.1.tgz#c59a0bea4ede394db331d482ee02e293d38f8218"
+ integrity sha512-Itc0g86fytOmKZoIoJyGgvNqohWSbh3NXIKNgH6W6FT9PC1ck4xas1tT3Rr/b3UlFXyA9Jjaw9QSXdZy2JwGMQ==
+
react-query@^3.33.4:
version "3.34.4"
resolved "https://registry.yarnpkg.com/react-query/-/react-query-3.34.4.tgz#da926717683fd9e9e310d46ab6f60f76a80ffaae"
@@ -14699,19 +14722,19 @@ react-style-singleton@^2.2.1:
invariant "^2.2.4"
tslib "^2.0.0"
-react-time-picker@^5.2.0:
- version "5.2.0"
- resolved "https://registry.yarnpkg.com/react-time-picker/-/react-time-picker-5.2.0.tgz#e2c49a2b852b63009627084d674705d262f1b7f8"
- integrity sha512-lM3gISzmPWsG3pZ+D2P/QNF0lrRW9qwpv9mejvwOAlVCuwX7O3nXDHE7gShi/aAd6i9YdU53r3gtDdYg2k+IRQ==
+react-time-picker@^6.5.0:
+ version "6.6.0"
+ resolved "https://registry.yarnpkg.com/react-time-picker/-/react-time-picker-6.6.0.tgz#5c5264d053dff22cbed9ad0ba927b1ea786c3a49"
+ integrity sha512-1PCetwrYcFNXALU9Oml32NAcFgPCPZLB5U8AQEgBoavJw61YmA0B0OSto6cOz9syGmPdcLZhDqRtN+EkZji+3w==
dependencies:
- "@wojtekmaj/date-utils" "^1.0.0"
- clsx "^1.2.1"
- get-user-locale "^1.2.0"
- make-event-props "^1.1.0"
+ "@wojtekmaj/date-utils" "^1.1.3"
+ clsx "^2.0.0"
+ get-user-locale "^2.2.1"
+ make-event-props "^1.6.0"
prop-types "^15.6.0"
- react-clock "^4.0.0"
- react-fit "^1.4.0"
- update-input-width "^1.2.2"
+ react-clock "^4.5.0"
+ react-fit "^1.7.0"
+ update-input-width "^1.4.0"
react-transition-group@^4.3.0:
version "4.4.2"
@@ -16814,10 +16837,10 @@ update-browserslist-db@^1.0.13:
escalade "^3.1.1"
picocolors "^1.0.0"
-update-input-width@^1.2.2:
- version "1.2.2"
- resolved "https://registry.yarnpkg.com/update-input-width/-/update-input-width-1.2.2.tgz#9a6a35858ae8e66fbfe0304437b23a4934fc7d37"
- integrity sha512-6QwD9ZVSXb96PxOZ01DU0DJTPwQGY7qBYgdniZKJN02Xzom2m+9J6EPxMbefskqtj4x78qbe5psDSALq9iNEYg==
+update-input-width@^1.4.0:
+ version "1.4.2"
+ resolved "https://registry.yarnpkg.com/update-input-width/-/update-input-width-1.4.2.tgz#49d327a39395185b0fd440b9c3b1d6f81173655c"
+ integrity sha512-/p0XLhrQQQ4bMWD7bL9duYObwYCO1qGr8R19xcMmoMSmXuQ7/1//veUnCObQ7/iW6E2pGS6rFkS4TfH4ur7e/g==
upper-case-first@^2.0.2:
version "2.0.2"
@@ -17079,6 +17102,13 @@ walker@^1.0.8:
dependencies:
makeerror "1.0.12"
+warning@^4.0.0:
+ version "4.0.3"
+ resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3"
+ integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==
+ dependencies:
+ loose-envify "^1.0.0"
+
watchpack@^2.2.0, watchpack@^2.4.0:
version "2.4.0"
resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.0.tgz#fa33032374962c78113f93c7f2fb4c54c9862a5d"