Compare commits
6 Commits
61d12f4972
...
feature/fr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
31f1fcecc4 | ||
|
|
67e7ab6f94 | ||
|
|
913e23b966 | ||
|
|
7219fbdada | ||
|
|
92fc020d94 | ||
|
|
dca8a0dbcf |
13
.dockerignore
Normal file
13
.dockerignore
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
venv/
|
||||||
|
backend/venv/
|
||||||
|
**/__pycache__/
|
||||||
|
*.pyc
|
||||||
|
*.pyo
|
||||||
|
*.db
|
||||||
|
.git/
|
||||||
|
.gitignore
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
data/
|
||||||
|
*.xls
|
||||||
|
*.xlsx
|
||||||
15
Dockerfile
Normal file
15
Dockerfile
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
FROM python:3.11-slim
|
||||||
|
|
||||||
|
COPY requirements.txt /app_src/requirements.txt
|
||||||
|
RUN pip install --no-cache-dir -r /app_src/requirements.txt
|
||||||
|
|
||||||
|
COPY backend/ /app_src/backend/
|
||||||
|
COPY frontend/ /app_src/frontend/
|
||||||
|
COPY docker-entrypoint.sh /app_src/docker-entrypoint.sh
|
||||||
|
RUN chmod +x /app_src/docker-entrypoint.sh
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
EXPOSE 8000
|
||||||
|
|
||||||
|
ENTRYPOINT ["/app_src/docker-entrypoint.sh"]
|
||||||
@@ -1,10 +1,13 @@
|
|||||||
# backend/database.py
|
# backend/database.py
|
||||||
|
import os
|
||||||
|
|
||||||
from sqlalchemy import create_engine
|
from sqlalchemy import create_engine
|
||||||
from sqlalchemy.orm import sessionmaker, scoped_session
|
from sqlalchemy.orm import sessionmaker, scoped_session
|
||||||
|
|
||||||
SQLALCHEMY_DATABASE_URL = "sqlite:///./app.db" # или PostgreSQL URL
|
SQLALCHEMY_DATABASE_URL = os.getenv("DATABASE_URL", "sqlite:///./app.db")
|
||||||
|
|
||||||
engine = create_engine(SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False})
|
connect_args = {"check_same_thread": False} if SQLALCHEMY_DATABASE_URL.startswith("sqlite") else {}
|
||||||
|
engine = create_engine(SQLALCHEMY_DATABASE_URL, connect_args=connect_args)
|
||||||
SessionLocal = scoped_session(sessionmaker(autocommit=False, autoflush=False, bind=engine))
|
SessionLocal = scoped_session(sessionmaker(autocommit=False, autoflush=False, bind=engine))
|
||||||
|
|
||||||
def get_db():
|
def get_db():
|
||||||
|
|||||||
27
backend/init_db.py
Normal file
27
backend/init_db.py
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
from sqlalchemy.orm import sessionmaker
|
||||||
|
|
||||||
|
from backend.models import Base, User
|
||||||
|
from backend.database import engine
|
||||||
|
from backend.security import get_password_hash
|
||||||
|
|
||||||
|
|
||||||
|
def init_db():
|
||||||
|
Base.metadata.create_all(bind=engine)
|
||||||
|
|
||||||
|
Session = sessionmaker(bind=engine)
|
||||||
|
db = Session()
|
||||||
|
try:
|
||||||
|
if not db.query(User).filter(User.username == 'admin').first():
|
||||||
|
db.add(User(
|
||||||
|
username='admin',
|
||||||
|
password_hash=get_password_hash('admin'),
|
||||||
|
role='admin',
|
||||||
|
))
|
||||||
|
db.commit()
|
||||||
|
print("Created default admin user (login: admin / password: admin)")
|
||||||
|
finally:
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
init_db()
|
||||||
10
docker-compose.yml
Normal file
10
docker-compose.yml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
services:
|
||||||
|
app:
|
||||||
|
build: .
|
||||||
|
ports:
|
||||||
|
- "8000:8000"
|
||||||
|
volumes:
|
||||||
|
- ./appdata:/app
|
||||||
|
environment:
|
||||||
|
- DATABASE_URL=sqlite:////app/data/app.db
|
||||||
|
restart: unless-stopped
|
||||||
11
docker-entrypoint.sh
Normal file
11
docker-entrypoint.sh
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Sync app code from image on every start (keeps code fresh after rebuilds)
|
||||||
|
rm -rf /app/backend /app/frontend
|
||||||
|
cp -r /app_src/backend /app_src/frontend /app/
|
||||||
|
|
||||||
|
mkdir -p /app/data
|
||||||
|
python -m backend.init_db
|
||||||
|
|
||||||
|
exec uvicorn backend.main:app --host 0.0.0.0 --port 8000
|
||||||
@@ -38,6 +38,38 @@ button {
|
|||||||
background-color: #E07D54;
|
background-color: #E07D54;
|
||||||
margin-top: 5px;
|
margin-top: 5px;
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
|
padding: 6px 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
padding: 8px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-sm {
|
||||||
|
padding: 5px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background-color: #E07D54;
|
||||||
|
border-color: #E07D54;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover, .btn-primary:focus, .btn-primary:active {
|
||||||
|
background-color: #c86a3d;
|
||||||
|
border-color: #c86a3d;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-outline-primary {
|
||||||
|
color: #E07D54;
|
||||||
|
border-color: #E07D54;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-outline-primary:hover, .btn-outline-primary:focus, .btn-outline-primary:active {
|
||||||
|
background-color: #E07D54;
|
||||||
|
border-color: #E07D54;
|
||||||
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card {
|
.card {
|
||||||
@@ -177,13 +209,13 @@ td.inv-col, th.inv-col {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.table {
|
.table {
|
||||||
border: 1px solid #000000;
|
border: 1px solid #E07D54;
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
font-size: 12pt;
|
font-size: 12pt;
|
||||||
}
|
}
|
||||||
|
|
||||||
.table th, .table td {
|
.table th, .table td {
|
||||||
border: 1px solid #000000;
|
border: 1px solid #E07D54;
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
font-size: 12pt;
|
font-size: 12pt;
|
||||||
word-break: normal;
|
word-break: normal;
|
||||||
@@ -199,19 +231,19 @@ td.inv-col, th.inv-col {
|
|||||||
}
|
}
|
||||||
|
|
||||||
table.rs-table-bordered {
|
table.rs-table-bordered {
|
||||||
border: 1px solid #000000;
|
border: 1px solid #E07D54;
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
font-size: 14pt;
|
font-size: 14pt;
|
||||||
}
|
}
|
||||||
|
|
||||||
table.rs-table-bordered > thead > tr > th {
|
table.rs-table-bordered > thead > tr > th {
|
||||||
border: 1px solid #000000;
|
border: 1px solid #E07D54;
|
||||||
padding: 2px;
|
padding: 2px;
|
||||||
font-size: 14pt;
|
font-size: 14pt;
|
||||||
}
|
}
|
||||||
|
|
||||||
table.rs-table-bordered > tbody > tr > td {
|
table.rs-table-bordered > tbody > tr > td {
|
||||||
border: 1px solid #000000;
|
border: 1px solid #E07D54;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
font-size: 14pt;
|
font-size: 14pt;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user