Update for public https://habr.com/post/421887/
This commit is contained in:
@@ -27,13 +27,10 @@ formatter = logging.Formatter(app.config['LONG_LOG_FORMAT'])
|
||||
handler.setFormatter(formatter)
|
||||
app.logger.addHandler(handler)
|
||||
|
||||
# celery
|
||||
from celery import Celery
|
||||
# API
|
||||
from . import ns_api
|
||||
|
||||
celery = Celery(app.name,
|
||||
broker=app.config['CELERY_BROKER_URL'],
|
||||
backend=app.config['CELERY_RESULT_BACKEND'],
|
||||
include=['myapp.tasks'])
|
||||
celery.conf.update(app.config)
|
||||
# Пользователи
|
||||
from . import ns_user
|
||||
|
||||
from . import views
|
||||
|
||||
14
myapp/lib/__init__.py
Normal file
14
myapp/lib/__init__.py
Normal file
@@ -0,0 +1,14 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: UTF-8 -*-
|
||||
|
||||
__author__ = 'RemiZOffAlex'
|
||||
__copyright__ = '(c) RemiZOffAlex'
|
||||
__license__ = 'MIT'
|
||||
__email__ = 'remizoffalex@mail.ru'
|
||||
|
||||
from .pagination import Pagination, getpage
|
||||
from .passwd import pwgen, get_hash_password
|
||||
from .storage import gettree, gethashtree
|
||||
from .info import get_user, get_ip
|
||||
|
||||
__all__ = []
|
||||
37
myapp/lib/info.py
Normal file
37
myapp/lib/info.py
Normal file
@@ -0,0 +1,37 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: UTF-8 -*-
|
||||
|
||||
__author__ = 'RemiZOffAlex'
|
||||
__copyright__ = '(c) RemiZOffAlex'
|
||||
__license__ = 'MIT'
|
||||
__email__ = 'remizoffalex@mail.ru'
|
||||
__url__ = 'http://remizoffalex.ru'
|
||||
|
||||
from flask import session, request
|
||||
from .. import app, models
|
||||
|
||||
def get_user():
|
||||
"""Получить текущего пользователя"""
|
||||
result = None
|
||||
if 'user_id' in session:
|
||||
result = models.db_session.query(
|
||||
models.User
|
||||
).filter(
|
||||
models.User.id==session['user_id']
|
||||
).first()
|
||||
if result is None:
|
||||
session.pop('logged_in', None)
|
||||
session.pop('user_id', None)
|
||||
return None
|
||||
return result
|
||||
|
||||
def get_ip():
|
||||
"""
|
||||
Получить IP
|
||||
"""
|
||||
result = ''
|
||||
if request.headers.getlist("X-Forwarded-For"):
|
||||
result = request.headers.get("X-Forwarded-For").split(",")[0]
|
||||
else:
|
||||
result = request.remote_addr
|
||||
return result
|
||||
56
myapp/lib/pagination.py
Normal file
56
myapp/lib/pagination.py
Normal file
@@ -0,0 +1,56 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: UTF-8 -*-
|
||||
|
||||
__author__ = 'RemiZOffAlex'
|
||||
__copyright__ = '(c) RemiZOffAlex'
|
||||
__license__ = 'MIT'
|
||||
__email__ = 'remizoffalex@mail.ru'
|
||||
|
||||
from math import ceil
|
||||
|
||||
|
||||
def getpage(query, page=1, page_size=10):
|
||||
"""
|
||||
Постраничный вывод
|
||||
"""
|
||||
if page_size:
|
||||
query = query.limit(page_size)
|
||||
if page:
|
||||
query = query.offset((page-1)*page_size)
|
||||
return query
|
||||
|
||||
|
||||
class Pagination(object):
|
||||
"""
|
||||
Пагинация
|
||||
"""
|
||||
|
||||
def __init__(self, page, per_page, total_count):
|
||||
self.page = page
|
||||
self.per_page = per_page
|
||||
self.total_count = total_count
|
||||
|
||||
@property
|
||||
def pages(self):
|
||||
return int(ceil(self.total_count / float(self.per_page)))
|
||||
|
||||
@property
|
||||
def has_prev(self):
|
||||
return self.page > 1
|
||||
|
||||
@property
|
||||
def has_next(self):
|
||||
return self.page < self.pages
|
||||
|
||||
def iter_pages(self, left_edge=2, left_current=2,
|
||||
right_current=5, right_edge=2):
|
||||
last = 0
|
||||
for num in range(1, self.pages + 1):
|
||||
if num <= left_edge or \
|
||||
(num > self.page - left_current - 1 and \
|
||||
num < self.page + right_current) or \
|
||||
num > self.pages - right_edge:
|
||||
if last + 1 != num:
|
||||
yield None
|
||||
yield num
|
||||
last = num
|
||||
37
myapp/lib/passwd.py
Normal file
37
myapp/lib/passwd.py
Normal file
@@ -0,0 +1,37 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: UTF-8 -*-
|
||||
|
||||
__author__ = 'RemiZOffAlex'
|
||||
__copyright__ = '(c) RemiZOffAlex'
|
||||
__license__ = 'MIT'
|
||||
__email__ = 'remizoffalex@mail.ru'
|
||||
|
||||
import random
|
||||
import hashlib
|
||||
|
||||
from .. import app
|
||||
|
||||
def pwgen(length=15, hex=False):
|
||||
"""
|
||||
Генератор пароля
|
||||
"""
|
||||
keylist='0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
|
||||
if hex:
|
||||
keylist='0123456789ABCDEF'
|
||||
password=[]
|
||||
|
||||
while len(password) < length:
|
||||
a_char = random.choice(keylist)
|
||||
password.append(a_char)
|
||||
return ''.join(password)
|
||||
|
||||
def get_hash_password(password, salt = None):
|
||||
"""
|
||||
Получить хеш пароля SHA-512
|
||||
"""
|
||||
if salt == None:
|
||||
salt = uuid.uuid4().hex
|
||||
text = password.encode('utf-8') + salt.encode('utf-8')
|
||||
h = hashlib.sha512()
|
||||
h.update(text)
|
||||
return str(h.hexdigest())
|
||||
29
myapp/lib/storage.py
Normal file
29
myapp/lib/storage.py
Normal file
@@ -0,0 +1,29 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: UTF-8 -*-
|
||||
|
||||
__author__ = 'RemiZOffAlex'
|
||||
__copyright__ = '(c) RemiZOffAlex'
|
||||
__license__ = 'MIT'
|
||||
__email__ = 'remizoffalex@mail.ru'
|
||||
|
||||
def gettree(number, count=3):
|
||||
"""
|
||||
Сформировать дерево каталогов
|
||||
"""
|
||||
result = []
|
||||
newline = str(number)
|
||||
while len(newline) % count:
|
||||
newline = '0' + newline
|
||||
for i in range(0, len(newline)//count):
|
||||
result.append(newline[i*count:i*count+count])
|
||||
return result
|
||||
|
||||
def gethashtree(hash, count=3):
|
||||
"""
|
||||
Сформировать дерево каталогов
|
||||
"""
|
||||
result = []
|
||||
for i in range(0, count):
|
||||
element = hash[2*i:2*(i+1)]
|
||||
result.append(element)
|
||||
return '/'.join(result)
|
||||
@@ -5,6 +5,7 @@ __author__ = 'RemiZOffAlex'
|
||||
__copyright__ = '(c) RemiZOffAlex'
|
||||
__license__ = 'MIT'
|
||||
__email__ = 'remizoffalex@mail.ru'
|
||||
__url__ = 'http://remizoffalex.ru'
|
||||
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import scoped_session, sessionmaker
|
||||
@@ -26,21 +27,6 @@ Base.query = db_session.query_property()
|
||||
# Пользователи
|
||||
from .users import User
|
||||
|
||||
# IP
|
||||
from .ip import IP
|
||||
|
||||
# ACL
|
||||
from .acl import (
|
||||
ObjectPermission,
|
||||
RolePermission,
|
||||
RoleSetPermission,
|
||||
UserPermission,
|
||||
UserRole,
|
||||
IPPermission
|
||||
)
|
||||
|
||||
Base.metadata.create_all(engine)
|
||||
|
||||
__all__ = [
|
||||
'db_session'
|
||||
]
|
||||
__all__ = []
|
||||
|
||||
@@ -1,180 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: UTF-8 -*-
|
||||
|
||||
__author__ = 'RemiZOffAlex'
|
||||
__copyright__ = '(c) RemiZOffAlex'
|
||||
__license__ = 'MIT'
|
||||
__email__ = 'remizoffalex@mail.ru'
|
||||
__url__ = 'http://remizoffalex.ru'
|
||||
|
||||
import datetime
|
||||
from sqlalchemy import (
|
||||
Table,
|
||||
Column,
|
||||
Boolean,
|
||||
Integer,
|
||||
ForeignKey,
|
||||
String,
|
||||
DateTime,
|
||||
Enum
|
||||
)
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
from . import Base
|
||||
|
||||
|
||||
class ObjectPermission(Base):
|
||||
"""
|
||||
Объекты доступа: процедура и содержащий процедуру модуль
|
||||
"""
|
||||
__tablename__ = "object_permission"
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
funcname = Column(String)
|
||||
modulename = Column(String)
|
||||
|
||||
# Связи
|
||||
user_permissions = relationship(
|
||||
"UserPermission",
|
||||
primaryjoin="ObjectPermission.id==UserPermission.object_id"
|
||||
)
|
||||
ip_permissions = relationship(
|
||||
"IPPermission",
|
||||
primaryjoin="ObjectPermission.id==IPPermission.object_id"
|
||||
)
|
||||
|
||||
def __init__(self, modulename, funcname):
|
||||
self.funcname = funcname
|
||||
self.modulename = modulename
|
||||
|
||||
def __repr__(self):
|
||||
return "<ObjectPermission('%s': '%s')>" % (self.funcname,
|
||||
self.modulename)
|
||||
|
||||
|
||||
class RolePermission(Base):
|
||||
"""Роли доступа"""
|
||||
__tablename__ = "role_permission"
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
name = Column(String)
|
||||
description = Column(String, default='')
|
||||
|
||||
# Связи
|
||||
set_objects = relationship(
|
||||
"RoleSetPermission",
|
||||
primaryjoin="RolePermission.id==RoleSetPermission.role_id"
|
||||
)
|
||||
users = relationship(
|
||||
"UserRole",
|
||||
primaryjoin="RolePermission.id==UserRole.role_id"
|
||||
)
|
||||
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
|
||||
|
||||
class RoleSetPermission(Base):
|
||||
"""Набор прав доступа для роли"""
|
||||
__tablename__ = "role_set_permission"
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
role_id = Column(Integer, ForeignKey('role_permission.id'))
|
||||
object_id = Column(Integer, ForeignKey('object_permission.id'))
|
||||
permission = Column(Enum('allow', 'deny')) # Разрешение
|
||||
|
||||
# Связи
|
||||
role = relationship(
|
||||
"RolePermission",
|
||||
primaryjoin="RoleSetPermission.role_id==RolePermission.id",
|
||||
uselist=False
|
||||
)
|
||||
object_permission = relationship(
|
||||
"ObjectPermission",
|
||||
primaryjoin="RoleSetPermission.object_id==ObjectPermission.id",
|
||||
uselist=False
|
||||
)
|
||||
|
||||
def __init__(self, role_permission, object_permission, permission):
|
||||
self.role_id = role_permission.id
|
||||
self.object_id = object_permission.id
|
||||
self.permission = permission
|
||||
|
||||
|
||||
class UserPermission(Base):
|
||||
"""Права доступа пользователя"""
|
||||
__tablename__ = "user_permission"
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
object_id = Column(Integer, ForeignKey('object_permission.id'))
|
||||
user_id = Column(Integer, ForeignKey('user.id'))
|
||||
permission = Column(Enum('allow', 'deny')) # Разрешение
|
||||
|
||||
# Связи
|
||||
user = relationship(
|
||||
"User",
|
||||
primaryjoin="UserPermission.user_id==User.id",
|
||||
uselist=False
|
||||
)
|
||||
object_permission = relationship(
|
||||
"ObjectPermission",
|
||||
primaryjoin="UserPermission.object_id==ObjectPermission.id",
|
||||
uselist=False
|
||||
)
|
||||
|
||||
def __init__(self, object_permission, user, permission):
|
||||
assert type(object_permission).__name__=='ObjectPermission', app.logger.info('Не передан объект ObjectPermission')
|
||||
assert type(user).__name__=='User', app.logger.info('Не передан объект User')
|
||||
self.object_id = object_permission.id
|
||||
self.user_id = user.id
|
||||
self.permission = permission
|
||||
|
||||
|
||||
class UserRole(Base):
|
||||
"""Роль пользователя"""
|
||||
__tablename__ = "user_role"
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
role_id = Column(Integer, ForeignKey('role_permission.id'))
|
||||
user_id = Column(Integer, ForeignKey('user.id'))
|
||||
|
||||
# Связи
|
||||
user = relationship(
|
||||
"User",
|
||||
primaryjoin="UserRole.user_id==User.id",
|
||||
uselist=False
|
||||
)
|
||||
role_permission = relationship(
|
||||
"RolePermission",
|
||||
primaryjoin="UserRole.role_id==RolePermission.id",
|
||||
uselist=False
|
||||
)
|
||||
|
||||
def __init__(self, role_permission, user):
|
||||
assert type(role_permission).__name__=='RolePermission', app.logger.info('Не передан объект RolePermission')
|
||||
assert type(user).__name__=='User', app.logger.info('Не передан объект User')
|
||||
self.role_id = role_permission.id
|
||||
self.user_id = user.id
|
||||
|
||||
|
||||
class IPPermission(Base):
|
||||
"""
|
||||
Права доступа для IP
|
||||
"""
|
||||
__tablename__ = "ip_permission"
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
object_id = Column(Integer, ForeignKey('object_permission.id'))
|
||||
ip_id = Column(Integer, ForeignKey('ip.id'))
|
||||
permission = Column(Enum('allow', 'deny')) # Разрешение
|
||||
|
||||
# Связи
|
||||
ip = relationship("IP", primaryjoin="IPPermission.ip_id==IP.id", uselist=False)
|
||||
object_permission = relationship("ObjectPermission", primaryjoin="IPPermission.object_id==ObjectPermission.id", uselist=False)
|
||||
|
||||
def __init__(self, object_permission, ip, permission):
|
||||
assert type(object_permission).__name__=='ObjectPermission', app.logger.info('Не передан объект ObjectPermission')
|
||||
assert type(ip).__name__=='IP', app.logger.info('Не передан объект IP')
|
||||
self.object_id = object_permission.id
|
||||
self.ip_id = ip.id
|
||||
self.permission = permission
|
||||
@@ -1,30 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: UTF-8 -*-
|
||||
|
||||
__author__ = 'RemiZOffAlex'
|
||||
__copyright__ = '(c) RemiZOffAlex'
|
||||
__license__ = 'MIT'
|
||||
__email__ = 'remizoffalex@mail.ru'
|
||||
__url__ = 'http://remizoffalex.ru'
|
||||
|
||||
import datetime
|
||||
from sqlalchemy import Table, Column, Boolean, Integer, ForeignKey, String, DateTime
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
from . import Base
|
||||
|
||||
class IP(Base):
|
||||
__tablename__ = "ip"
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
ip = Column(String, nullable=False, unique=True)
|
||||
description = Column(String)
|
||||
|
||||
# Связи
|
||||
# tagquestion = relationship("TagQuestion", primaryjoin="TagQuestion.tag_id==Tag.id")
|
||||
|
||||
def __init__(self, ip):
|
||||
self.ip = ip
|
||||
|
||||
def __repr__(self):
|
||||
return "<IP('%s')>" % (self.ip)
|
||||
@@ -5,21 +5,37 @@ __author__ = 'RemiZOffAlex'
|
||||
__copyright__ = '(c) RemiZOffAlex'
|
||||
__license__ = 'MIT'
|
||||
__email__ = 'remizoffalex@mail.ru'
|
||||
__url__ = 'http://remizoffalex.ru'
|
||||
|
||||
import datetime
|
||||
from sqlalchemy import Table, Column, Boolean, Integer, ForeignKey, String, DateTime
|
||||
from sqlalchemy import (
|
||||
Table,
|
||||
Column,
|
||||
Boolean,
|
||||
Integer,
|
||||
ForeignKey,
|
||||
String,
|
||||
DateTime
|
||||
)
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
from . import Base
|
||||
|
||||
|
||||
class User(Base):
|
||||
__tablename__ = "user"
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
name = Column(String, nullable=False, unique=True)
|
||||
password = Column(String, nullable=False)
|
||||
disabled = Column(Boolean, default=True)
|
||||
created = Column(DateTime)
|
||||
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
self.created = datetime.datetime.utcnow()
|
||||
|
||||
def as_dict(self):
|
||||
return {c.name: getattr(self, c.name)
|
||||
for c in self.__table__.columns
|
||||
if c.name!='password'}
|
||||
|
||||
12
myapp/ns_api/README.html
Normal file
12
myapp/ns_api/README.html
Normal file
@@ -0,0 +1,12 @@
|
||||
<a href="https://github.com/cenobites/flask-jsonrpc">https://github.com/cenobites/flask-jsonrpc</a>
|
||||
|
||||
<pre>
|
||||
curl -i -X POST \
|
||||
-H "Content-Type: application/json; indent=4" \
|
||||
-d '{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "users.getList",
|
||||
"params": {},
|
||||
"id": "1"
|
||||
}' http://localhost:8000/api
|
||||
</pre>
|
||||
16
myapp/ns_api/__init__.py
Normal file
16
myapp/ns_api/__init__.py
Normal file
@@ -0,0 +1,16 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: UTF-8 -*-
|
||||
|
||||
__author__ = 'RemiZOffAlex'
|
||||
__copyright__ = '(c) RemiZOffAlex'
|
||||
__license__ = 'MIT'
|
||||
__email__ = 'remizoffalex@mail.ru'
|
||||
__url__ = 'http://remizoffalex.ru'
|
||||
|
||||
from flask_jsonrpc import JSONRPC
|
||||
|
||||
from .. import app
|
||||
|
||||
jsonrpc = JSONRPC(app, '/api')
|
||||
|
||||
from . import user
|
||||
28
myapp/ns_api/user.py
Normal file
28
myapp/ns_api/user.py
Normal file
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: UTF-8 -*-
|
||||
|
||||
__author__ = 'RemiZOffAlex'
|
||||
__copyright__ = '(c) RemiZOffAlex'
|
||||
__license__ = 'MIT'
|
||||
__email__ = 'remizoffalex@mail.ru'
|
||||
__url__ = 'http://remizoffalex.ru'
|
||||
|
||||
from flask import abort, escape
|
||||
|
||||
from . import jsonrpc
|
||||
from .. import models
|
||||
|
||||
|
||||
@jsonrpc.method('users.getList')
|
||||
def users_getList():
|
||||
"""
|
||||
Показать список пользователей
|
||||
"""
|
||||
users = models.db_session.query(
|
||||
models.User
|
||||
).all()
|
||||
|
||||
result = []
|
||||
for item in users:
|
||||
result.append(item.as_dict())
|
||||
return result
|
||||
21
myapp/ns_user/__init__.py
Normal file
21
myapp/ns_user/__init__.py
Normal file
@@ -0,0 +1,21 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: UTF-8 -*-
|
||||
|
||||
__author__ = 'RemiZOffAlex'
|
||||
__copyright__ = '(c) RemiZOffAlex'
|
||||
__license__ = 'MIT'
|
||||
__email__ = 'remizoffalex@mail.ru'
|
||||
__url__ = 'http://remizoffalex.ru'
|
||||
|
||||
import os
|
||||
import jinja2
|
||||
|
||||
from . import views
|
||||
from .. import app
|
||||
|
||||
|
||||
my_loader = jinja2.ChoiceLoader([
|
||||
app.jinja_loader,
|
||||
jinja2.FileSystemLoader(os.path.dirname(os.path.abspath(__file__)) + "/templates"),
|
||||
])
|
||||
app.jinja_loader = my_loader
|
||||
67
myapp/ns_user/templates/users.html
Normal file
67
myapp/ns_user/templates/users.html
Normal file
@@ -0,0 +1,67 @@
|
||||
{% extends "skeleton.html" %}
|
||||
{% block body %}
|
||||
|
||||
<h3>Список пользователей</h3>
|
||||
<hr />
|
||||
|
||||
<div class="btn btn-outline-primary" v-on:click="getUserList()">Получить/обновить список пользователей</div>
|
||||
|
||||
{% raw %}
|
||||
<div class="row" v-for="(user, userIdx) in users">
|
||||
<div class="col py-2">
|
||||
<a :href="'/user/' + user.id">{{ user.name }}</a>
|
||||
|
||||
<small class="text-muted">
|
||||
<br />
|
||||
Создан: {{ user.created }}
|
||||
</small>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{% endraw %}
|
||||
|
||||
<backtotop-component></backtotop-component>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block breadcrumb %}
|
||||
<ol class="breadcrumb mt-3">
|
||||
<li class="breadcrumb-item"><a href="/"><i class="fa fa-home"></i></a></li>
|
||||
<li class="breadcrumb-item">Список пользователей</li>
|
||||
</ol>
|
||||
{% endblock %}
|
||||
|
||||
{% block script %}
|
||||
<script type="text/javascript" src="/static/components/backtotop.js"></script>
|
||||
<link rel="stylesheet" href="/static/components/backtotop.css"></link>
|
||||
|
||||
<script type="text/javascript">
|
||||
var app = new Vue({
|
||||
el: '#app',
|
||||
data: {
|
||||
users: []
|
||||
},
|
||||
methods: {
|
||||
getUserList: function() {
|
||||
var vm = this;
|
||||
axios.post(
|
||||
'/api',
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": 'users.getList',
|
||||
"params": {
|
||||
},
|
||||
"id": 1
|
||||
}
|
||||
).then(
|
||||
function(response) {
|
||||
if ('result' in response.data) {
|
||||
vm.users = response.data['result'];
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
||||
23
myapp/ns_user/views.py
Normal file
23
myapp/ns_user/views.py
Normal file
@@ -0,0 +1,23 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: UTF-8 -*-
|
||||
|
||||
__author__ = 'RemiZOffAlex'
|
||||
__copyright__ = '(c) RemiZOffAlex'
|
||||
__license__ = 'MIT'
|
||||
__email__ = 'remizoffalex@mail.ru'
|
||||
__url__ = 'http://remizoffalex.ru'
|
||||
|
||||
from flask import render_template
|
||||
|
||||
from .. import app
|
||||
|
||||
|
||||
@app.route('/users')
|
||||
def users():
|
||||
"""
|
||||
Список пользователей
|
||||
"""
|
||||
pagedata = {}
|
||||
pagedata['title'] = 'Список пользователей - ' + app.config['TITLE']
|
||||
body = render_template('users.html', pagedata=pagedata)
|
||||
return body
|
||||
18
myapp/static/components/backtotop.css
Normal file
18
myapp/static/components/backtotop.css
Normal file
@@ -0,0 +1,18 @@
|
||||
.scrollToTop{
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
position: fixed;
|
||||
bottom: 10px;
|
||||
left: 10px;
|
||||
opacity: 0.3;
|
||||
display: inline;
|
||||
}
|
||||
.scrollToTop:hover{
|
||||
text-decoration: none;
|
||||
opacity: 1.0;
|
||||
}
|
||||
.scrollToTop .card{
|
||||
margin-bottom: 0;
|
||||
border-color: #7ca8b1;
|
||||
}
|
||||
53
myapp/static/components/backtotop.js
Normal file
53
myapp/static/components/backtotop.js
Normal file
@@ -0,0 +1,53 @@
|
||||
var backtotopTemplate = `
|
||||
<div>
|
||||
<span class="scrollToTop" style="z-index: 10;" v-show="visible" v-on:click="backToTop">
|
||||
<div class="card">
|
||||
<div class="card-body py-2 px-2">
|
||||
<i class="fa fa-chevron-up"></i>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</div>`;
|
||||
|
||||
Vue.component('backtotop-component', {
|
||||
props: {
|
||||
visibleoffset: {
|
||||
type: [String, Number],
|
||||
default: 600
|
||||
},
|
||||
},
|
||||
template: backtotopTemplate,
|
||||
data () {
|
||||
return {
|
||||
visible: false
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
var vm = this;
|
||||
window.addEventListener('scroll', this.catchScroll);
|
||||
let currentScroll = document.documentElement.scrollTop || document.body.scrollTop
|
||||
vm.visible = (currentScroll > 100);
|
||||
},
|
||||
destroyed () {
|
||||
window.removeEventListener('scroll', this.catchScroll)
|
||||
},
|
||||
methods: {
|
||||
catchScroll () {
|
||||
var vm = this;
|
||||
vm.visible = (window.pageYOffset > 100);
|
||||
},
|
||||
backToTop () {
|
||||
var vm = this;
|
||||
vm.scrollAnimate();
|
||||
},
|
||||
scrollAnimate: function() {
|
||||
var vm = this;
|
||||
let currentScroll = document.documentElement.scrollTop || document.body.scrollTop
|
||||
if (currentScroll > 0) {
|
||||
//alert(currentScroll);
|
||||
window.requestAnimationFrame(vm.scrollAnimate)
|
||||
window.scrollTo(0, Math.floor(currentScroll - (currentScroll / 5)))
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
8899
myapp/static/css/bootstrap.css
vendored
8899
myapp/static/css/bootstrap.css
vendored
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
7
myapp/static/css/bootstrap.min.css
vendored
Normal file
7
myapp/static/css/bootstrap.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
2337
myapp/static/css/font-awesome.css
vendored
2337
myapp/static/css/font-awesome.css
vendored
File diff suppressed because it is too large
Load Diff
4
myapp/static/css/font-awesome.min.css
vendored
Normal file
4
myapp/static/css/font-awesome.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
9
myapp/static/js/axios.min.js
vendored
Normal file
9
myapp/static/js/axios.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
3892
myapp/static/js/bootstrap.js
vendored
3892
myapp/static/js/bootstrap.js
vendored
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
7
myapp/static/js/bootstrap.min.js
vendored
Normal file
7
myapp/static/js/bootstrap.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
10253
myapp/static/js/jquery-3.2.1.js
vendored
10253
myapp/static/js/jquery-3.2.1.js
vendored
File diff suppressed because it is too large
Load Diff
2
myapp/static/js/jquery-3.3.1.slim.min.js
vendored
Normal file
2
myapp/static/js/jquery-3.3.1.slim.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
5
myapp/static/js/popper.min.js
vendored
Normal file
5
myapp/static/js/popper.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
6
myapp/static/js/vue.min.js
vendored
Normal file
6
myapp/static/js/vue.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -3,10 +3,12 @@
|
||||
<meta name="author" content="Ремизов Александр" />
|
||||
<meta name="copyright" lang="ru" content="RemiZOffAlex" />
|
||||
<link rel="shortcut icon" href="/static/favicon.ico">
|
||||
<link rel="stylesheet" href="/static/css/bootstrap.css" />
|
||||
<link rel="stylesheet" href="/static/css/font-awesome.css" />
|
||||
<script type="text/javascript" src="/static/js/jquery-3.2.1.js"></script>
|
||||
<script type="text/javascript" src="/static/js/popper.js"></script>
|
||||
<script type="text/javascript" src="/static/js/bootstrap.js"></script>
|
||||
<link rel="stylesheet" href="/static/css/bootstrap.min.css" />
|
||||
<link rel="stylesheet" href="/static/css/font-awesome.min.css" />
|
||||
<script type="text/javascript" src="/static/js/jquery-3.3.1.slim.min.js"></script>
|
||||
<script type="text/javascript" src="/static/js/popper.min.js"></script>
|
||||
<script type="text/javascript" src="/static/js/bootstrap.min.js"></script>
|
||||
<script type="text/javascript" src="/static/js/vue.min.js"></script>
|
||||
<script type="text/javascript" src="/static/js/axios.min.js"></script>
|
||||
<title>{{ pagedata['title'] }}</title>
|
||||
</head>
|
||||
|
||||
7
myapp/templates/navbar.html
Normal file
7
myapp/templates/navbar.html
Normal file
@@ -0,0 +1,7 @@
|
||||
<div class="container-fluid py-2">
|
||||
<a class="btn btn-outline-success border-0 float-right" href="/login"><i class="fa fa-sign-in"></i></a>
|
||||
<a class="btn btn-outline-secondary border-0" href="/"><i class="fa fa-home"></i></a>
|
||||
<a class="btn btn-outline-secondary border-0" href="/edit"><i class="fa fa-edit"></i> Редактор</a>
|
||||
<a class="btn btn-outline-secondary border-0" href="/users">Vue & axios</a>
|
||||
<a class="btn btn-outline-secondary border-0" href="/api/browse">API JSON-RPC</a>
|
||||
</div>
|
||||
@@ -2,13 +2,18 @@
|
||||
<html lang="ru">
|
||||
{% include 'header.html' %}
|
||||
<body>
|
||||
<section id="app">
|
||||
|
||||
{% include 'topbar.html' %}
|
||||
{% include 'navbar.html' %}
|
||||
|
||||
<div class="container">
|
||||
{% block body %}
|
||||
{% endblock %}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{% block script %}
|
||||
{% endblock %}
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
<div class="container-fluid py-2">
|
||||
<a class="btn btn-outline-success float-right" href="/login"><i class="fa fa-sign-in"></i></a>
|
||||
<a class="btn btn-outline-secondary" href="/"><i class="fa fa-home"></i></a>
|
||||
<a class="btn btn-outline-secondary" href="/edit"><i class="fa fa-edit"></i></a>
|
||||
</div>
|
||||
Reference in New Issue
Block a user