Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
0697913
initialize
angelplusultra Apr 1, 2026
e105d57
expand tool result text limit | add syntax highlighting and json form…
angelplusultra Apr 2, 2026
4c4007e
fix onError jsdoc
angelplusultra Apr 2, 2026
61acc0f
lint
angelplusultra Apr 2, 2026
273b241
fix unread icon
angelplusultra Apr 2, 2026
a8f6356
Merge branch 'feat-background-jobs' of github.com:Mintplex-Labs/anyth…
angelplusultra Apr 2, 2026
58dc531
route protection
angelplusultra Apr 2, 2026
64875fa
improve form handling for NewJobModal
angelplusultra Apr 2, 2026
dd02590
safeJsonParse
angelplusultra Apr 2, 2026
2887524
remove unneeded comments
angelplusultra Apr 2, 2026
87900fe
remove trycatch
angelplusultra Apr 2, 2026
7258009
add truncateText helper
angelplusultra Apr 2, 2026
8328353
add explicit fallback value tos safeJsonParse
angelplusultra Apr 2, 2026
4060de2
add shared cron constant and helpers
angelplusultra Apr 2, 2026
5dc70da
reduce frontend indirection
angelplusultra Apr 2, 2026
bc69bcd
use isLight to compute syntax highlighting theme
angelplusultra Apr 2, 2026
51708a0
remove dead code
angelplusultra Apr 2, 2026
e3d3c29
remove forJob and make job limit to 50
angelplusultra Apr 2, 2026
2d310f1
create recomputeNextRunAt helper method
angelplusultra Apr 2, 2026
95b08a9
add comment about nextRunAt recomputation
angelplusultra Apr 2, 2026
1a86db9
add job queue and concurrency control to scheduled jobs
angelplusultra Apr 3, 2026
37cb578
use p-queue
angelplusultra Apr 3, 2026
da511e8
change default max concurrent value to 1
angelplusultra Apr 3, 2026
34a07c2
add comment explaining internal scheduling system
angelplusultra Apr 3, 2026
02a708c
add recomputeNextRunAt on boot
angelplusultra Apr 3, 2026
73ab691
add generated documents to run details
angelplusultra Apr 6, 2026
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
19 changes: 19 additions & 0 deletions frontend/src/components/PrivateRoute/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,25 @@ export function ManagerRoute({ Component }) {
);
}

// Allows access only in single user mode — redirects to home in multi-user mode
export function SingleUserRoute({ Component }) {
const { isAuthd, shouldRedirectToOnboarding, multiUserMode } =
useIsAuthenticated();
if (isAuthd === null) return <FullScreenLoader />;

if (shouldRedirectToOnboarding) {
return <Navigate to={paths.onboarding.home()} />;
}

return isAuthd && !multiUserMode ? (
<KeyboardShortcutWrapper>
<Component />
</KeyboardShortcutWrapper>
) : (
<Navigate to={paths.home()} />
);
}

export default function PrivateRoute({ Component }) {
const { isAuthd, shouldRedirectToOnboarding } = useIsAuthenticated();
if (isAuthd === null) return <FullScreenLoader />;
Expand Down
6 changes: 6 additions & 0 deletions frontend/src/components/SettingsSidebar/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,12 @@ const SidebarOptions = ({ user = null, t }) => (
flex: true,
roles: ["admin"],
},
{
btnText: "Scheduled Jobs",
href: paths.settings.scheduledJobs(),
flex: true,
hidden: !!user,
},
{
btnText: t("settings.api-keys"),
href: paths.settings.apiKeys(),
Expand Down
30 changes: 30 additions & 0 deletions frontend/src/main.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import App from "@/App.jsx";
import PrivateRoute, {
AdminRoute,
ManagerRoute,
SingleUserRoute,
} from "@/components/PrivateRoute";
import Login from "@/pages/Login";
import SimpleSSOPassthrough from "@/pages/Login/SSO/simple";
Expand Down Expand Up @@ -381,6 +382,35 @@ const router = createBrowserRouter([
return { element: <AdminRoute Component={TelegramBotSettings} /> };
},
},
{
path: "/settings/scheduled-jobs",
lazy: async () => {
const { default: ScheduledJobs } = await import(
"@/pages/GeneralSettings/ScheduledJobs"
);
return { element: <SingleUserRoute Component={ScheduledJobs} /> };
},
},
{
path: "/settings/scheduled-jobs/:id/runs",
lazy: async () => {
const { default: ScheduledJobRuns } = await import(
"@/pages/GeneralSettings/ScheduledJobs/RunHistoryPage"
);
return { element: <SingleUserRoute Component={ScheduledJobRuns} /> };
},
},
{
path: "/settings/scheduled-jobs/:id/runs/:runId",
lazy: async () => {
const { default: ScheduledJobRunDetail } = await import(
"@/pages/GeneralSettings/ScheduledJobs/RunDetailPage"
);
return {
element: <SingleUserRoute Component={ScheduledJobRunDetail} />,
};
},
},
// Catch-all route for 404s
{
path: "*",
Expand Down
147 changes: 147 additions & 0 deletions frontend/src/models/scheduledJobs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import { API_BASE } from "@/utils/constants";
import { baseHeaders } from "@/utils/request";

const ScheduledJobs = {
list: async function () {
return await fetch(`${API_BASE}/scheduled-jobs`, {
headers: baseHeaders(),
})
.then((res) => res.json())
.catch((e) => {
console.error(e);
return { jobs: [] };
});
},

create: async function (data) {
return await fetch(`${API_BASE}/scheduled-jobs/new`, {
method: "POST",
headers: baseHeaders(),
body: JSON.stringify(data),
})
.then((res) => res.json())
.catch((e) => {
console.error(e);
return { job: null, error: e.message };
});
},

get: async function (id) {
return await fetch(`${API_BASE}/scheduled-jobs/${id}`, {
headers: baseHeaders(),
})
.then((res) => res.json())
.catch((e) => {
console.error(e);
return { job: null };
});
},

update: async function (id, data) {
return await fetch(`${API_BASE}/scheduled-jobs/${id}`, {
method: "PUT",
headers: baseHeaders(),
body: JSON.stringify(data),
})
.then((res) => res.json())
.catch((e) => {
console.error(e);
return { job: null, error: e.message };
});
},

delete: async function (id) {
return await fetch(`${API_BASE}/scheduled-jobs/${id}`, {
method: "DELETE",
headers: baseHeaders(),
})
.then((res) => res.json())
.catch((e) => {
console.error(e);
return { success: false };
});
},

toggle: async function (id) {
return await fetch(`${API_BASE}/scheduled-jobs/${id}/toggle`, {
method: "POST",
headers: baseHeaders(),
})
.then((res) => res.json())
.catch((e) => {
console.error(e);
return { job: null };
});
},

trigger: async function (id) {
return await fetch(`${API_BASE}/scheduled-jobs/${id}/trigger`, {
method: "POST",
headers: baseHeaders(),
})
.then((res) => res.json())
.catch((e) => {
console.error(e);
return { success: false, error: e.message };
});
},

runs: async function (id) {
return await fetch(`${API_BASE}/scheduled-jobs/${id}/runs`, {
headers: baseHeaders(),
})
.then((res) => res.json())
.catch((e) => {
console.error(e);
return { runs: [] };
});
},

getRun: async function (runId) {
return await fetch(`${API_BASE}/scheduled-jobs/runs/${runId}`, {
headers: baseHeaders(),
})
.then((res) => res.json())
.catch((e) => {
console.error(e);
return { run: null, job: null };
});
},

markRunRead: async function (runId) {
return await fetch(`${API_BASE}/scheduled-jobs/runs/${runId}/read`, {
method: "POST",
headers: baseHeaders(),
})
.then((res) => res.json())
.catch((e) => {
console.error(e);
return { success: false };
});
},

continueInThread: async function (runId) {
return await fetch(`${API_BASE}/scheduled-jobs/runs/${runId}/continue`, {
method: "POST",
headers: baseHeaders(),
})
.then((res) => res.json())
.catch((e) => {
console.error(e);
return { workspaceSlug: null, threadSlug: null, error: e.message };
});
},

availableTools: async function () {
return await fetch(`${API_BASE}/scheduled-jobs/available-tools`, {
headers: baseHeaders(),
})
.then((res) => res.json())
.catch((e) => {
console.error(e);
return { tools: [] };
});
},
};

export default ScheduledJobs;
Loading
Loading