mirror of https://github.com/portainer/portainer
fix(activity logs): decode base64 [BE-11418] (#172)
parent
f2e7680bf3
commit
4f708309af
|
@ -5,82 +5,110 @@
|
||||||
library css for buttons is overriden by `.widget .widget-body button`
|
library css for buttons is overriden by `.widget .widget-body button`
|
||||||
so we have to force margin: 0
|
so we have to force margin: 0
|
||||||
*/
|
*/
|
||||||
.react-datetime-picker .react-calendar button {
|
.react-daterange-picker__calendar .react-calendar button {
|
||||||
margin: 0 !important;
|
margin: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Extending Calendar.css from react-datetime-picker
|
Extending Calendar.css from react-daterange-picker__calendar
|
||||||
*/
|
*/
|
||||||
.react-datetime-picker .react-calendar {
|
.react-daterange-picker__calendar .react-calendar {
|
||||||
background: var(--bg-calendar-color);
|
background: var(--bg-calendar-color);
|
||||||
color: var(--text-main-color);
|
color: var(--text-main-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* calendar nav buttons */
|
/* calendar nav buttons */
|
||||||
.react-datetime-picker .react-calendar__navigation button:disabled {
|
.react-daterange-picker__calendar .react-calendar__navigation button:disabled {
|
||||||
background-color: var(--bg-calendar-color);
|
background: var(--bg-calendar-color);
|
||||||
@apply opacity-60;
|
@apply opacity-60;
|
||||||
@apply brightness-95 th-dark:brightness-110;
|
@apply brightness-95 th-dark:brightness-110;
|
||||||
}
|
}
|
||||||
.react-datetime-picker .react-calendar__navigation button:enabled:hover,
|
.react-daterange-picker__calendar .react-calendar__navigation button:enabled:hover,
|
||||||
.react-datetime-picker .react-calendar__navigation button:enabled:focus {
|
.react-daterange-picker__calendar .react-calendar__navigation button:enabled:focus {
|
||||||
background-color: var(--bg-daterangepicker-color);
|
background: var(--bg-daterangepicker-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* date tile */
|
/* date tile */
|
||||||
.react-datetime-picker .react-calendar__tile:disabled {
|
.react-daterange-picker__calendar .react-calendar__tile:disabled {
|
||||||
background-color: var(--bg-calendar-color);
|
background: var(--bg-calendar-color);
|
||||||
@apply opacity-60;
|
@apply opacity-60;
|
||||||
@apply brightness-95 th-dark:brightness-110;
|
@apply brightness-95 th-dark:brightness-110;
|
||||||
}
|
}
|
||||||
.react-datetime-picker .react-calendar__tile:enabled:hover,
|
.react-daterange-picker__calendar .react-calendar__tile:enabled:hover,
|
||||||
.react-datetime-picker .react-calendar__tile:enabled:focus {
|
.react-daterange-picker__calendar .react-calendar__tile:enabled:focus {
|
||||||
background-color: var(--bg-daterangepicker-hover);
|
background: var(--bg-daterangepicker-hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* today's date tile */
|
/* today's date tile */
|
||||||
.react-datetime-picker .react-calendar__tile--now {
|
.react-daterange-picker__calendar .react-calendar__tile--now {
|
||||||
/* use background color to avoid white on yellow in dark/high contrast modes */
|
|
||||||
@apply th-highcontrast:text-[color:var(--bg-calendar-color)] th-dark:text-[color:var(--bg-calendar-color)];
|
@apply th-highcontrast:text-[color:var(--bg-calendar-color)] th-dark:text-[color:var(--bg-calendar-color)];
|
||||||
|
border-radius: 0.25rem !important;
|
||||||
}
|
}
|
||||||
.react-datetime-picker .react-calendar__tile--now:enabled:hover,
|
.react-daterange-picker__calendar .react-calendar__tile--now:enabled:hover,
|
||||||
.react-datetime-picker .react-calendar__tile--now:enabled:focus {
|
.react-daterange-picker__calendar .react-calendar__tile--now:enabled:focus {
|
||||||
background: var(--bg-daterangepicker-hover);
|
background: var(--bg-daterangepicker-hover);
|
||||||
color: var(--text-daterangepicker-hover);
|
color: var(--text-daterangepicker-hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* probably date tile in range */
|
/* probably date tile in range */
|
||||||
.react-datetime-picker .react-calendar__tile--hasActive {
|
.react-daterange-picker__calendar .react-calendar__tile--hasActive {
|
||||||
background: var(--bg-daterangepicker-end-date);
|
background: var(--bg-daterangepicker-end-date);
|
||||||
color: var(--text-daterangepicker-end-date);
|
color: var(--text-daterangepicker-end-date);
|
||||||
}
|
}
|
||||||
.react-datetime-picker .react-calendar__tile--hasActive:enabled:hover,
|
.react-daterange-picker__calendar .react-calendar__tile--hasActive:enabled:hover,
|
||||||
.react-datetime-picker .react-calendar__tile--hasActive:enabled:focus {
|
.react-daterange-picker__calendar .react-calendar__tile--hasActive:enabled:focus {
|
||||||
background: var(--bg-daterangepicker-hover);
|
background: var(--bg-daterangepicker-hover);
|
||||||
color: var(--text-daterangepicker-hover);
|
color: var(--text-daterangepicker-hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* selected date tile */
|
.react-daterange-picker__calendar .react-calendar__tile--active:enabled:hover,
|
||||||
.react-datetime-picker .react-calendar__tile--active {
|
.react-daterange-picker__calendar .react-calendar__tile--active:enabled:focus {
|
||||||
background: var(--bg-daterangepicker-active);
|
|
||||||
color: var(--text-daterangepicker-active);
|
|
||||||
}
|
|
||||||
.react-datetime-picker .react-calendar__tile--active:enabled:hover,
|
|
||||||
.react-datetime-picker .react-calendar__tile--active:enabled:focus {
|
|
||||||
background: var(--bg-daterangepicker-hover);
|
background: var(--bg-daterangepicker-hover);
|
||||||
color: var(--text-daterangepicker-hover);
|
color: var(--text-daterangepicker-hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.react-daterange-picker__calendar
|
||||||
|
.react-calendar__month-view__days__day:hover:not(.react-daterange-picker__calendar .react-calendar__tile--hoverEnd):not(
|
||||||
|
.react-daterange-picker__calendar .react-calendar__tile--hoverStart
|
||||||
|
):not(.react-calendar__tile--active) {
|
||||||
|
border-radius: 0.25rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
/* on range select hover */
|
/* on range select hover */
|
||||||
.react-datetime-picker .react-calendar--selectRange .react-calendar__tile--hover {
|
.react-daterange-picker__calendar .react-calendar--selectRange .react-calendar__tile--hover {
|
||||||
background-color: var(--bg-daterangepicker-in-range);
|
background: var(--bg-daterangepicker-in-range);
|
||||||
color: var(--text-daterangepicker-in-range);
|
color: var(--text-daterangepicker-in-range);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Extending DateTimePicker.css from react-datetime-picker
|
Extending DateTimePicker.css from react-daterange-picker__calendar
|
||||||
*/
|
*/
|
||||||
.react-datetime-picker .react-datetime-picker--disabled {
|
.react-daterange-picker__calendar .react-daterange-picker__calendar--disabled {
|
||||||
@apply opacity-40;
|
@apply opacity-40;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* selected date tile */
|
||||||
|
.react-daterange-picker__calendar .react-calendar__tile--active {
|
||||||
|
background: var(--bg-daterangepicker-active) !important;
|
||||||
|
color: var(--text-daterangepicker-active) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-daterange-picker__calendar .react-calendar__tile--rangeStart:not(.react-calendar__tile--rangeEnd),
|
||||||
|
.react-daterange-picker__calendar .react-calendar__tile--hoverStart {
|
||||||
|
border-top-left-radius: 0.25rem;
|
||||||
|
border-bottom-left-radius: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-daterange-picker__calendar .react-calendar__tile--rangeEnd:not(.react-calendar__tile--rangeStart),
|
||||||
|
.react-daterange-picker__calendar .react-calendar__tile--hoverEnd {
|
||||||
|
border-top-right-radius: 0.25rem;
|
||||||
|
border-bottom-right-radius: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-daterange-picker__calendar .react-calendar__month-view__days__day--weekend {
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-calendar__tile--active.react-calendar__month-view__days__day--weekend {
|
||||||
|
color: var(--text-daterangepicker-active);
|
||||||
|
}
|
||||||
|
|
|
@ -1,8 +1,19 @@
|
||||||
export interface ActivityLog {
|
interface BaseActivityLog {
|
||||||
timestamp: number;
|
timestamp: number;
|
||||||
action: string;
|
action: string;
|
||||||
context: string;
|
context: string;
|
||||||
id: number;
|
id: number;
|
||||||
payload: object;
|
|
||||||
username: string;
|
username: string;
|
||||||
}
|
}
|
||||||
|
export interface ActivityLogResponse extends BaseActivityLog {
|
||||||
|
payload: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ActivityLog extends BaseActivityLog {
|
||||||
|
payload: string | object;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ActivityLogsResponse {
|
||||||
|
logs: Array<ActivityLogResponse>;
|
||||||
|
totalCount: number;
|
||||||
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import axios, { parseAxiosError } from '@/portainer/services/axios';
|
||||||
|
|
||||||
import { isBE } from '../../feature-flags/feature-flags.service';
|
import { isBE } from '../../feature-flags/feature-flags.service';
|
||||||
|
|
||||||
import { ActivityLog } from './types';
|
import { ActivityLogResponse, ActivityLogsResponse } from './types';
|
||||||
|
|
||||||
export const sortKeys = ['Context', 'Action', 'Timestamp', 'Username'] as const;
|
export const sortKeys = ['Context', 'Action', 'Timestamp', 'Username'] as const;
|
||||||
export type SortKey = (typeof sortKeys)[number];
|
export type SortKey = (typeof sortKeys)[number];
|
||||||
|
@ -30,19 +30,18 @@ export function useActivityLogs(query: Query) {
|
||||||
queryKey: ['activityLogs', query] as const,
|
queryKey: ['activityLogs', query] as const,
|
||||||
queryFn: () => fetchActivityLogs(query),
|
queryFn: () => fetchActivityLogs(query),
|
||||||
keepPreviousData: true,
|
keepPreviousData: true,
|
||||||
|
select: (data) => ({
|
||||||
|
...data,
|
||||||
|
logs: decorateLogs(data.logs),
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ActivityLogsResponse {
|
|
||||||
logs: Array<ActivityLog>;
|
|
||||||
totalCount: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchActivityLogs(query: Query): Promise<ActivityLogsResponse> {
|
async function fetchActivityLogs(query: Query): Promise<ActivityLogsResponse> {
|
||||||
try {
|
try {
|
||||||
if (!isBE) {
|
if (!isBE) {
|
||||||
return {
|
return {
|
||||||
logs: [{}, {}, {}, {}, {}] as Array<ActivityLog>,
|
logs: [{}, {}, {}, {}, {}] as Array<ActivityLogResponse>,
|
||||||
totalCount: 5,
|
totalCount: 5,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -56,3 +55,40 @@ async function fetchActivityLogs(query: Query): Promise<ActivityLogsResponse> {
|
||||||
throw parseAxiosError(err, 'Failed loading user activity logs csv');
|
throw parseAxiosError(err, 'Failed loading user activity logs csv');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decorates logs with the payload parsed from base64
|
||||||
|
*/
|
||||||
|
function decorateLogs(logs?: ActivityLogResponse[]) {
|
||||||
|
if (!logs || logs.length === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return logs.map((log) => ({
|
||||||
|
...log,
|
||||||
|
payload: parseBase64AsObject(log.payload),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseBase64AsObject(value: string): string | object {
|
||||||
|
if (!value) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return JSON.parse(safeAtob(value));
|
||||||
|
} catch (err) {
|
||||||
|
return safeAtob(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function safeAtob(value: string) {
|
||||||
|
if (!value) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return window.atob(value);
|
||||||
|
} catch (err) {
|
||||||
|
// If the payload is not base64 encoded, return the original value
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -14,6 +14,9 @@ export default defineConfig({
|
||||||
},
|
},
|
||||||
bail: 2,
|
bail: 2,
|
||||||
include: ['./app/**/*.test.ts', './app/**/*.test.tsx'],
|
include: ['./app/**/*.test.ts', './app/**/*.test.tsx'],
|
||||||
|
env: {
|
||||||
|
PORTAINER_EDITION: 'CE',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
plugins: [svgr({ include: /\?c$/ }), tsconfigPaths()],
|
plugins: [svgr({ include: /\?c$/ }), tsconfigPaths()],
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue