strapi-plugin-timeline 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,166 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { useState, useEffect } from "react";
3
+ import { Main, Typography, Box, Flex, Button, Checkbox, Field, TextInput, Badge } from "@strapi/design-system";
4
+ import { ArrowClockwise } from "@strapi/icons";
5
+ import { useFetchClient, useNotification, Layouts } from "@strapi/strapi/admin";
6
+ import { P as PLUGIN_ID } from "./index-DWXpMwlf.mjs";
7
+ const SchedulePage = () => {
8
+ const { get, put, post } = useFetchClient();
9
+ const { toggleNotification } = useNotification();
10
+ const [status, setStatus] = useState(null);
11
+ const [loading, setLoading] = useState(true);
12
+ const [saving, setSaving] = useState(false);
13
+ const [running, setRunning] = useState(false);
14
+ const [cleanupEnabled, setCleanupEnabled] = useState(false);
15
+ const [cleanupCron, setCleanupCron] = useState("0 * * * *");
16
+ useEffect(() => {
17
+ loadStatus();
18
+ }, []);
19
+ const loadStatus = async () => {
20
+ try {
21
+ const res = await get(`/${PLUGIN_ID}/schedule`);
22
+ const data = res.data.data;
23
+ setStatus(data);
24
+ setCleanupEnabled(data.cleanupEnabled ?? false);
25
+ setCleanupCron(data.cleanupCron || "0 * * * *");
26
+ } catch {
27
+ toggleNotification({ type: "danger", message: "Failed to load schedule status." });
28
+ } finally {
29
+ setLoading(false);
30
+ }
31
+ };
32
+ const handleSave = async () => {
33
+ setSaving(true);
34
+ try {
35
+ await put(`/${PLUGIN_ID}/schedule`, { cleanupEnabled, cleanupCron });
36
+ toggleNotification({
37
+ type: "success",
38
+ message: "Schedule saved. Restart the server for cron changes to take effect."
39
+ });
40
+ await loadStatus();
41
+ } catch {
42
+ toggleNotification({ type: "danger", message: "Failed to save schedule." });
43
+ } finally {
44
+ setSaving(false);
45
+ }
46
+ };
47
+ const handleRunNow = async () => {
48
+ setRunning(true);
49
+ try {
50
+ const res = await post(`/${PLUGIN_ID}/schedule/run`, {});
51
+ const results = res.data.data?.results || {};
52
+ const totalDeleted = Object.values(results).reduce(
53
+ (sum, r) => sum + (r.deleted || 0),
54
+ 0
55
+ );
56
+ toggleNotification({
57
+ type: "success",
58
+ message: totalDeleted > 0 ? `Cleanup complete: ${totalDeleted} entries removed.` : "Cleanup complete: no expired entries found."
59
+ });
60
+ await loadStatus();
61
+ } catch {
62
+ toggleNotification({ type: "danger", message: "Cleanup failed." });
63
+ } finally {
64
+ setRunning(false);
65
+ }
66
+ };
67
+ const formatDate = (dateStr) => {
68
+ try {
69
+ return new Date(dateStr).toLocaleString();
70
+ } catch {
71
+ return dateStr;
72
+ }
73
+ };
74
+ if (loading) {
75
+ return /* @__PURE__ */ jsx(Layouts.Root, { children: /* @__PURE__ */ jsx(Main, { padding: 8, children: /* @__PURE__ */ jsx(Typography, { variant: "alpha", children: "Loading…" }) }) });
76
+ }
77
+ const durationsConfigured = status?.durationsConfigured || {};
78
+ const hasDurations = Object.keys(durationsConfigured).length > 0;
79
+ return /* @__PURE__ */ jsx(Layouts.Root, { children: /* @__PURE__ */ jsxs(Main, { padding: 8, children: [
80
+ /* @__PURE__ */ jsx(Box, { paddingBottom: 6, children: /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", children: [
81
+ /* @__PURE__ */ jsxs(Box, { children: [
82
+ /* @__PURE__ */ jsx(Typography, { variant: "alpha", tag: "h1", children: "Cleanup Schedule" }),
83
+ /* @__PURE__ */ jsx(Typography, { variant: "epsilon", textColor: "neutral600", children: "Configure automated cleanup of old timeline entries based on retention duration." })
84
+ ] }),
85
+ /* @__PURE__ */ jsxs(Flex, { gap: 2, children: [
86
+ /* @__PURE__ */ jsx(
87
+ Button,
88
+ {
89
+ onClick: handleRunNow,
90
+ loading: running,
91
+ disabled: running || !hasDurations,
92
+ variant: "secondary",
93
+ startIcon: /* @__PURE__ */ jsx(ArrowClockwise, {}),
94
+ children: "Run Now"
95
+ }
96
+ ),
97
+ /* @__PURE__ */ jsx(Button, { onClick: handleSave, loading: saving, disabled: saving, children: "Save" })
98
+ ] })
99
+ ] }) }),
100
+ /* @__PURE__ */ jsxs(Box, { background: "neutral0", shadow: "tableShadow", padding: 6, borderRadius: "4px", marginBottom: 4, children: [
101
+ /* @__PURE__ */ jsx(Typography, { variant: "delta", tag: "h2", paddingBottom: 4, children: "Cron Schedule" }),
102
+ /* @__PURE__ */ jsx(Box, { paddingBottom: 4, children: /* @__PURE__ */ jsx(
103
+ Checkbox,
104
+ {
105
+ checked: cleanupEnabled,
106
+ onCheckedChange: (checked) => setCleanupEnabled(checked),
107
+ children: "Enable automatic cleanup"
108
+ }
109
+ ) }),
110
+ /* @__PURE__ */ jsx(Box, { paddingBottom: 2, children: /* @__PURE__ */ jsxs(Field.Root, { width: "100%", hint: "Standard cron format (minute hour day month weekday). Default: every hour. Changes require a server restart.", children: [
111
+ /* @__PURE__ */ jsx(Field.Label, { children: "Cron Expression" }),
112
+ /* @__PURE__ */ jsx(
113
+ TextInput,
114
+ {
115
+ name: "cleanupCron",
116
+ value: cleanupCron,
117
+ onChange: (e) => setCleanupCron(e.target.value),
118
+ placeholder: "0 * * * *",
119
+ disabled: !cleanupEnabled
120
+ }
121
+ ),
122
+ /* @__PURE__ */ jsx(Field.Hint, {})
123
+ ] }) })
124
+ ] }),
125
+ /* @__PURE__ */ jsxs(Box, { background: "neutral0", shadow: "tableShadow", padding: 6, borderRadius: "4px", marginBottom: 4, children: [
126
+ /* @__PURE__ */ jsx(Typography, { variant: "delta", tag: "h2", paddingBottom: 2, children: "Retention Durations" }),
127
+ /* @__PURE__ */ jsx(Typography, { variant: "omega", textColor: "neutral600", paddingBottom: 4, children: "Content types with a duration limit configured in Settings. Cleanup removes entries older than the configured duration." }),
128
+ !hasDurations ? /* @__PURE__ */ jsx(Box, { padding: 4, background: "neutral100", borderRadius: "4px", children: /* @__PURE__ */ jsx(Typography, { textColor: "neutral600", children: "No content types have a retention duration configured. Set duration limits in the Content Types settings." }) }) : /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsxs("table", { style: { width: "100%", borderCollapse: "collapse" }, children: [
129
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
130
+ /* @__PURE__ */ jsx("th", { style: { textAlign: "left", padding: "8px 12px", borderBottom: "1px solid #eaeaea" }, children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", textColor: "neutral600", children: "Content Type" }) }),
131
+ /* @__PURE__ */ jsx("th", { style: { textAlign: "left", padding: "8px 12px", borderBottom: "1px solid #eaeaea" }, children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", textColor: "neutral600", children: "Retention" }) })
132
+ ] }) }),
133
+ /* @__PURE__ */ jsx("tbody", { children: Object.entries(durationsConfigured).map(([uid, cfg]) => /* @__PURE__ */ jsxs("tr", { children: [
134
+ /* @__PURE__ */ jsx("td", { style: { padding: "8px 12px", borderBottom: "1px solid #f5f5f5" }, children: /* @__PURE__ */ jsx(Typography, { children: cfg.displayName || uid }) }),
135
+ /* @__PURE__ */ jsx("td", { style: { padding: "8px 12px", borderBottom: "1px solid #f5f5f5" }, children: /* @__PURE__ */ jsxs(Badge, { children: [
136
+ cfg.durationLimit,
137
+ " ",
138
+ cfg.durationUnit
139
+ ] }) })
140
+ ] }, uid)) })
141
+ ] }) })
142
+ ] }),
143
+ /* @__PURE__ */ jsxs(Box, { background: "neutral0", shadow: "tableShadow", padding: 6, borderRadius: "4px", children: [
144
+ /* @__PURE__ */ jsx(Typography, { variant: "delta", tag: "h2", paddingBottom: 2, children: "Last Cleanup" }),
145
+ !status?.lastCleanupAt ? /* @__PURE__ */ jsx(Box, { padding: 4, background: "neutral100", borderRadius: "4px", children: /* @__PURE__ */ jsx(Typography, { textColor: "neutral600", children: 'No cleanup has been run yet. Use "Run Now" to execute manually or enable the cron schedule.' }) }) : /* @__PURE__ */ jsxs(Box, { children: [
146
+ /* @__PURE__ */ jsxs(Flex, { gap: 2, paddingBottom: 3, alignItems: "center", children: [
147
+ /* @__PURE__ */ jsx(Typography, { fontWeight: "bold", children: "Last run:" }),
148
+ /* @__PURE__ */ jsx(Typography, { children: formatDate(status.lastCleanupAt) })
149
+ ] }),
150
+ status.lastCleanupResults && Object.keys(status.lastCleanupResults).length > 0 ? /* @__PURE__ */ jsxs("table", { style: { width: "100%", borderCollapse: "collapse" }, children: [
151
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
152
+ /* @__PURE__ */ jsx("th", { style: { textAlign: "left", padding: "8px 12px", borderBottom: "1px solid #eaeaea" }, children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", textColor: "neutral600", children: "Content Type" }) }),
153
+ /* @__PURE__ */ jsx("th", { style: { textAlign: "left", padding: "8px 12px", borderBottom: "1px solid #eaeaea" }, children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", textColor: "neutral600", children: "Entries Deleted" }) })
154
+ ] }) }),
155
+ /* @__PURE__ */ jsx("tbody", { children: Object.entries(status.lastCleanupResults).map(([uid, result]) => /* @__PURE__ */ jsxs("tr", { children: [
156
+ /* @__PURE__ */ jsx("td", { style: { padding: "8px 12px", borderBottom: "1px solid #f5f5f5" }, children: /* @__PURE__ */ jsx(Typography, { children: durationsConfigured[uid]?.displayName || uid }) }),
157
+ /* @__PURE__ */ jsx("td", { style: { padding: "8px 12px", borderBottom: "1px solid #f5f5f5" }, children: /* @__PURE__ */ jsx(Badge, { active: true, children: result.deleted }) })
158
+ ] }, uid)) })
159
+ ] }) : /* @__PURE__ */ jsx(Typography, { textColor: "neutral600", children: "No entries were deleted in the last cleanup run." })
160
+ ] })
161
+ ] })
162
+ ] }) });
163
+ };
164
+ export {
165
+ SchedulePage
166
+ };
@@ -0,0 +1,166 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const jsxRuntime = require("react/jsx-runtime");
4
+ const React = require("react");
5
+ const designSystem = require("@strapi/design-system");
6
+ const icons = require("@strapi/icons");
7
+ const admin = require("@strapi/strapi/admin");
8
+ const index = require("./index-BPC9ghPd.js");
9
+ const SchedulePage = () => {
10
+ const { get, put, post } = admin.useFetchClient();
11
+ const { toggleNotification } = admin.useNotification();
12
+ const [status, setStatus] = React.useState(null);
13
+ const [loading, setLoading] = React.useState(true);
14
+ const [saving, setSaving] = React.useState(false);
15
+ const [running, setRunning] = React.useState(false);
16
+ const [cleanupEnabled, setCleanupEnabled] = React.useState(false);
17
+ const [cleanupCron, setCleanupCron] = React.useState("0 * * * *");
18
+ React.useEffect(() => {
19
+ loadStatus();
20
+ }, []);
21
+ const loadStatus = async () => {
22
+ try {
23
+ const res = await get(`/${index.PLUGIN_ID}/schedule`);
24
+ const data = res.data.data;
25
+ setStatus(data);
26
+ setCleanupEnabled(data.cleanupEnabled ?? false);
27
+ setCleanupCron(data.cleanupCron || "0 * * * *");
28
+ } catch {
29
+ toggleNotification({ type: "danger", message: "Failed to load schedule status." });
30
+ } finally {
31
+ setLoading(false);
32
+ }
33
+ };
34
+ const handleSave = async () => {
35
+ setSaving(true);
36
+ try {
37
+ await put(`/${index.PLUGIN_ID}/schedule`, { cleanupEnabled, cleanupCron });
38
+ toggleNotification({
39
+ type: "success",
40
+ message: "Schedule saved. Restart the server for cron changes to take effect."
41
+ });
42
+ await loadStatus();
43
+ } catch {
44
+ toggleNotification({ type: "danger", message: "Failed to save schedule." });
45
+ } finally {
46
+ setSaving(false);
47
+ }
48
+ };
49
+ const handleRunNow = async () => {
50
+ setRunning(true);
51
+ try {
52
+ const res = await post(`/${index.PLUGIN_ID}/schedule/run`, {});
53
+ const results = res.data.data?.results || {};
54
+ const totalDeleted = Object.values(results).reduce(
55
+ (sum, r) => sum + (r.deleted || 0),
56
+ 0
57
+ );
58
+ toggleNotification({
59
+ type: "success",
60
+ message: totalDeleted > 0 ? `Cleanup complete: ${totalDeleted} entries removed.` : "Cleanup complete: no expired entries found."
61
+ });
62
+ await loadStatus();
63
+ } catch {
64
+ toggleNotification({ type: "danger", message: "Cleanup failed." });
65
+ } finally {
66
+ setRunning(false);
67
+ }
68
+ };
69
+ const formatDate = (dateStr) => {
70
+ try {
71
+ return new Date(dateStr).toLocaleString();
72
+ } catch {
73
+ return dateStr;
74
+ }
75
+ };
76
+ if (loading) {
77
+ return /* @__PURE__ */ jsxRuntime.jsx(admin.Layouts.Root, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Main, { padding: 8, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "alpha", children: "Loading…" }) }) });
78
+ }
79
+ const durationsConfigured = status?.durationsConfigured || {};
80
+ const hasDurations = Object.keys(durationsConfigured).length > 0;
81
+ return /* @__PURE__ */ jsxRuntime.jsx(admin.Layouts.Root, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Main, { padding: 8, children: [
82
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { paddingBottom: 6, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "space-between", alignItems: "center", children: [
83
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { children: [
84
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "alpha", tag: "h1", children: "Cleanup Schedule" }),
85
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "epsilon", textColor: "neutral600", children: "Configure automated cleanup of old timeline entries based on retention duration." })
86
+ ] }),
87
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, children: [
88
+ /* @__PURE__ */ jsxRuntime.jsx(
89
+ designSystem.Button,
90
+ {
91
+ onClick: handleRunNow,
92
+ loading: running,
93
+ disabled: running || !hasDurations,
94
+ variant: "secondary",
95
+ startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.ArrowClockwise, {}),
96
+ children: "Run Now"
97
+ }
98
+ ),
99
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { onClick: handleSave, loading: saving, disabled: saving, children: "Save" })
100
+ ] })
101
+ ] }) }),
102
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { background: "neutral0", shadow: "tableShadow", padding: 6, borderRadius: "4px", marginBottom: 4, children: [
103
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "delta", tag: "h2", paddingBottom: 4, children: "Cron Schedule" }),
104
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { paddingBottom: 4, children: /* @__PURE__ */ jsxRuntime.jsx(
105
+ designSystem.Checkbox,
106
+ {
107
+ checked: cleanupEnabled,
108
+ onCheckedChange: (checked) => setCleanupEnabled(checked),
109
+ children: "Enable automatic cleanup"
110
+ }
111
+ ) }),
112
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { paddingBottom: 2, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { width: "100%", hint: "Standard cron format (minute hour day month weekday). Default: every hour. Changes require a server restart.", children: [
113
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: "Cron Expression" }),
114
+ /* @__PURE__ */ jsxRuntime.jsx(
115
+ designSystem.TextInput,
116
+ {
117
+ name: "cleanupCron",
118
+ value: cleanupCron,
119
+ onChange: (e) => setCleanupCron(e.target.value),
120
+ placeholder: "0 * * * *",
121
+ disabled: !cleanupEnabled
122
+ }
123
+ ),
124
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Hint, {})
125
+ ] }) })
126
+ ] }),
127
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { background: "neutral0", shadow: "tableShadow", padding: 6, borderRadius: "4px", marginBottom: 4, children: [
128
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "delta", tag: "h2", paddingBottom: 2, children: "Retention Durations" }),
129
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", textColor: "neutral600", paddingBottom: 4, children: "Content types with a duration limit configured in Settings. Cleanup removes entries older than the configured duration." }),
130
+ !hasDurations ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { padding: 4, background: "neutral100", borderRadius: "4px", children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral600", children: "No content types have a retention duration configured. Set duration limits in the Content Types settings." }) }) : /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { children: /* @__PURE__ */ jsxRuntime.jsxs("table", { style: { width: "100%", borderCollapse: "collapse" }, children: [
131
+ /* @__PURE__ */ jsxRuntime.jsx("thead", { children: /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
132
+ /* @__PURE__ */ jsxRuntime.jsx("th", { style: { textAlign: "left", padding: "8px 12px", borderBottom: "1px solid #eaeaea" }, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", textColor: "neutral600", children: "Content Type" }) }),
133
+ /* @__PURE__ */ jsxRuntime.jsx("th", { style: { textAlign: "left", padding: "8px 12px", borderBottom: "1px solid #eaeaea" }, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", textColor: "neutral600", children: "Retention" }) })
134
+ ] }) }),
135
+ /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: Object.entries(durationsConfigured).map(([uid, cfg]) => /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
136
+ /* @__PURE__ */ jsxRuntime.jsx("td", { style: { padding: "8px 12px", borderBottom: "1px solid #f5f5f5" }, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { children: cfg.displayName || uid }) }),
137
+ /* @__PURE__ */ jsxRuntime.jsx("td", { style: { padding: "8px 12px", borderBottom: "1px solid #f5f5f5" }, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Badge, { children: [
138
+ cfg.durationLimit,
139
+ " ",
140
+ cfg.durationUnit
141
+ ] }) })
142
+ ] }, uid)) })
143
+ ] }) })
144
+ ] }),
145
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { background: "neutral0", shadow: "tableShadow", padding: 6, borderRadius: "4px", children: [
146
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "delta", tag: "h2", paddingBottom: 2, children: "Last Cleanup" }),
147
+ !status?.lastCleanupAt ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { padding: 4, background: "neutral100", borderRadius: "4px", children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral600", children: 'No cleanup has been run yet. Use "Run Now" to execute manually or enable the cron schedule.' }) }) : /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { children: [
148
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, paddingBottom: 3, alignItems: "center", children: [
149
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { fontWeight: "bold", children: "Last run:" }),
150
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { children: formatDate(status.lastCleanupAt) })
151
+ ] }),
152
+ status.lastCleanupResults && Object.keys(status.lastCleanupResults).length > 0 ? /* @__PURE__ */ jsxRuntime.jsxs("table", { style: { width: "100%", borderCollapse: "collapse" }, children: [
153
+ /* @__PURE__ */ jsxRuntime.jsx("thead", { children: /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
154
+ /* @__PURE__ */ jsxRuntime.jsx("th", { style: { textAlign: "left", padding: "8px 12px", borderBottom: "1px solid #eaeaea" }, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", textColor: "neutral600", children: "Content Type" }) }),
155
+ /* @__PURE__ */ jsxRuntime.jsx("th", { style: { textAlign: "left", padding: "8px 12px", borderBottom: "1px solid #eaeaea" }, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", textColor: "neutral600", children: "Entries Deleted" }) })
156
+ ] }) }),
157
+ /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: Object.entries(status.lastCleanupResults).map(([uid, result]) => /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
158
+ /* @__PURE__ */ jsxRuntime.jsx("td", { style: { padding: "8px 12px", borderBottom: "1px solid #f5f5f5" }, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { children: durationsConfigured[uid]?.displayName || uid }) }),
159
+ /* @__PURE__ */ jsxRuntime.jsx("td", { style: { padding: "8px 12px", borderBottom: "1px solid #f5f5f5" }, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { active: true, children: result.deleted }) })
160
+ ] }, uid)) })
161
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral600", children: "No entries were deleted in the last cleanup run." })
162
+ ] })
163
+ ] })
164
+ ] }) });
165
+ };
166
+ exports.SchedulePage = SchedulePage;
@@ -0,0 +1,259 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const jsxRuntime = require("react/jsx-runtime");
4
+ const React = require("react");
5
+ const designSystem = require("@strapi/design-system");
6
+ const icons = require("@strapi/icons");
7
+ const admin = require("@strapi/strapi/admin");
8
+ const index = require("./index-BPC9ghPd.js");
9
+ const ALL_ACTIONS = ["create", "update", "delete", "publish"];
10
+ const DURATION_UNITS = [
11
+ { value: "hours", label: "Hours" },
12
+ { value: "days", label: "Days" },
13
+ { value: "weeks", label: "Weeks" },
14
+ { value: "months", label: "Months" }
15
+ ];
16
+ const makeDefaultConfig = () => ({
17
+ enabled: true,
18
+ actions: [...ALL_ACTIONS],
19
+ entriesLimit: 0,
20
+ durationLimit: 0,
21
+ durationUnit: "days"
22
+ });
23
+ const SettingsPage = () => {
24
+ const { get, put } = admin.useFetchClient();
25
+ const { toggleNotification } = admin.useNotification();
26
+ const [contentTypes, setContentTypes] = React.useState([]);
27
+ const [configs, setConfigs] = React.useState({});
28
+ const [loading, setLoading] = React.useState(true);
29
+ const [saving, setSaving] = React.useState(false);
30
+ const [addingUid, setAddingUid] = React.useState("");
31
+ const [expandedItem, setExpandedItem] = React.useState("");
32
+ const [removeTarget, setRemoveTarget] = React.useState(null);
33
+ React.useEffect(() => {
34
+ loadData();
35
+ }, []);
36
+ const loadData = async () => {
37
+ try {
38
+ const [settingsRes, ctRes] = await Promise.all([
39
+ get(`/${index.PLUGIN_ID}/settings`),
40
+ get(`/${index.PLUGIN_ID}/content-types`)
41
+ ]);
42
+ const raw = settingsRes.data.data || {};
43
+ setConfigs(raw.contentTypeConfigs || {});
44
+ setContentTypes(ctRes.data.data || []);
45
+ } catch {
46
+ toggleNotification({ type: "danger", message: "Failed to load settings." });
47
+ } finally {
48
+ setLoading(false);
49
+ }
50
+ };
51
+ const handleSave = async () => {
52
+ setSaving(true);
53
+ try {
54
+ await put(`/${index.PLUGIN_ID}/settings`, { contentTypeConfigs: configs });
55
+ toggleNotification({ type: "success", message: "Settings saved successfully." });
56
+ } catch {
57
+ toggleNotification({ type: "danger", message: "Failed to save settings." });
58
+ } finally {
59
+ setSaving(false);
60
+ }
61
+ };
62
+ const availableContentTypes = contentTypes.filter((ct) => !configs[ct.uid]);
63
+ const addContentType = () => {
64
+ if (!addingUid) return;
65
+ setConfigs((prev) => ({ ...prev, [addingUid]: makeDefaultConfig() }));
66
+ setExpandedItem(addingUid);
67
+ setAddingUid("");
68
+ };
69
+ const removeContentType = (uid) => {
70
+ setConfigs((prev) => {
71
+ const next = { ...prev };
72
+ delete next[uid];
73
+ return next;
74
+ });
75
+ setExpandedItem((prev) => prev === uid ? "" : prev);
76
+ };
77
+ const updateConfig = (uid, field, value) => {
78
+ setConfigs((prev) => ({
79
+ ...prev,
80
+ [uid]: { ...prev[uid], [field]: value }
81
+ }));
82
+ };
83
+ const getDisplayName = (uid) => {
84
+ const ct = contentTypes.find((c) => c.uid === uid);
85
+ return ct?.displayName || uid;
86
+ };
87
+ const getKindLabel = (uid) => {
88
+ const ct = contentTypes.find((c) => c.uid === uid);
89
+ return ct?.kind === "singleType" ? "Single Type" : "Collection Type";
90
+ };
91
+ if (loading) {
92
+ return /* @__PURE__ */ jsxRuntime.jsx(admin.Layouts.Root, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Main, { padding: 8, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "alpha", children: "Loading…" }) }) });
93
+ }
94
+ const configuredUids = Object.keys(configs);
95
+ return /* @__PURE__ */ jsxRuntime.jsx(admin.Layouts.Root, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Main, { padding: 8, children: [
96
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { paddingBottom: 6, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "space-between", alignItems: "center", children: [
97
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { children: [
98
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "alpha", tag: "h1", children: "Timeline Settings" }),
99
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "epsilon", textColor: "neutral600", children: "Configure which content types to track and how to manage their history." })
100
+ ] }),
101
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Check, {}), onClick: handleSave, loading: saving, disabled: saving, children: "Save" })
102
+ ] }) }),
103
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { background: "neutral0", shadow: "tableShadow", padding: 6, borderRadius: "4px", children: [
104
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { paddingBottom: 4, children: [
105
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "delta", tag: "h2", children: "Tracked Content Types" }),
106
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", textColor: "neutral600", children: "Add content types to track and configure actions, entry limits, and retention duration for each." })
107
+ ] }),
108
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, paddingBottom: 4, alignItems: "flex-end", children: [
109
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { flex: "1", children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { width: "100%", children: [
110
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: "Add Content Type" }),
111
+ /* @__PURE__ */ jsxRuntime.jsx(
112
+ designSystem.SingleSelect,
113
+ {
114
+ value: addingUid,
115
+ onChange: (value) => setAddingUid(String(value)),
116
+ placeholder: "Select a content type to add...",
117
+ disabled: availableContentTypes.length === 0,
118
+ children: availableContentTypes.map((ct) => /* @__PURE__ */ jsxRuntime.jsxs(designSystem.SingleSelectOption, { value: ct.uid, children: [
119
+ ct.displayName,
120
+ " (",
121
+ ct.kind === "singleType" ? "Single" : "Collection",
122
+ ")"
123
+ ] }, ct.uid))
124
+ }
125
+ )
126
+ ] }) }),
127
+ /* @__PURE__ */ jsxRuntime.jsx(
128
+ designSystem.Button,
129
+ {
130
+ startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Plus, {}),
131
+ size: "L",
132
+ onClick: addContentType,
133
+ variant: "secondary",
134
+ disabled: !addingUid,
135
+ children: "Add"
136
+ }
137
+ )
138
+ ] }),
139
+ configuredUids.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { padding: 4, background: "neutral100", borderRadius: "4px", children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral600", children: "No content types configured. Select one above to start tracking changes." }) }) : /* @__PURE__ */ jsxRuntime.jsx(
140
+ designSystem.Accordion.Root,
141
+ {
142
+ value: expandedItem,
143
+ onValueChange: (value) => setExpandedItem(value),
144
+ children: configuredUids.map((uid) => {
145
+ const config = configs[uid];
146
+ return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Accordion.Item, { value: uid, children: [
147
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Accordion.Header, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Accordion.Trigger, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, alignItems: "center", children: [
148
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { fontWeight: "bold", children: getDisplayName(uid) }),
149
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral500", children: getKindLabel(uid) }),
150
+ !config.enabled && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "danger600", children: "(disabled)" })
151
+ ] }) }) }),
152
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Accordion.Content, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { padding: 4, children: [
153
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "space-between", alignItems: "center", paddingBottom: 4, children: [
154
+ /* @__PURE__ */ jsxRuntime.jsx(
155
+ designSystem.Switch,
156
+ {
157
+ onCheckedChange: (checked) => updateConfig(uid, "enabled", checked),
158
+ checked: config.enabled,
159
+ visibleLabels: true
160
+ }
161
+ ),
162
+ /* @__PURE__ */ jsxRuntime.jsx(
163
+ designSystem.IconButton,
164
+ {
165
+ onClick: () => setRemoveTarget(uid),
166
+ label: "Remove content type",
167
+ variant: "danger-light",
168
+ children: /* @__PURE__ */ jsxRuntime.jsx(icons.Trash, {})
169
+ }
170
+ )
171
+ ] }),
172
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Grid.Root, { gap: 4, gridCols: 2, children: [
173
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Grid.Item, { col: 2, s: 2, alignItems: "flex-start", children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { width: "100%", hint: "Which lifecycle actions to record. Empty = all actions.", children: [
174
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: "Tracked Actions" }),
175
+ /* @__PURE__ */ jsxRuntime.jsx(
176
+ designSystem.MultiSelect,
177
+ {
178
+ value: config.actions,
179
+ onChange: (values) => updateConfig(uid, "actions", values),
180
+ placeholder: "Select actions to track...",
181
+ withTags: true,
182
+ children: ALL_ACTIONS.map((action) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.MultiSelectOption, { value: action, children: action.charAt(0).toUpperCase() + action.slice(1) }, action))
183
+ }
184
+ ),
185
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Hint, {})
186
+ ] }) }),
187
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Grid.Item, { col: 1, s: 2, alignItems: "flex-start", children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { width: "100%", hint: "Max entries to keep per content type. 0 = unlimited.", children: [
188
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: "Entries Limit" }),
189
+ /* @__PURE__ */ jsxRuntime.jsx(
190
+ designSystem.NumberInput,
191
+ {
192
+ name: `${uid}-entriesLimit`,
193
+ value: config.entriesLimit,
194
+ onValueChange: (value) => updateConfig(uid, "entriesLimit", value ?? 0),
195
+ step: 10
196
+ }
197
+ ),
198
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Hint, {})
199
+ ] }) }),
200
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Grid.Item, { col: 1, s: 2, alignItems: "flex-start", children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { width: "100%", hint: "Entries older than this are cleaned up by the scheduled task. 0 = no limit.", children: [
201
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, alignItems: "flex-end", children: [
202
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { flex: "1", children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { width: "100%", children: [
203
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: "Retention Duration" }),
204
+ /* @__PURE__ */ jsxRuntime.jsx(
205
+ designSystem.NumberInput,
206
+ {
207
+ name: `${uid}-durationLimit`,
208
+ value: config.durationLimit,
209
+ onValueChange: (value) => updateConfig(uid, "durationLimit", value ?? 0),
210
+ step: 1
211
+ }
212
+ )
213
+ ] }) }),
214
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: { minWidth: "120px" }, children: /* @__PURE__ */ jsxRuntime.jsx(
215
+ designSystem.SingleSelect,
216
+ {
217
+ value: config.durationUnit,
218
+ onChange: (value) => updateConfig(uid, "durationUnit", String(value)),
219
+ children: DURATION_UNITS.map((u) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: u.value, children: u.label }, u.value))
220
+ }
221
+ ) })
222
+ ] }),
223
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Hint, {})
224
+ ] }) })
225
+ ] })
226
+ ] }) })
227
+ ] }, uid);
228
+ })
229
+ }
230
+ )
231
+ ] }),
232
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Dialog.Root, { open: !!removeTarget, onOpenChange: (open) => !open && setRemoveTarget(null), children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Dialog.Content, { children: [
233
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Dialog.Header, { children: "Remove content type from tracking" }),
234
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Dialog.Body, { icon: /* @__PURE__ */ jsxRuntime.jsx(icons.Trash, {}), children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", gap: 2, children: [
235
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { children: [
236
+ "Are you sure you want to stop tracking ",
237
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { children: removeTarget ? getDisplayName(removeTarget) : "" }),
238
+ "?"
239
+ ] }),
240
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: "Existing timeline entries for this content type will not be deleted. You can re-add it later to resume tracking." })
241
+ ] }) }),
242
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Dialog.Footer, { children: [
243
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Dialog.Cancel, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "tertiary", children: "Cancel" }) }),
244
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Dialog.Action, { children: /* @__PURE__ */ jsxRuntime.jsx(
245
+ designSystem.Button,
246
+ {
247
+ variant: "danger",
248
+ onClick: () => {
249
+ if (removeTarget) removeContentType(removeTarget);
250
+ setRemoveTarget(null);
251
+ },
252
+ children: "Remove"
253
+ }
254
+ ) })
255
+ ] })
256
+ ] }) })
257
+ ] }) });
258
+ };
259
+ exports.SettingsPage = SettingsPage;