modern #2
|
|
@ -8,7 +8,7 @@ import re
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from base64 import b64encode
|
from base64 import b64encode
|
||||||
from paramecio.libraries.db.webmodel import WebModel
|
from paramecio.libraries.db.webmodel import WebModel
|
||||||
from paramecio.modules.admin.models.admin import UserAdmin
|
from paramecio.modules.admin2.models.admin import UserAdmin2
|
||||||
from subprocess import call
|
from subprocess import call
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
|
|
@ -265,7 +265,7 @@ def start():
|
||||||
|
|
||||||
f.close()
|
f.close()
|
||||||
|
|
||||||
config_text=config_text.replace("modules=['paramecio.modules.welcome']", "modules=['paramecio.modules.welcome', 'paramecio.modules.admin', 'paramecio.modules.lang']")
|
config_text=config_text.replace("modules=['paramecio.modules.welcome']", "modules=['paramecio.modules.welcome', 'paramecio.modules.admin2', 'paramecio.modules.lang']")
|
||||||
|
|
||||||
with open(path_settings+'/config.py', 'w') as f:
|
with open(path_settings+'/config.py', 'w') as f:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,8 @@ class DateTimeField(PhangoField):
|
||||||
|
|
||||||
self.type_sql='datetime'
|
self.type_sql='datetime'
|
||||||
|
|
||||||
|
self.default_value='0000-00-00 00:00:00'
|
||||||
|
|
||||||
def check(self, value):
|
def check(self, value):
|
||||||
|
|
||||||
if self.utc:
|
if self.utc:
|
||||||
|
|
@ -68,6 +70,9 @@ class DateTimeField(PhangoField):
|
||||||
|
|
||||||
value=datetime.format_local_strtime('YYYY-MM-DD HH:mm:ss', value)
|
value=datetime.format_local_strtime('YYYY-MM-DD HH:mm:ss', value)
|
||||||
|
|
||||||
|
if value=='':
|
||||||
|
return '0000-00-00 00:00:00'
|
||||||
|
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def show_formatted(self, value):
|
def show_formatted(self, value):
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ from mako.template import Template
|
||||||
from mako.lookup import TemplateLookup
|
from mako.lookup import TemplateLookup
|
||||||
from paramecio.libraries.urls import make_url, make_url_domain, make_media_url, make_media_url_module, add_get_parameters
|
from paramecio.libraries.urls import make_url, make_url_domain, make_media_url, make_media_url_module, add_get_parameters
|
||||||
from paramecio.libraries.i18n import I18n, PGetText
|
from paramecio.libraries.i18n import I18n, PGetText
|
||||||
from paramecio.libraries.sessions import get_session
|
from paramecio.libraries.sessionplugin import get_session
|
||||||
from paramecio.libraries.adminutils import make_admin_url
|
from paramecio.libraries.adminutils import make_admin_url
|
||||||
from paramecio.libraries.db.formsutils import csrf_token
|
from paramecio.libraries.db.formsutils import csrf_token
|
||||||
from paramecio.libraries.js import make_js_url
|
from paramecio.libraries.js import make_js_url
|
||||||
|
|
@ -448,7 +448,7 @@ class HeaderHTML:
|
||||||
|
|
||||||
s['flash']=''
|
s['flash']=''
|
||||||
|
|
||||||
s.save()
|
#s.save()
|
||||||
|
|
||||||
return message
|
return message
|
||||||
|
|
||||||
|
|
@ -458,7 +458,7 @@ def set_flash_message(message):
|
||||||
|
|
||||||
s['flash']=message
|
s['flash']=message
|
||||||
|
|
||||||
s.save()
|
#s.save()
|
||||||
|
|
||||||
def qf(text):
|
def qf(text):
|
||||||
|
|
||||||
|
|
|
||||||
304
paramecio/modules/admin2/app.py
Normal file
|
|
@ -0,0 +1,304 @@
|
||||||
|
|
||||||
|
#from paramecio import wsgi_app
|
||||||
|
from paramecio.libraries.i18n import I18n
|
||||||
|
from paramecio.libraries.mtemplates import env_theme, PTemplate
|
||||||
|
from paramecio.modules.admin2.models.admin import UserAdmin2, LoginTries2
|
||||||
|
from paramecio.libraries.db.webmodel import WebModel
|
||||||
|
from paramecio.libraries.db import simplequery
|
||||||
|
from settings import config
|
||||||
|
from paramecio.libraries.datetime import now, format_local_strtime, timestamp_to_datetime, obtain_timestamp
|
||||||
|
from paramecio.libraries.keyutils import create_key_encrypt, create_key
|
||||||
|
from time import time
|
||||||
|
from paramecio.wsgiapp import app
|
||||||
|
#from paramecio.modules.admin2 import admin_app
|
||||||
|
from bottle import request, redirect, Bottle
|
||||||
|
from paramecio.modules.admin2.libraries.loginplugin import check_login
|
||||||
|
from paramecio.libraries.sessionplugin import SessionPlugin
|
||||||
|
from paramecio.libraries.httputils import GetPostFiles
|
||||||
|
from paramecio.libraries.db.formsutils import check_form, csrf_token
|
||||||
|
from paramecio.libraries.db.coreforms import PasswordForm
|
||||||
|
|
||||||
|
admin_app=Bottle()
|
||||||
|
admin_app.install(SessionPlugin())
|
||||||
|
admin_app.install(check_login)
|
||||||
|
|
||||||
|
env=env_theme(__file__)
|
||||||
|
t=PTemplate(env)
|
||||||
|
|
||||||
|
usermodel=UserAdmin2()
|
||||||
|
|
||||||
|
usermodel.create_forms()
|
||||||
|
|
||||||
|
login_tries=5
|
||||||
|
|
||||||
|
if hasattr(config, 'login_tries'):
|
||||||
|
login_tries=config.login_tries
|
||||||
|
|
||||||
|
seconds_login=300
|
||||||
|
|
||||||
|
if hasattr(config, 'seconds_login'):
|
||||||
|
seconds_login=config.seconds_login
|
||||||
|
|
||||||
|
if hasattr(config, 'cookie_name'):
|
||||||
|
cookie_name=config.cookie_name
|
||||||
|
|
||||||
|
@admin_app.get('/test', skip=[check_login])
|
||||||
|
def test_session(session={}):
|
||||||
|
|
||||||
|
return session
|
||||||
|
|
||||||
|
|
||||||
|
@admin_app.get('/admin', name="admin_app.home_admin")
|
||||||
|
def home_admin(session={}):
|
||||||
|
|
||||||
|
#s=get_session()
|
||||||
|
|
||||||
|
i18n=I18n('admin2')
|
||||||
|
|
||||||
|
return t.load_template('layout.phtml', title=i18n.tlang('Admin'), module_selected='home_admin', session=session)
|
||||||
|
#return {}
|
||||||
|
|
||||||
|
|
||||||
|
@admin_app.get('/admin/login', name="admin_app.login_admin", skip=[check_login])
|
||||||
|
def login_admin(session={}):
|
||||||
|
|
||||||
|
db=WebModel.connection()
|
||||||
|
|
||||||
|
if cookie_name+'_remember' in request.cookies:
|
||||||
|
|
||||||
|
arr_user=simplequery.select(usermodel, db, dict_fields=['id', 'username'], where_sql='WHERE token_login=%s', dict_values=[request.cookies[cookie_name+'_remember']])
|
||||||
|
|
||||||
|
if len(arr_user)>0:
|
||||||
|
now_str=now()
|
||||||
|
date_now=format_local_strtime('YYYY-MM-DD HH:mm:ss', now_str)
|
||||||
|
|
||||||
|
db.query('update useradmin2 set last_login=%s WHERE id=%s', [date_now, arr_user[0]['id']])
|
||||||
|
|
||||||
|
request.session['login_admin']=True
|
||||||
|
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
redirect(app.get_url('app_admin.home_admin'))
|
||||||
|
|
||||||
|
with db.query('select count(id) as num_users from useradmin2', []) as cursor:
|
||||||
|
num_users=cursor.fetchone()['num_users']
|
||||||
|
|
||||||
|
if not num_users:
|
||||||
|
redirect(app.get_url('admin_app.signup_admin'))
|
||||||
|
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
i18n=I18n('admin2')
|
||||||
|
|
||||||
|
return t.load_template('login.phtml', title=i18n.tlang('Login'))
|
||||||
|
|
||||||
|
@admin_app.get('/admin/signup', skip=[check_login], name='admin_app.signup_admin')
|
||||||
|
def signup_admin(session={}):
|
||||||
|
|
||||||
|
db=WebModel.connection()
|
||||||
|
|
||||||
|
with db.query('select count(id) as num_users from useradmin2', []) as cursor:
|
||||||
|
num_users=cursor.fetchone()['num_users']
|
||||||
|
|
||||||
|
if num_users>0:
|
||||||
|
redirect(app.url_path_for('login_admin'))
|
||||||
|
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
i18n=I18n('admin2')
|
||||||
|
|
||||||
|
return t.load_template('signup.phtml', title=i18n.tlang('Signup'))
|
||||||
|
|
||||||
|
@admin_app.post('/admin/login', skip=[check_login], name='admin_app.check_login_admin')
|
||||||
|
def check_login_admin(session={}):
|
||||||
|
|
||||||
|
db=WebModel.connection()
|
||||||
|
|
||||||
|
i18n=I18n('admin2')
|
||||||
|
|
||||||
|
error=1
|
||||||
|
|
||||||
|
message=i18n.tlang('Invalid user and password')
|
||||||
|
|
||||||
|
no_login=check_login_tries(request, db)
|
||||||
|
|
||||||
|
#username=request.json.get('username', '')
|
||||||
|
#password=request.json.get('password', '')
|
||||||
|
#remember_login=request.json.get('remember_login', '')
|
||||||
|
getpost=GetPostFiles()
|
||||||
|
|
||||||
|
getpost.obtain_post()
|
||||||
|
|
||||||
|
username=getpost.post.get('username')
|
||||||
|
password=getpost.post.get('password')
|
||||||
|
remember_login=getpost.post.get('remember_login')
|
||||||
|
|
||||||
|
if username!='' and password!='' and not no_login:
|
||||||
|
|
||||||
|
with db.query('select * from useradmin2 WHERE username=%s', [username]) as cursor:
|
||||||
|
result=cursor.fetchone()
|
||||||
|
|
||||||
|
if result:
|
||||||
|
|
||||||
|
if usermodel.fields['password'].verify(password, result['password']):
|
||||||
|
|
||||||
|
remember_key=''
|
||||||
|
|
||||||
|
if remember_login==True:
|
||||||
|
# Send cookies
|
||||||
|
|
||||||
|
remember_key=create_key_encrypt()
|
||||||
|
|
||||||
|
timestamp=int(time())+315360000
|
||||||
|
|
||||||
|
response.set_cookie(key=cookie_name+'_remember', value=remember_key, expires=timestamp, max_age=315360000, httponly=True, path=config.application_root)
|
||||||
|
|
||||||
|
now_str=now()
|
||||||
|
date_now=format_local_strtime('YYYY-MM-DD HH:mm:ss', now_str)
|
||||||
|
|
||||||
|
db.query('update useradmin2 set token_login=%s, last_login=%s WHERE id=%s', [remember_key, date_now, result['id']])
|
||||||
|
|
||||||
|
session['login_admin']=True
|
||||||
|
session['user_id']=result['id']
|
||||||
|
session['theme']=result['dark_theme']
|
||||||
|
error=0
|
||||||
|
message=''
|
||||||
|
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
return {'error': error, 'message': message, 'no_login': no_login}
|
||||||
|
|
||||||
|
@admin_app.post('/admin/signup', skip=[check_login], name='admin_app.signup_insert_admin')
|
||||||
|
def signup_insert_admin(session={}):
|
||||||
|
|
||||||
|
i18n=I18n('admin2')
|
||||||
|
|
||||||
|
error=1
|
||||||
|
|
||||||
|
message=''
|
||||||
|
|
||||||
|
db=WebModel.connection()
|
||||||
|
|
||||||
|
arr_form={'username': usermodel.forms['username'], 'password': usermodel.forms['password'], 'repeat_password': PasswordForm('repeat_password', ''), 'email': usermodel.forms['email']}
|
||||||
|
|
||||||
|
getpost=GetPostFiles()
|
||||||
|
|
||||||
|
getpost.obtain_post()
|
||||||
|
|
||||||
|
#Only can exist and user
|
||||||
|
|
||||||
|
final_password=getpost.post.get('password', '')
|
||||||
|
|
||||||
|
(error, error_form, post, arr_form)=check_form(getpost.post, arr_form, sufix_form='_error')
|
||||||
|
|
||||||
|
post['password']=final_password
|
||||||
|
|
||||||
|
with db.query('select count(id) as num_users from useradmin2', []) as cursor:
|
||||||
|
num_users=cursor.fetchone()['num_users']
|
||||||
|
|
||||||
|
if num_users:
|
||||||
|
message="You cannot add new users from here"
|
||||||
|
else:
|
||||||
|
error=0
|
||||||
|
|
||||||
|
if not error:
|
||||||
|
|
||||||
|
if simplequery.insert(usermodel, db, dict(post)):
|
||||||
|
#if 1:
|
||||||
|
error=0
|
||||||
|
message="User added!"
|
||||||
|
else:
|
||||||
|
message=i18n.tlang('Sorry, cannot create the new user')
|
||||||
|
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
return {'error': error, 'message': message}
|
||||||
|
|
||||||
|
@admin_app.get('/admin/logout', name="admin_app.logout_admin", skip=[check_login])
|
||||||
|
def logout_admin(session={}):
|
||||||
|
|
||||||
|
if login_admin in session:
|
||||||
|
del session['login_admin']
|
||||||
|
|
||||||
|
if cookie_name+'_remember' in request.cookies:
|
||||||
|
response.delete_cookie(cookie_name+'_remember', path=session_opts['session.path'])
|
||||||
|
|
||||||
|
redirect(app.get_url('admin_app.login_admin'))
|
||||||
|
|
||||||
|
@admin_app.get('/change_lang')
|
||||||
|
def change_lang():
|
||||||
|
|
||||||
|
error=0
|
||||||
|
|
||||||
|
message=''
|
||||||
|
|
||||||
|
return {'error': 0, 'message': ''}
|
||||||
|
|
||||||
|
@admin_app.get('/admin/change_theme', name="admin_app.change_theme")
|
||||||
|
def change_theme(session={}):
|
||||||
|
|
||||||
|
try:
|
||||||
|
theme=int(request.query.get('theme', 0))
|
||||||
|
except:
|
||||||
|
theme=0
|
||||||
|
|
||||||
|
error=1
|
||||||
|
|
||||||
|
db=WebModel.connection()
|
||||||
|
|
||||||
|
session['theme']=theme
|
||||||
|
|
||||||
|
db.query('update useradmin2 set dark_theme=%s WHERE id=%s', [theme, session['user_id']])
|
||||||
|
|
||||||
|
error=0
|
||||||
|
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
return {'error': error, 'message': ''}
|
||||||
|
|
||||||
|
|
||||||
|
def check_login_tries(request, db):
|
||||||
|
|
||||||
|
logintries=LoginTries2(db)
|
||||||
|
|
||||||
|
logintries.safe_query()
|
||||||
|
|
||||||
|
ip=request.environ.get('HTTP_X_FORWARDED_FOR') or request.environ.get('REMOTE_ADDR')
|
||||||
|
|
||||||
|
"""
|
||||||
|
if 'x-real-ip' in request.headers:
|
||||||
|
ip=request.headers['x-real-ip']
|
||||||
|
elif 'x-forwarded-for' in request.headers:
|
||||||
|
ip=request.headers['x-forwarded-for']
|
||||||
|
else:
|
||||||
|
ip=request.client.host
|
||||||
|
"""
|
||||||
|
|
||||||
|
you_cannot_login=0
|
||||||
|
|
||||||
|
now_str=now()
|
||||||
|
date_now=format_local_strtime('YYYY-MM-DD HH:mm:ss', now_str)
|
||||||
|
|
||||||
|
date_check=format_local_strtime('YYYY-MM-DD HH:mm:ss', timestamp_to_datetime(obtain_timestamp(now_str)-seconds_login))
|
||||||
|
|
||||||
|
logintries.query('delete from logintries2 where last_login<%s', [date_check])
|
||||||
|
|
||||||
|
arr_try=logintries.set_conditions('WHERE ip=%s', [ip]).select_a_row_where()
|
||||||
|
|
||||||
|
if arr_try:
|
||||||
|
|
||||||
|
if arr_try['num_tries']<login_tries:
|
||||||
|
|
||||||
|
logintries.query('update logintries2 set num_tries=num_tries+1, last_login=%s WHERE ip=%s', [date_now, ip])
|
||||||
|
|
||||||
|
else:
|
||||||
|
|
||||||
|
you_cannot_login=1
|
||||||
|
|
||||||
|
else:
|
||||||
|
|
||||||
|
logintries.query('insert into logintries2 (`ip`, `num_tries`, `last_login`) VALUES (%s, %s, %s)', [ip, 1, date_now])
|
||||||
|
|
||||||
|
return you_cannot_login
|
||||||
|
|
||||||
|
app.merge(admin_app)
|
||||||
78
paramecio/modules/admin2/ausers.py
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
from paramecio.modules.admin2.models.admin import UserAdmin2
|
||||||
|
from paramecio.modules.admin2.app import admin_app
|
||||||
|
from paramecio.modules.admin2.libraries.config import modules_admin, modules_admin_icons
|
||||||
|
from paramecio.libraries.mtemplates import PTemplate, env_theme
|
||||||
|
from paramecio.libraries.i18n import I18n
|
||||||
|
import paramecio.modules.admin2.libraries.i18n
|
||||||
|
import os
|
||||||
|
#from paramecio.libraries.lists import SimpleList
|
||||||
|
from paramecio.libraries.generate_admin_class import GenerateAdminClass
|
||||||
|
from paramecio.libraries.db.formsutils import show_form
|
||||||
|
from paramecio.libraries.db.coreforms import SelectForm
|
||||||
|
from paramecio.libraries.db.coreforms import PasswordForm
|
||||||
|
from paramecio.wsgiapp import app
|
||||||
|
from paramecio.libraries.db.webmodel import WebModel
|
||||||
|
from paramecio.modules.admin2.models.admin import UserAdmin2
|
||||||
|
|
||||||
|
env=env_theme(__file__)
|
||||||
|
|
||||||
|
t=PTemplate(env)
|
||||||
|
|
||||||
|
#t.env.directories=admin_t.env.directories
|
||||||
|
"""
|
||||||
|
tpl_path=os.path.dirname(__file__).replace('/admin', '')+'/templates/admin'
|
||||||
|
|
||||||
|
if t.env.directories[1]!=tpl_path:
|
||||||
|
t.env.directories.insert(1, tpl_path)
|
||||||
|
"""
|
||||||
|
#modules_admin.append(['menu_users', 'people-circle', True])
|
||||||
|
|
||||||
|
modules_admin.append(['admin_app.admin_users', 'people-circle'])
|
||||||
|
|
||||||
|
modules_admin_icons.append('<symbol id="people-circle" viewBox="0 0 16 16"><path d="M11 6a3 3 0 1 1-6 0 3 3 0 0 1 6 0z"/><path fill-rule="evenodd" d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8zm8-7a7 7 0 0 0-5.468 11.37C3.242 11.226 4.805 10 8 10s4.757 1.225 5.468 2.37A7 7 0 0 0 8 1z"/></symbol>')
|
||||||
|
|
||||||
|
@admin_app.get('/admin/ausers', name="admin_app.admin_users")
|
||||||
|
@admin_app.post('/admin/ausers', name="admin_app.admin_users")
|
||||||
|
def admin_users(session={}):
|
||||||
|
|
||||||
|
i18n=I18n('admin2')
|
||||||
|
|
||||||
|
db=WebModel.connection()
|
||||||
|
|
||||||
|
user_admin=UserAdmin2(db)
|
||||||
|
|
||||||
|
user_admin.fields['privileges'].name_form=SelectForm
|
||||||
|
|
||||||
|
user_admin.fields['dark_theme'].name_form=SelectForm
|
||||||
|
|
||||||
|
user_admin.create_forms(['username', 'password', 'email', 'privileges', 'lang', 'dark_theme'])
|
||||||
|
|
||||||
|
user_admin.forms['privileges'].arr_select={0: I18n.lang('admin', 'without_privileges', 'Without privileges'), 1: I18n.lang('admin', 'selected_privileges', 'Selected privileges'), 2: I18n.lang('admin', 'administrator', 'Administrator')}
|
||||||
|
|
||||||
|
user_admin.forms['dark_theme'].arr_select={0: _('Light theme'), 1: _('Dark theme')}
|
||||||
|
|
||||||
|
user_admin.fields['password'].protected=False
|
||||||
|
|
||||||
|
url=app.get_url('admin_app.admin_users')
|
||||||
|
|
||||||
|
admin=GenerateAdminClass(user_admin, url, t)
|
||||||
|
|
||||||
|
admin.list.fields_showed=['username', 'email', 'double_auth', 'privileges', 'last_login']
|
||||||
|
|
||||||
|
admin.list.search_fields=['username']
|
||||||
|
|
||||||
|
admin.arr_fields_edit=['username', 'password', 'repeat_password', 'email', 'privileges', 'lang', 'dark_theme']
|
||||||
|
|
||||||
|
#slist=SimpleList(users, url, t)
|
||||||
|
|
||||||
|
#slist.fields_showed=['username', 'email', 'double_auth', 'last_login']
|
||||||
|
|
||||||
|
#slist=slist.show()
|
||||||
|
|
||||||
|
slist=admin.show()
|
||||||
|
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
return t.load_template('users.phtml', title=i18n.tlang('Admin users'), tlang=i18n.tlang, module_selected='admin_app.admin_users', slist=slist, session=session)
|
||||||
|
|
||||||
|
app.merge(admin_app)
|
||||||
10
paramecio/modules/admin2/libraries/config.py
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
#!/usr/bin/python3
|
||||||
|
|
||||||
|
# Every element
|
||||||
|
|
||||||
|
# ['Name of module admin', 'name_function_for_url_for', 'xml-icon']
|
||||||
|
|
||||||
|
modules_admin=[]
|
||||||
|
|
||||||
|
modules_admin_icons=[]
|
||||||
|
|
||||||
19
paramecio/modules/admin2/libraries/i18n.py
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
from parameciofast.libraries.i18n import I18n
|
||||||
|
|
||||||
|
"""
|
||||||
|
I18n.l['en-US']=I18n.l.get('en-US', {})
|
||||||
|
|
||||||
|
I18n.l['en-US']['fastadmin']=I18n.l['en-US'].get('fastadmin', {})
|
||||||
|
|
||||||
|
I18n.l['en-US']['fastadmin']['fastadmin_ausers']='Admin users'
|
||||||
|
|
||||||
|
I18n.l['es-ES']=I18n.l.get('es-ES', {})
|
||||||
|
|
||||||
|
I18n.l['es-ES']['fastadmin']=I18n.l['en-US'].get('fastadmin', {})
|
||||||
|
|
||||||
|
I18n.l['es-ES']['fastadmin']['fastadmin_ausers']='Usuarios admin'
|
||||||
|
"""
|
||||||
|
|
||||||
|
I18n.add_lang('en-US', 'admin2', 'admin_app.admin_users', 'Admin users')
|
||||||
|
I18n.add_lang('es-ES', 'admin2', 'admin_app.admin_users', 'Usuarios admin')
|
||||||
|
|
||||||
66
paramecio/modules/admin2/libraries/loginplugin.py
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
from bottle import request, response, redirect
|
||||||
|
from settings import config
|
||||||
|
import inspect
|
||||||
|
from paramecio.wsgiapp import app
|
||||||
|
|
||||||
|
def check_login(callback):
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
|
||||||
|
if 'session' in request.environ:
|
||||||
|
|
||||||
|
if request.environ['session'].get('login_admin', 0):
|
||||||
|
|
||||||
|
result = callback(*args, **kwargs)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
redirect(app.get_url('admin_app.login_admin'))
|
||||||
|
|
||||||
|
return wrapper
|
||||||
|
"""
|
||||||
|
class CheckLoginPlugin(object):
|
||||||
|
|
||||||
|
name = 'checklogin'
|
||||||
|
api = 2
|
||||||
|
|
||||||
|
def __init__(self, keyword='session'):
|
||||||
|
|
||||||
|
self.keyword=keyword
|
||||||
|
|
||||||
|
|
||||||
|
def setup(self, app):
|
||||||
|
''' Make sure that other installed plugins don't affect the same keyword argument.'''
|
||||||
|
for other in app.plugins:
|
||||||
|
if not isinstance(other, SessionPlugin): continue
|
||||||
|
if other.keyword == self.keyword:
|
||||||
|
raise PluginError("Found another login plugin with "\
|
||||||
|
"conflicting settings (non-unique keyword).")
|
||||||
|
|
||||||
|
def apply(self, callback, context):
|
||||||
|
|
||||||
|
# Test if the original callback accepts a 'session' keyword.
|
||||||
|
# Ignore it if it does not need a login handle.
|
||||||
|
|
||||||
|
conf = context.config.get('checklogin') or {}
|
||||||
|
|
||||||
|
keyword = conf.get('keyword', self.keyword)
|
||||||
|
|
||||||
|
args = inspect.getfullargspec(context.callback)[0]
|
||||||
|
|
||||||
|
if keyword not in args:
|
||||||
|
return callback
|
||||||
|
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
|
||||||
|
if 'session' in request.environ:
|
||||||
|
|
||||||
|
if request.environ['session'].get('login_admin', 1):
|
||||||
|
|
||||||
|
rv=callback(*args, **kwargs)
|
||||||
|
|
||||||
|
return rv
|
||||||
|
|
||||||
|
redirect(app.get_url('admin_app.home_admin'))
|
||||||
|
|
||||||
|
return wrapper
|
||||||
|
"""
|
||||||
1441
paramecio/modules/admin2/media/css/admin.css
Normal file
126
paramecio/modules/admin2/media/css/layout.css
Normal file
|
|
@ -0,0 +1,126 @@
|
||||||
|
|
||||||
|
body {
|
||||||
|
|
||||||
|
background: #121c27;
|
||||||
|
min-height: 100vh;
|
||||||
|
min-height: -webkit-fill-available;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
height: 100vh;
|
||||||
|
height: -webkit-fill-available;
|
||||||
|
max-height: 100vh;
|
||||||
|
overflow-x: auto;
|
||||||
|
/*overflow-y: hidden;*/
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
html {
|
||||||
|
height: -webkit-fill-available;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#header_dashboard {
|
||||||
|
|
||||||
|
background: #1d2939;
|
||||||
|
-webkit-box-shadow: 0px 3px 5px 0px rgba(0,0,0,0.75);
|
||||||
|
-moz-box-shadow: 0px 3px 5px 0px rgba(0,0,0,0.75);
|
||||||
|
box-shadow: 0px 3px 5px 0px rgba(0,0,0,0.75);
|
||||||
|
z-index:99999;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.bi {
|
||||||
|
vertical-align: -.125em;
|
||||||
|
pointer-events: none;
|
||||||
|
fill: currentColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hands {
|
||||||
|
|
||||||
|
vertical-align: -.140em !important;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Loader page */
|
||||||
|
|
||||||
|
.loader-div {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
height: 100vh;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index:999999;
|
||||||
|
/*background: url('../images/caledonian.png') no-repeat center 40%;
|
||||||
|
background-color: #ec1c24;*/
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loader {
|
||||||
|
position: relative;
|
||||||
|
width: 10vw;
|
||||||
|
height: 5vw;
|
||||||
|
padding: 1.5vw;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loader span {
|
||||||
|
position: absolute;
|
||||||
|
height: 0.8vw;
|
||||||
|
width: 0.8vw;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loader span:nth-child(1) {
|
||||||
|
animation: loading-dotsA 0.5s infinite linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loader span:nth-child(2) {
|
||||||
|
animation: loading-dotsB 0.5s infinite linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes loading-dotsA {
|
||||||
|
0% {
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
25% {
|
||||||
|
transform: translateX(2vw);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
75% {
|
||||||
|
transform: translateY(2vw);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes loading-dotsB {
|
||||||
|
0% {
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
25% {
|
||||||
|
transform: translateX(-2vw);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
75% {
|
||||||
|
transform: translateY(-2vw);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
49
paramecio/modules/admin2/media/css/responsive-nav.css
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
/*! responsive-nav.js 1.0.39 by @viljamis */
|
||||||
|
|
||||||
|
.nav-collapse ul {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
width: 100%;
|
||||||
|
display: block;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-collapse li {
|
||||||
|
width: 100%;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.js .nav-collapse {
|
||||||
|
clip: rect(0 0 0 0);
|
||||||
|
max-height: 0;
|
||||||
|
position: absolute;
|
||||||
|
display: block;
|
||||||
|
overflow: hidden;
|
||||||
|
zoom: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-collapse.opened {
|
||||||
|
max-height: 9999px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-toggle {
|
||||||
|
-webkit-tap-highlight-color: rgba(0,0,0,0);
|
||||||
|
-webkit-touch-callout: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
-ms-user-select: none;
|
||||||
|
-o-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: 40em) {
|
||||||
|
.js .nav-collapse {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.js .nav-collapse.closed {
|
||||||
|
max-height: none;
|
||||||
|
}
|
||||||
|
.nav-toggle {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
1
paramecio/modules/admin2/media/css/tooltipster.bundle.min.css
vendored
Normal file
BIN
paramecio/modules/admin2/media/images/ajax-loader.gif
Normal file
|
After Width: | Height: | Size: 500 B |
BIN
paramecio/modules/admin2/media/images/background.png
Normal file
|
After Width: | Height: | Size: 332 B |
BIN
paramecio/modules/admin2/media/images/background_title.png
Normal file
|
After Width: | Height: | Size: 476 B |
BIN
paramecio/modules/admin2/media/images/background_title_login.png
Normal file
|
After Width: | Height: | Size: 5 KiB |
BIN
paramecio/modules/admin2/media/images/languages/en-US.png
Normal file
|
After Width: | Height: | Size: 562 B |
BIN
paramecio/modules/admin2/media/images/languages/es-ES.png
Normal file
|
After Width: | Height: | Size: 539 B |
BIN
paramecio/modules/admin2/media/images/logo.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
2
paramecio/modules/admin2/media/js/jquery-3.7.1.min.js
vendored
Normal file
1
paramecio/modules/admin2/media/js/responsive-nav.min.js
vendored
Normal file
2
paramecio/modules/admin2/media/js/tooltipster.bundle.min.js
vendored
Normal file
0
paramecio/modules/admin2/models/__init__.py
Normal file
76
paramecio/modules/admin2/models/admin.py
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
from paramecio.libraries.i18n import I18n
|
||||||
|
from paramecio.libraries.db.webmodel import WebModel
|
||||||
|
#from paramecio.libraries.db.usermodel import UserModel
|
||||||
|
from paramecio.libraries.db import corefields
|
||||||
|
from paramecio.libraries.db.extrafields.emailfield import EmailField
|
||||||
|
from paramecio.libraries.db.extrafields.passwordfield import PasswordField
|
||||||
|
from paramecio.libraries.db.extrafields.langfield import LangField
|
||||||
|
from paramecio.libraries.db.extrafields.ipfield import IpField
|
||||||
|
from paramecio.libraries.db.extrafields.datetimefield import DateTimeField
|
||||||
|
from paramecio.libraries.db.usermodel import UserModel
|
||||||
|
|
||||||
|
class PrivilegesField2(corefields.IntegerField):
|
||||||
|
|
||||||
|
def show_formatted(self, value):
|
||||||
|
|
||||||
|
value=int(value)
|
||||||
|
|
||||||
|
if value==0:
|
||||||
|
return I18n.lang('admin', 'without_privileges', 'Without privileges')
|
||||||
|
elif value==1:
|
||||||
|
return I18n.lang('admin', 'selected_privileges', 'Selected privileges')
|
||||||
|
elif value==2:
|
||||||
|
return I18n.lang('admin', 'administrator', 'Administrator')
|
||||||
|
|
||||||
|
class UserAdmin2(UserModel):
|
||||||
|
|
||||||
|
#def create_fields(self):
|
||||||
|
def __init__(self, connection=None):
|
||||||
|
|
||||||
|
super().__init__(connection)
|
||||||
|
|
||||||
|
# I can change other fields here, how the name.
|
||||||
|
|
||||||
|
self.register(corefields.CharField('username'))
|
||||||
|
|
||||||
|
self.fields['username'].required=True
|
||||||
|
|
||||||
|
self.register(PasswordField('password'))
|
||||||
|
|
||||||
|
self.fields['password'].required=True
|
||||||
|
|
||||||
|
self.register(EmailField('email'))
|
||||||
|
|
||||||
|
self.fields['email'].required=True
|
||||||
|
|
||||||
|
self.register(corefields.CharField('token_recovery'))
|
||||||
|
|
||||||
|
self.register(corefields.CharField('token_login'))
|
||||||
|
|
||||||
|
#self.register(PasswordField('token_auth'))
|
||||||
|
|
||||||
|
self.register(PrivilegesField2('privileges'))
|
||||||
|
|
||||||
|
self.register(LangField('lang', 20))
|
||||||
|
|
||||||
|
self.register(corefields.BooleanField('disabled'))
|
||||||
|
|
||||||
|
self.register(corefields.BooleanField('double_auth'))
|
||||||
|
|
||||||
|
self.register(corefields.BooleanField('dark_theme'))
|
||||||
|
|
||||||
|
#self.register(corefields.IntegerField('num_tries', 1))
|
||||||
|
|
||||||
|
self.register(DateTimeField('last_login'))
|
||||||
|
|
||||||
|
class LoginTries2(WebModel):
|
||||||
|
|
||||||
|
def __init__(self, connection=None):
|
||||||
|
|
||||||
|
super().__init__(connection)
|
||||||
|
self.register(IpField('ip'))
|
||||||
|
self.register(corefields.IntegerField('num_tries', 1))
|
||||||
|
self.register(DateTimeField('last_login'))
|
||||||
|
|
||||||
176
paramecio/modules/admin2/templates/layout.phtml
Normal file
|
|
@ -0,0 +1,176 @@
|
||||||
|
<%
|
||||||
|
|
||||||
|
from paramecio.modules.admin2.libraries.config import modules_admin, modules_admin_icons
|
||||||
|
from parameciofast.libraries.i18n import I18n
|
||||||
|
|
||||||
|
i18n=I18n('admin2')
|
||||||
|
|
||||||
|
dark_checked=""
|
||||||
|
dark_css=""
|
||||||
|
|
||||||
|
if session.get('theme', '0')==True:
|
||||||
|
dark_checked='checked'
|
||||||
|
dark_css='dark'
|
||||||
|
|
||||||
|
|
||||||
|
%>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">
|
||||||
|
<title>${title}</title>
|
||||||
|
<link href="${make_media_url('css/admin.css', 'admin2')}" rel="stylesheet" />
|
||||||
|
<link href="${make_media_url('css/responsive-nav.css', 'admin2')}" rel="stylesheet" />
|
||||||
|
<link href="${make_media_url('css/tooltipster.bundle.min.css', 'admin2')}" rel="stylesheet" />
|
||||||
|
<!--<link href="https://fonts.googleapis.com/css?family=Roboto" rel="stylesheet"> -->
|
||||||
|
<%block name="extra_css">
|
||||||
|
</%block>
|
||||||
|
<script language="Javascript" src="${make_media_url('js/jquery-3.7.1.min.js', 'admin2')}"></script>
|
||||||
|
<script language="Javascript" src="${make_media_url('js/responsive-nav.min.js', 'admin2')}"></script>
|
||||||
|
<script language="Javascript" src="${make_media_url('js/tooltipster.bundle.min.js', 'admin2')}"></script>
|
||||||
|
<%block name="extra_js">
|
||||||
|
</%block>
|
||||||
|
<%block name="extra_header">
|
||||||
|
</%block>
|
||||||
|
</head>
|
||||||
|
<body class="${dark_css}">
|
||||||
|
<div id="layer_loading"><div id="container_loading"><div class="lds-dual-ring"></div></div></div>
|
||||||
|
<div id="languages_general">
|
||||||
|
</div>
|
||||||
|
<div id="logout">
|
||||||
|
<%block name="logout"><a href="${url_for('admin_app.logout_admin')}"><i class="fa fa-power-off" aria-hidden="true"></i> Logout</a></%block>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="center_body">
|
||||||
|
<div id="header">
|
||||||
|
<a href="#nav" id="toggle"><i class="fa fa-bars" aria-hidden="true"></i><span>Menu</span></a>
|
||||||
|
<%block name="title_admin"><span id="title_phango">Admin</span> <span id="title_framework">site!</span>
|
||||||
|
</%block>
|
||||||
|
<%block name="languages">
|
||||||
|
<div id="languages_general">
|
||||||
|
<%def name="select_lang(i18n, lang_selected)">${'choose_flag' if i18n==lang_selected else 'no_choose_flag'}</%def>
|
||||||
|
</%block>
|
||||||
|
% if lang_selected!=None:
|
||||||
|
% for i18n_lang in i18n.dict_i18n:
|
||||||
|
<a class="${select_lang(i18n_lang, lang_selected)}" href=""><img src="${make_media_url('images/languages/'+i18n_lang+'.png', 'admin2')}" alt="${i18n_lang}"/></a>
|
||||||
|
% endfor
|
||||||
|
% endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="content_admin">
|
||||||
|
<nav id="menu" class="nav-collapse">
|
||||||
|
<ul>
|
||||||
|
<li class="menu_title"><%block name="applications"><i class="fa fa-gear" aria-hidden="true"></i>${tlang('Applications')}</li></%block>
|
||||||
|
% for module in modules_admin:
|
||||||
|
<li>
|
||||||
|
% if len(module)>2:
|
||||||
|
<div class="father_admin">
|
||||||
|
<svg class="bi me-2" width="16" height="16"><use xlink:href="#${module[1]}"></use></svg>
|
||||||
|
${i18n.clang('admin2', module[0], module[0])}
|
||||||
|
</div>
|
||||||
|
% else:
|
||||||
|
<a href="${url_for(module[0])}" class="${'selected_menu' if module[0]==module_selected else ''}">
|
||||||
|
<svg class="bi me-2" width="16" height="16"><use xlink:href="#${module[1]}"></use></svg>
|
||||||
|
${i18n.clang('admin2', module[0], module[0])}
|
||||||
|
</a>
|
||||||
|
% endif
|
||||||
|
</li>
|
||||||
|
% endfor
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
<div class="contents">
|
||||||
|
<h1>${title}</h1>
|
||||||
|
<div class="switch-btn">
|
||||||
|
<div class="switch-text">
|
||||||
|
${_('Dark theme')}
|
||||||
|
</div>
|
||||||
|
<div class="switch-slider">
|
||||||
|
<label class="switch">
|
||||||
|
<input type="checkbox" name="theme" value="1" id="theme" ${dark_checked}/>
|
||||||
|
<span class="slider round"></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="content">
|
||||||
|
|
||||||
|
<%block name="content">
|
||||||
|
</%block>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="loading_ajax">
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
var navigation = responsiveNav(".nav-collapse", {customToggle: "#toggle"});
|
||||||
|
|
||||||
|
$('.tooltip').tooltipster({
|
||||||
|
animation: 'fade',
|
||||||
|
delay: 100,
|
||||||
|
trigger: 'click'
|
||||||
|
});
|
||||||
|
|
||||||
|
const slider = document.querySelector('input[name="theme"]');
|
||||||
|
|
||||||
|
slider.addEventListener("change", function () {
|
||||||
|
|
||||||
|
//Block button while send to ajax.
|
||||||
|
|
||||||
|
$(this).prop("disabled",true);
|
||||||
|
|
||||||
|
var dark='';
|
||||||
|
|
||||||
|
if (this.checked) {
|
||||||
|
document.body.classList.add("dark");
|
||||||
|
|
||||||
|
dark='1';
|
||||||
|
|
||||||
|
} else {
|
||||||
|
document.body.classList.remove("dark");
|
||||||
|
|
||||||
|
dark='0';
|
||||||
|
}
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: "${url_for('admin_app.change_theme')}?theme="+dark,
|
||||||
|
type: 'GET',
|
||||||
|
data: {},
|
||||||
|
success: function (data) {
|
||||||
|
|
||||||
|
if(!data.error) {
|
||||||
|
|
||||||
|
console.log('Changed to dark in all pages');
|
||||||
|
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
|
||||||
|
console.log('Cannot set dark theme in all pages!');
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
$(slider).prop("disabled",false);
|
||||||
|
|
||||||
|
},
|
||||||
|
error: function (data) {
|
||||||
|
|
||||||
|
alert('Error: '+data.status+' '+data.statusText);
|
||||||
|
|
||||||
|
$(slider).prop("disabled", false);
|
||||||
|
|
||||||
|
},
|
||||||
|
dataType: 'json'
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
$(document).ready(function () {
|
||||||
|
|
||||||
|
$('#layer_loading').hide();
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
<%block name="jscript_block">
|
||||||
|
</%block>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
123
paramecio/modules/admin2/templates/layout_bs.phtml
Normal file
|
|
@ -0,0 +1,123 @@
|
||||||
|
<%
|
||||||
|
|
||||||
|
from parameciofast.modules.fastadmin.libraries.config import modules_admin, modules_admin_icons
|
||||||
|
from parameciofast.libraries.i18n import I18n
|
||||||
|
|
||||||
|
i18n=I18n('fastadmin')
|
||||||
|
|
||||||
|
%>
|
||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>${title}</title>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
|
||||||
|
<link rel="stylesheet" href="${make_media_url('css/layout.css', 'fastadmin')}" type="text/css" media="all" />
|
||||||
|
<%block name="css">
|
||||||
|
</%block>
|
||||||
|
<%block name="header_js">
|
||||||
|
</%block>
|
||||||
|
</head>
|
||||||
|
<body data-bs-theme="dark">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
|
||||||
|
<symbol id="home" viewBox="0 0 16 16">
|
||||||
|
<path d="M8.354 1.146a.5.5 0 0 0-.708 0l-6 6A.5.5 0 0 0 1.5 7.5v7a.5.5 0 0 0 .5.5h4.5a.5.5 0 0 0 .5-.5v-4h2v4a.5.5 0 0 0 .5.5H14a.5.5 0 0 0 .5-.5v-7a.5.5 0 0 0-.146-.354L13 5.793V2.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.293L8.354 1.146zM2.5 14V7.707l5.5-5.5 5.5 5.5V14H10v-4a.5.5 0 0 0-.5-.5h-3a.5.5 0 0 0-.5.5v4H2.5z"/>
|
||||||
|
</symbol>
|
||||||
|
%for module_icon in modules_admin_icons:
|
||||||
|
|
||||||
|
${module_icon|n}
|
||||||
|
|
||||||
|
%endfor
|
||||||
|
</svg>
|
||||||
|
<div class="loader-div" id="loader-div" style="display:none;">
|
||||||
|
<span class="loader">
|
||||||
|
<span></span>
|
||||||
|
<span></span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<main>
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div id="header_dashboard" class="row pt-3 pb-3">
|
||||||
|
<div class="col-sm-6 m-0 d-flex align-content-center flex-wrap align-self-center">
|
||||||
|
<h2 class="text-left ms-4 mt-0 mt-0 p-0" id="form_title">${tlang('Dashboard')}</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-2 p-3 m-0 text-white bg-dark" style="height:91vh;">
|
||||||
|
<!--<a href="/" class="d-flex align-items-center mb-3 mb-md-0 me-md-auto text-white text-decoration-none">
|
||||||
|
<svg class="bi me-2" width="40" height="32"><use xlink:href="#bootstrap"></use></svg>
|
||||||
|
<span class="fs-4">Sidebar</span>
|
||||||
|
</a>-->
|
||||||
|
<hr>
|
||||||
|
<!--<ul class="nav nav-pills flex-column mb-auto">
|
||||||
|
<li>
|
||||||
|
<a href="#" class="nav-link active text-white">
|
||||||
|
<svg class="bi me-2" width="16" height="16"><use xlink:href="#speedometer2"></use></svg>
|
||||||
|
${tlang('Dashboard')}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>-->
|
||||||
|
% for module in modules_admin:
|
||||||
|
<ul class="nav nav-pills flex-column mb-auto">
|
||||||
|
<li>
|
||||||
|
<a href="${url_for(module[0])}" class="nav-link text-white menu_item ${'active' if module[0]==module_selected else ''}" id="menu_${module[0]}">
|
||||||
|
<svg class="bi me-2" width="16" height="16"><use xlink:href="#${module[1]}"></use></svg>
|
||||||
|
${i18n.clang('fastadmin', module[0], module[0])}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
% endfor
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="col-sm m-2 pt-3" style="">
|
||||||
|
<%block name="content">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header bg-primary bg-gradient">
|
||||||
|
${tlang('Welcome to admin')}
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<p>${tlang('This is the admin section of your site.')}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</%block>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
<footer class="footer m-0 p-0">
|
||||||
|
|
||||||
|
</footer>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.8/dist/umd/popper.min.js" integrity="sha384-I7E8VVD/ismYTF4hNIPjVp/Zjvgyol6VFvRkX/vR+Vc4jQkC+hVqc2pM8ODewa9r" crossorigin="anonymous"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.min.js" integrity="sha384-0pUGZvbkm6XF6gxjEnlmuGrJXVbNuzT9qBBavbLwCsOGabYfZo0T0to5eqruptLy" crossorigin="anonymous"></script>
|
||||||
|
<script src="https://code.jquery.com/jquery-3.7.1.min.js" integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo=" crossorigin="anonymous"></script>
|
||||||
|
<script>
|
||||||
|
document.documentElement.setAttribute('data-bs-theme','dark');
|
||||||
|
|
||||||
|
setTimeout(function () {
|
||||||
|
|
||||||
|
$('#loader-div').fadeOut(2000);
|
||||||
|
|
||||||
|
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
|
$('.menu_item').hover( function (e) {
|
||||||
|
|
||||||
|
if($(this).hasClass('active')) {
|
||||||
|
|
||||||
|
$(this).removeClass('active');
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
$(this).addClass('active');
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
<%block name="js">
|
||||||
|
</%block>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
171
paramecio/modules/admin2/templates/login.phtml
Normal file
|
|
@ -0,0 +1,171 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>${title}</title>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
|
||||||
|
<link href="${make_media_url('css/layout.css', 'admin2')}" rel="stylesheet" />
|
||||||
|
<%block name="css">
|
||||||
|
</%block>
|
||||||
|
<%block name="header_js">
|
||||||
|
</%block>
|
||||||
|
</head>
|
||||||
|
<body data-bs-theme="dark">
|
||||||
|
<div class="loader-div" id="loader-div" style="display:none;">
|
||||||
|
<span class="loader">
|
||||||
|
<span></span>
|
||||||
|
<span></span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="container-fluid m-0 p-0">
|
||||||
|
<div class="d-flex align-items-center" style="height:100vh;">
|
||||||
|
<div class="col-3"></div>
|
||||||
|
<div class="col">
|
||||||
|
<%block name="content">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header bg-primary">
|
||||||
|
${tlang('Login')}
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<form method="POST" action="${url_for('admin_app.check_login_admin')}" id="login_form" class="needs-validation" novalidate>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="username_form" class="form-label">${tlang('Username')}</label>
|
||||||
|
<input type="text" class="form-control form-control-lg has-validation" id="username_form" name="password" aria-describedby="username" autocomplete="off" required>
|
||||||
|
<div class="invalid-feedback">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="password_form" class="form-label">Password</label>
|
||||||
|
<input type="password" class="form-control form-control-lg has-validation" id="password_form" name="password" autocomplete="off" required>
|
||||||
|
<div class="invalid-feedback" id="login_invalid">
|
||||||
|
${tlang('Error: username or password invalid')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3 form-check">
|
||||||
|
<input type="checkbox" class="form-check-input" id="remember_login_form" name="remember_login" value="1">
|
||||||
|
<label class="form-check-label" for="autologin">${tlang('Remember login')}</label>
|
||||||
|
</div>
|
||||||
|
<button type="submit" id="login_submit" class="btn btn-primary">Submit</button>
|
||||||
|
${csrf_token()|n}
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</%block>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-3"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.8/dist/umd/popper.min.js" integrity="sha384-I7E8VVD/ismYTF4hNIPjVp/Zjvgyol6VFvRkX/vR+Vc4jQkC+hVqc2pM8ODewa9r" crossorigin="anonymous"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.min.js" integrity="sha384-0pUGZvbkm6XF6gxjEnlmuGrJXVbNuzT9qBBavbLwCsOGabYfZo0T0to5eqruptLy" crossorigin="anonymous"></script>
|
||||||
|
<script src="https://code.jquery.com/jquery-3.7.1.min.js" integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo=" crossorigin="anonymous"></script>
|
||||||
|
<script>
|
||||||
|
document.documentElement.setAttribute('data-bs-theme','dark');
|
||||||
|
</script>
|
||||||
|
<%block name="jscript">
|
||||||
|
<script>
|
||||||
|
|
||||||
|
/*setTimeout(function () {
|
||||||
|
|
||||||
|
$('#loader-div').fadeOut(2000);
|
||||||
|
|
||||||
|
|
||||||
|
}, 1000);*/
|
||||||
|
|
||||||
|
$(document).ready( function () {
|
||||||
|
|
||||||
|
$('#login_form').submit( function (event) {
|
||||||
|
|
||||||
|
$('#username_form').get(0).setCustomValidity("");
|
||||||
|
$('#password_form').get(0).setCustomValidity("");
|
||||||
|
|
||||||
|
form=document.getElementById('login_form');
|
||||||
|
|
||||||
|
error=false;
|
||||||
|
|
||||||
|
if (!form.checkValidity()) {
|
||||||
|
|
||||||
|
error=true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
|
||||||
|
form.classList.add('was-validated');
|
||||||
|
|
||||||
|
if(!error) {
|
||||||
|
|
||||||
|
$('#loader-div').show();
|
||||||
|
|
||||||
|
$('#login_submit').prop('disabled', true);
|
||||||
|
|
||||||
|
const is_checked=document.getElementById('remember_login_form').checked;
|
||||||
|
|
||||||
|
data_form={'username': $('#username_form').val(), 'password': $('#password_form').val(), 'csrf_token': $("#csrf_token").val(), 'remember_login': is_checked};
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: "${url_for('admin_app.check_login_admin')}",
|
||||||
|
method: "POST",
|
||||||
|
dataType: "json",
|
||||||
|
contentType : 'application/json',
|
||||||
|
data: JSON.stringify(data_form),
|
||||||
|
error: function (data) {
|
||||||
|
|
||||||
|
console.log(JSON.stringify(data));
|
||||||
|
$('#loader-div').hide();
|
||||||
|
$('#login_submit').prop('disabled', false);
|
||||||
|
|
||||||
|
alert('${tlang("Error: please, try again later")}');
|
||||||
|
|
||||||
|
},
|
||||||
|
}).done(function(data) {
|
||||||
|
|
||||||
|
if(data.error==0)
|
||||||
|
{
|
||||||
|
|
||||||
|
//location.reload()
|
||||||
|
location.href="${url_for('admin_app.home_admin')}";
|
||||||
|
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
|
||||||
|
$('#username_form').get(0).setCustomValidity("${tlang('Error: username or password invalid')}");
|
||||||
|
$('#password_form').get(0).setCustomValidity("${tlang('Error: username or password invalid')}");
|
||||||
|
|
||||||
|
console.log(JSON.stringify(data));
|
||||||
|
|
||||||
|
if(data.no_login) {
|
||||||
|
|
||||||
|
$('#login_invalid').html("${tlang('Error: you try login excessive times, please wait some minutes for try again')}");
|
||||||
|
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
|
||||||
|
$('#login_invalid').html("${tlang('Error: username or password invalid')}");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#login_submit').prop('disabled', false);
|
||||||
|
|
||||||
|
$('#loader-div').hide();
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</%block>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
186
paramecio/modules/admin2/templates/signup.phtml
Normal file
|
|
@ -0,0 +1,186 @@
|
||||||
|
<%inherit file="login.phtml"/>
|
||||||
|
<%block name="content">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header bg-primary">
|
||||||
|
${tlang('Signup')}
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<form method="POST" action="${url_for('admin_app.signup_insert_admin')}" id="login_form" class="needs-validation" novalidate>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="username_form" class="form-label">${tlang('Username')}*</label>
|
||||||
|
<input type="text" class="form-control form-control-lg has-validation" id="username_form" name="password" aria-describedby="username" autocomplete="off" minlength="4" pattern="\w{4,32}" required>
|
||||||
|
<div class="invalid-feedback">
|
||||||
|
${tlang('You need a valid username')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="email_form" class="form-label">${tlang('Email')}*</label>
|
||||||
|
<input type="email" class="form-control form-control-lg has-validation" id="email_form" name="email" autocomplete="off" minlength="4" required>
|
||||||
|
<div class="invalid-feedback">
|
||||||
|
${tlang('You need an email')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="password_form" class="form-label">Password*</label>
|
||||||
|
<input type="password" class="form-control form-control-lg has-validation" id="password_form" name="password" autocomplete="off" minlength="2" required>
|
||||||
|
<div class="invalid-feedback">
|
||||||
|
${tlang('You need a password')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="repeat_password_form" class="form-label">${tlang('Repeat password')}*</label>
|
||||||
|
<input type="password" class="form-control form-control-lg has-validation" id="repeat_password_form" name="repeat_password" autocomplete="off" minlength="2" required>
|
||||||
|
<div class="invalid-feedback">
|
||||||
|
${tlang('You need the same password in this field and not empty')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button type="submit" id="login_submit" class="btn btn-primary">${tlang('Create user')}</button>
|
||||||
|
${csrf_token()|n}
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</%block>
|
||||||
|
<%block name="jscript">
|
||||||
|
<script>
|
||||||
|
$(document).ready( function () {
|
||||||
|
|
||||||
|
$('#repeat_password_form').on('input',function(e){
|
||||||
|
|
||||||
|
if($('#repeat_password_form').val()!=$('#password_form').val()) {
|
||||||
|
|
||||||
|
console.log('No match');
|
||||||
|
|
||||||
|
$('#repeat_password_form').get(0).setCustomValidity("${tlang('Passwords doesn\'t match')}");
|
||||||
|
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
|
||||||
|
$('#repeat_password_form').get(0).setCustomValidity("");
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
// Fetch all the forms we want to apply custom Bootstrap validation styles to
|
||||||
|
var forms = document.querySelectorAll('.needs-validation');
|
||||||
|
var error=false;
|
||||||
|
|
||||||
|
// Loop over them and prevent submission
|
||||||
|
Array.prototype.slice.call(forms)
|
||||||
|
.forEach(function (form) {
|
||||||
|
form.addEventListener('submit', function (event) {
|
||||||
|
|
||||||
|
error=false;
|
||||||
|
|
||||||
|
if (!form.checkValidity()) {
|
||||||
|
|
||||||
|
error=true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
|
||||||
|
form.classList.add('was-validated');
|
||||||
|
|
||||||
|
var form_data=new FormData(form);
|
||||||
|
|
||||||
|
if(!error) {
|
||||||
|
|
||||||
|
console.log('send submit');
|
||||||
|
|
||||||
|
$('#loader-div').show();
|
||||||
|
|
||||||
|
$('#login_submit').prop('disabled', true);
|
||||||
|
|
||||||
|
data_form={'username': $('#username_form').val(), 'email': $('#email_form').val(), 'password': $('#password_form').val(), 'repeat_password': $('#repeat_password_form').val(), 'csrf_token': $("#csrf_token").val()};
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: "${url_for('admin_app.signup_insert_admin')}",
|
||||||
|
method: "POST",
|
||||||
|
dataType: "json",
|
||||||
|
contentType : 'application/json',
|
||||||
|
data: JSON.stringify(data_form),
|
||||||
|
error: function (data) {
|
||||||
|
|
||||||
|
console.log(JSON.stringify(data));
|
||||||
|
$('#loader-div').hide();
|
||||||
|
$('#login_submit').prop('disabled', false);
|
||||||
|
|
||||||
|
alert('${tlang("Error: please, try again later")}');
|
||||||
|
|
||||||
|
},
|
||||||
|
}).done(function(data) {
|
||||||
|
|
||||||
|
if(data.error==0)
|
||||||
|
{
|
||||||
|
|
||||||
|
//location.reload()
|
||||||
|
location.href="${url_for('admin_app.login_admin')}";
|
||||||
|
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
|
||||||
|
$('#login_submit').prop('disabled', false);
|
||||||
|
|
||||||
|
$('#loader-div').hide();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
/*$('#login_form').submit( function () {
|
||||||
|
|
||||||
|
$('#loader-div').show();
|
||||||
|
|
||||||
|
$('#login_submit').prop('disabled', true);
|
||||||
|
|
||||||
|
data_form={'username': $('#username_form').val(), 'email': $('#email_form').val(), 'password': $('#password_form').val(), 'repeat_password': $('#repeat_password_form').val(), 'csrf_token': $("#csrf_token").val()};
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: "${url_for('admin_app.signup_insert_admin')}",
|
||||||
|
method: "POST",
|
||||||
|
dataType: "json",
|
||||||
|
contentType : 'application/json',
|
||||||
|
data: JSON.stringify(data_form),
|
||||||
|
error: function (data) {
|
||||||
|
|
||||||
|
console.log(JSON.stringify(data));
|
||||||
|
$('#loader-div').hide();
|
||||||
|
$('#login_submit').prop('disabled', false);
|
||||||
|
|
||||||
|
},
|
||||||
|
}).done(function(data) {
|
||||||
|
|
||||||
|
if(data.error==0)
|
||||||
|
{
|
||||||
|
|
||||||
|
//location.reload()
|
||||||
|
location.href="${url_for('admin_app.home_admin')}";
|
||||||
|
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
|
||||||
|
$('#login_submit').prop('disabled', false);
|
||||||
|
|
||||||
|
$('#loader-div').hide();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
return false;
|
||||||
|
|
||||||
|
});*/
|
||||||
|
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</%block>
|
||||||
4
paramecio/modules/admin2/templates/users.phtml
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<%inherit file="layout.phtml"/>
|
||||||
|
<%block name="content">
|
||||||
|
${slist|n}
|
||||||
|
</%block>
|
||||||
|
|
@ -4,7 +4,7 @@ list_modules=[]
|
||||||
|
|
||||||
from paramecio.modules.welcome import index
|
from paramecio.modules.welcome import index
|
||||||
|
|
||||||
from paramecio.modules.admin import index
|
from paramecio.modules.admin2 import index
|
||||||
|
|
||||||
from paramecio.modules.lang import index
|
from paramecio.modules.lang import index
|
||||||
|
|
||||||
|
|
|
||||||