66import functools
77import requests
88import libinspector .global_state
9+ from libinspector .privacy import is_ad_tracked
910import pandas as pd
1011from typing import Any
1112import streamlit as st
1213import logging
14+ import re
1315
1416config_file_name = 'config.json'
1517config_lock = threading .Lock ()
@@ -35,16 +37,80 @@ def show_warning():
3537 True if the warning is still being shown (user has not accepted).
3638 False if the user has accepted the warning and can proceed.
3739 """
40+ current_id = config_get ("prolific_id" , "" )
3841
39- if config_get ("suppress_warning" , False ):
42+ # --- GATE 1: PROLIFIC ID CHECK (Must be valid to proceed to confirmation) ---
43+ if is_prolific_id_valid (current_id ):
44+ # --- SHOW CONFIRMATION UI (Only reached if ID is valid but warning is unaccepted) ---
45+ st .subheader ("1. Prolific ID Confirmation" )
46+ st .info (f"Your currently stored ID is: `{ current_id } `" )
47+
48+ # Allows the user to change the ID, which forces them back through GATE 1
49+ if st .button ("Change Prolific ID" ):
50+ config_set ("prolific_id" , "" ) # Clear the stored ID
51+ st .rerun ()
52+
53+ if not config_get ("suppress_warning" , False ):
54+ st .markdown ("---" )
55+ st .subheader ("2. Network Monitoring Warning" )
56+ st .markdown (warning_text )
57+
58+ if st .button ("OK, I understand and wish to proceed" ):
59+ config_set ("suppress_warning" , True )
60+ st .rerun ()
61+
62+ return not is_prolific_id_valid (config_get ("prolific_id" , "" ))
63+ else :
64+ # ID is missing or invalid -> BLOCK and show input form
65+ st .subheader ("Prolific ID Required" )
66+ st .warning ("Please enter your Prolific ID to proceed. This ID is essential for data labeling." )
67+
68+ with st .form ("prolific_id_form" ):
69+ input_id = st .text_input (
70+ "Enter your Prolific ID (1-50 Alphanumeric Characters):" ,
71+ value = "" ,
72+ key = "prolific_id_input"
73+ ).strip ()
74+
75+ submitted = st .form_submit_button ("Submit ID" )
76+
77+ if submitted :
78+ if is_prolific_id_valid (input_id ):
79+ config_set ("prolific_id" , input_id )
80+ st .success ("Prolific ID accepted. Please review the details below." )
81+ st .rerun () # Rerun to jump to the confirmation step (GATE 2)
82+ else :
83+ st .error ("Invalid Prolific ID. Must be 1-50 alphanumeric characters." )
84+
85+ return True # BLOCK: ID check still needs resolution.
86+
87+
88+ def is_prolific_id_valid (prolific_id : str ) -> bool :
89+ """
90+ Performs sanity checks on the Prolific ID:
91+ 1. Not empty.
92+ 2. Length between 1 and 50 characters (inclusive).
93+ 3. Contains only alphanumeric characters (A-Z, a-z, 0-9).
94+ Args:
95+ prolific_id (str): The Prolific ID to validate.
96+
97+ Returns:
98+ bool: True if the ID is non-empty, 1-50 characters long, and alphanumeric; False otherwise.
99+ """
100+ if not prolific_id or not isinstance (prolific_id , str ):
101+ return False
102+
103+ # 2. Length check
104+ if not 1 <= len (prolific_id ) <= 50 :
105+ return False
106+
107+ # 3. Alphanumeric check using regex (ensures no special characters)
108+ if not re .fullmatch (r'^[a-zA-Z0-9]+$' , prolific_id ):
40109 return False
41110
42- st .markdown (warning_text )
43- if st .button ("OK, I understand and wish to proceed" ):
44- config_set ("suppress_warning" , True )
45- st .rerun ()
46111 return True
47112
113+
48114def bar_graph_data_frame (mac_address : str , now : int ):
49115 sixty_seconds_ago = now - 60
50116 db_conn , rwlock = libinspector .global_state .db_conn_and_lock
@@ -144,8 +210,10 @@ def get_remote_hostnames(mac_address: str):
144210 """
145211 with rwlock :
146212 rows = db_conn .execute (sql , (mac_address , mac_address )).fetchall ()
147- hostnames = [row ['hostname' ] for row in rows if row ['hostname' ]]
148- remote_hostnames = '+' .join (hostnames ) if hostnames else ""
213+ hostnames = [row ['hostname' ] for row in rows if row ['hostname' ]]
214+ is_tracked = any (is_ad_tracked (hostname ) for hostname in hostnames )
215+ config_set (f'tracked@{ mac_address } ' , is_tracked )
216+ remote_hostnames = '+' .join (hostnames ) if hostnames else ""
149217 return remote_hostnames
150218
151219
@@ -171,19 +239,23 @@ def call_predict_api(dhcp_hostname: str, oui_vendor: str, remote_hostnames: str,
171239 dict: The response text from the API.
172240 """
173241 api_key = os .environ .get ("API_KEY" , "momo" )
242+ device_tracked_key = f'tracked@{ mac_address } '
243+
174244 headers = {
175245 "Content-Type" : "application/json" ,
176246 "x-api-key" : api_key
177247 }
178248 data = {
249+ "prolific_id" : config_get ("prolific_id" , "" ),
250+ "mac_address" : mac_address ,
179251 "fields" : {
180252 "oui_friendly" : oui_vendor ,
181253 "dhcp_hostname" : dhcp_hostname ,
182254 "remote_hostnames" : remote_hostnames ,
183255 "user_agent_info" : "" ,
184256 "netdisco_info" : "" ,
185257 "user_labels" : "" ,
186- "talks_to_ads" : False
258+ "talks_to_ads" : config_get ( device_tracked_key , False )
187259 }
188260 }
189261 non_empty_field_values = [
0 commit comments