Compare commits
7 Commits
v1.0.1-rc1
...
v1.0.4
| Author | SHA1 | Date | |
|---|---|---|---|
| bc4d5dc6d9 | |||
| 295e1a78e6 | |||
| 32f93f463e | |||
| 94e62b6ea9 | |||
| 19f133a1ef | |||
| 23633ad0f5 | |||
| 526ae88e2b |
5
.gitignore
vendored
@ -26,3 +26,8 @@ _templates
|
||||
# javascript
|
||||
package-lock.json
|
||||
.vscode/symbols.json
|
||||
|
||||
apps/static/assets/node_modules
|
||||
apps/static/assets/yarn.lock
|
||||
apps/static/assets/.temp
|
||||
|
||||
|
||||
29
CHANGELOG.md
@ -1,5 +1,34 @@
|
||||
# Change Log
|
||||
|
||||
## [1.0.4] 2021-11-10
|
||||
### Improvements
|
||||
|
||||
- Bump Codebase: [Flask Dashboard](https://github.com/app-generator/boilerplate-code-flask-dashboard) v2.0.0
|
||||
- Dependencies update (all packages)
|
||||
- Flask==2.0.1 (latest stable version)
|
||||
- Better Code formatting
|
||||
- Improved Files organization
|
||||
- Optimize imports
|
||||
- Docker Scripts Update
|
||||
- Gulp Tooling (SASS Compilation)
|
||||
- Fixes:
|
||||
- Import error caused by WTForms
|
||||
|
||||
## [1.0.3] 2021-05-16
|
||||
### Dependencies Update
|
||||
|
||||
- Bump Codebase: [Flask Dashboard](https://github.com/app-generator/boilerplate-code-flask-dashboard) v1.0.6
|
||||
- Freeze used versions in `requirements.txt`
|
||||
- jinja2 = 2.11.3
|
||||
|
||||
## [1.0.2] 2021-05-11
|
||||
### Improvements
|
||||
|
||||
- Bump Codebase: [Flask Dashboard](https://github.com/app-generator/boilerplate-code-flask-dashboard) v1.0.5
|
||||
- Freeze used versions in `requirements.txt`
|
||||
- flask_sqlalchemy = 2.4.4
|
||||
- sqlalchemy = 1.3.23
|
||||
|
||||
## [1.0.1] 2021-02-10
|
||||
### Improvements
|
||||
|
||||
|
||||
15
Dockerfile
@ -1,11 +1,14 @@
|
||||
FROM python:3.6
|
||||
FROM python:3.9
|
||||
|
||||
ENV FLASK_APP run.py
|
||||
COPY . .
|
||||
|
||||
COPY run.py gunicorn-cfg.py requirements.txt config.py .env ./
|
||||
COPY app app
|
||||
# set environment variables
|
||||
ENV PYTHONDONTWRITEBYTECODE 1
|
||||
ENV PYTHONUNBUFFERED 1
|
||||
|
||||
RUN pip install -r requirements.txt
|
||||
# install python dependencies
|
||||
RUN pip install --upgrade pip
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
EXPOSE 5005
|
||||
# gunicorn
|
||||
CMD ["gunicorn", "--config", "gunicorn-cfg.py", "run:app"]
|
||||
|
||||
154
README.md
@ -1,6 +1,6 @@
|
||||
# [Black Dashboard Flask](https://www.creative-tim.com/product/black-dashboard-flask) [](https://twitter.com/home?status=Material%20Dashboard,%20a%20free%20Material%20Bootstrap%204%20Admin%20Template%20%E2%9D%A4%EF%B8%8F%20https%3A//bit.ly/2Lyat1Y%20%23bootstrap%20%23material%20%23design%20%23developers%20%23freebie%20%20via%20%40CreativeTim)
|
||||
|
||||
 [](https://github.com/creativetimofficial/black-dashboard-flask/issues?q=is%3Aopen+is%3Aissue) [](https://github.com/creativetimofficial/black-dashboard-flask/issues?q=is%3Aissue+is%3Aclosed) [](https://gitter.im/creative-tim-general/Lobby) [](https://discord.gg/E4aHAQy)
|
||||
 [](https://github.com/creativetimofficial/black-dashboard-flask/issues?q=is%3Aopen+is%3Aissue) [](https://github.com/creativetimofficial/black-dashboard-flask/issues?q=is%3Aissue+is%3Aclosed) [](https://gitter.im/creative-tim-general/Lobby) [](https://discord.gg/E4aHAQy)
|
||||
|
||||

|
||||
|
||||
@ -8,6 +8,8 @@
|
||||
|
||||
> Free product - **Flask Dashboard** starter project - Features:
|
||||
|
||||
- Up-to-date [dependencies](./requirements.txt): **Flask 2.0.1**
|
||||
- [SCSS compilation](#recompile-css) via **Gulp**
|
||||
- UI Kit: **Black Dashboard** (Free Version) provided by **[Creative-Tim](https://www.creative-tim.com/)**
|
||||
- Flask Codebase - provided by **[AppSeed](https://appseed.us/)**
|
||||
- SQLite, PostgreSQL, SQLAlchemy ORM
|
||||
@ -22,6 +24,7 @@
|
||||
## Table of Contents
|
||||
|
||||
* [Demo](#demo)
|
||||
* [Docker Support](#docker-support)
|
||||
* [Quick Start](#quick-start)
|
||||
* [Documentation](#documentation)
|
||||
* [File Structure](#file-structure)
|
||||
@ -42,6 +45,27 @@
|
||||
|
||||
<br />
|
||||
|
||||
## Docker Support
|
||||
|
||||
> Get the code
|
||||
|
||||
```bash
|
||||
$ git clone https://github.com/app-generator/black-dashboard-flask.git
|
||||
$ cd black-dashboard-flask
|
||||
```
|
||||
|
||||
> Start the app in Docker
|
||||
|
||||
```bash
|
||||
$ docker-compose pull # download dependencies
|
||||
$ docker-compose build # local set up
|
||||
$ docker-compose up -d # start the app
|
||||
```
|
||||
|
||||
Visit `http://localhost:85` in your browser. The app should be up & running.
|
||||
|
||||
<br />
|
||||
|
||||
## Quick start
|
||||
|
||||
> UNZIP the sources or clone the private repository. After getting the code, open a terminal and navigate to the working directory, with product source code.
|
||||
@ -98,41 +122,115 @@ Within the download you'll find the following directories and files:
|
||||
```bash
|
||||
< PROJECT ROOT >
|
||||
|
|
||||
|-- app/
|
||||
| |-- home/ # Home Blueprint - serve app pages (private area)
|
||||
| |-- base/ # Base Blueprint - handles the authentication
|
||||
| |-- static/
|
||||
| | |-- <css, JS, images> # CSS files, Javascripts files
|
||||
| |
|
||||
| |-- templates/ # Templates used to render pages
|
||||
| |
|
||||
| |-- includes/ #
|
||||
| | |-- navigation.html # Top menu component
|
||||
| | |-- sidebar.html # Sidebar component
|
||||
| | |-- footer.html # App Footer
|
||||
| | |-- scripts.html # Scripts common to all pages
|
||||
| |
|
||||
| |-- layouts/ # Master pages
|
||||
| | |-- base-fullscreen.html # Used by Authentication pages
|
||||
| | |-- base.html # Used by common pages
|
||||
| |
|
||||
| |-- accounts/ # Authentication pages
|
||||
| |-- login.html # Login page
|
||||
| |-- register.html # Registration page
|
||||
|-- apps/
|
||||
| |
|
||||
| |-- home/ # A simple app that serve HTML files
|
||||
| | |-- routes.py # Define app routes
|
||||
| |
|
||||
| |-- authentication/ # Handles auth routes (login and register)
|
||||
| | |-- routes.py # Define authentication routes
|
||||
| | |-- models.py # Defines models
|
||||
| | |-- forms.py # Define auth forms (login and register)
|
||||
| |
|
||||
| |-- static/
|
||||
| | |-- <css, JS, images> # CSS files, Javascripts files
|
||||
| |
|
||||
| |-- templates/ # Templates used to render pages
|
||||
| | |-- includes/ # HTML chunks and components
|
||||
| | | |-- navigation.html # Top menu component
|
||||
| | | |-- sidebar.html # Sidebar component
|
||||
| | | |-- footer.html # App Footer
|
||||
| | | |-- scripts.html # Scripts common to all pages
|
||||
| | |
|
||||
| | |-- layouts/ # Master pages
|
||||
| | | |-- base-fullscreen.html # Used by Authentication pages
|
||||
| | | |-- base.html # Used by common pages
|
||||
| | |
|
||||
| | |-- accounts/ # Authentication pages
|
||||
| | | |-- login.html # Login page
|
||||
| | | |-- register.html # Register page
|
||||
| | |
|
||||
| | |-- home/ # UI Kit Pages
|
||||
| | |-- index.html # Index page
|
||||
| | |-- 404-page.html # 404 page
|
||||
| | |-- *.html # All other pages
|
||||
| |
|
||||
| config.py # Set up the app
|
||||
| __init__.py # Initialize the app
|
||||
|
|
||||
|-- requirements.txt # Development modules - SQLite storage
|
||||
|-- requirements-mysql.txt # Production modules - Mysql DMBS
|
||||
|-- requirements-pqsql.txt # Production modules - PostgreSql DMBS
|
||||
|-- requirements.txt # Development modules - SQLite storage
|
||||
|-- requirements-mysql.txt # Production modules - Mysql DMBS
|
||||
|-- requirements-pqsql.txt # Production modules - PostgreSql DMBS
|
||||
|
|
||||
|-- .env # Inject Configuration via Environment
|
||||
|-- config.py # Set up the app
|
||||
|-- run.py # Start the app - WSGI gateway
|
||||
|-- Dockerfile # Deployment
|
||||
|-- docker-compose.yml # Deployment
|
||||
|-- gunicorn-cfg.py # Deployment
|
||||
|-- nginx # Deployment
|
||||
| |-- appseed-app.conf # Deployment
|
||||
|
|
||||
|-- .env # Inject Configuration via Environment
|
||||
|-- run.py # Start the app - WSGI gateway
|
||||
|
|
||||
|-- ************************************************************************
|
||||
```
|
||||
|
||||
<br />
|
||||
|
||||
> The bootstrap flow
|
||||
|
||||
- `run.py` loads the `.env` file
|
||||
- Initialize the app using the specified profile: *Debug* or *Production*
|
||||
- If env.DEBUG is set to *True* the SQLite storage is used
|
||||
- If env.DEBUG is set to *False* the specified DB driver is used (MySql, PostgreSQL)
|
||||
- Call the app factory method `create_app` defined in app/__init__.py
|
||||
- Redirect the guest users to Login page
|
||||
- Unlock the pages served by *home* blueprint for authenticated users
|
||||
|
||||
<br />
|
||||
|
||||
## Recompile CSS
|
||||
|
||||
To recompile SCSS files, follow this setup:
|
||||
|
||||
<br />
|
||||
|
||||
**Step #1** - Install tools
|
||||
|
||||
- [NodeJS](https://nodejs.org/en/) 12.x or higher
|
||||
- [Gulp](https://gulpjs.com/) - globally
|
||||
- `npm install -g gulp-cli`
|
||||
- [Yarn](https://yarnpkg.com/) (optional)
|
||||
|
||||
<br />
|
||||
|
||||
**Step #2** - Change the working directory to `assets` folder
|
||||
|
||||
```bash
|
||||
$ cd apps/static/assets
|
||||
```
|
||||
|
||||
<br />
|
||||
|
||||
**Step #3** - Install modules (this will create a classic `node_modules` directory)
|
||||
|
||||
```bash
|
||||
$ npm install
|
||||
// OR
|
||||
$ yarn
|
||||
```
|
||||
|
||||
<br />
|
||||
|
||||
**Step #4** - Edit & Recompile SCSS files
|
||||
|
||||
```bash
|
||||
$ gulp scss
|
||||
```
|
||||
|
||||
The generated file is saved in `static/assets/css` directory.
|
||||
|
||||
<br />
|
||||
|
||||
## Browser Support
|
||||
|
||||
At present, we officially aim to support the last two versions of the following browsers:
|
||||
|
||||
@ -1,19 +0,0 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
"""
|
||||
Copyright (c) 2019 - present AppSeed.us
|
||||
"""
|
||||
|
||||
from flask_wtf import FlaskForm
|
||||
from wtforms import TextField, PasswordField
|
||||
from wtforms.validators import InputRequired, Email, DataRequired
|
||||
|
||||
## login and registration
|
||||
|
||||
class LoginForm(FlaskForm):
|
||||
username = TextField ('Username', id='username_login' , validators=[DataRequired()])
|
||||
password = PasswordField('Password', id='pwd_login' , validators=[DataRequired()])
|
||||
|
||||
class CreateAccountForm(FlaskForm):
|
||||
username = TextField('Username' , id='username_create' , validators=[DataRequired()])
|
||||
email = TextField('Email' , id='email_create' , validators=[DataRequired(), Email()])
|
||||
password = PasswordField('Password' , id='pwd_create' , validators=[DataRequired()])
|
||||
@ -1,112 +0,0 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
"""
|
||||
Copyright (c) 2019 - present AppSeed.us
|
||||
"""
|
||||
|
||||
from flask import jsonify, render_template, redirect, request, url_for
|
||||
from flask_login import (
|
||||
current_user,
|
||||
login_required,
|
||||
login_user,
|
||||
logout_user
|
||||
)
|
||||
|
||||
from app import db, login_manager
|
||||
from app.base import blueprint
|
||||
from app.base.forms import LoginForm, CreateAccountForm
|
||||
from app.base.models import User
|
||||
|
||||
from app.base.util import verify_pass
|
||||
|
||||
@blueprint.route('/')
|
||||
def route_default():
|
||||
return redirect(url_for('base_blueprint.login'))
|
||||
|
||||
## Login & Registration
|
||||
|
||||
@blueprint.route('/login', methods=['GET', 'POST'])
|
||||
def login():
|
||||
login_form = LoginForm(request.form)
|
||||
if 'login' in request.form:
|
||||
|
||||
# read form data
|
||||
username = request.form['username']
|
||||
password = request.form['password']
|
||||
|
||||
# Locate user
|
||||
user = User.query.filter_by(username=username).first()
|
||||
|
||||
# Check the password
|
||||
if user and verify_pass( password, user.password):
|
||||
|
||||
login_user(user)
|
||||
return redirect(url_for('base_blueprint.route_default'))
|
||||
|
||||
# Something (user or pass) is not ok
|
||||
return render_template( 'accounts/login.html', msg='Wrong user or password', form=login_form)
|
||||
|
||||
if not current_user.is_authenticated:
|
||||
return render_template( 'accounts/login.html',
|
||||
form=login_form)
|
||||
return redirect(url_for('home_blueprint.index'))
|
||||
|
||||
@blueprint.route('/register', methods=['GET', 'POST'])
|
||||
def register():
|
||||
login_form = LoginForm(request.form)
|
||||
create_account_form = CreateAccountForm(request.form)
|
||||
if 'register' in request.form:
|
||||
|
||||
username = request.form['username']
|
||||
email = request.form['email' ]
|
||||
|
||||
# Check usename exists
|
||||
user = User.query.filter_by(username=username).first()
|
||||
if user:
|
||||
return render_template( 'accounts/register.html',
|
||||
msg='Username already registered',
|
||||
success=False,
|
||||
form=create_account_form)
|
||||
|
||||
# Check email exists
|
||||
user = User.query.filter_by(email=email).first()
|
||||
if user:
|
||||
return render_template( 'accounts/register.html',
|
||||
msg='Email already registered',
|
||||
success=False,
|
||||
form=create_account_form)
|
||||
|
||||
# else we can create the user
|
||||
user = User(**request.form)
|
||||
db.session.add(user)
|
||||
db.session.commit()
|
||||
|
||||
return render_template( 'accounts/register.html',
|
||||
msg='User created please <a href="/login">login</a>',
|
||||
success=True,
|
||||
form=create_account_form)
|
||||
|
||||
else:
|
||||
return render_template( 'accounts/register.html', form=create_account_form)
|
||||
|
||||
@blueprint.route('/logout')
|
||||
def logout():
|
||||
logout_user()
|
||||
return redirect(url_for('base_blueprint.login'))
|
||||
|
||||
## Errors
|
||||
|
||||
@login_manager.unauthorized_handler
|
||||
def unauthorized_handler():
|
||||
return render_template('page-403.html'), 403
|
||||
|
||||
@blueprint.errorhandler(403)
|
||||
def access_forbidden(error):
|
||||
return render_template('page-403.html'), 403
|
||||
|
||||
@blueprint.errorhandler(404)
|
||||
def not_found_error(error):
|
||||
return render_template('page-404.html'), 404
|
||||
|
||||
@blueprint.errorhandler(500)
|
||||
def internal_error(error):
|
||||
return render_template('page-500.html'), 500
|
||||
@ -1 +0,0 @@
|
||||
{"version":3,"sources":["_site_dashboard_free/assets/js/dashboard-free.js"],"names":["transparent","transparentDemo","fixedTop","navbar_initialized","backgroundOrange","sidebar_mini_active","toggle_initialized","$html","$","$body","$navbar_minimize_fixed","$collapse","$navbar","$tagsinput","$selectpicker","$navbar_color","$full_screen_map","$datetimepicker","$datepicker","$timepicker","seq","delays","durations","seq2","delays2","durations2","hexToRGB","hex","alpha","r","parseInt","slice","g","b","navigator","platform","indexOf","length","PerfectScrollbar","wheelSpeed","wheelPropagation","minScrollbarLength","suppressScrollX","each","this","addClass","document","ready","offset","window","scroll","scrollTop","css","top","on","closest","removeClass","blackDashboard","initMinimizeSidebar","scroll_distance","attr","checkScrollForTransparentNavbar","parent","$this","data_on_label","data","data_off_label","bootstrapSwitch","onText","offText","$toggle","misc","navbar_menu_visible","setTimeout","remove","appendTo","click","resize","isExpanded","find","hasClass","width","showSidebarMessage","simulateWindowResize","setInterval","dispatchEvent","Event","clearInterval","message","notify","icon","type","timer","placement","from","align","e","console","log"],"mappings":"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"}
|
||||
@ -1,299 +0,0 @@
|
||||
.white-content{
|
||||
|
||||
background: $light-bg;
|
||||
|
||||
.navbar.navbar-transparent .navbar-brand{
|
||||
color: $black-states;
|
||||
}
|
||||
|
||||
.navbar.navbar-transparent .navbar-toggler-bar{
|
||||
background: $black-states;
|
||||
}
|
||||
.navbar.navbar-transparent .navbar-nav li a:not(.dropdown-item){
|
||||
color: $black-states;
|
||||
& i{
|
||||
color: $black-states;
|
||||
}
|
||||
}
|
||||
|
||||
.navbar.navbar-transparent .navbar-minimize button i{
|
||||
color: $black-states;
|
||||
}
|
||||
|
||||
.navbar.navbar-transparent .search-bar.input-group i{
|
||||
color: $black-states;
|
||||
}
|
||||
.navbar.navbar-transparent .search-bar.input-group .form-control{
|
||||
color: $default;
|
||||
&::placeholder{
|
||||
color: $dark-gray;
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar{
|
||||
box-shadow: 0 2px 22px 0 rgba(0,0,0,.1), 0 4px 20px 0 rgba(0,0,0,.15);
|
||||
p{
|
||||
color: $opacity-8;
|
||||
}
|
||||
}
|
||||
|
||||
.main-panel{
|
||||
background: $light-bg;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6, p, ol li, ul li, pre {
|
||||
color: $black-states;
|
||||
}
|
||||
|
||||
.font-icon-detail i{
|
||||
color: $black;
|
||||
}
|
||||
|
||||
.btn:not([data-action]):hover{
|
||||
box-shadow: 0 4px 6px rgba(50,50,93,.11), 0 1px 3px rgba(0,0,0,.08);
|
||||
}
|
||||
|
||||
.btn-neutral.btn-link{
|
||||
color: rgba($black, 0.7);
|
||||
}
|
||||
|
||||
//style for inputs
|
||||
|
||||
@include form-control-placeholder(rgba($black,0.4), 1);
|
||||
.has-danger{
|
||||
.form-control, .input-group-prepend .input-group-text{
|
||||
border-color: $danger-states;
|
||||
}
|
||||
}
|
||||
|
||||
.input-group-prepend .input-group-text{
|
||||
border-color: rgba($black-states,0.5);
|
||||
color: $black-states;
|
||||
}
|
||||
|
||||
.form-control{
|
||||
color: $black;
|
||||
border-color: rgba($black-states,0.5);
|
||||
&:focus{
|
||||
border-color: $primary;
|
||||
}
|
||||
}
|
||||
|
||||
.form-group.no-border,
|
||||
.input-group.no-border{
|
||||
.form-control,
|
||||
.form-control + .input-group-prepend .input-group-text,
|
||||
.form-control + .input-group-append .input-group-text,
|
||||
.input-group-prepend .input-group-text,
|
||||
.input-group-append .input-group-text{
|
||||
background-color: $opacity-gray-3;
|
||||
&:focus,
|
||||
&:active,
|
||||
&:active{
|
||||
background-color: $opacity-gray-5;
|
||||
}
|
||||
}
|
||||
|
||||
.form-control{
|
||||
&:focus{
|
||||
& + .input-group-prepend .input-group-text,
|
||||
& + .input-group-append .input-group-text{
|
||||
|
||||
background-color: $transparent-bg;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
.input-group[disabled]{
|
||||
.input-group-prepend .input-group-text,
|
||||
.input-group-append .input-group-text{
|
||||
background-color: $black;
|
||||
}
|
||||
}
|
||||
|
||||
.form-control[disabled], .form-control[readonly], fieldset[disabled] .form-control{
|
||||
background: $light-gray;
|
||||
border-color: rgba($black-states,0.3);
|
||||
}
|
||||
|
||||
.input-group-focus{
|
||||
.input-group-prepend .input-group-text,
|
||||
.input-group-append .input-group-text,
|
||||
.form-control{
|
||||
border-color: $primary;
|
||||
}
|
||||
|
||||
&.no-border{
|
||||
.input-group-prepend .input-group-text,
|
||||
.input-group-append .input-group-text{
|
||||
|
||||
background-color: $opacity-gray-5;
|
||||
}
|
||||
}
|
||||
}
|
||||
.input-group-prepend .input-group-text {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.input-group-append .input-group-text {
|
||||
border-left: none;
|
||||
}
|
||||
|
||||
.has-danger .form-control:focus, .has-success.input-group-focus .input-group-append .input-group-text, .has-success.input-group-focus .input-group-prepend .input-group-text {
|
||||
border-color: $danger-states;
|
||||
}
|
||||
|
||||
.has-success .form-control:focus, .has-success.input-group-focus .input-group-append .input-group-text, .has-success.input-group-focus .input-group-prepend .input-group-text {
|
||||
border-color: darken($success, 10%);
|
||||
}
|
||||
|
||||
.btn.btn-link{
|
||||
&:hover,&:active, &:focus{
|
||||
color: $dark-gray !important;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-group .btn.active{
|
||||
box-shadow: 0 4px 6px rgba(50,50,93,.11), 0 1px 3px rgba(0,0,0,.08);
|
||||
}
|
||||
|
||||
.card:not(.card-white){
|
||||
background: $white;
|
||||
box-shadow: 0 1px 15px 0 rgba(, , , 0.05);
|
||||
.card-header{
|
||||
color: $black;
|
||||
a[data-toggle="collapse"]{
|
||||
color: $black;
|
||||
}
|
||||
}
|
||||
.card-header .card-title, .card-body .card-title{
|
||||
color: $black-states;
|
||||
}
|
||||
|
||||
.card-body{
|
||||
.card-category, .card-description{
|
||||
color: $black-states;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
label:not(.btn){
|
||||
color: $default;
|
||||
}
|
||||
|
||||
|
||||
&.nav-pills .nav-item .nav-link {
|
||||
color: $nav-gray;
|
||||
background-color: hsla(0,0%,87%,.3);
|
||||
&:not(.active):hover{
|
||||
background: hsla(0,0%,87%,.5);
|
||||
}
|
||||
&.active{
|
||||
color: $white;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.tab-content .tab-pane {
|
||||
color: $nav-gray;
|
||||
}
|
||||
|
||||
|
||||
.card{
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
&.card-plain{
|
||||
background: $transparent-bg;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
&.card-tasks{
|
||||
.card-body{
|
||||
i{
|
||||
color: rgba(34,42,66,0.7);
|
||||
&:hover{
|
||||
color: $black;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.table{
|
||||
|
||||
> tbody > tr > td {
|
||||
color: rgba($black, 0.7) !important;
|
||||
|
||||
}
|
||||
|
||||
> thead > tr > th,
|
||||
> tbody > tr > th,
|
||||
> tfoot > tr > th,
|
||||
> thead > tr > td,
|
||||
> tbody > tr > td,
|
||||
> tfoot > tr > td{
|
||||
border-color: rgba($black, 0.2);
|
||||
padding: 12px 7px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
> thead > tr > th, button.btn-neutral.btn-link{
|
||||
color: rgba($black, 0.7);
|
||||
}
|
||||
}
|
||||
|
||||
.footer ul li a{
|
||||
color: $black-states;
|
||||
}
|
||||
|
||||
.footer .copyright{
|
||||
color: $black-states;
|
||||
}
|
||||
|
||||
.progress-container, .progress-container.progress.sm{
|
||||
.progress{
|
||||
background: rgba($black,0.1);
|
||||
box-shadow: 0 0 0 3px rgba($black,0.1);
|
||||
.progress-value{
|
||||
color: $default;
|
||||
}
|
||||
}
|
||||
|
||||
.progress-badge{
|
||||
color: $black-states;
|
||||
}
|
||||
}
|
||||
|
||||
.full-page{
|
||||
background: $light-bg;
|
||||
|
||||
h1, h2, h3, h4, h5, h6, p, ol li, ul li, pre {
|
||||
color: $black-states;
|
||||
}
|
||||
|
||||
.description{
|
||||
color: $dark-gray;
|
||||
}
|
||||
|
||||
.footer ul li a{
|
||||
color: $black-states;
|
||||
}
|
||||
|
||||
.footer .copyright{
|
||||
color: $black-states;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-pills .nav-item .nav-link:not(.active){
|
||||
background: darken($light-bg,10%);
|
||||
color: $black-states;
|
||||
&:hover{
|
||||
background: darken($white,20%);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,70 +0,0 @@
|
||||
{% extends "layouts/base.html" %}
|
||||
|
||||
{% block title %} Login {% endblock %}
|
||||
|
||||
<!-- Specific Page CSS goes HERE -->
|
||||
{% block stylesheets %}{% endblock stylesheets %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
<div class="card">
|
||||
|
||||
<form role="form" method="post" action="">
|
||||
|
||||
{{ form.hidden_tag() }}
|
||||
|
||||
<div class="card-header">
|
||||
<h5 class="title">Login</h5>
|
||||
|
||||
<h6 class="card-category">
|
||||
{% if msg %}
|
||||
{{ msg | safe }}
|
||||
{% else %}
|
||||
Add your credentials
|
||||
{% endif %}
|
||||
</h6>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
|
||||
<div class="row">
|
||||
|
||||
<div class="col-md-3 px-md-1">
|
||||
<div class="form-group">
|
||||
<label>Username</label>
|
||||
{{ form.username(placeholder="Username", class="form-control") }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="row">
|
||||
|
||||
<div class="col-md-3 px-md-1">
|
||||
<div class="form-group">
|
||||
<label>Password</label>
|
||||
{{ form.password(placeholder="Password", class="form-control", type="password") }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<button type="submit" name="login" class="btn btn-fill btn-primary">Login</button>
|
||||
|
||||
Don't have an account? <a href="{{ url_for('base_blueprint.register') }}" class="text-primary">Create</a>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{% endblock content %}
|
||||
|
||||
<!-- Specific Page JS goes HERE -->
|
||||
{% block javascripts %}{% endblock javascripts %}
|
||||
@ -1,74 +0,0 @@
|
||||
{% extends "layouts/base.html" %}
|
||||
|
||||
{% block title %} Register {% endblock %}
|
||||
|
||||
<!-- Specific Page CSS goes HERE -->
|
||||
{% block stylesheets %}{% endblock stylesheets %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
<div class="card">
|
||||
|
||||
<form role="form" method="post" action="">
|
||||
|
||||
<div class="card-header">
|
||||
<h5 class="title">Register</h5>
|
||||
|
||||
<h6 class="card-category">
|
||||
{% if msg %}
|
||||
{{ msg | safe }}
|
||||
{% else %}
|
||||
Add your credentials
|
||||
{% endif %}
|
||||
</h6>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-3 px-md-1">
|
||||
<div class="form-group">
|
||||
<label>Username</label>
|
||||
{{ form.username(placeholder="Username", class="form-control") }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-3 px-md-1">
|
||||
<div class="form-group">
|
||||
<label>Email</label>
|
||||
{{ form.email(placeholder="Email", class="input form-control", type="email") }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-3 px-md-1">
|
||||
<div class="form-group">
|
||||
<label>Password</label>
|
||||
{{ form.password(placeholder="Password", class="form-control", type="password") }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<button type="submit" name="register" class="btn btn-fill btn-primary">Register</button>
|
||||
|
||||
Have an account? <a href="{{ url_for('base_blueprint.login') }}" class="text-primary">Login</a>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{% endblock content %}
|
||||
|
||||
<!-- Specific Page JS goes HERE -->
|
||||
{% block javascripts %}{% endblock javascripts %}
|
||||
@ -1,82 +0,0 @@
|
||||
|
||||
<!-- Navbar -->
|
||||
<nav class="navbar navbar-expand-lg navbar-absolute navbar-transparent">
|
||||
<div class="container-fluid">
|
||||
<div class="navbar-wrapper">
|
||||
<div class="navbar-toggle d-inline">
|
||||
<button type="button" class="navbar-toggler">
|
||||
<span class="navbar-toggler-bar bar1"></span>
|
||||
<span class="navbar-toggler-bar bar2"></span>
|
||||
<span class="navbar-toggler-bar bar3"></span>
|
||||
</button>
|
||||
</div>
|
||||
<a class="navbar-brand" href="javascript:void(0)">Dashboard</a>
|
||||
</div>
|
||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navigation" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-bar navbar-kebab"></span>
|
||||
<span class="navbar-toggler-bar navbar-kebab"></span>
|
||||
<span class="navbar-toggler-bar navbar-kebab"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navigation">
|
||||
|
||||
{% if current_user.is_authenticated %}
|
||||
|
||||
<ul class="navbar-nav ml-auto">
|
||||
<li class="search-bar input-group">
|
||||
<button class="btn btn-link" id="search-button" data-toggle="modal" data-target="#searchModal"><i class="tim-icons icon-zoom-split" ></i>
|
||||
<span class="d-lg-none d-md-block">Search</span>
|
||||
</button>
|
||||
</li>
|
||||
<li class="dropdown nav-item">
|
||||
<a href="javascript:void(0)" class="dropdown-toggle nav-link" data-toggle="dropdown">
|
||||
<div class="notification d-none d-lg-block d-xl-block"></div>
|
||||
<i class="tim-icons icon-sound-wave"></i>
|
||||
<p class="d-lg-none">
|
||||
Notifications
|
||||
</p>
|
||||
</a>
|
||||
<ul class="dropdown-menu dropdown-menu-right dropdown-navbar">
|
||||
<li class="nav-link"><a href="#" class="nav-item dropdown-item">Mike John responded to your email</a></li>
|
||||
<li class="nav-link"><a href="javascript:void(0)" class="nav-item dropdown-item">You have 5 more tasks</a></li>
|
||||
<li class="nav-link"><a href="javascript:void(0)" class="nav-item dropdown-item">Your friend Michael is in town</a></li>
|
||||
<li class="nav-link"><a href="javascript:void(0)" class="nav-item dropdown-item">Another notification</a></li>
|
||||
<li class="nav-link"><a href="javascript:void(0)" class="nav-item dropdown-item">Another one</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="dropdown nav-item">
|
||||
<a href="#" class="dropdown-toggle nav-link" data-toggle="dropdown">
|
||||
<div class="photo">
|
||||
<img src="/static/assets/img/anime3.png" alt="Profile Photo">
|
||||
</div>
|
||||
<b class="caret d-none d-lg-block d-xl-block"></b>
|
||||
<p class="d-lg-none">
|
||||
Log out
|
||||
</p>
|
||||
</a>
|
||||
<ul class="dropdown-menu dropdown-navbar">
|
||||
<li class="nav-link"><a href="/page-user.html" class="nav-item dropdown-item">Profile</a></li>
|
||||
<li class="dropdown-divider"></li>
|
||||
<li class="nav-link"><a href="{{ url_for('base_blueprint.logout') }}" class="nav-item dropdown-item">Log out</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="separator d-lg-none"></li>
|
||||
</ul>
|
||||
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<div class="modal modal-search fade" id="searchModal" tabindex="-1" role="dialog" aria-labelledby="searchModal" aria-hidden="true">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<input type="text" class="form-control" id="inlineFormInputGroup" placeholder="SEARCH">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<i class="tim-icons icon-simple-remove"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- End Navbar -->
|
||||
@ -1,52 +0,0 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
"""
|
||||
Copyright (c) 2019 - present AppSeed.us
|
||||
"""
|
||||
|
||||
from app.home import blueprint
|
||||
from flask import render_template, redirect, url_for, request
|
||||
from flask_login import login_required, current_user
|
||||
from app import login_manager
|
||||
from jinja2 import TemplateNotFound
|
||||
|
||||
@blueprint.route('/index')
|
||||
@login_required
|
||||
def index():
|
||||
|
||||
return render_template('index.html', segment='index')
|
||||
|
||||
@blueprint.route('/<template>')
|
||||
@login_required
|
||||
def route_template(template):
|
||||
|
||||
try:
|
||||
|
||||
if not template.endswith( '.html' ):
|
||||
template += '.html'
|
||||
|
||||
# Detect the current page
|
||||
segment = get_segment( request )
|
||||
|
||||
# Serve the file (if exists) from app/templates/FILE.html
|
||||
return render_template( template, segment=segment )
|
||||
|
||||
except TemplateNotFound:
|
||||
return render_template('page-404.html'), 404
|
||||
|
||||
except:
|
||||
return render_template('page-500.html'), 500
|
||||
|
||||
# Helper - Extract current page name from request
|
||||
def get_segment( request ):
|
||||
|
||||
try:
|
||||
|
||||
segment = request.path.split('/')[-1]
|
||||
|
||||
if segment == '':
|
||||
segment = 'index'
|
||||
|
||||
return segment
|
||||
|
||||
except:
|
||||
return None
|
||||
@ -3,25 +3,27 @@
|
||||
Copyright (c) 2019 - present AppSeed.us
|
||||
"""
|
||||
|
||||
from flask import Flask, url_for
|
||||
from flask import Flask
|
||||
from flask_login import LoginManager
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
from importlib import import_module
|
||||
from logging import basicConfig, DEBUG, getLogger, StreamHandler
|
||||
from os import path
|
||||
|
||||
|
||||
db = SQLAlchemy()
|
||||
login_manager = LoginManager()
|
||||
|
||||
|
||||
def register_extensions(app):
|
||||
db.init_app(app)
|
||||
login_manager.init_app(app)
|
||||
|
||||
|
||||
def register_blueprints(app):
|
||||
for module_name in ('base', 'home'):
|
||||
module = import_module('app.{}.routes'.format(module_name))
|
||||
for module_name in ('authentication', 'home'):
|
||||
module = import_module('apps.{}.routes'.format(module_name))
|
||||
app.register_blueprint(module.blueprint)
|
||||
|
||||
|
||||
def configure_database(app):
|
||||
|
||||
@app.before_first_request
|
||||
@ -32,8 +34,9 @@ def configure_database(app):
|
||||
def shutdown_session(exception=None):
|
||||
db.session.remove()
|
||||
|
||||
|
||||
def create_app(config):
|
||||
app = Flask(__name__, static_folder='base/static')
|
||||
app = Flask(__name__)
|
||||
app.config.from_object(config)
|
||||
register_extensions(app)
|
||||
register_blueprints(app)
|
||||
@ -6,9 +6,7 @@ Copyright (c) 2019 - present AppSeed.us
|
||||
from flask import Blueprint
|
||||
|
||||
blueprint = Blueprint(
|
||||
'base_blueprint',
|
||||
'authentication_blueprint',
|
||||
__name__,
|
||||
url_prefix='',
|
||||
template_folder='templates',
|
||||
static_folder='static'
|
||||
url_prefix=''
|
||||
)
|
||||
31
apps/authentication/forms.py
Normal file
@ -0,0 +1,31 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
"""
|
||||
Copyright (c) 2019 - present AppSeed.us
|
||||
"""
|
||||
|
||||
from flask_wtf import FlaskForm
|
||||
from wtforms import TextField, PasswordField
|
||||
from wtforms.validators import Email, DataRequired
|
||||
|
||||
# login and registration
|
||||
|
||||
|
||||
class LoginForm(FlaskForm):
|
||||
username = TextField('Username',
|
||||
id='username_login',
|
||||
validators=[DataRequired()])
|
||||
password = PasswordField('Password',
|
||||
id='pwd_login',
|
||||
validators=[DataRequired()])
|
||||
|
||||
|
||||
class CreateAccountForm(FlaskForm):
|
||||
username = TextField('Username',
|
||||
id='username_create',
|
||||
validators=[DataRequired()])
|
||||
email = TextField('Email',
|
||||
id='email_create',
|
||||
validators=[DataRequired(), Email()])
|
||||
password = PasswordField('Password',
|
||||
id='pwd_create',
|
||||
validators=[DataRequired()])
|
||||
@ -4,20 +4,19 @@ Copyright (c) 2019 - present AppSeed.us
|
||||
"""
|
||||
|
||||
from flask_login import UserMixin
|
||||
from sqlalchemy import Binary, Column, Integer, String
|
||||
|
||||
from app import db, login_manager
|
||||
from apps import db, login_manager
|
||||
|
||||
from app.base.util import hash_pass
|
||||
from apps.authentication.util import hash_pass
|
||||
|
||||
class User(db.Model, UserMixin):
|
||||
class Users(db.Model, UserMixin):
|
||||
|
||||
__tablename__ = 'User'
|
||||
__tablename__ = 'Users'
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
username = Column(String, unique=True)
|
||||
email = Column(String, unique=True)
|
||||
password = Column(Binary)
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
username = db.Column(db.String(64), unique=True)
|
||||
email = db.Column(db.String(64), unique=True)
|
||||
password = db.Column(db.LargeBinary)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
for property, value in kwargs.items():
|
||||
@ -29,8 +28,8 @@ class User(db.Model, UserMixin):
|
||||
value = value[0]
|
||||
|
||||
if property == 'password':
|
||||
value = hash_pass( value ) # we need bytes here (not plain str)
|
||||
|
||||
value = hash_pass(value) # we need bytes here (not plain str)
|
||||
|
||||
setattr(self, property, value)
|
||||
|
||||
def __repr__(self):
|
||||
@ -39,10 +38,11 @@ class User(db.Model, UserMixin):
|
||||
|
||||
@login_manager.user_loader
|
||||
def user_loader(id):
|
||||
return User.query.filter_by(id=id).first()
|
||||
return Users.query.filter_by(id=id).first()
|
||||
|
||||
|
||||
@login_manager.request_loader
|
||||
def request_loader(request):
|
||||
username = request.form.get('username')
|
||||
user = User.query.filter_by(username=username).first()
|
||||
user = Users.query.filter_by(username=username).first()
|
||||
return user if user else None
|
||||
127
apps/authentication/routes.py
Normal file
@ -0,0 +1,127 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
"""
|
||||
Copyright (c) 2019 - present AppSeed.us
|
||||
"""
|
||||
|
||||
from flask import render_template, redirect, request, url_for
|
||||
from flask_login import (
|
||||
current_user,
|
||||
login_user,
|
||||
logout_user
|
||||
)
|
||||
|
||||
from apps import db, login_manager
|
||||
from apps.authentication import blueprint
|
||||
from apps.authentication.forms import LoginForm, CreateAccountForm
|
||||
from apps.authentication.models import Users
|
||||
|
||||
from apps.authentication.util import verify_pass
|
||||
|
||||
|
||||
@blueprint.route('/')
|
||||
def route_default():
|
||||
return redirect(url_for('authentication_blueprint.login'))
|
||||
|
||||
|
||||
# Login & Registration
|
||||
|
||||
@blueprint.route('/login', methods=['GET', 'POST'])
|
||||
def login():
|
||||
login_form = LoginForm(request.form)
|
||||
if 'login' in request.form:
|
||||
|
||||
# read form data
|
||||
username = request.form['username']
|
||||
password = request.form['password']
|
||||
|
||||
# Locate user
|
||||
user = Users.query.filter_by(username=username).first()
|
||||
|
||||
# Check the password
|
||||
if user and verify_pass(password, user.password):
|
||||
|
||||
login_user(user)
|
||||
return redirect(url_for('authentication_blueprint.route_default'))
|
||||
|
||||
# Something (user or pass) is not ok
|
||||
return render_template('accounts/login.html',
|
||||
segment = 'login',
|
||||
msg='Wrong user or password',
|
||||
form=login_form)
|
||||
|
||||
if not current_user.is_authenticated:
|
||||
return render_template('accounts/login.html',
|
||||
segment = 'login',
|
||||
form=login_form)
|
||||
return redirect(url_for('home_blueprint.index'))
|
||||
|
||||
|
||||
@blueprint.route('/register', methods=['GET', 'POST'])
|
||||
def register():
|
||||
create_account_form = CreateAccountForm(request.form)
|
||||
if 'register' in request.form:
|
||||
|
||||
username = request.form['username']
|
||||
email = request.form['email']
|
||||
|
||||
# Check usename exists
|
||||
user = Users.query.filter_by(username=username).first()
|
||||
if user:
|
||||
return render_template('accounts/register.html',
|
||||
msg='Username already registered',
|
||||
segment = 'register',
|
||||
success=False,
|
||||
form=create_account_form)
|
||||
|
||||
# Check email exists
|
||||
user = Users.query.filter_by(email=email).first()
|
||||
if user:
|
||||
return render_template('accounts/register.html',
|
||||
msg='Email already registered',
|
||||
segment = 'register',
|
||||
success=False,
|
||||
form=create_account_form)
|
||||
|
||||
# else we can create the user
|
||||
user = Users(**request.form)
|
||||
db.session.add(user)
|
||||
db.session.commit()
|
||||
|
||||
return render_template('accounts/register.html',
|
||||
msg='User created please <a href="/login">login</a>',
|
||||
segment = 'register',
|
||||
success=True,
|
||||
form=create_account_form)
|
||||
|
||||
else:
|
||||
return render_template( 'accounts/register.html',
|
||||
segment = 'register',
|
||||
form=create_account_form)
|
||||
|
||||
|
||||
@blueprint.route('/logout')
|
||||
def logout():
|
||||
logout_user()
|
||||
return redirect(url_for('authentication_blueprint.login'))
|
||||
|
||||
|
||||
# Errors
|
||||
|
||||
@login_manager.unauthorized_handler
|
||||
def unauthorized_handler():
|
||||
return render_template('home/page-403.html'), 403
|
||||
|
||||
|
||||
@blueprint.errorhandler(403)
|
||||
def access_forbidden(error):
|
||||
return render_template('home/page-403.html'), 403
|
||||
|
||||
|
||||
@blueprint.errorhandler(404)
|
||||
def not_found_error(error):
|
||||
return render_template('home/page-404.html'), 404
|
||||
|
||||
|
||||
@blueprint.errorhandler(500)
|
||||
def internal_error(error):
|
||||
return render_template('home/page-500.html'), 500
|
||||
@ -2,28 +2,33 @@
|
||||
"""
|
||||
Copyright (c) 2019 - present AppSeed.us
|
||||
"""
|
||||
|
||||
import hashlib, binascii, os
|
||||
|
||||
import os
|
||||
import hashlib
|
||||
import binascii
|
||||
|
||||
# Inspiration -> https://www.vitoshacademy.com/hashing-passwords-in-python/
|
||||
|
||||
def hash_pass( password ):
|
||||
|
||||
def hash_pass(password):
|
||||
"""Hash a password for storing."""
|
||||
|
||||
salt = hashlib.sha256(os.urandom(60)).hexdigest().encode('ascii')
|
||||
pwdhash = hashlib.pbkdf2_hmac('sha512', password.encode('utf-8'),
|
||||
salt, 100000)
|
||||
pwdhash = hashlib.pbkdf2_hmac('sha512', password.encode('utf-8'),
|
||||
salt, 100000)
|
||||
pwdhash = binascii.hexlify(pwdhash)
|
||||
return (salt + pwdhash) # return bytes
|
||||
return (salt + pwdhash) # return bytes
|
||||
|
||||
|
||||
def verify_pass(provided_password, stored_password):
|
||||
"""Verify a stored password against one provided by user"""
|
||||
|
||||
stored_password = stored_password.decode('ascii')
|
||||
salt = stored_password[:64]
|
||||
stored_password = stored_password[64:]
|
||||
pwdhash = hashlib.pbkdf2_hmac('sha512',
|
||||
provided_password.encode('utf-8'),
|
||||
salt.encode('ascii'),
|
||||
pwdhash = hashlib.pbkdf2_hmac('sha512',
|
||||
provided_password.encode('utf-8'),
|
||||
salt.encode('ascii'),
|
||||
100000)
|
||||
pwdhash = binascii.hexlify(pwdhash).decode('ascii')
|
||||
return pwdhash == stored_password
|
||||
|
||||
@ -4,11 +4,11 @@ Copyright (c) 2019 - present AppSeed.us
|
||||
"""
|
||||
|
||||
import os
|
||||
from decouple import config
|
||||
from decouple import config
|
||||
|
||||
class Config(object):
|
||||
|
||||
basedir = os.path.abspath(os.path.dirname(__file__))
|
||||
basedir = os.path.abspath(os.path.dirname(__file__))
|
||||
|
||||
# Set up the App SECRET_KEY
|
||||
SECRET_KEY = config('SECRET_KEY', default='S#perS3crEt_007')
|
||||
@ -17,29 +17,32 @@ class Config(object):
|
||||
SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'db.sqlite3')
|
||||
SQLALCHEMY_TRACK_MODIFICATIONS = False
|
||||
|
||||
|
||||
class ProductionConfig(Config):
|
||||
DEBUG = False
|
||||
|
||||
# Security
|
||||
SESSION_COOKIE_HTTPONLY = True
|
||||
SESSION_COOKIE_HTTPONLY = True
|
||||
REMEMBER_COOKIE_HTTPONLY = True
|
||||
REMEMBER_COOKIE_DURATION = 3600
|
||||
|
||||
# PostgreSQL database
|
||||
SQLALCHEMY_DATABASE_URI = '{}://{}:{}@{}:{}/{}'.format(
|
||||
config( 'DB_ENGINE' , default='postgresql' ),
|
||||
config( 'DB_USERNAME' , default='appseed' ),
|
||||
config( 'DB_PASS' , default='pass' ),
|
||||
config( 'DB_HOST' , default='localhost' ),
|
||||
config( 'DB_PORT' , default=5432 ),
|
||||
config( 'DB_NAME' , default='appseed-flask' )
|
||||
config('DB_ENGINE', default='postgresql'),
|
||||
config('DB_USERNAME', default='appseed'),
|
||||
config('DB_PASS', default='pass'),
|
||||
config('DB_HOST', default='localhost'),
|
||||
config('DB_PORT', default=5432),
|
||||
config('DB_NAME', default='appseed-flask')
|
||||
)
|
||||
|
||||
|
||||
class DebugConfig(Config):
|
||||
DEBUG = True
|
||||
|
||||
|
||||
# Load all possible configurations
|
||||
config_dict = {
|
||||
'Production': ProductionConfig,
|
||||
'Debug' : DebugConfig
|
||||
'Debug': DebugConfig
|
||||
}
|
||||
@ -8,7 +8,5 @@ from flask import Blueprint
|
||||
blueprint = Blueprint(
|
||||
'home_blueprint',
|
||||
__name__,
|
||||
url_prefix='',
|
||||
template_folder='templates',
|
||||
static_folder='static'
|
||||
url_prefix=''
|
||||
)
|
||||
53
apps/home/routes.py
Normal file
@ -0,0 +1,53 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
"""
|
||||
Copyright (c) 2019 - present AppSeed.us
|
||||
"""
|
||||
|
||||
from apps.home import blueprint
|
||||
from flask import render_template, request
|
||||
from flask_login import login_required
|
||||
from jinja2 import TemplateNotFound
|
||||
|
||||
|
||||
@blueprint.route('/index')
|
||||
@login_required
|
||||
def index():
|
||||
|
||||
return render_template('home/index.html', segment='index')
|
||||
|
||||
@blueprint.route('/<template>')
|
||||
@login_required
|
||||
def route_template(template):
|
||||
|
||||
try:
|
||||
|
||||
if not template.endswith('.html'):
|
||||
pass
|
||||
|
||||
# Detect the current page
|
||||
segment = get_segment(request)
|
||||
|
||||
# Serve the file (if exists) from app/templates/home/FILE.html
|
||||
return render_template("home/" + template, segment=segment)
|
||||
|
||||
except TemplateNotFound:
|
||||
return render_template('home/page-404.html'), 404
|
||||
|
||||
except:
|
||||
return render_template('home/page-500.html'), 500
|
||||
|
||||
|
||||
# Helper - Extract current page name from request
|
||||
def get_segment(request):
|
||||
|
||||
try:
|
||||
|
||||
segment = request.path.split('/')[-1]
|
||||
|
||||
if segment == '':
|
||||
segment = 'index'
|
||||
|
||||
return segment
|
||||
|
||||
except:
|
||||
return None
|
||||
13360
apps/static/assets/css/black-dashboard.css
Normal file
1
apps/static/assets/css/black-dashboard.css.map
Normal file
22
apps/static/assets/css/black-dashboard.min.css
vendored
Normal file
59
apps/static/assets/gulpfile.js
Normal file
@ -0,0 +1,59 @@
|
||||
/*
|
||||
|
||||
=========================================================
|
||||
* AppSeed - Simple SCSS compiler via Gulp
|
||||
=========================================================
|
||||
|
||||
*/
|
||||
|
||||
var autoprefixer = require('gulp-autoprefixer');
|
||||
var browserSync = require('browser-sync').create();
|
||||
var cleanCss = require('gulp-clean-css');
|
||||
var gulp = require('gulp');
|
||||
const npmDist = require('gulp-npm-dist');
|
||||
var sass = require('gulp-sass')(require('node-sass'));
|
||||
var wait = require('gulp-wait');
|
||||
var sourcemaps = require('gulp-sourcemaps');
|
||||
var rename = require("gulp-rename");
|
||||
|
||||
// Define COMMON paths
|
||||
|
||||
const paths = {
|
||||
src: {
|
||||
base: './',
|
||||
css: './css',
|
||||
scss: './scss',
|
||||
node_modules: './node_modules/',
|
||||
vendor: './vendor'
|
||||
}
|
||||
};
|
||||
|
||||
// Compile SCSS
|
||||
gulp.task('scss', function() {
|
||||
return gulp.src([paths.src.scss + '/black-dashboard.scss'])
|
||||
.pipe(wait(500))
|
||||
.pipe(sourcemaps.init())
|
||||
.pipe(sass().on('error', sass.logError))
|
||||
.pipe(autoprefixer({
|
||||
overrideBrowserslist: ['> 1%']
|
||||
}))
|
||||
.pipe(sourcemaps.write('.'))
|
||||
.pipe(gulp.dest(paths.src.css))
|
||||
.pipe(browserSync.stream());
|
||||
});
|
||||
|
||||
// Minify CSS
|
||||
gulp.task('minify:css', function() {
|
||||
return gulp.src([
|
||||
paths.src.css + '/black-dashboard.css'
|
||||
])
|
||||
.pipe(cleanCss())
|
||||
.pipe(rename(function(path) {
|
||||
// Updates the object in-place
|
||||
path.extname = ".min.css";
|
||||
}))
|
||||
.pipe(gulp.dest(paths.src.css))
|
||||
});
|
||||
|
||||
// Default Task: Compile SCSS and minify the result
|
||||
gulp.task('default', gulp.series('scss', 'minify:css'));
|
||||
|
Before Width: | Height: | Size: 1.6 MiB After Width: | Height: | Size: 1.6 MiB |
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 208 KiB After Width: | Height: | Size: 208 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 84 KiB After Width: | Height: | Size: 84 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 655 KiB After Width: | Height: | Size: 655 KiB |
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 73 KiB |
|
Before Width: | Height: | Size: 75 KiB After Width: | Height: | Size: 75 KiB |
|
Before Width: | Height: | Size: 8.0 KiB After Width: | Height: | Size: 8.0 KiB |
@ -1,2 +1 @@
|
||||
var transparent=!0,transparentDemo=!0,fixedTop=!1,navbar_initialized=!1,backgroundOrange=!1,sidebar_mini_active=!1,toggle_initialized=!1,$html=$("html"),$body=$("body"),$navbar_minimize_fixed=$(".navbar-minimize-fixed"),$collapse=$(".collapse"),$navbar=$(".navbar"),$tagsinput=$(".tagsinput"),$selectpicker=$(".selectpicker"),$navbar_color=$(".navbar[color-on-scroll]"),$full_screen_map=$(".full-screen-map"),$datetimepicker=$(".datetimepicker"),$datepicker=$(".datepicker"),$timepicker=$(".timepicker"),seq=0,delays=80,durations=500,seq2=0,delays2=80,durations2=500;function hexToRGB(a,e){var i=parseInt(a.slice(1,3),16),n=parseInt(a.slice(3,5),16),s=parseInt(a.slice(5,7),16);return e?"rgba("+i+", "+n+", "+s+", "+e+")":"rgb("+i+", "+n+", "+s+")"}!function(){if(-1<navigator.platform.indexOf("Win")){if(0!=$(".main-panel").length)new PerfectScrollbar(".main-panel",{wheelSpeed:2,wheelPropagation:!0,minScrollbarLength:20,suppressScrollX:!0});if(0!=$(".sidebar .sidebar-wrapper").length){new PerfectScrollbar(".sidebar .sidebar-wrapper");$(".table-responsive").each(function(){new PerfectScrollbar($(this)[0])})}$html.addClass("perfect-scrollbar-on")}else $html.addClass("perfect-scrollbar-off")}(),$(document).ready(function(){var a=$(".row").offset();(-1<navigator.platform.indexOf("Win")?$(".ps"):$(window)).scroll(function(){50<$(this).scrollTop()?$(".navbar-minimize-fixed").css("opacity","1"):$(".navbar-minimize-fixed").css("opacity","0")}),$(document).scroll(function(){$(this).scrollTop()>a.top?$(".navbar-minimize-fixed").css("opacity","1"):$(".navbar-minimize-fixed").css("opacity","0")}),0==$(".full-screen-map").length&&0==$(".bd-docs").length&&$(".collapse").on("show.bs.collapse",function(){$(this).closest(".navbar").removeClass("navbar-transparent").addClass("bg-white")}).on("hide.bs.collapse",function(){$(this).closest(".navbar").addClass("navbar-transparent").removeClass("bg-white")}),blackDashboard.initMinimizeSidebar(),$navbar=$(".navbar[color-on-scroll]"),scroll_distance=$navbar.attr("color-on-scroll")||500,0!=$(".navbar[color-on-scroll]").length&&(blackDashboard.checkScrollForTransparentNavbar(),$(window).on("scroll",blackDashboard.checkScrollForTransparentNavbar)),$(".form-control").on("focus",function(){$(this).parent(".input-group").addClass("input-group-focus")}).on("blur",function(){$(this).parent(".input-group").removeClass("input-group-focus")}),$(".bootstrap-switch").each(function(){$this=$(this),data_on_label=$this.data("on-label")||"",data_off_label=$this.data("off-label")||"",$this.bootstrapSwitch({onText:data_on_label,offText:data_off_label})})}),$(document).on("click",".navbar-toggle",function(){var a=$(this);if(1==blackDashboard.misc.navbar_menu_visible)$html.removeClass("nav-open"),blackDashboard.misc.navbar_menu_visible=0,setTimeout(function(){a.removeClass("toggled"),$(".bodyClick").remove()},550);else{setTimeout(function(){a.addClass("toggled")},580);$('<div class="bodyClick"></div>').appendTo("body").click(function(){$html.removeClass("nav-open"),blackDashboard.misc.navbar_menu_visible=0,setTimeout(function(){a.removeClass("toggled"),$(".bodyClick").remove()},550)}),$html.addClass("nav-open"),blackDashboard.misc.navbar_menu_visible=1}}),$(window).resize(function(){if(seq=seq2=0,0==$full_screen_map.length&&0==$(".bd-docs").length){var a=$navbar.find('[data-toggle="collapse"]').attr("aria-expanded");$navbar.hasClass("bg-white")&&991<$(window).width()?$navbar.removeClass("bg-white").addClass("navbar-transparent"):$navbar.hasClass("navbar-transparent")&&$(window).width()<991&&"false"!=a&&$navbar.addClass("bg-white").removeClass("navbar-transparent")}}),blackDashboard={misc:{navbar_menu_visible:0},initMinimizeSidebar:function(){0!=$(".sidebar-mini").length&&(sidebar_mini_active=!0),$("#minimizeSidebar").click(function(){$(this);1==sidebar_mini_active?($("body").removeClass("sidebar-mini"),sidebar_mini_active=!1,blackDashboard.showSidebarMessage("Sidebar mini deactivated...")):($("body").addClass("sidebar-mini"),sidebar_mini_active=!0,blackDashboard.showSidebarMessage("Sidebar mini activated..."));var a=setInterval(function(){window.dispatchEvent(new Event("resize"))},180);setTimeout(function(){clearInterval(a)},1e3)})},showSidebarMessage:function(a){try{$.notify({icon:"tim-icons ui-1_bell-53",message:a},{type:"info",timer:4e3,placement:{from:"top",align:"right"}})}catch(a){console.log("Notify library is missing, please make sure you have the notifications library added.")}}};
|
||||
//# sourceMappingURL=_site_dashboard_free/assets/js/dashboard-free.js.map
|
||||
42
apps/static/assets/package.json
Normal file
@ -0,0 +1,42 @@
|
||||
{
|
||||
"name": "generic-compilation-script",
|
||||
"version": "1.0.0",
|
||||
"description": "Generic Builder",
|
||||
"main": "gulpfile.js",
|
||||
"author": "AppSeed",
|
||||
"keywords": [
|
||||
"css",
|
||||
"sass",
|
||||
"gulp",
|
||||
"web"
|
||||
],
|
||||
"homepage": "https://appseed.us",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/app-generator"
|
||||
},
|
||||
"bugs": {
|
||||
"email": "support@appseed.us"
|
||||
},
|
||||
"license": "MIT License",
|
||||
"devDependencies": {
|
||||
"browser-sync": "^2.27.4",
|
||||
"del": "^6.0.0",
|
||||
"gulp": "^4.0.2",
|
||||
"gulp-autoprefixer": "^8.0.0",
|
||||
"gulp-clean-css": "^4.3.0",
|
||||
"gulp-cssbeautify": "^3.0.0",
|
||||
"node-sass": "^6.0.1",
|
||||
"gulp-file-include": "^2.3.0",
|
||||
"gulp-header": "^2.0.9",
|
||||
"gulp-htmlmin": "^5.0.1",
|
||||
"gulp-npm-dist": "^1.0.3",
|
||||
"gulp-plumber": "^1.2.1",
|
||||
"gulp-rename": "^2.0.0",
|
||||
"gulp-sass": "^5.0.0",
|
||||
"gulp-sourcemaps": "^3.0.0",
|
||||
"gulp-uglify": "^3.0.2",
|
||||
"gulp-wait": "^0.0.2",
|
||||
"merge-stream": "^2.0.0"
|
||||
}
|
||||
}
|
||||