Compare commits

..

No commits in common. "dev/2.7.10" and "master" have entirely different histories.

88 changed files with 384 additions and 1296 deletions

2
.gitignore vendored
View file

@ -1,6 +1,6 @@
settings.ini
.idea/
.venv/
# Created by .ignore support plugin (hsz.mobi)
### Python template
# Byte-compiled / optimized / DLL files

View file

@ -1,5 +0,0 @@
.venv
.git
__pycache__/
*.egg-info/
venv

BIN
FreeSans.ttf Normal file

Binary file not shown.

BIN
FreeSansBold.ttf Normal file

Binary file not shown.

BIN
FreeSansBoldOblique.ttf Normal file

Binary file not shown.

BIN
FreeSansOblique.ttf Normal file

Binary file not shown.

View file

@ -1,4 +0,0 @@
DEST:=/opt/nativecloud
sync:
rsync -rvza --exclude-from .rsyncignore --delete ./ root@192.168.158.136:/opt/nativecloud/

View file

@ -10,7 +10,6 @@ Statuses:
- 3: Процесс деактивации
- 4: Создание...
- 5: Удаление...
- 6: Перезапуск
_Tasks_
@ -34,12 +33,6 @@ Statuses:
_Get containers status_
```shell
curl -X GET http://127.0.0.1:5000/server_api/containers/status/ \
-u f411b7d6-bf93-4fcd-91ee-03e5343d0187:b3c9a8b0-95ca-11e5-bec1-28d244e159e9
```
`curl -X GET http://127.0.0.1:5000/server_api/containers/status/ -u f411b7d6-bf93-4fcd-91ee-03e5343d0187:b3c9a8b0-95ca-11e5-bec1-28d244e159e9`
```shell
curl -X POST http://127.0.0.1:5000/server_api/containers/status/663b31b4-22b1-4846-bfaf-27d6389beef4 \
-u f411b7d6-bf93-4fcd-91ee-03e5343d0187:b3c9a8b0-95ca-11e5-bec1-28d244e159e9 -d 'status=0&message="test"'
```
`curl -X POST http://127.0.0.1:5000/server_api/containers/status/663b31b4-22b1-4846-bfaf-27d6389beef4 -u f411b7d6-bf93-4fcd-91ee-03e5343d0187:b3c9a8b0-95ca-11e5-bec1-28d244e159e9 -d 'status=0&message="test"'`

View file

@ -0,0 +1,35 @@
# coding: utf-8
from flask import Blueprint, jsonify, g
from flask_httpauth import HTTPBasicAuth
from SWSCloudCore.controllers.users import ControllerAPI
from SWSCloudCore.controllers.users import ControllerUsers
api = Blueprint('api', __name__, url_prefix='/api/v1')
auth = HTTPBasicAuth()
"""
TODO: Реализовать Процесс авторизации в версии API 2.0
- получаем емейл и секретный ключ
- создаём временный токен
- выдаём токен
"""
@auth.verify_password
def verify_password(username, password):
if not ControllerAPI().auth(username, password):
return False
g.user_id = ControllerUsers().get_id_by_email(username)
return True
@api.route('/')
@auth.login_required
def index():
"""
curl -X POST http://localhost:5000/api/v1/ -u <email>:<secret>
:return:
"""
return jsonify(user_id=g.user_id)

View file

@ -1,2 +0,0 @@
from .token import api_v2_token

View file

@ -1,59 +0,0 @@
# coding: utf-8
from flask import Blueprint, jsonify, request, g
from SWSCloudCore.models import Users
from SWSCloudAPI.API.compute.v2.common import *
from SWSCloudAPI.Utils import decorators, Tokens
api_v2_token = Blueprint('v2token', __name__, url_prefix='/api/auth/v2')
@api_v2_token.route('/token', methods=['POST'])
@decorators.content_type
@decorators.login_required
def token_post():
"""Get token
_Example_
curl -X POST http://localhost:5001/api/auth/v2/token -d '{"email":"vanzhiganov@ya.ru","password":"qwepoi123"}' -H "Content-Type: application/json"
{
"status": "ok",
"payload": {
"token": "422f45a4-eab9-4a79-9954-61c568bae0eb",
"email": "email@my.com"
}
}
:return:
"""
# get user data
user = Users.get_by_email(request.json.get('email'))
#
new_token = Tokens().set(dict(user_id=str(user.id), email=user.email), TOKEN_TTL)
#
return jsonify(status='ok', payload={'token': new_token})
@api_v2_token.route('/token', methods=['GET'])
@decorators.content_type
@decorators.auth_token
def token_get():
"""Get token data
curl -X GET http://localhost:5001/api/auth/v2/token -H "X-Auth-Token: 422f45a4-eab9-4a79-9954-61c568bae0eb" -H "Content-Type: application/json"
:return:
"""
return jsonify(status='ok', payload=g.tokens.get(g.auth_token))
@api_v2_token.route('/token', methods=['DELETE'])
@decorators.content_type
@decorators.auth_token
def token_delete():
"""
curl -X DELETE http://localhost:5001/api/auth/v2/token -H "X-Auth-Token: 422f45a4-eab9-4a79-9954-61c568bae0eb" -H "Content-Type: application/json"
:return:
"""
g.tokens.delete(g.auth_token)
return jsonify(status='ok')

View file

@ -1 +0,0 @@
# coding: utf-8

View file

@ -1,16 +0,0 @@
from SWSCloudCore.controllers.users import ControllerAPI
from SWSCloudCore.controllers.users import ControllerUsers
from flask import g
from flask_httpauth import HTTPBasicAuth
auth = HTTPBasicAuth()
@auth.verify_password
def verify_password(username, password):
if not ControllerAPI().auth(username, password):
return False
g.user_id = ControllerUsers().get_id_by_email(username)
return True

View file

@ -1,4 +0,0 @@
from .containers import api_v1_containers
from .datacenters import api_v1_datacenters
from .pricing import api_v1_pricing
from .vms import api_v1_vms

View file

@ -1,24 +0,0 @@
# coding: utf-8
from flask import Blueprint, jsonify
from SWSCloudAPI.API.compute.v1.common import auth
from SWSCloudCore.models import DataCenters
api_v1_datacenters = Blueprint('datacenters', __name__, url_prefix='/api/compute/v1/datacenters')
@api_v1_datacenters.route('/', methods=['GET'])
@auth.login_required
def index():
"""
get containers list
curl -X http://localhost:5000/api/compute/v1/datacenters/ -u <email>:<secret>
:return:
"""
# dc_list = ControllerDataCenters().get()
dc_list = DataCenters.get_available()
items = list()
for x in dc_list:
items.append(x)
return jsonify(total=len(dc_list), items=items)

View file

@ -1,3 +0,0 @@
from .views.datacenters import api_v2_datacenters
from .views.pricing import api_v2_pricing
from .views.vms import api_v2_vms

View file

@ -1,4 +0,0 @@
TOKEN_TTL = 1800
TOKEN_PREFIX = 'token_'

View file

@ -1,254 +0,0 @@
# coding: utf-8
from uuid import uuid4
from flask import Blueprint, jsonify, request, g
from SWSCloudAPI.Utils import Tokens
from SWSCloudAPI.Utils import decorators
from SWSCloudCore.controllers.billing import ControllerBilling
from SWSCloudCore.controllers.common import ControllerCommon, ControllerMessagesEmail
from SWSCloudCore.controllers.containers import (
ControllerContainers, ControllerContainersStatistics, ControllerContainersStatisticsState)
from SWSCloudCore.controllers.datacenters import ControllerDataCenters
from SWSCloudCore.controllers.ips import ControllerIps
from SWSCloudCore.controllers.tasks import ControllerTasks
from SWSCloudCore.controllers.users import ControllerSSHKey
from SWSCloudCore.controllers.users import ControllerUsers
api_v2_containers = Blueprint('v2containers', __name__, url_prefix='/api/compute/v2/containers')
@api_v2_containers.route('/', methods=['GET'])
@decorators.content_type
@decorators.auth_token
def containers_list():
"""
curl -X GET http://localhost:5000/api/v1/containers/ -u <email>:<secret>
:return:
"""
tokens = Tokens()
# get containers list
containers = ControllerContainers(g.user_id).get_items()
#
return jsonify(total=containers['total'], items=containers['items'])
@api_v2_containers.route('/', methods=['POST'])
@decorators.content_type
@decorators.auth_token
def container_create():
"""
curl -X POST http://localhost:5000/api/v1/containers/ -u <email>:<secret> -d "datacenter_id=123"
:return:
"""
# Check exists data center
if not ControllerDataCenters().exists(request.form.get('datacenter')):
return jsonify(message='datacenter not exists')
tokens = Tokens()
# Check money
if ControllerBilling().get(g.user_id) <= 0:
return jsonify(message='no money')
# select server from selected region with available ip-addresses
# select IP
select_ip = ControllerIps().getfree(request.form.get('datacenter'))
# mark ip as busy (taken)
ControllerIps().setbusy(select_ip.id)
# generate password for container user
password = ControllerCommon().generate_password(size=14)
user_ssh = ControllerSSHKey(g.user_id)
new_container = dict(
container_id=str(uuid4()),
datacenter_id=str(select_ip.datacenter.id),
server_id=str(select_ip.server.id),
ipv4=select_ip.ipv4,
ipv6=select_ip.ipv6,
ipv4_gateway=select_ip.ipv4_gateway,
ipv6_gateway=select_ip.ipv6_gateway,
username='ubuntu',
password=password,
ssh_key='',
)
# SSH key
if user_ssh.check():
new_container['ssh_key'] = user_ssh.get()
# create container record in database
# status 4: creation
status = 4
container_create = ControllerContainers(g.user_id).create(
new_container['container_id'],
new_container['datacenter_id'],
new_container['server_id'],
new_container['ipv4'],
new_container['ipv6'],
status
)
# create default state data
ControllerContainersStatisticsState().set(new_container['container_id'], dict())
if container_create:
# create task for create new container
ControllerTasks(g.user_id).create(
new_container['datacenter_id'],
new_container['server_id'],
'container_create',
0,
container_id=new_container['container_id'],
ipv4=new_container['ipv4'],
ipv6=new_container['ipv6'],
ipv4_gateway=new_container['ipv4_gateway'],
ipv6_gateway=new_container['ipv6_gateway'],
username=new_container['username'],
password=new_container['password'],
ssh_key=new_container['ssh_key']
)
# send mail message with recovery code
message_parts = []
if new_container['ipv4']:
message_parts.append(u"IPv4: %s" % new_container['ipv4'])
if new_container['ipv6']:
message_parts.append(u"IPv6: %s" % new_container['ipv6'])
message_parts.append(u"Пользователь: %s" % new_container['username'])
message_parts.append(u"Пароль: %s" % new_container['password'])
if new_container['ssh_key']:
message_parts.append(u"SSH ключ: добавлен")
message = '<br/>\n'.join(message_parts)
subject = u'GoCloud.ru: Новый контейнер'
lead = u"""Поздравляем с новым контейнером."""
callout = u"""
Для входа в личный кабинет воспользуйтесь
<a href="https://gocloud.ru/account/login">страницей авторизации</a>.
"""
user_data = ControllerUsers(g.user_id).get()
email = ControllerMessagesEmail()
email.send(title=subject, to=user_data.email, lead=lead, message=message, callout=callout)
#
return jsonify(message='ok')
@api_v2_containers.route('/container/<uuid:container_id>', methods=['DELETE'])
@decorators.content_type
@decorators.auth_token
def container_delete(container_id):
"""
curl -X DELETE http://gocloud.ru/api/v1/container/<uuid:container_id> -u <email>:<secret> -d "container_id=<uuid:container_id>"
:return:
"""
auth_token = request.headers.get('X-Auth-Token')
tokens = Tokens()
containers = ControllerContainers(g.user_id)
# check the user have a selected rule
# if user not have a container then redirect to the container list
if not containers.check_exists_item(container_id):
return jsonify(message='container_not_exists')
# get container details
container_details = ControllerContainers(g.user_id).get_item(container_id)
# Обновляем статус "5" для правила
containers.set_status(container_id, 5)
# Создание задания
ControllerTasks(g.user_id).create(
container_details.datacenter.id,
container_details.server.id,
'container_delete',
0,
container_id=container_id
)
# TODO: send email container was deleted about
return jsonify(status=0, message='container has been deleted')
@api_v2_containers.route('/<uuid:container_id>', methods=['GET'])
@decorators.content_type
@decorators.auth_token
def container_info(container_id):
"""
curl -X GET http://localhost:5000/api/v1/container/<uuid:container_id> -u <email>:<secret>
:return:
"""
# init
containers = ControllerContainers(g.user_id)
# check the user have a selected rule
# if user not have a container then redirect to the container list
if not containers.check_exists_item(container_id):
return jsonify(message='container_not_found')
# get container details
container_details = containers.get_item(container_id)
# print ControllerContainersStatisticsState().get(container_id)
statistics = list()
for s in ControllerContainersStatistics(container_id).size_get(1):
created = s.created
statistics.append({
'year': created.strftime('%Y'),
'month': created.strftime('%m'),
'day': created.strftime('%d'),
'hour': created.strftime('%H'),
'minute': created.strftime('%M'),
'data': s.memory
})
# return
return jsonify(container=container_details, stats_memory=statistics)
@api_v2_containers.route('/<uuid:container_id>', methods=['POST'])
@decorators.content_type
@decorators.auth_token
def container_actions(container_id):
"""
curl -X POST http://localhost:5000/api/v1/container/<uuid:container_id> -u <email>:<secret> -d "status=inactive"
curl -X POST http://localhost:5000/api/v1/container/<uuid:container_id> -u <email>:<secret> -d "action=active"
:return:
"""
containers = ControllerContainers(g.user_id)
# check the user have a selected rule
# if user not have a container then redirect to the container list
if not containers.check_exists_item(container_id):
return jsonify(status='error', message='container_not_found')
# get container details
container_details = containers.get_item(container_id)
if request.form.get('status') == "inactive":
containers.set_status(container_id, 3)
# Создание задания
ControllerTasks(g.user_id).create(
container_details.datacenter.id,
container_details.server.id,
'container_stop',
0,
container_id=container_id
)
elif request.form.get('status') == 'active':
balance = ControllerBilling().get(g.user_id)
if balance <= 0:
return jsonify(message='no money')
containers.set_status(container_id, 2)
# Создание задания
ControllerTasks(g.user_id).create(
container_details.datacenter.id,
container_details.server.id,
'container_start',
0,
container_id=container_details.id
)
return jsonify(message='ok')
else:
return jsonify(status=1, message='require action')

View file

@ -1,25 +0,0 @@
# coding: utf-8
from flask import Blueprint, jsonify, g
from SWSCloudAPI.Utils import decorators
from SWSCloudCore.models import DataCenters
api_v2_datacenters = Blueprint('v2datacenters', __name__, url_prefix='/api/compute/v2/datacenters')
@api_v2_datacenters.route('/', methods=['GET'])
@decorators.content_type
@decorators.auth_token
def datacenter_list():
"""Get containers list
curl -X http://localhost:5000/api/compute/v2/datacenters/ -H "X-Auth-Token: a90bb6cd-681f-4f87-b1ca-ea30921e3440" -H "Content-Type: application/json"
:return:
"""
#
dc_list = DataCenters.get_available()
items = list()
for x in dc_list:
items.append(dict(id=str(x.id), name=x.name, country=x.country, city=x.city))
return jsonify(status='ok', payload=items, total=len(dc_list))

View file

@ -1,22 +0,0 @@
# coding: utf-8
from flask import Blueprint, jsonify, g
from SWSCloudAPI.Utils import decorators
from SWSCloudCore.controllers.plans import ControllerPlans
api_v2_pricing = Blueprint('v2pricing', __name__, url_prefix='/api/compute/v2/pricing')
@api_v2_pricing.route('/')
@decorators.content_type
@decorators.auth_token
def pricing():
"""get pricing list
curl -XGET http://localhost:5001/api/compute/v2/pricing/ -H "X-Auth-Token: ad89abee-49bc-4434-98ee-c7598c2f0adc" -H "Content-Type: application/json"
:return:
"""
#
payload = ControllerPlans().get_plans(status='active')
return jsonify(status='ok', payload=payload)

View file

@ -1,245 +0,0 @@
# coding: utf-8
from uuid import uuid4
from flask import Blueprint, jsonify, request, g
from SWSCloudCore import models
from SWSCloudAPI.Utils import decorators
from SWSCloudCore.controllers.billing import ControllerBilling
from SWSCloudCore.controllers.common import ControllerCommon, ControllerMessagesEmail
from SWSCloudCore.controllers.ips import ControllerIps
from SWSCloudCore.controllers.plans import ControllerPlans
from SWSCloudCore.controllers.tasks import ControllerTasks
from SWSCloudCore.controllers.users import ControllerSSHKey
from SWSCloudCore.controllers.users import ControllerUsers
from SWSCloudCore.controllers.vms import ControllerVMS
api_v2_vms = Blueprint('v2vms', __name__, url_prefix='/api/compute/v2/vms')
@api_v2_vms.route('/', methods=['GET'])
@decorators.content_type
@decorators.auth_token
def vms_list():
"""Get virtual servers list"""
return jsonify(status='ok', payload=models.Vms.get_user_items(g.user.get('user_id')))
@api_v2_vms.route('/', methods=['POST'])
@decorators.content_type
@decorators.auth_token
def vms_create():
"""Create virtual server"""
user_balance = ControllerBilling().get(g.user.get('user_id'))
user_ssh = ControllerSSHKey(g.user.get('user_id'))
controller_plans = ControllerPlans()
# check user money
if user_balance <= 0:
return jsonify(status='error', message='no money')
new_vm = dict()
new_vm['vm_id'] = str(uuid4())
# check exists plan
if not controller_plans.exists(request.json.get('plan')):
return jsonify(status='error', message='plan not exists')
# load plan details
plan_details = controller_plans.plan_get(request.json.get('plan'))
new_vm['plan'] = request.json.get('plan')
# select server from selected region with available ip-addresses
# select IP
select_ip = ControllerIps().getfree(request.json.get('datacenter'))
# mark ip as busy (taken)
ControllerIps().setbusy(select_ip.id)
# generate password for container user
new_vm['password'] = ControllerCommon().generate_password(size=14)
new_vm['hostname'] = ControllerCommon().generate_password(size=7)
new_vm['datacenter_id'] = str(select_ip.datacenter.id)
new_vm['server_id'] = str(select_ip.server.id)
new_vm['platform'] = 'x86_64'
new_vm['ipv4'] = select_ip.ipv4
new_vm['ipv6'] = select_ip.ipv6
new_vm['ipv4_gateway'] = select_ip.ipv4_gateway
new_vm['ipv6_gateway'] = select_ip.ipv6_gateway
# TODO: remove hardcore
new_vm['dns1'] = '8.8.8.8'
new_vm['dns2'] = '8.8.4.4'
new_vm['cores'] = plan_details.cores
new_vm['storage'] = plan_details.storage
new_vm['swap'] = plan_details.swap
new_vm['memory'] = plan_details.memory
new_vm['os_name'] = 'ubuntu'
new_vm['os_suite'] = 'trusty'
# sshkey
new_vm['ssh_key'] = None
if user_ssh.check():
new_vm['ssh_key'] = user_ssh.get()
# create container record in database
# status 4: creation
status = 4
container_create = ControllerVMS(g.user.get('user_id')).create(
vm_id=new_vm['vm_id'],
datacenter_id=new_vm['datacenter_id'],
server_id=new_vm['server_id'],
hostname=new_vm['hostname'],
ipv4=new_vm['ipv4'],
ipv6=new_vm['ipv6'],
plan=new_vm['plan'],
platform=new_vm['platform'],
os_name=new_vm['os_name'],
os_suite=new_vm['os_suite'],
status=status
)
# create default state data
# ControllerContainersStatisticsState().set(new_container['container_id'], dict())
if not container_create:
# mark ip as free
ControllerIps().setfree(select_ip.id)
return jsonify(status='error', message='fail')
# create task for create new container
ControllerTasks(g.user.get('user_id')).create(
new_vm['datacenter_id'],
new_vm['server_id'],
'vm_create',
0,
vm_id=new_vm['vm_id'],
ipv4=new_vm['ipv4'],
ipv4_gateway=new_vm['ipv4_gateway'],
ipv6=new_vm['ipv6'],
ipv6_gateway=new_vm['ipv6_gateway'],
password=new_vm['password'],
hostname=new_vm['hostname'],
platform=new_vm['platform'],
# TODO: remove hardcore
dns1=new_vm['dns1'],
dns2=new_vm['dns2'],
cores=new_vm['cores'],
storage=new_vm['storage'],
swap=new_vm['swap'],
memory=new_vm['memory'],
os_name=new_vm['os_name'],
os_suite=new_vm['os_suite'],
ssh_key=new_vm['ssh_key'],
)
# send mail message with recovery code
message_parts = []
if new_vm['ipv4']:
message_parts.append(u"IPv4: %s" % new_vm['ipv4'])
if new_vm['ipv6']:
message_parts.append(u"IPv6: %s" % new_vm['ipv6'])
message_parts.append(u"Пользователь: %s" % 'administrator')
# message_parts.append(u"Пользователь: %s" % new_vm['username'])
message_parts.append(u"Пароль: %s" % new_vm['password'])
if new_vm['ssh_key']:
message_parts.append(u"SSH ключ: добавлен")
message = '<br/>\n'.join(message_parts)
subject = u'GoCloud.ru: Новый виртуальный сервер'
lead = u"""Поздравляем с новым виртуальным сервером."""
callout = u"""
Для входа в личный кабинет воспользуйтесь
<a href="https://gocloud.ru/account/login">страницей авторизации</a>.
"""
user_data = ControllerUsers(g.user.get('user_id')).get()
email = ControllerMessagesEmail()
email.send(title=subject, to=user_data.email, lead=lead, message=message, callout=callout)
return jsonify(
status='ok', payload=dict(
user='root', password=new_vm['password'], ipv4=new_vm['ipv4'],
public_ssh_key='added' if new_vm['ssh_key'] else 'empty'))
@api_v2_vms.route('/<uuid:vm_id>/status', methods=['POST'])
@decorators.content_type
@decorators.auth_token
def vm_actions(vm_id):
"""
"""
# init ...
vm = ControllerVMS(g.user.get('user_id'))
# get container details
vm_details = vm.get(vm_id=vm_id)
if request.json.get('action') == "start":
if ControllerBilling().get(g.user.get('user_id')) <= 0:
return jsonify(message='no money')
vm.set_status(vm_id, 2)
# Создание задания
ControllerTasks(g.user.get('user_id')).create(
vm_details.datacenter.id, vm_details.server.id, 'vm_start', 0, vm_id=vm_details.id)
if request.json.get('action') == "restart":
#
vm.status_set(vm_id, 6)
# Создание задания
ControllerTasks(g.user.get('user_id')).create(
vm_details.datacenter.id, vm_details.server.id, 'vm_restart', 0, vm_id=vm_id)
if request.json.get('action') == "stop":
#
vm.status_set(vm_id, 3)
# Создание задания
ControllerTasks(g.user.get('user_id')).create(
vm_details.datacenter.id, vm_details.server.id, 'vm_stop', 0, vm_id=vm_id)
if request.json.get('action') == "delete":
# Обновляем статус "5" для правила
vm.set_status(vm_id, 5)
# Создание задания
models.Tasks.set_task(
vm_details.datacenter.id, vm_details.server.id, vm_details.user.id, 'vm_delete', 0, vm_id=vm_id)
return jsonify(status='ok')
@api_v2_vms.route('/<uuid:vm_id>', methods=['DELETE'])
@decorators.content_type
@decorators.auth_token
def vm_delete(vm_id):
"""Delete virtual machine
:param vm_id:
:return:
"""
vms = ControllerVMS(g.user.get('user_id'))
tasks = ControllerTasks(g.user.get('user_id'))
# check the user have a selected rule
# if user not have a container then redirect to the container list
if not vms.exists(vm_id):
return jsonify(status='error', message='not exists')
# get container details
vm_details = models.Vms.get_item(vm_id)
# Обновляем статус "5" для правила
vms.set_status(vm_id, 5)
# Создание задания
models.Tasks.set_task(
vm_details.datacenter.id, vm_details.server.id, vm_details.user.id, 'vm_delete', 0, vm_id=vm_id)
return jsonify(status=0, message='ok')

View file

@ -2,7 +2,7 @@
from uuid import uuid4
from flask import Blueprint, jsonify, request, g
from SWSCloudAPI.API.compute.v1.common import auth
from SWSCloudAPI.API import auth
from SWSCloudCore.controllers.datacenters import ControllerDataCenters
from SWSCloudCore.controllers.ips import ControllerIps
from SWSCloudCore.controllers.users import ControllerUsers
@ -13,7 +13,7 @@ from SWSCloudCore.controllers.tasks import ControllerTasks
from SWSCloudCore.controllers.containers import (
ControllerContainers, ControllerContainersStatistics, ControllerContainersStatisticsState)
api_v1_containers = Blueprint('containers', __name__, url_prefix='/api/compute/v1/containers')
api_v1_containers = Blueprint('containers', __name__, url_prefix='/api/v1/containers')
@api_v1_containers.route('/', methods=['GET'])

View file

@ -0,0 +1,20 @@
# coding: utf-8
from flask import Blueprint, jsonify
from SWSCloudAPI.API import auth
from SWSCloudCore.controllers.datacenters import ControllerDataCenters
api_v1_datacenters = Blueprint('datacenters', __name__, url_prefix='/api/v1/datacenters')
@api_v1_datacenters.route('/', methods=['GET'])
@auth.login_required
def index():
"""
get containers list
curl -X http://localhost:5000/api/v1/datacenters/ -u <email>:<secret>
:return:
"""
dc_list = ControllerDataCenters().get()
return jsonify(total=dc_list.get('total'), items=dc_list.get('items'))

View file

@ -1,13 +1,13 @@
# coding: utf-8
from flask import Blueprint, jsonify
from SWSCloudAPI.API.compute.v1.common import auth
from SWSCloudAPI.API import auth
from SWSCloudCore.controllers.plans import ControllerPlans
api_v1_pricing = Blueprint('pricing', __name__, url_prefix='/api/compute/v1/pricing')
api_v1_pricing = Blueprint('pricing', __name__, url_prefix='/api/v1/pricing')
@api_v1_pricing.route('/vms')
@api_v1_pricing.route('/pricing/vms/')
@auth.login_required
def pricing_vms():
"""
@ -18,7 +18,7 @@ def pricing_vms():
return jsonify(pricing=ControllerPlans().get_plans(status='active'))
@api_v1_pricing.route('/containers')
@api_v1_pricing.route('/pricing/containers/')
@auth.login_required
def pricing_containers():
"""

View file

@ -2,7 +2,7 @@
from uuid import uuid4
from flask import Blueprint, jsonify, request, g
from SWSCloudAPI.API.compute.v1.common import auth
from SWSCloudAPI.API import auth
from SWSCloudCore.controllers.ips import ControllerIps
from SWSCloudCore.controllers.plans import ControllerPlans
from SWSCloudCore.controllers.users import ControllerUsers
@ -12,7 +12,7 @@ from SWSCloudCore.controllers.common import ControllerCommon, ControllerMessages
from SWSCloudCore.controllers.tasks import ControllerTasks
from SWSCloudCore.controllers.vms import ControllerVMS
api_v1_vms = Blueprint('vms', __name__, url_prefix='/api/compute/v1/vms')
api_v1_vms = Blueprint('vms', __name__, url_prefix='/api/v1/vms')
@api_v1_vms.route('/', methods=['GET'])

View file

@ -1,102 +0,0 @@
# Compute API v1
...
# Compute API v2
## Auth
Create new Token
$ curl -X POST http://localhost:5001/api/auth/v2/token \
-d '{"email":"vanzhiganov@ya.ru","password":"qwepoi123"}' \
-H "Content-Type: application/json"
{
"payload": {
"token": true
},
"status": "ok"
}
Get token data
curl -X GET http://localhost:5001/api/auth/v2/token \
-H 'X-Auth-Token: e7a49627-1199-4c3a-a7d3-b482c00fa503' \
-H "Content-Type: application/json"
{
"payload": {
"email": "vanzhiganov@ya.ru",
"user_id": "1f3936af-be4c-43d5-b352-2f9c88c56857"
},
"status": "ok"
}
Delete token
curl -X DELETE http://localhost:5001/api/auth/v2/token \
-H 'X-Auth-Token: e7a49627-1199-4c3a-a7d3-b482c00fa503' \
-H "Content-Type: application/json"
{
"status": "ok"
}
## Data Centers
Get list data centers
curl -X GET http://localhost:5001/api/compute/v2/datacenters/ \
-H "X-Auth-Token: ef381b13-425d-4635-9d8a-bba91910dd26" \
-H "Content-Type: application/json"
{
"payload": [
{
"city": "moscow",
"country": "russia",
"id": "531511b2-dcf5-11e6-9175-a315ee8fdabf",
"name": "moscow 1"
}
],
"status": "ok",
"total": 1
}
## Pricing
curl -X GET http://localhost:5001/api/compute/v2/pricing/ \
-H "X-Auth-Token: ef381b13-425d-4635-9d8a-bba91910dd26" \
-H "Content-Type: application/json"
{
"payload": [],
"status": "ok"
}
## Virtual machines
Create new virtual achine
curl -X POST http://localhost:5001/api/compute/v2/vms/ \
-H "X-Auth-Token: ef381b13-425d-4635-9d8a-bba91910dd26" \
-H "Content-Type: application/json" '
-d '{"plan": "", "datacenter": "", }'
Errors
{
"message": "no money",
"status": "error"
}
Get list virtual achines
curl -X GET http://localhost:5001/api/compute/v2/vms/ \
-H "X-Auth-Token: ef381b13-425d-4635-9d8a-bba91910dd26" \
-H "Content-Type: application/json"
{
"payload": [],
"status": "ok"
}

View file

@ -1 +0,0 @@
from .tokens import Tokens

View file

@ -1,48 +0,0 @@
from functools import wraps
import validators
from flask import g, request, jsonify
from SWSCloudAPI.Utils import Tokens
from SWSCloudCore.models import Users
def login_required(f):
@wraps(f)
def decorated_function(*args, **kwargs):
# validate email
if not validators.email(request.json.get('email')):
return jsonify(status='error', message='invalid email format')
# verify email/password
if not Users.auth(request.json.get('email'), request.json.get('password').encode()):
return jsonify(status='error', message='invalid auth')
#
return f(*args, **kwargs)
return decorated_function
def content_type(f):
@wraps(f)
def decorated_function(*args, **kwargs):
if request.headers.get('Content-Type') != 'application/json':
return jsonify(status='error', message='content-type must be application/json')
return f(*args, **kwargs)
return decorated_function
def auth_token(f):
@wraps(f)
def decorated_function(*args, **kwargs):
#
g.auth_token = request.headers.get('X-Auth-Token', None)
#
if not g.auth_token:
return jsonify(status='error', message='X-Auth-Token not specified')
# check exists token
if not Tokens.exists(g.auth_token):
return jsonify(status='error', message='token not exists')
#
g.tokens = Tokens()
g.user = g.tokens.get(g.auth_token)
return f(*args, **kwargs)
return decorated_function

View file

@ -1,5 +0,0 @@
from SWSCloudCore.config import config
from redis import StrictRedis
rediskc = StrictRedis(config.get('Redis', 'host'), config.get('Redis', 'port'), config.get('Redis', 'db'))

View file

@ -1,74 +0,0 @@
import json
import uuid
from flask import g
from SWSCloudAPI.API.compute.v2.common import TOKEN_PREFIX, TOKEN_TTL
class Tokens(object):
def set(self, payload, ttl=1800):
"""Create new token
Example
>>> Tokens().set({'user_id': 1, 'email': 'user@email.com'}, 1800)
:param payload: dict
:param ttl: int
:return: boolean
"""
token = self.generate_id()
g.redis_connect.set(TOKEN_PREFIX + token, json.dumps(payload), ttl)
return token
@staticmethod
def get(token, ttl=None):
"""Get token data
>>> Tokens.get('d0dd3bfa-e0f2-11e6-a2ce-17cec9ffa761')
>>> Tokens.get('d0dd3bfa-e0f2-11e6-a2ce-17cec9ffa761', 1800)
"""
if ttl and type(ttl) == int:
g.redis_connect.expire(TOKEN_PREFIX + token, ttl)
return json.loads(g.redis_connect.get(TOKEN_PREFIX + token))
@staticmethod
def expire(token, ttl=1800):
"""
>>> Tokens.expire('d0dd3bfa-e0f2-11e6-a2ce-17cec9ffa761', 1800)
:param token: uuid
:param ttl: int
:return: boolean
"""
g.redis_connect.expire(TOKEN_PREFIX + token, ttl)
return True
@staticmethod
def exists(token):
"""Check exists token in redis
:param token: uuid
:return: boolean
"""
if g.redis_connect.exists(TOKEN_PREFIX + token):
return True
return False
@staticmethod
def delete(token):
try:
g.redis_connect.delete(TOKEN_PREFIX + token)
except:
return False
else:
return True
@staticmethod
def generate_id():
"""Generate UUID strinfg
>>> Tokens.generate_id()
:return: str
"""
return str(uuid.uuid4())

View file

@ -1,61 +1,55 @@
# coding: utf-8
from flask import Flask, g, jsonify
from SWSCloudAPI.API import api
from SWSCloudAPI.API.vms import api_v1_vms
from SWSCloudAPI.API.containers import api_v1_containers
from SWSCloudAPI.API.datacenters import api_v1_datacenters
from SWSCloudAPI.API.pricing import api_v1_pricing
from SWSCloudCore import models
from SWSCloudCore.config import config
from SWSCloudCore.models import database
# v1
from SWSCloudAPI.API.compute.v1.views import *
# v2
from SWSCloudAPI.API.auth.v2 import *
from SWSCloudAPI.API.compute.v2 import *
#
from SWSCloudAPI.Utils.redisc import rediskc
from SWSCloudCore.config import config
app = Flask(__name__)
# Думал, что получится сделать автопрефик для всего приложения, но это не сработало
# app.config["APPLICATION_ROOT"] = "/api/v1"
# app.config['SERVER_NAME'] = settings.get('Application', 'SERVER_NAME')
app.config['DEBUG'] = config.getboolean('Application', 'DEBUG')
app.config['SECRET_KEY'] = config.get("Application", "SECRET_KEY")
# V1
app.register_blueprint(api)
app.register_blueprint(api_v1_vms)
app.register_blueprint(api_v1_containers)
app.register_blueprint(api_v1_pricing)
app.register_blueprint(api_v1_datacenters)
# V2
app.register_blueprint(api_v2_token)
app.register_blueprint(api_v2_pricing)
app.register_blueprint(api_v2_datacenters)
app.register_blueprint(api_v2_vms)
@app.errorhandler(404)
def page_not_found(e):
app.logger.error(e)
return jsonify(status='error', message='resource not found')
return jsonify(dict(message='resource not found')), 404
@app.errorhandler(403)
def access_deny(e):
app.logger.error(e)
return jsonify(status='error', message='access deny'), 403
return jsonify(dict(message='access deny')), 403
@app.errorhandler(410)
def page_not_found(e):
app.logger.error(e)
return jsonify({})
return jsonify(dict()), 410
@app.errorhandler(500)
def page_not_found(e):
app.logger.error(e)
return jsonify(status='maintenance')
return jsonify(dict(message='maintenance')), 500
@app.before_request
def before_request():
g.redis_connect = rediskc
g.settings = dict()
# извлекаем настройки и определяем их в глобальную переменную
for setting in models.Settings.select(models.Settings.key, models.Settings.val).execute():

View file

@ -21,38 +21,35 @@ from SWSCloudCore import models
viewAdministrator = Blueprint('administrator', __name__, url_prefix='/administrator')
@viewAdministrator.route('/login.html', methods=['GET'])
@viewAdministrator.route('/login.html', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
admin_email = request.form.get('email').encode('utf-8')
admin_password = request.form.get('password').encode('utf-8')
# validation entered data
if not validators.email(admin_email):
flash('Invalid registration data')
return redirect(url_for('administrator.login'))
# try auth only active users (with status code 1)
if models.Admins.auth(admin_email, admin_password, 1):
# get user_id
user_id = ControllerAdministrators().get_id_by_email(admin_email)
# save user data to session
session['admin_id'] = str(user_id)
session['admin_email'] = admin_email
session['admin_password'] = admin_password
# redirect to rules list
return redirect(url_for('administrator.dashboard'))
else:
flash('Invalid login. Please try again.')
return redirect(url_for('administrator.login'))
return render_template('administrator/login.html')
@viewAdministrator.route('/login.html', methods=['POST'])
def login_post():
admin_email = request.form.get('email').encode('utf-8')
admin_password = request.form.get('password').encode('utf-8')
# validation entered data
if not validators.email(admin_email):
flash('Invalid registration data')
return redirect(url_for('administrator.login'))
# try auth only active users (with status code 1)
if not models.Admins.auth(admin_email, admin_password, 1):
flash('Invalid login. Please try again.')
return redirect(url_for('administrator.login'))
# get user_id
user_id = ControllerAdministrators().get_id_by_email(admin_email)
# save user data to session
session['admin_id'] = str(user_id)
session['admin_email'] = admin_email
session['admin_password'] = admin_password
# redirect to rules list
return redirect(url_for('administrator.dashboard'))
@viewAdministrator.route('/logout.html')
def logout():
session.pop('admin_id', None)
@ -167,7 +164,7 @@ def ips_index():
def ips_create():
#
if request.method == "POST":
print(request.form)
print request.form
if ControllerManageIPs().is_valid_ipv4_address(request.form['ipv4'])\
and ControllerManageIPs().is_valid_ipv4_address(request.form['ipv4_gateway']):
ControllerManageIPs().item_create(
@ -203,7 +200,7 @@ def ips_create():
def ips_edit(ip_id):
#
if request.method == 'POST':
print(request.form)
print request.form
# if ControllerManageIPs().is_valid_ipv4_address(request.form['ipv4'])\
# and ControllerManageIPs().is_valid_ipv4_address(request.form['ipv4_gateway']):
ControllerManageIPs().item_update(
@ -235,7 +232,7 @@ def ips_delete():
@requires_login
def servers_create():
if request.method == "POST":
print(request.form)
print request.form
params = {
'datacenter_id': request.form['datacenter_id'],
'server_id': uuid4(),

View file

@ -7,19 +7,15 @@ from SWSCloudCore.controllers.tasks import ControllerTasks
from SWSCloudCore import models
from SWSCloudAdministrator.Administrator.common import requires_login
view_administrator_compute_vms = Blueprint(
'administrator_compute_vms',
__name__,
url_prefix='/administrator/compute/vms')
view_administrator_compute_vms = Blueprint('administrator_compute_vms', __name__, url_prefix='/administrator/compute/vms')
@view_administrator_compute_vms.route('/', methods=['GET'])
@requires_login
def index():
"""Virtual Machines list"""
template = 'administrator/compute/vms/index.html'
items = models.Vms.get_items()
return render_template(template, vms=items)
# формируем список правил
return render_template(
'administrator/compute/vms/index.html', vms=models.Vms.get_items())
@view_administrator_compute_vms.route('/<uuid:vm_id>', methods=['GET'])

View file

@ -1,14 +1,16 @@
"""Stack Web Services LLC"""
# coding: utf-8
import validators
from flask import Blueprint, redirect, render_template, request, url_for
# from uuid import uuid4
# import validators
from flask import Blueprint, flash, g, jsonify, redirect, render_template, request, session, url_for
from SWSCloudCore.controllers.administrators import ControllerAdministrators
from SWSCloudCore.controllers.tasks.manage import ControllerManageTasks
from SWSCloudAdministrator.Administrator.common import requires_login
from SWSCloudCore import models
view_administrator_tasks = Blueprint(
'administrator_tasks', __name__,
url_prefix='/administrator/tasks')
view_administrator_tasks = Blueprint('administrator_tasks', __name__, url_prefix='/administrator/tasks')
@view_administrator_tasks.route('/', methods=['GET'])
@ -23,29 +25,23 @@ def index():
@view_administrator_tasks.route('/edit.html', methods=['GET'])
@requires_login
def edit():
task_id = request.form.get('task_id')
if not validators.uuid(task_id) or not models.Tasks.exists(task_id):
return redirect(url_for('administrator_tasks.index'))
task = ControllerManageTasks().get_task(task_id)
task_id = request.args.get('task_id')
# TODO: check exists
return render_template(
'administrator/tasks/edit.html',
task=task)
task=ControllerManageTasks().get_task(task_id))
@view_administrator_tasks.route('/edit.html', methods=['POST'])
@requires_login
def edit_post():
task_id = request.form.get('task_id')
# todo: validate
status = request.form.get('status')
if not validators.uuid(task_id) or not models.Tasks.exists(task_id):
return redirect(url_for('administrator_tasks.index'))
task = models.Tasks.update(
status=status
).where(models.Tasks.id == task_id)
task.execute()
# TODO: check exists
x = models.Tasks.update(status=request.form.get('status')).where(models.Tasks.id == task_id)
x.execute()
return redirect(url_for('administrator_tasks.edit', task_id=task_id))
@ -55,13 +51,11 @@ def edit_post():
def delete():
task_id = request.args.get('task_id')
if not validators.uuid(task_id) or not models.Tasks.exists(task_id):
return redirect(url_for('administrator_tasks.index'))
# TODO: check exists
task = ControllerManageTasks().get_task(task_id)
return render_template(
'administrator/tasks/delete.html',
task=task
task=ControllerManageTasks().get_task(task_id)
)
@ -70,10 +64,8 @@ def delete():
def delete_post():
task_id = request.form.get('task_id')
if not validators.uuid(task_id) or not models.Tasks.exists(task_id):
return redirect(url_for('administrator_tasks.index'))
task = models.Tasks.delete().where(models.Tasks.id == task_id)
task.execute()
# TODO: check exists
x = models.Tasks.delete().where(models.Tasks.id == task_id)
x.execute()
return redirect(url_for('administrator_tasks.index'))

View file

@ -43,7 +43,7 @@ app.register_blueprint(view_administrator_compute_containers)
# @app.errorhandler(500)
# def page_not_found(e):
# print(e)
# print e
# return render_template('errors/500.html'), 500
@ -51,11 +51,7 @@ app.register_blueprint(view_administrator_compute_containers)
def before_request():
g.settings = dict()
# извлекаем настройки и определяем их в глобальную переменную
settings = models.Settings.select(
models.Settings.key,
models.Settings.val
).execute()
for setting in settings:
for setting in models.Settings.select(models.Settings.key, models.Settings.val).execute():
g.settings[setting.key] = setting.val

View file

@ -14,8 +14,8 @@
</div>
{% endif %}
{% endwith %}
<form action="{{ url_for('administrator.login_post') }}" method="post">
<label for="email">email</label>
<form action="{{ url_for('administrator.login') }}" method="post">
<label for="email">email</lable>
<input type="text" name="email" value="" id="email" />
<label for="password">password</label>
<input type="password" name="password" value="" id="password" />

View file

@ -1,11 +1,13 @@
# coding: utf-8
import sys
import os
from configparser import ConfigParser
import ConfigParser
__config_file__ = os.getenv('CONFIG', '/etc/sws/cloud/core.ini')
__config_file__ = os.getenv('CLOUD_CONFIG_FILE', '/etc/sws/cloud/core.ini')
# setting file read
config = ConfigParser()
config = ConfigParser.ConfigParser()
if os.path.exists(__config_file__):
config.read(__config_file__)

View file

@ -222,7 +222,7 @@ class Datacenters:
"status": i.status
}
dcs['items'].append(dc)
# print(i)
# print i
return dcs

View file

@ -23,7 +23,7 @@ class ControllerContainers:
)
except Exception as e:
# TODO: write to log
print(e)
print e
return False
return True

View file

@ -36,7 +36,7 @@ class ControllerUsers:
def update(self, user_id, **kwargs):
if 'password' in kwargs:
x = models.Users.update(
password=md5(kwargs['password'].encode()).hexdigest()
password=md5(kwargs['password']).hexdigest()
).where(
models.Users.id == user_id
)
@ -69,7 +69,7 @@ class ControllerUsers:
:param password:
:return:
"""
password_hash = md5(password.encode()).hexdigest()
password_hash = md5(password).hexdigest()
result = models.Users.select().where(
models.Users.email == email,
models.Users.password == password_hash,
@ -86,7 +86,7 @@ class ControllerUsers:
:param password:
:return:
"""
password_hash = md5(password.encode()).hexdigest()
password_hash = md5(password).hexdigest()
user_id = uuid.uuid4()
# TODO: add date registration and update of date - =datetime.datetime.now()
user = models.Users.create(id=user_id, email=email, password=password_hash, status=1)
@ -95,7 +95,7 @@ class ControllerUsers:
return False
def password_update(self, user_id, password):
password_hash = md5(password.encode()).hexdigest()
password_hash = md5(password).hexdigest()
u = models.Users.get(models.Users.id == user_id)
u.password = password_hash
u.save()

View file

@ -35,7 +35,7 @@ class ControllerManageUsers:
models.UsersBalance
)
for j in jj:
print(j.usersbalance.balance)
print j.usersbalance.balance
return {
'total': len(jj),
'items': jj

View file

@ -1,6 +1,5 @@
# coding: utf-8
import json
from hashlib import md5
import datetime
import uuid
@ -43,16 +42,6 @@ class DataCenters(PgSQLModel):
city = CharField()
status = IntegerField(default=0)
@staticmethod
def get_available():
return DataCenters.select().where(
DataCenters.status == 1 and
DataCenters.id << Servers.select(Servers.datacenter).where(
Servers.status == 1 and
Servers.id << Ips.select(Ips.server).where(
Ips.status == 0).group_by(Ips.server)
).group_by(Servers.datacenter)).execute()
class Servers(PgSQLModel):
id = UUIDField(unique=True, primary_key=True)
@ -121,9 +110,7 @@ class Users(PgSQLModel):
@staticmethod
def auth(email, password, status=1):
if Users.select().where(
Users.email == email,
Users.password == Users.hash_password(password),
Users.status == status
Users.email == email, Users.password == Users.hash_password(password), Users.status == status
).count() == 0:
return False
return True
@ -134,11 +121,7 @@ class Users(PgSQLModel):
@staticmethod
def get_by_id(user_id):
return Users.select().where(Users.id == user_id).first()
@staticmethod
def get_by_email(email):
return Users.select().where(Users.email == email).first()
return Users.select().where(Users.id == user_id).get()
class UsersRecoveryCodes(PgSQLModel):
@ -264,14 +247,6 @@ class Vms(PgSQLModel):
def get_items():
return Vms.select()
@staticmethod
def get_user_items(user_id):
items = list()
for x in Vms.select().where(Vms.user == user_id):
items.append(dict(id=str(x.id), datacenter=str(x.datacenter.id), plan=x.plan.id, hostname=x.hostname,
osname=x.os_name, ossuite=x.os_suite, ipv4=x.ipv4, ipv6=x.ipv6, status=x.status))
return items
@staticmethod
def get_item(vm_id):
return Vms.select().where(Vms.id == vm_id).first()
@ -323,27 +298,22 @@ class Tasks(PgSQLModel):
Tasks.select().where(Tasks.status == status).count()
return Tasks.select().count()
@staticmethod
def set_task(datacenter_id, server_id, user_id, task, status, **args):
task_id = uuid.uuid4()
plain = dict()
for arg in args:
plain[arg] = str(args[arg])
Tasks.create(
id=task_id,
datacenter=datacenter_id,
server=server_id,
task=task,
status=status,
user=user_id,
plain=json.dumps(plain))
@staticmethod
def exists(task_id):
if Tasks.select().where(Tasks.id == task_id).count() == 0:
return False
return True
# @staticmethod
# def set_task(datacenter_id, server_id, task, status, **args):
# task_id = uuid.uuid4()
# plain = dict()
# for arg in args:
# plain[arg] = str(args[arg])
#
# Tasks.create(
# id=task_id,
# datacenter=datacenter_id,
# server=server_id,
# task=task,
# status=status,
# user=self.user_id,
# plain=json.dumps(plain)
# )
class Settings(PgSQLModel):

View file

@ -1,4 +1,4 @@
"""Stack Web Services LLC."""
# coding: utf-8
import json
from flask import Blueprint, jsonify, request, g

View file

@ -1,5 +1,5 @@
# coding: utf-8
import logging
import os
from flask import Flask, g, render_template
from flask_babel import Babel
@ -42,7 +42,7 @@ if app.config.get('THEME_STATIC_FOLDER', False):
static_folder = app.config['THEME_STATIC_FOLDER']
if static_folder[0] != '/':
static_folder = os.path.join(app.root_path, 'static', static_folder)
# print(static_folder)
print static_folder
# Unlike templates, to serve static files from multiples folders we
# need flask-multistatic
# app.static_folder = [static_folder, os.path.join(app.root_path, 'static')]
@ -90,20 +90,17 @@ def page_not_found(e):
@app.errorhandler(500)
def page_not_found(e):
print(e)
print e
return render_template('errors/500.html'), 500
@app.before_request
def before_request():
g.settings = dict()
try:
# извлекаем настройки и определяем их в глобальную переменную
for setting in models.Settings.select(models.Settings.key, models.Settings.val).execute():
g.settings[setting.key] = setting.val
except Exception as e:
logging.error(e)
# exit(1)
# извлекаем настройки и определяем их в глобальную переменную
for setting in models.Settings.select(models.Settings.key, models.Settings.val).execute():
g.settings[setting.key] = setting.val
@app.before_first_request
def before_first_request():

View file

@ -16,7 +16,7 @@ def requires_login(f):
else:
return redirect(url_for("account.logout"))
if not Users.auth(session.get('email'), session.get('password').encode(), 1):
if not Users.auth(session.get('email'), session.get('password'), 1):
return redirect(url_for("account.logout"))
return f(*args, **kwargs)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

View file

@ -83,5 +83,6 @@
</div>
{% endblock %}
{% block footer %}
{% endblock %}

View file

@ -5,7 +5,7 @@
{% block content %}
<div class="row">
<div class="large-12 columns">
<h2>{{ _('Вход') }}</h2>
<h3>{{ _('Вход') }}</h3>
</div>
</div>
<div class="row">

View file

@ -8,12 +8,18 @@
<h2>Регистрация</h2>
<form action="{{ url_for('account.registration') }}" method="post">
<input type="hidden" name="method" value="member_add" />
<label for="email">Адрес электронной почты</label>
<input type="text" name="email" value="" class="email" id="email" />
<label for="password">Пароль</label>
<input type="password" name="password" value="" id="password" />
<label for="password2">Пароль (повторно)</label>
<input type="password" name="password2" value="" id="password2" />
<label for="email">
Адрес ел. почты
<input type="text" name="email" value="" class="email" id="email" />
</label>
<label for="password">
Пароль
<input type="password" name="password" value="" id="password" />
</label>
<label for="password2">
Пароль (повторно)
<input type="password" name="password2" value="" id="password2" />
</label>
<input type="submit" value="Зарегистрироваться" class="button success" />
</form>
</div>

View file

@ -0,0 +1,11 @@
{% extends "default/_layout.html" %}
{% block title %}База знаний{% endblock %}
{% block content %}
<div class="row">
<div class="large-12 columns">
{% filter markdown %}{{ kb_markdown }}{% endfilter %}
</div>
</div>
{% endblock %}

View file

@ -4,8 +4,7 @@
<meta charset="utf-8" />
<meta http-equiv="x-ua-compatible" content="ie=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}Облачный хостинг GoCloud{% endblock %}</title>
<meta name="description" content="{% block description %}GoCloud предлагает вам арендовать виртуальные серверные мощности{% endblock %}">
<title>Облачный хостинг GoCloud</title>
<link rel="stylesheet" href="{{ url_for('static', filename='assets/css/app.css') }}">
</head>
<body>

View file

@ -7,7 +7,7 @@
<h1>Облачный хостинг</h1>
<h2>Виртуальный сервер от 200 рублей в месяц</h2>
<h2>
<a href="{{ url_for('account.registration') }}" class="button alert">Создать виртуальный сервер</a>
<a href="{{ url_for('account.registration') }}" class="button success">Создать виртуальный сервер</a>
</h2>
</div>
</div>

View file

@ -1,9 +1,5 @@
{% extends "gocloud2016/layouts/default.html" %}
{% block title %}Стоимость аренды виртуального сервера - GoCloud{% endblock %}
{% block description %}Расценки на стоимость аренды виртуальных серверов различной конфигурации{% endblock %}
{% block content %}
<div class="row">
<div class="large-12 columns">

View file

@ -1,30 +0,0 @@
{% extends "gocloud2016/layouts/default.html" %}
{% block title %}Поддержка онлайн виртуального сервера - GoCloud{% endblock %}
{% block description %}Наши технические специалисты ответят на ваши вопросы{% endblock %}
{% block content %}
<div class="row">
<div class="large-12 columns">
<h2>Поддержка</h2>
</div>
</div>
<div class="row">
<div class="large-12 columns">
<form action="{{ url_for('support.index_post') }}" method="post">
{% if not session['email'] %}
<label for="email">Адрес электронной почты</label>
<input type="text" name="email" id="email" />
{% else %}
<input type="hidden" name="email" value="{{ session['email'] }}" />
{% endif %}
<label for="subject">Заголовок</label>
<input type="text" name="subject" id="subject" />
<label for="message">Сообщение</label>
<textarea name="message" id="message" rows="7"></textarea>
<input type="submit" class="button success large" value="Отправить сообщение" />
</form>
</div>
</div>
{% endblock %}

View file

@ -1,21 +0,0 @@
{% extends "gocloud2016/layouts/default.html" %}
{% block title %}Поддержка онлайн виртуального сервера - GoCloud{% endblock %}
{% block description %}Наши технические специалисты ответят на ваши вопросы{% endblock %}
{% block content %}
<div class="row">
<div class="large-12 columns">
<h2>Поддержка</h2>
</div>
</div>
<div class="row">
<div class="large-12 columns">
<h3>Спасибо!</h3>
</div>
<div class="large-12 columns">
<p>Спасибо за обращение в техническую поддержку. Мы ответим вам в кратчайшее время.</p>
</div>
</div>
{% endblock %}

View file

@ -114,15 +114,14 @@ def registration():
user_id = ControllerUsers().get_id_by_email(email)
ControllerUsersDetails(user_id).details_create()
ControllerBilling().create(user_id, g.settings.get('bonus', 0))
ControllerBilling().create(user_id, g.settings['bonus'])
ControllerAPI().set(user_id=user_id, secret=user_id, acl='', status=0)
# ControllerU
# send mail message with recovery code
message = u"""
Е-почта: %s
Пароль: %s
""" % (request.form.get('email'), request.form.get('password'))
""" % (request.form['email'], request.form['password'])
subject = u'GoCloud.ru: Успешная регистрация'
lead = u"""
Поздравляем с успешной регистрацией.
@ -131,12 +130,10 @@ def registration():
Для входа в личный кабинет воспользуйтесь
<a href="https://gocloud.ru/account/login">страницей авторизации</a>.
"""
try:
email = ControllerMessagesEmail()
email.send(title=subject, to=request.form['email'], lead=lead, message=message, callout=callout)
except Exception as e:
# TODO: write message
pass
email = ControllerMessagesEmail()
email.send(title=subject, to=request.form['email'], lead=lead, message=message, callout=callout)
# redirect to login page
flash(u'Учетная запись успешно зарегистрирована.', 'success')
return redirect(url_for('account.login'))

View file

@ -1,38 +1,42 @@
# coding: utf-8
import validators
from flask import Blueprint, g, redirect, render_template, request, url_for, flash
from SWSCloudCore.controllers.common import ControllerMessagesEmail
from flask import Blueprint
from flask import g
from flask import redirect
from flask import render_template
from flask import request
from flask import url_for
viewSupport = Blueprint('support', __name__, url_prefix='/support')
@viewSupport.route('/', methods=['GET'])
@viewSupport.route('/', methods=['GET', 'POST'])
def index():
return render_template('gocloud2016/pages/support/index.html')
# ControllerMessagesEmail().send()
# print session
if request.method == "POST":
print request.form
# TODO: validate
ticket_title = request.form['title']
ticket_message = request.form['message']
ticket_email = request.form['email']
# send mail message with recovery code
subject = ticket_title
message = ticket_message
lead = u'Отправитель: %s' % ticket_email
callout = u''
email = ControllerMessagesEmail()
# email.send(title=subject, to=ticket_email, lead=lead, message=message, callout=callout)
email.send(title=subject, to=g.settings['contacts.email'], lead=lead, message=message, callout=callout)
return redirect(url_for('support.thank'))
return render_template('default/support/index.html')
@viewSupport.route('/', methods=['POST'])
def index_post():
ticket_subject = request.form.get('subject')
ticket_message = request.form.get('message')
ticket_email = request.form.get('email')
if not validators.email(ticket_email):
flash('Некорректный адрес электронной почты', category='error')
return redirect(url_for('support.index'))
# send mail message with recovery code
lead = u'Отправитель: %s' % ticket_email
callout = u''
email = ControllerMessagesEmail()
# email.send(title=subject, to=ticket_email, lead=lead, message=message, callout=callout)
email.send(
title=ticket_subject, to=g.settings.get('contacts.email'), lead=lead, message=ticket_message, callout=callout)
return redirect(url_for('support.thank'))
@viewSupport.route('/thank.html')
@viewSupport.route('/thank')
def thank():
return render_template('gocloud2016/pages/support/thanks.html')
return render_template('default/support/thank.html')

View file

@ -23,7 +23,7 @@ def view(document_name):
if not os.path.exists('docs/%s.md' % document_name):
return redirect(url_for('homepage.index'))
#
print(document_name)
print document_name
doc_markdown = u''
for ss in file('docs/%s.md' % document_name, 'r'):
doc_markdown += ss.decode('UTF-8')

View file

@ -2,13 +2,8 @@ import requests
class SWSStatisticsClient(object):
def get_vm(self, vm_id: str, limit: int = 96):
r = requests.get(
f"http://server-stats.gocloud.ru/stats/v1/compute/vms/{vm_id}",
params={
'limit': limit
}
)
def get_vm(self, vm_id, limit=96):
r = requests.get('http://server-stats.gocloud.ru/stats/v1/compute/vms/%s' % vm_id, params={'limit': limit})
if r.status_code == 200:
return r.json()
return None

5
cloud-admin-add.py Executable file → Normal file
View file

@ -1,7 +1,6 @@
#!/usr/bin/env python
# coding: utf-8
import sys
import argparse
import validators
from hashlib import md5
@ -26,13 +25,13 @@ if not validators.email(args.email):
admin_id = uuid4()
admin_email = args.email
admin_password = md5(args.password.encode()).hexdigest()
admin_password = md5(args.password).hexdigest()
models.database.connect()
if models.Admins.select().where(models.Admins.email == args.email).count() == 0:
models.Admins.create(id=admin_id, email=admin_email, password=admin_password, status=1)
else:
print("already exists")
print "already exists"
models.database.close()

12
cloud-admin-ls.py Executable file → Normal file
View file

@ -9,13 +9,13 @@ __author__ = 'vanzhiganov'
admins_total = models.Admins.select().count()
admins_items = models.Admins.select()
print("Total admins: %i" % admins_total)
print "Total admins: %i" % admins_total
if admins_total == 0:
print('')
print ''
else:
print('List:')
print 'List:'
for item in admins_items:
print('%s\t%s\t%s' % (item.id, item.email, item.status))
print('---')
print('For create a new admin account use command "procdn-admin-add --email <email> --password <password>"')
print '%s\t%s\t%s' % (item.id, item.email, item.status)
print '---'
print 'For create a new admin account use command "procdn-admin-add --email <email> --password <password>"'

10
cloud-admin-password.py Executable file → Normal file
View file

@ -18,13 +18,13 @@ args = parser.parse_args()
admin_id = uuid4()
# todo: validate admin email
admin_email = args.email
admin_password = md5(args.password.encode()).hexdigest()
admin_password = md5(args.password).hexdigest()
if models.Admins.select().where(models.Admins.email == args.email).count() == 0:
print("Admin account with email '%s' not exists." % admin_email)
print('---')
print('For create a new admin account use command "procdn-admin-add --email <email> --password <password>"')
print "Admin account with email '%s' not exists." % admin_email
print '---'
print 'For create a new admin account use command "procdn-admin-add --email <email> --password <password>"'
else:
query = models.Admins.update(password=admin_password).where(models.Admins.email == admin_email)
query.execute()
print('Password updated.')
print 'Password updated.'

10
cloud-cron-balance.py Executable file → Normal file
View file

@ -8,13 +8,13 @@ from SWSCloudCore.controllers.vms import ControllerVMS
nb = models.Settings.get_item('NEGATIVE_BALANCE')
if int(models.Settings.get_item('SERVICE_VMS_ENABLE')) == 1:
print('total vms:', models.Vms.select().where(models.Vms.status == 1).count())
print 'total vms:', models.Vms.select().where(models.Vms.status == 1).count()
for vm in models.Vms.select().where(models.Vms.status == 1):
# Высчитываем, сколько стоит виртуальная машина 15 минут
price_quarter = vm.plan.price / 30 / 24 / 4
print(dict(id=vm.id, user=vm.user.id, plan=vm.plan.id, cost_mo=vm.plan.price, cost_quarter=price_quarter))
print dict(id=vm.id, user=vm.user.id, plan=vm.plan.id, cost_mo=vm.plan.price, cost_quarter=price_quarter)
# Списание средств
x = models.UsersBalance.update(
@ -26,14 +26,14 @@ if int(models.Settings.get_item('SERVICE_VMS_ENABLE')) == 1:
user_balance = models.UsersBalance.select(
models.UsersBalance.balance).where(models.UsersBalance.user == vm.user.id).get().balance
if -500 > user_balance and models.Vms.get_state(vm.id) == 1:
print("user_balance", user_balance)
print "user_balance", user_balance
ControllerVMS(vm.user.id).status_set(vm.id, 3)
# Создание задания
ControllerTasks(vm.user.id).create(vm.datacenter.id, vm.server.id, 'vm_stop', 0, vm_id=vm.id)
if int(models.Settings.get_item('SERVICE_CONTAINERS_ENABLE')) == 1:
print('total containers:', models.Containers.select().count())
print 'total containers:', models.Containers.select().count()
for container in models.Containers.select():
container_state = models.ContainersStatisticsState.select().where(
models.ContainersStatisticsState.container == container.id
@ -43,7 +43,7 @@ if int(models.Settings.get_item('SERVICE_CONTAINERS_ENABLE')) == 1:
# # Высчитываем, сколько стоит виртуальная машина 15 минут
price_quarter = min_price / 30 / 24 / 4
#
# print(dict(id=vm.id, user=vm.user.id, plan=vm.plan.id, cost_mo=vm.plan.price, cost_quarter=price_quarter))
# print dict(id=vm.id, user=vm.user.id, plan=vm.plan.id, cost_mo=vm.plan.price, cost_quarter=price_quarter)
#
# Списание средств
x = models.UsersBalance.update(

0
cloud-db-init.py Executable file → Normal file
View file

2
cloud-dc-add.py Executable file → Normal file
View file

@ -28,4 +28,4 @@ if models.DataCenters.select().where(
status=1
)
else:
print("Data center with code '%s' already exists." % args.code)
print "Data center with code '%s' already exists." % args.code

12
cloud-dc-ls.py Executable file → Normal file
View file

@ -25,13 +25,13 @@ x = models.Ips.select().join(models.Servers).where(
).first()
print('total: %s' % total)
print('items: ')
print 'total: %s' % total
print 'items: '
for item in items:
print('id: %s code: %s name: %s country: %s, city: %s, status: %s' % (
print 'id: %s code: %s name: %s country: %s, city: %s, status: %s' % (
item.id, item.code, item.name, item.country, item.city, item.status
))
)
print('---')
print('if you want to add a new data center, use cli-dc-add.py --')
print '---'
print 'if you want to add a new data center, use cli-dc-add.py --'

2
cloud-invoicer.py Executable file → Normal file
View file

@ -169,7 +169,7 @@ html = u"""
</html>
"""
print(type(html))
print type(html)
pdf = MyFPDF()
# First page
pdf.add_page()

0
cloud-invoicer2.py Executable file → Normal file
View file

4
cloud-ip-add.py Executable file → Normal file
View file

@ -3,7 +3,7 @@
import argparse
from SWSCloudCore import models
from SWSCloudCore.controllers.ips.manage import ControllerManageIPs
from SWSCloudCore.controllers import ControllerManageIPs
parser = argparse.ArgumentParser(description='')
parser.add_argument('--dc', dest="datacenter")
@ -30,5 +30,5 @@ if success:
args.datacenter, args.server, args.ipv4, args.ipv4_gateway, args.ipv6, args.ipv6_gateway, args.status
)
else:
print('fail')
print 'fail'
# print "Admin account with email '%s' already exists." % args

0
cloud-runserver-admin.py Executable file → Normal file
View file

1
cloud-runserver.py Executable file → Normal file
View file

@ -1,4 +1,5 @@
#!/usr/bin/env python
# coding: utf-8
from SWSCloudWeb import app

4
cloud-server-add.py Executable file → Normal file
View file

@ -2,7 +2,7 @@
import argparse
from uuid import uuid4
from SWSCloudCore.controllers.servers.manage import ControllerManageServer
from SWSCloudCore.controllers import ControllerManageServer
parser = argparse.ArgumentParser(description='')
parser.add_argument('--dc', dest="datacenter")
@ -21,5 +21,5 @@ if not ControllerManageServer().check_exists(args.ip, None, args.hostname):
args.ip, None, args.status
)
else:
print('fail')
print 'fail'
# print "Admin account with email '%s' already exists." % args

8
cloud-server-ls.py Executable file → Normal file
View file

@ -16,10 +16,10 @@ items = models.Servers.select().where(
models.Servers.status == args.status
)
print('total: %s' % total)
print('items: ')
print 'total: %s' % total
print 'items: '
for item in items:
print('id: %s dc: %s status: %s ip: %s hostname: %s' % (
print 'id: %s dc: %s status: %s ip: %s hostname: %s' % (
item.id, item.datacenter.id, item.status, item.ip, item.hostname
))
)

2
cloud-settings-init.py Executable file → Normal file
View file

@ -7,7 +7,7 @@ def create_key(key, val=''):
if models.Settings.select().where(models.Settings.key == key).count() == 0:
models.Settings.create(key=key, val=val)
else:
print('key %s already exists' % key)
print 'key %s already exists' % key
create_key('bonus', '300')

0
cloud-settings.py Executable file → Normal file
View file

View file

@ -1,9 +1,9 @@
[Database]
host = localhost
user = cherry
password = P@ss5476
user = postgres
password = postgres
port = 5432
name = testdb
name = gocloud
[Application]
DEBUG = true

View file

@ -1,23 +1,19 @@
[uwsgi]
env = HOME=/opt/nativecloud/.venv
env = CONFIG=/opt/nativecloud/extra/settings.origin.ini
chdir = /opt/nativecloud
virtualenv = /home/gocloud/env
;for http
protocol = http
socket = 0.0.0.0:8080
;protocol = http
;socket = 127.0.0.1:8080
; for unix-socket
; socket = /var/run/nativecloud-web.sock
; chmod-socket = 777
socket = /var/run/sws/gocloud_web.sock
chmod-socket = 777
module = SWSCloudWeb:app
master = true
processes = 2
vacuum = true
die-on-term = true
thunder-lock = true
enable-threads = true
vacuum = true
die-on-term = true

13
kb/README.md Normal file
View file

@ -0,0 +1,13 @@
# База знаний
## Обзие вопросы
- Как создать контейнер
- Как оплатить
-
## API
- Описание
- Запросы
- Коды ошибок

57
letter.pdf Normal file
View file

@ -0,0 +1,57 @@
%PDF-1.4
%“Œ‹ž ReportLab Generated PDF document http://www.reportlab.com
1 0 obj
<< /F1 2 0 R /F2 3 0 R /F3 4 0 R >>
endobj
2 0 obj
<< /BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font >>
endobj
3 0 obj
<< /BaseFont /ZapfDingbats /Encoding /ZapfDingbatsEncoding /Name /F2 /Subtype /Type1 /Type /Font >>
endobj
4 0 obj
<< /BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding /Name /F3 /Subtype /Type1 /Type /Font >>
endobj
5 0 obj
<< /Contents 9 0 R /MediaBox [ 0 0 612 792 ] /Parent 8 0 R /Resources << /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] >> /Rotate 0 /Trans << >>
/Type /Page >>
endobj
6 0 obj
<< /Outlines 10 0 R /PageMode /UseNone /Pages 8 0 R /Type /Catalog >>
endobj
7 0 obj
<< /Author (anonymous) /CreationDate (D:20161016043545-03'00') /Creator (ReportLab PDF Library - www.reportlab.com) /Keywords () /ModDate (D:20161016043545-03'00') /Producer (ReportLab PDF Library - www.reportlab.com)
/Subject (unspecified) /Title (untitled) /Trapped /False >>
endobj
8 0 obj
<< /Count 1 /Kids [ 5 0 R ] /Type /Pages >>
endobj
9 0 obj
<< /Filter [ /ASCII85Decode /FlateDecode ] /Length 735 >>
stream
GasJPgN)"%&;KYAnN1Qb2mh$p3r4rMVN;4S9M8:0o\'IG#dN4>AdE<^Be<hY@c;s8q=i@@FtJURd5T$fOTF5_E-4g)1.YG2KeFQoJ8+#&rd#f(=\4%YGebF-,<qiQl'Vh@an4dMP`TG>kQ+u/`Zr=3r2g^b-k.lUmI^LCk74u@CK)JQ#\18LWr5OS(-LdAS.D-;?`\=9'WQ4E)2H=GYuQ&Q:IVk]/;C:l,6%eP0-JM^Zo*hl9jJh13EmLqHkqBD>Fd\H2g+u@9V&>(\Lr(ZQ1*g4Q1/*N?/uhi?OS@-82hCT,T;,0dWmd#HA`%oY>R#_4KDHdZUmAd&#4H%(AsJ1C4KcP[FDkE/;>k(8W,b"d_+GdLB1<BW$RY%:1"40!+_$"V>@5R;R"gGohPUPnc7,oXVAuZmRX:!mDlIV+%o0q+r6%6G_c\l:h4ZQ0a*n,/G=3b/IIli@D4@7"^rsL,8!"o'B7*N6I_.<76^h:?OIIk_oRsp`I=[aXl(P.[]4MPhpSe,6WhKr^h$#^6Q'EJ(Cf#9[g^o.O'pVP1<l/9g\KR2>FGms]h6\mnF++Nc#`c[5M^T0T1R[^UYMBR$o3nG#;>SA&Z)_+*ZRTi=A]Th,)r\&#?IWGdY82-R'\?<!NN$c<6bWHVXIL#)60S&<+<jAYeJb)DkoSHQqsV@:.N>%O@5Fq@s],dSsSJJS`X-9'K]D3V<LYJ[Au3rWiC:C+3rf(rW.VMNog~>endstream
endobj
10 0 obj
<< /Count 0 /Type /Outlines >>
endobj
xref
0 11
0000000000 65535 f
0000000075 00000 n
0000000129 00000 n
0000000239 00000 n
0000000357 00000 n
0000000472 00000 n
0000000669 00000 n
0000000757 00000 n
0000001057 00000 n
0000001119 00000 n
0000001949 00000 n
trailer
<< /ID
% ReportLab generated PDF document -- digest (http://www.reportlab.com)
[(F\377\262C\004ZI\203\233\321\244^\250\251\025\300) (F\377\262C\004ZI\203\233\321\244^\250\251\025\300)]
/Info 7 0 R /Root 6 0 R /Size 11 >>
startxref
1999
%%EOF

View file

@ -1,38 +0,0 @@
async-timeout==5.0.1
babel==2.16.0
certifi==2024.8.30
charset-normalizer==3.4.0
click==8.1.7
configparser==7.1.0
dateutils==0.6.12
decorator==5.1.1
ecdsa==0.19.0
Flask==0.12.5
Flask-Babel==0.9
Flask-HTTPAuth==3.1.1
Flask-Markdown==0.3
flask-multistatic==1.0
flask-peewee==0.6.0
idna==3.10
importlib_metadata==8.5.0
itsdangerous==2.0.1
Jinja2==2.8
Markdown==3.7
MarkupSafe==2.0.1
peewee==2.8.0
psycopg2-binary==2.9.10
pycrypto==2.6.1
python-dateutil==2.9.0.post0
pytz==2024.2
pyuwsgi==2.0.28.post1
redis==5.2.1
requests==2.32.3
six==1.17.0
speaklater==1.3
sshpubkeys==1.0.6
urllib3==2.2.3
validators==0.10
Werkzeug==0.16.1
wtf-peewee==0.2.6
WTForms==3.2.1
zipp==3.21.0

View file

@ -1,12 +1,10 @@
import os
from setuptools import setup
# coding: utf-8
with open('requirements.txt') as f:
required = f.read().splitlines()
from setuptools import setup
setup(
name='SWSCloudCore',
version='2.7.10',
version='2.7.9',
author='Vyacheslav Anzhiganov',
author_email='hello@anzhiganov.com',
packages=[
@ -81,6 +79,7 @@ setup(
'templates/default/documents/*.html',
'templates/default/homepage/*.html',
'templates/default/id/*.html',
'templates/default/kb/*.html',
'templates/default/payment/*.html',
'templates/default/payment/robokassa/*.html',
'templates/default/support/*.html',
@ -91,7 +90,6 @@ setup(
'templates/gocloud2016/layouts/*.html',
'templates/gocloud2016/macros/*.html',
'templates/gocloud2016/pages/*.html',
'templates/gocloud2016/pages/*/*.html',
# Errors
'templates/errors/*.html',
],
@ -120,6 +118,7 @@ setup(
'templates/administrator/settings/messages/*.html',
'templates/administrator/tasks/*.html',
'templates/administrator/users/*.html',
# 'templates/administrator/payments/*.html',
'templates/administrator/wiki/*.html',
'templates/administrator/wiki/article/*.html',
'templates/administrator/wiki/category/*.html',
@ -147,5 +146,22 @@ setup(
# billing
'cloud-cron-balance.py',
],
install_requires=required
install_requires=[
'Flask==0.10',
'Flask-Markdown==0.3',
'Flask-Babel==0.9',
'flask-peewee==0.6.0',
'flask-multistatic',
'Jinja2==2.8',
'peewee==2.8',
'validators==0.10',
'psycopg2==2.6.1',
'configparser',
'flask_httpauth==3.1.1',
'requests==2.7',
'uWSGI==2.0.11.1',
'wsgiref==0.1.2',
'sshpubkeys==1.0.6',
'dateutils',
]
)

BIN
tutorial.pdf Normal file

Binary file not shown.