Compare commits

..

2 commits

Author SHA1 Message Date
Marcin Kowalicki
f7fe427576 Merge branch 'master' into feature/grades 2021-09-05 12:39:56 +02:00
Patryk
47a1b038a9
Delete install.bat 2021-04-16 12:29:35 +02:00
127 changed files with 10121 additions and 25905 deletions

View file

@ -1,22 +1,27 @@
name: Backend
on: [push]
env:
PYTEST_ADDOPTS: "--color=yes"
jobs:
build:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:10.8
ports:
- 5432:5432
# needed because the postgres container does not provide a healthcheck
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
steps:
- uses: actions/checkout@v2
- name: Set up Python 3.10.4
uses: actions/setup-python@v2
- uses: actions/checkout@v1
- name: Set up Python 3.9
uses: actions/setup-python@v1
with:
python-version: 3.10.4
python-version: 3.9
- name: psycopg2 prerequisites
run: sudo apt-get install python-dev libpq-dev
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r .github/workflows/requirements.txt
pip install -r requirements.txt
pip install pytest-django
- name: Run tests
working-directory: ./backend/
run: pytest test.py
run: python manage.py test

71
.github/workflows/codeql-analysis.yml vendored Normal file
View file

@ -0,0 +1,71 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [ master ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ master ]
schedule:
- cron: '26 12 * * 5'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'javascript', 'python', "typescript" ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
# Learn more:
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
steps:
- name: Checkout repository
uses: actions/checkout@v2
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1

View file

@ -2,15 +2,11 @@
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v2
- name: Install deps
working-directory: ./frontend
run: npm install --immutable
- uses: actions/checkout@v1
- name: Install npm
run: npm install --prefix frontend
- name: Build app
working-directory: ./frontend
run: npm run build
run: npm run build --prefix frontend

View file

@ -1,12 +0,0 @@
beautifulsoup4
bs4
requests
cryptography
pytest
pydantic~=1.9.0
uvicorn~=0.17.5
fastapi~=0.74.0
starlette~=0.17.1
lxml
sty
pytest-xdist

15
.gitignore vendored
View file

@ -8,7 +8,7 @@ __pycache__/
*.code-workspace
.idea/*
.venv/*
.DS_Store
### VisualStudioCode Patch ###
# Ignore all local history of files
.history
@ -152,15 +152,4 @@ dmypy.json
# Cython debug symbols
cython_debug/
node_modules/
/tests/e2e/videos/
/tests/e2e/screenshots/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
node_modules/

View file

@ -1,29 +1,34 @@
FROM nikolaik/python-nodejs:python3.10-nodejs16-bullseye
FROM nikolaik/python-nodejs:python3.9-nodejs15
ENV PYTHONUNBUFFERED 1
WORKDIR /src/frontend
WORKDIR /src
COPY frontend/package-lock.json /src/frontend/package-lock.json
COPY frontend/package.json /src/frontend/package.json
COPY frontend/package-lock.json .
COPY frontend/package*.json ./frontend/
RUN npm install
WORKDIR /src/backend
COPY requirements.txt .
COPY backend/requirements.txt .
RUN pip install -r requirements.txt
COPY app/* ./app/
COPY app/API/* ./app/API/
COPY app/migrations/* ./app/migrations/
COPY frontend/* ./frontend/
COPY frontend/src/* ./frontend/src/
COPY frontend/public/* ./frontend/public/
COPY frontend/tests/* ./frontend/tests/
COPY backend/* ./backend/
COPY backend/app/* ./backend/app/
COPY backend/app/endpoints/* ./backend/app/endpoints/
COPY backend/app/models/* ./backend/app/models/
COPY frontend/migrations/* ./frontend/migrations/
COPY frontend/static/frontend/* ./frontend/static/frontend/
COPY frontend/static/frontend/css/* ./frontend/static/frontend/css/
COPY frontend/static/frontend/images/* ./frontend/static/frontend/images/
COPY frontend/templates/frontend/* ./frontend/templates/frontend/
COPY wulkanowy/* ./wulkanowy/
COPY manage.py .
RUN python manage.py makemigrations
RUN python manage.py migrate
WORKDIR /src/frontend
EXPOSE 8000
ENTRYPOINT [ "python3", "main", "0.0.0.0:8000"]
ENTRYPOINT [ "python3", "manage.py", "runserver", "0.0.0.0:8000" ]

View file

@ -186,7 +186,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2022 Wulkanowy Web Team.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

100
README.md
View file

@ -1,49 +1,51 @@
# Wulkanowy Web
🌋 Unofficial VULCAN UONET+ browser client for students and their parents
![GitHub Workflow Status](https://github.com/wulkanowy/wulkanowy-web/workflows/Python%20application/badge.svg)
## Join our Discord server!
[![Discord](https://discordapp.com/api/guilds/390889354199040011/widget.png?style=banner2)](https://discord.com/invite/vccAQBr)
# Development
## 1. Install dependencies
```sh
cd backend
pip install -r requirements.txt
```
And in frontend:
```sh
cd frontend
npm install
```
## 2. Start the server
```sh
cd backend
py -m main
```
And in frontend:
```sh
cd frontend
npm run serve
```
# Docker
With docker compose
```sh
docker-compose up -d
```
Without docker compose
```sh
docker build -t wulkanowy/web .
docker run -d -p 8000:8000 wulkanowy/web
```
# Wulkanowy Web
🌋 Unofficial VULCAN UONET+ browser client for students and their parents
![GitHub Workflow Status](https://github.com/wulkanowy/wulkanowy-web/workflows/Python%20application/badge.svg)
## Join our Discord server!
[![Discord](https://discordapp.com/api/guilds/390889354199040011/widget.png?style=banner2)](https://discord.com/invite/vccAQBr)
# Development
## 1. Install dependencies
```sh
pip install -r requirements.txt
npm i --prefix frontend
```
## 2. Make migrations
```sh
python manage.py makemigrations
python manage.py migrate
```
## 3. Start the server
```sh
python manage.py runserver
```
And in frontend:
```shell
cd frontend
npm run build
```
# Docker
With docker compose
```sh
docker-compose up -d
```
Without docker compose
```sh
docker build -t wulkanowy/web .
docker run -d -p 8000:8000 wulkanowy/web
```

15
app/API/attendance.py Normal file
View file

@ -0,0 +1,15 @@
import requests
import json
from .generate_cookies import autogenerate_cookies
def get_attendance(register_id, students, oun, s, date):
cookies = autogenerate_cookies(students, s)
with open('app/API/headers.json') as f:
headers = json.load(f)
attendance_lessons = requests.post(oun+'/FrekwencjaStatystykiPrzedmioty.mvc/Get', headers=headers, cookies=cookies)
attendance_json_id = attendance_lessons.json()['data'][0]['Id']
attendance = requests.post(oun+'/Frekwencja.mvc/Get', headers=headers, cookies=cookies, json={'idTypWpisuFrekwencji': attendance_json_id, 'data': date})
return [attendance.json(), attendance_lessons.json()]

37
app/API/dashboard.py Normal file
View file

@ -0,0 +1,37 @@
import json
import requests
import re
from bs4 import BeautifulSoup
from .generate_cookies import autogenerate_cookies
def get_dashboard(register_id, students, s, diary_url, symbol):
if diary_url != 'http://cufs.fakelog.tk/':
diary_url = 'http://uonetplus.vulcan.net.pl/'
else:
diary_url = 'http://uonetplus.fakelog.tk/'
cookies = autogenerate_cookies(students, s)
with open('app/API/headers.json') as f:
headers = json.load(f)
index = requests.get(f'{diary_url}{symbol}/Start.mvc/Index', headers=headers, cookies=cookies)
permissions_value = re.search("permissions: '(.)*'", index.text)
permissions_value = permissions_value.group()
permissions_value = permissions_value.replace('permissions: ', '').replace("'", "")
permissions = {
"permissions": permissions_value
}
last_notes = requests.post(f'{diary_url}{symbol}/Start.mvc/GetLastNotes', headers=headers, cookies=cookies, json=permissions)
free_days = requests.post(f'{diary_url}{symbol}/Start.mvc/GetFreeDays', headers=headers, cookies=cookies, json=permissions)
lucky_number = requests.post(f'{diary_url}{symbol}/Start.mvc/GetKidsLuckyNumbers', headers=headers, cookies=cookies, json=permissions)
return_data = {
"last_notes": last_notes.json(),
"free_days": free_days.json(),
"lucky_number": lucky_number.json()
}
return return_data

13
app/API/exams.py Normal file
View file

@ -0,0 +1,13 @@
import json
import requests
from .generate_cookies import autogenerate_cookies
def get_exams(register_id, students, oun, s, date, school_year):
cookies = autogenerate_cookies(students, s)
with open('app/API/headers.json') as f:
headers = json.load(f)
exams = requests.post(oun+'/Sprawdziany.mvc/Get', headers=headers, cookies=cookies, json={'data': date, 'rokSzkolny': school_year})
return exams.json()

View file

@ -0,0 +1,12 @@
import json
def autogenerate_cookies(students, s):
cookies = s
cookies.update({
"biezacyRokSzkolny": f"{students['data'][0]['DziennikRokSzkolny']}",
"idBiezacyDziennik": f"{students['data'][0]['IdDziennik']}",
"idBiezacyDziennikPrzedszkole": f"{students['data'][0]['IdPrzedszkoleDziennik']}",
"idBiezacyUczen": f"{students['data'][0]['IdUczen']}"
})
return cookies

13
app/API/grades.py Normal file
View file

@ -0,0 +1,13 @@
import json
import requests
from .generate_cookies import autogenerate_cookies
def get_grades(register_id, students, oun, s):
cookies = autogenerate_cookies(students, s)
with open('app/API/headers.json') as f:
headers = json.load(f)
grades = requests.post(oun+'/Oceny.mvc/Get', headers=headers, cookies=cookies, json={'okres': register_id})
return grades.json()

6
app/API/headers.json Normal file
View file

@ -0,0 +1,6 @@
{
"Accept-Encoding": "gzip, deflate",
"Accept": "*/*",
"Connection": "keep-alive",
"User-Agent": "Wulkanowy-web :)"
}

13
app/API/homeworks.py Normal file
View file

@ -0,0 +1,13 @@
import requests
import json
from .generate_cookies import autogenerate_cookies
def get_homeworks(register_id, students, oun, s, date, school_year):
cookies = autogenerate_cookies(students, s)
with open('app/API/headers.json') as f:
headers = json.load(f)
homeworks = requests.post(oun+'/Homework.mvc/Get', headers=headers, cookies=cookies, json={'schoolYear': school_year, 'date': date, 'statusFilter': '-1'})
return homeworks.json()

182
app/API/messages.py Normal file
View file

@ -0,0 +1,182 @@
import requests
import json
import calendar
import time
import re
from .generate_cookies import autogenerate_cookies
def get_received_messages(register_id, students, oun, s, date, school_year, symbol):
with open('app/API/headers.json') as f:
headers = json.load(f)
now = calendar.timegm(time.gmtime())
if oun == 'http://uonetplus-uczen.fakelog.tk/powiatwulkanowy/123458':
received_messages = requests.get(f'http://uonetplus-uzytkownik.fakelog.tk/{symbol}/Wiadomosc.mvc/GetInboxMessages?_dc={now}&dataOd=&dataDo=&page=1&start=0&limit=25', headers=headers, cookies=s)
else:
received_messages = requests.get(f'https://uonetplus-uzytkownik.vulcan.net.pl/{symbol}/Wiadomosc.mvc/GetInboxMessages?_dc={now}&dataOd=&dataDo=&page=1&start=0&limit=25', headers=headers, cookies=s)
return received_messages.json()
def get_sent_messages(register_id, students, oun, s, date, school_year, symbol):
with open('app/API/headers.json') as f:
headers = json.load(f)
now = calendar.timegm(time.gmtime())
if oun == 'http://uonetplus-uczen.fakelog.tk/powiatwulkanowy/123458':
sent_messages = requests.get(f'http://uonetplus-uzytkownik.fakelog.tk/{symbol}/Wiadomosc.mvc/GetInboxMessages?_dc={now}&dataOd=&dataDo=&page=1&start=0&limit=25', headers=headers, cookies=s)
else:
sent_messages = requests.get(f'https://uonetplus-uzytkownik.vulcan.net.pl/{symbol}/Wiadomosc.mvc/GetInboxMessages?_dc={now}&dataOd=&dataDo=&page=1&start=0&limit=25', headers=headers, cookies=s)
return sent_messages.json()
def get_deleted_messages(register_id, students, oun, s, date, school_year, symbol):
with open('app/API/headers.json') as f:
headers = json.load(f)
now = calendar.timegm(time.gmtime())
if oun == 'http://uonetplus-uczen.fakelog.tk/powiatwulkanowy/123458':
deleted_messages = requests.get(f'http://uonetplus-uzytkownik.fakelog.tk/{symbol}/Wiadomosc.mvc/GetOutboxMessages?_dc={now}&dataOd=&dataDo=&page=1&start=0&limit=25', headers=headers, cookies=s)
else:
deleted_messages = requests.get(f'https://uonetplus-uzytkownik.vulcan.net.pl/{symbol}/Wiadomosc.mvc/GetOutboxMessages?_dc={now}&dataOd=&dataDo=&page=1&start=0&limit=25', headers=headers, cookies=s)
return deleted_messages.json()
def get_recipients(register_id, students, oun, s, date, school_year, symbol):
with open('app/API/headers.json') as f:
headers = json.load(f)
if oun == 'http://uonetplus-uczen.fakelog.tk/powiatwulkanowy/123458':
link = f'http://uonetplus-uzytkownik.fakelog.tk/{symbol}'
else:
link = f'https://uonetplus-uzytkownik.vulcan.net.pl/{symbol}'
get_jednostki = requests.get(f'{link}/NowaWiadomosc.mvc/GetJednostkiUzytkownika', headers=headers, cookies=s)
id_jednostka = get_jednostki.json()['data'][0]['IdJednostkaSprawozdawcza']
data = {
"paramsVo":{"IdJednostkaSprawozdawcza":id_jednostka, 'Rola': 2}
}
get_addressee = requests.post(f'{link}/Adresaci.mvc/GetAddressee', headers=headers, cookies=s, json=data)
return {'addressee': get_addressee.json(), 'unitId': id_jednostka}
def send_message(register_id, students, oun, s, date, school_year, symbol, send_data):
with open('app/API/headers.json') as f:
headers = json.load(f)
if oun == 'http://uonetplus-uczen.fakelog.tk/powiatwulkanowy/123458':
link = f'http://uonetplus-uzytkownik.fakelog.tk/{symbol}'
else:
link = f'https://uonetplus-uzytkownik.vulcan.net.pl/{symbol}'
student_data = students['data'][0]['UczenNazwisko']+' '+students['data'][0]['UczenImie']
sess = requests.Session()
sess.cookies.update(s)
sess.headers.update(headers)
index = sess.get(link)
antiForgeryToken = re.search("antiForgeryToken: '(.)*'", index.text)
antiForgeryToken = antiForgeryToken.group()
antiForgeryToken = antiForgeryToken.replace('antiForgeryToken: ', '').replace("'", "")
appGuid = re.search("appGuid: '(.)*'", index.text)
appGuid = appGuid.group()
appGuid = appGuid.replace('appGuid: ', '').replace("'", "")
sess.headers.update({
'X-V-RequestVerificationToken': antiForgeryToken,
'X-V-AppGuid': appGuid,
'Content-Type': 'application/json',
'TE': "Trailers"
})
payload = {
"incomming": {
"Id": 0,
"Nieprzeczytane": 0,
"Przeczytane": 0,
"Nieprzeczytana": False,
"FolderWiadomosci": 0,
"WiadomoscPowitalna": False,
"Data": None,
"Tresc": send_data['content'],
"Temat": send_data['subject'],
"IdWiadomosci": 0,
"HasZalaczniki": False,
"Zalaczniki": "",
"Adresaci": [
{
"Id": send_data['data']['Id'],
"IdReceiver": "",
"Name": send_data['data']['Name'],
"Role": send_data['data']['Role'],
"UnitId": send_data['data']['UnitId'],
"IdLogin": send_data['data']['IdLogin'],
"PushWiadomosc": False,
"Hash": send_data['data']['Hash'],
"Date": None,
"IsMarked": False
}
],
"WyslijJako": student_data,
"WiadomoscAdresatLogin": "",
"IdWiadomoscAdresatLogin": None,
"RolaNadawcy": 0,
"NieprzeczytanePrzeczytane": "0/0",
"NadawcaNazwa": "Brak nadawcy",
"IdNadawca": 0,
"AdresaciNazwa": "Brak adresata"
}
}
send = sess.post(f'{link}/NowaWiadomosc.mvc/InsertWiadomosc', data=json.dumps(payload))
return send.json()
def get_message_content(register_id, students, oun, s, date, school_year, symbol, message_id):
with open('app/API/headers.json') as f:
headers = json.load(f)
if oun == 'http://uonetplus-uczen.fakelog.tk/powiatwulkanowy/123458':
link = f'http://uonetplus-uzytkownik.fakelog.tk/{symbol}'
else:
link = f'https://uonetplus-uzytkownik.vulcan.net.pl/{symbol}'
sess = requests.Session()
sess.cookies.update(s)
sess.headers.update(headers)
index = sess.get(link)
antiForgeryToken = re.search("antiForgeryToken: '(.)*'", index.text)
antiForgeryToken = antiForgeryToken.group()
antiForgeryToken = antiForgeryToken.replace('antiForgeryToken: ', '').replace("'", "")
appGuid = re.search("appGuid: '(.)*'", index.text)
appGuid = appGuid.group()
appGuid = appGuid.replace('appGuid: ', '').replace("'", "")
sess.headers.update({
'X-V-RequestVerificationToken': antiForgeryToken,
'X-V-AppGuid': appGuid
})
payload = {
'messageId': message_id
}
content = sess.post(f'{link}/Wiadomosc.mvc/GetInboxMessageDetails', data=json.dumps(payload))
if content.status_code != 200:
while True:
content = sess.post(f'{link}/Wiadomosc.mvc/GetInboxMessageDetails', data=json.dumps(payload))
if content.status_code == 200:
break
return content.json()

23
app/API/mobile_access.py Normal file
View file

@ -0,0 +1,23 @@
import json
import requests
from .generate_cookies import autogenerate_cookies
def get_registered_devices(register_id, students, oun, s):
cookies = autogenerate_cookies(students, s)
with open('app/API/headers.json') as f:
headers = json.load(f)
registered = requests.post(oun+'/ZarejestrowaneUrzadzenia.mvc/Get', headers=headers, cookies=cookies)
return registered.json()
def register_device(register_id, students, oun, s):
cookies = autogenerate_cookies(students, s)
with open('app/API/headers.json') as f:
headers = json.load(f)
register_data = requests.post(oun+'/RejestracjaUrzadzeniaToken.mvc/Get', headers=headers, cookies=cookies)
return register_data.json()

13
app/API/notes.py Normal file
View file

@ -0,0 +1,13 @@
import json
import requests
from .generate_cookies import autogenerate_cookies
def get_notes(register_id, students, oun, s):
cookies = autogenerate_cookies(students, s)
with open('app/API/headers.json') as f:
headers = json.load(f)
notes = requests.post(oun+'/UwagiIOsiagniecia.mvc/Get', headers=headers, cookies=cookies)
return notes.json()

13
app/API/school_data.py Normal file
View file

@ -0,0 +1,13 @@
import json
import requests
from .generate_cookies import autogenerate_cookies
def get_school_data(register_id, students, oun, s):
cookies = autogenerate_cookies(students, s)
with open('app/API/headers.json') as f:
headers = json.load(f)
school_data = requests.post(oun+'/SzkolaINauczyciele.mvc/Get', headers=headers, cookies=cookies)
return school_data.json()

24
app/API/stats.py Normal file
View file

@ -0,0 +1,24 @@
import json
import requests
from .generate_cookies import autogenerate_cookies
def get_partial(register_id, students, oun, s):
cookies = autogenerate_cookies(students, s)
with open('app/API/headers.json') as f:
headers = json.load(f)
partial = requests.post(oun+'/Statystyki.mvc/GetOcenyCzastkowe', headers=headers, cookies=cookies, json={'idOkres': register_id})
return partial.json()
def get_year(register_id, students, oun, s):
cookies = autogenerate_cookies(students, s)
with open('app/API/headers.json') as f:
headers = json.load(f)
year = requests.post(oun+'/Statystyki.mvc/GetOcenyRoczne', headers=headers, cookies=cookies, json={'idOkres': register_id})
return year.json()

13
app/API/student_data.py Normal file
View file

@ -0,0 +1,13 @@
import json
import requests
from .generate_cookies import autogenerate_cookies
def get_student_data(register_id, students, oun, s):
cookies = autogenerate_cookies(students, s)
with open('app/API/headers.json') as f:
headers = json.load(f)
student_data = requests.post(f'{oun}/Uczen.mvc/Get', headers=headers, cookies=cookies)
return student_data.json()

14
app/API/timetable.py Normal file
View file

@ -0,0 +1,14 @@
import json
import requests
from bs4 import BeautifulSoup
from .generate_cookies import autogenerate_cookies
def get_timetable(register_id, students, oun, s, date):
cookies = autogenerate_cookies(students, s)
with open('app/API/headers.json') as f:
headers = json.load(f)
timetable = requests.post(oun+'/PlanZajec.mvc/Get', headers=headers, cookies=cookies, json={'data': date})
return timetable.json()

3
app/admin.py Normal file
View file

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

5
app/apps.py Normal file
View file

@ -0,0 +1,5 @@
from django.apps import AppConfig
class WulkanowyConfig(AppConfig):
name = 'Wulkanowy'

9
app/decrypt.py Normal file
View file

@ -0,0 +1,9 @@
import json
from cryptography.fernet import Fernet
def decrypt_cookies(s, key):
s = bytes(s, 'utf-8')
key = Fernet(key)
s = key.decrypt(s)
s = json.loads(s.decode('utf-8'))
return s

2
app/forms.py Normal file
View file

@ -0,0 +1,2 @@
from django import forms

86
app/login.py Normal file
View file

@ -0,0 +1,86 @@
import os
import sys
import requests
from django.contrib.sessions.models import Session
from django.http import JsonResponse
from django import template
from django.utils.safestring import mark_safe
from django.shortcuts import render
import json
import requests
from django.shortcuts import redirect
from bs4 import BeautifulSoup
import datetime
def sender(url, loginName, Password, params_names, fail_phrase, symbol, diary_url, s):
data = [params_names[0], loginName, params_names[1], Password]
sender_return = send(url, data, fail_phrase, diary_url, symbol, s)
if sender_return == {'success': False}:
return {'success': False}
else:
return sender_return
def send(url, data, fail, diary_url, symbol, s):
ready_data = {data[0]: data[1], data[2]: data[3]}
page = s.post(url=url, data=ready_data)
if fail in page.text:
return {'success': False}
else:
if diary_url == 'http://cufs.fakelog.tk/':
page = s.get('http://cufs.fakelog.tk/powiatwulkanowy/FS/LS?wa=wsignin1.0&wtrealm=http://uonetplus.fakelog.localhost:300/powiatwulkanowy/LoginEndpoint.aspx&wctx=http://uonetplus.fakelog.localhost:300/powiatwulkanowy/LoginEndpoint.aspx')
bs = BeautifulSoup(page.text, 'html.parser')
wa = bs.find('input', {'name': 'wa'})['value']
cert = bs.find('input', {'name': 'wresult'})['value']
wctx = bs.find('input', {'name': 'wctx'})['value']
crtr = s.post(url=wctx, headers={"User-Agent": "Wulkanowy-web :)"}, data={"wa": wa, "wresult": cert, "wctx": wctx})
if 'nie został zarejestrowany w bazie szkoły, do której się logujesz' in crtr.text:
return {'success': False}
bs = BeautifulSoup(crtr.content, 'html.parser')
for a in bs.find_all('a', title='Uczeń'):
school_url = a['href']
break
if diary_url == 'http://cufs.fakelog.tk/':
school_url = 'http://uonetplus-uczen.fakelog.tk/powiatwulkanowy/123458'
cookies = get_cookies(symbol, school_url, s, diary_url)
return cookies
def get_cookies(symbol, school_url, s, diary_url):
students = s.post(school_url+'/UczenDziennik.mvc/Get')
register_id = students.json()['data'][0]['Okresy'][0]['Id']
now = datetime.datetime.now()
weekday = now.weekday()
for x in range(7):
if weekday == x:
now = now - datetime.timedelta(days=x)
day = now.day
month = now.month
year = now.year
date = datetime.date(year, month, day).isoformat()
date = f'{date}T00:00:00'
school_year = students.json()['data'][0]['DziennikRokSzkolny']
data = {
'register_id': register_id,
'students': students.json(),
'school_url': school_url,
'date': str(date),
'school_year': school_year,
'symbol': symbol,
's': s.cookies.get_dict(),
'diary_url': diary_url
}
return data

1
app/models.py Normal file
View file

@ -0,0 +1 @@
from django.db import models

3
app/tests.py Normal file
View file

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

445
app/views.py Normal file
View file

@ -0,0 +1,445 @@
from requests import get
from cryptography.fernet import Fernet
from django.contrib.sessions.backends.db import SessionStore
from django.http import HttpResponse, JsonResponse
from django.shortcuts import render
import json
import requests
from rest_framework.decorators import api_view
from django.core import serializers
from django.shortcuts import redirect
from django.contrib.sessions.models import Session
from .login import sender
from .API.grades import get_grades
from .API.exams import get_exams
from .API.timetable import get_timetable
from .API.notes import get_notes
from .API.attendance import get_attendance
from .API.messages import get_received_messages, get_sent_messages, get_deleted_messages, get_recipients, send_message, get_message_content
from .API.homeworks import get_homeworks
from .API.mobile_access import get_registered_devices, register_device
from .API.school_data import get_school_data
from .API.dashboard import get_dashboard
from .API.student_data import get_student_data
from .API.stats import get_partial, get_year
from .decrypt import decrypt_cookies
import datetime
#API
@api_view(['POST'])
def login(request, *args, **kwargs):
data = json.loads(request.body)
loginName = data['loginName']
Password = data['Password']
symbol = data['Symbol']
diary_url = data['diaryUrl']
if diary_url != 'http://cufs.fakelog.tk/':
link = f'{diary_url}{symbol}/Account/LogOn?ReturnUrl=%2F{symbol}%2FFS%2FLS%3Fwa%3Dwsignin1.0%26wtrealm%3Dhttps%253a%252f%252fuonetplus.vulcan.net.pl%252f{symbol}%252fLoginEndpoint.aspx%26wctx%3Dhttps%253a%252f%252fuonetplus.vulcan.net.pl%252f{symbol}%252fLoginEndpoint.aspx'
else:
link = 'http://cufs.fakelog.tk/powiatwulkanowy/FS/LS?wa=wsignin1.0&wtrealm=http://uonetplus.fakelog.localhost:300/powiatwulkanowy/LoginEndpoint.aspx&wctx=http://uonetplus.fakelog.localhost:300/powiatwulkanowy/LoginEndpoint.aspx'
s = requests.Session()
sender_return = sender(link, loginName, Password, ('loginName', 'Password'), 'Zła nazwa użytkownika lub hasło', symbol, diary_url, s)
if sender_return == {'success': False}:
data_response = {
'success': False
}
else:
request.session['is_logged'] = True
request.session[request.session.session_key] = Fernet.generate_key().decode('utf-8')
rkey = Fernet(bytes(request.session[request.session.session_key], 'utf-8'))
sender_return['s'] = json.dumps(sender_return['s'])
sender_return['s'] = sender_return['s'].encode()
sender_return['s'] = rkey.encrypt(sender_return['s'])
sender_return['s'] = sender_return['s'].decode('utf-8')
data_response = {'success': True, 'data': sender_return}
return JsonResponse(data_response)
@api_view(['POST'])
def grades(request, *args, **kwargs):
if request.session.has_key('is_logged'):
data = json.loads(request.body)
register_id = data['data']['register_id']
students = data['data']['students']
school_url = data['data']['school_url']
s = data['data']['s']
key = bytes(request.session[request.session.session_key], 'utf-8')
s = decrypt_cookies(s, key)
grades = get_grades(register_id, students, school_url, s)
return JsonResponse(grades)
else:
return redirect('../')
@api_view(['POST'])
def timetable(request, *args, **kwargs):
if request.session.has_key('is_logged'):
data = json.loads(request.body)
week = data['week']
data = json.loads(data['cookies'])
register_id = data['data']['register_id']
students = data['data']['students']
school_url = data['data']['school_url']
s = data['data']['s']
key = bytes(request.session[request.session.session_key], 'utf-8')
s = decrypt_cookies(s, key)
now = datetime.datetime.now()
weekday = now.weekday()
for x in range(7):
if weekday == x:
now = now - datetime.timedelta(days=x)
now = now + datetime.timedelta(days=week*7)
day = now.day
month = now.month
year = now.year
date = datetime.date(year, month, day).isoformat()
date = f'{date}T00:00:00'
timetable = get_timetable(register_id, students, school_url, s, date)
return JsonResponse(timetable)
else:
return redirect('../')
@api_view(['POST'])
def exams(request, *args, **kwargs):
if request.session.has_key('is_logged'):
data = json.loads(request.body)
week = data['week']
data = json.loads(data['cookies'])
register_id = data['data']['register_id']
students = data['data']['students']
school_url = data['data']['school_url']
s = data['data']['s']
key = bytes(request.session[request.session.session_key], 'utf-8')
s = decrypt_cookies(s, key)
now = datetime.datetime.now()
weekday = now.weekday()
for x in range(7):
if weekday == x:
now = now - datetime.timedelta(days=x)
now = now + datetime.timedelta(days=week*7)
day = now.day
month = now.month
year = now.year
date = datetime.date(year, month, day).isoformat()
date = f'{date}T00:00:00'
school_year = data['data']['school_year']
exams = get_exams(register_id, students, school_url, s, date, school_year)
return JsonResponse(exams)
else:
return redirect('../')
@api_view(['POST'])
def homeworks(request, *args, **kwargs):
if request.session.has_key('is_logged'):
data = json.loads(request.body)
week = data['week']
data = json.loads(data['cookies'])
register_id = data['data']['register_id']
students = data['data']['students']
school_url = data['data']['school_url']
s = data['data']['s']
key = bytes(request.session[request.session.session_key], 'utf-8')
s = decrypt_cookies(s, key)
now = datetime.datetime.now()
weekday = now.weekday()
for x in range(7):
if weekday == x:
now = now - datetime.timedelta(days=x)
now = now + datetime.timedelta(days=week*7)
day = now.day
month = now.month
year = now.year
date = datetime.date(year, month, day).isoformat()
date = f'{date}T00:00:00'
school_year = data['data']['school_year']
homeworks = get_homeworks(register_id, students, school_url, s, date, school_year)
return JsonResponse(homeworks)
else:
return redirect('../')
@api_view(['POST'])
def attendance(request, *args, **kwargs):
if request.session.has_key('is_logged'):
data = json.loads(request.body)
week = data['week']
data = json.loads(data['cookies'])
register_id = data['data']['register_id']
students = data['data']['students']
school_url = data['data']['school_url']
s = data['data']['s']
key = bytes(request.session[request.session.session_key], 'utf-8')
s = decrypt_cookies(s, key)
now = datetime.datetime.now()
weekday = now.weekday()
for x in range(7):
if weekday == x:
now = now - datetime.timedelta(days=x)
now = now + datetime.timedelta(days=week*7)
day = now.day
month = now.month
year = now.year
date = datetime.date(year, month, day).isoformat()
date = f'{date}T00:00:00'
attendance = get_attendance(register_id, students, school_url, s, date)
return JsonResponse(attendance, safe=False)
else:
return redirect('../')
@api_view(['POST'])
def notes(request, *args, **kwargs):
if request.session.has_key('is_logged'):
data = json.loads(request.body)
register_id = data['data']['register_id']
students = data['data']['students']
school_url = data['data']['school_url']
s = data['data']['s']
key = bytes(request.session[request.session.session_key], 'utf-8')
s = decrypt_cookies(s, key)
notes = get_notes(register_id, students, school_url, s)
return JsonResponse(notes)
else:
return redirect('../')
@api_view(['POST'])
def registered_devices(request, *args, **kwargs):
if request.session.has_key('is_logged'):
data = json.loads(request.body)
register_id = data['data']['register_id']
students = data['data']['students']
school_url = data['data']['school_url']
s = data['data']['s']
key = bytes(request.session[request.session.session_key], 'utf-8')
s = decrypt_cookies(s, key)
registered = get_registered_devices(register_id, students, school_url, s)
return JsonResponse(registered)
else:
return redirect('../')
@api_view(['POST'])
def register_device_(request, *args, **kwargs):
if request.session.has_key('is_logged'):
data = json.loads(request.body)
register_id = data['data']['register_id']
students = data['data']['students']
school_url = data['data']['school_url']
s = data['data']['s']
key = bytes(request.session[request.session.session_key], 'utf-8')
s = decrypt_cookies(s, key)
register_data = register_device(register_id, students, school_url, s)
return JsonResponse(register_data)
else:
return redirect('../')
@api_view(['POST'])
def received_messages(request, *args, **kwargs):
if request.session.has_key('is_logged'):
data = json.loads(request.body)
register_id = data['data']['register_id']
students = data['data']['students']
school_url = data['data']['school_url']
s = data['data']['s']
key = bytes(request.session[request.session.session_key], 'utf-8')
s = decrypt_cookies(s, key)
date = data['data']['date']
school_year = data['data']['school_year']
symbol = data['data']['symbol']
received_messages = get_received_messages(register_id, students, school_url, s, date, school_year, symbol)
return JsonResponse(received_messages)
else:
return redirect('../')
@api_view(['POST'])
def sent_messages(request, *args, **kwargs):
if request.session.has_key('is_logged'):
data = json.loads(request.body)
register_id = data['data']['register_id']
students = data['data']['students']
school_url = data['data']['school_url']
s = data['data']['s']
key = bytes(request.session[request.session.session_key], 'utf-8')
s = decrypt_cookies(s, key)
date = data['data']['date']
school_year = data['data']['school_year']
symbol = data['data']['symbol']
sent_messages = get_sent_messages(register_id, students, school_url, s, date, school_year, symbol)
return JsonResponse(sent_messages)
else:
return redirect('../')
@api_view(['POST'])
def deleted_messages(request, *args, **kwargs):
if request.session.has_key('is_logged'):
data = json.loads(request.body)
register_id = data['data']['register_id']
students = data['data']['students']
school_url = data['data']['school_url']
s = data['data']['s']
key = bytes(request.session[request.session.session_key], 'utf-8')
s = decrypt_cookies(s, key)
date = data['data']['date']
school_year = data['data']['school_year']
symbol = data['data']['symbol']
deleted_messages = get_deleted_messages(register_id, students, school_url, s, date, school_year, symbol)
return JsonResponse(deleted_messages)
else:
return redirect('../')
@api_view(['POST'])
def recipients(request, *args, **kwargs):
if request.session.has_key('is_logged'):
data = json.loads(request.body)
register_id = data['data']['register_id']
students = data['data']['students']
school_url = data['data']['school_url']
s = data['data']['s']
key = bytes(request.session[request.session.session_key], 'utf-8')
s = decrypt_cookies(s, key)
date = data['data']['date']
school_year = data['data']['school_year']
symbol = data['data']['symbol']
recipients = get_recipients(register_id, students, school_url, s, date, school_year, symbol)
return JsonResponse(recipients)
else:
return redirect('../')
@api_view(['POST'])
def school_data(request, *args, **kwargs):
if request.session.has_key('is_logged'):
data = json.loads(request.body)
register_id = data['data']['register_id']
students = data['data']['students']
school_url = data['data']['school_url']
s = data['data']['s']
key = bytes(request.session[request.session.session_key], 'utf-8')
s = decrypt_cookies(s, key)
school_data = get_school_data(register_id, students, school_url, s)
return JsonResponse(school_data)
else:
return redirect('../')
@api_view(['POST'])
def dashboard(request, *args, **kwargs):
if request.session.has_key('is_logged'):
data = json.loads(request.body)
register_id = data['data']['register_id']
students = data['data']['students']
s = data['data']['s']
key = bytes(request.session[request.session.session_key], 'utf-8')
s = decrypt_cookies(s, key)
diary_url = data['data']['diary_url']
symbol = data['data']['symbol']
dashboard = get_dashboard(register_id, students, s, diary_url, symbol)
return JsonResponse(dashboard)
else:
return redirect('../')
@api_view(['POST'])
def send(request, *args, **kwargs):
if request.session.has_key('is_logged'):
data = json.loads(request.body)
cookies_data = json.loads(data['cookies_data'])
register_id = cookies_data['data']['register_id']
students = cookies_data['data']['students']
school_url = cookies_data['data']['school_url']
s = cookies_data['data']['s']
key = bytes(request.session[request.session.session_key], 'utf-8')
s = decrypt_cookies(s, key)
date = cookies_data['data']['date']
school_year = cookies_data['data']['school_year']
symbol = cookies_data['data']['symbol']
send_data = {'data': data['data'], 'subject': data['subject'], 'content': data['content']}
send = send_message(register_id, students, school_url, s, date, school_year, symbol, send_data)
return JsonResponse(send, safe=False)
else:
return redirect('../')
@api_view(['POST'])
def message_content(request, *args, **kwargs):
if request.session.has_key('is_logged'):
data = json.loads(request.body)
cookies_data = json.loads(data['cookies_data'])
register_id = cookies_data['data']['register_id']
students = cookies_data['data']['students']
school_url = cookies_data['data']['school_url']
s = cookies_data['data']['s']
key = bytes(request.session[request.session.session_key], 'utf-8')
s = decrypt_cookies(s, key)
date = cookies_data['data']['date']
school_year = cookies_data['data']['school_year']
symbol = cookies_data['data']['symbol']
message_id = data['message_id']
content = get_message_content(register_id, students, school_url, s, date, school_year, symbol, message_id)
return JsonResponse(content, safe=False)
else:
return redirect('../')
@api_view(['POST'])
def student_data(request, *args, **kwargs):
if request.session.has_key('is_logged'):
data = json.loads(request.body)
register_id = data['data']['register_id']
students = data['data']['students']
school_url = data['data']['school_url']
s = data['data']['s']
key = bytes(request.session[request.session.session_key], 'utf-8')
s = decrypt_cookies(s, key)
data = get_student_data(register_id, students, school_url, s)
return JsonResponse(data)
else:
return redirect('../')
#STATS
@api_view(['POST'])
def partial(request, *args, **kwargs):
if request.session.has_key('is_logged'):
data = json.loads(request.body)
register_id = data['data']['register_id']
students = data['data']['students']
school_url = data['data']['school_url']
s = data['data']['s']
key = bytes(request.session[request.session.session_key], 'utf-8')
s = decrypt_cookies(s, key)
partial_stats = get_partial(register_id, students, school_url, s)
return JsonResponse(partial_stats)
else:
return redirect('../')
@api_view(['POST'])
def year(request, *args, **kwargs):
if request.session.has_key('is_logged'):
data = json.loads(request.body)
register_id = data['data']['register_id']
students = data['data']['students']
school_url = data['data']['school_url']
s = data['data']['s']
key = bytes(request.session[request.session.session_key], 'utf-8')
s = decrypt_cookies(s, key)
year_stats = get_year(register_id, students, school_url, s)
return JsonResponse(year_stats)
else:
return redirect('../')
@api_view(['GET'])
def log_out(request, *args, **kwargs):
del request.session[request.session.session_key]
del request.session['is_logged']
return JsonResponse({'logOut': True})

View file

@ -1,15 +0,0 @@
# Wulkanowy Web Backend
Based on [Marioneq's UonetplusAPI](https://github.com/Marioneq4958/uonetplus_api)
## 1. Install dependencies
```sh
pip install -r requirements.txt
```
## 2. Start the server
```sh
py -m main
```

View file

@ -1,193 +0,0 @@
from fastapi import APIRouter, HTTPException, Response
from starlette import status
from bs4 import BeautifulSoup
from urllib.parse import quote
from datetime import datetime
from cryptography.fernet import Fernet
import requests
import re
from app import models, paths
router = APIRouter()
@router.post("/login")
def login(data: models.Login, response: Response):
session = requests.Session()
cers = send_credentials(data.username, data.password, data.symbol, data.host, data.ssl, session)
students = get_students(data.symbol, data.host, data.ssl, cers, session)
cookies = get_cookies(data.ssl, data.host, data.symbol, session, students, response)
return cookies
def send_credentials(username: str, password: str, symbol: str, host: str, ssl: bool, session):
realm = build_url(subd="uonetplus", host=host, ssl=ssl)
url = build_url(
subd="cufs",
host=host,
path=paths.CUFS.START,
realm=quote(quote(realm, safe=""), safe=""),
symbol=symbol,
ssl=ssl,
)
payload = {"LoginName": username, "Password": password}
try:
page = session.post(url, payload)
if page.status_code == 404:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST, detail="Host or ssl is invalid"
)
except:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST, detail="Host or ssl is invalid"
)
soup = BeautifulSoup(page.text, "lxml")
error_tags = soup.select(".ErrorMessage, #ErrorTextLabel, #loginArea #errorText")
for error_tag in error_tags:
msg = re.sub(r"\s+", " ", error_tag.text).strip()
if msg:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN, detail="Username or password is incorrect"
)
wa: str = soup.select_one('input[name="wa"]')["value"]
wresult: str = soup.select_one('input[name="wresult"]')["value"]
wctx_tag = soup.select_one('input[name="wctx"]')
wctx: str = wctx_tag["value"] if wctx_tag else None
cers = {"wa": wa, "wresult": wresult, "wctx": wctx}
return cers
def build_url(subd: str = None, host: str = None, path: str = None, ssl: bool = True, **kwargs):
if ssl:
url = "https://"
else:
url = "http://"
if subd:
url += subd + "."
url += str(host)
if path:
url += path
if not kwargs.get("symbol"):
kwargs["symbol"] = "Deflaut"
for k in kwargs:
url = url.replace(f"{{{k.upper()}}}", str(kwargs[k]))
return url
def get_cookies(ssl: bool, host: str, symbol: str, session, students, response):
key = Fernet.generate_key().decode("utf-8")
fernet = Fernet(bytes(key, "utf-8"))
vulcan_cookies = session.cookies.get_dict()
cookies = fernet.encrypt(str(vulcan_cookies).encode("utf-8"))
response.set_cookie(key="key", value=key, max_age=1200)
data = {
"students": students,
"vulcan_cookies": cookies,
"symbol": symbol,
"host": host,
"ssl": ssl,
}
return data
def get_students(symbol: str, host: str, ssl: bool, cers, session):
students = []
url = build_url(subd="uonetplus", path=paths.UONETPLUS.START, symbol=symbol, host=host, ssl=ssl)
crtr = session.post(
url=url,
headers={
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:93.0) Gecko/20100101 Firefox/93.0"
},
data=cers,
)
if not "nie został zarejestrowany" in crtr.text:
soup = BeautifulSoup(crtr.text, "lxml")
schools = soup.select('.panel.linkownia.pracownik.klient a[href*="uonetplus-uczen"]')
for school in schools:
school_id = school["href"].split("/")[4]
url = build_url(
subd="uonetplus-uczen",
path=paths.UCZEN.START,
symbol=symbol,
host=host,
schoolid=school_id,
ssl=ssl,
)
page = session.get(url)
school_name = get_script_param(page.text, "organizationName")
anti_forgery_token = get_script_param(page.text, "antiForgeryToken")
app_guid = get_script_param(page.text, "appGuid")
version = get_script_param(page.text, "version")
url = build_url(
subd="uonetplus-uczen",
path=paths.UCZEN.UCZENDZIENNIK_GET,
symbol=symbol,
host=host,
schoolid=school_id,
ssl=ssl,
)
students_response = session.post(url)
for student in students_response.json()["data"]:
semesters = []
headers = {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate, br",
"Connection": "keep-alive",
"X-V-AppVersion": version,
"X-V-AppGuid": app_guid,
"X-V-RequestVerificationToken": anti_forgery_token,
}
for semester in student["Okresy"]:
semester = models.Semester(
number=semester["NumerOkresu"],
level=semester["Poziom"],
start=datetime.fromisoformat(semester["DataOd"]),
end=datetime.fromisoformat(semester["DataDo"]),
class_id=semester["IdOddzial"],
unit_id=semester["IdJednostkaSprawozdawcza"],
current=semester["IsLastOkres"],
id=semester["Id"],
)
semesters.append(semester)
student = models.Student(
id=student["Id"],
student_id=student["IdUczen"],
student_name=student["UczenImie"],
student_second_name=student["UczenImie2"],
student_surname=student["UczenNazwisko"],
is_register=student["IsDziennik"],
register_id=student["IdDziennik"],
kindergarten_register_id=student["IdPrzedszkoleDziennik"],
level=student["Poziom"],
symbol=student["Symbol"],
name=student["Nazwa"],
year=student["DziennikRokSzkolny"],
start=datetime.fromisoformat(student["DziennikDataOd"]),
end=datetime.fromisoformat(student["DziennikDataDo"]),
full_name=student["UczenPelnaNazwa"],
school_id=school_id,
school_symbol=symbol,
school_name=school_name,
cookies={
"idBiezacyDziennik": str(student["IdDziennik"]),
"idBiezacyUczen": str(student["IdUczen"]),
"idBiezacyDziennikPrzedszkole": str(student["IdPrzedszkoleDziennik"]),
"biezacyRokSzkolny": str(student["DziennikRokSzkolny"]),
},
headers=headers,
semesters=semesters,
)
students.append(student)
else:
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Symbol is incorrect")
return students
def get_script_param(text: str, param: str, default: str = None) -> str:
m = re.search(f"{param}: '(.+?)'", text)
return m.group(1) if m else default

View file

@ -1,222 +0,0 @@
from fastapi import APIRouter, HTTPException, Depends
from fastapi.security import APIKeyCookie
from starlette import status
from app import models, paths
import requests
from datetime import datetime
from cryptography.fernet import Fernet
import ast
cookie_sec = APIKeyCookie(name="key")
router = APIRouter()
@router.post("/uonetplus-uczen/notes")
def get_notes(data: models.UonetPlusUczen, key: str = Depends(cookie_sec)):
data.vulcan_cookies = encrypt_cookies(key, data.vulcan_cookies)
path = paths.UCZEN.UWAGIIOSIAGNIECIA_GET
response = get_response(data, path)
notes = []
for note in response.json()["data"]["Uwagi"]:
note = models.Note(
date=datetime.fromisoformat(note["DataWpisu"]).strftime("%d.%m.%Y %H:%M"),
teacher=note["Nauczyciel"],
category=note["Kategoria"],
content=note["TrescUwagi"],
points=note["Punkty"],
show_points=int(note["PokazPunkty"]),
category_type=bool(note["KategoriaTyp"]),
)
notes.append(note)
notes_and_achievements = models.NotesAndAchievements(
notes=notes, achievements=response.json()["data"]["Osiagniecia"]
)
return notes_and_achievements
@router.post("/uonetplus-uczen/school-info")
def get_school_info(data: models.UonetPlusUczen, key: str = Depends(cookie_sec)):
data.vulcan_cookies = encrypt_cookies(key, data.vulcan_cookies)
path = paths.UCZEN.SZKOLAINAUCZYCIELE_GET
response = get_response(data, path)
teachers = []
school = models.School(
name=response.json()["data"]["Szkola"]["Nazwa"],
address=response.json()["data"]["Szkola"]["Adres"],
contact=response.json()["data"]["Szkola"]["Kontakt"],
headmaster=response.json()["data"]["Szkola"]["Dyrektor"],
pedagogue=response.json()["data"]["Szkola"]["Pedagog"],
)
for teacher in response.json()["data"]["Nauczyciele"]:
teacher = models.Teacher(name=teacher["Nauczyciel"], subject=teacher["Nazwa"])
teachers.append(teacher)
school_info = models.SchoolInfo(school=school, teachers=teachers)
return school_info
@router.post("/uonetplus-uczen/conferences")
def get_conferences(data: models.UonetPlusUczen, key: str = Depends(cookie_sec)):
data.vulcan_cookies = encrypt_cookies(key, data.vulcan_cookies)
path = paths.UCZEN.ZEBRANIA_GET
response = get_response(data, path)
conferences = []
for conference in response.json()["data"]:
split = conference["Tytul"].split(", ")
title = ", ".join(split[2:])
date = datetime.strptime(split[1].replace(" godzina", ""), "%d.%m.%Y %H:%M")
conference = models.Conference(
title=title,
subject=conference["TematZebrania"],
agenda=conference["Agenda"],
present_on_conference=conference["ObecniNaZebraniu"],
online=conference["ZebranieOnline"],
id=conference["Id"],
date=date.strftime("%d.%m.%Y %H:%M"),
)
conferences.append(conference)
return conferences
@router.post("/uonetplus-uczen/grades")
def get_grades(data: models.UonetPlusUczen, key: str = Depends(cookie_sec)):
data.vulcan_cookies = encrypt_cookies(key, data.vulcan_cookies)
path = paths.UCZEN.OCENY_GET
response = get_response(data, path)
subjects = []
descriptive_grades = []
for subject in response.json()["data"]["Oceny"]:
subject_grades = []
for grade in subject["OcenyCzastkowe"]:
grade = models.Grade(
entry=grade["Wpis"],
color=grade["KolorOceny"],
symbol=grade["KodKolumny"],
description=grade["NazwaKolumny"],
weight_value=grade["Waga"],
date=grade["DataOceny"],
teacher=grade["Nauczyciel"],
)
subject_grades.append(grade)
subject = models.Subject(
name=subject["Przedmiot"],
visible_subject=subject["WidocznyPrzedmiot"],
position=subject["Pozycja"],
average=subject["Srednia"],
proposed_grade=subject["ProponowanaOcenaRoczna"],
final_grade=subject["OcenaRoczna"],
proposed_points=subject["ProponowanaOcenaRocznaPunkty"],
final_points=subject["OcenaRocznaPunkty"],
grades=subject_grades,
)
subjects.append(subject)
for descriptive_grade in response.json()["data"]["OcenyOpisowe"]:
descriptive_grade = models.DescriptiveGrade(
subject=descriptive_grade["NazwaPrzedmiotu"],
description=descriptive_grade["Opis"],
is_religion_or_ethics=descriptive_grade["IsReligiaEtyka"],
)
descriptive_grades.append(descriptive_grade)
grades = models.Grades(
is_average=response.json()["data"]["IsSrednia"],
is_points=response.json()["data"]["IsPunkty"],
subjects=subjects,
descriptive_grades=descriptive_grades,
)
return grades
@router.post("/uonetplus-uczen/mobile-access/get-registered-devices")
def get_registered_devices(data: models.UonetPlusUczen, key: str = Depends(cookie_sec)):
data.vulcan_cookies = encrypt_cookies(key, data.vulcan_cookies)
path = paths.UCZEN.ZAREJESTROWANEURZADZENIA_GET
response = get_response(data, path)
registered_devices = []
for device in response.json()["data"]:
device = models.Device(
id=device["Id"],
name=device["NazwaUrzadzenia"],
create_date=datetime.fromisoformat(device["DataUtworzenia"]).strftime("%d.%m.%Y %H:%M"),
)
registered_devices.append(device)
return registered_devices
@router.post("/uonetplus-uczen/mobile-access/register-device")
def get_register_device_token(data: models.UonetPlusUczen, key: str = Depends(cookie_sec)):
data.vulcan_cookies = encrypt_cookies(key, data.vulcan_cookies)
path = paths.UCZEN.REJESTRACJAURZADZENIATOKEN_GET
response = get_response(data, path)
token_response = models.TokenResponse(
token=response.json()["data"]["TokenKey"],
symbol=response.json()["data"]["CustomerGroup"],
pin=response.json()["data"]["PIN"],
qr_code_image=response.json()["data"]["QrCodeImage"],
)
return token_response
@router.post("/uonetplus-uczen/mobile-access/delete-registered-device")
def get_register_device_token(data: models.UonetPlusUczen, key: str = Depends(cookie_sec)):
data.vulcan_cookies = encrypt_cookies(key, data.vulcan_cookies)
path = paths.UCZEN.ZAREJESTROWANEURZADZENIA_DELETE
response = get_response(data, path)
return response.json()
def build_url(subd: str = None, host: str = None, path: str = None, ssl: bool = True, **kwargs):
if ssl:
url = "https://"
else:
url = "http://"
if subd:
url += subd + "."
url += host
if path:
url += path
if not kwargs.get("symbol"):
kwargs["symbol"] = "Deflaut"
for k in kwargs:
url = url.replace(f"{{{k.upper()}}}", str(kwargs[k]))
return url
def get_response(data, path):
session = requests.Session()
data.vulcan_cookies.update(data.student)
url = build_url(
subd="uonetplus-uczen",
path=path,
symbol=data.symbol,
host=data.host,
schoolid=data.school_id,
ssl=data.ssl,
)
response = session.post(
url=url,
headers=data.headers,
json=data.payload,
cookies=data.vulcan_cookies,
)
if response.status_code != 200:
detail = "UONET+ error code: " + response.status_code
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=detail)
if (
"Wystąpił błąd aplikacji. Prosimy zalogować się ponownie. Jeśli problem będzie się powtarzał, prosimy o kontakt z serwisem."
in response.text
):
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="UONET+ error"
)
return response
def encrypt_cookies(key: str, vulcan_cookies: str):
fernet = Fernet(bytes(key, "utf-8"))
cookies = fernet.decrypt((vulcan_cookies).encode())
cookies = ast.literal_eval(cookies.decode("utf-8"))
return cookies

View file

@ -1,7 +0,0 @@
from .student import Student, Semester
from .requests import Login, UonetPlusUczen
from .notes import NotesAndAchievements, Note
from .grades import Grades, Subject, Grade, DescriptiveGrade
from .conferences import Conference
from .school_info import SchoolInfo, School, Teacher
from .mobile_access import Device, TokenResponse

View file

@ -1,13 +0,0 @@
from pydantic import BaseModel
from typing import Optional
from datetime import datetime
class Conference(BaseModel):
title: str
subject: str
agenda: str
present_on_conference: str
online: Optional[str]
id: int
date: str

View file

@ -1,37 +0,0 @@
from pydantic import BaseModel
from typing import Optional
class Grades(BaseModel):
is_average: bool
is_points: bool
subjects: list
descriptive_grades: list
class Subject(BaseModel):
name: str
visible_subject: bool
position: int
average: float
proposed_grade: Optional[str]
fianl_grade: Optional[str]
proposed_points: Optional[str]
final_points: Optional[str]
grades: list
class Grade(BaseModel):
entry: str
color: str
symbol: Optional[str]
description: Optional[str]
weight_value: float
date: str
teacher: str
class DescriptiveGrade(BaseModel):
subject: str
description: str
is_religion_or_ethics: bool

View file

@ -1,15 +0,0 @@
from pydantic import BaseModel
from typing import Optional
class Device(BaseModel):
id: int
name: Optional[str]
create_date: Optional[str]
class TokenResponse(BaseModel):
token: str
symbol: str
pin: str
qr_code_image: str

View file

@ -1,17 +0,0 @@
from pydantic import BaseModel
from typing import Optional
class NotesAndAchievements(BaseModel):
notes: list
achievements: list
class Note(BaseModel):
date: str
teacher: str
category: str
content: str
points: Optional[str]
show_points: bool = False
category_type: int = 0

View file

@ -1,21 +0,0 @@
from pydantic import BaseModel
from typing import Optional
class Login(BaseModel):
username: str
password: str
symbol: str
host: str
ssl: Optional[bool]
class UonetPlusUczen(BaseModel):
host: str
symbol: str
school_id: str
ssl: bool
headers: object
student: object
vulcan_cookies: object
payload: Optional[dict]

View file

@ -1,19 +0,0 @@
from pydantic import BaseModel
class SchoolInfo(BaseModel):
school: object
teachers: list
class School(BaseModel):
name: str
address: str
contact: str
headmaster: str
pedagogue: str
class Teacher(BaseModel):
name: str
subject: str

View file

@ -1,38 +0,0 @@
from pydantic import BaseModel
from typing import Optional
from datetime import datetime
class Semester(BaseModel):
number: int
level: int
start: datetime
end: datetime
class_id: int
unit_id: int
current: bool
id: int
class Student(BaseModel):
id: int
student_id: int
student_name: str
student_second_name: Optional[str]
student_surname: str
is_register: bool
register_id: int
kindergarten_register_id: int
level: int
symbol: str
name: Optional[str]
year: int
start: datetime
end: datetime
full_name: str
school_id: str
school_name: str
school_symbol: str
cookies: object
headers: object
semesters: list

View file

@ -1,30 +0,0 @@
class CUFS:
START: str = "/{SYMBOL}/Account/LogOn?ReturnUrl=%2F{SYMBOL}%2FFS%2FLS%3Fwa%3Dwsignin1.0%26wtrealm%3D{REALM}"
LOGOUT: str = "/{SYMBOL}/FS/LS?wa=wsignout1.0"
class UONETPLUS:
START: str = "/{SYMBOL}/LoginEndpoint.aspx"
GETKIDSLUCKYNUMBERS: str = "/{SYMBOL}/Start.mvc/GetKidsLuckyNumbers"
GETSTUDENTDIRECTORINFORMATIONS: str = "/{SYMBOL}/Start.mvc/GetStudentDirectorInformations"
class UZYTKOWNIK:
NOWAWIADOMOSC_GETJEDNOSTKIUZYTKOWNIKA: str = (
"/{SYMBOL}/NowaWiadomosc.mvc/GetJednostkiUzytkownika"
)
class UCZEN:
START: str = "/{SYMBOL}/{SCHOOLID}/Start"
UCZENDZIENNIK_GET: str = "/{SYMBOL}/{SCHOOLID}/UczenDziennik.mvc/Get"
OCENY_GET: str = "/{SYMBOL}/{SCHOOLID}/Oceny.mvc/Get"
STATYSTYKI_GETOCENYCZASTKOWE: str = "/{SYMBOL}/{SCHOOLID}/Statystyki.mvc/GetOcenyCzastkowe"
UWAGIIOSIAGNIECIA_GET: str = "/{SYMBOL}/{SCHOOLID}/UwagiIOsiagniecia.mvc/Get"
ZEBRANIA_GET: str = "/{SYMBOL}/{SCHOOLID}/Zebrania.mvc/Get"
SZKOLAINAUCZYCIELE_GET: str = "/{SYMBOL}/{SCHOOLID}/SzkolaINauczyciele.mvc/Get"
ZAREJESTROWANEURZADZENIA_GET: str = "/{SYMBOL}/{SCHOOLID}/ZarejestrowaneUrzadzenia.mvc/Get"
ZAREJESTROWANEURZADZENIA_DELETE: str = (
"/{SYMBOL}/{SCHOOLID}/ZarejestrowaneUrzadzenia.mvc/Delete"
)
REJESTRACJAURZADZENIATOKEN_GET: str = "/{SYMBOL}/{SCHOOLID}/RejestracjaUrzadzeniaToken.mvc/Get"

View file

@ -1,25 +0,0 @@
import uvicorn
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from app.endpoints import login, uonetplus_uczen
app = FastAPI(title="Uonetplus API")
app.include_router(login.router)
app.include_router(uonetplus_uczen.router)
origins = [
"http://localhost:8080",
]
app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
if __name__ == "__main__":
uvicorn.run("main:app", host="127.0.0.1", port=8000, reload=True)

View file

@ -1,2 +0,0 @@
[pytest]
addopts = -s -v

View file

@ -1,12 +0,0 @@
beautifulsoup4
bs4
requests
cryptography
pytest
pydantic~=1.9.0
uvicorn~=0.17.5
fastapi~=0.74.0
starlette~=0.17.1
lxml
sty
pytest-xdist

View file

@ -1,421 +0,0 @@
from errno import errorcode
from fastapi.testclient import TestClient
from main import app
import pytest
import json
import requests
client = TestClient(app)
class fg:
lightgreen = "\x1B[38;5;46m"
orange = "\x1B[38;5;208m"
red = "\x1B[38;5;160m"
rs = "\033[0m"
# Ustawienia dla wszystkich testów
nick = "jan@fakelog.cf"
password = "jan123"
host = "fakelog.cf"
backuphost = "fakelog.tk"
symbol = "powiatwulkanowy"
ssl = "false"
# Ustawienia tygodni dla testów
week_grades = "16"
# Ustawienia id dla testów
id_mobile_deleted = 1234
# Ustawienia dla test_login_incorrect i test_symbol_incorrect
nick_invalid = "jan@fakelog.cf"
password_invalid = "Jan321"
symbol_invalid = "warszawa"
def status_check(status_check_code, status_check_json):
if status_check_code == 200:
status_check_response = print("\n" + fg.lightgreen + "OK " + str(status_check_code) + fg.rs)
elif status_check_code == 111:
status_check_response = print("\n" + fg.red + "Connection refused " + str(status_check_code) + fg.rs)
elif status_check_code == 307:
status_check_response = print("\n" + fg.orange + "Temporary redirect " + str(status_check_code) + fg.rs)
elif status_check_code == 308:
status_check_response = print("\n" + fg.orange + "Permanent redirect " + str(status_check_code) + fg.rs)
elif status_check_code == 310:
status_check_response = print("\n" + fg.red + "Too many redirects " + str(status_check_code) + fg.rs)
elif status_check_code == 400:
status_check_response = print("\n" + fg.red + "Bad Request " + str(status_check_code) + fg.rs)
try:
print(json.dumps(status_check_json, indent=4))
except:
print(status_check_json)
elif status_check_code == 401:
status_check_response = print("\n" + fg.red + "Unauthorized " + str(status_check_code) + fg.rs)
elif status_check_code == 403:
status_check_response = print("\n" + fg.red + "Forbidden " + str(status_check_code) + fg.rs)
elif status_check_code == 404:
status_check_response = print("\n" + fg.orange + "Not Found " + str(status_check_code) + fg.rs)
elif status_check_code == 405:
status_check_response = print("\n" + fg.orange + "Method Not Allowed " + str(status_check_code) + fg.rs)
try:
print(json.dumps(status_check_json, indent=4))
except:
print(status_check_json)
elif status_check_code == 408:
status_check_response = print("\n" + fg.orange + "Request Timeout " + str(status_check_code) + fg.rs)
elif status_check_code == 422:
status_check_response = print("\n" + fg.red + "Unprocessable Entity " + str(status_check_code) + fg.rs)
try:
print(json.dumps(status_check_json, indent=4))
except:
print(status_check_json)
elif status_check_code == 429:
status_check_response = print("\n" + fg.red + "Too Many Requests " + str(status_check_code) + fg.rs)
elif status_check_code == 500:
status_check_response = print("\n" + fg.red + "Internal Server Error " + str(status_check_code) + fg.rs)
elif status_check_code == 502:
status_check_response = print("\n" + fg.orange + "Bad Gateway " + str(status_check_code) + fg.rs)
elif status_check_code == 503:
status_check_response = print("\n" + fg.orange + "Service Unavailable " + str(status_check_code) + fg.rs)
elif status_check_code == 504:
status_check_response = print("\n" + fg.orange + "Gateway Timeout " + str(status_check_code) + fg.rs)
elif status_check_code == 505:
status_check_response = print("\n" + fg.orange + "HTTP Version Not Supported " + str(status_check_code) + fg.rs)
elif status_check_code == 521:
status_check_response = print("\n" + fg.red + "Web server is down " + str(status_check_code) + fg.rs)
elif status_check_code == 522:
status_check_response = print("\n" + fg.red + "Connection timed out " + str(status_check_code) + fg.rs)
elif status_check_code == 525:
status_check_response = print("\n" + fg.red + "SSL Handshake Failed " + str(status_check_code) + fg.rs)
elif status_check_code == 526:
status_check_response = print("\n" + fg.red + "Invalid SSL Certificate " + str(status_check_code) + fg.rs)
try:
return status_check_response, status_check_json
except:
return status_check_response
def test_check_connection():
if ssl == "true":
check = requests.get(
"https://fakelog.cf",
)
elif ssl == "false":
check = requests.get(
"http://fakelog.cf",
)
status_check(check.status_code, check.json)
if check.status_code == 503 or check.status_code == 521 or check.status_code == 522 or check.status_code == 525 or check.status_code == 526 or check.status_code == 429 or check.status_code == 408 or check.status_code == 500 or check.status_code == 502 or check.status_code == 504 or check.status_code == 111:
global host
host = backuphost
print(fg.orange + "Main host unavailable. Changed to backup host" + fg.rs)
def test_login_correct():
global cookies, headars, student, school_id, errorcode
login = client.post(
"/login",
headers={"Content-Type": "application/json"},
json={
"username": nick,
"password": password,
"host": host,
"symbol": symbol,
"ssl": ssl,
},
)
cookies = login.json()["vulcan_cookies"]
headars = login.json()["students"][0]["headers"]
student = login.json()["students"][0]["cookies"]
school_id = login.json()["students"][0]["school_id"]
# print(login.json())
if login.status_code == 200:
print("\n" + fg.lightgreen + "OK " + str(login.status_code) + fg.rs)
assert login.json()["symbol"] == "powiatwulkanowy"
try:
assert login.json()["host"] == "fakelog.cf"
except:
assert login.json()["host"] == "fakelog.tk"
if not cookies:
errorcode = 1
print("\nCookies output: ")
print(login.json()["vulcan_cookies"])
pytest.fail("No VULCAN cookies detected")
elif not headars:
errorcode = 2
print("\nHeaders output: ")
print(login.json()["students"][0]["headers"])
pytest.fail("No headers detected")
elif not student:
errorcode = 3
print("\nStudent output: ")
print(login.json()["students"][0]["cookies"])
pytest.fail("No student cookies detected")
elif not school_id:
errorcode = 4
print("\nSchool ID output: ")
print(login.json()["students"][0]["school_id"])
pytest.fail("No school ID detected")
def test_login_incorrect():
if errorcode == 1:
pytest.skip("Skipped due to no cookies detected")
elif errorcode == 2:
pytest.skip("Skipped due to no headers detected")
elif errorcode == 3:
pytest.skip("Skipped due to no student cookies detected")
elif errorcode == 4:
pytest.skip("Skipped due to no school ID detected")
response = client.post(
"/login",
headers={"Content-Type": "application/json"},
json={
"username": nick_invalid,
"password": password_invalid,
"host": host,
"symbol": symbol,
"ssl": ssl,
"json": {},
"headers": headars,
},
)
status_check(response.status_code, response.json())
assert response.json() == {'detail': 'Username or password is incorrect'}
def test_symbol_incorrect():
if errorcode == 1:
pytest.skip("Skipped due to no cookies detected")
elif errorcode == 2:
pytest.skip("Skipped due to no headers detected")
elif errorcode == 3:
pytest.skip("Skipped due to no student cookies detected")
elif errorcode == 4:
pytest.skip("Skipped due to no school ID detected")
response = client.post(
"/login",
headers={"Content-Type": "application/json"},
json={
"username": nick,
"password": password,
"host": host,
"symbol": symbol_invalid,
"ssl": ssl,
"json": {},
"headers": headars,
},
)
status_check(response.status_code, response.json())
assert response.json() == {"detail": "Symbol is incorrect"}
def test_notes():
if errorcode == 1:
pytest.skip("Skipped due to no cookies detected")
elif errorcode == 2:
pytest.skip("Skipped due to no headers detected")
elif errorcode == 3:
pytest.skip("Skipped due to no student cookies detected")
elif errorcode == 4:
pytest.skip("Skipped due to no school ID detected")
response = client.post(
"/uonetplus-uczen/notes",
headers={"Content-Type": "application/json"},
json={
"vulcan_cookies": cookies,
"student": student,
"school_id": school_id,
"host": host,
"symbol": symbol,
"ssl": ssl,
"json": {},
"headers": headars,
},
)
status_check(response.status_code, response.json())
assert response.json()["notes"][0]["teacher"] == "Karolina Kowalska [AN]"
assert response.json()["notes"][1]["content"] == "+ 20p za udział w Konkursie Języka Angielskiego"
#print(response.json())
def test_grades():
if errorcode == 1:
pytest.skip("Skipped due to no cookies detected")
elif errorcode == 2:
pytest.skip("Skipped due to no headers detected")
elif errorcode == 3:
pytest.skip("Skipped due to no student cookies detected")
elif errorcode == 4:
pytest.skip("Skipped due to no school ID detected")
response = client.post(
"/uonetplus-uczen/grades",
headers={"Content-Type": "application/json"},
json={
"vulcan_cookies": cookies,
"student": student,
"school_id": school_id,
"host": host,
"symbol": symbol,
"ssl": ssl,
"json": {"okres": week_grades},
"headers": headars,
},
)
status_check(response.status_code, response.json())
assert response.json()["subjects"][0]["grades"][0]["teacher"] == "Karolina Kowalska"
assert response.json()["subjects"][0]["grades"][0]["symbol"] == "Akt"
# print(response.json())
# assert response.json()['grades'][3]['grade'] == '4'
def test_school_info():
if errorcode == 1:
pytest.skip("Skipped due to no cookies detected")
elif errorcode == 2:
pytest.skip("Skipped due to no headers detected")
elif errorcode == 3:
pytest.skip("Skipped due to no student cookies detected")
elif errorcode == 4:
pytest.skip("Skipped due to no school ID detected")
response = client.post(
"/uonetplus-uczen/school-info",
headers={"Content-Type": "application/json"},
json={
"vulcan_cookies": cookies,
"student": student,
"school_id": school_id,
"host": host,
"symbol": symbol,
"ssl": ssl,
"json": {},
"headers": headars,
},
)
status_check(response.status_code, response.json())
assert (
response.json()["school"]["name"]
== "Publiczna szkoła Wulkanowego nr 1 w fakelog.cf"
)
assert response.json()["teachers"][0]["name"] == "Karolina Kowalska [AN]"
# print(response.json())
def test_conference():
if errorcode == 1:
pytest.skip("Skipped due to no cookies detected")
elif errorcode == 2:
pytest.skip("Skipped due to no headers detected")
elif errorcode == 3:
pytest.skip("Skipped due to no student cookies detected")
elif errorcode == 4:
pytest.skip("Skipped due to no school ID detected")
response = client.post(
"/uonetplus-uczen/conferences",
headers={"Content-Type": "application/json"},
json={
"vulcan_cookies": cookies,
"student": student,
"school_id": school_id,
"host": host,
"symbol": symbol,
"ssl": ssl,
"json": {},
"headers": headars,
},
)
status_check(response.status_code, response.json())
assert (
response.json()[0]["subject"]
== "Podsumowanie I semestru - średnia klasy, oceny, frekwencja, zachowanie."
)
assert response.json()[1]["date"] == "06.09.2019 16:30"
# print(response.json())
def test_mobile_access_registed():
if errorcode == 1:
pytest.skip("Skipped due to no cookies detected")
elif errorcode == 2:
pytest.skip("Skipped due to no headers detected")
elif errorcode == 3:
pytest.skip("Skipped due to no student cookies detected")
elif errorcode == 4:
pytest.skip("Skipped due to no school ID detected")
response = client.post(
"/uonetplus-uczen/mobile-access/get-registered-devices",
headers={"Content-Type": "application/json"},
json={
"vulcan_cookies": cookies,
"student": student,
"school_id": school_id,
"host": host,
"symbol": symbol,
"ssl": ssl,
"json": {},
"headers": headars,
},
)
status_check(response.status_code, response.json())
assert (
response.json()[0]["name"]
== "To Be Filled By O.E.M.#To Be Filled By O.E.M. (Windows 8.1)"
)
assert response.json()[1]["id"] == 1234
# print(response.json())
def test_mobile_access_register():
if errorcode == 1:
pytest.skip("Skipped due to no cookies detected")
elif errorcode == 2:
pytest.skip("Skipped due to no headers detected")
elif errorcode == 3:
pytest.skip("Skipped due to no student cookies detected")
elif errorcode == 4:
pytest.skip("Skipped due to no school ID detected")
response = client.post(
"/uonetplus-uczen/mobile-access/register-device",
headers={"Content-Type": "application/json"},
json={
"vulcan_cookies": cookies,
"student": student,
"school_id": school_id,
"host": host,
"symbol": symbol,
"ssl": ssl,
},
)
status_check(response.status_code, response.json())
assert response.json()["pin"] == "999999"
assert response.json()["qr_code_image"]
# print(response.json())
def test_mobile_access_delete_registed():
if errorcode == 1:
pytest.skip("Skipped due to no cookies detected")
elif errorcode == 2:
pytest.skip("Skipped due to no headers detected")
elif errorcode == 3:
pytest.skip("Skipped due to no student cookies detected")
elif errorcode == 4:
pytest.skip("Skipped due to no school ID detected")
response = client.post(
"/uonetplus-uczen/mobile-access/delete-registered-device",
headers={"Content-Type": "application/json"},
json={
"vulcan_cookies": cookies,
"student": student,
"school_id": school_id,
"host": host,
"symbol": symbol,
"ssl": ssl,
"json": {"id": id_mobile_deleted},
"headers": headars,
},
)
status_check(response.status_code, response.json())
# Nowa metoda testowania
# if response.status_code == 404:
# print(response.json())
# else:
# print("Test")
assert response.json()["success"] == True
# print(response.json())

View file

@ -3,17 +3,24 @@ module.exports = {
env: {
node: true,
},
extends: ["plugin:vue/essential", "eslint:recommended", "@vue/typescript/recommended"],
extends: [
'plugin:vue/essential',
'@vue/airbnb',
'@vue/typescript/recommended',
],
parserOptions: {
ecmaVersion: 2020,
},
rules: {
"no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
"no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off",
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
},
overrides: [
{
files: ["**/__tests__/*.{j,t}s?(x)", "**/tests/unit/**/*.spec.{j,t}s?(x)"],
files: [
'**/__tests__/*.{j,t}s?(x)',
'**/tests/unit/**/*.spec.{j,t}s?(x)',
],
env: {
mocha: true,
},

1
frontend/.gitignore vendored
View file

@ -24,4 +24,3 @@ pnpm-debug.log*
*.njsproj
*.sln
*.sw?

View file

@ -1,13 +1,34 @@
# Wulkanowy Web Frontend
# frontend
## 1. Install dependencies
```sh
## Project setup
```
npm install
```
## 2. Start the server
```sh
### Compiles and hot-reloads for development
```
npm run serve
```
### Compiles and minifies for production
```
npm run build
```
### Run your unit tests
```
npm run test:unit
```
### Run your end-to-end tests
```
npm run test:e2e
```
### Lints and fixes files
```
npm run lint
```
### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).

0
frontend/__init__.py Normal file
View file

3
frontend/admin.py Normal file
View file

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

5
frontend/apps.py Normal file
View file

@ -0,0 +1,5 @@
from django.apps import AppConfig
class FrontendConfig(AppConfig):
name = 'frontend'

5
frontend/babel.config.js Normal file
View file

@ -0,0 +1,5 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset',
],
};

3
frontend/cypress.json Normal file
View file

@ -0,0 +1,3 @@
{
"pluginsFile": "tests/e2e/plugins/index.js"
}

View file

3
frontend/models.py Normal file
View file

@ -0,0 +1,3 @@
from django.db import models
# Create your models here.

29396
frontend/package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -3,17 +3,20 @@
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve --openssl-legacy-provider",
"build": "vue-cli-service build --openssl-legacy-provider",
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"test:unit": "vue-cli-service test:unit",
"test:e2e": "vue-cli-service test:e2e",
"lint": "vue-cli-service lint --openssl-legacy-provider"
"lint": "vue-cli-service lint"
},
"dependencies": {
"axios": "^0.26.1",
"axios": "^0.21.1",
"core-js": "^3.6.5",
"register-service-worker": "^1.7.1",
"universal-cookie": "^4.0.4",
"vue": "^2.6.11",
"vue-router": "^3.2.0",
"vuetify": "^2.6.4",
"vuetify": "^2.4.0",
"vuex": "^3.4.0"
},
"devDependencies": {
@ -21,26 +24,28 @@
"@types/mocha": "^5.2.4",
"@typescript-eslint/eslint-plugin": "^4.18.0",
"@typescript-eslint/parser": "^4.18.0",
"@vue/cli-plugin-e2e-cypress": "~4.5.13",
"@vue/cli-plugin-eslint": "~4.5.13",
"@vue/cli-plugin-router": "~4.5.13",
"@vue/cli-plugin-typescript": "~4.5.13",
"@vue/cli-plugin-unit-mocha": "~4.5.13",
"@vue/cli-plugin-vuex": "~4.5.13",
"@vue/cli-service": "~4.5.13",
"@vue/cli-plugin-babel": "~4.5.0",
"@vue/cli-plugin-e2e-cypress": "~4.5.0",
"@vue/cli-plugin-eslint": "~4.5.0",
"@vue/cli-plugin-pwa": "~4.5.0",
"@vue/cli-plugin-router": "~4.5.0",
"@vue/cli-plugin-typescript": "~4.5.0",
"@vue/cli-plugin-unit-mocha": "~4.5.0",
"@vue/cli-plugin-vuex": "~4.5.0",
"@vue/cli-service": "~4.5.0",
"@vue/eslint-config-airbnb": "^5.0.2",
"@vue/eslint-config-typescript": "^7.0.0",
"@vue/test-utils": "^1.0.3",
"chai": "^4.1.2",
"eslint": "^6.7.2",
"eslint-plugin-import": "^2.20.2",
"eslint-plugin-vue": "^6.2.2",
"sass": "~1.32.0",
"node-sass": "^4.12.0",
"sass": "^1.32.0",
"sass-loader": "^10.0.0",
"typescript": "~4.1.5",
"vue-cli-plugin-vuetify": "~2.4.8",
"vue-cli-plugin-vuetify": "~2.3.1",
"vue-template-compiler": "^2.6.11",
"vuetify-loader": "^1.7.0"
},
"engines": {
"node": "16.x"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 103 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

View file

@ -1,26 +1,18 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
<link rel="icon" href="<%= BASE_URL %>favicon.ico" />
<title>Wulkanowy</title>
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900"
/>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/@mdi/font@latest/css/materialdesignicons.min.css"
/>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<link rel="stylesheet" href="//fonts.googleapis.com/css?family=Roboto:400,500,700,400italic|Material+Icons">
<title><%= htmlWebpackPlugin.options.title %></title>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@mdi/font@latest/css/materialdesignicons.min.css">
</head>
<body>
<noscript>
<strong
>We're sorry but Wulkanowy doesn't work properly without JavaScript
enabled. Please enable it to continue.</strong
>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->

View file

@ -1,46 +1,13 @@
<template>
<v-app>
<router-view />
<router-view></router-view>
</v-app>
</template>
<script lang="ts">
import Vue from "vue";
import Vue from 'vue';
export default Vue.extend({
name: "App",
beforeMount() {
let darkTheme = localStorage.getItem("dark_theme");
if (darkTheme) {
if (darkTheme == "true") {
this.$vuetify.theme.dark = true;
} else {
this.$vuetify.theme.dark = false;
}
} else {
localStorage.setItem("dark_theme", "false");
}
},
created() {
window.addEventListener("resize", this.handleResize);
this.handleResize();
},
destroyed() {
window.removeEventListener("resize", this.handleResize);
},
methods: {
handleResize() {
const screen_width = window.innerWidth;
this.$store.state.small_ui = screen_width < 1264;
},
},
name: 'App',
});
</script>
<style lang="scss">
.v-card__text,
.v-card__title {
word-break: normal !important;
}
</style>

View file

@ -1,45 +0,0 @@
import axios from 'axios';
import store from '@/store/index';
export default {
login: async (username: string, password: string, symbol: string, host: string, ssl: boolean) => {
const response: any = await axios({
method: 'POST',
url: 'http://localhost:8000/login',
headers: {
'Content-Type': 'application/json',
},
data: {
username,
password,
symbol,
host,
ssl,
},
withCredentials: true,
})
.catch(function (error: any) {
if(error.toJSON().message == 'Network Error'){
store.state.error.description = 'No internet connection';
store.state.error.details = error.toJSON().stack;
store.state.error.show = true;
store.state.loading = false;
} else {
store.state.error.description = error.response.data.detail;
store.state.error.details = error.toJSON().stack;
store.state.error.show = true;
store.state.loading = false;
}
});
if (!response.data.students.length) {
store.state.error.description = 'This account have not any students';
store.state.error.show = true;
store.state.loading = false;
return null;
} else {
store.state.logged_in = true;
store.state.loading = false;
return response;
}
},
};

27
frontend/src/api/login.ts Normal file
View file

@ -0,0 +1,27 @@
import axios, { AxiosResponse } from 'axios';
import Cookies from 'universal-cookie';
export default {
login: async (email: string, password: string, symbol: string, diaryUrl: string)
: Promise<AxiosResponse> => {
const cookies = new Cookies();
const response = await axios({
method: 'POST',
url: 'http://localhost:8000/api/login',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': cookies.get('csrftoken'),
},
data: {
loginName: email,
Password: password,
Symbol: symbol,
diaryUrl,
},
withCredentials: true,
});
document.cookie = response.headers['Set-Cookie'];
return response;
},
};

View file

@ -0,0 +1,12 @@
{
"diaries": [
{
"name": "Vulcan",
"url": "https://cufs.vulcan.net.pl/"
},
{
"name": "Fakelog",
"url": "http://cufs.fakelog.tk/"
}
]
}

View file

@ -0,0 +1,129 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
height="1024"
width="3584"
xml:space="preserve"
viewBox="0 0 3584 1024"
y="0px"
x="0px"
id="Layer_1"
version="1.1"
sodipodi:docname="wulkanowy-full-flat.svg"
inkscape:version="0.92.2 (5c3e80d, 2017-08-06)"><sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1017"
id="namedview25"
showgrid="false"
inkscape:zoom="0.33928571"
inkscape:cx="1860.4549"
inkscape:cy="586.56969"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="text8460" /><metadata
id="metadata15"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title /></cc:Work></rdf:RDF></metadata><defs
id="defs13" /><style
id="style2"
type="text/css">
.st0{fill:#D32F2F;}
.st1{fill:#AD2A2A;}
.st2{fill:#FFFFFF;}
</style><g
style="display:none"
id="layer4"><rect
style="display:inline;fill:#d32f2f;stroke-width:1.02195609"
height="1024"
width="3584"
class="st0"
y="0"
x="0"
id="XMLID_57_" /></g><g
style="display:none"
id="layer3"><path
id="path18992"
d="M 3046.8164,390.66602 3134.3164,542 v 91.33398 L 3524.9824,1024 H 3584 V 732.18359 L 3242.4824,390.66602 h -23.666 l -53.0352,94.63086 -94.6308,-94.63086 z"
style="font-style:normal;font-variant:normal;font-weight:300;font-stretch:normal;font-size:341.33334351px;line-height:1.25;font-family:Roboto;-inkscape-font-specification:'Roboto Light';letter-spacing:0px;word-spacing:0px;display:inline;fill:#ad2a2a;fill-opacity:1;stroke:none" /><path
id="path18990"
d="m 2746.9824,390.66602 62,242.66796 L 3199.6484,1024 H 3584 V 940.68359 L 3033.9824,390.66602 h -21 l -21.9043,90.92773 -90.9277,-90.92773 h -18.5 l -25.4043,88.26367 -88.2637,-88.26367 z"
style="font-style:normal;font-variant:normal;font-weight:300;font-stretch:normal;font-size:341.33334351px;line-height:1.25;font-family:Roboto;-inkscape-font-specification:'Roboto Light';letter-spacing:0px;word-spacing:0px;display:inline;fill:#ad2a2a;fill-opacity:1;stroke:none" /><path
id="path18988"
d="m 2620.8164,387.33398 c -18.6667,0 -35.1667,4.60982 -49.5,13.83204 -14.3333,9.11111 -25.4451,22.22287 -33.334,39.33398 -7.7778,17 -11.666,36.5549 -11.666,58.66602 v 25 c 0,34.44444 8.7216,61.83463 26.166,82.16796 L 2970.1484,1024 h 323.168 l -623.166,-623.16602 c -14.2222,-9 -30.6673,-13.5 -49.334,-13.5 z"
style="font-style:normal;font-variant:normal;font-weight:300;font-stretch:normal;font-size:341.33334351px;line-height:1.25;font-family:Roboto;-inkscape-font-specification:'Roboto Light';letter-spacing:0px;word-spacing:0px;display:inline;fill:#ad2a2a;fill-opacity:1;stroke:none" /><path
id="path18984"
d="M 2293.4824,390.66602 V 633.33398 L 2684.1484,1024 h 423.336 l -633.334,-633.33398 h -20.334 v 139.66601 l -139.666,-139.66601 z"
style="font-style:normal;font-variant:normal;font-weight:300;font-stretch:normal;font-size:341.33334351px;line-height:1.25;font-family:Roboto;-inkscape-font-specification:'Roboto Light';letter-spacing:0px;word-spacing:0px;display:inline;fill:#ad2a2a;fill-opacity:1;stroke:none" /><path
id="path18978"
d="M 1864.8164,390.66602 V 633.33398 L 2255.4824,1024 h 413.334 l -633.334,-633.33398 h -25.832 l -60.584,63.75 -63.75,-63.75 z"
style="font-style:normal;font-variant:normal;font-weight:300;font-stretch:normal;font-size:341.33334351px;line-height:1.25;font-family:Roboto;-inkscape-font-specification:'Roboto Light';letter-spacing:0px;word-spacing:0px;display:inline;fill:#ad2a2a;fill-opacity:1;stroke:none" /><path
id="path18976"
d="M 1684.8164,390.66602 V 633.33398 L 2075.4824,1024 h 263.334 l -633.334,-633.33398 z"
style="font-style:normal;font-variant:normal;font-weight:300;font-stretch:normal;font-size:341.33334351px;line-height:1.25;font-family:Roboto;-inkscape-font-specification:'Roboto Light';letter-spacing:0px;word-spacing:0px;display:inline;fill:#ad2a2a;fill-opacity:1;stroke:none" /><path
id="path19059"
d="m 1133.6504,390.66602 62,242.66796 L 1586.3164,1024 h 467.668 l -633.334,-633.33398 h -21 l -21.9043,90.92773 -90.9277,-90.92773 h -18.5 l -25.4043,88.26367 -88.2637,-88.26367 z"
style="font-style:normal;font-variant:normal;font-weight:300;font-stretch:normal;font-size:341.33334351px;line-height:1.25;font-family:Roboto;-inkscape-font-specification:'Roboto Light';letter-spacing:0px;word-spacing:0px;display:inline;fill:#ad2a2a;fill-opacity:1;stroke:none" /><path
id="path18966"
d="m 1456.4824,390.66602 v 167.16796 c 0.5556,24.66667 8.5007,44 23.834,58 L 1888.4824,1024 h 372.168 l -633.334,-633.33398 h -20.666 V 520.5 l -129.834,-129.83398 z"
style="font-style:normal;font-variant:normal;font-weight:300;font-stretch:normal;font-size:341.33334351px;line-height:1.25;font-family:Roboto;-inkscape-font-specification:'Roboto Light';letter-spacing:0px;word-spacing:0px;display:inline;fill:#ad2a2a;fill-opacity:1;stroke:none" /><path
id="path18982"
d="M 2146.3164,390.66602 2054.4824,633.33398 2445.1484,1024 h 354.002 l -633.334,-633.33398 z"
style="font-style:normal;font-variant:normal;font-weight:300;font-stretch:normal;font-size:341.33334351px;line-height:1.25;font-family:Roboto;-inkscape-font-specification:'Roboto Light';letter-spacing:0px;word-spacing:0px;display:inline;fill:#ad2a2a;fill-opacity:1;stroke:none" /><path
id="XMLID_64_"
d="M 637.15234,214.95703 487.75,364.35742 466.01562,386.0918 c 0.31273,0.31271 0.54872,0.54666 0.70508,0.85937 0.0782,0.23454 0.23432,0.54671 0.3125,0.78125 0.31272,0.54726 0.47071,1.17339 0.47071,1.79883 0.0782,0.54726 -0.0799,1.01725 -0.31446,1.48633 -0.23454,0.54725 -0.70285,1.40597 -1.09375,1.79687 l 150.8086,149.71485 -23.68946,23.6875 -12.74414,-12.74219 -13.44726,-13.44727 -78.80469,-78.80664 -11.17969,-11.17968 -7.5039,-7.50391 -35.41602,-35.17969 -3.08984,-0.98047 -4.33594,4.26367 v 0.46876 c 0,7.34888 0.38998,15.00865 -1.48633,22.20117 -0.85998,3.28355 -2.34444,6.25595 -4.14258,8.91406 -0.15636,0.15636 -0.23627,0.23426 -0.31445,0.39062 -1.87631,2.57993 -4.06471,4.84619 -6.48828,6.95704 -5.3944,4.53442 -11.25752,8.67896 -17.27734,12.50976 -0.15637,0.0782 -0.23427,0.1562 -0.39063,0.23438 -2.11085,1.40723 -4.3012,2.7354 -6.49023,4.06445 -8.91248,5.39439 -18.37192,10.08772 -28.37891,13.13672 -1.25087,0.31272 -2.42317,-0.001 -3.36133,-0.70508 l -6.01953,5.94141 c 1.25087,0.62543 2.03136,1.87776 1.875,3.51953 -10e-6,0.15636 -0.0762,0.23231 -0.0762,0.38867 0,0.0782 -0.0781,0.23628 -0.0781,0.31445 -1.32905,4.45624 -2.34505,8.98897 -3.2832,13.60156 -0.15636,0.70363 -0.23622,1.33154 -0.39258,2.03516 -0.85997,4.37806 -1.64209,8.83288 -2.3457,13.21094 0.23453,5.3944 0.39263,11.0234 0.31445,16.65234 v 0.39258 c -0.0782,7.66161 -0.78373,15.32114 -2.8164,22.51367 -2.26721,8.28704 -6.64376,15.63728 -10.55274,23.22071 -0.0782,0.15636 -0.15815,0.23426 -0.23633,0.39062 -1.25088,2.42357 -2.49924,4.92399 -3.59375,7.50391 -4.84714,11.33605 -7.42749,23.92328 -10.55468,35.88476 -0.23454,0.70362 -0.39046,1.48578 -0.625,2.26758 0,0.15636 -0.0801,0.23427 -0.0801,0.39063 -2.97082,11.10151 -6.09819,22.28173 -10.94532,32.75781 -1.40724,2.97082 -2.81531,5.86322 -4.3789,8.75586 -0.15636,0.23454 -0.23231,0.46858 -0.38867,0.70312 -0.62544,1.09451 -1.25152,2.26871 -1.87696,3.44141 -0.0782,0.15636 -0.15619,0.23426 -0.23437,0.39062 -3.51809,6.25438 -7.27098,12.43118 -10.78906,18.68555 -5.0035,8.8343 -8.99075,18.13635 -13.83789,27.04883 -0.0782,0.15636 -0.1562,0.23426 -0.23438,0.39062 -0.70362,1.32905 -1.48579,2.65728 -2.26758,3.98633 -5.0035,8.20887 -10.63256,16.0279 -16.57422,23.61133 -0.15635,0.15636 -0.23426,0.3124 -0.39062,0.46875 -0.7818,1.01634 -1.48578,1.95443 -2.26758,2.89258 -3.90898,4.92532 -7.97378,9.85009 -11.96094,14.77539 -0.0782,0.15637 -0.23432,0.23622 -0.3125,0.39258 -8.75612,10.71061 -17.35628,21.49761 -24.54883,33.30273 0,0.70362 -0.15602,1.33159 -0.46874,1.95703 -1.25087,2.42357 -2.65734,4.68971 -3.90821,7.11328 -0.0782,0.15636 0.62511,1.24989 0.46875,1.40625 L 429.86133,1024 H 1463.0215 L 661.85547,222.92969 c -0.93816,2.11087 -5.23681,1.40935 -7.34766,-0.23242 -1.71995,-1.32906 -3.12603,-3.05147 -4.45508,-4.84961 -0.62544,-0.31271 -1.25168,-0.62288 -1.64257,-0.85743 -2.89265,-1.40723 -6.09933,-1.48632 -9.30469,-1.48632 -0.7818,-0.0782 -1.40588,-0.23416 -1.95313,-0.54688 z m -206.12304,191.41992 0.11914,-0.11523 -0.23438,0.0781 z"
style="display:inline;fill:#ad2a2a;stroke-width:0.78179646" /></g><g
style="display:inline"
id="layer2"><path
style="fill:#ffffff;stroke-width:0.78179646"
d="m 385.17894,776.98989 c 0.62544,2.03267 0.31272,4.2217 -0.7818,6.01983 l -15.08867,24.47023 c -1.48541,2.42357 -4.2217,3.98716 -7.19253,3.98716 H 220.92351 c -6.56709,0 -10.55425,-6.8798 -7.03616,-12.03966 l 90.92292,-133.92173 c 0.54726,-0.78179 0.85997,-1.64177 1.09451,-2.50174 l 41.66975,-177.93687 c 0.46908,-2.11085 1.87631,-3.90898 3.90898,-5.00349 l 56.4457,-30.95914 c 2.03267,-1.09452 3.43991,-2.89265 3.90899,-5.08168 l 7.58342,-33.06999 c 1.56359,-6.8798 11.41423,-8.36522 15.32321,-2.34538 l 2.73629,4.37806 c 1.17269,1.87631 1.48541,4.2217 0.70362,6.33255 l -51.44221,148.69767 c -0.3909,1.1727 -0.54726,2.42357 -0.31272,3.67445 l 12.82146,71.14347 c 0.23454,1.40723 0.0782,2.89265 -0.54725,4.2217 l -30.56824,68.64172 c -0.70362,1.64178 -0.85998,3.43991 -0.31272,5.08168 z m 417.94836,34.47722 h -97.25548 c -2.81446,0 -5.39439,-1.40723 -6.95798,-3.59626 l -65.43636,-92.01744 c -0.3909,-0.62543 -0.7818,-1.25087 -1.01634,-1.95449 L 600.01659,619.1452 c -0.62544,-1.87631 -2.11085,-3.51809 -3.98716,-4.45624 L 516.59891,574.5828 c -2.18903,-1.09451 -3.75262,-3.12718 -4.2217,-5.39439 l -11.72694,-58.16566 c -0.15636,-0.93815 -0.54726,-1.87631 -1.1727,-2.73628 L 472.5056,468.64939 c -1.48542,-2.18903 -1.71995,-4.84714 -0.62544,-7.19253 l 16.41772,-35.10266 c 1.25088,-2.6581 3.98717,-4.45624 7.11435,-4.6126 l 30.49006,-1.79813 c 1.79813,-0.0782 3.43991,-0.70361 4.76896,-1.79813 l 22.43756,-17.43406 c 4.2217,-3.28354 10.63243,-1.87631 12.89964,2.73629 l 77.31966,157.29744 c 0.31272,0.70361 0.54726,1.40723 0.70362,2.11085 l 9.53792,63.24733 c 0.23453,1.48541 0.85997,2.81446 1.87631,3.90898 l 154.01389,168.5553 c 4.53442,5.0035 0.70362,12.89964 -6.33255,12.89964 z M 424.97238,319.32628 c 0,-17.90314 11.33604,-33.30453 27.59741,-40.49706 -1.1727,-1.95449 -1.87631,-4.06534 -1.87631,-6.33255 0,-8.67794 10.00699,-15.71411 22.2812,-15.71411 0.31271,0 0.54725,0 0.85997,0 5.08168,-8.4434 14.77595,-14.15051 25.87746,-14.15051 0.7818,0 1.5636,0 2.34539,0.0782 1.01634,0.0782 2.03267,-0.3909 2.57993,-1.1727 8.20886,-12.50874 29.00465,-21.42122 53.31851,-21.42122 8.99066,0 17.51224,1.25088 25.09567,3.36173 1.79813,-2.97083 5.78529,-5.0035 10.39789,-5.0035 5.23804,0 9.69428,2.65811 11.02333,6.33255 6.41073,-7.42707 17.2777,-12.2742 29.63008,-12.2742 19.70127,0 35.64992,12.43056 35.64992,27.75377 0,1.87631 -0.23454,3.67444 -0.70362,5.47257 -0.31271,1.25088 0.3909,2.50175 1.71996,2.97083 12.58692,4.6126 21.1085,13.36872 21.1085,23.37571 0,11.41423 -11.02333,21.18669 -26.58108,25.01749 -1.25087,0.31272 -2.03267,1.40723 -2.03267,2.57993 0,0.0782 0,0.15635 0,0.23453 0,8.8343 -8.75612,16.10501 -20.01399,16.88681 0.0782,0.3909 0.0782,0.78179 0.0782,1.25087 0,16.96498 -32.28819,30.64642 -72.08163,30.64642 -8.59976,0 -16.80862,-0.62544 -24.39204,-1.79813 0,0.15636 0,0.23454 0,0.3909 0,7.03616 -9.06884,12.74328 -20.17035,12.74328 -0.62544,0 -1.1727,0 -1.71995,-0.0782 0.78179,1.71995 1.17269,3.51808 1.17269,5.39439 0,10.94515 -13.8378,19.77945 -30.95914,19.77945 -1.87631,0 -3.67444,-0.0782 -5.47257,-0.31272 -1.40724,-0.15635 -2.73629,0.70362 -3.04901,2.03268 -1.48541,5.47257 -6.25437,9.45973 -11.96148,9.45973 -6.87981,0 -12.35239,-5.86347 -12.35239,-13.056 0,-3.36172 1.1727,-6.41073 3.12719,-8.67794 0.70362,-0.78179 0.93816,-1.87631 0.46908,-2.81447 -1.09452,-2.03267 -1.5636,-4.14352 -1.5636,-6.41073 v 0 c 0,-1.25087 -1.01633,-2.26721 -2.2672,-2.57992 -21.18669,-4.69078 -37.13533,-22.35938 -37.13533,-43.46788 z m 179.50045,424.6718 c 0.31272,0.93816 0.3909,1.87632 0.3909,2.89265 l -4.53442,57.30568 c -0.31272,4.06534 -3.90898,7.2707 -8.28704,7.2707 H 445.68998 c -3.12718,0 -6.01983,-1.79813 -7.42707,-4.37806 l -24.1575,-44.87511 c -0.15636,-0.23454 -0.23454,-0.46908 -0.31272,-0.70362 l -22.75028,-55.11664 c -0.85997,-1.95449 -0.70361,-4.2217 0.23454,-6.09802 l 37.76077,-73.87976 c 0.93815,-1.79813 1.09451,-3.90898 0.3909,-5.78529 l -18.4504,-51.12948 c -0.62544,-1.79814 -0.54726,-3.83081 0.23454,-5.55076 l 30.80278,-65.59272 c 3.12718,-6.56709 13.36872,-6.01983 15.55775,0.85998 l 9.61609,29.86462 29.31737,78.49236 c 0.78179,2.18903 2.6581,3.90898 5.00349,4.69078 l 62.54372,21.34304 c 2.42357,0.85998 4.29988,2.65811 5.08167,4.92532 z"
id="XMLID_42_" /><g
id="text4752"
style="font-style:normal;font-variant:normal;font-weight:300;font-stretch:normal;font-size:341.33334351px;line-height:1.25;font-family:Roboto;-inkscape-font-specification:'Roboto Light';letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"
aria-label="WULKANOWY" /></g><g
style="display:inline"
id="layer1"><g
aria-label="WULKANOWY"
style="font-style:normal;font-variant:normal;font-weight:300;font-stretch:normal;font-size:341.33334351px;line-height:1.25;font-family:Roboto;-inkscape-font-specification:'Roboto Light';letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"
id="text8460"><path
d="m 1198.9832,567.83331 7.5,37.66667 9.1666,-36 52.6667,-178.83334 h 18.5 l 52,178.83334 9,36.33333 8,-38 43.8333,-177.16667 h 21 l -62.1666,242.66667 h -19.6667 l -55,-189.83334 -6.1667,-24 -6,24 -56.3333,189.83334 h -19.6667 l -62,-242.66667 h 21 z"
style="font-style:normal;font-variant:normal;font-weight:300;font-stretch:normal;font-size:341.33334351px;font-family:Roboto;-inkscape-font-specification:'Roboto Light';fill:#ffffff"
id="path4764" /><path
d="m 1627.3165,390.66664 v 165.66667 q -0.1667,24.5 -10.8333,42.66667 -10.6667,18.16667 -30.1667,28 -19.3333,9.66667 -44.5,9.66667 -38.3333,0 -61.5,-20.83334 -23,-21 -23.8333,-58 V 390.66664 h 20.3333 v 164.16667 q 0,30.66667 17.5,47.66667 17.5,16.83333 47.5,16.83333 30,0 47.3333,-17 17.5,-17 17.5,-47.33333 V 390.66664 Z"
style="font-style:normal;font-variant:normal;font-weight:300;font-stretch:normal;font-size:341.33334351px;font-family:Roboto;-inkscape-font-specification:'Roboto Light';fill:#ffffff"
id="path4766" /><path
d="m 1705.4832,615.99998 h 119.8333 v 17.33333 h -140.5 V 390.66664 h 20.6667 z"
style="font-style:normal;font-variant:normal;font-weight:300;font-stretch:normal;font-size:341.33334351px;font-family:Roboto;-inkscape-font-specification:'Roboto Light';fill:#ffffff"
id="path4768" /><path
d="m 1919.4832,512.83331 -34.1667,33.66667 v 86.83333 h -20.5 V 390.66664 h 20.5 v 130.83334 l 124.3333,-130.83334 h 25.8334 l -101.6667,108 109.5,134.66667 h -25 z"
style="font-style:normal;font-variant:normal;font-weight:300;font-stretch:normal;font-size:341.33334351px;font-family:Roboto;-inkscape-font-specification:'Roboto Light';fill:#ffffff"
id="path4770" /><path
d="m 2211.1498,565.33331 h -110.1666 l -25,68 h -21.5 l 91.8333,-242.66667 h 19.5 l 91.8333,242.66667 h -21.3333 z m -103.8333,-17.5 h 97.3333 l -48.6666,-132.16667 z"
style="font-style:normal;font-variant:normal;font-weight:300;font-stretch:normal;font-size:341.33334351px;font-family:Roboto;-inkscape-font-specification:'Roboto Light';fill:#ffffff"
id="path4772" /><path
d="m 2474.1499,633.33331 h -20.5 l -139.5,-207.66667 v 207.66667 h -20.6667 V 390.66664 h 20.6667 l 139.6666,207.83334 V 390.66664 h 20.3334 z"
style="font-style:normal;font-variant:normal;font-weight:300;font-stretch:normal;font-size:341.33334351px;font-family:Roboto;-inkscape-font-specification:'Roboto Light';fill:#ffffff"
id="path4774" /><path
d="m 2715.4832,525.16664 q 0,33.5 -11.6667,58.83334 -11.5,25.33333 -33,39 -21.5,13.66667 -49.6667,13.66667 -42.5,0 -68.6666,-30.33334 -26.1667,-30.5 -26.1667,-82.16667 v -25 q 0,-33.16667 11.6667,-58.66667 11.8333,-25.66666 33.3333,-39.33333 21.5,-13.83333 49.5,-13.83333 28,0 49.3333,13.5 21.5,13.5 33.1667,38.16666 11.6667,24.66667 12.1667,57.16667 z m -20.5,-26.33333 q 0,-43.83334 -19.8334,-68.66667 -19.8333,-24.83333 -54.3333,-24.83333 -33.8333,0 -54,25 -20,24.83333 -20,69.5 v 25.33333 q 0,43.16667 20,68.50001 20,25.16666 54.3333,25.16666 34.8334,0 54.3334,-24.83333 19.5,-25 19.5,-69.5 z"
style="font-style:normal;font-variant:normal;font-weight:300;font-stretch:normal;font-size:341.33334351px;font-family:Roboto;-inkscape-font-specification:'Roboto Light';fill:#ffffff"
id="path4776" /><path
d="m 2812.3165,567.83331 7.5,37.66667 9.1667,-36 52.6667,-178.83334 h 18.5 l 52,178.83334 9,36.33333 8,-38 43.8333,-177.16667 h 21 l -62.1667,242.66667 h -19.6666 l -55,-189.83334 -6.1667,-24 -6,24 -56.3333,189.83334 h -19.6667 l -62,-242.66667 h 21 z"
style="font-style:normal;font-variant:normal;font-weight:300;font-stretch:normal;font-size:341.33334351px;font-family:Roboto;-inkscape-font-specification:'Roboto Light';fill:#ffffff"
id="path4778" /><path
d="m 3144.6499,522.99998 74.1666,-132.33334 h 23.6667 l -87.6667,151.33334 v 91.33333 h -20.5 v -91.33333 l -87.5,-151.33334 h 24.3334 z"
style="font-style:normal;font-variant:normal;font-weight:300;font-stretch:normal;font-size:341.33334351px;font-family:Roboto;-inkscape-font-specification:'Roboto Light';fill:#ffffff"
id="path4780" /></g></g></svg>

After

Width:  |  Height:  |  Size: 18 KiB

View file

@ -1,12 +0,0 @@
[
{
"name": "Vulcan",
"host": "vulcan.net.pl",
"ssl": true
},
{
"name": "Fakelog",
"host": "fakelog.cf",
"ssl": false
}
]

File diff suppressed because it is too large Load diff

View file

Before

Width:  |  Height:  |  Size: 203 KiB

After

Width:  |  Height:  |  Size: 203 KiB

View file

Before

Width:  |  Height:  |  Size: 6.2 KiB

After

Width:  |  Height:  |  Size: 6.2 KiB

View file

@ -1,84 +0,0 @@
<template>
<div id="baner" class="d-flex justify-center align-center fill-height flex-column">
<img
:src="`${
this.$vuetify.theme.dark
? require('../../assets/img/black_icon.svg')
: require('../../assets/img/white_icon.svg')
}`"
width="140"
/>
<v-card-title
:class="`text-center justify-center pa-2 headline ${
this.$vuetify.theme.dark ? 'black--text' : 'white--text'
}`"
>
Wulkanowy
</v-card-title>
<v-card-text
:class="`${this.$vuetify.theme.dark ? 'black--text' : 'white--text'} text-center pa-2`"
>
Unofficial VULCAN UONET+ browser client for students and their parents
</v-card-text>
<v-card-subtitle
:class="`text-center justify-center mt-6 ${
this.$vuetify.theme.dark ? 'black--text' : 'white--text'
}`"
>
<v-btn
icon
v-for="(item, i) in social"
:key="i"
:href="item.link"
target="_blank"
:light="$vuetify.theme.dark"
:dark="!$vuetify.theme.dark"
>
<v-icon>{{ item.icon }}</v-icon>
</v-btn>
</v-card-subtitle>
<v-card-subtitle
:class="`text-center justify-center ${
this.$vuetify.theme.dark ? 'black--text' : 'white--text'
}`"
>
feature/add-new-backend-and-rewrite-login
</v-card-subtitle>
</div>
</template>
<script lang="ts">
import Vue from "vue";
interface BanerData {
social: any;
}
export default Vue.extend({
name: "Baner",
data: (): BanerData => ({
social: [
{
icon: "mdi-web",
link: "https://wulkanowy.github.io",
},
{
icon: "mdi-github",
link: "https://github.com/wulkanowy",
},
{
icon: "mdi-twitter",
link: "https://twitter.com/wulkanowy",
},
{
icon: "mdi-facebook",
link: "https://facebook.com/wulkanowy",
},
{
icon: "mdi-discord",
link: "https://discord.gg/vccAQBr",
},
],
}),
});
</script>

View file

@ -1,12 +0,0 @@
<template>
<div id="loading" class="d-flex fill-height justify-center align-center">
<v-progress-circular indeterminate color="primary" />
</div>
</template>
<script lang="ts">
import Vue from "vue";
export default Vue.extend({
name: "Loading",
});
</script>

View file

@ -1,144 +0,0 @@
<template>
<div id="login-form" class="fill-height">
<v-form @submit.prevent="login" class="d-flex fill-height flex-column">
<v-card-title class="d-flex justify-center text-center"
>Sign in with the student or parent account</v-card-title
>
<div class="px-5">
<v-text-field outlined label="Email" v-model="loginData.username" :rules="[rules.required]" />
<v-text-field
outlined
label="Password"
v-model="loginData.password"
:rules="[rules.required]"
:type="showPassword ? 'text' : 'password'"
:append-icon="showPassword ? 'mdi-eye' : 'mdi-eye-off'"
@click:append="showPassword = !showPassword"
/>
<v-autocomplete
outlined
label="Symbol"
v-model="loginData.symbol"
:rules="[rules.required]"
:items="
loginData.selectedRegisterVariantName == 'Fakelog'
? ['powiatwulkanowy', 'adsf']
: symbols
"
/>
<v-select
outlined
label="UONET+ register variant"
v-model="loginData.selectedRegisterVariantName"
:rules="[rules.required]"
:items="registerVariantsNames"
@change="registerVariantSelected()"
/>
</div>
<v-card-actions class="px-5 pb-5 pt-0 mt-auto">
<v-spacer />
<v-btn color="primary" type="submit">Sign in</v-btn>
</v-card-actions>
</v-form>
</div>
</template>
<script lang="ts">
import Vue from "vue";
import Api from "@/api";
import RegisterVariants from "@/assets/res/registers.json";
import Symbols from "@/assets/res/symbols.json";
interface LoginFormData {
loginData: {
username: string;
password: string;
symbol: string;
selectedRegisterVariantName: string;
};
registerVariantsNames: Array<string>;
symbols: Array<string>;
showPassword: boolean;
rules: any;
}
export default Vue.extend({
data: (): LoginFormData => ({
loginData: {
username: "",
password: "",
symbol: "",
selectedRegisterVariantName: "Vulcan",
},
registerVariantsNames: [],
symbols: [],
showPassword: false,
rules: {
required: (value: string) => !!value || "This field are required.",
},
}),
created() {
this.registerVariantsNames = RegisterVariants.map((item): string => item.name);
this.symbols = Symbols;
},
computed: {
host() {
return RegisterVariants[
RegisterVariants.findIndex(
(item) => item.name === this.loginData.selectedRegisterVariantName
)
].host;
},
ssl() {
return RegisterVariants[
RegisterVariants.findIndex(
(item) => item.name === this.loginData.selectedRegisterVariantName
)
].ssl;
},
},
methods: {
registerVariantSelected() {
if (this.loginData.selectedRegisterVariantName == "Fakelog") {
this.loginData.username = "jan@fakelog.cf";
this.loginData.password = "jan123";
this.loginData.symbol = "powiatwulkanowy";
}
},
async login() {
const state = this.$store.state;
state.loading = true;
state.error.show = false;
state.error.description = "";
state.logged_in = false;
state.selected_student = undefined;
if (!this.loginData.username || !this.loginData.password || !this.loginData.symbol) {
state.error.description = "All fields are required!";
state.error.show = true;
state.loading = false;
} else {
let response: any = await Api.login(
this.loginData.username,
this.loginData.password,
this.loginData.symbol,
this.host,
this.ssl
);
if (response) {
this.$store.state.loginData = response.data;
}
}
},
},
});
</script>
<style lang="scss" scoped>
.theme--dark.v-btn {
color: #1e1e1e !important;
}
</style>

View file

@ -1,66 +1,54 @@
<template>
<div id="select-student" class="fill-height">
<v-form @submit.prevent="select_student" class="fill-height d-flex flex-column">
<v-card-title class="d-flex justify-center">Select Student</v-card-title>
<div id="login-students-list" class="overflow-y-auto">
<v-list>
<v-list-item-group color="primary" v-model="$store.state.selected_student" mandatory>
<v-list-item
v-for="(student, id) in this.$store.state.loginData.students"
:key="id"
:value="id"
>
<template #default="{ active }">
<v-list-item-action>
<v-icon
:color="active ? 'primary' : ''"
v-text="active ? 'mdi-radiobox-marked' : 'mdi-radiobox-blank'"
/>
</v-list-item-action>
<v-list-item-content>
<v-list-item-title>
{{ student.student_name }}
{{ student.student_second_name }}
{{ student.student_surname }}
{{ student.level }}{{ student.symbol }}
</v-list-item-title>
<v-list-item-subtitle>{{ student.school_name }}</v-list-item-subtitle>
</v-list-item-content>
</template>
</v-list-item>
</v-list-item-group>
</v-list>
</div>
<v-card-actions class="px-5 pb-5 pt-0 mt-auto">
<v-spacer />
<v-btn color="primary" type="submit">Sign in</v-btn>
</v-card-actions>
</v-form>
</div>
<v-row align="center">
<v-col cols="12">
<a>Select student to login!</a>
</v-col>
<v-col cols="12">
<v-radio-group v-model="radioGroup" @change="$store.state.selectedStudent = radioGroup">
<v-radio
v-model="selectedStudent"
v-for="student in this.$store.state.loginData.data.students.data"
:key="student.UczenPelnaNazwa"
:label="student.UczenPelnaNazwa">
</v-radio>
</v-radio-group>
</v-col>
<v-col cols="12">
<v-btn
class="login-button"
depressed
color="primary"
@click="chooseClicked()">
Choose</v-btn>
</v-col>
</v-row>
</template>
<script lang="ts">
import Vue from "vue";
export default Vue.extend({
name: "SelectStudent",
<script>
export default {
name: 'SelectStudent',
data() {
return {
radioGroup: 0,
selectedStudent: '',
studentList: {
type: Array,
},
};
},
beforeMount() {
console.log(this.$store.state.loginData.data.students.data[0]);
},
methods: {
select_student() {
this.$router.push("/user");
async chooseClicked() {
this.$store.state.selectedUser = this.selectedStudent;
this.$store.state.showStudentsList = true;
await this.$router.push('/user');
},
},
});
};
</script>
<style lang="scss" scoped>
@media only screen and (min-width: 960px) {
#login-students-list {
max-height: 410px;
}
}
<style scoped>
.theme--dark.v-btn {
color: #1e1e1e !important;
}
</style>

View file

@ -1,42 +0,0 @@
<template>
<div id="snackbar">
<v-snackbar v-model="$store.state.error.show">
{{ this.$store.state.error.description }}
<template #action="{ attrs }">
<v-dialog width="650" v-if="$store.state.error.details">
<template #activator="{ on }">
<v-btn color="snackbar" text v-on="on">Details</v-btn>
</template>
<template #default="dialog">
<v-card>
<v-card-title>Details</v-card-title>
<v-card-text>
<v-alert text type="error">
{{ $store.state.error.description }}
<code class="d-block mt-3 py-2 px-sm-3">
{{ $store.state.error.details }}
</code>
</v-alert>
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn text color="primary" @click="dialog.value = false">Close</v-btn>
</v-card-actions>
</v-card>
</template>
</v-dialog>
<v-btn color="snackbar" text v-bind="attrs" @click="$store.state.error.show = false"
>Close</v-btn
>
</template>
</v-snackbar>
</div>
</template>
<script lang="ts">
import Vue from "vue";
export default Vue.extend({
name: "Snackbar",
});
</script>

View file

@ -0,0 +1,93 @@
<template>
<div>
<v-row align="center">
<v-col cols="12"></v-col>
<v-col cols="12">
<v-text-field
class="login-input"
v-model="login"
label="E-mail"
outlined
clearable>
</v-text-field>
<v-text-field
class="login-input"
v-model="password"
label="Password"
outlined
clearable>
</v-text-field>
</v-col>
<v-col cols="12">
<v-select
:items="diaryNames"
v-model="selectedDiary"
item-value=""
v-on:change="itemSelected()"
label="Symbol"
selection="index"
outlined></v-select>
</v-col>
<v-col cols="12">
<v-btn
class="login-button"
depressed
color="primary"
@click="loginUser()">
Log in!</v-btn>
<v-divider style="padding: 5px"></v-divider>
<a style="">You forgot password click here!</a>
</v-col>
</v-row>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import login from '../../api/login';
import diary from '../../assets/data/diary.json';
interface Login {
login: string
password: string
diaryNames: Array<string>
selectedDiary: string
}
export default Vue.extend({
name: 'UserLogin',
data: (): Login => ({
login: '',
password: '',
diaryNames: [],
selectedDiary: '',
}),
created() {
this.diaryNames = diary.diaries.map((item): string => item.name);
},
methods: {
async loginUser() {
Vue.set(this.$store.state, 'isLoading', true);
const index = diary.diaries.findIndex((item) => item.name === this.selectedDiary);
const response = await login.login(this.login, this.password,
'powiatwulkanowy', diary.diaries[index].url);
this.$store.state.loginData = response.data;
if (this.$store.state.loginData.data.students.data.length > 1) {
this.$store.state.isLoading = false;
this.$store.state.showStudentsList = true;
}
},
itemSelected() {
if (this.selectedDiary === 'Fakelog') {
this.login = 'jan@fakelog.tk';
this.password = 'jan123';
}
},
},
});
</script>
<style scoped>
</style>

View file

@ -1,68 +0,0 @@
<template>
<div id="account-manager">
<v-dialog width="450" scrollable>
<template #activator="{ on }">
<v-btn icon v-on="on">
<v-avatar color="primary" class="white--text">{{ initials }}</v-avatar>
</v-btn>
</template>
<template #default="dialog">
<v-card>
<v-card-title>Select student</v-card-title>
<v-divider></v-divider>
<div class="overflow-y-auto">
<v-list>
<v-list-item-group color="primary" v-model="$store.state.selected_student" mandatory>
<v-list-item v-for="(student, id) in students" :key="id" :value="id">
<template #default="{ active }">
<v-list-item-action>
<v-icon
:color="active ? 'primary' : ''"
v-text="active ? 'mdi-radiobox-marked' : 'mdi-radiobox-blank'"
/>
</v-list-item-action>
<v-list-item-content>
<v-list-item-title>
{{ student.student_name }}
{{ student.student_second_name }}
{{ student.student_surname }}
{{ student.level }}{{ student.symbol }}
</v-list-item-title>
<v-list-item-subtitle>{{ student.school_name }}</v-list-item-subtitle>
</v-list-item-content>
</template>
</v-list-item>
</v-list-item-group>
</v-list>
</div>
<v-divider></v-divider>
<v-card-actions>
<v-spacer />
<v-btn text color="primary" @click="$store.commit('log_out')">Log out</v-btn>
<v-btn text color="primary" @click="dialog.value = false">Close</v-btn>
</v-card-actions>
</v-card>
</template>
</v-dialog>
</div>
</template>
<script lang="ts">
import Vue from "vue";
export default Vue.extend({
name: "AccountManager",
computed: {
students() {
return this.$store.state.loginData.students;
},
initials() {
const index = this.$store.state.selected_student;
return (
this.$store.state.loginData.students[index].student_name.charAt(0) +
this.$store.state.loginData.students[index].student_surname.charAt(0)
);
},
},
});
</script>

View file

@ -1,26 +0,0 @@
<template>
<div id="app-bar">
<v-app-bar app clipped-left>
<v-btn icon @click="$store.commit('drawer_show')">
<v-icon>mdi-menu</v-icon>
</v-btn>
<v-btn icon @click="$store.commit('drawer_mini')" v-if="!$store.state.small_ui">
<v-icon v-if="$store.state.drawer.mini">mdi-chevron-right</v-icon>
<v-icon v-else>mdi-chevron-left</v-icon>
</v-btn>
<v-toolbar-title>{{ this.$store.state.view }}</v-toolbar-title>
<v-spacer />
<AccountManager />
</v-app-bar>
</div>
</template>
<script lang="ts">
import Vue from "vue";
import AccountManager from "../../components/Panel/AccountManager.vue";
export default Vue.extend({
name: "AppBar",
components: { AccountManager },
});
</script>

View file

@ -0,0 +1,75 @@
<template>
<div>
<div>
<v-app-bar
app
color="primary"
dark>
<v-app-bar-nav-icon
@click="changeDrawerState">
</v-app-bar-nav-icon>
<v-toolbar-title>Wulkanowy - {{ this.$store.state.appbarTitle }}</v-toolbar-title>
<v-spacer></v-spacer>
<v-menu offset-y class="text-center" style="width: 200px">
<template v-slot:activator="{ on }">
<v-avatar
v-on="on"
color="blue">
<span class="white--text headline">{{ initials }}</span>
</v-avatar>
</template>
<v-list>
<v-list-item @click="changeStudent" link>
<v-icon>mdi-account-arrow-right</v-icon>
<v-list-item-title>Change Student</v-list-item-title>
</v-list-item>
<v-divider></v-divider>
<v-list-item @click="logout" link>
<v-icon>mdi-logout</v-icon>
<v-list-item-title>Logout</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
</v-app-bar>
</div>
</div>
</template>
<script>
import router from '../../router';
export default {
name: 'Appbar',
data: () => ({
name: 'Dashboard',
onAvatarClicked: false,
initials: 'TK',
}),
beforeMount() {
this.initials = this.getInitials();
},
methods: {
changeDrawerState() {
this.$store.state.drawer = !this.$store.state.drawer;
},
async logout() {
document.cookie = '';
this.$store.state.showStudentsList = false;
this.$store.state.loginData = null;
await router.push('/');
},
async changeStudent() {
await router.push('/');
},
getInitials() {
const index = this.$store.state.selectedStudent;
return this.$store.state.loginData.data.students.data[index].UczenImie.charAt(0)
+ this.$store.state.loginData.data.students.data[index].UczenNazwisko.charAt(0);
},
},
};
</script>
<style scoped>
</style>

View file

@ -1,20 +1,88 @@
<template>
<div id="drawer">
<div>
<v-navigation-drawer
:mini-variant="$store.state.drawer.mini && !$store.state.small_ui"
v-model="$store.state.drawer.show"
clipped
app
>
<v-list nav dense>
<v-list-item-group v-model="$store.state.view" mandatory color="primary">
<v-list-item v-for="(item, i) in nav" :key="i" :value="item.name">
v-model="this.$store.state.drawer"
:mini-variant.sync="mini"
pernament>
<v-list>
<v-list-item class="px-2">
<v-list-item-avatar>
<v-icon>mdi-account</v-icon>
</v-list-item-avatar>
<v-list-item-content>
<v-list-item-title class="title">
{{ nameSurname }}
</v-list-item-title>
<v-list-item-subtitle>{{ className }}</v-list-item-subtitle>
</v-list-item-content>
<v-btn
icon
@click.stop="mini = !mini">
<v-icon>mdi-chevron-right</v-icon>
</v-btn>
</v-list-item>
</v-list>
<v-divider></v-divider>
<v-list
nav
dense>
<v-list-item-group
v-model="this.$store.state.group">
<v-list-item>
<v-list-item-icon>
<v-icon>{{ item.icon }}</v-icon>
<v-icon>mdi-home</v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title class="text-capitalize">{{ item.name }}</v-list-item-title>
</v-list-item-content>
<v-list-item-title>Dashboard</v-list-item-title>
</v-list-item>
<v-list-item>
<v-list-item-icon>
<v-icon>mdi-numeric-6-box-multiple-outline</v-icon>
</v-list-item-icon>
<v-list-item-title>Grades</v-list-item-title>
</v-list-item>
<v-list-item>
<v-list-item-icon>
<v-icon>mdi-table</v-icon>
</v-list-item-icon>
<v-list-item-title>Attendance</v-list-item-title>
</v-list-item>
<v-list-item>
<v-list-item-icon>
<v-icon>mdi-table-clock</v-icon>
</v-list-item-icon>
<v-list-item-title>Timetable</v-list-item-title>
</v-list-item>
<v-list-item>
<v-list-item-icon>
<v-icon>mdi-email-outline</v-icon>
</v-list-item-icon>
<v-list-item-title>Messages</v-list-item-title>
</v-list-item>
<v-list-item>
<v-list-item-icon>
<v-icon>mdi-notebook-outline</v-icon>
</v-list-item-icon>
<v-list-item-title>Homework</v-list-item-title>
</v-list-item>
<v-list-item>
<v-list-item-icon>
<v-icon>mdi-devices</v-icon>
</v-list-item-icon>
<v-list-item-title>Mobile Devices</v-list-item-title>
</v-list-item>
<v-list-item>
<v-list-item-icon>
<v-icon>mdi-domain</v-icon>
</v-list-item-icon>
<v-list-item-title>School</v-list-item-title>
</v-list-item>
</v-list-item-group>
</v-list>
@ -22,79 +90,34 @@
</div>
</template>
<script lang="ts">
import Vue from "vue";
interface DrawerData {
nav: any;
}
export default Vue.extend({
name: "Drawer",
data: (): DrawerData => ({
nav: [
{
icon: "mdi-view-dashboard-outline",
name: "dashboard",
},
{
icon: "mdi-numeric-6-box-multiple-outline",
name: "grades",
},
{
icon: "mdi-table-edit",
name: "attedance",
},
{
icon: "mdi-timetable",
name: "timetable",
},
{
icon: "mdi-calendar",
name: "exams",
},
{
icon: "mdi-notebook-outline",
name: "homework",
},
{
icon: "mdi-trophy-outline",
name: "notes and achievements",
},
{
icon: "mdi-clover",
name: "lucky number",
},
{
icon: "mdi-account-multiple-outline",
name: "conferences",
},
{
icon: "mdi-alert-circle-outline",
name: "school annocuments",
},
{
icon: "mdi-domain",
name: "school and teachers",
},
{
icon: "mdi-card-account-details-outline",
name: "student data",
},
{
icon: "mdi-devices",
name: "mobile devices",
},
{
icon: "mdi-email-outline",
name: "messages",
},
{
icon: "mdi-cog-outline",
name: "settings",
},
],
<script>
export default {
name: 'Drawer',
data: () => ({
mini: false,
nameSurname: '',
className: '',
}),
});
beforeMount() {
const index = this.$store.state.selectedStudent;
this.nameSurname = `${this.getName(index)} ${this.getSurname(index)}`;
this.className = this.getClassName(index);
},
methods: {
getName(index) {
return this.$store.state.loginData.data.students.data[index].UczenImie;
},
getSurname(index) {
return this.$store.state.loginData.data.students.data[index].UczenNazwisko;
},
getClassName(index) {
return this.$store.state.loginData.data.students.data[index].Poziom.toString()
+ this.$store.state.loginData.data.students.data[index].Symbol;
},
},
};
</script>
<style scoped>
</style>

View file

@ -1,11 +0,0 @@
// Login
export { default as LoginForm } from "./Login/LoginForm.vue";
export { default as SelectStudent } from "./Login/SelectStudent.vue";
export { default as Loading } from "./Login/Loading.vue";
export { default as Baner } from "./Login/Baner.vue";
export { default as Snackbar } from "./Login/Snackbar.vue";
// Panel
export { default as AppBar } from "./Panel/AppBar.vue";
export { default as Drawer } from "./Panel/Drawer.vue";
export { default as AccountManager } from "./Panel/AccountManager.vue";

View file

@ -1,8 +1,9 @@
import Vue from "vue";
import App from "./App.vue";
import router from "./router";
import store from "./store";
import vuetify from "./plugins/vuetify";
import Vue from 'vue';
import App from './App.vue';
import './registerServiceWorker';
import router from './router';
import store from './store';
import vuetify from './plugins/vuetify';
Vue.config.productionTip = false;
@ -11,4 +12,4 @@ new Vue({
store,
vuetify,
render: (h) => h(App),
}).$mount("#app");
}).$mount('#app');

View file

@ -1,5 +1,6 @@
import Vue from "vue";
import Vuetify from "vuetify/lib";
import Vue from 'vue';
import Vuetify from 'vuetify/lib/framework';
import colors from 'vuetify/lib/util/colors';
Vue.use(Vuetify);
@ -7,14 +8,9 @@ export default new Vuetify({
theme: {
themes: {
light: {
primary: "#9a0007",
error: "#ff5722",
snackbar: "#e57373",
},
dark: {
primary: "#e57373",
error: "#ff5722",
snackbar: "#9a0007",
primary: colors.red.darken1, // #E53935
secondary: colors.red.lighten4, // #FFCDD2
accent: colors.indigo.base, // #3F51B5
},
},
},

View file

@ -0,0 +1,32 @@
/* eslint-disable no-console */
import { register } from 'register-service-worker';
if (process.env.NODE_ENV === 'production') {
register(`${process.env.BASE_URL}service-worker.js`, {
ready() {
console.log(
'App is being served from cache by a service worker.\n'
+ 'For more details, visit https://goo.gl/AFskqB',
);
},
registered() {
console.log('Service worker has been registered.');
},
cached() {
console.log('Content has been cached for offline use.');
},
updatefound() {
console.log('New content is downloading.');
},
updated() {
console.log('New content is available; please refresh.');
},
offline() {
console.log('No internet connection found. App is running in offline mode.');
},
error(error) {
console.error('Error during service worker registration:', error);
},
});
}

View file

@ -1,30 +1,31 @@
import Vue from "vue";
import VueRouter, { RouteConfig } from "vue-router";
import Login from "@/views/Login.vue";
import Panel from "@/views/Panel.vue";
import Vue from 'vue';
import VueRouter, { RouteConfig } from 'vue-router';
import Login from '../views/Login.vue';
Vue.use(VueRouter);
const routes: Array<RouteConfig> = [
{
path: "/",
name: "Login",
path: '/',
name: 'Login',
component: Login,
},
{
path: "/user",
name: "User",
component: Panel,
path: '/about',
name: 'About',
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import(/* webpackChunkName: "about" */ '../views/About.vue'),
},
{
path: "*",
redirect: "/",
path: '/user',
name: 'User',
component: () => import('../views/Panel.vue'),
},
];
const router = new VueRouter({
mode: "history",
base: process.env.BASE_URL,
routes,
});

View file

@ -1,4 +1,4 @@
import Vue, { VNode } from "vue";
import Vue, { VNode } from 'vue';
declare global {
namespace JSX {
@ -7,7 +7,7 @@ declare global {
// tslint:disable no-empty-interface
interface ElementClass extends Vue {}
interface IntrinsicElements {
[elem: string]: any;
[elem: string]: any
}
}
}

View file

@ -1,4 +1,5 @@
declare module "*.vue" {
import Vue from "vue";
declare module '*.vue' {
import Vue from 'vue';
export default Vue;
}

View file

@ -1,4 +1,5 @@
declare module "vuetify/lib/framework" {
import Vuetify from "vuetify";
declare module 'vuetify/lib/framework' {
import Vuetify from 'vuetify';
export default Vuetify;
}

View file

@ -1,58 +1,28 @@
import Vue from "vue";
import Vuex from "vuex";
import router from "@/router";
import Vue from 'vue';
import Vuex, { Store } from 'vuex';
Vue.use(Vuex);
interface State {
loading: boolean;
loginData: any;
logged_in: boolean;
selected_student: number;
small_ui: boolean;
view: string;
drawer: {
show: boolean;
mini: boolean;
};
error: {
show: boolean;
description: string;
details: string;
};
interface IndexState {
drawer: boolean
group: any
mini: boolean
appbarTitle: string
selectedStudent: number
}
export default new Vuex.Store({
state: (): State => ({
loading: false,
loginData: [],
logged_in: false,
selected_student: 0,
small_ui: false,
view: "dashboard",
drawer: {
show: true,
mini: false,
},
error: {
show: false,
description: "",
details: "",
},
state: (): IndexState => ({
drawer: true,
group: null,
mini: true,
appbarTitle: 'Dashboard',
selectedStudent: 0,
}),
mutations: {
log_out(state) {
state.loginData = [];
state.logged_in = false;
router.push("/")
},
drawer_show(state) {
state.drawer.show = !state.drawer.show
},
drawer_mini(state) {
state.drawer.mini = !state.drawer.mini
},
},
actions: {},
modules: {},
actions: {
},
modules: {
},
});

View file

@ -0,0 +1,24 @@
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
interface LoginState {
isLoading: boolean
loginData: any
showStudentsList: boolean
}
export default new Vuex.Store({
state: (): LoginState => ({
isLoading: false,
loginData: null,
showStudentsList: false,
}),
mutations: {
},
actions: {
},
modules: {
},
});

View file

@ -0,0 +1,5 @@
<template>
<div class="about">
<h1>This is an about page</h1>
</div>
</template>

View file

@ -1,71 +1,66 @@
<template>
<div id="login" class="d-flex fill-hieght justify-center align-center">
<v-card id="login-card" elevation="15">
<v-row no-gutters class="d-flex fill-height">
<v-col cols="12" md="5" class="primary rounded-l d-md-flex d-none">
<Baner />
</v-col>
<v-col cols="12" md="7">
<LoginForm v-if="!this.$store.state.logged_in & !this.$store.state.loading" />
<Loading v-if="this.$store.state.loading" />
<SelectStudent v-if="this.$store.state.logged_in & !this.$store.state.loading" />
</v-col>
</v-row>
</v-card>
<Snackbar />
<div id="login">
<img class="image" src="../assets/logo_login.svg" width="500" alt="Wulkanowy">
<v-main style="width: 100%;">
<v-card
:loading="this.$store.state.isLoading"
elevation="24"
id="login-form"
class="mx-auto mt-9">
<form>
<v-container>
<UserLogin v-if="!this.$store.state.showStudentsList"></UserLogin>
<SelectStudent v-if="this.$store.state.showStudentsList"></SelectStudent>
</v-container>
</form>
</v-card>
</v-main>
</div>
</template>
<script lang="ts">
import Vue from "vue";
import { LoginForm, SelectStudent, Loading, Baner, Snackbar } from "@/components";
export default Vue.extend({
name: "Login",
<script>
import UserLogin from '../components/Login/UserLogin.vue';
import SelectStudent from '../components/Login/SelectStudent.vue';
export default {
name: 'Login',
components: {
LoginForm,
Loading,
SelectStudent,
Baner,
Snackbar,
UserLogin,
},
beforeMount() {
if (this.$store.state.logged_in) {
this.$router.push("/user");
}
methods: {
getLoading() {
return this.$store.state.isLoading;
},
},
});
};
</script>
<style lang="scss" scoped>
<style>
::-webkit-scrollbar {
display: none;
}
#login {
background: linear-gradient(rgba(0, 0, 0, 0.1), rgba(0, 0, 0, 0.1)),
url("../assets/img/login_wallpaper.jpg");
text-align: center;
overflow: hidden;
background-image: url("../assets/wallpaper.jpg");
background-size: cover;
height: 100%;
width: 100%;
}
@media only screen and (max-width: 959px) {
#login-card {
width: 100vw;
min-height: 100vh;
border-radius: 0;
.row {
min-height: 100vh;
}
}
#login-form {
width: 500px;
top: 15%;
bottom: 50%;
}
@media only screen and (min-width: 960px) {
#login-card {
width: 750px;
.login-input {
margin: 10px;
}
.row {
align-items: stretch;
}
}
.login-button {
margin: 10px;
}
</style>

View file

@ -1,59 +1,25 @@
<template>
<div id="panel">
<AppBar />
<Drawer />
<v-main>
<v-container fluid>
<v-window v-model="$store.state.view" touchless>
<v-window-item transition="false" value="dashboard">this is dashbaord</v-window-item>
<v-window-item transition="false" value="grades">this is grades</v-window-item>
<v-window-item transition="false" value="attedance">this is attedance</v-window-item>
<v-window-item transition="false" value="timetable">this is timetable</v-window-item>
<v-window-item transition="false" value="exams">this is exams</v-window-item>
<v-window-item transition="false" value="homework">this is homework</v-window-item>
<v-window-item transition="false" value="notes and achievements"
>this is notes</v-window-item
>
<v-window-item transition="false" value="lucky number"
>this is lucky number</v-window-item
>
<v-window-item transition="false" value="conferences">this is conferences</v-window-item>
<v-window-item transition="false" value="school annocuments"
>this is school annocuments</v-window-item
>
<v-window-item transition="false" value="student data"
>this is student data</v-window-item
>
<v-window-item transition="false" value="school and teachers"
>this is school and teachers</v-window-item
>
<v-window-item transition="false" value="mobile devices"
>this is mobile devices</v-window-item
>
<v-window-item transition="false" value="messages">this is messages</v-window-item>
<v-window-item transition="false" value="settings">this is settings</v-window-item>
</v-window>
</v-container>
</v-main>
<div>
<div id="appbar">
<Appbar></Appbar>
<Drawer></Drawer>
</div>
</div>
</template>
<script lang="ts">
import Vue from "vue";
import { AppBar, Drawer } from "@/components";
export default Vue.extend({
name: "Panel",
<script>
import Appbar from '../components/Panel/Appbar.vue';
import Drawer from '../components/Panel/Drawer.vue';
export default {
name: 'Panel',
components: {
AppBar,
Appbar,
Drawer,
},
beforeMount() {
if (!this.$store.state.logged_in) {
this.$router.push("/");
}
},
});
};
</script>
<style scoped>
</style>

View file

@ -0,0 +1,88 @@
body {
padding: 0 !important;
height: 100vh;
background: #fff;
overflow: auto;
display: -webkit-box;
display: -ms-flexbox;
font-family: 'Poppins', sans-serif;
font-size: 14px;
margin: 0 !important;
color: black;
}
/*.logo img[data-v-cd2b8d62] {
width: 768px;
max-width: 30vw;
display: inline;
-webkit-filter: drop-shadow(0 5px 5px rgba(0,0,0,.2)) drop-shadow(0 8px 10px rgba(0,0,0,.14)) drop-shadow(0 3px 14px rgba(0,0,0,.12));
filter: drop-shadow(0 5px 5px rgba(0,0,0,.2)) drop-shadow(0 8px 10px rgba(0,0,0,.14)) drop-shadow(0 3px 14px rgba(0,0,0,.12));
}*/
#container{
width: 100%;
}
#menu{
width: 95%;
height: 18%;
margin-left: 2.5%;
border-bottom: 4px solid white;
border-radius: 3px;
font-family: 'Open Sans Condensed', sans-serif;
font-family: 'Quicksand', sans-serif;
}
.option{
cursor: pointer;
}
.grade {
display: block;
border-radius: 5px;
cursor: pointer !important;
font-size: 20px !important;
width: 25px !important;
height: 30px !important;
text-align: center !important;
margin-left: auto !important;
margin-right: auto !important;
margin-top: 5px !important;
}
.noteElement {
padding: 10px;
border: black 1px solid;
width: 90%;
height: 75px;
margin-top: 5px;
margin-left: auto;
margin-right: auto;
}
.noteContent {
width: 50%;
}
.achievementElement {
padding: 10px;
border: black 1px solid;
width: 90%;
height: 75px;
margin-top: 5px;
margin-left: auto;
margin-right: auto;
}
.log-out {
float: right;
margin-right: 10px;
color: blue;
cursor: pointer;
}
#bar {
background-color: #d32f2f;
}
.MuiTouchRipple-rippleVisible {
color: #d32f2f !important;
opacity: 100% !important;
}

View file

@ -0,0 +1,126 @@
@media screen and (max-width: 768px) {
#box-content {
position: relative;
text-align: center;
}
h1 {
width: 0 !important;
position: relative !important;
top: 30% !important;
}
#box {
left: 27% !important;
}
img {
width: 0 !important;
}
#icons {
width: 0% !important;
}
}
body {
height: 100vh;
background: -webkit-gradient(linear,left top,left bottom,from(rgba(0,0,0,.3)),to(rgba(0,0,0,.3))),url(../images/wallpaper.b24bba72.jpg);
background: linear-gradient(rgba(0,0,0,.3),rgba(0,0,0,.3)),url(../images/wallpaper.jpg);
background-size: cover;
background-position: 50%;
background-attachment: fixed;
overflow: auto;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
font-family: 'Roboto', sans-serif;
font-size: 10px;
margin: 0 !important;
color: black;
}
#box {
background-color: white;
padding: 5em;
width: 30vw;
position: absolute;
top: 35%;
left: 32%;
text-align: center;
}
#box-content {
text-align: center;
}
#form {
margin-bottom: 20px;
}
.logo img[data-v-cd2b8d62] {
width: 768px;
-webkit-filter: drop-shadow(0 5px 5px rgba(0,0,0,.2)) drop-shadow(0 8px 10px rgba(0,0,0,.14)) drop-shadow(0 3px 14px rgba(0,0,0,.12));
filter: drop-shadow(0 5px 5px rgba(0,0,0,.2)) drop-shadow(0 8px 10px rgba(0,0,0,.14)) drop-shadow(0 3px 14px rgba(0,0,0,.12));
}
h1 {
font-size: 2em !important;
font-weight: bold;
}
img {
font-size: 70%;
margin-bottom: 70%;
}
.icon {
display: inline-block;
width: 50px;
margin: 0px;
margin-left: 0.6%;
margin-right: 0.6%;
padding: 0px;
}
#icons {
width: 100%;
text-align: center;
font-size: 80%;
margin-bottom: 8em;
margin-left: 1em;
}
a {
font-size: 120%;
color: blue;
margin-bottom: 5%;
}
#help-box {
font-size: 200%;
}
p {
size: 0px;
padding: 0px;
margin: 0px;
height: 48px;
margin-bottom: 15px;
margin-top: 10px;
}
#button {
background-color: #d32f2f;
border-radius: 0;
margin-top: 10%;
}
#error {
color: red;
}

View file

Before

Width:  |  Height:  |  Size: 7.2 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 432 B

View file

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
height="200"
width="200"
version="1.1"
viewBox="0 0 200 200"
id="Layer_1">
<metadata
id="metadata13">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs11" />
<style
id="style2">.st0{fill:#FFFFFF;}</style>
<path
style="fill:#ffffff"
id="path4"
d="m 81.9,83.9 c -5.7,0 -10.2,5 -10.2,11.1 0,6.1 4.6,11.1 10.2,11.1 5.7,0 10.2,-5 10.2,-11.1 0.1,-6.1 -4.5,-11.1 -10.2,-11.1 z m 36.5,0 c -5.7,0 -10.2,5 -10.2,11.1 0,6.1 4.6,11.1 10.2,11.1 5.7,0 10.2,-5 10.2,-11.1 0,-6.1 -4.5,-11.1 -10.2,-11.1 z"
class="st0" />
<path
style="fill:#ffffff"
id="path6"
d="M 167,0 H 33 C 21.7,0 12.5,9.2 12.5,20.6 v 135.2 c 0,11.4 9.2,20.6 20.5,20.6 h 113.4 l -5.3,-18.5 12.8,11.9 12.1,11.2 21.5,19 V 20.6 C 187.5,9.2 178.3,0 167,0 Z m -38.6,130.6 c 0,0 -3.6,-4.3 -6.6,-8.1 13.1,-3.7 18.1,-11.9 18.1,-11.9 -4.1,2.7 -8,4.6 -11.5,5.9 -5,2.1 -9.8,3.5 -14.5,4.3 -9.6,1.8 -18.4,1.3 -25.9,-0.1 -5.7,-1.1 -10.6,-2.7 -14.7,-4.3 -2.3,-0.9 -4.8,-2 -7.3,-3.4 -0.3,-0.2 -0.6,-0.3 -0.9,-0.5 -0.2,-0.1 -0.3,-0.2 -0.4,-0.3 -1.8,-1 -2.8,-1.7 -2.8,-1.7 0,0 4.8,8 17.5,11.8 -3,3.8 -6.7,8.3 -6.7,8.3 C 50.6,129.9 42.2,115.4 42.2,115.4 42.2,83.2 56.6,57.1 56.6,57.1 71,46.3 84.7,46.6 84.7,46.6 l 1,1.2 c -18,5.2 -26.3,13.1 -26.3,13.1 0,0 2.2,-1.2 5.9,-2.9 10.7,-4.7 19.2,-6 22.7,-6.3 0.6,-0.1 1.1,-0.2 1.7,-0.2 6.1,-0.8 13,-1 20.2,-0.2 9.5,1.1 19.7,3.9 30.1,9.6 0,0 -7.9,-7.5 -24.9,-12.7 l 1.4,-1.6 c 0,0 13.7,-0.3 28.1,10.5 0,0 14.4,26.1 14.4,58.3 0,0 -8.5,14.5 -30.6,15.2 z"
class="st0" />
</svg>

After

Width:  |  Height:  |  Size: 2 KiB

View file

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
id="svg4"
viewBox="0 0 22 22"
height="22"
width="22"
version="1.1">
<metadata
id="metadata10">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs8" />
<path
style="fill:#ffffff"
id="path2"
d="M 19,7 11,12 3,7 V 5 l 8,5 8,-5 M 19,3 H 3 C 1.89,3 1,3.89 1,5 v 12 a 2,2 0 0 0 2,2 h 16 a 2,2 0 0 0 2,-2 V 5 C 21,3.89 20.1,3 19,3 Z" />
</svg>

After

Width:  |  Height:  |  Size: 896 B

Some files were not shown because too many files have changed in this diff Show more