Spaces:
Sleeping
Sleeping
Commit
·
f19a974
1
Parent(s):
4253e50
pesan update, misal: memperbaiki bug di halaman laporan
Browse files- .gitignore +7 -15
- revisi_skripsi +1 -0
- tempCodeRunnerFile.py +253 -0
.gitignore
CHANGED
@@ -1,23 +1,15 @@
|
|
1 |
-
# Folder Virtual Environment
|
2 |
-
# Isinya bisa ratusan MB dan spesifik untuk komputermu saja.
|
3 |
.venv/
|
|
|
|
|
4 |
|
5 |
-
#
|
6 |
__pycache__/
|
7 |
*.pyc
|
8 |
|
9 |
-
# File
|
10 |
-
# JANGAN PERNAH upload file ini. Isinya bisa password database, API key, dll.
|
11 |
.env
|
12 |
-
|
13 |
-
# Pengaturan spesifik VS Code (Tidak dibutuhkan di server)
|
14 |
.vscode/
|
15 |
|
16 |
-
# File
|
17 |
-
|
18 |
-
|
19 |
-
# Folder file sementara (jika ada)
|
20 |
-
temp_files/
|
21 |
-
|
22 |
-
# Submodul/folder sisa dari error sebelumnya
|
23 |
-
revisi_skripsi/
|
|
|
1 |
+
# Folder Virtual Environment
|
|
|
2 |
.venv/
|
3 |
+
venv/
|
4 |
+
env/
|
5 |
|
6 |
+
# Python cache
|
7 |
__pycache__/
|
8 |
*.pyc
|
9 |
|
10 |
+
# File konfigurasi lokal (sering berisi data rahasia)
|
|
|
11 |
.env
|
|
|
|
|
12 |
.vscode/
|
13 |
|
14 |
+
# File sistem operasi
|
15 |
+
.DS_Store
|
|
|
|
|
|
|
|
|
|
|
|
revisi_skripsi
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
Subproject commit 4f9a9e31d91ffe69d300621d9852937447d3e59d
|
tempCodeRunnerFile.py
ADDED
@@ -0,0 +1,253 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# app.py (Versi Revisi Siap Deploy)
|
2 |
+
|
3 |
+
import dash
|
4 |
+
import dash_bootstrap_components as dbc
|
5 |
+
from dash import Dash, dcc, html, Output, Input, State, no_update
|
6 |
+
|
7 |
+
# Impor dari proyek Anda
|
8 |
+
from pages import (
|
9 |
+
beranda,
|
10 |
+
analisis_tren_penyakit,
|
11 |
+
distribusi_kasus_demografi,
|
12 |
+
input_data,
|
13 |
+
laporan,
|
14 |
+
pengaturan
|
15 |
+
)
|
16 |
+
from auth import login, signup
|
17 |
+
from components import sidebar
|
18 |
+
|
19 |
+
# -----------------------------------------------------------------------------
|
20 |
+
# BAGIAN 1: INISIALISASI APLIKASI (PERUBAHAN UTAMA DI SINI)
|
21 |
+
# -----------------------------------------------------------------------------
|
22 |
+
# Tidak perlu mengimpor Flask atau menginisialisasi server Flask secara manual lagi.
|
23 |
+
# Dash akan melakukannya secara otomatis.
|
24 |
+
|
25 |
+
# Konfigurasi Tema
|
26 |
+
THEME_LIGHT = dbc.themes.FLATLY
|
27 |
+
THEME_DARK = dbc.themes.DARKLY
|
28 |
+
|
29 |
+
# Inisialisasi aplikasi Dash
|
30 |
+
app = Dash(
|
31 |
+
__name__,
|
32 |
+
suppress_callback_exceptions=True,
|
33 |
+
external_stylesheets=[THEME_LIGHT, '/assets/style.css'],
|
34 |
+
meta_tags=[{"name": "viewport", "content": "width=device-width, initial-scale=1"}]
|
35 |
+
)
|
36 |
+
|
37 |
+
# Baris ini SANGAT PENTING untuk deployment.
|
38 |
+
# Gunicorn akan mencari variabel bernama 'server'.
|
39 |
+
server = app.server
|
40 |
+
|
41 |
+
|
42 |
+
# -----------------------------------------------------------------------------
|
43 |
+
# BAGIAN 2: LAYOUT UTAMA APLIKASI (TIDAK ADA PERUBAHAN)
|
44 |
+
# -----------------------------------------------------------------------------
|
45 |
+
app.layout = html.Div([
|
46 |
+
dcc.Location(id='url', refresh=False),
|
47 |
+
dcc.Store(id='login-status', storage_type='session'),
|
48 |
+
dcc.Store(id='user-theme-preference-store', storage_type='local'),
|
49 |
+
dcc.Store(id='previous-url-store', storage_type='session'),
|
50 |
+
html.Div(id='app-wrapper'),
|
51 |
+
dbc.Modal(
|
52 |
+
[
|
53 |
+
dbc.ModalHeader(dbc.ModalTitle("Konfirmasi Logout")),
|
54 |
+
dbc.ModalBody("Apakah Anda yakin ingin keluar dari sesi ini?"),
|
55 |
+
dbc.ModalFooter([
|
56 |
+
dbc.Button("Tidak", id="logout-confirm-no", color="secondary", className="ms-auto", n_clicks=0),
|
57 |
+
dbc.Button("Ya, Logout", id="logout-confirm-yes", color="danger", className="ms-2", n_clicks=0),
|
58 |
+
]),
|
59 |
+
],
|
60 |
+
id="logout-confirm-modal",
|
61 |
+
is_open=False,
|
62 |
+
centered=True,
|
63 |
+
),
|
64 |
+
dcc.Location(id='logout-redirect-location', refresh=True)
|
65 |
+
])
|
66 |
+
|
67 |
+
# -----------------------------------------------------------------------------
|
68 |
+
# BAGIAN 3: CALLBACKS (TIDAK ADA PERUBAHAN)
|
69 |
+
# -----------------------------------------------------------------------------
|
70 |
+
|
71 |
+
# --- Callbacks untuk Tema (JavaScript Inline) ---
|
72 |
+
app.clientside_callback(
|
73 |
+
"""
|
74 |
+
function(pathname, storedThemePreference) {
|
75 |
+
let themeToApply = 'LIGHT';
|
76 |
+
if (storedThemePreference && typeof storedThemePreference.theme === 'string') {
|
77 |
+
themeToApply = storedThemePreference.theme;
|
78 |
+
}
|
79 |
+
const lightThemeUrl = "https://cdn.jsdelivr.net/npm/[email protected]/dist/flatly/bootstrap.min.css";
|
80 |
+
const darkThemeUrl = "https://cdn.jsdelivr.net/npm/[email protected]/dist/darkly/bootstrap.min.css";
|
81 |
+
let newThemeUrl = lightThemeUrl;
|
82 |
+
document.body.classList.remove('theme-dark', 'theme-light');
|
83 |
+
if (themeToApply === 'DARK') {
|
84 |
+
newThemeUrl = darkThemeUrl;
|
85 |
+
document.body.classList.add('theme-dark');
|
86 |
+
} else {
|
87 |
+
newThemeUrl = lightThemeUrl;
|
88 |
+
document.body.classList.add('theme-light');
|
89 |
+
}
|
90 |
+
let themeLink = document.getElementById('bootstrap-theme');
|
91 |
+
if (!themeLink) {
|
92 |
+
themeLink = document.createElement('link');
|
93 |
+
themeLink.id = 'bootstrap-theme';
|
94 |
+
themeLink.rel = 'stylesheet';
|
95 |
+
themeLink.type = 'text/css';
|
96 |
+
document.getElementsByTagName('head')[0].appendChild(themeLink);
|
97 |
+
}
|
98 |
+
if (themeLink.href !== newThemeUrl) {
|
99 |
+
themeLink.href = newThemeUrl;
|
100 |
+
}
|
101 |
+
return null;
|
102 |
+
}
|
103 |
+
""",
|
104 |
+
Output('app-wrapper', 'className'),
|
105 |
+
Input('url', 'pathname'),
|
106 |
+
State('user-theme-preference-store', 'data')
|
107 |
+
)
|
108 |
+
app.clientside_callback(
|
109 |
+
"""
|
110 |
+
function(themePreferenceFromStore) {
|
111 |
+
let themeToApply = 'LIGHT';
|
112 |
+
if (themePreferenceFromStore && typeof themePreferenceFromStore.theme === 'string') {
|
113 |
+
themeToApply = themePreferenceFromStore.theme;
|
114 |
+
}
|
115 |
+
const lightThemeUrl = "https://cdn.jsdelivr.net/npm/[email protected]/dist/flatly/bootstrap.min.css";
|
116 |
+
const darkThemeUrl = "https://cdn.jsdelivr.net/npm/[email protected]/dist/darkly/bootstrap.min.css";
|
117 |
+
let newThemeUrl = lightThemeUrl;
|
118 |
+
document.body.classList.remove('theme-dark', 'theme-light');
|
119 |
+
if (themeToApply === 'DARK') {
|
120 |
+
newThemeUrl = darkThemeUrl;
|
121 |
+
document.body.classList.add('theme-dark');
|
122 |
+
} else {
|
123 |
+
newThemeUrl = lightThemeUrl;
|
124 |
+
document.body.classList.add('theme-light');
|
125 |
+
}
|
126 |
+
let themeLink = document.getElementById('bootstrap-theme');
|
127 |
+
if (!themeLink) {
|
128 |
+
themeLink = document.createElement('link');
|
129 |
+
themeLink.id = 'bootstrap-theme';
|
130 |
+
themeLink.rel = 'stylesheet';
|
131 |
+
themeLink.type = 'text/css';
|
132 |
+
document.getElementsByTagName('head')[0].appendChild(themeLink);
|
133 |
+
}
|
134 |
+
if (themeLink.href !== newThemeUrl) {
|
135 |
+
themeLink.href = newThemeUrl;
|
136 |
+
}
|
137 |
+
return null;
|
138 |
+
}
|
139 |
+
""",
|
140 |
+
Output('app-wrapper', 'className', allow_duplicate=True),
|
141 |
+
Input('user-theme-preference-store', 'data'),
|
142 |
+
prevent_initial_call=True
|
143 |
+
)
|
144 |
+
|
145 |
+
# Callback untuk Navigasi Halaman, Kontrol Sidebar, dan Modal Logout
|
146 |
+
@app.callback(
|
147 |
+
Output('app-wrapper', 'children'),
|
148 |
+
Output('logout-confirm-modal', 'is_open'),
|
149 |
+
Input('url', 'pathname'),
|
150 |
+
State('login-status', 'data')
|
151 |
+
)
|
152 |
+
def display_page_logic(pathname, login_data):
|
153 |
+
is_logged_in = login_data and login_data.get('logged_in', False)
|
154 |
+
no_sidebar_pages = ['/login', '/signup']
|
155 |
+
open_logout_modal = False
|
156 |
+
|
157 |
+
if pathname == '/logout' and is_logged_in:
|
158 |
+
open_logout_modal = True
|
159 |
+
|
160 |
+
# KASUS 1: Pengguna belum login
|
161 |
+
if not is_logged_in:
|
162 |
+
if pathname in no_sidebar_pages or pathname == '/' or pathname is None:
|
163 |
+
if pathname == '/signup':
|
164 |
+
return signup.layout, False
|
165 |
+
else:
|
166 |
+
return login.layout, False
|
167 |
+
else:
|
168 |
+
return dcc.Location(pathname="/login", id="redirect-to-login-unauth"), False
|
169 |
+
|
170 |
+
# KASUS 2: Pengguna sudah login
|
171 |
+
else:
|
172 |
+
if pathname == '/logout':
|
173 |
+
page_layout_content = beranda.layout
|
174 |
+
elif pathname in no_sidebar_pages or pathname == '/' or pathname is None:
|
175 |
+
return dcc.Location(pathname="/beranda", id="redirect-to-home-auth"), False
|
176 |
+
elif pathname == '/beranda':
|
177 |
+
page_layout_content = beranda.layout
|
178 |
+
elif pathname == '/analisis_tren_penyakit':
|
179 |
+
page_layout_content = analisis_tren_penyakit.layout
|
180 |
+
elif pathname == '/distribusi_kasus_demografi':
|
181 |
+
page_layout_content = distribusi_kasus_demografi.layout
|
182 |
+
elif pathname == '/input_data':
|
183 |
+
page_layout_content = input_data.layout
|
184 |
+
elif pathname == '/laporan':
|
185 |
+
page_layout_content = laporan.layout
|
186 |
+
elif pathname == '/pengaturan':
|
187 |
+
page_layout_content = pengaturan.layout
|
188 |
+
else:
|
189 |
+
page_layout_content = html.H1("404 - Halaman Tidak Ditemukan", className="text-center mt-5")
|
190 |
+
|
191 |
+
return html.Div([
|
192 |
+
sidebar.sidebar_layout,
|
193 |
+
html.Div(page_layout_content, id="page-content")
|
194 |
+
]), open_logout_modal
|
195 |
+
|
196 |
+
# Callback untuk menyimpan URL sebelum logout
|
197 |
+
@app.callback(
|
198 |
+
Output('previous-url-store', 'data'),
|
199 |
+
Input('url', 'pathname'),
|
200 |
+
State('previous-url-store', 'data')
|
201 |
+
)
|
202 |
+
def store_previous_url(current_pathname, last_stored_url):
|
203 |
+
excluded_paths = ['/logout', '/login', '/signup']
|
204 |
+
if current_pathname not in excluded_paths and current_pathname != last_stored_url:
|
205 |
+
return current_pathname
|
206 |
+
return no_update
|
207 |
+
|
208 |
+
# Callback untuk Update Profil di Sidebar
|
209 |
+
@app.callback(
|
210 |
+
Output('sidebar-profile-name', 'children'),
|
211 |
+
Output('sidebar-profile-section', 'style'),
|
212 |
+
Input('login-status', 'data'),
|
213 |
+
Input('url', 'pathname')
|
214 |
+
)
|
215 |
+
def update_sidebar_profile(login_data, pathname):
|
216 |
+
no_sidebar_pages_or_logout = ['/login', '/signup', '/logout']
|
217 |
+
if login_data and login_data.get('logged_in'):
|
218 |
+
nama_pengguna = login_data.get('nama_lengkap', login_data.get('username', 'Pengguna'))
|
219 |
+
if pathname not in no_sidebar_pages_or_logout:
|
220 |
+
return nama_pengguna, {'display': 'block'}
|
221 |
+
else:
|
222 |
+
return no_update, {'display': 'none'}
|
223 |
+
return "Nama Pengguna", {'display': 'none'}
|
224 |
+
|
225 |
+
# Callback untuk Aksi Modal Logout
|
226 |
+
@app.callback(
|
227 |
+
Output('logout-redirect-location', 'pathname'),
|
228 |
+
Output('login-status', 'clear_data', allow_duplicate=True),
|
229 |
+
Output('logout-confirm-modal', 'is_open', allow_duplicate=True),
|
230 |
+
Input('logout-confirm-yes', 'n_clicks'),
|
231 |
+
Input('logout-confirm-no', 'n_clicks'),
|
232 |
+
State('previous-url-store', 'data'),
|
233 |
+
prevent_initial_call=True
|
234 |
+
)
|
235 |
+
def handle_logout_confirmation_app(n_yes, n_no, previous_url):
|
236 |
+
triggered_id = dash.callback_context.triggered_id if dash.callback_context.triggered_id else None
|
237 |
+
|
238 |
+
if triggered_id == 'logout-confirm-yes':
|
239 |
+
return '/login', True, False
|
240 |
+
|
241 |
+
elif triggered_id == 'logout-confirm-no':
|
242 |
+
url_to_return = previous_url if previous_url else '/beranda'
|
243 |
+
return url_to_return, False, False
|
244 |
+
|
245 |
+
return no_update, no_update, False
|
246 |
+
|
247 |
+
# -----------------------------------------------------------------------------
|
248 |
+
# BAGIAN 4: MENJALANKAN APLIKASI (TIDAK ADA PERUBAHAN)
|
249 |
+
# -----------------------------------------------------------------------------
|
250 |
+
# Blok ini memastikan server development hanya berjalan saat file ini dieksekusi langsung,
|
251 |
+
# dan tidak akan berjalan saat diimpor oleh Gunicorn di server produksi.
|
252 |
+
if __name__ == '__main__':
|
253 |
+
app.run(debug=True, port=8050)
|