This commit is contained in:
2018-08-31 17:48:29 +03:00
parent be28af1ccb
commit 48ebf82a1f
44 changed files with 645 additions and 28077 deletions

View File

@@ -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
View 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
View 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
View 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
View 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
View 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)

View File

@@ -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__ = []

View File

@@ -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

View File

@@ -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)

View File

@@ -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
View 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
View 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
View 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
View 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

View 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
View 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

View 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;
}

View 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)))
}
}
}
});

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

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

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

File diff suppressed because one or more lines are too long

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

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

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

File diff suppressed because one or more lines are too long

6
myapp/static/js/vue.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -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>

View 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>

View File

@@ -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>

View File

@@ -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>