madboard 0.1.0__tar.gz

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.
madboard-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Theo Heimel
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,4 @@
1
+ recursive-include madboard/frontend/build *
2
+ recursive-include madboard/backend *
3
+ include README.md
4
+ include LICENSE
@@ -0,0 +1,31 @@
1
+ Metadata-Version: 2.4
2
+ Name: madboard
3
+ Version: 0.1.0
4
+ Summary: A simple web application with Python Flask backend and React frontend
5
+ Author-email: Your Name <your.email@example.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/theoheimel/madboard
8
+ Project-URL: Documentation, https://github.com/theoheimel/madboard#readme
9
+ Project-URL: Repository, https://github.com/theoheimel/madboard.git
10
+ Project-URL: Issues, https://github.com/theoheimel/madboard/issues
11
+ Keywords: flask,react,web-application
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Topic :: Internet :: WWW/HTTP
21
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
+ Requires-Python: >=3.8
23
+ Description-Content-Type: text/markdown
24
+ License-File: LICENSE
25
+ Requires-Dist: flask>=2.3.0
26
+ Requires-Dist: flask-cors>=4.0.0
27
+ Dynamic: license-file
28
+
29
+ # madboard
30
+
31
+ Interactive modern web interface for MadGraph
@@ -0,0 +1,3 @@
1
+ # madboard
2
+
3
+ Interactive modern web interface for MadGraph
@@ -0,0 +1,3 @@
1
+ """MadBoard package."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1 @@
1
+ """MadBoard backend package."""
@@ -0,0 +1,6 @@
1
+ """Entry point for running MadBoard server."""
2
+
3
+ from madboard.backend.server import run_server
4
+
5
+ if __name__ == "__main__":
6
+ run_server()
@@ -0,0 +1,47 @@
1
+ """Flask application factory with static file serving."""
2
+
3
+ import os
4
+
5
+ from flask import Flask, send_from_directory
6
+ from flask_cors import CORS
7
+
8
+
9
+ def create_app():
10
+ """Create and configure the Flask application."""
11
+ app = Flask(
12
+ __name__, static_folder="../frontend/build/static", static_url_path="/static"
13
+ )
14
+
15
+ # Enable CORS for local development and production
16
+ CORS(app)
17
+
18
+ # Configuration
19
+ app.config["DEBUG"] = os.getenv("FLASK_ENV", "development") == "development"
20
+
21
+ # Register blueprints
22
+ from madboard.backend.routes import api_bp
23
+
24
+ app.register_blueprint(api_bp, url_prefix="/api")
25
+
26
+ @app.route("/health")
27
+ def health():
28
+ """Health check endpoint."""
29
+ return {"status": "ok"}, 200
30
+
31
+ @app.route("/", defaults={"path": ""})
32
+ @app.route("/<path:path>")
33
+ def serve_react(path):
34
+ """Serve React app. For any route, serve index.html."""
35
+ # If it's an API route, let Flask handle it
36
+ if path.startswith("api/"):
37
+ return {"error": "Not found"}, 404
38
+
39
+ # Check if static file exists
40
+ static_dir = os.path.join(os.path.dirname(__file__), "../frontend/build")
41
+ if path and os.path.exists(os.path.join(static_dir, path)):
42
+ return send_from_directory(static_dir, path)
43
+
44
+ # Otherwise serve index.html for React routing
45
+ return send_from_directory(static_dir, "index.html")
46
+
47
+ return app
@@ -0,0 +1,197 @@
1
+ """API routes for MadBoard backend."""
2
+
3
+ import json
4
+ import os
5
+ import shutil
6
+
7
+ from flask import Blueprint, request, send_file
8
+
9
+ api_bp = Blueprint("api", __name__)
10
+
11
+
12
+ @api_bp.route("/processes", methods=["GET"])
13
+ def get_processes():
14
+ """Get list of processes with their runs."""
15
+ processes = []
16
+ for process_dir in os.scandir("."):
17
+ if not process_dir.is_dir():
18
+ continue
19
+ subfolders = [f.name for f in os.scandir(process_dir) if f.is_dir()]
20
+ if "Cards" not in subfolders or "Events" not in subfolders:
21
+ continue
22
+ runs = [
23
+ f.name
24
+ for f in os.scandir(os.path.join(process_dir, "Events"))
25
+ if f.is_dir()
26
+ ]
27
+
28
+ processes.append(
29
+ {
30
+ "name": process_dir.name,
31
+ "runs": sorted(runs),
32
+ }
33
+ )
34
+ return {"processes": processes}, 200
35
+
36
+
37
+ @api_bp.route("/processes/<process_name>", methods=["DELETE"])
38
+ def delete_process(process_name):
39
+ """Delete a specific process."""
40
+ if not process_name:
41
+ return {"error": "Process name is required"}, 400
42
+ process_dir = os.path.join(".", process_name)
43
+ if not os.path.isdir(process_dir):
44
+ return {"error": "Process not found"}, 404
45
+ shutil.rmtree(process_dir)
46
+ return {"message": "Process deleted successfully"}, 200
47
+
48
+
49
+ @api_bp.route("/processes/<process_name>/cards", methods=["GET"])
50
+ def get_cards(process_name):
51
+ """Get list of cards for a specific process."""
52
+ cards = []
53
+ process_dir = os.path.join(".", process_name)
54
+ if not os.path.isdir(process_dir):
55
+ return {"error": "Process not found"}, 404
56
+ cards_dir = os.path.join(process_dir, "Cards")
57
+ if not os.path.isdir(cards_dir):
58
+ return {"error": "Cards directory not found"}, 404
59
+ for card_file in os.scandir(cards_dir):
60
+ if card_file.is_file():
61
+ cards.append(card_file.name)
62
+ return {"cards": cards}, 200
63
+
64
+
65
+ @api_bp.route("/processes/<process_name>/cards/<card_name>", methods=["GET"])
66
+ def get_card(process_name, card_name):
67
+ """Get a specific card for a process."""
68
+ process_dir = os.path.join(".", process_name)
69
+ if not os.path.isdir(process_dir):
70
+ return {"error": "Process not found"}, 404
71
+ cards_dir = os.path.join(process_dir, "Cards")
72
+ if not os.path.isdir(cards_dir):
73
+ return {"error": "Cards directory not found"}, 404
74
+ card_path = os.path.join(cards_dir, card_name)
75
+ if not os.path.isfile(card_path):
76
+ return {"error": "Card not found"}, 404
77
+ with open(card_path, "r") as f:
78
+ content = f.read()
79
+ return {"content": content}, 200
80
+
81
+
82
+ @api_bp.route("/processes/<process_name>/cards/<card_name>/download", methods=["GET"])
83
+ def download_card(process_name, card_name):
84
+ """Download a specific card for a process."""
85
+ process_dir = os.path.join(".", process_name)
86
+ if not os.path.isdir(process_dir):
87
+ return {"error": "Process not found"}, 404
88
+ cards_dir = os.path.join(process_dir, "Cards")
89
+ if not os.path.isdir(cards_dir):
90
+ return {"error": "Cards directory not found"}, 404
91
+ card_path = os.path.join(cards_dir, card_name)
92
+ if not os.path.isfile(card_path):
93
+ return {"error": "Card not found"}, 404
94
+ return send_file(os.path.abspath(card_path), as_attachment=True)
95
+
96
+
97
+ @api_bp.route("/processes/<process_name>/cards/<card_name>", methods=["POST"])
98
+ def update_card(process_name, card_name):
99
+ """Update a specific card for a process."""
100
+ process_dir = os.path.join(".", process_name)
101
+ if not os.path.isdir(process_dir):
102
+ return {"error": "Process not found"}, 404
103
+ cards_dir = os.path.join(process_dir, "Cards")
104
+ if not os.path.isdir(cards_dir):
105
+ return {"error": "Cards directory not found"}, 404
106
+ card_path = os.path.join(cards_dir, card_name)
107
+ if not os.path.isfile(card_path):
108
+ return {"error": "Card not found"}, 404
109
+ with open(card_path, "w") as f:
110
+ f.write(request.json.get("content", ""))
111
+ return {"message": "Card updated successfully"}, 200
112
+
113
+
114
+ @api_bp.route("/processes/<process_name>/runs", methods=["GET"])
115
+ def get_runs(process_name):
116
+ """Get list of runs for a specific process."""
117
+ runs = []
118
+ process_dir = os.path.join(".", process_name)
119
+ if not os.path.isdir(process_dir):
120
+ return {"error": "Process not found"}, 404
121
+ events_dir = os.path.join(process_dir, "Events")
122
+ if not os.path.isdir(events_dir):
123
+ return {"error": "Events directory not found"}, 404
124
+ for run_dir in os.scandir(events_dir):
125
+ if run_dir.is_dir():
126
+ info_file = os.path.join(run_dir, "info.json")
127
+ if os.path.isfile(info_file):
128
+ with open(info_file, "r") as f:
129
+ info = json.load(f)
130
+ else:
131
+ info = {"status": "unknown"}
132
+ files = [
133
+ file_entry.name
134
+ for file_entry in os.scandir(run_dir)
135
+ if file_entry.is_file()
136
+ ]
137
+ runs.append(
138
+ {
139
+ **info,
140
+ "name": run_dir.name,
141
+ "files": files,
142
+ }
143
+ )
144
+ return {"runs": sorted(runs, key=lambda run: run["name"])}, 200
145
+
146
+
147
+ @api_bp.route("/processes/<process_name>/runs/<run_name>", methods=["DELETE"])
148
+ def delete_run(process_name, run_name):
149
+ """Delete a specific run for a process."""
150
+ process_dir = os.path.join(".", process_name)
151
+ if not os.path.isdir(process_dir):
152
+ return {"error": "Process not found"}, 404
153
+ run_dir = os.path.join(process_dir, "Events", run_name)
154
+ if not os.path.isdir(run_dir):
155
+ return {"error": "Run not found"}, 404
156
+ try:
157
+ shutil.rmtree(run_dir)
158
+ return {"message": "Run deleted successfully"}, 200
159
+ except Exception as e:
160
+ return {"error": str(e)}, 500
161
+
162
+
163
+ @api_bp.route("/processes/<process_name>/runs/<run_name>/info", methods=["GET"])
164
+ def get_run_info(process_name, run_name):
165
+ """Get details of a specific run for a process."""
166
+ process_dir = os.path.join(".", process_name)
167
+ if not os.path.isdir(process_dir):
168
+ return {"error": "Process not found"}, 404
169
+ run_dir = os.path.join(process_dir, "Events", run_name)
170
+ if not os.path.isdir(run_dir):
171
+ return {"error": "Run not found"}, 404
172
+ info_file = os.path.join(run_dir, "info.json")
173
+ if not os.path.isfile(info_file):
174
+ return {"status": "unknown"}, 200
175
+ files = [
176
+ file_entry.name for file_entry in os.scandir(run_dir) if file_entry.is_file()
177
+ ]
178
+ with open(info_file, "r") as f:
179
+ info = json.load(f)
180
+ return {**info, "files": files}, 200
181
+
182
+
183
+ @api_bp.route(
184
+ "/processes/<process_name>/runs/<run_name>/download/<filename>", methods=["GET"]
185
+ )
186
+ def download_run_file(process_name, run_name, filename):
187
+ """Download a specific file from a run."""
188
+ process_dir = os.path.join(".", process_name)
189
+ if not os.path.isdir(process_dir):
190
+ return {"error": "Process not found"}, 404
191
+ run_dir = os.path.join(process_dir, "Events", run_name)
192
+ if not os.path.isdir(run_dir):
193
+ return {"error": "Run not found"}, 404
194
+ file_path = os.path.join(run_dir, filename)
195
+ if not os.path.isfile(file_path):
196
+ return {"error": "File not found"}, 404
197
+ return send_file(os.path.abspath(file_path), as_attachment=True)
@@ -0,0 +1,33 @@
1
+ """Development server with automatic browser opening."""
2
+
3
+ import os
4
+ import webbrowser
5
+ from threading import Timer
6
+
7
+ from madboard.backend.app import create_app
8
+
9
+
10
+ def open_browser(port=5000):
11
+ """Open the frontend in the browser after a short delay."""
12
+
13
+ def _open():
14
+ webbrowser.open(f"http://localhost:{port}")
15
+
16
+ timer = Timer(1.0, _open)
17
+ timer.daemon = True
18
+ timer.start()
19
+
20
+
21
+ def run_server(port=5000, debug=True):
22
+ """Run the Flask development server with browser opening."""
23
+ app = create_app()
24
+
25
+ # Open browser on startup
26
+ open_browser(port)
27
+
28
+ # Run the server
29
+ app.run(host="127.0.0.1", port=port, debug=debug, use_reloader=debug)
30
+
31
+
32
+ if __name__ == "__main__":
33
+ run_server()
@@ -0,0 +1,10 @@
1
+ {
2
+ "files": {
3
+ "main.js": "/static/js/main.bdab60b3.js",
4
+ "index.html": "/index.html",
5
+ "main.bdab60b3.js.map": "/static/js/main.bdab60b3.js.map"
6
+ },
7
+ "entrypoints": [
8
+ "static/js/main.bdab60b3.js"
9
+ ]
10
+ }
@@ -0,0 +1 @@
1
+ <!doctype html><html lang="en"><head><meta charset="utf-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="MadBoard - A simple web application"/><title>MadBoard</title><script defer="defer" src="/static/js/main.bdab60b3.js"></script></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>