Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions apps/app/public/static/locales/en_US/admin.json
Original file line number Diff line number Diff line change
Expand Up @@ -870,6 +870,19 @@
"log_type": "https://docs.growi.org/en/admin-guide/admin-cookbook/audit-log-setup.html#log-types"
}
},
"audit_log_export": {
"export": "Export",
"export_audit_log": "Export Audit Log",
"modal_description": "Select filters to export audit log data. The exported file will be delivered via in-app notification.",
"notification_info": "When the export is complete, you will receive a notification with a download link in the app.",
"export_started": "Audit log export has been started",
"notification_message": "You will receive a notification when the export is complete",
"duplicate_job_error": "Duplicate job in progress (created at: {{createdAt}})",
"export_failed": "Failed to start export: {{message}}",
"request_failed": "Request failed: {{error}}",
"start_export": "Start Export",
"exporting": "Exporting..."
},
"g2g_data_transfer": {
"transfer_data_to_another_growi": "Transfer data from this GROWI to another GROWI",
"advanced_options": "Advanced options",
Expand Down
13 changes: 13 additions & 0 deletions apps/app/public/static/locales/ja_JP/admin.json
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

全言語対応する後続タスクは作ってある?

Original file line number Diff line number Diff line change
Expand Up @@ -879,6 +879,19 @@
"log_type": "https://docs.growi.org/ja/admin-guide/admin-cookbook/audit-log-setup.html#log-types"
}
},
"audit_log_export": {
"export": "エクスポート",
"export_audit_log": "監査ログのエクスポート",
"modal_description": "監査ログデータをエクスポートするためのフィルターを選択してください。エクスポートされたファイルはアプリ内通知で配信されます。",
"notification_info": "エクスポートが完了すると、アプリ内でダウンロードリンク付きの通知を受け取ります。",
"export_started": "監査ログのエクスポートを開始しました",
"notification_message": "エクスポートが完了すると通知が届きます",
"duplicate_job_error": "重複するジョブが進行中です(作成日時: {{createdAt}})",
"export_failed": "エクスポートの開始に失敗しました: {{message}}",
"request_failed": "リクエストに失敗しました: {{error}}",
"start_export": "エクスポート開始",
"exporting": "エクスポート中..."
},
"g2g_data_transfer": {
"transfer_data_to_another_growi": "このGROWIのデータを別GROWIへ移行する",
"advanced_options": "詳細オプション",
Expand Down
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

このファイル魔改造されてる気がする

mousedown イベントハンドラや stopPropagation() が必要になるケースは稀なので、なにか変なことをやっていないか要チェック

Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { FC } from 'react';
import React, { useMemo, useCallback } from 'react';
import React, { useMemo, useCallback, useState, useRef, useEffect } from 'react';

import { useTranslation } from 'react-i18next';

Expand All @@ -22,6 +22,9 @@ export const SelectActionDropdown: FC<Props> = (props: Props) => {
actionMap, availableActions, onChangeAction, onChangeMultipleAction,
} = props;

const [isDropdownOpen, setIsDropdownOpen] = useState(false);
const dropdownRef = useRef<HTMLDivElement>(null);

const dropdownItems = useMemo<Array<{actionCategory: SupportedActionCategoryType, actions: SupportedActionType[]}>>(() => {
return (
[
Expand Down Expand Up @@ -77,15 +80,40 @@ export const SelectActionDropdown: FC<Props> = (props: Props) => {
}
}, [onChangeMultipleAction]);

// Handle click outside to close dropdown
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
setIsDropdownOpen(false);
}
};

if (isDropdownOpen) {
document.addEventListener('mousedown', handleClickOutside);
}

return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, [isDropdownOpen]);

const toggleDropdown = useCallback(() => {
setIsDropdownOpen(!isDropdownOpen);
}, [isDropdownOpen]);

return (
<div className="btn-group me-2 admin-audit-log">
<button className="btn btn-outline-secondary dropdown-toggle" type="button" id="dropdownMenuButton" data-bs-toggle="dropdown">
<div className="btn-group me-2 admin-audit-log" ref={dropdownRef}>
<button
className="btn btn-outline-secondary dropdown-toggle"
type="button"
onClick={toggleDropdown}
>
<span className="material-symbols-outlined me-1">bolt</span>{t('admin:audit_log_management.action')}
</button>
<ul className="dropdown-menu select-action-dropdown" aria-labelledby="dropdownMenuButton">
<ul className={`dropdown-menu select-action-dropdown ${isDropdownOpen ? 'show' : ''}`} onClick={(e) => e.stopPropagation()}>
{dropdownItems.map(item => (
<div key={item.actionCategory}>
<div className="dropdown-item">
<div className="dropdown-item" onClick={(e) => e.stopPropagation()}>
<div className="px-2 m-0">
<input
type="checkbox"
Expand All @@ -98,7 +126,7 @@ export const SelectActionDropdown: FC<Props> = (props: Props) => {
</div>
{
item.actions.map(action => (
<div className="dropdown-item" key={action}>
<div className="dropdown-item" key={action} onClick={(e) => e.stopPropagation()}>
<div className="px-4 m-0">
<input
type="checkbox"
Expand Down
11 changes: 8 additions & 3 deletions apps/app/src/client/components/Admin/AuditLogManagement.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import { DateRangePicker } from './AuditLog/DateRangePicker';
import { SearchUsernameTypeahead } from './AuditLog/SearchUsernameTypeahead';
import { SelectActionDropdown } from './AuditLog/SelectActionDropdown';

import { AuditLogExportButton } from '~/features/audit-log-export/client/components/AuditLogExportButton';

const formatDate = (date: Date | null) => {
if (date == null) {
return '';
Expand Down Expand Up @@ -162,9 +164,12 @@ export const AuditLogManagement: FC = () => {
{isSettingPage ? t('audit_log_management.audit_log_settings') : t('audit_log_management.audit_log')}
</span>
{ !isSettingPage && (
<button type="button" className="btn btn-sm ms-auto grw-btn-reload" onClick={reloadButtonPushedHandler}>
<span className="material-symbols-outlined">refresh</span>
</button>
<div className="d-flex ms-auto">
<button type="button" className="btn btn-sm grw-btn-reload me-2" onClick={reloadButtonPushedHandler}>
<span className="material-symbols-outlined">refresh</span>
</button>
<AuditLogExportButton />
</div>
)}
</h2>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import React from 'react';

import { isPopulated, type HasObjectId } from '@growi/core';
import { useTranslation } from 'react-i18next';

import type { IAuditLogExportJobHasId } from '~/features/audit-log-export/interfaces/audit-log-bulk-export';
import { SupportedAction, SupportedTargetModel } from '~/interfaces/activity';
import type { IInAppNotification } from '~/interfaces/in-app-notification';
import * as auditLogExportJobSerializers from '~/models/serializers/in-app-notification-snapshot/audit-log-export-job';

import { ModelNotification } from './ModelNotification';
import { useActionMsgAndIconForModelNotification } from './useActionAndMsg';

import type { ModelNotificationUtils } from '.';


export const useAuditLogExportJobModelNotification = (notification: IInAppNotification & HasObjectId): ModelNotificationUtils | null => {

const { t } = useTranslation();
const { actionMsg, actionIcon } = useActionMsgAndIconForModelNotification(notification);

const isAuditLogExportJobModelNotification = (
notification: IInAppNotification & HasObjectId,
): notification is IInAppNotification<IAuditLogExportJobHasId> & HasObjectId => {
return notification.targetModel === SupportedTargetModel.MODEL_AUDIT_LOG_EXPORT_JOB;
};

if (!isAuditLogExportJobModelNotification(notification)) {
return null;
}

const actionUsers = notification.user.username;

try {
notification.parsedSnapshot = auditLogExportJobSerializers.parseSnapshot(notification.snapshot);
} catch (error) {
console.error('Error parsing audit log export job notification:', error, notification);
return null;
}

const getSubMsg = (): JSX.Element => {
if (notification.action === SupportedAction.ACTION_AUDIT_LOG_EXPORT_COMPLETED && notification.target == null) {
return <div className="text-danger"><small>Audit log export download has expired</small></div>;
}
if (notification.action === SupportedAction.ACTION_AUDIT_LOG_EXPORT_JOB_EXPIRED) {
return <div className="text-danger"><small>Audit log export job has expired</small></div>;
}
return <></>;
};

const Notification = () => {
return (
<ModelNotification
notification={notification}
actionMsg={actionMsg}
actionIcon={actionIcon}
actionUsers={actionUsers}
hideActionUsers
subMsg={getSubMsg()}
/>
);
};

const clickLink = (notification.action === SupportedAction.ACTION_AUDIT_LOG_EXPORT_COMPLETED
&& notification.target?.attachment != null && isPopulated(notification.target?.attachment))
? notification.target.attachment.downloadPathProxied : undefined;

return {
Notification,
clickLink,
isDisabled: notification.target == null,
};

};
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type { HasObjectId } from '@growi/core';
import type { IInAppNotification } from '~/interfaces/in-app-notification';


import { useAuditLogExportJobModelNotification } from './AuditLogExportJobModelNotification';
import { usePageBulkExportJobModelNotification } from './PageBulkExportJobModelNotification';
import { usePageModelNotification } from './PageModelNotification';
import { useUserModelNotification } from './UserModelNotification';
Expand All @@ -23,8 +24,9 @@ export const useModelNotification = (notification: IInAppNotification & HasObjec
const pageModelNotificationUtils = usePageModelNotification(notification);
const userModelNotificationUtils = useUserModelNotification(notification);
const pageBulkExportResultModelNotificationUtils = usePageBulkExportJobModelNotification(notification);
const auditLogExportResultModelNotificationUtils = useAuditLogExportJobModelNotification(notification);

const modelNotificationUtils = pageModelNotificationUtils ?? userModelNotificationUtils ?? pageBulkExportResultModelNotificationUtils;
const modelNotificationUtils = pageModelNotificationUtils ?? userModelNotificationUtils ?? pageBulkExportResultModelNotificationUtils ?? auditLogExportResultModelNotificationUtils;


return modelNotificationUtils;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,15 @@ export const useActionMsgAndIconForModelNotification = (notification: IInAppNoti
actionMsg = 'export failed for';
actionIcon = 'error';
break;
case SupportedAction.ACTION_AUDIT_LOG_EXPORT_COMPLETED:
actionMsg = 'audit log export completed for';
actionIcon = 'download';
break;
case SupportedAction.ACTION_AUDIT_LOG_EXPORT_FAILED:
case SupportedAction.ACTION_AUDIT_LOG_EXPORT_JOB_EXPIRED:
actionMsg = 'audit log export failed for';
actionIcon = 'error';
break;
default:
actionMsg = '';
actionIcon = '';
Expand Down
Loading
Loading