ishaq101 commited on
Commit
7539d81
Β·
1 Parent(s): e304f26

Add admin-only dataset management panel via ?admin query param

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +83 -37
src/streamlit_app.py CHANGED
@@ -69,6 +69,18 @@ st.markdown("""
69
  font-size: 0.78rem; margin-top: 4px;
70
  }
71
 
 
 
 
 
 
 
 
 
 
 
 
 
72
  /* ── Sidebar ──────────────────────────────────────────────────────────────── */
73
  [data-testid="stSidebar"] { background: #f8f9fa; }
74
  [data-testid="stSidebar"] .stButton > button {
@@ -83,6 +95,15 @@ st.markdown("""
83
  """, unsafe_allow_html=True)
84
 
85
 
 
 
 
 
 
 
 
 
 
86
  def _section(icon, title, subtitle=""):
87
  sub_html = f'<div class="sub">{subtitle}</div>' if subtitle else ""
88
  st.markdown(
@@ -373,47 +394,60 @@ with st.sidebar:
373
  unsafe_allow_html=True,
374
  )
375
 
376
- # ── Dataset upload ─────────────────────────────────────────────────────────
377
- df = None
378
- data_source = "bad_actor_simulation.xlsx (default)"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
379
 
380
- with st.expander("Dataset", expanded=False):
381
- uploaded = st.file_uploader(
382
- "Upload CSV or XLSX (leave empty to use default file)",
383
- type=["csv", "xlsx"],
384
- )
385
 
386
- if uploaded is not None:
387
- raw = uploaded.read()
388
- ext = uploaded.name.rsplit(".", 1)[-1].lower()
 
 
 
 
 
389
 
390
- if ext == "csv":
391
- df = load_csv(raw)
392
- data_source = uploaded.name
393
- missing = REQUIRED_COLS - set(df.columns)
394
  if missing:
395
- st.error(f"CSV missing columns: {', '.join(sorted(missing))}")
396
- df = None
397
-
398
- elif ext == "xlsx":
399
- sheets = get_xlsx_sheets(raw)
400
- if len(sheets) == 1:
401
- sheet = sheets[0]
402
  else:
403
- sheet = st.selectbox("Sheet name", sheets)
404
- df = load_xlsx(raw, sheet)
405
- data_source = f"{uploaded.name} [sheet: {sheet}]"
406
- missing = REQUIRED_COLS - set(df.columns)
407
- if missing:
408
- st.error(f"XLSX missing columns: {', '.join(sorted(missing))}")
409
- df = None
410
 
411
- if df is None:
412
- df = load_default()
413
 
414
- st.caption(f"Loaded: {data_source} | {len(df):,} rows")
415
-
416
- st.divider()
417
 
418
  # ── Filters ────────────────────────────────────────────────────────────────
419
  with st.expander("πŸŽ›οΈ Filters", expanded=True):
@@ -450,6 +484,19 @@ with st.sidebar:
450
  st.caption("Adjust filters above, then click Run.")
451
 
452
  # ── Main area ──────────────────────────────────────────────────────────────────
 
 
 
 
 
 
 
 
 
 
 
 
 
453
  hcol1, hcol2 = st.columns([3, 1])
454
  with hcol1:
455
  st.markdown(
@@ -483,9 +530,8 @@ else:
483
  <h2>Welcome to the Simulation Console</h2>
484
  <p>Identify equipment that consistently underperforms relative to its peers.</p>
485
  <ul class="step-list">
486
- <li><span class="step-num">1</span> Open <strong>Dataset</strong> in the sidebar to upload a CSV or XLSX file, or use the default.</li>
487
- <li><span class="step-num">2</span> Expand <strong>Filters</strong> to set year, site, section, model, and observation month.</li>
488
- <li><span class="step-num">3</span> Click <strong>Run Simulation</strong> to compute scores and flag bad actors.</li>
489
  </ul>
490
  </div>
491
  """, unsafe_allow_html=True)
 
69
  font-size: 0.78rem; margin-top: 4px;
70
  }
71
 
72
+ /* ── Admin banner ─────────────────────────────────────────────────────────── */
73
+ .admin-banner {
74
+ background: linear-gradient(135deg, #212529 0%, #343a40 100%);
75
+ border-radius: 10px;
76
+ padding: 16px 20px;
77
+ color: white;
78
+ margin-bottom: 20px;
79
+ border-left: 4px solid #ffc107;
80
+ }
81
+ .admin-banner h3 { margin: 0 0 4px 0; font-size: 1rem; color: #ffc107; }
82
+ .admin-banner p { margin: 0; font-size: 0.82rem; color: rgba(255,255,255,0.75); }
83
+
84
  /* ── Sidebar ──────────────────────────────────────────────────────────────── */
85
  [data-testid="stSidebar"] { background: #f8f9fa; }
86
  [data-testid="stSidebar"] .stButton > button {
 
95
  """, unsafe_allow_html=True)
96
 
97
 
98
+ # ── Admin mode detection ───────────────────────────────────────────────────────
99
+ is_admin = "admin" in st.query_params
100
+
101
+ # ── Session state init ─────────────────────────────────────────────────────────
102
+ if "custom_df" not in st.session_state:
103
+ st.session_state.custom_df = None
104
+ st.session_state.custom_source = None
105
+
106
+
107
  def _section(icon, title, subtitle=""):
108
  sub_html = f'<div class="sub">{subtitle}</div>' if subtitle else ""
109
  st.markdown(
 
394
  unsafe_allow_html=True,
395
  )
396
 
397
+ # ── Dataset management (admin only) ───────────────────────────────────────
398
+ if is_admin:
399
+ with st.expander("πŸ—‚οΈ Dataset", expanded=True):
400
+ # Status aktif saat ini
401
+ if st.session_state.custom_df is not None:
402
+ st.success(
403
+ f"**Custom dataset aktif**\n\n"
404
+ f"{st.session_state.custom_source}\n\n"
405
+ f"{len(st.session_state.custom_df):,} rows"
406
+ )
407
+ if st.button("πŸ—‘οΈ Hapus & kembali ke default", use_container_width=True):
408
+ st.session_state.custom_df = None
409
+ st.session_state.custom_source = None
410
+ st.rerun()
411
+ else:
412
+ st.info("Menggunakan **default** dataset\n\n`bad_actor_simulation.xlsx`")
413
+
414
+ st.markdown("---")
415
+ st.caption("Upload file untuk mengganti dataset aktif:")
416
+ uploaded = st.file_uploader(
417
+ "CSV atau XLSX",
418
+ type=["csv", "xlsx"],
419
+ label_visibility="collapsed",
420
+ )
421
 
422
+ if uploaded is not None:
423
+ raw = uploaded.read()
424
+ ext = uploaded.name.rsplit(".", 1)[-1].lower()
 
 
425
 
426
+ if ext == "csv":
427
+ candidate_df = load_csv(raw)
428
+ candidate_source = uploaded.name
429
+ else:
430
+ sheets = get_xlsx_sheets(raw)
431
+ sheet = sheets[0] if len(sheets) == 1 else st.selectbox("Pilih sheet", sheets)
432
+ candidate_df = load_xlsx(raw, sheet)
433
+ candidate_source = f"{uploaded.name} [sheet: {sheet}]"
434
 
435
+ missing = REQUIRED_COLS - set(candidate_df.columns)
 
 
 
436
  if missing:
437
+ st.error(f"Kolom tidak ditemukan: {', '.join(sorted(missing))}")
 
 
 
 
 
 
438
  else:
439
+ st.caption(f"Preview β€” {len(candidate_df):,} rows, {len(candidate_df.columns)} cols")
440
+ st.dataframe(candidate_df.head(3), use_container_width=True)
441
+ if st.button("βœ… Terapkan dataset ini", use_container_width=True):
442
+ st.session_state.custom_df = candidate_df
443
+ st.session_state.custom_source = candidate_source
444
+ st.rerun()
 
445
 
446
+ st.divider()
 
447
 
448
+ # ── Determine active dataframe ─────────────────────────────────────────────
449
+ df = st.session_state.custom_df if st.session_state.custom_df is not None else load_default()
450
+ data_source = st.session_state.custom_source or "bad_actor_simulation.xlsx (default)"
451
 
452
  # ── Filters ────────────────────────────────────────────────────────────────
453
  with st.expander("πŸŽ›οΈ Filters", expanded=True):
 
484
  st.caption("Adjust filters above, then click Run.")
485
 
486
  # ── Main area ──────────────────────────────────────────────────────────────────
487
+
488
+ # Admin banner (hanya muncul di mode admin)
489
+ if is_admin:
490
+ st.markdown(
491
+ '<div class="admin-banner">'
492
+ '<h3>βš™οΈ Admin Mode</h3>'
493
+ '<p>Dataset management aktif. Gunakan panel <strong>Dataset</strong> di sidebar '
494
+ 'untuk upload, replace, atau hapus dataset. '
495
+ 'File default (<code>bad_actor_simulation.xlsx</code>) tidak akan pernah diubah.</p>'
496
+ '</div>',
497
+ unsafe_allow_html=True,
498
+ )
499
+
500
  hcol1, hcol2 = st.columns([3, 1])
501
  with hcol1:
502
  st.markdown(
 
530
  <h2>Welcome to the Simulation Console</h2>
531
  <p>Identify equipment that consistently underperforms relative to its peers.</p>
532
  <ul class="step-list">
533
+ <li><span class="step-num">1</span> Expand <strong>Filters</strong> to set year, site, section, model, and observation month.</li>
534
+ <li><span class="step-num">2</span> Click <strong>Run Simulation</strong> to compute scores and flag bad actors.</li>
 
535
  </ul>
536
  </div>
537
  """, unsafe_allow_html=True)