Commit 751a54e2 authored by ThinhNC's avatar ThinhNC

feat(system-config): add sync from .env button and update 7-day retention UI

parent be29c7a3
......@@ -13,7 +13,9 @@ import {
AlertCircle,
RefreshCw,
Lock,
FileCode,
} from "lucide-react";
import { toast } from "sonner";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { useLanguage } from "@/providers/language-provider";
......@@ -24,6 +26,7 @@ import {
useUpdateSystemConfig,
useToggleFeatureFlag,
useDeleteSystemConfig,
useSyncEnvConfigs,
} from "@/hooks/use-system-config";
import {
CreateSystemConfigDto,
......@@ -99,6 +102,7 @@ export function SystemConfigsManagementView({
const updateMutation = useUpdateSystemConfig();
const toggleMutation = useToggleFeatureFlag();
const deleteMutation = useDeleteSystemConfig();
const syncEnvMutation = useSyncEnvConfigs();
// Sort toggle handler
const handleToggleSort = (column: string) => {
......@@ -173,23 +177,53 @@ export function SystemConfigsManagementView({
}
};
const handleSyncEnv = async () => {
try {
const res = await syncEnvMutation.mutateAsync();
toast.success(
res?.syncedCount !== undefined
? `${t.systemConfigs.toast.syncEnvSuccess} (${res.syncedCount})`
: t.systemConfigs.toast.syncEnvSuccess,
);
} catch (err: unknown) {
const errorMsg =
err instanceof Error ? err.message : t.systemConfigs.toast.error;
toast.error(errorMsg);
}
};
const handleSubmitCreate = async (dto: CreateSystemConfigDto) => {
try {
await createMutation.mutateAsync(dto);
toast.success(t.systemConfigs.toast.createSuccess);
setIsFormModalOpen(false);
} catch {
toast.error(t.systemConfigs.toast.error);
}
};
const handleSubmitUpdate = async (
key: string,
dto: UpdateSystemConfigDto,
) => {
try {
await updateMutation.mutateAsync({ key, dto });
toast.success(t.systemConfigs.toast.updateSuccess);
setIsFormModalOpen(false);
} catch {
toast.error(t.systemConfigs.toast.error);
}
};
const handleConfirmDelete = async () => {
if (!deletingConfig) return;
try {
await deleteMutation.mutateAsync(deletingConfig.key);
toast.success(t.systemConfigs.toast.deleteSuccess);
setDeletingConfig(null);
} catch {
toast.error(t.systemConfigs.toast.error);
}
};
return (
......@@ -211,13 +245,26 @@ export function SystemConfigsManagementView({
</div>
{canManage && (
<div className="flex items-center gap-2">
<Button
type="button"
variant="outline"
onClick={handleSyncEnv}
disabled={syncEnvMutation.isPending}
className="rounded-2xl border-emerald-500/30 hover:bg-emerald-500/10 text-emerald-600 dark:text-emerald-400 text-xs font-bold px-4 py-2.5 shadow-sm cursor-pointer transition-all hover:scale-[1.02] flex items-center gap-2"
>
<FileCode className={`h-4 w-4 ${syncEnvMutation.isPending ? "animate-spin" : ""}`} />
<span>{t.systemConfigs.syncEnvBtn}</span>
</Button>
<Button
type="button"
onClick={handleOpenCreate}
className="rounded-2xl bg-emerald-600 hover:bg-emerald-700 text-white text-xs font-bold px-4 py-2.5 shadow-md shadow-emerald-600/20 cursor-pointer transition-all hover:scale-[1.02] flex items-center gap-2"
>
<Plus className="h-4 w-4" />
<span>{t.systemConfigs.createBtn}</span>
</Button>
</div>
)}
</div>
)}
......@@ -315,6 +362,22 @@ export function SystemConfigsManagementView({
</button>
</div>
{canManage && (
<button
onClick={handleSyncEnv}
disabled={syncEnvMutation.isPending}
className="flex items-center gap-1.5 h-9 px-3 rounded-2xl border border-emerald-500/30 bg-emerald-500/10 text-xs font-semibold text-emerald-600 dark:text-emerald-400 hover:bg-emerald-500/20 transition-all cursor-pointer disabled:opacity-50"
title={t.systemConfigs.syncEnvBtn}
>
<FileCode
className={`h-4 w-4 ${syncEnvMutation.isPending ? "animate-spin" : ""}`}
/>
<span className="hidden sm:inline">
{t.systemConfigs.syncEnvBtn}
</span>
</button>
)}
<button
onClick={() => refetch()}
disabled={isFetching}
......
......@@ -136,3 +136,19 @@ export function useDeleteSystemConfig() {
},
});
}
/**
* Hook đồng bộ toàn bộ giá trị cấu hình từ file .env
*/
export function useSyncEnvConfigs() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: () => systemConfigService.syncFromEnv(),
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: SYSTEM_CONFIG_QUERY_KEYS.all,
});
},
});
}
......@@ -1348,6 +1348,7 @@ export const translations = {
editBtn: "Chỉnh sửa",
deleteBtn: "Xóa cấu hình",
refreshBtn: "Làm mới",
syncEnvBtn: "Đồng bộ từ .env",
table: {
key: "Khóa cấu hình",
value: "Giá trị",
......@@ -1399,6 +1400,7 @@ export const translations = {
updateSuccess: "Đã cập nhật cấu hình thành công",
toggleSuccess: "Đã thay đổi trạng thái cờ tính năng",
deleteSuccess: "Đã xóa cấu hình thành công",
syncEnvSuccess: "Đã đồng bộ toàn bộ giá trị cấu hình từ file .env thành công",
error: "Đã có lỗi xảy ra, vui lòng thử lại",
},
},
......@@ -1447,7 +1449,7 @@ export const translations = {
jobLabel: "Tác vụ đang chọn:",
cronLabel: "Lịch biểu:",
paramsLabel: "Tham số tùy biến:",
paramsPlaceholder: '{\n "retentionDays": 30\n}',
paramsPlaceholder: '{\n "retentionDays": 7\n}',
paramsHint: "Để trống nếu muốn sử dụng tham số mặc định của hệ thống.",
executing: "Đang thực thi tác vụ...",
runBtn: "Bắt Đầu Chạy",
......@@ -2813,6 +2815,7 @@ export const translations = {
editBtn: "Edit",
deleteBtn: "Delete configuration",
refreshBtn: "Refresh",
syncEnvBtn: "Sync from .env",
table: {
key: "Configuration Key",
value: "Current Value",
......@@ -2864,6 +2867,7 @@ export const translations = {
updateSuccess: "Configuration updated successfully",
toggleSuccess: "Feature flag state toggled successfully",
deleteSuccess: "Configuration deleted successfully",
syncEnvSuccess: "Configurations synchronized from .env successfully",
error: "An error occurred, please try again",
},
},
......@@ -2912,7 +2916,7 @@ export const translations = {
jobLabel: "Selected Task:",
cronLabel: "Cron Schedule:",
paramsLabel: "Custom Parameters:",
paramsPlaceholder: '{\n "retentionDays": 30\n}',
paramsPlaceholder: '{\n "retentionDays": 7\n}',
paramsHint: "Leave blank to use the system default parameters.",
executing: "Executing task...",
runBtn: "Execute Now",
......
......@@ -96,4 +96,17 @@ export const systemConfigService = {
async deleteConfig(key: string): Promise<void> {
await apiClient.delete(`/system/configs/${encodeURIComponent(key)}`);
},
/**
* Đồng bộ toàn bộ cấu hình từ biến môi trường (.env) vào Database
* POST /api/proxy/system/configs/sync-env
*/
async syncFromEnv(): Promise<{ syncedCount: number; keys: string[] }> {
const response = await apiClient.post<{
success: boolean;
data: { syncedCount: number; keys: string[] };
message?: string;
}>("/system/configs/sync-env");
return response.data.data;
},
};
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment