Skip to content

Commit c2e3e96

Browse files
Improving the web interface (#1975)
* update web interface with commandline options * improve web interface * update README images of web interface * fix bug in app.py * fix web interface
1 parent 900ed84 commit c2e3e96

File tree

7 files changed

+710
-138
lines changed

7 files changed

+710
-138
lines changed

maigret/web/app.py

+84-34
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
# app.py
21
from flask import (
32
Flask,
43
render_template,
@@ -22,7 +21,7 @@
2221
app = Flask(__name__)
2322
app.secret_key = 'your-secret-key-here'
2423

25-
# Add background job tracking
24+
#add background job tracking
2625
background_jobs = {}
2726
job_results = {}
2827

@@ -46,16 +45,38 @@ async def maigret_search(username, options):
4645
logger = setup_logger(logging.WARNING, 'maigret')
4746
try:
4847
db = MaigretDatabase().load_from_path(MAIGRET_DB_FILE)
49-
sites = db.ranked_sites_dict(top=int(options.get('top_sites', 500)))
48+
49+
top_sites = int(options.get('top_sites') or 500)
50+
if options.get('all_sites'):
51+
top_sites = 999999999 # effectively all
52+
53+
tags = options.get('tags', [])
54+
site_list= options.get('site_list', [])
55+
logger.info(f"Filtering sites by tags: {tags}")
56+
57+
sites = db.ranked_sites_dict(
58+
top=top_sites,
59+
tags=tags,
60+
names=site_list,
61+
disabled=False,
62+
id_type='username'
63+
)
64+
65+
logger.info(f"Found {len(sites)} sites matching the tag criteria")
5066

5167
results = await maigret.search(
5268
username=username,
5369
site_dict=sites,
5470
timeout=int(options.get('timeout', 30)),
5571
logger=logger,
56-
id_type=options.get('id_type', 'username'),
72+
id_type='username',
5773
cookies=COOKIES_FILE if options.get('use_cookies') else None,
58-
is_parsing_enabled=True,
74+
is_parsing_enabled=(not options.get('disable_extracting', False)),
75+
recursive_search_enabled=(not options.get('disable_recursive_search', False)),
76+
check_domains=options.get('with_domains', False),
77+
proxy=options.get('proxy', None),
78+
tor_proxy=options.get('tor_proxy', None),
79+
i2p_proxy=options.get('i2p_proxy', None),
5980
)
6081
return results
6182
except Exception as e:
@@ -68,36 +89,31 @@ async def search_multiple_usernames(usernames, options):
6889
for username in usernames:
6990
try:
7091
search_results = await maigret_search(username.strip(), options)
71-
results.append((username.strip(), options['id_type'], search_results))
92+
results.append((username.strip(), 'username', search_results))
7293
except Exception as e:
7394
logging.error(f"Error searching username {username}: {str(e)}")
7495
return results
7596

7697

7798
def process_search_task(usernames, options, timestamp):
7899
try:
79-
# Setup event loop for async operations
80100
loop = asyncio.new_event_loop()
81101
asyncio.set_event_loop(loop)
82102

83-
# Run the search
84103
general_results = loop.run_until_complete(
85104
search_multiple_usernames(usernames, options)
86105
)
87106

88-
# Create session folder
89107
session_folder = os.path.join(REPORTS_FOLDER, f"search_{timestamp}")
90108
os.makedirs(session_folder, exist_ok=True)
91109

92-
# Save the combined graph
93110
graph_path = os.path.join(session_folder, "combined_graph.html")
94111
maigret.report.save_graph_report(
95112
graph_path,
96113
general_results,
97114
MaigretDatabase().load_from_path(MAIGRET_DB_FILE),
98115
)
99116

100-
# Save individual reports
101117
individual_reports = []
102118
for username, id_type, results in general_results:
103119
report_base = os.path.join(session_folder, f"report_{username}")
@@ -154,25 +170,42 @@ def process_search_task(usernames, options, timestamp):
154170
}
155171
)
156172

157-
# Save results and mark job as complete
173+
# save results and mark job as complete using timestamp as key
158174
job_results[timestamp] = {
159175
'status': 'completed',
160176
'session_folder': f"search_{timestamp}",
161177
'graph_file': os.path.join(f"search_{timestamp}", "combined_graph.html"),
162178
'usernames': usernames,
163179
'individual_reports': individual_reports,
164180
}
181+
165182
except Exception as e:
183+
logging.error(f"Error in search task for timestamp {timestamp}: {str(e)}")
166184
job_results[timestamp] = {'status': 'failed', 'error': str(e)}
167185
finally:
168186
background_jobs[timestamp]['completed'] = True
169187

170188

171189
@app.route('/')
172190
def index():
173-
return render_template('index.html')
174-
175-
191+
#load site data for autocomplete
192+
db = MaigretDatabase().load_from_path(MAIGRET_DB_FILE)
193+
site_options = []
194+
195+
for site in db.sites:
196+
#add main site name
197+
site_options.append(site.name)
198+
#add URL if different from name
199+
if site.url_main and site.url_main not in site_options:
200+
site_options.append(site.url_main)
201+
202+
#sort and deduplicate
203+
site_options = sorted(set(site_options))
204+
205+
return render_template('index.html', site_options=site_options)
206+
207+
208+
# Modified search route
176209
@app.route('/search', methods=['POST'])
177210
def search():
178211
usernames_input = request.form.get('usernames', '').strip()
@@ -187,15 +220,28 @@ def search():
187220
# Create timestamp for this search session
188221
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
189222

190-
logging.info(f"Starting search for usernames: {usernames}")
223+
# Get selected tags - ensure it's a list
224+
selected_tags = request.form.getlist('tags')
225+
logging.info(f"Selected tags: {selected_tags}")
191226

192227
options = {
193-
'top_sites': request.form.get('top_sites', '500'),
194-
'timeout': request.form.get('timeout', '30'),
195-
'id_type': 'username', # fixed as username
228+
'top_sites': request.form.get('top_sites') or '500',
229+
'timeout': request.form.get('timeout') or '30',
196230
'use_cookies': 'use_cookies' in request.form,
231+
'all_sites': 'all_sites' in request.form,
232+
'disable_recursive_search': 'disable_recursive_search' in request.form,
233+
'disable_extracting': 'disable_extracting' in request.form,
234+
'with_domains': 'with_domains' in request.form,
235+
'proxy': request.form.get('proxy', None) or None,
236+
'tor_proxy': request.form.get('tor_proxy', None) or None,
237+
'i2p_proxy': request.form.get('i2p_proxy', None) or None,
238+
'permute': 'permute' in request.form,
239+
'tags': selected_tags, # Pass selected tags as a list
240+
'site_list': [s.strip() for s in request.form.get('site', '').split(',') if s.strip()],
197241
}
198242

243+
logging.info(f"Starting search for usernames: {usernames} with tags: {selected_tags}")
244+
199245
# Start background job
200246
background_jobs[timestamp] = {
201247
'completed': False,
@@ -205,46 +251,42 @@ def search():
205251
}
206252
background_jobs[timestamp]['thread'].start()
207253

208-
logging.info(f"Search job started with timestamp: {timestamp}")
209-
210-
# Redirect to status page
211254
return redirect(url_for('status', timestamp=timestamp))
212255

213-
214256
@app.route('/status/<timestamp>')
215257
def status(timestamp):
216258
logging.info(f"Status check for timestamp: {timestamp}")
217259

218260
# Validate timestamp
219261
if timestamp not in background_jobs:
220-
flash('Invalid search session', 'danger')
262+
flash('Invalid search session.', 'danger')
263+
logging.error(f"Invalid search session: {timestamp}")
221264
return redirect(url_for('index'))
222265

223266
# Check if job is completed
224267
if background_jobs[timestamp]['completed']:
225268
result = job_results.get(timestamp)
226269
if not result:
227-
flash('No results found for this search session', 'warning')
270+
flash('No results found for this search session.', 'warning')
271+
logging.error(f"No results found for completed session: {timestamp}")
228272
return redirect(url_for('index'))
229273

230274
if result['status'] == 'completed':
231-
# Redirect to results page once done
275+
# Note: use the session_folder from the results to redirect
232276
return redirect(url_for('results', session_id=result['session_folder']))
233277
else:
234-
error_msg = result.get('error', 'Unknown error occurred')
278+
error_msg = result.get('error', 'Unknown error occurred.')
235279
flash(f'Search failed: {error_msg}', 'danger')
280+
logging.error(f"Search failed for session {timestamp}: {error_msg}")
236281
return redirect(url_for('index'))
237282

238-
# If job is still running, show status page with a simple spinner
283+
# If job is still running, show a status page
239284
return render_template('status.html', timestamp=timestamp)
240285

241286

242287
@app.route('/results/<session_id>')
243288
def results(session_id):
244-
if not session_id.startswith('search_'):
245-
flash('Invalid results session format', 'danger')
246-
return redirect(url_for('index'))
247-
289+
# Find completed results that match this session_folder
248290
result_data = next(
249291
(
250292
r
@@ -254,6 +296,11 @@ def results(session_id):
254296
None,
255297
)
256298

299+
if not result_data:
300+
flash('No results found for this session ID.', 'danger')
301+
logging.error(f"Results for session {session_id} not found in job_results.")
302+
return redirect(url_for('index'))
303+
257304
return render_template(
258305
'results.html',
259306
usernames=result_data['usernames'],
@@ -266,7 +313,9 @@ def results(session_id):
266313
@app.route('/reports/<path:filename>')
267314
def download_report(filename):
268315
try:
269-
file_path = os.path.join(REPORTS_FOLDER, filename)
316+
file_path = os.path.normpath(os.path.join(REPORTS_FOLDER, filename))
317+
if not file_path.startswith(REPORTS_FOLDER):
318+
raise Exception("Invalid file path")
270319
return send_file(file_path)
271320
except Exception as e:
272321
logging.error(f"Error serving file {filename}: {str(e)}")
@@ -278,4 +327,5 @@ def download_report(filename):
278327
level=logging.INFO,
279328
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
280329
)
281-
app.run(debug=True)
330+
debug_mode = os.getenv('FLASK_DEBUG', 'False').lower() in ['true', '1', 't']
331+
app.run(debug=debug_mode)

maigret/web/static/maigret.png

44.6 KB
Loading

0 commit comments

Comments
 (0)