This commit is contained in:
parent
c01ea3208a
commit
c9d4ef99af
17
README.md
17
README.md
@ -90,6 +90,23 @@ Streamlit 앱 실행:
|
|||||||
streamlit run src/app.py --server.port=20000
|
streamlit run src/app.py --server.port=20000
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 인증 설정
|
||||||
|
|
||||||
|
애플리케이션은 streamlit-authenticator를 사용하여 로그인 기능을 제공합니다. 기본 계정은 다음과 같습니다:
|
||||||
|
|
||||||
|
- 관리자: 아이디 `admin`, 비밀번호 `adminpass`
|
||||||
|
- 일반 사용자: 아이디 `user`, 비밀번호 `userpass`
|
||||||
|
|
||||||
|
비밀번호 변경 또는 계정 추가를 위한 steps:
|
||||||
|
|
||||||
|
1. `src/config/generate_credentials.py` 파일을 수정하여 원하는 계정 정보 입력
|
||||||
|
2. 아래 명령어 실행:
|
||||||
|
```
|
||||||
|
cd src/config
|
||||||
|
python generate_credentials.py
|
||||||
|
```
|
||||||
|
3. 생성된 `credentials.yaml` 파일을 확인하여 적용
|
||||||
|
|
||||||
## 모듈
|
## 모듈
|
||||||
|
|
||||||
### 데이터 수집
|
### 데이터 수집
|
||||||
|
|||||||
7
requirements.txt
Normal file
7
requirements.txt
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
streamlit
|
||||||
|
pandas
|
||||||
|
pymysql
|
||||||
|
sqlalchemy
|
||||||
|
python-dotenv
|
||||||
|
PyYAML
|
||||||
|
streamlit-authenticator>=0.2.2
|
||||||
88
src/app.py
88
src/app.py
@ -1,7 +1,11 @@
|
|||||||
"""
|
"""
|
||||||
Main Streamlit application for the Quant Manager.
|
Main Streamlit application for the Quant Manager.
|
||||||
"""
|
"""
|
||||||
|
import os
|
||||||
|
import yaml
|
||||||
import streamlit as st
|
import streamlit as st
|
||||||
|
import streamlit_authenticator as stauth
|
||||||
|
from yaml.loader import SafeLoader
|
||||||
from ui.pages.data_page import render_data_page
|
from ui.pages.data_page import render_data_page
|
||||||
from ui.pages.multi_factor_page import render_multi_factor_page
|
from ui.pages.multi_factor_page import render_multi_factor_page
|
||||||
from ui.pages.quality_page import render_quality_page
|
from ui.pages.quality_page import render_quality_page
|
||||||
@ -18,29 +22,69 @@ st.set_page_config(
|
|||||||
# Define the sidebar navigation
|
# Define the sidebar navigation
|
||||||
def main():
|
def main():
|
||||||
"""Main application function."""
|
"""Main application function."""
|
||||||
# Create sidebar navigation
|
# Load authentication configuration
|
||||||
st.sidebar.title("콴트 매니저")
|
config_path = os.path.join(os.path.dirname(__file__), 'config', 'credentials.yaml')
|
||||||
|
|
||||||
# Navigation options
|
if os.path.exists(config_path):
|
||||||
pages = {
|
with open(config_path) as file:
|
||||||
"데이터 수집": render_data_page,
|
config = yaml.load(file, Loader=SafeLoader)
|
||||||
"멀티 팩터 전략": render_multi_factor_page,
|
|
||||||
"슈퍼 퀄리티 전략": render_quality_page,
|
# Create authenticator object
|
||||||
"슈퍼 밸류 모멘텀 전략": render_value_momentum_page
|
authenticator = stauth.Authenticate(
|
||||||
}
|
config['credentials'],
|
||||||
|
config['cookie']['name'],
|
||||||
# Select page
|
config['cookie']['key'],
|
||||||
selection = st.sidebar.radio("메뉴", list(pages.keys()))
|
config['cookie']['expiry_days'],
|
||||||
|
)
|
||||||
# Render the selected page
|
|
||||||
pages[selection]()
|
# Initialize variables
|
||||||
|
name = None
|
||||||
# Footer
|
|
||||||
st.sidebar.markdown("---")
|
# Create login widget with error handling
|
||||||
st.sidebar.info(
|
try:
|
||||||
"© 2023-2025 콴트 매니저\n\n"
|
authenticator.login()
|
||||||
"한국 주식 시장을 위한 퀀트 투자 도구"
|
except Exception as e:
|
||||||
)
|
st.error(f"로그인 처리 중 오류가 발생했습니다: {str(e)}")
|
||||||
|
st.info("authenticator.login() 호출 문제가 발생했습니다. 구성 파일을 확인하세요.")
|
||||||
|
|
||||||
|
# Handle authentication status
|
||||||
|
if st.session_state['authentication_status'] == False:
|
||||||
|
st.error('아이디 또는 비밀번호가 올바르지 않습니다.')
|
||||||
|
|
||||||
|
if st.session_state['authentication_status'] is None:
|
||||||
|
st.warning('로그인이 필요합니다.')
|
||||||
|
|
||||||
|
# Display application if authenticated
|
||||||
|
if st.session_state['authentication_status']:
|
||||||
|
# Create sidebar navigation
|
||||||
|
st.sidebar.title(f"콴트 매니저 - {st.session_state["name"]}")
|
||||||
|
|
||||||
|
# Add logout button
|
||||||
|
authenticator.logout('로그아웃', 'sidebar')
|
||||||
|
|
||||||
|
# Navigation options
|
||||||
|
pages = {
|
||||||
|
"데이터 수집": render_data_page,
|
||||||
|
"멀티 팩터 전략": render_multi_factor_page,
|
||||||
|
"슈퍼 퀄리티 전략": render_quality_page,
|
||||||
|
"슈퍼 밸류 모멘텀 전략": render_value_momentum_page
|
||||||
|
}
|
||||||
|
|
||||||
|
# Select page
|
||||||
|
selection = st.sidebar.radio("메뉴", list(pages.keys()))
|
||||||
|
|
||||||
|
# Render the selected page
|
||||||
|
pages[selection]()
|
||||||
|
|
||||||
|
# Footer
|
||||||
|
st.sidebar.markdown("---")
|
||||||
|
st.sidebar.info(
|
||||||
|
"© 2023-2025 콴트 매니저\n\n"
|
||||||
|
"한국 주식 시장을 위한 퀀트 투자 도구"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
st.error("인증 설정 파일을 찾을 수 없습니다. config/credentials.yaml 파일을 확인하세요.")
|
||||||
|
st.info(f"찾을 파일 경로: {config_path}")
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
10
src/config/credentials.yaml
Normal file
10
src/config/credentials.yaml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
cookie:
|
||||||
|
expiry_days: 30
|
||||||
|
key: 37A93A1A98AC9E896CBB21DD1F84FABC
|
||||||
|
name: quant_manager_auth
|
||||||
|
credentials:
|
||||||
|
usernames:
|
||||||
|
zephyrdark:
|
||||||
|
email: zephyrdark@gamil.com
|
||||||
|
name: ayuriel
|
||||||
|
password: $2b$12$nqVU.Mw8lErQU59TxuGWLebfImar1O9zirjpc.waN/3LNYzc2o4.6
|
||||||
51
src/config/generate_credentials.py
Normal file
51
src/config/generate_credentials.py
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
"""
|
||||||
|
Generate a credentials.yaml file with hashed passwords for streamlit-authenticator.
|
||||||
|
This script should be run once to set up authentication credentials.
|
||||||
|
"""
|
||||||
|
import yaml
|
||||||
|
import streamlit_authenticator as stauth
|
||||||
|
from yaml.loader import SafeLoader
|
||||||
|
|
||||||
|
# Create credentials
|
||||||
|
usernames = ["zephyrdark"]
|
||||||
|
names = ["ayuriel"]
|
||||||
|
passwords = [""] # Replace with secure passwords
|
||||||
|
|
||||||
|
# Hash passwords
|
||||||
|
try:
|
||||||
|
# For newer versions of streamlit-authenticator
|
||||||
|
hashed_passwords = [stauth.Hasher([passwords[0]]).generate()[0]]
|
||||||
|
except:
|
||||||
|
# For older versions of streamlit-authenticator
|
||||||
|
hashed_passwords = [stauth.Hasher().hash(passwords[0])]
|
||||||
|
|
||||||
|
# Create config dictionary
|
||||||
|
config = {
|
||||||
|
'credentials': {
|
||||||
|
'usernames': {}
|
||||||
|
},
|
||||||
|
'cookie': {
|
||||||
|
'name': 'quant_manager_auth',
|
||||||
|
'key': '37A93A1A98AC9E896CBB21DD1F84FABC', # Key should be a string that is 32 bytes long
|
||||||
|
'expiry_days': 30
|
||||||
|
},
|
||||||
|
'preauthorized': {
|
||||||
|
'emails': []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Add user credentials
|
||||||
|
for i, username in enumerate(usernames):
|
||||||
|
config['credentials']['usernames'][username] = {
|
||||||
|
'name': names[i],
|
||||||
|
'password': hashed_passwords[i],
|
||||||
|
'email': f"{username}@gamil.com" # Replace with actual emails if needed
|
||||||
|
}
|
||||||
|
|
||||||
|
# Write to YAML file
|
||||||
|
with open('credentials.yaml', 'w') as file:
|
||||||
|
yaml.dump(config, file, default_flow_style=False)
|
||||||
|
|
||||||
|
print("Credentials file has been generated successfully!")
|
||||||
|
print("Update the usernames, passwords, and other information as needed.")
|
||||||
|
print("Then move the credentials.yaml file to the config directory.")
|
||||||
75
src/config/update_password.py
Normal file
75
src/config/update_password.py
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
"""
|
||||||
|
Update password for an existing user in credentials.yaml.
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import yaml
|
||||||
|
import getpass
|
||||||
|
import streamlit_authenticator as stauth
|
||||||
|
from yaml.loader import SafeLoader
|
||||||
|
|
||||||
|
def main():
|
||||||
|
# Get the path to credentials.yaml
|
||||||
|
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
config_path = os.path.join(script_dir, 'credentials.yaml')
|
||||||
|
|
||||||
|
# Check if credentials file exists
|
||||||
|
if not os.path.exists(config_path):
|
||||||
|
print(f"Error: {config_path} not found.")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Load the credentials file
|
||||||
|
with open(config_path, 'r') as file:
|
||||||
|
config = yaml.load(file, Loader=SafeLoader)
|
||||||
|
|
||||||
|
# Get all usernames
|
||||||
|
usernames = list(config['credentials']['usernames'].keys())
|
||||||
|
|
||||||
|
if not usernames:
|
||||||
|
print("No users found in credentials file.")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Print available users
|
||||||
|
print("Available users:")
|
||||||
|
for i, username in enumerate(usernames, 1):
|
||||||
|
print(f"{i}. {username}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Get user selection
|
||||||
|
choice = int(input("\nSelect user number to update password: "))
|
||||||
|
if choice < 1 or choice > len(usernames):
|
||||||
|
print("Invalid selection.")
|
||||||
|
return
|
||||||
|
|
||||||
|
selected_username = usernames[choice - 1]
|
||||||
|
|
||||||
|
# Get new password
|
||||||
|
new_password = getpass.getpass("Enter new password: ")
|
||||||
|
confirm_password = getpass.getpass("Confirm new password: ")
|
||||||
|
|
||||||
|
if new_password != confirm_password:
|
||||||
|
print("Passwords do not match.")
|
||||||
|
return
|
||||||
|
|
||||||
|
if not new_password:
|
||||||
|
print("Password cannot be empty.")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Hash the new password
|
||||||
|
hashed_password = stauth.Hasher([new_password]).generate()[0]
|
||||||
|
|
||||||
|
# Update the password
|
||||||
|
config['credentials']['usernames'][selected_username]['password'] = hashed_password
|
||||||
|
|
||||||
|
# Save the updated config
|
||||||
|
with open(config_path, 'w') as file:
|
||||||
|
yaml.dump(config, file, default_flow_style=False)
|
||||||
|
|
||||||
|
print(f"Password updated successfully for {selected_username}.")
|
||||||
|
|
||||||
|
except (ValueError, IndexError):
|
||||||
|
print("Invalid input.")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"An error occurred: {e}")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Loading…
x
Reference in New Issue
Block a user