ssfakude commited on
Commit
06cdae5
1 Parent(s): 9ecd181

Add application file

Browse files
app.py ADDED
@@ -0,0 +1,500 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import print_function
2
+ from imaplib import _Authenticator
3
+ from itertools import count
4
+ import json
5
+ import pickle
6
+ from pathlib import Path
7
+ from unicodedata import name
8
+ import streamlit_authenticator as stauth
9
+ from re import X
10
+ import pandas as pd # pip install pandas openpyxl
11
+ import plotly.express as px # pip install plotly-express
12
+ import plotly.graph_objects as go
13
+ import numpy as np
14
+ import requests
15
+ import timeit
16
+ import altair as alt
17
+ import streamlit as st # pip install streamlit
18
+ from streamlit_lottie import st_lottie
19
+ from streamlit_lottie import st_lottie_spinner
20
+ import datetime as dt
21
+ import time
22
+ from datetime import datetime
23
+ from datetime import timedelta
24
+ from dateutil.relativedelta import relativedelta # to add days or years
25
+ # emojis: https://www.webfx.com/tools/emoji-cheat-sheet/
26
+
27
+ start = timeit.default_timer()
28
+ st.set_page_config(page_title="1 Nav sync Zoho", page_icon="rc_logo.ico")
29
+
30
+
31
+ CLIENT_ID = '1000.0G3DK4Y761UL7AHEKYKIMHQLTXWJLO'
32
+ CLIENT_SECRET = '7752cf983497614b4144c1fa482b21fd378db6a0fa'
33
+ ZOHO_DATA = {
34
+ "access_token": "1000.cc8e2f20b4b26629fd81166df1c745e5.e223cc6c2d4f877eff83c24eaaaca5ab",
35
+ "refresh_token": "1000.5b482d0f8da0e2aa6773b97f0cc9d7f9.29b3cbfd1dc7314dc3a4ec12394434b1",
36
+ "api_domain": "https://www.zohoapis.com",
37
+ "token_type": "Bearer",
38
+ "expires_in": 3600
39
+ }
40
+
41
+ def refresh_auth():
42
+
43
+ url = "https://accounts.zoho.com/oauth/v2/token?refresh_token=1000.5b482d0f8da0e2aa6773b97f0cc9d7f9.29b3cbfd1dc7314dc3a4ec12394434b1&client_id=1000.0G3DK4Y761UL7AHEKYKIMHQLTXWJLO&client_secret=7752cf983497614b4144c1fa482b21fd378db6a0fa&grant_type=refresh_token"
44
+ r = requests.post(url)
45
+ data = json.loads(r.text)
46
+ if 'access_token' in data:
47
+ ZOHO_DATA['access_token'] = data['access_token']
48
+
49
+ return data['access_token']
50
+
51
+
52
+ access_token = refresh_auth()
53
+
54
+ not_found_order =[]
55
+ not_found_invoiced =[]
56
+ not_found_return =[]
57
+ time_out =[]
58
+ #----------User AUth----------
59
+ names = ["Simphiwe Fakude", "Robert Jacobs", "Robert joubert","Jean-Pierre Myburg","Paul Oosthuizen", "Lee Douglas Webster", "Nazley Miranda", "Cindy Santamaria","Natasha Naidoo", "Carla kolbe", "RC Admin"]
60
+ usernames = ["simphiwef", "robertj","robert", "jp","paulo","leew","nazleym","cindys","natashan","carla", "rcadmin"]
61
+ file_path = Path(__file__).parent / "hashed_pw.pkl"
62
+ with file_path.open("rb") as file:
63
+ hashed_passwords = pickle.load(file)
64
+ authenticator = stauth.Authenticate(names, usernames, hashed_passwords,"rc_dashboard", "abcdef", cookie_expiry_days=30 )# cookie
65
+ name, authentication_status, username = authenticator.login('Please Login', 'main')
66
+
67
+
68
+ if authentication_status == False:
69
+ st.error('Username/password is incorrect')
70
+ # elif authentication_status == None:
71
+ # st.warning('Please enter your username and password')
72
+ elif authentication_status:
73
+
74
+ def load_lottieurl(url: str): #load from the web
75
+
76
+ r = requests.get(url)
77
+ if r.status_code != 200:
78
+ return None
79
+ return r.json()
80
+ st.write(f'Welcome *{name}*')
81
+ lottie_dog=load_lottieurl("https://assets7.lottiefiles.com/packages/lf20_xBGyhl.json")
82
+ with st_lottie_spinner(lottie_dog, width= 300, key="dog"):
83
+
84
+ @st.cache()
85
+ def read_file(data_file):
86
+
87
+ xls = pd.ExcelFile(data_file)
88
+
89
+ try:
90
+ df_Order = pd.read_excel(xls, 'Open Released')
91
+ except Exception as e:
92
+ st.error("Incorect Sheet name for Open Released:(")
93
+ st.stop()
94
+ try:
95
+ df_Invoiced = pd.read_excel(xls, 'Posted Invoices')
96
+ except Exception as e:
97
+ st.error("Incorect Sheet name for Posted Invoices:(")
98
+ st.stop()
99
+ try:
100
+ df_Return = pd.read_excel(xls, 'SRT')
101
+ except Exception as e:
102
+ st.error("Incorect Sheet name for SRT:(")
103
+ st.stop()
104
+ return df_Order, df_Invoiced, df_Return
105
+
106
+
107
+
108
+
109
+ #time.sleep(4)
110
+ def main():
111
+ st.title("1 Nav + Zoho Integration Test")
112
+ st.subheader("The file should contain this 3 sheets:")
113
+ data_file = st.file_uploader("[Open Released, Posted Invoices ,SRT]",type=['xlsx'])
114
+ # lottie_nodata=load_lottieurl("https://assets6.lottiefiles.com/packages/lf20_5awivhzm.json")
115
+ # st_lottie(lottie_nodata, key="load", width=600)
116
+ if st.button("Process"):
117
+
118
+ if data_file is not None:
119
+ file_details = {"Filename":data_file.name,"FileType":data_file.type,"FileSize":data_file.size}
120
+
121
+ df_Order = read_file(data_file)[0]
122
+ df_Invoiced = read_file(data_file)[1]
123
+ df_Return =read_file(data_file)[2]
124
+
125
+ # st.write(file_details)
126
+ # df = pd.read_csv(data_file)
127
+ # left_column, middle_column, right_column = st.columns(3)
128
+ # with left_column:
129
+ # st.dataframe(df_Order)
130
+ # with middle_column:
131
+ # st.dataframe(df_Invoiced)
132
+ # with right_column:
133
+ # st.dataframe(df_Return)
134
+
135
+
136
+ headers = {"Authorization" : "Zoho-oauthtoken "+access_token, "orgId": "725575894"}
137
+ latest_iteration = st.empty()
138
+ print("-----------------------Open Released-------------------")
139
+ len_df_Order =len(df_Order.index)
140
+ for i, j in df_Order.iterrows():
141
+ so_number = j[1]
142
+ if len_df_Order - i == 1:
143
+ latest_iteration.text('Done updating Open Released spreadsheet')
144
+ else:
145
+ latest_iteration.text(f'Open Released: {len_df_Order - i} records left - {j[1]}')
146
+ if pd.isna(so_number) ==True:
147
+ break
148
+ else:
149
+
150
+ so_number = so_number[5:]
151
+ dateTime= str(j[2])
152
+ dateTime = dateTime.replace(" ", "T")
153
+ dateTime = dateTime[:19]+".000Z"
154
+ cf_1nav_customer_name= j[4]
155
+ nav_overdue_bal = j[6]
156
+ nav_credit_hold =j[5]
157
+ cf_1nav_cus_price_grp =j[8]
158
+ cf_1nav_sales_resp = j[9]
159
+ cf_1nav_net_weight = j[11]
160
+ cf_1nav_amount = j[18]
161
+ cf_1nav_location_code = j[10]
162
+ now = datetime.now()
163
+
164
+
165
+ dt_string = now.strftime("%Y-%m-%d %H:%M:%S")
166
+ sync_date=dt_string.replace(" ", "T")+ ".000Z"
167
+ status_ = j[0]
168
+ if status_ == "Open":
169
+ #desk_status = "Approval Rejected"
170
+ desk_status = "Pending - finance query"
171
+ else:
172
+ desk_status = "Pending - awaiting shipment"
173
+
174
+
175
+ if nav_overdue_bal <1 or str(nav_overdue_bal) =="FALSE" :
176
+ cf_1nav_overdue_bal = "false"
177
+
178
+ else:
179
+ cf_1nav_overdue_bal= "true"
180
+
181
+
182
+
183
+ if nav_credit_hold <1 or str(nav_credit_hold) =="FALSE" :
184
+ cf_1nav_credit_hold = "false"
185
+ else:
186
+ cf_1nav_credit_hold ="true"
187
+
188
+
189
+ URL = "https://desk.zoho.com/api/v1/tickets/search?limit=1&customField1=cf_s_o_number:"+so_number
190
+
191
+
192
+
193
+
194
+ try:
195
+ req = requests.get(url = URL, headers= headers)
196
+
197
+ except:
198
+ print("Connection refused by the server..")
199
+ print("Let me sleep for 5 seconds")
200
+ print("ZZzzzz...")
201
+ time.sleep(2)
202
+ print("Was a nice sleep, now let me continue...")
203
+
204
+
205
+
206
+ if req.status_code == 200:
207
+ data_respo = json.loads(req.text)
208
+ ticket_id = data_respo['data'][0]['id']
209
+ # So Number founf then update fields
210
+ url = "https://desk.zoho.com/api/v1/tickets/"+ticket_id
211
+ data ={ "status":desk_status,
212
+ "cf":{
213
+ "cf_1_nav_sync":"true",
214
+ "cf_1nav_status":status_,
215
+ "cf_1nav_customer_name":cf_1nav_customer_name,
216
+ "cf_1nav_cus_price_grp":cf_1nav_cus_price_grp,
217
+ "cf_1nav_sales_resp":cf_1nav_sales_resp,
218
+ "cf_1nav_date_time":dateTime,
219
+ "cf_1nav_amount":cf_1nav_amount,
220
+ "cf_1nav_net_weight":cf_1nav_net_weight,
221
+ "cf_1nav_overdue_bal":cf_1nav_overdue_bal,
222
+ "cf_1nav_credit_hold":cf_1nav_credit_hold,
223
+ "cf_1nav_location_code": cf_1nav_location_code,
224
+ "cf_1nav_sync_time": sync_date,
225
+
226
+ "cf_added_by": name
227
+ }
228
+ }
229
+ try:
230
+ r = requests.patch(url, headers=headers, json=data)
231
+ except:
232
+ print("Connection refused by the server..")
233
+ print("Let me sleep for 5 seconds")
234
+ print("ZZzzzz...")
235
+ time_out.append(so_number)
236
+ time.sleep(3)
237
+ print("Was a nice sleep, now let me continue...")
238
+
239
+
240
+ else:
241
+ not_found_order.append(so_number)
242
+ print("Search: ", req.status_code, " Update: ",req.status_code, " - ", so_number)
243
+
244
+ print("-----------------------Invoiced-------------------")
245
+ latest_iteration = st.empty()
246
+ len_df_Invoiced =len(df_Invoiced.index)
247
+ for i, j in df_Invoiced.iterrows():
248
+ if len_df_Invoiced - i == 1:
249
+ latest_iteration.text('Done updating Invoiced spreadsheet')
250
+ else:
251
+ latest_iteration.text(f'Invoiced: {((len_df_Invoiced - i))} records left - {j[6]}')
252
+ so_number = j[6]
253
+ if pd.isna(so_number) ==True:
254
+ break
255
+ else:
256
+
257
+ so_number = so_number[5:]
258
+ cf_1nav_customer_name= j[2]
259
+ #cf_1nav_cus_price_grp =j[8]
260
+ cf_1nav_sales_resp = j[7]
261
+ cf_1nav_net_weight = j[9]
262
+ cf_1nav_amount = j[3]
263
+ cf_1nav_location_code = j[8]
264
+ cf_1nav_req_del_date = str(j[5])[:10]
265
+ cf_1nav_shipping_date = str(j[11])[:10]
266
+ cf_1nav_doc_date =str(j[10])[:10]
267
+ now = datetime.now()
268
+
269
+
270
+ dt_string = now.strftime("%Y-%m-%d %H:%M:%S")
271
+ sync_date = dt_string.replace(" ", "T")+ ".000Z"
272
+
273
+
274
+ URL = "https://desk.zoho.com/api/v1/tickets/search?limit=1&customField1=cf_s_o_number:"+so_number
275
+ headers = {"Authorization" : "Zoho-oauthtoken "+access_token, "orgId": "725575894"}
276
+
277
+
278
+ try:
279
+ req = requests.get(url = URL, headers= headers)
280
+
281
+ except:
282
+ print("Connection refused by the server..")
283
+ print("Let me sleep for 3 seconds")
284
+ print("ZZzzzz...")
285
+ time.sleep(3)
286
+ print("Was a nice sleep, now let me continue...")
287
+
288
+
289
+ if req.status_code == 200:
290
+ data_resp = json.loads(req.text)
291
+ ticket_id = data_resp['data'][0]['id']
292
+ url = "https://desk.zoho.com/api/v1/tickets/"+ticket_id
293
+ # So Number founf then update fields
294
+ data ={
295
+ "cf":{"status": "Closed",
296
+ "cf_1_nav_sync":"true",
297
+ "cf_1nav_status":"Invoiced",
298
+ "cf_1nav_customer_name":cf_1nav_customer_name,
299
+ "cf_1nav_sales_resp":cf_1nav_sales_resp,
300
+ "cf_1nav_amount":cf_1nav_amount,
301
+ "cf_1nav_net_weight":cf_1nav_net_weight,
302
+ "cf_1nav_req_del_date":cf_1nav_req_del_date,
303
+ "cf_1nav_shipping_date":cf_1nav_shipping_date,
304
+ "cf_1nav_location_code": cf_1nav_location_code,
305
+ "cf_1nav_doc_date": cf_1nav_doc_date,
306
+ "cf_1nav_overdue_bal":"false",
307
+ "cf_1nav_credit_hold":"false",
308
+ "cf_1nav_sync_time": sync_date,
309
+ "cf_added_by": name
310
+ }
311
+ }
312
+ try:
313
+ r = requests.patch(url, headers=headers, json=data)
314
+
315
+ except:
316
+ print("Connection refused by the server..")
317
+ print("Let me sleep for 3 seconds")
318
+ print("ZZzzzz...")
319
+ time.sleep(3)
320
+ time_out.append(so_number)
321
+ print("Was a nice sleep, now let me continue...")
322
+
323
+
324
+ else:
325
+ not_found_invoiced.append(so_number)
326
+ print("Search: ", req.status_code, " Update: ",req.status_code, " - ", so_number)
327
+
328
+
329
+ print("-----------------------SRT-------------------")
330
+ len_df_Return =len(df_Return.index)
331
+ for i, j in df_Return.iterrows():
332
+ so_number = j[0]
333
+ if len_df_Return - i == 1:
334
+ latest_iteration.text('Done updating SRT spreadsheet ')
335
+ else:
336
+ latest_iteration.text(f'SRT: {len_df_Return - i} records left - {j[0]}')
337
+ if pd.isna(so_number) ==True:
338
+ break
339
+ else:
340
+
341
+ cf_1nav_customer_name = j[2]
342
+ cf_1nav_sales_resp = j[3]
343
+ so_number = so_number[6:]
344
+ cf_1nav_location_code =j[4]
345
+ cf_1nav_doc_date =str(j[8])[:10]
346
+ now = datetime.now()
347
+
348
+
349
+ dt_string = now.strftime("%Y-%m-%d %H:%M:%S")
350
+ sync_date = dt_string.replace(" ", "T")+ ".000Z"
351
+
352
+ URL = "https://desk.zoho.com/api/v1/tickets/search?limit=1&customField1=cf_s_o_number:"+so_number
353
+ headers = {"Authorization" : "Zoho-oauthtoken "+access_token, "orgId": "725575894"}
354
+
355
+
356
+ try:
357
+ req = requests.get(url = URL, headers= headers)
358
+
359
+ except:
360
+ print("Connection refused by the server..")
361
+ print("Let me sleep for 3 seconds")
362
+ print("ZZzzzz...")
363
+ time.sleep(3)
364
+ print("Was a nice sleep, now let me continue...")
365
+
366
+
367
+ if req.status_code == 200:
368
+ data_resp = json.loads(req.text)
369
+ ticket_id = data_resp['data'][0]['id']
370
+ # So Number founf then update fields
371
+ url = "https://desk.zoho.com/api/v1/tickets/"+ticket_id
372
+
373
+ data ={
374
+ "cf":{
375
+ "cf_1nav_status":"Processed",
376
+ "cf_1_nav_sync":"true",
377
+ "cf_1nav_customer_name":cf_1nav_customer_name,
378
+ "cf_1nav_sales_resp":cf_1nav_sales_resp,
379
+ "cf_1nav_location_code": cf_1nav_location_code,
380
+ "cf_1nav_doc_date": cf_1nav_doc_date,
381
+ "cf_1nav_overdue_bal":"false",
382
+ "cf_1nav_credit_hold":"false",
383
+ "cf_1nav_sync_time": sync_date,
384
+ "cf_added_by": name
385
+
386
+ }
387
+ }
388
+ try:
389
+ r = requests.patch(url, headers=headers, json=data)
390
+
391
+ except:
392
+ print("Connection refused by the server..")
393
+ print("Let me sleep for 5 seconds")
394
+ print("ZZzzzz...")
395
+ time.sleep(5)
396
+ time_out.append(so_number)
397
+ print("Was a nice sleep, now let me continue...")
398
+
399
+ else:
400
+ not_found_return.append(so_number)
401
+ print("Search: ", req.status_code, " Update: ",req.status_code, " - ", so_number)
402
+ stop = timeit.default_timer()
403
+ execution_time = stop - start
404
+ c = """<html>
405
+ <head></head>
406
+ <body><p>Hi Naz and Cindy, <br><br></p></body>
407
+ </html>"""
408
+ space= """<html>
409
+ <br><br>
410
+ </html>"""
411
+ content = c+"Here is the lists of all the SO Number that are not on Zoho Desk, but in 1 Nav: " + space +"""<html>
412
+ <head></head><body><p>------------------ <strong>Open Released</strong>------------------- <br> </p></body>
413
+ </html>"""+str(not_found_order)+"""<html>
414
+ <head></head>
415
+ <br>
416
+ <body><p>---------------------<strong>Posted Invoices</strong>--------------------- <br></p></body>
417
+ </html>"""+ str(not_found_invoiced)+"""<html>
418
+ <head></head>
419
+ <br>
420
+ <body><p>-----------------------<strong>SRT</strong>---------------------------- <br></p></body>
421
+ </html>"""+ str(not_found_return)+"""<html>
422
+ <head></head>
423
+ <br>
424
+ <body><p>-----------------------<strong>Time Out</strong>---------------------------- <br></p></body>
425
+ </html>"""+ str(time_out)
426
+
427
+
428
+
429
+
430
+ email_body = """<html>
431
+ <head></head>
432
+ <body><p>Hi, <br><br></p> The Integration has been completed<br><br> </body>
433
+ </html>""" +"Runtime: "+ str(round(execution_time,2))+" seconds."
434
+
435
+ url = "https://desk.zoho.com/api/v1/tickets"
436
+ data ={ "subject":"SO Number not Found in CRM",
437
+ "departmentId":"541303000000434081",
438
+
439
+ "status" : "Open",
440
+ "teamId":"541303000031453272",
441
+ "contactId":"541303000024559001",
442
+ "description":content,
443
+
444
+ "cf":{
445
+ "cf_ticket_type":"1Nav error",
446
+ }
447
+ }
448
+ r = requests.post(url, headers=headers, json=data)
449
+
450
+ url = 'https://mail.zoho.com/api/accounts/6014958000000008002/messages'
451
+
452
+ data = {
453
+ "fromAddress":"simphiwef@boomerangsa.co.za",
454
+ "toAddress": username+"@boomerangsa.co.za",
455
+ "ccAddress": "simphiwef@boomerangsa.co.za",
456
+ "bccAddress": "",
457
+ "subject": "Zoho Desk Integration completed",
458
+ "content": email_body,
459
+ "askReceipt": "no"
460
+ }
461
+ headers = {
462
+ 'Authorization': 'Zoho-oauthtoken ' + ZOHO_DATA['access_token']
463
+ }
464
+ r = requests.post(url, headers=headers, json=data)
465
+
466
+
467
+
468
+
469
+
470
+ st.markdown("<h2 style='text-align: center; color: white;'>Synchronization completed!</h2>", unsafe_allow_html=True)
471
+ lottie_nodata=load_lottieurl("https://assets7.lottiefiles.com/private_files/lf30_rjqwaenm.json")
472
+ #st_lottie(lottie_nodata, key="done", width=270)
473
+ st.balloons()
474
+ print("-----------------------------------------------")
475
+
476
+ print (f"Run Time: {execution_time:.2f} Seconds")
477
+ st.subheader(f"Run Time: {execution_time:.2f} Seconds")
478
+
479
+
480
+
481
+ if __name__ == '__main__':
482
+ main()
483
+
484
+
485
+
486
+
487
+ # ---- HIDE STREAMLIT STYLE ----
488
+ hide_st_style = """
489
+ <style>
490
+ #MainMenu {visibility: hidden;}
491
+ footer {visibility: hidden;}
492
+ header {visibility: hidden;}
493
+ #title {
494
+ text-align: center
495
+ </style>
496
+ """
497
+
498
+ st.markdown(hide_st_style, unsafe_allow_html=True)
499
+
500
+
generate_keys.py ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pickle
2
+ from pathlib import Path
3
+ from argon2 import hash_password
4
+ import streamlit_authenticator as stauth
5
+ names = ["Simphiwe Fakude", "Robert Jacobs", "Robert joubert","Jean-Pierre Myburg","Paul Oosthuizen", "Lee Douglas Webster", "Nazley Miranda", "Cindy Santamaria","Natasha Naidoo", "Carla kolbe", "RC Admin"]
6
+ usernames = ["simphiwef", "robertj","robert", "jp","paulo","leew","nazleym","cindys","natashan","carla", "rcadmin"]
7
+ passwords = ["sleepingdog","Boom@123","master","doubleclick", "justdoit","tryme", "letmein","hello","master", "letmein" ,"2Birds1Stone"]
8
+ hashed_passwords = stauth.Hasher(passwords).generate() # bycrat algor
9
+ file_path = Path(__file__).parent / "hashed_pw.pkl"
10
+ with file_path.open("wb") as file:
11
+ pickle.dump(hashed_passwords, file)
hashed_pw.pkl ADDED
Binary file (709 Bytes). View file
 
rc_logo.ico ADDED
requirements.txt ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ plotly
2
+ pip
3
+ pandas
4
+ streamlit
5
+ openpyxl
6
+ protobuf
7
+ streamlit-lottie
8
+ streamlit-authenticator
streamlit_authenticator/__init__.py ADDED
@@ -0,0 +1,267 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import jwt
2
+ import yaml
3
+ import bcrypt
4
+ import streamlit as st
5
+ from yaml.loader import SafeLoader
6
+ from datetime import datetime, timedelta
7
+ import extra_streamlit_components as stx
8
+ import streamlit.components.v1 as components
9
+
10
+ _RELEASE = True
11
+
12
+ class Hasher:
13
+ def __init__(self, passwords):
14
+ """Create a new instance of "Hasher".
15
+ Parameters
16
+ ----------
17
+ passwords: list
18
+ The list of plain text passwords to be hashed.
19
+ Returns
20
+ -------
21
+ list
22
+ The list of hashed passwords.
23
+ """
24
+ self.passwords = passwords
25
+
26
+ def hash(self, password):
27
+ """
28
+ Parameters
29
+ ----------
30
+ password: str
31
+ The plain text password to be hashed.
32
+ Returns
33
+ -------
34
+ str
35
+ The hashed password.
36
+ """
37
+ return bcrypt.hashpw(password.encode(), bcrypt.gensalt()).decode()
38
+
39
+ def generate(self):
40
+ """
41
+ Returns
42
+ -------
43
+ list
44
+ The list of hashed passwords.
45
+ """
46
+ hashedpw = []
47
+
48
+ for password in self.passwords:
49
+ hashedpw.append(self.hash(password))
50
+ return hashedpw
51
+
52
+ class Authenticate:
53
+ def __init__(self, names, usernames, passwords, cookie_name, key, cookie_expiry_days=30):
54
+ """Create a new instance of "Authenticate".
55
+ Parameters
56
+ ----------
57
+ names: list
58
+ The list of names of users.
59
+ usernames: list
60
+ The list of usernames in the same order as names.
61
+ passwords: list
62
+ The list of hashed passwords in the same order as names.
63
+ cookie_name: str
64
+ The name of the JWT cookie stored on the client's browser for passwordless reauthentication.
65
+ key: str
66
+ The key to be used for hashing the signature of the JWT cookie.
67
+ cookie_expiry_days: int
68
+ The number of days before the cookie expires on the client's browser.
69
+ Returns
70
+ -------
71
+ str
72
+ Name of authenticated user.
73
+ boolean
74
+ The status of authentication, None: no credentials entered, False: incorrect credentials, True: correct credentials.
75
+ str
76
+ Username of authenticated user.
77
+ """
78
+ self.names = names
79
+ self.usernames = usernames
80
+ self.passwords = passwords
81
+ self.cookie_name = cookie_name
82
+ self.key = key
83
+ self.cookie_expiry_days = cookie_expiry_days
84
+ self.cookie_manager = stx.CookieManager()
85
+
86
+ if 'name' not in st.session_state:
87
+ st.session_state['name'] = None
88
+ if 'authentication_status' not in st.session_state:
89
+ st.session_state['authentication_status'] = None
90
+ if 'username' not in st.session_state:
91
+ st.session_state['username'] = None
92
+ if 'logout' not in st.session_state:
93
+ st.session_state['logout'] = None
94
+
95
+ def token_encode(self):
96
+ """
97
+ Returns
98
+ -------
99
+ str
100
+ The JWT cookie for passwordless reauthentication.
101
+ """
102
+ return jwt.encode({'name':st.session_state['name'],
103
+ 'username':st.session_state['username'],
104
+ 'exp_date':self.exp_date}, self.key, algorithm='HS256')
105
+
106
+ def token_decode(self):
107
+ """
108
+ Returns
109
+ -------
110
+ str
111
+ The decoded JWT cookie for passwordless reauthentication.
112
+ """
113
+ try:
114
+ return jwt.decode(self.token, self.key, algorithms=['HS256'])
115
+ except:
116
+ return False
117
+
118
+ def exp_date(self):
119
+ """
120
+ Returns
121
+ -------
122
+ str
123
+ The JWT cookie's expiry timestamp in Unix epoch.
124
+ """
125
+ return (datetime.utcnow() + timedelta(days=self.cookie_expiry_days)).timestamp()
126
+
127
+ def check_pw(self):
128
+ """
129
+ Returns
130
+ -------
131
+ boolean
132
+ The validation state for the input password by comparing it to the hashed password on disk.
133
+ """
134
+ return bcrypt.checkpw(self.password.encode(), self.passwords[self.index].encode())
135
+
136
+ def login(self, form_name, location='main'):
137
+ """Create a new instance of "authenticate".
138
+ Parameters
139
+ ----------
140
+ form_name: str
141
+ The rendered name of the login form.
142
+ location: str
143
+ The location of the login form i.e. main or sidebar.
144
+ Returns
145
+ -------
146
+ str
147
+ Name of authenticated user.
148
+ boolean
149
+ The status of authentication, None: no credentials entered, False: incorrect credentials, True: correct credentials.
150
+ str
151
+ Username of authenticated user.
152
+ """
153
+ if location not in ['main', 'sidebar']:
154
+ raise ValueError("Location must be one of 'main' or 'sidebar'")
155
+
156
+ if not st.session_state['authentication_status']:
157
+ self.token = self.cookie_manager.get(self.cookie_name)
158
+ if self.token is not None:
159
+ self.token = self.token_decode()
160
+ if self.token is not False:
161
+ if not st.session_state['logout']:
162
+ if self.token['exp_date'] > datetime.utcnow().timestamp():
163
+ if 'name' and 'username' in self.token:
164
+ st.session_state['name'] = self.token['name']
165
+ st.session_state['username'] = self.token['username']
166
+ st.session_state['authentication_status'] = True
167
+
168
+ if st.session_state['authentication_status'] != True:
169
+ if location == 'main':
170
+ login_form = st.form('Login')
171
+ elif location == 'sidebar':
172
+ login_form = st.sidebar.form('Login')
173
+
174
+ login_form.subheader(form_name)
175
+ self.username = login_form.text_input('Username')
176
+ st.session_state['username'] = self.username
177
+ self.password = login_form.text_input('Password', type='password')
178
+
179
+ if login_form.form_submit_button('Login'):
180
+ self.index = None
181
+ for i in range(0, len(self.usernames)):
182
+ if self.usernames[i] == self.username:
183
+ self.index = i
184
+ if self.index is not None:
185
+ try:
186
+ if self.check_pw():
187
+ st.session_state['name'] = self.names[self.index]
188
+ self.exp_date = self.exp_date()
189
+ self.token = self.token_encode()
190
+ self.cookie_manager.set(self.cookie_name, self.token,
191
+ expires_at=datetime.now() + timedelta(days=self.cookie_expiry_days))
192
+ st.session_state['authentication_status'] = True
193
+ else:
194
+ st.session_state['authentication_status'] = False
195
+ except Exception as e:
196
+ print(e)
197
+ else:
198
+ st.session_state['authentication_status'] = False
199
+
200
+ return st.session_state['name'], st.session_state['authentication_status'], st.session_state['username']
201
+
202
+ def logout(self, button_name, location='main'):
203
+ """Creates a logout button.
204
+ Parameters
205
+ ----------
206
+ button_name: str
207
+ The rendered name of the logout button.
208
+ location: str
209
+ The location of the logout button i.e. main or sidebar.
210
+ """
211
+ if location not in ['main', 'sidebar']:
212
+ raise ValueError("Location must be one of 'main' or 'sidebar'")
213
+
214
+ if location == 'main':
215
+ if st.button(button_name):
216
+ self.cookie_manager.delete(self.cookie_name)
217
+ st.session_state['logout'] = True
218
+ st.session_state['name'] = None
219
+ st.session_state['username'] = None
220
+ st.session_state['authentication_status'] = None
221
+ elif location == 'sidebar':
222
+ if st.sidebar.button(button_name):
223
+ self.cookie_manager.delete(self.cookie_name)
224
+ st.session_state['logout'] = True
225
+ st.session_state['name'] = None
226
+ st.session_state['username'] = None
227
+ st.session_state['authentication_status'] = None
228
+
229
+ if not _RELEASE:
230
+
231
+ #hashed_passwords = Hasher(['123', '456']).generate()
232
+
233
+ with open('../config.yaml') as file:
234
+ config = yaml.load(file, Loader=SafeLoader)
235
+
236
+ authenticator = Authenticate(
237
+ config['credentials']['names'],
238
+ config['credentials']['usernames'],
239
+ config['credentials']['passwords'],
240
+ config['cookie']['name'],
241
+ config['cookie']['key'],
242
+ config['cookie']['expiry_days']
243
+ )
244
+
245
+ name, authentication_status, username = authenticator.login('Login', 'main')
246
+
247
+ if authentication_status:
248
+ authenticator.logout('Logout', 'main')
249
+ st.write(f'Welcome *{name}*')
250
+ st.title('Some content')
251
+ elif authentication_status == False:
252
+ st.error('Username/password is incorrect')
253
+ elif authentication_status == None:
254
+ st.warning('Please enter your username and password')
255
+
256
+ # Alternatively you use st.session_state['name'] and
257
+ # st.session_state['authentication_status'] to access the name and
258
+ # authentication_status.
259
+
260
+ # if st.session_state['authentication_status']:
261
+ # authenticator.logout('Logout', 'main')
262
+ # st.write(f'Welcome *{st.session_state["name"]}*')
263
+ # st.title('Some content')
264
+ # elif st.session_state['authentication_status'] == False:
265
+ # st.error('Username/password is incorrect')
266
+ # elif st.session_state['authentication_status'] == None:
267
+ # st.warning('Please enter your username and password')
streamlit_authenticator/__pycache__/__init__.cpython-38.pyc ADDED
Binary file (7.47 kB). View file