First pass at vite ui
This commit is contained in:
parent
4880ef61cf
commit
1f94855504
16 changed files with 276 additions and 42 deletions
1
Pipfile
1
Pipfile
|
|
@ -12,6 +12,7 @@ peewee = "*"
|
|||
peewee-migrate = "*"
|
||||
flask = "*"
|
||||
flask-login = "*"
|
||||
flask-vite = "*"
|
||||
|
||||
[dev-packages]
|
||||
|
||||
|
|
|
|||
11
Pipfile.lock
generated
11
Pipfile.lock
generated
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "a598b3b97fc476eba41807a363b2528dfd7ec66f33b33f6b0703c004aaad22f9"
|
||||
"sha256": "cbf2aa09aa805f5a444be073d6495925f6d37d04290e62140fb65bc02a170a75"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {
|
||||
|
|
@ -201,6 +201,15 @@
|
|||
"markers": "python_version >= '3.7'",
|
||||
"version": "==0.6.3"
|
||||
},
|
||||
"flask-vite": {
|
||||
"hashes": [
|
||||
"sha256:1681765668fa9dc7ca985f5ee395b1f7ed1d4db861407cccbcfdaf0346d4112c",
|
||||
"sha256:5267abdd0bb4c8c9bb5b1a44b1260dc015a3f7e39befcf120babedc386d43558"
|
||||
],
|
||||
"index": "pypi",
|
||||
"markers": "python_version >= '3.9' and python_version < '4.0'",
|
||||
"version": "==0.6.1"
|
||||
},
|
||||
"frozenlist": {
|
||||
"hashes": [
|
||||
"sha256:0325024fe97f94c41c08872db482cf8ac4800d80e79222c6b0b7b162d5b13686",
|
||||
|
|
|
|||
|
|
@ -30,6 +30,8 @@ class DomSubUsers(BaseModel):
|
|||
class OrdersPool(BaseModel):
|
||||
name = TextField()
|
||||
|
||||
user = ForeignKeyField(column_name='user_id', field='id', model=User, backref='orders_pools')
|
||||
|
||||
class Meta:
|
||||
table_name = 'orders_pool'
|
||||
|
||||
|
|
@ -38,6 +40,8 @@ class Order(BaseModel):
|
|||
weight = IntegerField()
|
||||
repeat = FloatField()
|
||||
|
||||
pool = ForeignKeyField(column_name='orders_pool_id', field='id', model=OrdersPool, backref='orders')
|
||||
|
||||
class Meta:
|
||||
table_name = 'order'
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import datetime
|
||||
|
||||
from .models import database, User, DomSubUsers, Repeat, SkipDay, OrderStatus, PunishmentStatus
|
||||
from .models import database, User, OrdersPool, DomSubUsers, Repeat, SkipDay, OrderStatus, PunishmentStatus
|
||||
|
||||
def initdb():
|
||||
database.connect()
|
||||
|
|
@ -22,6 +22,9 @@ def user_get(username):
|
|||
telegram_username=username
|
||||
)
|
||||
|
||||
def orders_pool_list(user_id):
|
||||
return OrdersPool.select().where(OrdersPool.user_id == user_id)
|
||||
|
||||
def domsubusers_add(sub, dom):
|
||||
return DomSubUsers.create(
|
||||
sub=sub,
|
||||
|
|
@ -34,6 +37,9 @@ def domsubusers_delete(sub, dom):
|
|||
).delete()
|
||||
return q.execute()
|
||||
|
||||
def domsubusers_list(dom):
|
||||
return DomSubUsers.select().where(DomSubUsers.dom == dom)
|
||||
|
||||
def repeat_get():
|
||||
try:
|
||||
return Repeat.get()
|
||||
|
|
|
|||
42
flask/api.py
Normal file
42
flask/api.py
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
from flask import Blueprint, jsonify, abort
|
||||
from flask_login import current_user
|
||||
from db.queries import user_get, domsubusers_list, orders_pool_list
|
||||
|
||||
api = Blueprint('api', __name__)
|
||||
|
||||
@api.route('/subs')
|
||||
def subs():
|
||||
return jsonify(
|
||||
[
|
||||
{"sub_username": dsu.sub.telegram_username}
|
||||
for dsu
|
||||
in domsubusers_list(current_user.db_user)
|
||||
]
|
||||
)
|
||||
|
||||
@api.route('/subs/<username>/orders')
|
||||
def sub_orders(username):
|
||||
try:
|
||||
sub = user_get(username)
|
||||
except:
|
||||
abort(500)
|
||||
return
|
||||
|
||||
if sub.telegram_username not in [dsu.sub.telegram_username for dsu in domsubusers_list(current_user.db_user)]:
|
||||
abort(500)
|
||||
return
|
||||
|
||||
return jsonify([
|
||||
{
|
||||
'id': op.id,
|
||||
'name': op.name,
|
||||
'orders': [{
|
||||
'id': order.id,
|
||||
'name': order.name,
|
||||
'weight': order.weight,
|
||||
'repeat': order.repeat
|
||||
} for order in op.orders]
|
||||
}
|
||||
for op
|
||||
in orders_pool_list(sub.id)
|
||||
])
|
||||
13
flask/app.py
13
flask/app.py
|
|
@ -6,10 +6,10 @@ from flask import Flask, render_template, request, redirect, flash
|
|||
from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required, current_user
|
||||
import hashlib
|
||||
import hmac
|
||||
import base64
|
||||
|
||||
from settings import FLASK_SECRET_KEY, TELEGRAM_API_TOKEN, TELEGRAM_BOT_NAME, TELEGRAM_BOT_DOMAIN
|
||||
from db.queries import user_get
|
||||
from api import api
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
|
|
@ -34,17 +34,20 @@ def load_user(user_id):
|
|||
except:
|
||||
return None
|
||||
|
||||
app.register_blueprint(api, url_prefix='/api')
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
if not current_user.is_authenticated:
|
||||
data = {'bot_name': TELEGRAM_BOT_NAME, 'bot_damin': TELEGRAM_BOT_DOMAIN}
|
||||
return render_template('index.html', data = data)
|
||||
else:
|
||||
return redirect('/dashboard')
|
||||
return redirect('/dashboard/')
|
||||
|
||||
@app.route('/dashboard')
|
||||
@app.route('/dashboard/', defaults={'path': ''})
|
||||
@app.route('/dashboard/<path:path>')
|
||||
@login_required
|
||||
def dashboard():
|
||||
def dashboard(path):
|
||||
return render_template('dashboard.html')
|
||||
|
||||
@app.route('/logout')
|
||||
|
|
@ -90,7 +93,7 @@ def login():
|
|||
return redirect('/')
|
||||
|
||||
|
||||
return redirect('/dashboard')
|
||||
return redirect('/dashboard/')
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(host='0.0.0.0', debug=True, port=8080)
|
||||
|
|
@ -1,35 +1,16 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<!-- Required meta tags -->
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
|
||||
<title>Gear Orders Bot</title>
|
||||
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 18pt;
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: grid;
|
||||
justify-items: center;
|
||||
align-content: center;
|
||||
height: 80vh;
|
||||
}
|
||||
</style>
|
||||
<script type="module" src="/v/@vite/client"></script>
|
||||
<script type="module" src="/v/src/main.tsx"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="grid">
|
||||
<h1>Dashboard</h1>
|
||||
<p>Successfuly logged in and the login verified</p>
|
||||
</div>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<!-- Required meta tags -->
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
|
||||
|
|
|
|||
4
flask/vite/.gitignore
vendored
Normal file
4
flask/vite/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
/node_modules
|
||||
/dist
|
||||
/package-lock.json
|
||||
/yarn.lock
|
||||
11
flask/vite/index.html
Normal file
11
flask/vite/index.html
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Used only to please vite</title>
|
||||
</head>
|
||||
<body>
|
||||
<script type="module" src="/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
24
flask/vite/package.json
Normal file
24
flask/vite/package.json
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"name": "demo-tailwind",
|
||||
"version": "0.0.1",
|
||||
"description": "",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"vite": "^5.0.11"
|
||||
},
|
||||
"dependencies": {
|
||||
"@mantine/core": "^8.3.12",
|
||||
"postcss": "^8.5.6",
|
||||
"react": "^19.2.3",
|
||||
"react-dom": "^19.2.3",
|
||||
"react-router": "^7.12.0",
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
}
|
||||
76
flask/vite/src/SubOrders.tsx
Normal file
76
flask/vite/src/SubOrders.tsx
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
import {
|
||||
Container,
|
||||
Accordion,
|
||||
TextInput,
|
||||
Divider,
|
||||
Input,
|
||||
Slider,
|
||||
} from "@mantine/core";
|
||||
import React from "react";
|
||||
import { Params, useLoaderData, useParams } from "react-router";
|
||||
|
||||
export const subOrdersLoader = async ({
|
||||
params: { username },
|
||||
}: {
|
||||
params: Params<string>;
|
||||
}) => fetch(`/api/subs/${username}/orders`).then((response) => response.json());
|
||||
|
||||
export const SubOrders: React.FC = () => {
|
||||
const { username: sub_username } = useParams();
|
||||
const orders = useLoaderData<
|
||||
{
|
||||
id: number;
|
||||
name: string;
|
||||
orders: {
|
||||
id: number;
|
||||
name: string;
|
||||
weight: number;
|
||||
repeat: number;
|
||||
}[];
|
||||
}[]
|
||||
>();
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<h1>Orders for {sub_username}</h1>
|
||||
<Accordion>
|
||||
{orders
|
||||
? orders.map(({ id, name, orders }) => (
|
||||
<Accordion.Item key={id} value={`${id}`}>
|
||||
<Accordion.Control>{name}</Accordion.Control>
|
||||
<Accordion.Panel>
|
||||
<>
|
||||
<TextInput label="Name" value={name} />
|
||||
{orders.map(
|
||||
({ id: order_id, name: order_name, weight, repeat }) => (
|
||||
<React.Fragment key={order_id}>
|
||||
<Divider my="md" />
|
||||
<TextInput label="Name" value={order_name} />
|
||||
<Input.Wrapper
|
||||
label="Weight"
|
||||
description="Chance this order will be selected"
|
||||
>
|
||||
<Slider domain={[0, 100]} value={weight} />
|
||||
</Input.Wrapper>
|
||||
<Input.Wrapper
|
||||
label="Repeat"
|
||||
description="Percent chance that this order will repeat the next day"
|
||||
>
|
||||
<Slider
|
||||
domain={[0, 1]}
|
||||
step={0.05}
|
||||
value={repeat}
|
||||
/>
|
||||
</Input.Wrapper>
|
||||
</React.Fragment>
|
||||
)
|
||||
)}
|
||||
</>
|
||||
</Accordion.Panel>
|
||||
</Accordion.Item>
|
||||
))
|
||||
: null}
|
||||
</Accordion>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
21
flask/vite/src/SubsList.tsx
Normal file
21
flask/vite/src/SubsList.tsx
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import React from "react";
|
||||
import { Container } from "@mantine/core";
|
||||
import { useLoaderData, Link } from "react-router";
|
||||
|
||||
export const subsListLoader = () =>
|
||||
fetch("/api/subs").then((response) => response.json());
|
||||
|
||||
export const SubsList: React.FC = () => {
|
||||
const subs = useLoaderData<{ sub_username: string }[]>();
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<h1>Subs</h1>
|
||||
{subs.map(({ sub_username }) => (
|
||||
<Link key={sub_username} to={`/dashboard/subs/${sub_username}`}>
|
||||
Orders for {sub_username}
|
||||
</Link>
|
||||
))}
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
42
flask/vite/src/main.tsx
Normal file
42
flask/vite/src/main.tsx
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import { createBrowserRouter } from "react-router";
|
||||
import { RouterProvider } from "react-router/dom";
|
||||
import { createTheme, MantineProvider, Input } from "@mantine/core";
|
||||
|
||||
import "@mantine/core/styles.css";
|
||||
|
||||
import { SubsList, subsListLoader } from "./SubsList";
|
||||
import { SubOrders, subOrdersLoader } from "./SubOrders";
|
||||
|
||||
const theme = createTheme({
|
||||
components: {
|
||||
InputWrapper: Input.Wrapper.extend({
|
||||
defaultProps: {
|
||||
inputWrapperOrder: ["label", "input", "description", "error"],
|
||||
my: "md",
|
||||
},
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
const router = createBrowserRouter([
|
||||
{
|
||||
path: "dashboard",
|
||||
Component: SubsList,
|
||||
loader: subsListLoader,
|
||||
},
|
||||
{
|
||||
path: "dashboard/subs/:username",
|
||||
Component: SubOrders,
|
||||
loader: subOrdersLoader,
|
||||
},
|
||||
]);
|
||||
|
||||
ReactDOM.createRoot(document.getElementById("root")!).render(
|
||||
<React.StrictMode>
|
||||
<MantineProvider theme={theme}>
|
||||
<RouterProvider router={router} />
|
||||
</MantineProvider>
|
||||
</React.StrictMode>
|
||||
);
|
||||
9
flask/vite/vite.config.js
Normal file
9
flask/vite/vite.config.js
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
export default {
|
||||
base: '/v/',
|
||||
|
||||
server: {
|
||||
host: 'orders-dev.johnnygear.net',
|
||||
port: 3000,
|
||||
strictPort: true
|
||||
}
|
||||
};
|
||||
|
|
@ -63,24 +63,26 @@ def migrate(migrator: Migrator, database: pw.Database, *, fake=False):
|
|||
table_name = "dom_sub"
|
||||
indexes = [(('dom_id', 'sub_id'), True)]
|
||||
|
||||
@migrator.create_model
|
||||
class OrdersPool(pw.Model):
|
||||
id = pw.AutoField()
|
||||
name = pw.TextField()
|
||||
user = pw.ForeignKeyField(column_name='user_id', field='id', model=migrator.orm['user'])
|
||||
|
||||
class Meta:
|
||||
table_name = "orders_pool"
|
||||
|
||||
@migrator.create_model
|
||||
class Order(pw.Model):
|
||||
id = pw.AutoField()
|
||||
name = pw.TextField()
|
||||
weight = pw.IntegerField()
|
||||
repeat = pw.FloatField()
|
||||
pool = pw.ForeignKeyField(column_name='orders_pool_id', field='id', model=migrator.orm['orders_pool'])
|
||||
|
||||
class Meta:
|
||||
table_name = "order"
|
||||
|
||||
@migrator.create_model
|
||||
class OrdersPool(pw.Model):
|
||||
id = pw.AutoField()
|
||||
name = pw.TextField()
|
||||
|
||||
class Meta:
|
||||
table_name = "orders_pool"
|
||||
|
||||
@migrator.create_model
|
||||
class PunishmentStatus(pw.Model):
|
||||
id = pw.AutoField()
|
||||
|
|
@ -135,10 +137,10 @@ def rollback(migrator: Migrator, database: pw.Database, *, fake=False):
|
|||
|
||||
migrator.remove_model('punishment_status')
|
||||
|
||||
migrator.remove_model('orders_pool')
|
||||
|
||||
migrator.remove_model('order')
|
||||
|
||||
migrator.remove_model('orders_pool')
|
||||
|
||||
migrator.remove_model('dom_sub')
|
||||
|
||||
migrator.remove_model('user')
|
||||
|
|
|
|||
Loading…
Reference in a new issue