diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..429160a --- /dev/null +++ b/.gitignore @@ -0,0 +1,69 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*,cover +.hypothesis/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +#Ipython Notebook +.ipynb_checkpoints + +#proxadmin custom ignores +*.sqlite +db_repository/ +migrations/ +alchemydumps/ +config.py diff --git a/app/models.py b/app/models.py index bd3f72c..f44a88d 100644 --- a/app/models.py +++ b/app/models.py @@ -84,7 +84,7 @@ class User(db.Model, UserMixin): currency = db.Column(db.String(3), default='BGN') inv_deployments = db.relationship('Deployment', backref='owner', lazy='dynamic') - inv_contracts = db.relationship('Contract', backref='owner', lazy='dynamic') + inv_services = db.relationship('Service', backref='owner', lazy='dynamic') inv_domains = db.relationship('Domain', backref='owner', lazy='dynamic') inv_address = db.relationship('Address', backref='owner', lazy='dynamic') @@ -219,104 +219,21 @@ def contact_proxmaster(data, method, cubeid=0): except: return None -#TEMPLATE CLASSES -class Product(db.Model): - __tablename__ = 'products' - pid = db.Column(db.Integer, primary_key=True) #PK - group = db.Column(db.Integer) - name = db.Column(db.String(64)) - image = db.Column(db.String(128)) - description = db.Column(db.String(128)) - cpu = db.Column(db.Integer) #default cpu - mem = db.Column(db.Integer) #default mem - hdd = db.Column(db.Integer) #default hdd - recipe = db.Column(db.String(128)) #defaut template name - enabled = db.Column(db.Boolean) - - @staticmethod - def insert_products(): - products = current_app.config['PRODUCTS'] - for p in products: - product = Product.query.filter_by(pid=p).first() - if product is None: - product = Product(name=p) - - #insert default values - product.group = products[p][0] - product.name = products[p][1] - product.image = products[p][2] - product.description = products[p][3] - product.cpu = products[p][4] - product.mem = products[p][5] - product.hdd = products[p][6] - product.recipe = products[p][7] - product.enabled = products[p][8] - db.session.add(product) - db.session.commit() - - @staticmethod - def get_products(): - result = Product.query.all() - products = {} - for product in result: - if product.enabled == True: - products[int(product.pid)] = { 'group': product.group, - 'img': '/static/images/' + product.image, - 'name': product.name, - 'description': product.description, - 'cpu': product.cpu, - 'mem': product.mem, - 'hdd': product.hdd, - 'recipe': product.recipe - } - return products - class Service(db.Model): __tablename__ = 'services' pid = db.Column(db.Integer, primary_key=True) #PK + user_id = db.Column(db.Integer, db.ForeignKey('users.pid')) #FK name = db.Column(db.String(64)) image = db.Column(db.String(128)) description = db.Column(db.Unicode(128)) + unit = db.Column(db.Integer) unitprice = db.Column(db.Float) enabled = db.Column(db.Boolean) - @staticmethod - def insert_services(): - services = current_app.config['SERVICES'] - for s in services: - service = Service.query.filter_by(pid=p).first() - if service is None: - service = Service(name=s) - - #insert default values - service.name = products[p][1] - service.image = products[p][2] - service.description = products[p][3] - service.unitprice = products[p][4] - service.enabled = products[p][5] - db.session.add(service) - db.session.commit() - - @staticmethod - def get_services(): - result = Service.query.all() - services = {} - for service in result: - if service.enabled == True: - services[int(service.pid)] = {'img': '/static/images/' + service.image, - 'name': service.name, - 'description': service.description, - 'unitprice': service.unitprice - } - return services - - -#INVENTORY CLASSES class Deployment(db.Model): __tablename__ = 'deployments' pid = db.Column(db.Integer, primary_key=True) user_id = db.Column(db.Integer, db.ForeignKey('users.pid')) #FK - product_id = db.Column(db.Integer, db.ForeignKey('products.pid')) #FK date_created = db.Column(db.DateTime, index=True, default=datetime.utcnow) date_expire = db.Column(db.DateTime) enabled = db.Column(db.Boolean) @@ -326,6 +243,7 @@ class Deployment(db.Model): machine_cpu = db.Column(db.Integer) machine_mem = db.Column(db.Integer) machine_hdd = db.Column(db.Integer) + machine_ipv4list = db.Column(db.String) def charge(): result = Deployment.query.all() @@ -347,33 +265,24 @@ class Deployment(db.Model): #TODO: Send emails here. db.session.commit() -class Contract(db.Model): - __tablename__ = 'contracts' +#NAMESPACE +class Region(db.Model): + __tablename__ = 'regions' pid = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String(64)) + description = db.Column(db.String(128)) + extraprice = db.Column(db.Float) + +class Address(db.Model): + __tablename__ = 'address' + pid = db.Column(db.Integer, primary_key=True) + date_assigned = db.Column(db.DateTime, index=True, default=datetime.utcnow) user_id = db.Column(db.Integer, db.ForeignKey('users.pid')) #FK - service_id = db.Column(db.Integer, db.ForeignKey('services.pid')) #FK - date_created = db.Column(db.DateTime, index=True, default=datetime.utcnow) - date_expire = db.Column(db.DateTime) - enabled = db.Column(db.Boolean) - - description = db.Column(db.Unicode) - units = db.Column(db.Integer) - discount = db.Column(db.Integer) #percent - - def charge(): - result = Contract.query.all() - for contract in result: - managed_user = User.query.get(contract.user_id) - db.session.add(contract) - #if datetime.utcnow.date() > (contract.date_expire - timedelta(days=10)): - if contract.enabled == True: - print('{}> Contract {} will expire in 10 days at {}. Creating new order...'.format(managed_user.email, contract.pid, contract.date_expire)) - current_service = Service.query.get(int(contract.product_id)) - transaction = Transaction(user_id=managed_user.id, units=contract.units, unitvalue=(current_service.unitprice * contract.units), description=current_service.name) - db.session.add(transaction) - contract.data_expire = datetime.utcnow.date() + timedelta(days=30) - db.session.commit() - return True + region_id = db.Column(db.Integer, db.ForeignKey('regions.pid')) #FK + ipv4 = db.Column(db.String(64)) + ipv6 = db.Column(db.String(256)) + rdns = db.Column(db.String(256)) + macaddr = db.Column(db.String(128)) class Domain(db.Model): __tablename__ = 'domains' @@ -381,34 +290,9 @@ class Domain(db.Model): user_id = db.Column(db.Integer, db.ForeignKey('users.pid')) #FK date_created = db.Column(db.DateTime, index=True, default=datetime.utcnow) date_expire = db.Column(db.DateTime) - enabled = db.Column(db.Boolean) - fqdn = db.Column(db.String, unique=True) auto_update = db.Column(db.Boolean) - def charge(): - result = Domain.query.all() - for domain in result: - managed_user = User.query.get(domain.user_id) - db.session.add(domain) - #if datetime.utcnow.date() > (domain.date_expire - timedelta(days=60)): - if domain.enabled == True: - print('{}> Domain {} will expire in 60 days at {}. Creating new order...'.format(managed_user.email, domain.fqdn, domain.date_expire)) - transaction = Transaction(user_id=managed_user.id, unitvalue=25, description=domain.fqdn) - db.session.add(transaction) - db.session.commit() - return True - -class Address(db.Model): - __tablename__ = 'address' - pid = db.Column(db.Integer, primary_key=True) - user_id = db.Column(db.Integer, db.ForeignKey('users.pid')) #FK - date_created = db.Column(db.DateTime, index=True, default=datetime.utcnow) - - ipaddr = db.Column(db.String(128)) - macaddr = db.Column(db.String(128)) - - #UINVOICE class Transaction(db.Model): __tablename__ = 'transaction' diff --git a/app/static/css/style.css b/app/static/css/style.css index 483749f..0b8df70 100644 --- a/app/static/css/style.css +++ b/app/static/css/style.css @@ -177,10 +177,14 @@ a:active { padding-left: 16px; } +.container { + width: 100%; +} + .container-fluid { position: relative; - max-width: 1170px; - min-width: 480px; + max-width: 100%; + min-width: 280px; } .container-fluid-index { @@ -252,9 +256,15 @@ a:active { border-color: #070; } -.panel > .panel-heading { +.panel-heading { padding: 6px 15px; border-bottom: 1px solid transparent; border-top-left-radius: 3px; border-top-right-radius: 3px; } + +.tooltip-inner { + max-width: 350px; + /* If max-width does not work, try using width instead */ + width: 350px; +} diff --git a/app/templates/main/index.html b/app/templates/main/index.html index a127ba6..b42fd26 100644 --- a/app/templates/main/index.html +++ b/app/templates/main/index.html @@ -1,12 +1,12 @@ {% extends "base.html" %} +{% block head %} +{{ super() }} +{% endblock %} + {% block styles %} {{ super() }} -{% endblock %} - -{% block head %} -{{ super() }} + + +{% endblock %} + {% block scripts %} {{ super() }} -{% endblock %} - -{% block page_content %} - +{% endblock %} - - - +{% block page_content %}

- {% block sidebar %} -
-
-
- {% include "/settings/acc_avatar.html" %} -
-
-
- {% endblock %} +
+
+
Deployments
+

+ {% if inv_deploymens == None %} + + + + + + + + + + + + + {% for deploy in inv_deployments %} + + + + + + + + + + {% endfor %} + +
NameCPUMemHDDIPv4Control
+ {% if status[deploy.machine_id] == 'running' %}{% else %}{% endif %}{{ deploy.machine_alias }}{{ deploy.machine_cpu }} Cores{{ deploy.machine_mem }} MB{{ deploy.machine_hdd }} GB{% for ip in deploy.machine_ipv4list %}{{ ip }}{% endfor %}{% if status[deploy.machine_id] == 'running' %} + + + {% else %} + + {% endif %}{% if status[deploy.machine_id] == 'running' %} + + {% endif %}
+ {% endif %} + +

+
+
-
-
+
+
+
Domains
+

+ {% for domain in inv_domains %} + {{ fqdn }} + {% endfor %} + +

+
+
+ + +
+
Services

{% for contract in inv_contracts %} @@ -85,115 +184,15 @@ addEventListener("DOMContentLoaded", function() { {{ contract.units }}
{% if not contract.discount == 0 %} Discount %{{ contract.discount }}
- Credit: {{ contract.credit }}

+ Credit: {{ contract.credit }}
{% endif %} {% endfor %} -

+ +

-
-
-
Domains
-

- {% for domain in inv_domains %} - {{ fqdn }}
- {% endfor %} -

-

-
-
- - -
-
-
-
Deployments
-

- {% for deploy in inv_deployments %} -

-
-
-
-
- -
-
- {% if status[deploy.machine_id] == 'running' %} - - - - {% else %} - - {% endif %} -

- Grid ID# {{ deploy.machine_id }}
- CPU: {{ deploy.machine_cpu }} cores
- Memory: {{ deploy.machine_mem }} MB
- Disk Space: {{ deploy.machine_hdd }} GB
-
- Expiry date: {{ deploy.date_expire }}
- Credit: {{ deploy.credit }} -
- -
- - - - - - - -
Network Bandwidth

- - - - - - - - - -
CPU Load

- - - - - - - - -
Memory Usage

- - - - - - - - -
Disk Input/Output

-
- -
- Not yet implemented. -
- -
-
- {% endfor %} -
-
- -
-
-
diff --git a/app/templates/vmanager/deploy.html b/app/templates/vmanager/deploy.html index 89cfe97..1b9021b 100644 --- a/app/templates/vmanager/deploy.html +++ b/app/templates/vmanager/deploy.html @@ -83,6 +83,7 @@ {{ form.mem.label }} {{ form.mem }}
{{ form.hdd.label }} {{ form.hdd }}
{{ form.recipe.label }} {{ form.recipe }}
+ {{ form.ipv4.label }} {{ form.ipv4 }}


diff --git a/app/vmanager/forms.py b/app/vmanager/forms.py index 57b5d60..e4a92db 100644 --- a/app/vmanager/forms.py +++ b/app/vmanager/forms.py @@ -12,7 +12,7 @@ class DeployForm(FlaskForm): mem = StringField('Памет:') hdd = StringField('Дисково пространство:') recipe = SelectField('Рецепта') - #ipv4 = SelectField('Брой публични IP адреса', choices=[('1', '1'),('2', '2' ), ('3', '3')]) + ipv4 = SelectField('Брой публични IP адреса', choices=[('1', '1'),('2', '2' ), ('3', '3')]) invite_key = StringField('Покана', [validators.DataRequired(), validators.Length(6,35)]) def validate_invite_key(self, field): diff --git a/app/vmanager/routes.py b/app/vmanager/routes.py index 0208a2a..1449d32 100644 --- a/app/vmanager/routes.py +++ b/app/vmanager/routes.py @@ -6,13 +6,14 @@ from . import vmanager from .forms import DeployForm from .. import db from ..email import send_email -from ..models import User, Role, Product, Deployment, contact_proxmaster, Contract, Domain +from ..models import User, Role, Deployment, Service, Region, Address, Domain, contact_proxmaster from ..decorators import admin_required, permission_required import base64 import string import random from datetime import datetime, timedelta, date, time +import ast def randstr(n): return ''.join(random.SystemRandom().choice(string.ascii_lowercase + string.digits) for _ in range(n)) @@ -95,17 +96,16 @@ def deploy(product_id=None): 'vps_cpu': form.cpu.data, 'vps_mem': form.mem.data, 'vps_hdd': form.hdd.data, - 'vps_ipv4': '1' } + 'vps_ipv4': form.ipv4.data } try: query = contact_proxmaster(data, 'vmcreate') except: flash('Region unreachable! Please try again later...') - return redirect(url_for('vmanager.index')) + return redirect(url_for('vmanager.dashboard')) if query is not None: - cubeid = query['cube'] - deployment = Deployment(user_id=client_id, product_id=product_id, machine_alias=form.servername.data, machine_id=cubeid, machine_cpu=form.cpu.data, machine_mem=form.mem.data, machine_hdd=form.hdd.data, date_expire=(datetime.utcnow() + timedelta(days=30)), enabled=True) + deployment = Deployment(user_id=client_id, product_id=product_id, machine_alias=form.servername.data, machine_id=query['cube'], machine_cpu=form.cpu.data, machine_mem=form.mem.data, machine_hdd=form.hdd.data, date_expire=(datetime.utcnow() + timedelta(days=30)), enabled=True) db.session.add(deployment) db.session.commit() @@ -113,7 +113,7 @@ def deploy(product_id=None): else: flash('Deploy cancelled! Please try again later...') - return redirect(url_for('vmanager.index')) + return redirect(url_for('vmanager.dashboard')) return render_template('vmanager/deploy.html', page=page, form=form, product_id=product_id, product_pic=product_pic, product_name=product_name, product_description=product_description, product_recipe=product_recipe) @@ -130,11 +130,11 @@ def dashboard(): inv_deploycubeids.extend([invcls.machine_id]) inv_deploynames.extend([invcls.machine_alias]) - contracts = current_user.inv_contracts.order_by(Contract.date_created.desc()).all() - inv_contracts = [] - for invcls in contracts: + services = current_user.inv_services.order_by(Service.date_created.desc()).all() + inv_services = [] + for invcls in services: if invcls.enabled == True: - inv_contracts.extend([invcls.template]) + inv_services.extend([invcls.description]) domains = current_user.inv_domains.order_by(Domain.date_created.desc()).all() inv_domains = [] @@ -221,4 +221,3 @@ def command(cmd=None, vmid=0): #TODO: log ips abort(404) - diff --git a/before_upgrade.txt b/before_upgrade.txt index 6ec6465..f0aa4ea 100644 --- a/before_upgrade.txt +++ b/before_upgrade.txt @@ -1,36 +1,48 @@ -alembic==0.9.0 -Babel==2.3.4 +alembic==0.9.2 +appdirs==1.4.3 +Babel==2.4.0 blinker==1.4 +certifi==2017.4.17 +chardet==3.0.3 click==6.7 dnspython==1.15.0 dnspython3==1.15.0 dominate==2.3.1 -Flask==0.12 -Flask-Babel==0.11.1 +facepy==1.0.9 +Flask==0.12.2 +Flask-AlchemyDumps==0.0.10 +Flask-Babel==0.11.2 Flask-Bootstrap==3.3.7.1 Flask-Login==0.4.0 Flask-Mail==0.9.1 -Flask-Migrate==2.0.3 +Flask-Migrate==2.0.4 +Flask-Moment==0.5.1 Flask-Script==2.0.5 Flask-SQLAlchemy==2.2 Flask-WTF==0.14.2 -gunicorn==19.7.0 +gunicorn==19.7.1 +idna==2.5 iso3166==0.8 itsdangerous==0.24 -Jinja2==2.9.5 +Jinja2==2.9.6 Mako==1.0.6 -MarkupSafe==0.23 +MarkupSafe==1.0 onetimepass==1.0.1 pkg-resources==0.0.0 -psycopg2==2.6.2 +psycopg2==2.7.1 +Pygments==2.2.0 PyQRCode==1.2.1 +python-dateutil==2.6.0 python-editor==1.0.3 -pytz==2016.10 -requests==2.13.0 +pytz==2017.2 +requests==2.17.3 schedule==0.4.2 six==1.10.0 sortedcontainers==1.5.7 -SQLAlchemy==1.1.6 +SQLAlchemy==1.1.10 +traits==4.6.0 +Unipath==1.1 +urllib3==1.21.1 visitor==0.1.3 -Werkzeug==0.11.15 +Werkzeug==0.12.2 WTForms==2.1 diff --git a/manage.py b/manage.py index 95ff0ae..67134e4 100644 --- a/manage.py +++ b/manage.py @@ -3,6 +3,7 @@ import os import subprocess, shlex from app import app, db +from flask_alchemydumps import AlchemyDumps, AlchemyDumpsCommand from flask_script import Manager, Shell, Command from flask_migrate import Migrate, MigrateCommand @@ -15,33 +16,29 @@ def make_shell_context(): Deployment=Deployment) migrate = Migrate(app, db) +dump = AlchemyDumps(app, db) manager = Manager(app) manager.add_command('shell', Shell(make_context=make_shell_context)) manager.add_command('db', MigrateCommand) +manager.add_command('dump', AlchemyDumpsCommand) @manager.command def deploy(): """Run deployment tasks.""" from flask_migrate import upgrade - from app.models import Role, User, Deployment, Product + from app.models import Role, User # migrate database to latest revision upgrade() # create user roles Role.insert_roles() - Product.insert_products() @manager.command -@manager.option('-r' '--restore_file', help='Restore from grid dump file') -def restore(restore_file): - """ recreate db from grid export with python3 manage.py restore /path/grid.tar.bz2 """ - print(str(restore_file)) - #TODO - from app.models import User - db.session.add(User(email=str(user), password=str(password), confirmed=True, confirmed_on=datetime.datetime.now())) - db.session.commit() +def sampledata(): + """Deploy Sample Data""" + pass def run_scheduler(): command_line = 'python3 /home/proxadmin/appserver/proxmaster-panel/schedulerd.py' @@ -65,7 +62,7 @@ def charge_domains(): @manager.command def runserver(): - print('Starting Scheduler...') + #print('Starting Scheduler...') #run_scheduler() print('Starting Flask...') @@ -74,4 +71,3 @@ def runserver(): if __name__ == '__main__': manager.run() - diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index fff826c..0000000 --- a/requirements.txt +++ /dev/null @@ -1,42 +0,0 @@ -alembic==0.9.2 -appdirs==1.4.3 -Babel==2.4.0 -blinker==1.4 -click==6.7 -dnspython==1.15.0 -dnspython3==1.15.0 -dominate==2.3.1 -facepy==1.0.8 -Flask==0.12.2 -Flask-Babel==0.11.2 -Flask-Bootstrap==3.3.7.1 -Flask-Login==0.4.0 -Flask-Mail==0.9.1 -Flask-Migrate==2.0.3 -Flask-Moment==0.5.1 -Flask-Script==2.0.5 -Flask-SQLAlchemy==2.2 -Flask-WTF==0.14.2 -gunicorn==19.7.1 -iso3166==0.8 -itsdangerous==0.24 -Jinja2==2.9.6 -Mako==1.0.6 -MarkupSafe==1.0 -onetimepass==1.0.1 -pkg-resources==0.0.0 -psycopg2==2.7.1 -Pygments==2.2.0 -PyQRCode==1.2.1 -python-dateutil==2.6.0 -python-editor==1.0.3 -pytz==2017.2 -requests==2.14.2 -schedule==0.4.2 -six==1.10.0 -sortedcontainers==1.5.7 -SQLAlchemy==1.1.10 -traits==4.6.0 -visitor==0.1.3 -Werkzeug==0.12.2 -WTForms==2.1