context menu
This commit is contained in:
parent
f02381910d
commit
2658bd148b
76
app.py
76
app.py
@ -13,6 +13,7 @@ import io
|
||||
|
||||
from core.session_manager import session_manager
|
||||
from config import config
|
||||
from core.graph_manager import NodeType
|
||||
|
||||
|
||||
app = Flask(__name__)
|
||||
@ -278,6 +279,81 @@ def get_graph_data():
|
||||
}), 500
|
||||
|
||||
|
||||
@app.route('/api/graph/node/<node_id>', methods=['DELETE'])
|
||||
def delete_graph_node(node_id):
|
||||
"""Delete a node from the graph for the current user session."""
|
||||
try:
|
||||
user_session_id, scanner = get_user_scanner()
|
||||
if not scanner:
|
||||
return jsonify({'success': False, 'error': 'No active session found'}), 404
|
||||
|
||||
success = scanner.graph.remove_node(node_id)
|
||||
|
||||
if success:
|
||||
# Persist the change
|
||||
session_manager.update_session_scanner(user_session_id, scanner)
|
||||
return jsonify({'success': True, 'message': f'Node {node_id} deleted successfully.'})
|
||||
else:
|
||||
return jsonify({'success': False, 'error': f'Node {node_id} not found in graph.'}), 404
|
||||
|
||||
except Exception as e:
|
||||
print(f"ERROR: Exception in delete_graph_node endpoint: {e}")
|
||||
traceback.print_exc()
|
||||
return jsonify({'success': False, 'error': f'Internal server error: {str(e)}'}), 500
|
||||
|
||||
|
||||
@app.route('/api/graph/revert', methods=['POST'])
|
||||
def revert_graph_action():
|
||||
"""Reverts a graph action, such as re-adding a deleted node."""
|
||||
try:
|
||||
data = request.get_json()
|
||||
if not data or 'type' not in data or 'data' not in data:
|
||||
return jsonify({'success': False, 'error': 'Invalid revert request format'}), 400
|
||||
|
||||
user_session_id, scanner = get_user_scanner()
|
||||
if not scanner:
|
||||
return jsonify({'success': False, 'error': 'No active session found'}), 404
|
||||
|
||||
action_type = data['type']
|
||||
action_data = data['data']
|
||||
|
||||
if action_type == 'delete':
|
||||
# Re-add the node
|
||||
node_to_add = action_data.get('node')
|
||||
if node_to_add:
|
||||
scanner.graph.add_node(
|
||||
node_id=node_to_add['id'],
|
||||
node_type=NodeType(node_to_add['type']),
|
||||
attributes=node_to_add.get('attributes'),
|
||||
description=node_to_add.get('description'),
|
||||
metadata=node_to_add.get('metadata')
|
||||
)
|
||||
|
||||
# Re-add the edges
|
||||
edges_to_add = action_data.get('edges', [])
|
||||
for edge in edges_to_add:
|
||||
# Add edge only if both nodes exist to prevent errors
|
||||
if scanner.graph.graph.has_node(edge['from']) and scanner.graph.graph.has_node(edge['to']):
|
||||
scanner.graph.add_edge(
|
||||
source_id=edge['from'],
|
||||
target_id=edge['to'],
|
||||
relationship_type=edge['metadata']['relationship_type'],
|
||||
confidence_score=edge['metadata']['confidence_score'],
|
||||
source_provider=edge['metadata']['source_provider'],
|
||||
raw_data=edge.get('raw_data', {})
|
||||
)
|
||||
|
||||
# Persist the change
|
||||
session_manager.update_session_scanner(user_session_id, scanner)
|
||||
return jsonify({'success': True, 'message': 'Delete action reverted successfully.'})
|
||||
|
||||
return jsonify({'success': False, 'error': f'Unknown revert action type: {action_type}'}), 400
|
||||
|
||||
except Exception as e:
|
||||
print(f"ERROR: Exception in revert_graph_action endpoint: {e}")
|
||||
traceback.print_exc()
|
||||
return jsonify({'success': False, 'error': f'Internal server error: {str(e)}'}), 500
|
||||
|
||||
|
||||
@app.route('/api/export', methods=['GET'])
|
||||
def export_results():
|
||||
|
1
cache/crtsh/app_fleischkombinat-ost_de.json
vendored
Normal file
1
cache/crtsh/app_fleischkombinat-ost_de.json
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"domain":"app.fleischkombinat-ost.de","first_cached":"2025-09-14T21:11:17.304989+00:00","last_upstream_query":"2025-09-14T21:11:17.304992+00:00","upstream_query_count":1,"certificates":[{"issuer_ca_id":295819,"issuer_name":"C=US, O=Let's Encrypt, CN=E6","common_name":"app.fleischkombinat-ost.de","name_value":"app.fleischkombinat-ost.de","id":19374493240,"entry_timestamp":"2025-07-01T14:09:36.354","not_before":"2025-07-01T13:11:00","not_after":"2025-09-29T13:10:59","serial_number":"0693231ff5e3212cabc2588e38b5d8337528","result_count":2},{"issuer_ca_id":295819,"issuer_name":"C=US, O=Let's Encrypt, CN=E6","common_name":"app.fleischkombinat-ost.de","name_value":"app.fleischkombinat-ost.de","id":19374489847,"entry_timestamp":"2025-07-01T14:09:30.117","not_before":"2025-07-01T13:11:00","not_after":"2025-09-29T13:10:59","serial_number":"0693231ff5e3212cabc2588e38b5d8337528","result_count":2}]}
|
1
cache/crtsh/cc24_dev.json
vendored
Normal file
1
cache/crtsh/cc24_dev.json
vendored
Normal file
File diff suppressed because one or more lines are too long
1
cache/crtsh/cloud_cc24_dev.json
vendored
Normal file
1
cache/crtsh/cloud_cc24_dev.json
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"domain":"cloud.cc24.dev","first_cached":"2025-09-14T21:36:46.109884+00:00","last_upstream_query":"2025-09-14T21:36:46.109891+00:00","upstream_query_count":1,"certificates":[{"issuer_ca_id":295815,"issuer_name":"C=US, O=Let's Encrypt, CN=R11","common_name":"cloud.cc24.dev","name_value":"cloud.cc24.dev","id":20275852221,"entry_timestamp":"2025-08-12T00:08:47.671","not_before":"2025-08-11T23:10:16","not_after":"2025-11-09T23:10:15","serial_number":"0531b80da7039a455eb889201f8e62ba8cc9","result_count":2},{"issuer_ca_id":295815,"issuer_name":"C=US, O=Let's Encrypt, CN=R11","common_name":"cloud.cc24.dev","name_value":"cloud.cc24.dev","id":20275837420,"entry_timestamp":"2025-08-12T00:08:47.014","not_before":"2025-08-11T23:10:16","not_after":"2025-11-09T23:10:15","serial_number":"0531b80da7039a455eb889201f8e62ba8cc9","result_count":2},{"issuer_ca_id":295815,"issuer_name":"C=US, O=Let's Encrypt, CN=R11","common_name":"cloud.cc24.dev","name_value":"cloud.cc24.dev","id":18987132752,"entry_timestamp":"2025-06-13T00:06:00.445","not_before":"2025-06-12T23:07:30","not_after":"2025-09-10T23:07:29","serial_number":"066841f1e247045c3bb244d599955addbc66","result_count":2},{"issuer_ca_id":295815,"issuer_name":"C=US, O=Let's Encrypt, CN=R11","common_name":"cloud.cc24.dev","name_value":"cloud.cc24.dev","id":18987133145,"entry_timestamp":"2025-06-13T00:06:00.158","not_before":"2025-06-12T23:07:30","not_after":"2025-09-10T23:07:29","serial_number":"066841f1e247045c3bb244d599955addbc66","result_count":2},{"issuer_ca_id":295815,"issuer_name":"C=US, O=Let's Encrypt, CN=R11","common_name":"cloud.cc24.dev","name_value":"cloud.cc24.dev","id":17831462233,"entry_timestamp":"2025-04-14T00:17:56.109","not_before":"2025-04-13T23:19:24","not_after":"2025-07-12T23:19:23","serial_number":"051efd08e5e21db1fe47698ba7cb273c05b7","result_count":2},{"issuer_ca_id":295815,"issuer_name":"C=US, O=Let's Encrypt, CN=R11","common_name":"cloud.cc24.dev","name_value":"cloud.cc24.dev","id":17831396685,"entry_timestamp":"2025-04-14T00:17:54.708","not_before":"2025-04-13T23:19:24","not_after":"2025-07-12T23:19:23","serial_number":"051efd08e5e21db1fe47698ba7cb273c05b7","result_count":2},{"issuer_ca_id":295814,"issuer_name":"C=US, O=Let's Encrypt, CN=R10","common_name":"cloud.cc24.dev","name_value":"cloud.cc24.dev","id":17170810540,"entry_timestamp":"2025-02-13T00:02:07.841","not_before":"2025-02-12T23:03:37","not_after":"2025-05-13T23:03:36","serial_number":"03fe022033cd38b75215385397375a1a3741","result_count":2},{"issuer_ca_id":295814,"issuer_name":"C=US, O=Let's Encrypt, CN=R10","common_name":"cloud.cc24.dev","name_value":"cloud.cc24.dev","id":16704105434,"entry_timestamp":"2025-02-13T00:02:07.708","not_before":"2025-02-12T23:03:37","not_after":"2025-05-13T23:03:36","serial_number":"03fe022033cd38b75215385397375a1a3741","result_count":2},{"issuer_ca_id":295815,"issuer_name":"C=US, O=Let's Encrypt, CN=R11","common_name":"cloud.cc24.dev","name_value":"cloud.cc24.dev","id":17122390503,"entry_timestamp":"2025-02-09T15:21:20.386","not_before":"2025-02-09T14:22:49","not_after":"2025-05-10T14:22:48","serial_number":"031fcf4e1368ddbc276836806c2d3df6c376","result_count":2},{"issuer_ca_id":295815,"issuer_name":"C=US, O=Let's Encrypt, CN=R11","common_name":"cloud.cc24.dev","name_value":"cloud.cc24.dev","id":16635540435,"entry_timestamp":"2025-02-09T15:21:19.831","not_before":"2025-02-09T14:22:49","not_after":"2025-05-10T14:22:48","serial_number":"031fcf4e1368ddbc276836806c2d3df6c376","result_count":2}]}
|
1
cache/crtsh/code_cc24_dev.json
vendored
Normal file
1
cache/crtsh/code_cc24_dev.json
vendored
Normal file
File diff suppressed because one or more lines are too long
1
cache/crtsh/console_s3_cc24_dev.json
vendored
Normal file
1
cache/crtsh/console_s3_cc24_dev.json
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"domain":"console.s3.cc24.dev","first_cached":"2025-09-14T21:33:25.149502+00:00","last_upstream_query":"2025-09-14T21:33:25.149505+00:00","upstream_query_count":1,"certificates":[{"issuer_ca_id":295819,"issuer_name":"C=US, O=Let's Encrypt, CN=E6","common_name":"console.s3.cc24.dev","name_value":"console.s3.cc24.dev","id":20287575466,"entry_timestamp":"2025-08-12T12:55:37.077","not_before":"2025-08-12T11:57:05","not_after":"2025-11-10T11:57:04","serial_number":"066bdfa83088f8d7e67284da94dd5d122ed6","result_count":2},{"issuer_ca_id":295819,"issuer_name":"C=US, O=Let's Encrypt, CN=E6","common_name":"console.s3.cc24.dev","name_value":"console.s3.cc24.dev","id":20287575457,"entry_timestamp":"2025-08-12T12:55:36.75","not_before":"2025-08-12T11:57:05","not_after":"2025-11-10T11:57:04","serial_number":"066bdfa83088f8d7e67284da94dd5d122ed6","result_count":2}]}
|
1
cache/crtsh/dnsrecon_cc24_dev.json
vendored
Normal file
1
cache/crtsh/dnsrecon_cc24_dev.json
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"domain":"dnsrecon.cc24.dev","first_cached":"2025-09-14T21:33:58.773156+00:00","last_upstream_query":"2025-09-14T21:33:58.773159+00:00","upstream_query_count":1,"certificates":[{"issuer_ca_id":295809,"issuer_name":"C=US, O=Let's Encrypt, CN=E8","common_name":"dnsrecon.cc24.dev","name_value":"dnsrecon.cc24.dev","id":20965278266,"entry_timestamp":"2025-09-12T09:49:23.247","not_before":"2025-09-12T08:50:53","not_after":"2025-12-11T08:50:52","serial_number":"060fbe619a364febd85aebccb1c6fcf7153f","result_count":2},{"issuer_ca_id":295809,"issuer_name":"C=US, O=Let's Encrypt, CN=E8","common_name":"dnsrecon.cc24.dev","name_value":"dnsrecon.cc24.dev","id":20965277886,"entry_timestamp":"2025-09-12T09:49:23.039","not_before":"2025-09-12T08:50:53","not_after":"2025-12-11T08:50:52","serial_number":"060fbe619a364febd85aebccb1c6fcf7153f","result_count":2}]}
|
1
cache/crtsh/element_cc24_dev.json
vendored
Normal file
1
cache/crtsh/element_cc24_dev.json
vendored
Normal file
File diff suppressed because one or more lines are too long
1
cache/crtsh/fleischkombinat-ost_de.json
vendored
Normal file
1
cache/crtsh/fleischkombinat-ost_de.json
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"domain":"fleischkombinat-ost.de","first_cached":"2025-09-14T21:11:00.028593+00:00","last_upstream_query":"2025-09-14T21:11:00.028596+00:00","upstream_query_count":1,"certificates":[{"issuer_ca_id":295819,"issuer_name":"C=US, O=Let's Encrypt, CN=E6","common_name":"app.fleischkombinat-ost.de","name_value":"app.fleischkombinat-ost.de","id":19374493240,"entry_timestamp":"2025-07-01T14:09:36.354","not_before":"2025-07-01T13:11:00","not_after":"2025-09-29T13:10:59","serial_number":"0693231ff5e3212cabc2588e38b5d8337528","result_count":2},{"issuer_ca_id":295819,"issuer_name":"C=US, O=Let's Encrypt, CN=E6","common_name":"app.fleischkombinat-ost.de","name_value":"app.fleischkombinat-ost.de","id":19374489847,"entry_timestamp":"2025-07-01T14:09:30.117","not_before":"2025-07-01T13:11:00","not_after":"2025-09-29T13:10:59","serial_number":"0693231ff5e3212cabc2588e38b5d8337528","result_count":2},{"issuer_ca_id":295810,"issuer_name":"C=US, O=Let's Encrypt, CN=E5","common_name":"fleischkombinat-ost.de","name_value":"fleischkombinat-ost.de","id":19374378473,"entry_timestamp":"2025-07-01T14:01:50.593","not_before":"2025-07-01T13:03:20","not_after":"2025-09-29T13:03:19","serial_number":"06315dfed8c93d1497c26b21c448857b6f2c","result_count":2},{"issuer_ca_id":295810,"issuer_name":"C=US, O=Let's Encrypt, CN=E5","common_name":"fleischkombinat-ost.de","name_value":"fleischkombinat-ost.de","id":19374376791,"entry_timestamp":"2025-07-01T14:01:50.385","not_before":"2025-07-01T13:03:20","not_after":"2025-09-29T13:03:19","serial_number":"06315dfed8c93d1497c26b21c448857b6f2c","result_count":2},{"issuer_ca_id":158800,"issuer_name":"C=AT, O=ZeroSSL, CN=ZeroSSL RSA Domain Secure Site CA","common_name":"*.fleischkombinat-ost.de","name_value":"*.fleischkombinat-ost.de\nfleischkombinat-ost.de","id":19369530786,"entry_timestamp":"2025-07-01T09:12:02.496","not_before":"2025-07-01T00:00:00","not_after":"2025-09-29T23:59:59","serial_number":"07c51a2c164b3a6c5769b0e03a9f4085","result_count":3},{"issuer_ca_id":158800,"issuer_name":"C=AT, O=ZeroSSL, CN=ZeroSSL RSA Domain Secure Site CA","common_name":"*.fleischkombinat-ost.de","name_value":"*.fleischkombinat-ost.de\nfleischkombinat-ost.de","id":19369530780,"entry_timestamp":"2025-07-01T09:12:01.04","not_before":"2025-07-01T00:00:00","not_after":"2025-09-29T23:59:59","serial_number":"07c51a2c164b3a6c5769b0e03a9f4085","result_count":3}]}
|
1
cache/crtsh/forensics_cc24_dev.json
vendored
Normal file
1
cache/crtsh/forensics_cc24_dev.json
vendored
Normal file
File diff suppressed because one or more lines are too long
1
cache/crtsh/forum_cc24_dev.json
vendored
Normal file
1
cache/crtsh/forum_cc24_dev.json
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"domain":"forum.cc24.dev","first_cached":"2025-09-14T21:37:55.208070+00:00","last_upstream_query":"2025-09-14T21:37:55.208073+00:00","upstream_query_count":1,"certificates":[{"issuer_ca_id":295815,"issuer_name":"C=US, O=Let's Encrypt, CN=R11","common_name":"forum.cc24.dev","name_value":"forum.cc24.dev","id":20275855672,"entry_timestamp":"2025-08-12T00:09:25.872","not_before":"2025-08-11T23:10:53","not_after":"2025-11-09T23:10:52","serial_number":"05e7fa80df3e45a7ecec44d25dafad0904e5","result_count":2},{"issuer_ca_id":295815,"issuer_name":"C=US, O=Let's Encrypt, CN=R11","common_name":"forum.cc24.dev","name_value":"forum.cc24.dev","id":20275850214,"entry_timestamp":"2025-08-12T00:09:23.776","not_before":"2025-08-11T23:10:53","not_after":"2025-11-09T23:10:52","serial_number":"05e7fa80df3e45a7ecec44d25dafad0904e5","result_count":2},{"issuer_ca_id":295814,"issuer_name":"C=US, O=Let's Encrypt, CN=R10","common_name":"forum.cc24.dev","name_value":"forum.cc24.dev","id":18987146858,"entry_timestamp":"2025-06-13T00:06:40.302","not_before":"2025-06-12T23:08:03","not_after":"2025-09-10T23:08:02","serial_number":"055efe65125ee83f14545a2f5d99590e635f","result_count":2},{"issuer_ca_id":295814,"issuer_name":"C=US, O=Let's Encrypt, CN=R10","common_name":"forum.cc24.dev","name_value":"forum.cc24.dev","id":18987145127,"entry_timestamp":"2025-06-13T00:06:34.097","not_before":"2025-06-12T23:08:03","not_after":"2025-09-10T23:08:02","serial_number":"055efe65125ee83f14545a2f5d99590e635f","result_count":2},{"issuer_ca_id":295814,"issuer_name":"C=US, O=Let's Encrypt, CN=R10","common_name":"forum.cc24.dev","name_value":"forum.cc24.dev","id":17831472866,"entry_timestamp":"2025-04-14T00:18:33.004","not_before":"2025-04-13T23:20:02","not_after":"2025-07-12T23:20:01","serial_number":"064fefe879ff5e732ce6c6a63f1e911db568","result_count":2},{"issuer_ca_id":295814,"issuer_name":"C=US, O=Let's Encrypt, CN=R10","common_name":"forum.cc24.dev","name_value":"forum.cc24.dev","id":17831402949,"entry_timestamp":"2025-04-14T00:18:32.224","not_before":"2025-04-13T23:20:02","not_after":"2025-07-12T23:20:01","serial_number":"064fefe879ff5e732ce6c6a63f1e911db568","result_count":2},{"issuer_ca_id":295815,"issuer_name":"C=US, O=Let's Encrypt, CN=R11","common_name":"forum.cc24.dev","name_value":"forum.cc24.dev","id":17170813968,"entry_timestamp":"2025-02-13T00:02:16.332","not_before":"2025-02-12T23:03:44","not_after":"2025-05-13T23:03:43","serial_number":"036e8e11288b54228648cab399477ea43c56","result_count":2},{"issuer_ca_id":295815,"issuer_name":"C=US, O=Let's Encrypt, CN=R11","common_name":"forum.cc24.dev","name_value":"forum.cc24.dev","id":16705244862,"entry_timestamp":"2025-02-13T00:02:14.119","not_before":"2025-02-12T23:03:44","not_after":"2025-05-13T23:03:43","serial_number":"036e8e11288b54228648cab399477ea43c56","result_count":2},{"issuer_ca_id":295815,"issuer_name":"C=US, O=Let's Encrypt, CN=R11","common_name":"forum.cc24.dev","name_value":"forum.cc24.dev","id":17122401304,"entry_timestamp":"2025-02-09T15:21:53.869","not_before":"2025-02-09T14:23:23","not_after":"2025-05-10T14:23:22","serial_number":"03e0928358e7636b3cc59a7dbc3b188bd7ca","result_count":2},{"issuer_ca_id":295815,"issuer_name":"C=US, O=Let's Encrypt, CN=R11","common_name":"forum.cc24.dev","name_value":"forum.cc24.dev","id":16636457115,"entry_timestamp":"2025-02-09T15:21:53.738","not_before":"2025-02-09T14:23:23","not_after":"2025-05-10T14:23:22","serial_number":"03e0928358e7636b3cc59a7dbc3b188bd7ca","result_count":2}]}
|
1
cache/crtsh/git_cc24_dev.json
vendored
Normal file
1
cache/crtsh/git_cc24_dev.json
vendored
Normal file
File diff suppressed because one or more lines are too long
1
cache/crtsh/graph_cc24_dev.json
vendored
Normal file
1
cache/crtsh/graph_cc24_dev.json
vendored
Normal file
File diff suppressed because one or more lines are too long
1
cache/crtsh/hoarder_cc24_dev.json
vendored
Normal file
1
cache/crtsh/hoarder_cc24_dev.json
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"domain":"hoarder.cc24.dev","first_cached":"2025-09-14T21:33:21.961821+00:00","last_upstream_query":"2025-09-14T21:33:21.961824+00:00","upstream_query_count":1,"certificates":[{"issuer_ca_id":295814,"issuer_name":"C=US, O=Let's Encrypt, CN=R10","common_name":"hoarder.cc24.dev","name_value":"hoarder.cc24.dev","id":17651896110,"entry_timestamp":"2025-04-03T19:39:13.874","not_before":"2025-04-03T18:40:43","not_after":"2025-07-02T18:40:42","serial_number":"062e2d17ef9c31ca560ab40299e0e00701c0","result_count":2},{"issuer_ca_id":295814,"issuer_name":"C=US, O=Let's Encrypt, CN=R10","common_name":"hoarder.cc24.dev","name_value":"hoarder.cc24.dev","id":17610697718,"entry_timestamp":"2025-04-03T19:39:13.522","not_before":"2025-04-03T18:40:43","not_after":"2025-07-02T18:40:42","serial_number":"062e2d17ef9c31ca560ab40299e0e00701c0","result_count":2}]}
|
1
cache/crtsh/hub_cc24_dev.json
vendored
Normal file
1
cache/crtsh/hub_cc24_dev.json
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"domain":"hub.cc24.dev","first_cached":"2025-09-14T21:33:48.364903+00:00","last_upstream_query":"2025-09-14T21:33:48.364908+00:00","upstream_query_count":1,"certificates":[{"issuer_ca_id":295810,"issuer_name":"C=US, O=Let's Encrypt, CN=E5","common_name":"hub.cc24.dev","name_value":"hub.cc24.dev","id":20317572962,"entry_timestamp":"2025-08-14T00:02:25.791","not_before":"2025-08-13T23:03:55","not_after":"2025-11-11T23:03:54","serial_number":"05ee38e8ae7bff4c5c414784e2c64d933b7a","result_count":2},{"issuer_ca_id":295810,"issuer_name":"C=US, O=Let's Encrypt, CN=E5","common_name":"hub.cc24.dev","name_value":"hub.cc24.dev","id":20317572564,"entry_timestamp":"2025-08-14T00:02:25.581","not_before":"2025-08-13T23:03:55","not_after":"2025-11-11T23:03:54","serial_number":"05ee38e8ae7bff4c5c414784e2c64d933b7a","result_count":2},{"issuer_ca_id":295819,"issuer_name":"C=US, O=Let's Encrypt, CN=E6","common_name":"hub.cc24.dev","name_value":"hub.cc24.dev","id":20189230503,"entry_timestamp":"2025-08-08T00:05:51.4","not_before":"2025-08-07T23:07:21","not_after":"2025-11-05T23:07:20","serial_number":"0502ddbfff49d3df12f3a15ba33ad895ccf1","result_count":2},{"issuer_ca_id":295819,"issuer_name":"C=US, O=Let's Encrypt, CN=E6","common_name":"hub.cc24.dev","name_value":"hub.cc24.dev","id":20189230585,"entry_timestamp":"2025-08-08T00:05:51.234","not_before":"2025-08-07T23:07:21","not_after":"2025-11-05T23:07:20","serial_number":"0502ddbfff49d3df12f3a15ba33ad895ccf1","result_count":2},{"issuer_ca_id":295819,"issuer_name":"C=US, O=Let's Encrypt, CN=E6","common_name":"hub.cc24.dev","name_value":"hub.cc24.dev","id":20060643580,"entry_timestamp":"2025-08-02T00:02:15.895","not_before":"2025-08-01T23:03:45","not_after":"2025-10-30T23:03:44","serial_number":"05c750b95573daa27e1a55a8803a2e6d21ad","result_count":2},{"issuer_ca_id":295819,"issuer_name":"C=US, O=Let's Encrypt, CN=E6","common_name":"hub.cc24.dev","name_value":"hub.cc24.dev","id":20060643807,"entry_timestamp":"2025-08-02T00:02:15.683","not_before":"2025-08-01T23:03:45","not_after":"2025-10-30T23:03:44","serial_number":"05c750b95573daa27e1a55a8803a2e6d21ad","result_count":2},{"issuer_ca_id":295819,"issuer_name":"C=US, O=Let's Encrypt, CN=E6","common_name":"hub.cc24.dev","name_value":"hub.cc24.dev","id":19929376671,"entry_timestamp":"2025-07-27T00:02:55.144","not_before":"2025-07-26T23:04:22","not_after":"2025-10-24T23:04:21","serial_number":"052c4fe632cf76d0306310cfb9faecc224ba","result_count":2},{"issuer_ca_id":295819,"issuer_name":"C=US, O=Let's Encrypt, CN=E6","common_name":"hub.cc24.dev","name_value":"hub.cc24.dev","id":19929375724,"entry_timestamp":"2025-07-27T00:02:52.759","not_before":"2025-07-26T23:04:22","not_after":"2025-10-24T23:04:21","serial_number":"052c4fe632cf76d0306310cfb9faecc224ba","result_count":2},{"issuer_ca_id":295819,"issuer_name":"C=US, O=Let's Encrypt, CN=E6","common_name":"hub.cc24.dev","name_value":"hub.cc24.dev","id":19803769083,"entry_timestamp":"2025-07-21T00:01:15.171","not_before":"2025-07-20T23:02:40","not_after":"2025-10-18T23:02:39","serial_number":"05e9927e9ea80c233e8a9502f11e4958cd06","result_count":2},{"issuer_ca_id":295819,"issuer_name":"C=US, O=Let's Encrypt, CN=E6","common_name":"hub.cc24.dev","name_value":"hub.cc24.dev","id":19803778513,"entry_timestamp":"2025-07-21T00:01:10.987","not_before":"2025-07-20T23:02:40","not_after":"2025-10-18T23:02:39","serial_number":"05e9927e9ea80c233e8a9502f11e4958cd06","result_count":2},{"issuer_ca_id":295810,"issuer_name":"C=US, O=Let's Encrypt, CN=E5","common_name":"hub.cc24.dev","name_value":"hub.cc24.dev","id":19665369127,"entry_timestamp":"2025-07-14T21:15:11.092","not_before":"2025-07-14T20:16:38","not_after":"2025-10-12T20:16:37","serial_number":"069e06aa4855496ab6b766889d6b45135d4e","result_count":2},{"issuer_ca_id":295810,"issuer_name":"C=US, O=Let's Encrypt, CN=E5","common_name":"hub.cc24.dev","name_value":"hub.cc24.dev","id":19665373526,"entry_timestamp":"2025-07-14T21:15:08.866","not_before":"2025-07-14T20:16:38","not_after":"2025-10-12T20:16:37","serial_number":"069e06aa4855496ab6b766889d6b45135d4e","result_count":2}]}
|
1
cache/crtsh/keep_cc24_dev.json
vendored
Normal file
1
cache/crtsh/keep_cc24_dev.json
vendored
Normal file
File diff suppressed because one or more lines are too long
1
cache/crtsh/matrix_cc24_dev.json
vendored
Normal file
1
cache/crtsh/matrix_cc24_dev.json
vendored
Normal file
File diff suppressed because one or more lines are too long
1
cache/crtsh/misp_cc24_dev.json
vendored
Normal file
1
cache/crtsh/misp_cc24_dev.json
vendored
Normal file
File diff suppressed because one or more lines are too long
1
cache/crtsh/mx_kundenserver_de.json
vendored
Normal file
1
cache/crtsh/mx_kundenserver_de.json
vendored
Normal file
File diff suppressed because one or more lines are too long
1
cache/crtsh/s3_cc24_dev.json
vendored
Normal file
1
cache/crtsh/s3_cc24_dev.json
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"domain":"s3.cc24.dev","first_cached":"2025-09-14T21:38:35.568153+00:00","last_upstream_query":"2025-09-14T21:38:35.568155+00:00","upstream_query_count":1,"certificates":[{"issuer_ca_id":295819,"issuer_name":"C=US, O=Let's Encrypt, CN=E6","common_name":"console.s3.cc24.dev","name_value":"console.s3.cc24.dev","id":20287575466,"entry_timestamp":"2025-08-12T12:55:37.077","not_before":"2025-08-12T11:57:05","not_after":"2025-11-10T11:57:04","serial_number":"066bdfa83088f8d7e67284da94dd5d122ed6","result_count":2},{"issuer_ca_id":295819,"issuer_name":"C=US, O=Let's Encrypt, CN=E6","common_name":"console.s3.cc24.dev","name_value":"console.s3.cc24.dev","id":20287575457,"entry_timestamp":"2025-08-12T12:55:36.75","not_before":"2025-08-12T11:57:05","not_after":"2025-11-10T11:57:04","serial_number":"066bdfa83088f8d7e67284da94dd5d122ed6","result_count":2},{"issuer_ca_id":295819,"issuer_name":"C=US, O=Let's Encrypt, CN=E6","common_name":"s3.cc24.dev","name_value":"s3.cc24.dev","id":20285693027,"entry_timestamp":"2025-08-12T10:58:41.611","not_before":"2025-08-12T10:00:11","not_after":"2025-11-10T10:00:10","serial_number":"06f0f73404258136977fdbfe4cf51db786a7","result_count":2},{"issuer_ca_id":295819,"issuer_name":"C=US, O=Let's Encrypt, CN=E6","common_name":"s3.cc24.dev","name_value":"s3.cc24.dev","id":20285693495,"entry_timestamp":"2025-08-12T10:58:41.366","not_before":"2025-08-12T10:00:11","not_after":"2025-11-10T10:00:10","serial_number":"06f0f73404258136977fdbfe4cf51db786a7","result_count":2}]}
|
1
cache/crtsh/timesketch_cc24_dev.json
vendored
Normal file
1
cache/crtsh/timesketch_cc24_dev.json
vendored
Normal file
File diff suppressed because one or more lines are too long
1
cache/crtsh/www_overcuriousity_org.json
vendored
Normal file
1
cache/crtsh/www_overcuriousity_org.json
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"domain":"www.overcuriousity.org","first_cached":"2025-09-14T21:16:24.041839+00:00","last_upstream_query":"2025-09-14T21:16:24.041842+00:00","upstream_query_count":1,"certificates":[{"issuer_ca_id":295819,"issuer_name":"C=US, O=Let's Encrypt, CN=E6","common_name":"signaling.mikoshi.de","name_value":"www.overcuriousity.org","id":19208272013,"entry_timestamp":"2025-06-23T18:20:29.619","not_before":"2025-06-23T17:21:56","not_after":"2025-09-21T17:21:55","serial_number":"0540be6c8cb99dcaa5492af7b934f40466f9","result_count":1},{"issuer_ca_id":295819,"issuer_name":"C=US, O=Let's Encrypt, CN=E6","common_name":"signaling.mikoshi.de","name_value":"www.overcuriousity.org","id":19208263539,"entry_timestamp":"2025-06-23T18:20:26.82","not_before":"2025-06-23T17:21:56","not_after":"2025-09-21T17:21:55","serial_number":"0540be6c8cb99dcaa5492af7b934f40466f9","result_count":1}]}
|
@ -414,6 +414,29 @@ class GraphManager:
|
||||
self.last_modified = datetime.now(timezone.utc).isoformat()
|
||||
return True
|
||||
|
||||
def remove_node(self, node_id: str) -> bool:
|
||||
"""Remove a node and its connected edges from the graph."""
|
||||
if not self.graph.has_node(node_id):
|
||||
return False
|
||||
|
||||
# Remove node from the graph (NetworkX handles removing connected edges)
|
||||
self.graph.remove_node(node_id)
|
||||
|
||||
# Clean up the correlation index
|
||||
keys_to_delete = []
|
||||
for value, nodes in self.correlation_index.items():
|
||||
if node_id in nodes:
|
||||
del nodes[node_id]
|
||||
if not nodes: # If no other nodes are associated with this value, remove it
|
||||
keys_to_delete.append(value)
|
||||
|
||||
for key in keys_to_delete:
|
||||
if key in self.correlation_index:
|
||||
del self.correlation_index[key]
|
||||
|
||||
self.last_modified = datetime.now(timezone.utc).isoformat()
|
||||
return True
|
||||
|
||||
def get_node_count(self) -> int:
|
||||
"""Get total number of nodes in the graph."""
|
||||
return self.graph.number_of_nodes()
|
||||
|
@ -397,6 +397,35 @@ input[type="text"]:focus, select:focus {
|
||||
background: rgba(42, 42, 42, 1);
|
||||
}
|
||||
|
||||
.graph-context-menu {
|
||||
position: absolute;
|
||||
z-index: 1000;
|
||||
background-color: #2a2a2a;
|
||||
border: 1px solid #444;
|
||||
box-shadow: 0 2px 5px rgba(0,0,0,0.5);
|
||||
display: none;
|
||||
font-family: 'Roboto Mono', monospace;
|
||||
font-size: 0.9rem;
|
||||
color: #c7c7c7;
|
||||
}
|
||||
|
||||
.graph-context-menu ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.graph-context-menu ul li {
|
||||
padding: 0.75rem 1rem;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.graph-context-menu ul li:hover {
|
||||
background-color: #3a3a3a;
|
||||
color: #00ff41;
|
||||
}
|
||||
|
||||
.graph-placeholder {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
@ -12,6 +12,8 @@ class GraphManager {
|
||||
this.isInitialized = false;
|
||||
this.currentLayout = 'physics';
|
||||
this.nodeInfoPopup = null;
|
||||
this.contextMenu = null;
|
||||
this.history = [];
|
||||
|
||||
this.options = {
|
||||
nodes: {
|
||||
@ -117,6 +119,8 @@ class GraphManager {
|
||||
};
|
||||
|
||||
this.createNodeInfoPopup();
|
||||
this.createContextMenu();
|
||||
document.body.addEventListener('click', () => this.hideContextMenu());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -129,6 +133,24 @@ class GraphManager {
|
||||
document.body.appendChild(this.nodeInfoPopup);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create context menu
|
||||
*/
|
||||
createContextMenu() {
|
||||
this.contextMenu = document.createElement('div');
|
||||
this.contextMenu.id = 'graph-context-menu';
|
||||
this.contextMenu.className = 'graph-context-menu';
|
||||
this.contextMenu.style.display = 'none';
|
||||
|
||||
// Prevent body click listener from firing when clicking the menu itself
|
||||
this.contextMenu.addEventListener('click', (event) => {
|
||||
event.stopPropagation();
|
||||
});
|
||||
|
||||
document.body.appendChild(this.contextMenu);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Initialize the network graph
|
||||
*/
|
||||
@ -173,6 +195,8 @@ class GraphManager {
|
||||
<button class="graph-control-btn" id="graph-fit" title="Fit to Screen">[FIT]</button>
|
||||
<button class="graph-control-btn" id="graph-physics" title="Toggle Physics">[PHYSICS]</button>
|
||||
<button class="graph-control-btn" id="graph-cluster" title="Cluster Nodes">[CLUSTER]</button>
|
||||
<button class="graph-control-btn" id="graph-unhide" title="Unhide All">[UNHIDE]</button>
|
||||
<button class="graph-control-btn" id="graph-revert" title="Revert Last Action">[REVERT]</button>
|
||||
`;
|
||||
|
||||
this.container.appendChild(controlsContainer);
|
||||
@ -181,6 +205,8 @@ class GraphManager {
|
||||
document.getElementById('graph-fit').addEventListener('click', () => this.fitView());
|
||||
document.getElementById('graph-physics').addEventListener('click', () => this.togglePhysics());
|
||||
document.getElementById('graph-cluster').addEventListener('click', () => this.toggleClustering());
|
||||
document.getElementById('graph-unhide').addEventListener('click', () => this.unhideAll());
|
||||
document.getElementById('graph-revert').addEventListener('click', () => this.revertLastAction());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -189,8 +215,29 @@ class GraphManager {
|
||||
setupNetworkEvents() {
|
||||
if (!this.network) return;
|
||||
|
||||
// Use a standard DOM event listener for the context menu for better reliability
|
||||
this.container.addEventListener('contextmenu', (event) => {
|
||||
event.preventDefault();
|
||||
|
||||
// Get coordinates relative to the canvas
|
||||
const pointer = {
|
||||
x: event.offsetX,
|
||||
y: event.offsetY
|
||||
};
|
||||
|
||||
const nodeId = this.network.getNodeAt(pointer);
|
||||
|
||||
if (nodeId) {
|
||||
// Pass the original client event for positioning
|
||||
this.showContextMenu(nodeId, event);
|
||||
} else {
|
||||
this.hideContextMenu();
|
||||
}
|
||||
});
|
||||
|
||||
// Node click event with details
|
||||
this.network.on('click', (params) => {
|
||||
this.hideContextMenu();
|
||||
if (params.nodes.length > 0) {
|
||||
const nodeId = params.nodes[0];
|
||||
if (this.network.isCluster(nodeId)) {
|
||||
@ -216,10 +263,6 @@ class GraphManager {
|
||||
}
|
||||
});
|
||||
|
||||
this.network.on('oncontext', (params) => {
|
||||
params.event.preventDefault();
|
||||
});
|
||||
|
||||
// Stabilization events with progress
|
||||
this.network.on('stabilizationProgress', (params) => {
|
||||
const progress = params.iterations / params.total;
|
||||
@ -846,6 +889,7 @@ class GraphManager {
|
||||
clear() {
|
||||
this.nodes.clear();
|
||||
this.edges.clear();
|
||||
this.history = [];
|
||||
|
||||
// Show placeholder
|
||||
const placeholder = this.container.querySelector('.graph-placeholder');
|
||||
@ -919,6 +963,238 @@ class GraphManager {
|
||||
|
||||
console.log('Filters applied.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Show context menu for a node
|
||||
* @param {string} nodeId - The ID of the node
|
||||
* @param {Event} event - The contextmenu event
|
||||
*/
|
||||
showContextMenu(nodeId, event) {
|
||||
this.contextMenu.innerHTML = `
|
||||
<ul>
|
||||
<li data-action="hide" data-node-id="${nodeId}">Hide Node</li>
|
||||
<li data-action="delete" data-node-id="${nodeId}">Delete Node</li>
|
||||
</ul>
|
||||
`;
|
||||
this.contextMenu.style.left = `${event.clientX}px`;
|
||||
this.contextMenu.style.top = `${event.clientY}px`;
|
||||
this.contextMenu.style.display = 'block';
|
||||
|
||||
this.contextMenu.querySelectorAll('li').forEach(item => {
|
||||
item.addEventListener('click', (e) => {
|
||||
const action = e.target.dataset.action;
|
||||
const nodeId = e.target.dataset.nodeId;
|
||||
this.performContextMenuAction(action, nodeId);
|
||||
this.hideContextMenu();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide the context menu
|
||||
*/
|
||||
hideContextMenu() {
|
||||
if (this.contextMenu) {
|
||||
this.contextMenu.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform action from the context menu
|
||||
* @param {string} action - The action to perform ('hide' or 'delete')
|
||||
* @param {string} nodeId - The ID of the node
|
||||
*/
|
||||
performContextMenuAction(action, nodeId) {
|
||||
switch (action) {
|
||||
case 'hide':
|
||||
this.hideNodeAndOrphans(nodeId);
|
||||
break;
|
||||
case 'delete':
|
||||
this.deleteNodeAndOrphans(nodeId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an operation to the history stack
|
||||
* @param {string} type - The type of operation ('hide', 'delete')
|
||||
* @param {Object} data - The data needed to revert the operation
|
||||
*/
|
||||
addToHistory(type, data) {
|
||||
this.history.push({ type, data });
|
||||
}
|
||||
|
||||
/**
|
||||
* Revert the last action
|
||||
*/
|
||||
async revertLastAction() {
|
||||
const lastAction = this.history.pop();
|
||||
if (!lastAction) {
|
||||
console.log('No actions to revert.');
|
||||
return;
|
||||
}
|
||||
|
||||
switch (lastAction.type) {
|
||||
case 'hide':
|
||||
// Revert hiding nodes by un-hiding them
|
||||
const updates = lastAction.data.nodeIds.map(id => ({ id: id, hidden: false }));
|
||||
this.nodes.update(updates);
|
||||
break;
|
||||
case 'delete':
|
||||
try {
|
||||
const response = await fetch('/api/graph/revert', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(lastAction)
|
||||
});
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
console.log('Delete action reverted successfully on backend.');
|
||||
// Re-add all nodes and edges from the history to the local view
|
||||
this.nodes.add(lastAction.data.nodes);
|
||||
this.edges.add(lastAction.data.edges);
|
||||
} else {
|
||||
console.error('Failed to revert delete action on backend:', result.error);
|
||||
// Push the action back onto the history stack if the API call failed
|
||||
this.history.push(lastAction);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error during revert API call:', error);
|
||||
this.history.push(lastAction);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide a node and recursively hide any neighbors that become disconnected.
|
||||
* @param {string} nodeId - The ID of the node to start hiding from.
|
||||
*/
|
||||
hideNodeAndOrphans(nodeId) {
|
||||
const historyData = { nodeIds: [] };
|
||||
const queue = [nodeId];
|
||||
const visited = new Set([nodeId]);
|
||||
|
||||
while (queue.length > 0) {
|
||||
const currentId = queue.shift();
|
||||
const node = this.nodes.get(currentId);
|
||||
if (!node || node.hidden) continue;
|
||||
|
||||
// 1. Hide the current node and add to history
|
||||
this.nodes.update({ id: currentId, hidden: true });
|
||||
historyData.nodeIds.push(currentId);
|
||||
|
||||
// 2. Check its neighbors
|
||||
const neighbors = this.network.getConnectedNodes(currentId);
|
||||
for (const neighborId of neighbors) {
|
||||
if (visited.has(neighborId)) continue;
|
||||
|
||||
const connectedEdges = this.network.getConnectedEdges(neighborId);
|
||||
let hasVisibleEdge = false;
|
||||
// 3. See if the neighbor still has any visible connections
|
||||
for (const edgeId of connectedEdges) {
|
||||
const edge = this.edges.get(edgeId);
|
||||
const sourceNode = this.nodes.get(edge.from);
|
||||
const targetNode = this.nodes.get(edge.to);
|
||||
if ((sourceNode && !sourceNode.hidden) && (targetNode && !targetNode.hidden)) {
|
||||
hasVisibleEdge = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 4. If no visible connections, add to queue to be hidden
|
||||
if (!hasVisibleEdge) {
|
||||
queue.push(neighborId);
|
||||
visited.add(neighborId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (historyData.nodeIds.length > 0) {
|
||||
this.addToHistory('hide', historyData);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a node and recursively delete any neighbors that become disconnected.
|
||||
* @param {string} nodeId - The ID of the node to start deleting from.
|
||||
*/
|
||||
async deleteNodeAndOrphans(nodeId) {
|
||||
const deletionQueue = [nodeId];
|
||||
const processedForDeletion = new Set([nodeId]);
|
||||
const historyData = { nodes: [], edges: [] };
|
||||
let operationFailed = false;
|
||||
|
||||
while (deletionQueue.length > 0) {
|
||||
const currentId = deletionQueue.shift();
|
||||
const node = this.nodes.get(currentId);
|
||||
if (!node) continue;
|
||||
|
||||
const neighbors = this.network.getConnectedNodes(currentId);
|
||||
const connectedEdgeIds = this.network.getConnectedEdges(currentId);
|
||||
const edges = this.edges.get(connectedEdgeIds);
|
||||
|
||||
// Store state for potential revert
|
||||
historyData.nodes.push(node);
|
||||
historyData.edges.push(...edges);
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/graph/node/${currentId}`, { method: 'DELETE' });
|
||||
const result = await response.json();
|
||||
|
||||
if (!result.success) {
|
||||
console.error(`Failed to delete node ${currentId} from backend:`, result.error);
|
||||
operationFailed = true;
|
||||
break;
|
||||
}
|
||||
|
||||
console.log(`Node ${currentId} deleted from backend.`);
|
||||
this.nodes.remove({ id: currentId }); // Remove from view
|
||||
|
||||
// Check if former neighbors are now orphans
|
||||
neighbors.forEach(neighborId => {
|
||||
if (!processedForDeletion.has(neighborId) && this.nodes.get(neighborId)) {
|
||||
if (this.network.getConnectedEdges(neighborId).length === 0) {
|
||||
deletionQueue.push(neighborId);
|
||||
processedForDeletion.add(neighborId);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error during node deletion API call:', error);
|
||||
operationFailed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Add to history only if the entire operation was successful
|
||||
if (!operationFailed && historyData.nodes.length > 0) {
|
||||
// Ensure edges in history are unique
|
||||
historyData.edges = Array.from(new Map(historyData.edges.map(e => [e.id, e])).values());
|
||||
this.addToHistory('delete', historyData);
|
||||
} else if (operationFailed) {
|
||||
console.log("Reverting UI changes due to failed delete operation.");
|
||||
// If any part of the chain failed, restore the UI to its original state
|
||||
this.nodes.add(historyData.nodes);
|
||||
this.edges.add(historyData.edges);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unhide all hidden nodes
|
||||
*/
|
||||
unhideAll() {
|
||||
const allNodes = this.nodes.get({
|
||||
filter: (node) => node.hidden === true
|
||||
});
|
||||
const updates = allNodes.map(node => ({ id: node.id, hidden: false }));
|
||||
this.nodes.update(updates);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Export for use in main.js
|
||||
|
Loading…
x
Reference in New Issue
Block a user