11# Streamlit Launcher
22
3- <img src =" https://github.com/DwiDevelopes/gambar/raw/main/Desain%20tanpa%20judul%20(8) .jpg " width =" 100% " height =" 100% " >
3+ <img src =" https://github.com/DwiDevelopes/gambar/raw/main/launcher .jpg " width =" 100% " height =" 100% " >
44
55[ ![ PyPI version] ( https://badge.fury.io/py/streamlit-launcher.svg )] ( https://badge.fury.io/py/streamlit-launcher )
66[ ![ Downloads] ( https://pepy.tech/badge/streamlit-launcher/week )] ( https://pepy.tech/project/streamlit-launcher )
1414
1515- ** PyPI Package** : [ https://pypi.org/project/streamlit-launcher/ ] ( https://pypi.org/project/streamlit-launcher/ )
1616- ** Demo Online** : [ https://stremlit-launcher.streamlit.app/ ] ( https://stremlit-launcher.streamlit.app/ )
17- - ** GitHub Repository** : [ https://github.com/royhtml/streamlit-launcher ] ( https://github.com/royhtml/streamlit-launcher )
17+ - ** GitHub Repository** : [ https://github.com/DwiDevelopes/Launcher ] ( https://github.com/DwiDevelopes/Launcher )
1818- ** Documentation** : [ https://streamlit-launcher-docs.readthedocs.io/ ] ( https://streamlit-launcher-docs.readthedocs.io/ )
1919
2020## 📊 Statistik Penggunaan
2121
2222| Metric | Value |
2323| --------| -------|
24- | Total Downloads | 15 ,000+ |
24+ | Total Downloads | 10 ,000+ |
2525| Monthly Downloads | 2,500+ |
2626| Weekly Downloads | 600+ |
2727| Python Version Support | 3.7+ |
2828| Streamlit Version | 1.28+ |
29- | Test Coverage | 85 %+ |
29+ | Test Coverage | 92 %+ |
3030| Last Update | September 2024 |
3131
3232## 📖 Overview
4242- ** Researchers** - Eksperimen dan presentasi hasil penelitian
4343- ** Educators** - Materi pembelajaran interaktif
4444
45- # Update 3.8.7 Install Streamlit Launcher Make Termux ☕
45+ # Streamlit Launcher V4 Update
46+
47+ <img src =" https://github.com/DwiDevelopes/gambar/raw/main/launcher.jpg " width =" 100% " height =" 100% " >
48+ <img src =" https://github.com/DwiDevelopes/gambar/raw/main/tas/1.png " width =" 100% " />
49+ <p align =" center " >
50+ <img src =" https://github.com/DwiDevelopes/gambar/raw/main/tas/2.png " width =" 48% " />
51+ <img src =" https://github.com/DwiDevelopes/gambar/raw/main/tas/3.png " width =" 48% " />
52+ </p >
53+ <p align =" center " >
54+ <img src =" https://github.com/DwiDevelopes/gambar/raw/main/tas/4.png " width =" 48% " />
55+ <img src =" https://github.com/DwiDevelopes/gambar/raw/main/tas/5.png " width =" 48% " />
56+ </p >
57+ <p align =" center " >
58+ <img src =" https://github.com/DwiDevelopes/gambar/raw/main/tas/6.png " width =" 48% " />
59+ <img src =" https://github.com/DwiDevelopes/gambar/raw/main/tas/7.png " width =" 48% " />
60+ </p >
61+
62+ ## Update V4 Grafik Saham
63+
64+ <img src =" https://github.com/DwiDevelopes/gambar/raw/main/Screenshot%202025-11-09%20001502.png " width =" 100% " height =" 100% " >
65+
66+
67+ 1 . Menambahkan grafik saham
68+ 2 . Menambahkan fitur pencarian
69+ 3 . Menambahkan fitur pengaturan
70+ 4 . Menambahkan fitur pengaturan pengguna
71+ 5 . Vitur Saham Grafik
72+ 6 . Chart Saham Lengkap
73+ 7 . OLDC
74+
75+ # Update V4 Install Streamlit Launcher Make Termux ☕
4676
4777<img src =" https://github.com/DwiDevelopes/gambar/raw/main/Desain%20tanpa%20judul%20(10).jpg " width =" 100% " height =" 100% " >
4878
79+
4980# 🌱 Pantun untuk Anak Programming
5081Baik, saatnya puisi kecil untuk pejuang logika digital.
5182Pantun bukan algoritma, tapi ritmenya juga perlu rapi supaya compiler kehidupan tidak error karna logika.
@@ -319,7 +350,7 @@ Bayangkan ini sebagai menu spell:
319350- 📈 Remove Background
320351- 📈 dll
321352
322- # Update 3.8.7 Anlisis And Doctor Analisis and Install Streamlit Launcher Make Termux ☕
353+ # Update V4 Anlisis And Doctor Analisis and Install Streamlit Launcher Make Termux ☕
323354
324355## vitur Update Api Key And Api Server
325356
@@ -342,39 +373,189 @@ import pandas as pd
342373import sqlite3
343374from datetime import datetime
344375import os
376+ import logging
377+ from typing import Optional, Tuple, Any
378+
379+ # Setup logging
380+ logging.basicConfig(level = logging.INFO )
381+ logger = logging.getLogger(__name__ )
345382
346383app = Flask(__name__ )
384+ app.config[' JSON_SORT_KEYS' ] = False
347385
348- def validate_api_key (api_key ):
386+ class DatabaseManager :
387+ """ Class untuk mengelola koneksi database"""
388+
389+ @ staticmethod
390+ def get_connection (db_path : str ) -> sqlite3.Connection:
391+ """ Membuat koneksi database dengan error handling"""
392+ try :
393+ conn = sqlite3.connect(db_path)
394+ conn.row_factory = sqlite3.Row # Mengembalikan hasil sebagai dictionary
395+ return conn
396+ except sqlite3.Error as e:
397+ logger.error(f " Database connection error: { e} " )
398+ raise
399+
400+ def validate_api_key (api_key : str ) -> bool :
401+ """
402+ Validasi API key dari database
403+
404+ Args:
405+ api_key: String API key untuk divalidasi
406+
407+ Returns:
408+ Boolean indicating validity of API key
409+ """
410+ if not api_key:
411+ return False
412+
349413 try :
350- conn = sqlite3.connect (' api_keys.db' )
414+ conn = DatabaseManager.get_connection (' api_keys.db' )
351415 c = conn.cursor()
352- c.execute(" SELECT * FROM api_keys WHERE api_key = ? AND is_active = 1" , (api_key,))
416+
417+ c.execute("""
418+ SELECT * FROM api_keys
419+ WHERE api_key = ? AND is_active = 1
420+ """ , (api_key,))
421+
353422 result = c.fetchone()
354423 conn.close()
424+
355425 return result is not None
426+
427+ except sqlite3.Error as e:
428+ logger.error(f " Database error during API key validation: { e} " )
429+ return False
356430 except Exception as e:
357- print (f " Validation error: { e} " )
431+ logger.error (f " Unexpected error during API key validation : { e} " )
358432 return False
359433
434+ def get_api_key () -> Optional[str ]:
435+ """
436+ Mendapatkan API key dari header atau query parameters
437+
438+ Returns:
439+ API key string atau None jika tidak ditemukan
440+ """
441+ # Cek di header terlebih dahulu
442+ api_key = request.headers.get(' X-API-Key' )
443+
444+ # Jika tidak ada di header, cek di query parameters
445+ if not api_key:
446+ api_key = request.args.get(' api_key' )
447+
448+ return api_key
449+
450+ @app.before_request
451+ def log_request_info ():
452+ """ Middleware untuk logging setiap request"""
453+ logger.info(f " Request: { request.method} { request.path} - IP: { request.remote_addr} " )
454+
455+ @app.after_request
456+ def after_request (response ):
457+ """ Middleware untuk menambahkan header security"""
458+ response.headers.add(' X-Content-Type-Options' , ' nosniff' )
459+ response.headers.add(' X-Frame-Options' , ' DENY' )
460+ response.headers.add(' X-XSS-Protection' , ' 1; mode=block' )
461+ return response
462+
360463@app.route (' /api/v1/health' , methods = [' GET' ])
361464def health_check ():
362- return jsonify({' status' : ' healthy' , ' timestamp' : datetime.now().isoformat()})
465+ """ Endpoint untuk health check API"""
466+ try :
467+ # Test database connection
468+ conn = DatabaseManager.get_connection(' api_keys.db' )
469+ conn.close()
470+
471+ return jsonify({
472+ ' status' : ' healthy' ,
473+ ' timestamp' : datetime.now().isoformat(),
474+ ' version' : ' 1.0.0'
475+ })
476+ except Exception as e:
477+ logger.error(f " Health check failed: { e} " )
478+ return jsonify({
479+ ' status' : ' unhealthy' ,
480+ ' error' : str (e),
481+ ' timestamp' : datetime.now().isoformat()
482+ }), 503
363483
364484@app.route (' /api/v1/statistics' , methods = [' GET' ])
365485def get_statistics ():
366- api_key = request.args.get(' api_key' )
486+ """ Endpoint untuk mendapatkan statistics"""
487+ api_key = get_api_key()
488+
367489 if not api_key or not validate_api_key(api_key):
368- return jsonify({' error' : ' Invalid or inactive API key' }), 401
490+ logger.warning(f " Invalid API key attempt from IP: { request.remote_addr} " )
491+ return jsonify({
492+ ' error' : ' Invalid or inactive API key' ,
493+ ' code' : ' UNAUTHORIZED'
494+ }), 401
369495
370496 try :
371497 # Your statistics logic here
372- return jsonify({' status' : ' success' , ' data' : ' statistics' })
498+ # Contoh: membaca data dari database atau file
499+ statistics_data = {
500+ ' total_users' : 1000 ,
501+ ' active_users' : 750 ,
502+ ' requests_today' : 15000 ,
503+ ' average_response_time' : ' 45ms'
504+ }
505+
506+ logger.info(f " Statistics request successful for API key: { api_key[:8 ]} ... " )
507+
508+ return jsonify({
509+ ' status' : ' success' ,
510+ ' timestamp' : datetime.now().isoformat(),
511+ ' data' : statistics_data
512+ })
513+
373514 except Exception as e:
374- return jsonify({' error' : str (e)}), 500
515+ logger.error(f " Error in statistics endpoint: { e} " )
516+ return jsonify({
517+ ' error' : ' Internal server error' ,
518+ ' code' : ' INTERNAL_ERROR'
519+ }), 500
520+
521+ @app.errorhandler (404 )
522+ def not_found (error ):
523+ """ Handler untuk 404 Not Found"""
524+ return jsonify({
525+ ' error' : ' Endpoint not found' ,
526+ ' code' : ' NOT_FOUND'
527+ }), 404
528+
529+ @app.errorhandler (405 )
530+ def method_not_allowed (error ):
531+ """ Handler untuk 405 Method Not Allowed"""
532+ return jsonify({
533+ ' error' : ' Method not allowed' ,
534+ ' code' : ' METHOD_NOT_ALLOWED'
535+ }), 405
536+
537+ @app.errorhandler (500 )
538+ def internal_server_error (error ):
539+ """ Handler untuk 500 Internal Server Error"""
540+ logger.error(f " Internal server error: { error} " )
541+ return jsonify({
542+ ' error' : ' Internal server error' ,
543+ ' code' : ' INTERNAL_ERROR'
544+ }), 500
375545
376546if __name__ == ' __main__' :
377- app.run(host = ' 0.0.0.0' , port = 5000 , debug = True )
547+ # Cek apakah database exists
548+ if not os.path.exists(' api_keys.db' ):
549+ logger.warning(" Database file 'api_keys.db' not found. Please create it first." )
550+
551+ # Run aplikasi
552+ debug_mode = os.getenv(' FLASK_DEBUG' , ' False' ).lower() == ' true'
553+
554+ app.run(
555+ host = os.getenv(' FLASK_HOST' , ' 0.0.0.0' ),
556+ port = int (os.getenv(' FLASK_PORT' , 5000 )),
557+ debug = debug_mode
558+ )
378559```
379560
380561## vitur Update Scatterplot Visualisasi Interval
0 commit comments