Skip to content

Commit 4e08964

Browse files
authored
add run resource limits (#49)
* feat: Implement query resource limits for memory and duration * feat: Enhance dark mode support across various components
1 parent 885d91e commit 4e08964

File tree

14 files changed

+299
-105
lines changed

14 files changed

+299
-105
lines changed

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,20 @@ pip install -r requirements/dev-requirements.in
2525

2626
The server is configured using a `config.json` file in `src/server`. An example configuration file is provided in `src/server/config.example.json`.
2727

28+
You can optionally configure resource limits for queries to prevent long-running or memory-intensive queries from impacting server stability. Add a `query_limits` section with the following fields:
29+
30+
```json
31+
"query_limits": {
32+
"max_ram_pct": 0.5,
33+
"max_ram_bytes": 1073741824,
34+
"max_duration_seconds": 120
35+
}
36+
```
37+
38+
`max_ram_pct` is a fraction of total system memory (default: 0.5), `max_ram_bytes` is an absolute memory limit in bytes (optional; if set, overrides `max_ram_pct`), and `max_duration_seconds` is the maximum wall-clock time in seconds for a query (default: 120).
39+
40+
Note: On non-Linux/Darwin systems, install `psutil` to enable memory limit detection.
41+
2842
## Running the Server
2943

3044
To run the server in "development" mode, run:

motifstudio-web/src/app/FileMenu.tsx

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,9 @@ export function FileMenu({ queryText, currentGraph, onLoad }: FileMenuProps) {
4949
<>
5050
<Menu as="div" className="relative inline-block text-left">
5151
<div>
52-
<Menu.Button className="inline-flex w-full justify-center gap-x-1.5 rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm hover:bg-gray-50">
52+
<Menu.Button className="inline-flex w-full justify-center gap-x-1.5 rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm hover:bg-gray-50 dark:bg-gray-700 dark:text-gray-200 dark:hover:bg-gray-600">
5353
File
54-
<ChevronDownIcon className="-mr-1 h-5 w-5 text-gray-400" aria-hidden="true" />
54+
<ChevronDownIcon className="-mr-1 h-5 w-5 text-gray-400 dark:text-gray-300" aria-hidden="true" />
5555
</Menu.Button>
5656
</div>
5757

@@ -64,17 +64,19 @@ export function FileMenu({ queryText, currentGraph, onLoad }: FileMenuProps) {
6464
leaveFrom="transform opacity-100 scale-100"
6565
leaveTo="transform opacity-0 scale-95"
6666
>
67-
<Menu.Items className="absolute left-0 z-10 mt-2 w-56 origin-top-right rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none">
67+
<Menu.Items className="absolute left-0 z-10 mt-2 w-56 origin-top-right rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none dark:bg-gray-800 dark:ring-gray-700 dark:ring-opacity-50">
6868
<div className="py-1">
6969
<Menu.Item>
7070
{({ active }) => (
7171
<button
7272
onClick={() => setIsSaveDialogOpen(true)}
7373
className={`${
74-
active ? "bg-gray-100 text-gray-900" : "text-gray-700"
74+
active
75+
? "bg-gray-100 text-gray-900 dark:bg-gray-700 dark:text-gray-200"
76+
: "text-gray-700 dark:text-gray-200"
7577
} group flex w-full items-center px-4 py-2 text-sm`}
7678
>
77-
<DocumentIcon className="mr-3 h-5 w-5 text-gray-400 group-hover:text-gray-500" />
79+
<DocumentIcon className="mr-3 h-5 w-5 text-gray-400 dark:text-gray-400 group-hover:text-gray-500 dark:group-hover:text-gray-300" />
7880
Save
7981
</button>
8082
)}
@@ -84,10 +86,12 @@ export function FileMenu({ queryText, currentGraph, onLoad }: FileMenuProps) {
8486
<button
8587
onClick={() => setIsOpenDialogOpen(true)}
8688
className={`${
87-
active ? "bg-gray-100 text-gray-900" : "text-gray-700"
89+
active
90+
? "bg-gray-100 text-gray-900 dark:bg-gray-700 dark:text-gray-200"
91+
: "text-gray-700 dark:text-gray-200"
8892
} group flex w-full items-center px-4 py-2 text-sm`}
8993
>
90-
<FolderOpenIcon className="mr-3 h-5 w-5 text-gray-400 group-hover:text-gray-500" />
94+
<FolderOpenIcon className="mr-3 h-5 w-5 text-gray-400 dark:text-gray-400 group-hover:text-gray-500 dark:group-hover:text-gray-300" />
9195
Open
9296
</button>
9397
)}
@@ -97,10 +101,12 @@ export function FileMenu({ queryText, currentGraph, onLoad }: FileMenuProps) {
97101
<button
98102
onClick={handleExport}
99103
className={`${
100-
active ? "bg-gray-100 text-gray-900" : "text-gray-700"
104+
active
105+
? "bg-gray-100 text-gray-900 dark:bg-gray-700 dark:text-gray-200"
106+
: "text-gray-700 dark:text-gray-200"
101107
} group flex w-full items-center px-4 py-2 text-sm`}
102108
>
103-
<ArrowDownTrayIcon className="mr-3 h-5 w-5 text-gray-400 group-hover:text-gray-500" />
109+
<ArrowDownTrayIcon className="mr-3 h-5 w-5 text-gray-400 dark:text-gray-400 group-hover:text-gray-500 dark:group-hover:text-gray-300" />
104110
Export
105111
</button>
106112
)}

motifstudio-web/src/app/MotifVisualizer.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export const MotifVisualizer = ({ motifSource }: { motifSource: string }) => {
1414
// All React hooks must be called before any conditional returns
1515
const debouncedQuery = useThrottle(motifSource, 1000);
1616
let elements = useRef<any[]>([]);
17-
17+
1818
// Get query type from URL parameters
1919
const { query_type } = typeof window !== "undefined" ? getQueryParams() : { query_type: "dotmotif" };
2020

@@ -77,7 +77,8 @@ export const MotifVisualizer = ({ motifSource }: { motifSource: string }) => {
7777
<div className="text-sm text-gray-500 dark:text-gray-400">
7878
Visualization is not available for Cypher queries.
7979
<br />
80-
Cypher queries can return arbitrary data structures that cannot be displayed as traditional motif graphs.
80+
Cypher queries can return arbitrary data structures that cannot be displayed as traditional
81+
motif graphs.
8182
</div>
8283
</div>
8384
</div>

motifstudio-web/src/app/ResultsFetcher.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,12 @@ export function ResultsFetcher({
2929

3030
if (queryIsLoading) return <LoadingSpinner />;
3131

32+
// If there was a fetching error, show it to the user
33+
if (queryError) {
34+
const msg = queryError instanceof Error ? queryError.message : String(queryError);
35+
return <div className="text-red-500 p-4">Error fetching query: {msg}</div>;
36+
}
37+
3238
let durationString = "";
3339
if (queryData?.response_duration_ms) {
3440
// < 2 sec, show ms
@@ -50,6 +56,11 @@ export function ResultsFetcher({
5056
}
5157
}
5258

59+
// If server returned an error message, display it
60+
if (errorText) {
61+
return <div className="text-red-500 p-4">{errorText}</div>;
62+
}
63+
5364
let motifCountString = "";
5465
if (queryData?.motif_count) {
5566
motifCountString = queryData.motif_count.toLocaleString();

motifstudio-web/src/app/components/DeleteConfirmDialog.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export function DeleteConfirmDialog({ isOpen, onClose, project, onConfirm }: Del
1515
<div className="fixed inset-0 bg-black/25" />
1616
<div className="fixed inset-0 overflow-y-auto">
1717
<div className="flex min-h-full items-center justify-center p-4">
18-
<Dialog.Panel className="w-full max-w-md rounded-lg bg-white p-6 shadow-xl">
18+
<Dialog.Panel className="w-full max-w-md rounded-lg bg-white p-6 shadow-xl dark:bg-gray-800">
1919
<div className="flex items-center space-x-3">
2020
<div className="flex-shrink-0">
2121
<svg
@@ -33,11 +33,11 @@ export function DeleteConfirmDialog({ isOpen, onClose, project, onConfirm }: Del
3333
</svg>
3434
</div>
3535
<div className="flex-1">
36-
<Dialog.Title className="text-lg font-medium text-gray-900">
36+
<Dialog.Title className="text-lg font-medium text-gray-900 dark:text-gray-200">
3737
Delete Project
3838
</Dialog.Title>
3939
<div className="mt-2">
40-
<p className="text-sm text-gray-500">
40+
<p className="text-sm text-gray-500 dark:text-gray-400">
4141
Are you sure you want to delete "{project?.name}"? This action cannot be undone.
4242
</p>
4343
</div>
@@ -46,7 +46,7 @@ export function DeleteConfirmDialog({ isOpen, onClose, project, onConfirm }: Del
4646
<div className="mt-6 flex justify-end gap-3">
4747
<button
4848
onClick={onClose}
49-
className="px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50"
49+
className="px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50 dark:bg-gray-700 dark:text-gray-200 dark:border-gray-600 dark:hover:bg-gray-600"
5050
>
5151
Cancel
5252
</button>

motifstudio-web/src/app/components/OpenDialog.tsx

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@ export function OpenDialog({ isOpen, onClose, savedProjects, onLoad, onDelete }:
1010
<div className="fixed inset-0 bg-black/25" />
1111
<div className="fixed inset-0 overflow-y-auto">
1212
<div className="flex min-h-full items-center justify-center p-4">
13-
<Dialog.Panel className="w-full max-w-4xl max-h-[80vh] rounded-lg bg-white shadow-xl flex flex-col">
14-
<div className="flex items-center justify-between p-6 border-b">
15-
<Dialog.Title className="text-lg font-medium text-gray-900">Open Project</Dialog.Title>
16-
<button onClick={onClose} className="text-gray-400 hover:text-gray-600">
13+
<Dialog.Panel className="w-full max-w-4xl max-h-[80vh] rounded-lg bg-white shadow-xl flex flex-col dark:bg-gray-800">
14+
<div className="flex items-center justify-between p-6 border-b dark:border-gray-700">
15+
<Dialog.Title className="text-lg font-medium text-gray-900 dark:text-gray-200">Open Project</Dialog.Title>
16+
<button onClick={onClose} className="text-gray-400 hover:text-gray-600 dark:text-gray-400 dark:hover:text-gray-200">
1717
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1818
<path
1919
strokeLinecap="round"
@@ -29,19 +29,19 @@ export function OpenDialog({ isOpen, onClose, savedProjects, onLoad, onDelete }:
2929
{savedProjects.length === 0 ? (
3030
<div className="flex items-center justify-center h-64">
3131
<div className="text-center">
32-
<FolderOpenIcon className="mx-auto h-16 w-16 text-gray-300" />
33-
<p className="mt-4 text-lg text-gray-500 font-medium">
32+
<FolderOpenIcon className="mx-auto h-16 w-16 text-gray-300 dark:text-gray-400" />
33+
<p className="mt-4 text-lg text-gray-500 font-medium dark:text-gray-200">
3434
No saved projects found
3535
</p>
36-
<p className="mt-1 text-sm text-gray-400">
36+
<p className="mt-1 text-sm text-gray-400 dark:text-gray-400">
3737
Create your first project using the Save option
3838
</p>
3939
</div>
4040
</div>
4141
) : (
4242
<div className="h-full flex flex-col">
43-
<div className="px-6 py-3 bg-gray-50 border-b">
44-
<h3 className="text-sm font-medium text-gray-700">
43+
<div className="px-6 py-3 bg-gray-50 border-b dark:bg-gray-700 dark:border-gray-700">
44+
<h3 className="text-sm font-medium text-gray-700 dark:text-gray-200">
4545
{savedProjects.length} saved project
4646
{savedProjects.length !== 1 ? "s" : ""}
4747
</h3>
@@ -51,18 +51,18 @@ export function OpenDialog({ isOpen, onClose, savedProjects, onLoad, onDelete }:
5151
{savedProjects.map((project) => (
5252
<div
5353
key={project.id}
54-
className="group p-4 border border-gray-200 rounded-lg hover:border-blue-300 hover:bg-blue-50 transition-all duration-150"
54+
className="group p-4 border border-gray-200 rounded-lg hover:border-blue-300 hover:bg-blue-50 transition-all duration-150 dark:border-gray-700 dark:hover:border-blue-300 dark:hover:bg-blue-900/20"
5555
>
5656
<div className="flex items-start justify-between">
5757
<div className="flex items-start space-x-3 flex-1 min-w-0">
5858
<div className="flex-shrink-0 mt-1">
5959
<DocumentIcon className="h-6 w-6 text-blue-500" />
6060
</div>
6161
<div className="flex-1 min-w-0">
62-
<h4 className="text-base font-semibold text-gray-900 truncate group-hover:text-blue-900">
62+
<h4 className="text-base font-semibold text-gray-900 truncate group-hover:text-blue-900 dark:text-gray-200 dark:group-hover:text-blue-300">
6363
{project.name}
6464
</h4>
65-
<div className="mt-1 flex items-center space-x-2 text-sm text-gray-500">
65+
<div className="mt-1 flex items-center space-x-2 text-sm text-gray-500 dark:text-gray-400">
6666
<span>Saved on</span>
6767
<span className="font-medium">
6868
{new Date(
@@ -87,7 +87,7 @@ export function OpenDialog({ isOpen, onClose, savedProjects, onLoad, onDelete }:
8787
</div>
8888
)}
8989
<div className="mt-2">
90-
<p className="text-xs text-gray-400 line-clamp-2">
90+
<p className="text-xs text-gray-400 dark:text-gray-400 line-clamp-2">
9191
Query:{" "}
9292
{project.queryText.length > 100
9393
? project.queryText.substring(0, 100) +
@@ -120,10 +120,10 @@ export function OpenDialog({ isOpen, onClose, savedProjects, onLoad, onDelete }:
120120
)}
121121
</div>
122122

123-
<div className="px-6 py-4 bg-gray-50 border-t flex justify-end">
123+
<div className="px-6 py-4 bg-gray-50 border-t flex justify-end dark:bg-gray-700 dark:border-gray-700">
124124
<button
125125
onClick={onClose}
126-
className="px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50"
126+
className="px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50 dark:bg-gray-700 dark:text-gray-200 dark:border-gray-600 dark:hover:bg-gray-600"
127127
>
128128
Cancel
129129
</button>

0 commit comments

Comments
 (0)