modern #2

Merged
absurdo merged 38 commits from modern into master 2025-03-15 00:00:11 +00:00
30 changed files with 2846 additions and 6 deletions
Showing only changes of commit 352f3ec36b - Show all commits

View file

@ -8,7 +8,7 @@ import re
from pathlib import Path
from base64 import b64encode
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 urllib.parse import urlparse
@ -265,7 +265,7 @@ def start():
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:

View file

@ -40,6 +40,8 @@ class DateTimeField(PhangoField):
self.type_sql='datetime'
self.default_value='0000-00-00 00:00:00'
def check(self, value):
if self.utc:
@ -68,6 +70,9 @@ class DateTimeField(PhangoField):
value=datetime.format_local_strtime('YYYY-MM-DD HH:mm:ss', value)
if value=='':
return '0000-00-00 00:00:00'
return value
def show_formatted(self, value):

View file

@ -24,7 +24,7 @@ from mako.template import Template
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.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.db.formsutils import csrf_token
from paramecio.libraries.js import make_js_url
@ -448,7 +448,7 @@ class HeaderHTML:
s['flash']=''
s.save()
#s.save()
return message
@ -458,7 +458,7 @@ def set_flash_message(message):
s['flash']=message
s.save()
#s.save()
def qf(text):

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

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

View 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=[]

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

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

File diff suppressed because it is too large Load diff

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

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

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 500 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 332 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 476 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 562 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 539 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

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

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

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

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

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

View file

@ -0,0 +1,4 @@
<%inherit file="layout.phtml"/>
<%block name="content">
${slist|n}
</%block>

View file

@ -4,7 +4,7 @@ list_modules=[]
from paramecio.modules.welcome import index
from paramecio.modules.admin import index
from paramecio.modules.admin2 import index
from paramecio.modules.lang import index