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`
|
||||
so we have to force margin: 0
|
||||
*/
|
||||
.react-datetime-picker .react-calendar button {
|
||||
.react-daterange-picker__calendar .react-calendar button {
|
||||
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);
|
||||
color: var(--text-main-color);
|
||||
}
|
||||
|
||||
/* calendar nav buttons */
|
||||
.react-datetime-picker .react-calendar__navigation button:disabled {
|
||||
background-color: var(--bg-calendar-color);
|
||||
.react-daterange-picker__calendar .react-calendar__navigation button:disabled {
|
||||
background: var(--bg-calendar-color);
|
||||
@apply opacity-60;
|
||||
@apply brightness-95 th-dark:brightness-110;
|
||||
}
|
||||
.react-datetime-picker .react-calendar__navigation button:enabled:hover,
|
||||
.react-datetime-picker .react-calendar__navigation button:enabled:focus {
|
||||
background-color: var(--bg-daterangepicker-color);
|
||||
.react-daterange-picker__calendar .react-calendar__navigation button:enabled:hover,
|
||||
.react-daterange-picker__calendar .react-calendar__navigation button:enabled:focus {
|
||||
background: var(--bg-daterangepicker-color);
|
||||
}
|
||||
|
||||
/* date tile */
|
||||
.react-datetime-picker .react-calendar__tile:disabled {
|
||||
background-color: var(--bg-calendar-color);
|
||||
.react-daterange-picker__calendar .react-calendar__tile:disabled {
|
||||
background: var(--bg-calendar-color);
|
||||
@apply opacity-60;
|
||||
@apply brightness-95 th-dark:brightness-110;
|
||||
}
|
||||
.react-datetime-picker .react-calendar__tile:enabled:hover,
|
||||
.react-datetime-picker .react-calendar__tile:enabled:focus {
|
||||
background-color: var(--bg-daterangepicker-hover);
|
||||
.react-daterange-picker__calendar .react-calendar__tile:enabled:hover,
|
||||
.react-daterange-picker__calendar .react-calendar__tile:enabled:focus {
|
||||
background: var(--bg-daterangepicker-hover);
|
||||
}
|
||||
|
||||
/* today's date tile */
|
||||
.react-datetime-picker .react-calendar__tile--now {
|
||||
/* use background color to avoid white on yellow in dark/high contrast modes */
|
||||
.react-daterange-picker__calendar .react-calendar__tile--now {
|
||||
@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-datetime-picker .react-calendar__tile--now:enabled:focus {
|
||||
.react-daterange-picker__calendar .react-calendar__tile--now:enabled:hover,
|
||||
.react-daterange-picker__calendar .react-calendar__tile--now:enabled:focus {
|
||||
background: var(--bg-daterangepicker-hover);
|
||||
color: var(--text-daterangepicker-hover);
|
||||
}
|
||||
|
||||
/* 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);
|
||||
color: var(--text-daterangepicker-end-date);
|
||||
}
|
||||
.react-datetime-picker .react-calendar__tile--hasActive:enabled:hover,
|
||||
.react-datetime-picker .react-calendar__tile--hasActive:enabled:focus {
|
||||
.react-daterange-picker__calendar .react-calendar__tile--hasActive:enabled:hover,
|
||||
.react-daterange-picker__calendar .react-calendar__tile--hasActive:enabled:focus {
|
||||
background: var(--bg-daterangepicker-hover);
|
||||
color: var(--text-daterangepicker-hover);
|
||||
}
|
||||
|
||||
/* selected date tile */
|
||||
.react-datetime-picker .react-calendar__tile--active {
|
||||
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 {
|
||||
.react-daterange-picker__calendar .react-calendar__tile--active:enabled:hover,
|
||||
.react-daterange-picker__calendar .react-calendar__tile--active:enabled:focus {
|
||||
background: var(--bg-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 */
|
||||
.react-datetime-picker .react-calendar--selectRange .react-calendar__tile--hover {
|
||||
background-color: var(--bg-daterangepicker-in-range);
|
||||
.react-daterange-picker__calendar .react-calendar--selectRange .react-calendar__tile--hover {
|
||||
background: var(--bg-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;
|
||||
}
|
||||
|
||||
/* 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;
|
||||
action: string;
|
||||
context: string;
|
||||
id: number;
|
||||
payload: object;
|
||||
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 { ActivityLog } from './types';
|
||||
import { ActivityLogResponse, ActivityLogsResponse } from './types';
|
||||
|
||||
export const sortKeys = ['Context', 'Action', 'Timestamp', 'Username'] as const;
|
||||
export type SortKey = (typeof sortKeys)[number];
|
||||
|
@ -30,19 +30,18 @@ export function useActivityLogs(query: Query) {
|
|||
queryKey: ['activityLogs', query] as const,
|
||||
queryFn: () => fetchActivityLogs(query),
|
||||
keepPreviousData: true,
|
||||
select: (data) => ({
|
||||
...data,
|
||||
logs: decorateLogs(data.logs),
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
interface ActivityLogsResponse {
|
||||
logs: Array<ActivityLog>;
|
||||
totalCount: number;
|
||||
}
|
||||
|
||||
async function fetchActivityLogs(query: Query): Promise<ActivityLogsResponse> {
|
||||
try {
|
||||
if (!isBE) {
|
||||
return {
|
||||
logs: [{}, {}, {}, {}, {}] as Array<ActivityLog>,
|
||||
logs: [{}, {}, {}, {}, {}] as Array<ActivityLogResponse>,
|
||||
totalCount: 5,
|
||||
};
|
||||
}
|
||||
|
@ -56,3 +55,40 @@ async function fetchActivityLogs(query: Query): Promise<ActivityLogsResponse> {
|
|||
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,
|
||||
include: ['./app/**/*.test.ts', './app/**/*.test.tsx'],
|
||||
env: {
|
||||
PORTAINER_EDITION: 'CE',
|
||||
},
|
||||
},
|
||||
plugins: [svgr({ include: /\?c$/ }), tsconfigPaths()],
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue