Building a Continuous Background Alarm Notification System with Dynamic Settings

Hello guys,

I’m making an alarm notification application.The application is to run all the time in the background as checking the queries (machines).Only when the user changes the parameters and the alert recievers (emails) will the app make a change and according to those changes will continue checking the queries (machines) in the background. At the moment I have four machines in four tabs, but I need help connecting the backend, to the settings and to the queries and also that the app works in the background all the time.Also I’m using an api to get the emails, and also to add new users or deleting the current ones.

1.This is my UI.

I will explain for this machine br206.

For Alert 1 settings I need to:
BDE Downtime after and the time the user picks (for an example 15 minutes) and Select Downtime Receivers ( emails from api) need to be tied to the first button and then the app changes the checking of the Alert 1 settings in the background.

Scrap rate (for an example 3%) and Scrap Alert After (means after 15 mins if the scrap rate is still 3%) need to be tied to the second button that sents an email to one of the Select Scrap Receivers that the user picks (from the api).

This is my UI code:

dcc.Tabs(id='tabs', value='br206', children=[
        dcc.Tab(label='BR206', value='br206', children=[
            html.Div([html.H1("BR206 Alert Notifications", className='header-title'),  # Centered header
                html.Div([
                # Alert 1 Settings
                    html.Div([
                        html.H3("Alert 1 Settings"),
                        html.Div(style={'border-top': '5px solid #ddd', 'margin-top': '10px', 'margin-bottom': '10px'}),
                        html.Div([
                            html.H3("BDE Downtime after:", className='dropdown-label'),
                            dcc.Dropdown(
                                id='br206-bde-connection-interval-dropdown-alert1',
                                options=generate_bde_connection_interval_options(),
                                value=15,
                                className='dropdown',
                            ),
                        ], className='bde-connection-interval', style={'margin-bottom': '10px'}),
                        html.H3("Select Downtime Receivers:", className='dropdown-label'),
                        dcc.Dropdown(
                            id='br206-downtime-employee-category-dropdown1',
                            options=[{'label': key, 'value': key} for key in employee_positions.keys()],
                            className='dropdown'
                        ),
                        html.Button('Make Changes', className='button' ,id='br206-make-changes-button1-downtime', n_clicks=0, style={'margin-top': '10px'}),
                        html.Div(style={'border-top': '5px solid #ddd', 'margin-top': '10px', 'margin-bottom':'10px'}),
                        html.H3("Scrap Rate:", className='input-header'),
                        dcc.Dropdown(
                            id='br206-scrap-rate-input-alert1',
                            options=[{'label': f'{i}%', 'value': f'{i}%'} for i in range(1, 13)],
                            value='3%',
                            className='input-field',
                            style={'width': '150px', 'margin-left': '14px'}
                        ),
                        html.H3("Scrap Alert After:", className='dropdown-label'),
                            dcc.Dropdown(
                                id='br206-scrap-connection-interval-dropdown-alert1',
                                options=generate_bde_connection_interval_options(),
                                value=15,
                                className='dropdown',
                            ),    
                        html.H3("Select Scrap Receivers:", className='dropdown-label'),
                        dcc.Dropdown(
                            id='br206-employee-category-dropdown-alert1',
                            options=[{'label': key, 'value': key} for key in employee_positions.keys()],
                            className='dropdown'
                        ),
                        # Div to display employee names based on selected category
                        html.Div(id='br206-employee-list-alert1'),
                        html.Button('Make Changes', className='button' ,id='br206-make-changes-button1-scrap', n_clicks=0, style={'margin-top': '10px'}),
                    ], className='alert-box'),
                ], style={'margin': '20px', 'border': '1px solid #ddd', 'padding': '20px', 'display': 'inline-block'}),
                # Alert 2 Settings
                html.Div([
                    html.H3("Alert 2 Settings"),
                    html.Div(style={'border-top': '5px solid #ddd', 'margin-top': '10px', 'margin-bottom': '10px'}),
                    # BDE Connection interval dropdown
                    html.Div([
                        html.H3("BDE Downtime after:", className='dropdown-label'),
                        dcc.Dropdown(
                            id='br206-bde-connection-interval-dropdown-alert2',
                            options=generate_bde_connection_interval_options(),
                            value=15,
                            className='dropdown'
                        ),
                    ], className='bde-connection-interval'),
                        html.H3("Select Downtime Receivers:", className='dropdown-label'),
                        dcc.Dropdown(
                            id='br206-downtime-employee-category-dropdown2',
                            options=[{'label': key, 'value': key} for key in employee_positions.keys()],
                            className='dropdown'
                        ),
                    html.Button('Make Changes', className='button' ,id='br206-make-changes-button2-downtime', n_clicks=0, style={'margin-top': '10px'}),
                    html.Div(style={'border-top': '5px solid #ddd', 'margin-top': '10px', 'margin-bottom': '10px'}),
                    html.H3("Scrap Rate:", className='input-header'),
                    dcc.Dropdown(
                        id='br206-scrap-rate-input-alert2',
                        options=[{'label': f'{i}%', 'value': f'{i}%'} for i in range(1, 13)],
                        value='3%',
                        className='input-field',
                        style={'width': '150px', 'margin-left': '14px'}
                    ),
                    html.H3("Scrap Alert After:", className='dropdown-label'),
                        dcc.Dropdown(
                            id='br206-scrap-connection-interval-dropdown-alert2',
                            options=generate_bde_connection_interval_options(),
                            value=15,
                            className='dropdown',
                        ),   
                    html.H3("Select Scrap Receivers:", className='dropdown-label'),
                    dcc.Dropdown(
                        id='br206-employee-category-dropdown-alert2',
                        options=[{'label': key, 'value': key} for key in employee_positions.keys()],
                        className='dropdown'
                    ),
                    # Div to display employee names based on selected category
                    html.Div(id='br206-employee-list-alert2'),
                    html.Button('Make Changes', className='button' ,id='br206-make-changes-button2-scrap', n_clicks=0, style={'margin-top': '10px'})
                ], className='alert-box', style={'margin': '20px', 'border': '1px solid #ddd', 'padding': '20px', 'display': 'inline-block'}),

                # Alert 3 Settings
                html.Div([
                    html.H3("Alert 3 Settings"),
                    html.Div(style={'border-top': '5px solid #ddd', 'margin-top': '10px', 'margin-bottom': '10px'}),
                    # BDE Connection interval dropdown
                    html.Div([
                        html.H3("BDE Downtime after:", className='dropdown-label'),
                        dcc.Dropdown(
                            id='br206-bde-connection-interval-dropdown-alert3',
                            options=generate_bde_connection_interval_options(),
                            value=15,
                            className='dropdown'
                        ),
                    ], className='bde-connection-interval'),
                        html.H3("Select Downtime Receivers:", className='dropdown-label'),
                        dcc.Dropdown(
                            id='br206-downtime-employee-category-dropdown3',
                            options=[{'label': key, 'value': key} for key in employee_positions.keys()],
                            className='dropdown'
                        ),
                    html.Button('Make Changes', className='button' ,id='br206-make-changes-button3-downtime', n_clicks=0, style={'margin-top': '10px'}),
                    html.Div(style={'border-top': '5px solid #ddd', 'margin-top': '10px', 'margin-bottom': '10px'}),
                    html.H3("Scrap Rate:", className='input-header'),
                    dcc.Dropdown(
                        id='br206-scrap-rate-input-alert3',
                        options=[{'label': f'{i}%', 'value': f'{i}%'} for i in range(1, 13)],
                        value='3%',
                        className='input-field',
                        style={'width': '150px', 'margin-left': '14px'}
                    ),
                    html.H3("Scrap Alert After:", className='dropdown-label'),
                        dcc.Dropdown(
                            id='br206-scrap-connection-interval-dropdown-alert3',
                            options=generate_bde_connection_interval_options(),
                            value=15,
                            className='dropdown',
                        ),   
                    html.H3("Select Scrap Receivers:", className='dropdown-label'),
                    dcc.Dropdown(
                        id='br206-employee-category-dropdown-alert3',
                        options=[{'label': key, 'value': key} for key in employee_positions.keys()],
                        className='dropdown'
                    ),
                    # Div to display employee names based on selected category
                    html.Div(id='br206-employee-list-alert3'),
                    html.Button('Make Changes', className='button' ,id='br206-make-changes-button3-scrap', n_clicks=0, style={'margin-top': '10px'}),
                    html.Button("Add New User", id="add-new-user-button1", className="btn btn-success button", n_clicks=0, style={'position': 'absolute', 'bottom': '150px', 'right': '1220px'}),
                ], className='alert-box', style={'margin': '20px', 'border': '1px solid #ddd', 'padding': '20px', 'display': 'inline-block'})
            ], style={'text-align': 'center'})
        ]),

I made all unique ID’s for all the settings and buttons, please keep in mind that I did this for all the machines and all the settings.
Now my backend code for BR206 Alert Settings 1 is

##############################     Alert 1 Settings #####################################

# Callback to handle changes in Alert 1 Downtime settings br206
@app.callback(
    Output('br206-output-alert1-downtime', 'children'),
    [Input('br206-make-changes-button1-downtime', 'n_clicks')],
    [State('br206-bde-connection-interval-dropdown-alert1', 'value'),
     State('br206-downtime-employee-category-dropdown1', 'value')]
)
def br206_handle_alert1_downtime_changes(n_clicks, bde_interval, downtime_receivers):
    if n_clicks > 0:
        print("BDE Interval:", bde_interval)
        print("Downtime Receivers:", downtime_receivers)
        return "Changes saved successfully!"

# Callback to handle changes in Alert 1 Scrap settings br206
@app.callback(
    Output('br206-output-alert1-scrap', 'children'),
    [Input('br206-make-changes-button1-scrap', 'n_clicks')],
    [State('br206-scrap-rate-input-alert1', 'value'),
     State('br206-scrap-connection-interval-dropdown-alert1', 'value'),
     State('br206-employee-category-dropdown-alert1', 'value')]
)
def br206_handle_alert1_scrap_changes(n_clicks, scrap_rate, scrap_interval, scrap_receivers):
    if n_clicks > 0:
        print("Scrap Rate:", scrap_rate)
        print("Scrap Interval:", scrap_interval)
        print("Scrap Receivers:", scrap_receivers)
        # Return a message or confirmation
        return "Changes saved successfully!"



##############################     Alert 2 Settings #####################################

# Callback to handle changes in Alert 2 Downtime settings br206
@app.callback(
    Output('br206-output-alert2-downtime', 'children'),
    [Input('br206-make-changes-button2-downtime', 'n_clicks')],
    [State('br206-bde-connection-interval-dropdown-alert2', 'value'),
     State('br206-downtime-employee-category-dropdown2', 'value')]
)
def br206_handle_alert2_downtime_changes(n_clicks, bde_interval, downtime_receivers):
    # Logic to save the selected parameters when the button is clicked
    if n_clicks > 0:
        # Save the selected BDE interval and downtime receivers
        # You can perform any further processing or database updates here
        print("BDE Interval:", bde_interval)
        print("Downtime Receivers:", downtime_receivers)
        return "Changes saved successfully!"

# Callback to handle changes in Alert 2 Scrap settings br206
@app.callback(
    Output('br206-output-alert2-scrap', 'children'),
    [Input('br206-make-changes-button2-scrap', 'n_clicks')],
    [State('br206-scrap-rate-input-alert2', 'value'),
     State('br206-scrap-connection-interval-dropdown-alert2', 'value'),
     State('br206-employee-category-dropdown-alert2', 'value')]
)
def br206_handle_alert2_scrap_changes(n_clicks, scrap_rate, scrap_interval, scrap_receivers):
    if n_clicks > 0:
        print("Scrap Rate:", scrap_rate)
        print("Scrap Interval:", scrap_interval)
        print("Scrap Receivers:", scrap_receivers)
        return "Changes saved successfully!"


##############################     Alert 3 Settings #####################################


# Callback to handle changes in Alert 3 Downtime settings br206
@app.callback(
    Output('br206-output-alert3-downtime', 'children'),
    [Input('br206-make-changes-button3-downtime', 'n_clicks')],
    [State('br206-bde-connection-interval-dropdown-alert3', 'value'),
     State('br206-downtime-employee-category-dropdown3', 'value')]
)
def br206_handle_alert3_downtime_changes(n_clicks, bde_interval, downtime_receivers):
    if n_clicks > 0:
        print("BDE Interval:", bde_interval)
        print("Downtime Receivers:", downtime_receivers)
        # Return a message or confirmation
        return "Changes saved successfully!"

# Callback to handle changes in Alert 3 Scrap settings br206
@app.callback(
    Output('br206-output-alert3-scrap', 'children'),
    [Input('br206-make-changes-button3-scrap', 'n_clicks')],
    [State('br206-scrap-rate-input-alert3', 'value'),
     State('br206-scrap-connection-interval-dropdown-alert3', 'value'),
     State('br206-employee-category-dropdown-alert3', 'value')]
)
def br206_handle_alert3_scrap_changes(n_clicks, scrap_rate, scrap_interval, scrap_receivers):
    if n_clicks > 0:
        print("Scrap Rate:", scrap_rate)
        print("Scrap Interval:", scrap_interval)
        print("Scrap Receivers:", scrap_receivers)
        return "Changes saved successfully!"

So from the UI to this backend code I need when I click the buttons “Make Changes” that the app reads the backend tied to the buttons and executes the new queries and starts sending the emails when something from the inputs happen, but I don’t know how to do it alone.

For an example:
When the downtime for br206 is 15 minutes it will send to alert 1 Users, but if the downtime is 30 minues (from Alert 2) it will then send to the Alert 2 emails, also if downtime is still happening after 120 minutes it will send the email to the Alert 3 emails.

Now for my semail sending code I have this:

def send_email(subject, message, to):
    try:
        outlook = win32com.client.Dispatch('outlook.application')
        mail = outlook.CreateItem(0)

        # Email configuration
        email = 'test@local'
        mail.To = ', '.join(to)
        mail.Subject = subject
        mail.BCC = "test-mail"

        # HTML body
        mail.HTMLBody = message

        mail.Send()
    except Exception as e:
        print(f"Error sending email: {str(e)}")

And this is a function that just test the connection every 30 mins and sends an email to the user.

def fetch_the_downtime():
    # Define the end time (current time)
    end_time = datetime.datetime.now()
    # Define the start time (30 minutes ago)
    start_time = end_time - datetime.timedelta(minutes=30)

    # Define the time range strings
    start_time_str = start_time.strftime('%Y-%m-%d %H:%M:%S')
    end_time_str = end_time.strftime('%Y-%m-%d %H:%M:%S')

    # Define the database configurations
    databases = [
        {"name": "ked33", "server": "ip ", "database": "ked33", "uid": "bde", "pwd": "pass"},
        {"name": "br206", "server": "ip ", "database": "br206", "uid": "bde", "pwd": "pass"},
        {"name": "GSL-1 BAT", "server": "ip ", "database": "GSL-1 BAT", "uid": "BAT", "pwd": "pass"},
        {"name": "GSL-2 SUSO", "server": "ip ", "database": "GSL-2 SUSO", "uid": "BAT", "pwd": "pass"}
    ]

    # Iterate over each database configuration
    for db_info in databases:
        conn = None  # Initialize connection variable
        try:
            # Establish a connection to the database
            conn = pyodbc.connect(
                f"DRIVER={{ODBC Driver 18 for SQL Server}};"
                f"SERVER={db_info['server']};"
                f"DATABASE={db_info['database']};"
                f"UID={db_info['uid']};"
                f"PWD={db_info['pwd']};"
                "TrustServerCertificate=yes"
            )

            # Define the SQL query to fetch downtime occurrences
            query = f'''
                SELECT COUNT(*) AS total_downtime
                FROM [{db_info['database']}].[dbo].[NotificationLog]
                WHERE [Timestamp] BETWEEN '{start_time_str}' AND '{end_time_str}' AND [Duration] > 3600
            '''

            # Execute the query
            cursor = conn.cursor()
            cursor.execute(query)

            # Fetch the result
            result = cursor.fetchone()

            # Check if result is not None and if there are any downtime occurrences
            if result and result[0] > 0:
                # Send email notification
                subject = f"{db_info['name']} downtime detected"
                message = f"{result[0]} downtime occurrence(s) with duration over 3600 seconds detected in the last 30 minutes."
                send_email(subject, message, [test@mail.com'])
            print(f"Downtime check for {db_info['name']} completed at {end_time}")

        except Exception as e:
            # Handle any exceptions
            print(f"Error for {db_info['name']}: {str(e)}")

        finally:
            # Close the database connection
            if conn:
                conn.close()
schedule.every(30).minutes.do(fetch_the_downtime)

Also I made for bde downtime

def generate_bde_connection_interval_options():
    return [
        {'label': '15 min', 'value': 15},
        {'label': '30 min', 'value': 30},
        {'label': '60 min', 'value': 60},
        {'label': '120 min', 'value': 120}
    ]

but for scrap I did:

                        dcc.Dropdown(
                            id='br206-scrap-rate-input-alert1',
                            options=[{'label': f'{i}%', 'value': f'{i}%'} for i in range(1, 13)],
                            value='3%',
                            className='input-field',
                            style={'width': '150px', 'margin-left': '14px'}
                        ),

should I do it like I did for downtime? so the query will pick up the setting?
Now what I need help is making this
When the downtime for br206 is 15 minutes it will send to alert 1 Users, but if the downtime is 30 minues (from Alert 2) it will then send to the Alert 2 emails, also if downtime is still happening after 120 minutes it will send the email to the Alert 3 emails.

So I need queries like that one that will take the user inputs, save them, check them all the time in the background and if thet alert setting happens then it will send the email to the ones that the user picked up from the api options.
Also that it is all connected to the ui trough the callbacks that I made.

I think I just need one Alert setting to work, then the rest would be the same just different ID’s.
Also I made for bde downtime

Thank you.