Projekt

Obecné

Profil

« Předchozí | Další » 

Revize 2fe004b1

Přidáno uživatelem Matěj Zeman před více než 2 roky(ů)

re #9708 updated requirements. Added logout and template versions for host users.

Zobrazit rozdíly:

server/requirements.txt
5 5
psycopg2-binary==2.8.6
6 6
jinja2==3.1.1
7 7
python-multipart==0.0.5
8
fastapi-jwt-auth=0.5.0
8
fastapi-jwt-auth==0.5.0
server/sql_app/api/auth.py
1

  
2

  
3
from fastapi import Depends, APIRouter, Form
4
from fastapi import Request
5
from fastapi.responses import HTMLResponse
6
from fastapi.templating import Jinja2Templates
7
from fastapi.responses import HTMLResponse
8
from fastapi_jwt_auth import AuthJWT
9
from pydantic import BaseModel
10

  
11

  
12
# Path to html templates used in this file
13
templates = Jinja2Templates(directory="templates/auth")
14

  
15
# prefix used for all endpoints in this file
16
auth = APIRouter(prefix="")
17

  
18

  
19

  
20
class Settings(BaseModel):
21
    authjwt_secret_key: str = "secret"
22
    # Configure application to store and get JWT from cookies
23
    authjwt_token_location: set = {"cookies"}
24
    # Disable CSRF Protection for this example. default is True
25
    authjwt_cookie_csrf_protect: bool = False
26

  
27

  
28
@AuthJWT.load_config
29
def get_config():
30
    return Settings()
31

  
32

  
33
fake_users_db = {
34
    "admin": {
35
        "username": "admin",
36
        "password": "admin"
37
    }
38
}
39

  
40

  
41
@auth.get("/login", response_class=HTMLResponse)
42
async def login_get(request: Request):
43
    return templates.TemplateResponse("login.html", {"request": request})
44

  
45

  
46
@auth.post("/login", response_class=HTMLResponse)
47
async def login(username: str = Form(...), password: str = Form(...), Authorize: AuthJWT = Depends()):
48
    user_dict = fake_users_db.get(username)
49

  
50
    if user_dict != None:
51
        if user_dict["username"] == username and user_dict["password"] == password:
52
            access_token = Authorize.create_access_token(subject="admin", expires_time=False)
53
            refresh_token = Authorize.create_refresh_token(subject="admin", expires_time=False)
54
        else:
55
            access_token = Authorize.create_access_token(subject="host", expires_time=False)
56
            refresh_token = Authorize.create_refresh_token(subject="host", expires_time=False)
57
    else:
58
        access_token = Authorize.create_access_token(subject="host", expires_time=False)
59
        refresh_token = Authorize.create_refresh_token(subject="host", expires_time=False)
60

  
61
    # Set the JWT cookies in the response
62
    Authorize.set_access_cookies(access_token)
63
    Authorize.set_refresh_cookies(refresh_token)
64
    return """
65
    <html>
66
        <head>
67
            <title>Login</title>
68
        </head>
69
        <body>
70
            <h1>Logged in</h1>
71
            <form action="/logs-web" method="get">
72
                <input type="submit" value="Back" />
73
            </form>
74
        </body>
75
    </html>
76
    """
77

  
78

  
79
@auth.post('/refresh')
80
def refresh(Authorize: AuthJWT = Depends()):
81
    Authorize.jwt_refresh_token_required()
82

  
83
    current_user = Authorize.get_jwt_subject()
84
    new_access_token = Authorize.create_access_token(subject=current_user)
85
    # Set the JWT cookies in the response
86
    Authorize.set_access_cookies(new_access_token)
87
    return {"msg": "The token has been refresh"}
88

  
89

  
90
@auth.get('/logout', response_class=HTMLResponse)
91
def logout(Authorize: AuthJWT = Depends()):
92
    """
93
    Because the JWT are stored in an httponly cookie now, we cannot
94
    log the user out by simply deleting the cookies in the frontend.
95
    We need the backend to send us a response to delete the cookies.
96
    """
97
    Authorize.jwt_optional()
98

  
99
    Authorize.unset_jwt_cookies()
100
    return """
101
        <html>
102
            <head>
103
                <title>Logout</title>
104
            </head>
105
            <body>
106
                <h1>Logged Out</h1>
107
                <form action="/logs-web" method="get">
108
                    <input type="submit" value="Back" />
109
                </form>
110
            </body>
111
        </html>
112
        """
server/sql_app/api/devices_web.py
2 2

  
3 3
from fastapi import Depends, APIRouter, Form
4 4
from fastapi import Request
5
from fastapi.responses import HTMLResponse
5
from fastapi.responses import HTMLResponse, RedirectResponse
6 6
from fastapi.templating import Jinja2Templates
7 7
from fastapi_jwt_auth import AuthJWT
8 8
from pydantic import BaseModel
9 9
from sqlalchemy.orm import Session
10

  
10
from sql_app.api.auth import fake_users_db
11 11
from sql_app import crud, models
12 12
from ..database import SessionLocal, engine
13 13

  
......
20 20
device_web = APIRouter(prefix="")
21 21

  
22 22

  
23

  
24
class Settings(BaseModel):
25
    authjwt_secret_key: str = "secret"
26
    # Configure application to store and get JWT from cookies
27
    authjwt_token_location: set = {"cookies"}
28
    # Disable CSRF Protection for this example. default is True
29
    authjwt_cookie_csrf_protect: bool = False
30

  
31

  
32
@AuthJWT.load_config
33
def get_config():
34
    return Settings()
35

  
36

  
37
fake_users_db = {
38
    "admin": {
39
        "username": "admin",
40
        "password": "admin"
41
    },
42
    "editor": {
43
        "username": "editor",
44
        "password": "editor"
45
    },
46
}
47

  
48

  
49 23
# Dependency
50 24
def get_db():
51 25
    db = SessionLocal()
......
55 29
        db.close()
56 30

  
57 31

  
58
@device_web.get("/token", response_class=HTMLResponse)
59
async def login_get(request: Request):
60
    return templates.TemplateResponse("login.html", {"request": request})
61

  
62

  
63
@device_web.post("/token", response_class=HTMLResponse)
64
async def login(username: str = Form(...), password: str = Form(...), Authorize: AuthJWT = Depends()):
65
    user_dict = fake_users_db.get(username)
66

  
67
    access_token = Authorize.create_access_token(subject=username, expires_time=False)
68
    refresh_token = Authorize.create_refresh_token(subject=username, expires_time=False)
69

  
70
    # Set the JWT cookies in the response
71
    Authorize.set_access_cookies(access_token)
72
    Authorize.set_refresh_cookies(refresh_token)
73
    return """
74
    <html>
75
        <head>
76
            <title>Some HTML in here</title>
77
        </head>
78
        <body>
79
            <h1>Look ma! HTML!</h1>
80
            <form action="/devices-web" method="get">
81
                <input type="submit" value="Login" />
82
            </form>
83
        </body>
84
    </html>
85
    """
86

  
87

  
88
@device_web.post('/refresh')
89
def refresh(Authorize: AuthJWT = Depends()):
90
    Authorize.jwt_refresh_token_required()
91

  
92
    current_user = Authorize.get_jwt_subject()
93
    new_access_token = Authorize.create_access_token(subject=current_user)
94
    # Set the JWT cookies in the response
95
    Authorize.set_access_cookies(new_access_token)
96
    return {"msg": "The token has been refresh"}
97

  
98

  
99
@device_web.get('/logout', response_class=HTMLResponse)
100
def logout(Authorize: AuthJWT = Depends()):
101
    """
102
    Because the JWT are stored in an httponly cookie now, we cannot
103
    log the user out by simply deleting the cookies in the frontend.
104
    We need the backend to send us a response to delete the cookies.
105
    """
106
    Authorize.jwt_required()
107

  
108
    Authorize.unset_jwt_cookies()
109
    return """
110
        <html>
111
            <head>
112
                <title>Some HTML in here</title>
113
            </head>
114
            <body>
115
                <h1>Look ma! HTML!</h1>
116
                <form action="/devices-web" method="get">
117
                    <input type="submit" value="Login" />
118
                </form>
119
            </body>
120
        </html>
121
        """
122

  
123

  
124 32
@device_web.get("/devices-web", response_class=HTMLResponse)
125 33
async def read_devices(request: Request, skip: int = 0, limit: int = 100, db: Session = Depends(get_db),
126 34
                       Authorize: AuthJWT = Depends()):
......
136 44
    for i in range(0, len(devices)):
137 45
        statuses.append(devices[i].logs[len(devices[i].logs) - 1].status)
138 46
    licenses = crud.get_licenses(db, skip=skip, limit=limit)
139
    return templates.TemplateResponse("devices.html", {"request": request, "devs": len(devices), "devices": devices,
140
                                                       "statuses": statuses, "licenses": licenses})
47
    if current_user == "admin":
48
        return templates.TemplateResponse("devices.html", {"request": request, "devs": len(devices), "devices": devices,
49
                                                           "statuses": statuses, "licenses": licenses})
50
    else:
51
        return templates.TemplateResponse("devices_normal.html", {"request": request, "devs": len(devices), "devices": devices,
52
                                                           "statuses": statuses, "licenses": licenses})
141 53

  
142 54

  
143 55
@device_web.post("/devices-web", response_class=HTMLResponse)
......
177 89

  
178 90

  
179 91
@device_web.post("/devices-web/{device_id}", response_class=HTMLResponse)
180
async def connect_post(request: Request, device_id: int, lic: str = Form(...), skip: int = 0, limit: int = 100,
181
                       db: Session = Depends(get_db)):
92
async def connect_post(device_id: int, lic: str = Form(...), db: Session = Depends(get_db)):
182 93
    """
183 94
    Endpoint called from template for connecting device with license. Adds entry to devices_licenses
184 95
    table and returns template with all devices in database
185 96
    """
186 97
    crud.create_device_license(db, device_id, int(lic), datetime.now())
187
    devices = crud.get_devices(db, skip=skip, limit=limit)
188
    statuses = []
189
    # adding state for each device in list
190
    for i in range(0, len(devices)):
191
        statuses.append(devices[i].logs[len(devices[i].logs) - 1].status)
192
    licenses = crud.get_licenses(db, skip=skip, limit=limit)
193
    return templates.TemplateResponse("devices.html", {"request": request, "devs": len(devices), "devices": devices,
194
                                                       "statuses": statuses, "licenses": licenses})
98
    return RedirectResponse("/devices-web")
server/sql_app/api/licenses_web.py
6 6
from sql_app import crud, models, schemas
7 7
from ..database import SessionLocal, engine
8 8
from fastapi import FastAPI, Request
9
from fastapi.responses import HTMLResponse
9
from fastapi.responses import HTMLResponse, RedirectResponse
10 10
from fastapi.staticfiles import StaticFiles
11 11
from fastapi.templating import Jinja2Templates
12 12

  
......
47 47

  
48 48

  
49 49
@licenses_web.post("/licenses-web", response_class=HTMLResponse)
50
def create_license(request: Request, name: str = Form(...), expdate: date = Form(...), skip: int = 0, limit: int = 100,
51
                   db: Session = Depends(get_db)):
50
def create_license(name: str = Form(...), expdate: date = Form(...), db: Session = Depends(get_db)):
52 51
    """
53 52
    Endpoint called from create license form. Creates new license and returns template with all licenses in database
54 53
    """
55 54
    db_license = crud.create_license(db, name, expdate)
56 55
    if db_license is None:
57 56
        print("something went wrong")
58
    devices = crud.get_devices(db, skip=skip, limit=limit)
59
    statuses = []
60
    for i in range(0, len(devices)):
61
        statuses.append(devices[i].logs[len(devices[i].logs) - 1].status)
62
    licenses = crud.get_licenses(db, skip=skip, limit=limit)
63
    return device_templates.TemplateResponse("devices.html", {"request": request, "devs": len(devices), "devices": devices,
64
                                                       "statuses": statuses, "licenses": licenses})
57
    return RedirectResponse("/devices-web")
server/sql_app/api/pcs_web.py
4 4
from sql_app import crud, models, schemas
5 5
from ..database import SessionLocal, engine
6 6
from fastapi import FastAPI, Request
7
from fastapi.responses import HTMLResponse
7
from fastapi.responses import HTMLResponse, RedirectResponse
8
from fastapi_jwt_auth import AuthJWT
8 9
from fastapi.staticfiles import StaticFiles
9 10
from fastapi.templating import Jinja2Templates
10 11

  
......
27 28

  
28 29

  
29 30
@pcs_web.get("/pcs-web", response_class=HTMLResponse)
30
async def read_pcs(request: Request, skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
31
async def read_pcs(request: Request, skip: int = 0, limit: int = 100, db: Session = Depends(get_db),
32
                   Authorize: AuthJWT = Depends()):
31 33
    """
32 34
    Returns template with all pcs currently saved in database
33 35
    """
36
    Authorize.jwt_optional()
37
    current_user = Authorize.get_jwt_subject()
34 38
    pcs = crud.get_pcs(db, skip=skip, limit=limit)
35
    return templates.TemplateResponse("pcs.html", {"request": request, "pcs": pcs})
39
    if current_user == "admin":
40
        return templates.TemplateResponse("pcs.html", {"request": request, "pcs": pcs})
41
    else:
42
        return templates.TemplateResponse("pcs_normal.html", {"request": request, "pcs": pcs})
36 43

  
37 44

  
38 45
@pcs_web.get("/pc-team/{pc_id}", response_class=HTMLResponse)
......
47 54

  
48 55

  
49 56
@pcs_web.post("/pcs-web/{pc_id}", response_class=HTMLResponse)
50
async def connect_post(request: Request, pc_id: int, team: str = Form(...), skip: int = 0, limit: int = 100,
51
                       db: Session = Depends(get_db)):
57
async def connect_post(pc_id: int, team: str = Form(...), db: Session = Depends(get_db)):
52 58
    """
53 59
    Endpoint called from within form for connecting pc with team. Updates certain pc with new team.
54 60
    """
55 61
    old_pc = crud.update_pc(db, pc_id, team)
56
    pcs = crud.get_pcs(db, skip=skip, limit=limit)
57
    return templates.TemplateResponse("pcs.html", {"request": request, "pcs": pcs})
62
    RedirectResponse("/pcs-web")
server/sql_app/api/teams_web.py
5 5
from sql_app import crud, models, schemas
6 6
from ..database import SessionLocal, engine
7 7
from fastapi import FastAPI, Request
8
from fastapi.responses import HTMLResponse
8
from fastapi.responses import HTMLResponse, RedirectResponse
9
from fastapi_jwt_auth import AuthJWT
9 10
from fastapi.staticfiles import StaticFiles
10 11
from fastapi.templating import Jinja2Templates
11 12

  
......
28 29

  
29 30

  
30 31
@teams_web.get("/teams-web", response_class=HTMLResponse)
31
async def read_devices(request: Request, skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
32
async def read_devices(request: Request, skip: int = 0, limit: int = 100, db: Session = Depends(get_db),
33
                       Authorize: AuthJWT = Depends()):
32 34
    """
33 35
    Returns template with all teams currently saved in database
34 36
    """
37
    Authorize.jwt_optional()
38
    current_user = Authorize.get_jwt_subject()
35 39
    teams = crud.get_teams(db, skip=skip, limit=limit)
36
    return templates.TemplateResponse("teams.html", {"request": request, "teams": teams})
40
    if current_user == "admin":
41
        return templates.TemplateResponse("teams.html", {"request": request, "teams": teams})
42
    else:
43
        return templates.TemplateResponse("teams_normal.html", {"request": request, "teams": teams})
37 44

  
38 45

  
39 46
@teams_web.get("/team-create", response_class=HTMLResponse)
......
45 52

  
46 53

  
47 54
@teams_web.post("/teams-web", response_class=HTMLResponse)
48
def create_team(request: Request, name: str = Form(...), skip: int = 0, limit: int = 100,
49
                   db: Session = Depends(get_db)):
55
def create_team(name: str = Form(...), db: Session = Depends(get_db)):
50 56
    """
51 57
    Endpoint called from within form for creating new team. Creates new team and returns all teams in database
52 58
    """
53 59
    team = crud.create_team(db, name)
54 60
    if team is None:
55 61
        print("something went wrong")
56
    teams = crud.get_teams(db, skip=skip, limit=limit)
57
    return templates.TemplateResponse("teams.html", {"request": request, "teams": teams})
62
    RedirectResponse("/teams-web")
server/sql_app/main.py
9 9
from sql_app.api.usb_logs_web import usblogs_web
10 10
from sql_app.api.teams import teams
11 11
from sql_app.api.teams_web import teams_web
12
from sql_app.api.auth import auth
12 13
from fastapi import FastAPI
13 14

  
14 15

  
......
27 28
app.include_router(pcs_web)
28 29
app.include_router(teams_web)
29 30
app.include_router(usblogs_web)
31
app.include_router(auth)
30 32

  
31 33
'''
32 34
if __name__ == "__main__":
server/templates/auth/login.html
1
<!DOCTYPE html>
2
<html lang="en">
3
<head>
4
    <meta charset="UTF-8">
5
    <title>Login</title>
6
</head>
7
<body>
8
<form action="/login" method="post">
9
  <label for="username">Username:</label><br>
10
  <input type="text" id="username" name="username"><br><br>
11
    <label for="password">Expiration Date</label>
12
  <input type="text" id="password" name="password">
13
  <input type="submit" value="Submit">
14
</form>
15
</body>
16
</html>
server/templates/devices/devices.html
3 3
    <title>Devices Details</title>
4 4
</head>
5 5
<body>
6
<div style='float:left'>
6 7
<form action="" method="get">
7 8
  <label for="view">Choose view:</label>
8 9
  <select id="view" name="view" onchange="this.form.action=this.value;">
......
14 15
  </select>
15 16
  <input type="submit" value="OK">
16 17
</form>
17
<form action="/token" method="get">
18
</div>
19
<div style='float:left'>
20
<form action="/login" method="get">
18 21
    <input type="submit" value="Login" />
19 22
</form>
23
</div>
24
<form action="/logout" method="get">
25
    <input type="submit" value="Logout" />
26
</form>
20 27
<form action="/devices-web" method="post">
21 28
    <label for="lic">License:</label>
22 29
    <input id="lic" name="lic" type="text" list="licenses" value="" placeholder="all">
server/templates/devices/devices_normal.html
1
<html>
2
<head>
3
    <title>Devices Details</title>
4
</head>
5
<body>
6
<div style='float:left'>
7
<form action="" method="get">
8
  <label for="view">Choose view:</label>
9
  <select id="view" name="view" onchange="this.form.action=this.value;">
10
      <option value=""></option>
11
      <option value="/logs-web">Logs</option>
12
      <option value="/devices-web">Devices</option>
13
      <option value="/teams-web">Teams</option>
14
      <option value="/pcs-web">PCs</option>
15
  </select>
16
  <input type="submit" value="OK">
17
</form>
18
</div>
19
<div style='float:left'>
20
<form action="/login" method="get">
21
    <input type="submit" value="Login" />
22
</form>
23
</div>
24
<form action="/logout" method="get">
25
    <input type="submit" value="Logout" />
26
</form>
27
<form action="/devices-web" method="post">
28
    <label for="lic">License:</label>
29
    <input id="lic" name="lic" type="text" list="licenses" value="" placeholder="all">
30
    <datalist id="licenses">
31
        {% for license in licenses %}
32
        <option value="{{license.name}}"></option>
33
        {% endfor %}
34
    </datalist>
35
  <input type="submit" value="Filter">
36
</form>
37
<table>
38
    <TR>
39
        <TH>ID</TH>
40
        <TH>Vendor ID</TH>
41
        <TH>Product ID</TH>
42
        <TH>Serial Number</TH>
43
        <TH>Licenses</TH>
44
        <TH>Status</TH>
45
    </TR>
46
    {% for i in range(devs) %}
47
    <TR>
48
        <TD class="ID">{{devices[i].id}}</TD>
49
        <TD class="Vendor ID">{{devices[i].vendor_id}}</TD>
50
        <TD class="Product ID">{{devices[i].product_id}}</TD>
51
        <TD class="Serial Number">{{devices[i].serial_number}}</TD>
52
        <TD class="License">
53
            {% for lic in devices[i].licenses %}
54
                {{lic.licenses.name}}<BR>
55
            {% endfor %}
56
        </TD>
57
        <TD class="Status">{{statuses[i]}}</TD>
58
    </TR>
59
    {% endfor %}
60
</table>
61
</body>
62
</html>
server/templates/devices/login.html
1
<!DOCTYPE html>
2
<html lang="en">
3
<head>
4
    <meta charset="UTF-8">
5
    <title>Login</title>
6
</head>
7
<body>
8
<form action="/token" method="post">
9
  <label for="username">Username:</label><br>
10
  <input type="text" id="username" name="username"><br><br>
11
    <label for="password">Expiration Date</label>
12
  <input type="text" id="password" name="password">
13
  <input type="submit" value="Submit">
14
</form>
15
</body>
16
</html>
server/templates/licenses/licenses.html
3 3
    <title>Licenses Details</title>
4 4
</head>
5 5
<body>
6
<div style='float:left'>
6 7
<form action="" method="get">
7 8
  <label for="view">Choose view:</label>
8 9
  <select id="view" name="view" onchange="this.form.action=this.value;">
......
14 15
  </select>
15 16
  <input type="submit" value="OK">
16 17
</form>
18
</div>
19
<div style='float:left'>
20
<form action="/login" method="get">
21
    <input type="submit" value="Login" />
22
</form>
23
</div>
24
<form action="/logout" method="get">
25
    <input type="submit" value="Logout" />
26
</form>
17 27
<table>
18 28
    <TR>
19 29
        <TH>ID</TH>
server/templates/pcs/pcs.html
3 3
    <title>Pcs Details</title>
4 4
</head>
5 5
<body>
6
<div style='float:left'>
6 7
<form action="" method="get">
7 8
  <label for="view">Choose view:</label>
8 9
  <select id="view" name="view" onchange="this.form.action=this.value;">
......
14 15
  </select>
15 16
  <input type="submit" value="OK">
16 17
</form>
18
</div>
19
<div style='float:left'>
20
<form action="/login" method="get">
21
    <input type="submit" value="Login" />
22
</form>
23
</div>
24
<form action="/logout" method="get">
25
    <input type="submit" value="Logout" />
26
</form>
17 27
<table>
18 28
    <TR>
19 29
        <TH>ID</TH>
server/templates/pcs/pcs_normal.html
1
<html>
2
<head>
3
    <title>Pcs Details</title>
4
</head>
5
<body>
6
<div style='float:left'>
7
<form action="" method="get">
8
  <label for="view">Choose view:</label>
9
  <select id="view" name="view" onchange="this.form.action=this.value;">
10
      <option value=""></option>
11
      <option value="/logs-web">Logs</option>
12
      <option value="/devices-web">Devices</option>
13
      <option value="/teams-web">Teams</option>
14
      <option value="/pcs-web">PCs</option>
15
  </select>
16
  <input type="submit" value="OK">
17
</form>
18
</div>
19
<div style='float:left'>
20
<form action="/login" method="get">
21
    <input type="submit" value="Login" />
22
</form>
23
</div>
24
<form action="/logout" method="get">
25
    <input type="submit" value="Logout" />
26
</form>
27
<table>
28
    <TR>
29
        <TH>ID</TH>
30
        <TH>Username</TH>
31
        <TH>Hostname</TH>
32
        <TH>Team</TH>
33
    </TR>
34
    {% for pc in pcs %}
35
    <TR>
36
        <TD class="ID">{{pc.id}}</TD>
37
        <TD class="Vendor ID">{{pc.username}}</TD>
38
        <TD class="Product ID">{{pc.hostname}}</TD>
39
        {% if pc.team == None %}
40
            <TD class="Team">NONE</TD>
41
        {% else %}
42
            <TD class="Team">{{pc.team.name}}</TD>
43
        {% endif %}
44
    </TR>
45
    {% endfor %}
46
</table>
47
</body>
48
</html>
server/templates/teams/teams.html
3 3
    <title>Teams Details</title>
4 4
</head>
5 5
<body>
6
<div style='float:left'>
6 7
<form action="" method="get">
7 8
  <label for="view">Choose view:</label>
8 9
  <select id="view" name="view" onchange="this.form.action=this.value;">
......
14 15
  </select>
15 16
  <input type="submit" value="OK">
16 17
</form>
18
</div>
19
<div style='float:left'>
20
<form action="/login" method="get">
21
    <input type="submit" value="Login" />
22
</form>
23
</div>
24
<form action="/logout" method="get">
25
    <input type="submit" value="Logout" />
26
</form>
17 27
<table>
18 28
    <TR>
19 29
        <TH>ID</TH>
server/templates/teams/teams_normal.html
1
<html>
2
<head>
3
    <title>Teams Details</title>
4
</head>
5
<body>
6
<div style='float:left'>
7
<form action="" method="get">
8
  <label for="view">Choose view:</label>
9
  <select id="view" name="view" onchange="this.form.action=this.value;">
10
      <option value=""></option>
11
      <option value="/logs-web">Logs</option>
12
      <option value="/devices-web">Devices</option>
13
      <option value="/teams-web">Teams</option>
14
      <option value="/pcs-web">PCs</option>
15
  </select>
16
  <input type="submit" value="OK">
17
</form>
18
</div>
19
<div style='float:left'>
20
<form action="/login" method="get">
21
    <input type="submit" value="Login" />
22
</form>
23
</div>
24
<form action="/logout" method="get">
25
    <input type="submit" value="Logout" />
26
</form>
27
<table>
28
    <TR>
29
        <TH>ID</TH>
30
        <TH>Name</TH>
31
    </TR>
32
    {% for team in teams %}
33
    <TR>
34
        <TD class="ID">{{team.id}}</TD>
35
        <TD class="Vendor ID">{{team.name}}</TD>
36
    </TR>
37
    {% endfor %}
38
</table>
39
</body>
40
</html>
server/templates/usb-logs/logs.html
3 3
    <title>Logs Details</title>
4 4
</head>
5 5
<body>
6
<div style='float:left'>
6 7
<form action="" method="get">
7 8
  <label for="view">Choose view:</label>
8 9
  <select id="view" name="view" onchange="this.form.action=this.value;">
......
14 15
  </select>
15 16
  <input type="submit" value="OK">
16 17
</form>
18
</div>
19
<div style='float:left'>
20
<form action="/login" method="get">
21
    <input type="submit" value="Login" />
22
</form>
23
</div>
24
<form action="/logout" method="get">
25
    <input type="submit" value="Logout" />
26
</form>
17 27
<form action="/logs-web" method="post">
18 28
  <label for="pc">PC:</label>
19 29
  <input id="pc" name="pc" type="text" list="pcs" value="" placeholder="all">

Také k dispozici: Unified diff