[Python] Plotly dashboard logout after refreshing page

Discussão em 'Python' iniciado por Stack, Maio 26, 2025.

  1. Stack

    Stack Membro Participativo

    I'm trying to make a Dashboard with Login, Register and Dashboard with Navbar and Tabs. With below code everything working well but I have a problem that if I refreshed page, Dashboard will return to login screen.

    from dash import Dash, dcc, html, Input, Output, State, ctx
    import dash_mantine_components as dmc
    import pandas as pd
    import dash
    from dash_ag_grid import AgGrid
    import plotly.express as px
    import os
    from datetime import datetime
    from pandas.tseries.offsets import BDay
    from datetime import datetime, date, timedelta
    import base64
    import io
    import plotly.graph_objects as go
    import dash_ag_grid as dag
    import numpy as np

    excel_file = 'user2.xlsx'

    # Tạo file nếu chưa có
    if not os.path.exists(excel_file):
    pd.DataFrame(columns=["username", "password", "email", "code", "branch", "created_at"]).to_excel(excel_file, index=False)

    app = dash.Dash(__name__, suppress_callback_exceptions=True)

    def t(lang, key):
    translations = {
    "vi": {
    "login": "Đăng nhập",
    "register": "Đăng ký",
    "username": "Tên đăng nhập",
    "password": "Mật khẩu",
    "confirm": "Nhập lại mật khẩu",
    "email": "Email",
    "code": "Mã nhân viên",
    "branch": "Chi nhánh",
    "no_account": "Chưa có tài khoản?",
    "register_now": "Đăng ký ngay",
    "has_account": "Đã có tài khoản?",
    "create_account": "Tạo tài khoản",
    "success": "Đăng ký thành công. Vui lòng đăng nhập."
    },
    "en": {
    "login": "Login",
    "register": "Register",
    "username": "Username",
    "password": "Password",
    "confirm": "Confirm Password",
    "email": "Email",
    "code": "Employee Code",
    "branch": "Branch",
    "no_account": "No account?",
    "register_now": "Register now",
    "has_account": "Already have an account?",
    "create_account": "Create Account",
    "success": "Successfully registered. Please login."
    },
    "ko": {
    "login": "로그인",
    "register": "회원가입",
    "username": "사용자 이름",
    "password": "비밀번호",
    "confirm": "비밀번호 확인",
    "email": "이메일",
    "code": "사원 번호",
    "branch": "지점",
    "no_account": "계정이 없으신가요?",
    "register_now": "지금 등록하세요",
    "has_account": "이미 계정이 있으신가요?",
    "create_account": "계정 만들기",
    "success": "성공적으로 등록되었습니다. 로그인하세요."
    }
    }
    return translations.get(lang, translations["vi"]).get(key, key)

    def login_screen(lang):
    return dmc.Container([
    dmc.Select(
    id="language-select",
    label="Ngôn ngữ / Language / 언어",
    data=[
    {"label": "Tiếng Việt", "value": "vi"},
    {"label": "English", "value": "en"},
    {"label": "한국어", "value": "ko"}
    ],
    value=lang,
    style={"marginBottom": 20}
    ),
    dmc.Title(t(lang, "login"), order=2, align="center", mb=20),
    dmc.TextInput(id="login-username", label=t(lang, "username")),
    dmc.PasswordInput(id="login-password", label=t(lang, "password"), mt=10),
    dmc.Button(t(lang, "login"), id="login-submit", mt=20),
    dmc.Text(id="alert-text", color="red", size="sm", mt=10),
    dmc.Text(t(lang, "no_account"), span=True),
    dmc.Button(t(lang, "register_now"), id="go-register", variant="subtle", compact=True)
    ], style={"maxWidth": 400, "margin": "auto", "paddingTop": 100})

    def register_screen(lang):
    return dmc.Container([
    dmc.Title(t(lang, "register"), order=2, align="center", mb=20),
    dmc.TextInput(id="register-username", label=t(lang, "username")),
    dmc.PasswordInput(id="register-password", label=t(lang, "password"), mt=10),
    dmc.PasswordInput(id="register-confirm", label=t(lang, "confirm"), mt=10),
    dmc.TextInput(id="register-email", label=t(lang, "email"), mt=10),
    dmc.TextInput(id="register-code", label=t(lang, "code"), mt=10),
    dmc.TextInput(id="register-branch", label=t(lang, "branch"), mt=10),
    dmc.Button(t(lang, "create_account"), id="register-submit", fullWidth=True, mt=20),
    dmc.Text(id="alert-text", color="red", size="sm", mt=10),
    dmc.Text(t(lang, "has_account"), span=True),
    dmc.Button(t(lang, "login"), id="go-login", variant="subtle", compact=True)
    ], style={"maxWidth": 400, "margin": "auto", "paddingTop": 100})


    def full_dashboard_layout(lang):
    return dmc.Container([
    dmc.Group([
    dmc.Switch(id="theme-toggle", label="Dark mode", size="md", offLabel="☀️", onLabel=""),
    dmc.Button("Signout", id="sign-out", variant="light", color="red", size="sm")
    ], position="apart", mb=20),
    html.Div([
    html.Div(id="sidebar", children=[
    dmc.Stack([
    dmc.NavLink(label="Nav 1", id="nav-1-btn"),
    dmc.NavLink(label="Nav 2", id="nav-2-btn"),
    dmc.NavLink(label="Nav 3", id="nav-3-btn"),
    ])
    ], style={
    "width": "200px",
    "minHeight": "100vh",
    "borderRight": "1px solid #eee",
    "padding": "10px"
    }),
    html.Div(id="content-area", style={"flex": 1, "padding": "20px"})
    ], style={"display": "flex"})
    ], fluid = True)

    app.layout = html.Div([
    dcc.Location(id="url", refresh=False),
    dcc.Store(id="auth-status", data="login", storage_type="local"),
    dcc.Store(id="selected-lang", data="vi"),
    dcc.Store(id="alert-store", data={}),
    dcc.Store(id="theme-store", data="light"),
    dmc.MantineProvider(
    id="theme-provider",
    theme={"colorScheme": "light"},
    withGlobalStyles=True,
    withNormalizeCSS=True,
    children=html.Div(id="app-screen")
    ),
    dmc.Text(id="alert-text", color="red", size="sm", mt=10)
    ])


    @app.callback(
    Output("auth-status", "data", allow_duplicate=True),
    Input("go-login", "n_clicks"), prevent_initial_call=True, prevent_initial_callbacks=True)
    def back_to_login(go_login): return "login"

    @app.callback(
    Output("auth-status", "data", allow_duplicate=True),
    Output("alert-store", "data", allow_duplicate=True),
    Output("url", "pathname", allow_duplicate=True), # điều hướng sau login
    Input("go-register", "n_clicks"),
    Input("login-submit", "n_clicks"),
    State("login-username", "value"),
    State("login-password", "value"),
    prevent_initial_call=True
    )
    def login_flow(go_reg, login, username, password):
    if ctx.triggered_id == "go-register":
    return "register", {}, dash.no_update

    if not username or not password:
    return dash.no_update, {}, dash.no_update

    path = "users2.xlsx"
    try:
    df = pd.read_excel(path)
    if "username" not in df.columns or "password" not in df.columns:
    return dash.no_update, {"message": "Invalid data"}, dash.no_update
    except:
    return dash.no_update, {"message": "Cannot read users"}, dash.no_update

    match = df[(df["username"] == username) & (df["password"] == password)]
    if match.empty:
    return dash.no_update, {"message": "Sai tên đăng nhập hoặc mật khẩu"}, dash.no_update

    return "authenticated", {}, "/dashboard"

    @app.callback(
    Output("auth-status", "data", allow_duplicate=True),
    Output("alert-store", "data", allow_duplicate=True),
    Input("register-submit", "n_clicks"),
    State("register-username", "value"),
    State("register-password", "value"),
    State("register-confirm", "value"),
    State("register-email", "value"),
    State("register-code", "value"),
    State("register-branch", "value"),
    prevent_initial_call=True
    )
    def handle_register(_, username, password, confirm, email, code, branch):
    if not any([username, password, confirm, email, code, branch]):
    return dash.no_update, {}
    if not all([username, password, confirm, email, code, branch]):
    return dash.no_update, {"message": "Vui lòng nhập đủ thông tin"}
    if password != confirm:
    return dash.no_update, {"message": "Mật khẩu xác nhận không khớp"}
    if not (email.endswith("@wooribank.com") or email.endswith("@woori.com.vn")):
    return dash.no_update, {"message": "Email không hợp lệ"}

    path = "users2.xlsx"
    try:
    df = pd.read_excel(path)
    except:
    df = pd.DataFrame(columns=["username", "password", "email", "code", "branch", "created_at"])

    if any(df["username"] == username) or any(df["email"] == email) or any(df["code"] == code):
    return dash.no_update, {"message": "Tài khoản/email/mã đã tồn tại"}

    df = pd.concat([df, pd.DataFrame([{
    "username": username,
    "password": password,
    "email": email,
    "code": code,
    "branch": branch,
    "created_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    }])], ignore_index=True)
    df.to_excel(path, index=False)
    return "register", {"message": "Đăng ký thành công"}

    @app.callback(Output("alert-text", "children"),
    Input("alert-store", "data"))
    def show_alert(data):
    return data.get("message", "") if isinstance(data, dict) else ""

    @app.callback(Output("alert-text", "color"),
    Input("auth-status", "data"),
    Input("alert-store", "data"))
    def alert_color(status, data):
    if status == "register" and data.get("message") == "Đăng ký thành công":
    return "green"
    return "red"

    @app.callback(
    Output("app-screen", "children"),
    Input("auth-status", "data"),
    Input("url", "pathname"), # đọc URL khi load lại
    State("selected-lang", "data")
    )
    def render_app(status, pathname, lang):
    print(" render_app | Status:", status, "| Path:", pathname)

    if status == "authenticated" and pathname == "/dashboard":
    return full_dashboard_layout(lang)
    if status == "register":
    return register_screen(lang)
    return login_screen(lang)

    @app.callback(Output("theme-store", "data"),
    Input("theme-toggle", "checked"), prevent_initial_call=True)
    def toggle_theme(checked): return "dark" if checked else "light"

    @app.callback(Output("theme-provider", "theme"),
    Input("theme-store", "data"))
    def update_theme(scheme): return {"colorScheme": scheme}

    @app.callback(Output("selected-lang", "data"),
    Input("language-select", "value"))
    def set_language(value): return value

    @app.callback(
    Output("content-area", "children"),
    Input("nav-1-btn", "n_clicks"),
    Input("nav-2-btn", "n_clicks"),
    Input("nav-3-btn", "n_clicks"),
    State("theme-store", "data"),
    State("selected-lang", "data")
    )
    def update_content(n1, n2, n3, theme, lang):
    nav = ctx.triggered_id
    if nav: return generate_tabs(nav.replace("-btn", ""), theme, lang)
    return dmc.Text(t(lang, "choose_menu"))

    @app.callback(
    Output("auth-status", "data", allow_duplicate=True),
    Input("sign-out", "n_clicks"),
    prevent_initial_call=True,
    prevent_initial_callbacks=True
    )
    def logout(n):
    return "login"

    def generate_tabs(nav_id, theme, lang):
    if nav_id == "nav-1":
    return dmc.Tabs(
    id="nav-1-tabs",
    value="tab-1",
    children=[
    dmc.TabsList([
    dmc.Tab(t(lang, "tab1"), value="tab-1"),
    dmc.Tab(t(lang, "tab2"), value="tab-2"),
    dmc.Tab(t(lang, "tab3"), value="tab-3"),
    ]),
    dmc.TabsPanel(id="nav-1-tab-content", value="tab-1"),
    dmc.TabsPanel(dmc.Text(t(lang, "tab2_content")), value="tab-2"),
    dmc.TabsPanel(dmc.Text(t(lang, "tab3_content")), value="tab-3"),
    ]
    )

    elif nav_id == "nav-2":
    return dmc.Tabs(
    id="nav-2-tabs",
    value="tab-a",
    children=[
    dmc.TabsList([
    dmc.Tab("Bộ lọc", value="tab-a"),
    dmc.Tab("Lịch", value="tab-b"),
    ]),
    dmc.TabsPanel([
    dmc.Select(label="Chi nhánh", data=["Hà Nội", "HCM"]),
    dmc.DatePicker()
    ], value="tab-a"),
    dmc.TabsPanel(dmc.Text("Lịch hoạt động ở đây..."), value="tab-b"),
    ]
    )

    elif nav_id == "nav-3":
    return dmc.Tabs(
    id="nav-3-tabs",
    value="tab-x",
    children=[
    dmc.TabsList([
    dmc.Tab("Thông báo", value="tab-x"),
    dmc.Tab("Người dùng", value="tab-y"),
    dmc.Tab("Vai trò", value="tab-z"),
    dmc.Tab("Báo cáo", value="tab-w")
    ]),
    dmc.TabsPanel(dmc.Text("Thông báo hệ thống"), value="tab-x"),
    dmc.TabsPanel(dmc.Text("Danh sách người dùng"), value="tab-y"),
    dmc.TabsPanel(dmc.Text("Quản lý vai trò"), value="tab-z"),
    dmc.TabsPanel(dmc.Text("Báo cáo tổng hợp"), value="tab-w"),
    ]
    )

    return dmc.Text("Không có nội dung.")


    def blank_fig():
    fig = go.Figure(go.Bar(x=[], y=[]))
    fig.update_layout({'paper_bgcolor':'rgba(0,0,0,0)','plot_bgcolor':'rgba(0,0,0,0)'},
    margin = dict(l=10,r=10,t=10, b=10),font_color='white')
    fig.update_xaxes(showline=False,showgrid=False,exponentformat="none",separatethousands=True)
    fig.update_yaxes(showline=False,showgrid=False,exponentformat="none",separatethousands=True)
    return fig

    def blank_fig2():
    fig2 = go.Figure(go.Bar(x=[], y=[]))
    fig2.update_layout({'paper_bgcolor':'rgba(0,0,0,0)','plot_bgcolor':'rgba(0,0,0,0)'},
    margin = dict(l=10,r=10,t=10, b=10),font_color='white', height=200)
    fig2.update_xaxes(showline=False,showgrid=False,exponentformat="none",separatethousands=True)
    fig2.update_yaxes(showline=False,showgrid=False,exponentformat="none",separatethousands=True)
    return fig2

    def get_dates(start_date, num_days=365):
    return [(start_date + timedelta(days=i)).isoformat()
    for i in range(num_days)
    if (start_date + timedelta(days=i)).weekday()==4 or (start_date + timedelta(days=i)).weekday()==5]

    @app.callback(
    Output("nav-1-tab-content", "children"),
    Input("nav-1-tabs", "value"),
    Input("theme-store", "data"),
    State("selected-lang", "data")
    )
    def nav1_tabs(tab, theme, lang):
    theme == "dark"
    if tab == "tab-1":
    return dmc.LoadingOverlay([
    dmc.Container([
    dmc.Grid([
    dmc.Col([
    dmc.DatePicker(id='datepickersingle_1',
    value=[],
    minDate=date(2023,12,29),
    disabledDates = get_dates(date.today()),
    style={'width': 200})
    ], span='auto'),
    dmc.Col([
    dmc.DatePicker(id='datepickersingle_2',
    value=[],
    minDate=date(2023,12,29),
    disabledDates = get_dates(date.today()),
    style={'width': 200})
    ], span='auto'),
    dmc.Col([
    dmc.DatePicker(id='datepickersingle_3',
    value=[],
    minDate=date(2023,12,29),
    disabledDates = get_dates(date.today()),
    style={'width': 200})
    ], span='auto'),
    dmc.Col([
    dmc.DatePicker(id='datepickersingle_4',
    value=(datetime.today() - BDay(1)).date(),
    minDate=date(2023,12,29),
    disabledDates = get_dates(date.today()),
    style={'width': 200})
    ], span='auto'),
    dmc.Col([
    dmc.NumberInput(id='input_1',
    label='KPI Exchange Rate',
    value=[],
    precision=4,
    style={'width': 200})
    ], span=2),
    dmc.Col([
    dmc.Select(label = "Select branch",
    id='dropdown_1',
    data = ['001+','100','101','102','103','200','201','202','300','301',
    '302','303','304','500','700','701','702','703', '203'],
    placeholder = 'Please select branch',
    value = '001+', clearable=True)
    ], span=2),
    dmc.Col([
    dmc.Button('Submit', color='gray', id='btn_1')
    ], span='auto', offset=1)
    ], align='flex-start', justify = 'center', style={'margin-top':10}),
    ], fluid=True)
    ], loaderProps={'variant':"dot", 'color':'blue', 'fullscreen':True})

    return dash.no_update


    if __name__ == "__main__":
    app.run_server(debug=False, port = 1522, jupyter_mode="external")


    I'm guessing that login and Dashboard has same link and not be separated with href so this situation but I'm not sure how to fix it.

    Continue reading...

Compartilhe esta Página