Transform PackageMgr into queue-backed AppMgr
This commit is contained in:
parent
eb27d92383
commit
b772f92c22
@ -6,23 +6,48 @@ import os
|
|||||||
import requests
|
import requests
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import tempfile
|
import time
|
||||||
|
import uuid
|
||||||
|
|
||||||
from cryptography.exceptions import InvalidSignature
|
from cryptography.exceptions import InvalidSignature
|
||||||
from cryptography.hazmat.backends import default_backend
|
from cryptography.hazmat.backends import default_backend
|
||||||
from cryptography.hazmat.primitives import hashes
|
from cryptography.hazmat.primitives import hashes
|
||||||
from cryptography.hazmat.primitives.asymmetric import ec
|
from cryptography.hazmat.primitives.asymmetric import ec
|
||||||
from cryptography.hazmat.primitives.serialization import load_pem_public_key
|
from cryptography.hazmat.primitives.serialization import load_pem_public_key
|
||||||
|
from threading import Lock
|
||||||
|
|
||||||
|
from . import tools
|
||||||
|
|
||||||
PUB_FILE = '/srv/vm/packages.pub'
|
PUB_FILE = '/srv/vm/packages.pub'
|
||||||
LXC_ROOT = '/var/lib/lxc'
|
LXC_ROOT = '/var/lib/lxc'
|
||||||
|
|
||||||
class PackageManager:
|
class ActionItem:
|
||||||
def __init__(self, conf):
|
def __init__(self, action, app):
|
||||||
# Load JSON configuration
|
self.timestamp = int(time.time())
|
||||||
self.conf = conf
|
self.action = action
|
||||||
|
self.app = app
|
||||||
|
self.started = False
|
||||||
|
self.finished = False
|
||||||
|
self.data = None
|
||||||
|
|
||||||
|
class InstallItem:
|
||||||
|
def __init__(self, total):
|
||||||
|
# Stage 0 = download, 1 = deps install, 2 = app install
|
||||||
|
self.stage = 0
|
||||||
|
self.total = total
|
||||||
|
self.downloaded = 0
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
# Limit the disaplyed percentage between 1 - 99 for aestethical and psychological reasons
|
||||||
|
return str(max(1, min(99, round(self.downloaded / self.total * 100))))
|
||||||
|
|
||||||
|
class AppMgr:
|
||||||
|
def __init__(self, vmmgr):
|
||||||
|
self.vmmgr = vmmgr
|
||||||
|
self.conf = vmmgr.conf
|
||||||
self.online_packages = {}
|
self.online_packages = {}
|
||||||
self.bytes_downloaded = 0
|
self.action_queue = {}
|
||||||
|
self.lock = Lock()
|
||||||
|
|
||||||
def get_repo_resource(self, url, stream=False):
|
def get_repo_resource(self, url, stream=False):
|
||||||
return requests.get('{}/{}'.format(self.conf['repo']['url'], url), auth=(self.conf['repo']['user'], self.conf['repo']['pwd']), stream=stream)
|
return requests.get('{}/{}'.format(self.conf['repo']['url'], url), auth=(self.conf['repo']['user'], self.conf['repo']['pwd']), stream=stream)
|
||||||
@ -37,44 +62,78 @@ class PackageManager:
|
|||||||
pub_key.verify(packages_sig, packages, ec.ECDSA(hashes.SHA512()))
|
pub_key.verify(packages_sig, packages, ec.ECDSA(hashes.SHA512()))
|
||||||
self.online_packages = json.loads(packages)
|
self.online_packages = json.loads(packages)
|
||||||
|
|
||||||
def register_pending_installation(self, name):
|
def enqueue_action(self, action, app):
|
||||||
# Registers pending installation. Fetch online packages here instead of install_pacakges() to fail early if the repo isn't reachable
|
# Remove actions older than 1 day
|
||||||
self.fetch_online_packages()
|
for id,item in self.action_queue.items():
|
||||||
self.bytes_downloaded = 1
|
if item.timestamp < time.time() - 86400:
|
||||||
# Return total size for download
|
del self.item[id]
|
||||||
deps = [d for d in self.get_install_deps(name) if d not in self.conf['packages']]
|
# Enqueue action
|
||||||
return sum(self.online_packages[d]['size'] for d in deps)
|
id = '{}:{}'.format(app, uuid.uuid4())
|
||||||
|
item = ActionItem(action, app)
|
||||||
|
self.action_queue[id] = item
|
||||||
|
return id,item
|
||||||
|
|
||||||
def install_package(self, name):
|
def get_actions(self, ids):
|
||||||
|
# Return list of requested actions
|
||||||
|
result = {}
|
||||||
|
for id in ids:
|
||||||
|
result[id] = self.action_queue[id] if id in self.action_queue else None
|
||||||
|
return result
|
||||||
|
|
||||||
|
def process_action(self, id):
|
||||||
|
# Main method for deferred queue processing called by WSGI close handler
|
||||||
|
item = self.action_queue[id]
|
||||||
|
with self.lock:
|
||||||
|
item.started = True
|
||||||
|
try:
|
||||||
|
# Call the action method inside exclusive lock
|
||||||
|
getattr(self, item.action)(item)
|
||||||
|
except BaseException as e:
|
||||||
|
item.data = e
|
||||||
|
finally:
|
||||||
|
item.finished = True
|
||||||
|
|
||||||
|
def start_app(self, item):
|
||||||
|
if not tools.is_service_started(item.app):
|
||||||
|
self.vmmgr.start_app(item.app)
|
||||||
|
|
||||||
|
def stop_app(self, app):
|
||||||
|
if tools.is_service_started(item.app):
|
||||||
|
self.vmmgr.stop_app(item.app)
|
||||||
|
|
||||||
|
def install_app(self, item):
|
||||||
# Main installation function. Wrapper for download, registration and install script
|
# Main installation function. Wrapper for download, registration and install script
|
||||||
deps = [d for d in self.get_install_deps(name) if d not in self.conf['packages']]
|
deps = [d for d in self.get_install_deps(item.app) if d not in self.conf['packages']]
|
||||||
try:
|
item.data = InstallItem(sum(self.online_packages[d]['size'] for d in deps))
|
||||||
for dep in deps:
|
for dep in deps:
|
||||||
self.download_package(dep)
|
self.download_package(dep, item.data)
|
||||||
self.register_package(dep)
|
for dep in deps:
|
||||||
self.run_install_script(dep)
|
item.data.stage = 2 if dep == deps[-1] else 1
|
||||||
self.bytes_downloaded = 0
|
self.unpack_package(dep)
|
||||||
except:
|
# Run uninstall script before installation to purge previous failed installation
|
||||||
# Store exception state for retrieval via get_install_progress_action()
|
self.run_uninstall_script(dep)
|
||||||
self.bytes_downloaded = -1
|
self.register_package(dep)
|
||||||
|
self.run_install_script(dep)
|
||||||
|
|
||||||
def uninstall_package(self, name):
|
def uninstall_app(self, item):
|
||||||
# Main uninstallation function. Wrapper for uninstall script, filesystem purge and unregistration
|
# Main uninstallation function. Wrapper for uninstall script, filesystem purge and unregistration
|
||||||
deps = self.get_install_deps(name, False)[::-1]
|
deps = self.get_install_deps(item.app, False)[::-1]
|
||||||
for dep in deps:
|
for dep in deps:
|
||||||
if dep not in self.get_uninstall_deps():
|
if dep not in self.get_uninstall_deps():
|
||||||
self.run_uninstall_script(dep)
|
self.run_uninstall_script(dep)
|
||||||
self.purge_package(dep)
|
self.purge_package(dep)
|
||||||
self.unregister_package(dep)
|
self.unregister_package(dep)
|
||||||
|
|
||||||
def download_package(self, name):
|
def download_package(self, name, installitem):
|
||||||
# Downloads, verifies, unpacks and sets up a package
|
tmp_archive = '/tmp/{}.tar.xz'.format(name)
|
||||||
tmp_archive = tempfile.mkstemp('.tar.xz')[1]
|
|
||||||
r = self.get_repo_resource('{}.tar.xz'.format(name), True)
|
r = self.get_repo_resource('{}.tar.xz'.format(name), True)
|
||||||
with open(tmp_archive, 'wb') as f:
|
with open(tmp_archive, 'wb') as f:
|
||||||
for chunk in r.iter_content(chunk_size=65536):
|
for chunk in r.iter_content(chunk_size=65536):
|
||||||
if chunk:
|
if chunk:
|
||||||
self.bytes_downloaded += f.write(chunk)
|
installitem.downloaded += f.write(chunk)
|
||||||
|
|
||||||
|
def unpack_package(self, name):
|
||||||
|
tmp_archive = '/tmp/{}.tar.xz'.format(name)
|
||||||
# Verify hash
|
# Verify hash
|
||||||
if self.online_packages[name]['sha512'] != hash_file(tmp_archive):
|
if self.online_packages[name]['sha512'] != hash_file(tmp_archive):
|
||||||
raise InvalidSignature(name)
|
raise InvalidSignature(name)
|
@ -1,25 +1,22 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
import fcntl
|
|
||||||
import json
|
import json
|
||||||
|
from threading import Lock
|
||||||
|
|
||||||
CONF_FILE = '/srv/vm/config.json'
|
CONF_FILE = '/srv/vm/config.json'
|
||||||
# Locking is needed in order to prevent race conditions in WSGI threads
|
|
||||||
LOCK_FILE = '/srv/vm/config.lock'
|
|
||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
self.lock = Lock()
|
||||||
self.load()
|
self.load()
|
||||||
|
|
||||||
def load(self):
|
def load(self):
|
||||||
with open(LOCK_FILE, 'w') as l:
|
with self.lock:
|
||||||
fcntl.flock(l, fcntl.LOCK_EX)
|
|
||||||
with open(CONF_FILE, 'r') as f:
|
with open(CONF_FILE, 'r') as f:
|
||||||
self.data = json.load(f)
|
self.data = json.load(f)
|
||||||
|
|
||||||
def save(self):
|
def save(self):
|
||||||
with open(LOCK_FILE, 'w') as l:
|
with self.lock:
|
||||||
fcntl.flock(l, fcntl.LOCK_EX)
|
|
||||||
with open(CONF_FILE, 'w') as f:
|
with open(CONF_FILE, 'w') as f:
|
||||||
json.dump(self.data, f, sort_keys=True, indent=4)
|
json.dump(self.data, f, sort_keys=True, indent=4)
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ from jinja2 import Environment, FileSystemLoader
|
|||||||
|
|
||||||
from . import VMMgr, CERT_PUB_FILE
|
from . import VMMgr, CERT_PUB_FILE
|
||||||
from . import tools
|
from . import tools
|
||||||
from .pkgmgr import PackageManager
|
from .appmgr import AppMgr
|
||||||
from .validator import InvalidValueException
|
from .validator import InvalidValueException
|
||||||
from .wsgilang import WSGILang
|
from .wsgilang import WSGILang
|
||||||
from .wsgisession import WSGISession
|
from .wsgisession import WSGISession
|
||||||
@ -22,8 +22,8 @@ SESSION_KEY = os.urandom(26)
|
|||||||
class WSGIApp(object):
|
class WSGIApp(object):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.vmmgr = VMMgr()
|
self.vmmgr = VMMgr()
|
||||||
|
self.appmgr = AppMgr(self.vmmgr)
|
||||||
self.conf = self.vmmgr.conf
|
self.conf = self.vmmgr.conf
|
||||||
self.pkgmgr = PackageManager(self.conf)
|
|
||||||
self.jinja_env = Environment(loader=FileSystemLoader('/srv/vm/templates'), autoescape=True, lstrip_blocks=True, trim_blocks=True)
|
self.jinja_env = Environment(loader=FileSystemLoader('/srv/vm/templates'), autoescape=True, lstrip_blocks=True, trim_blocks=True)
|
||||||
self.jinja_env.globals.update(is_app_visible=self.is_app_visible)
|
self.jinja_env.globals.update(is_app_visible=self.is_app_visible)
|
||||||
self.jinja_env.globals.update(is_service_autostarted=tools.is_service_autostarted)
|
self.jinja_env.globals.update(is_service_autostarted=tools.is_service_autostarted)
|
||||||
@ -81,7 +81,7 @@ class WSGIApp(object):
|
|||||||
Rule('/start-app', endpoint='start_app_action'),
|
Rule('/start-app', endpoint='start_app_action'),
|
||||||
Rule('/stop-app', endpoint='stop_app_action'),
|
Rule('/stop-app', endpoint='stop_app_action'),
|
||||||
Rule('/install-app', endpoint='install_app_action'),
|
Rule('/install-app', endpoint='install_app_action'),
|
||||||
Rule('/get-install-progress', endpoint='get_install_progress_action'),
|
Rule('/get-progress', endpoint='get_progress_action'),
|
||||||
Rule('/uninstall-app', endpoint='uninstall_app_action'),
|
Rule('/uninstall-app', endpoint='uninstall_app_action'),
|
||||||
Rule('/update-password', endpoint='update_password_action'),
|
Rule('/update-password', endpoint='update_password_action'),
|
||||||
Rule('/shutdown-vm', endpoint='shutdown_vm_action'),
|
Rule('/shutdown-vm', endpoint='shutdown_vm_action'),
|
||||||
@ -148,15 +148,62 @@ class WSGIApp(object):
|
|||||||
def setup_apps_view(self, request):
|
def setup_apps_view(self, request):
|
||||||
# Application manager view.
|
# Application manager view.
|
||||||
try:
|
try:
|
||||||
self.pkgmgr.fetch_online_packages()
|
self.appmgr.fetch_online_packages()
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
all_apps = sorted(set([k for k,v in self.pkgmgr.online_packages.items() if 'host' in v] + list(self.conf['apps'].keys())))
|
all_apps = sorted(set([k for k,v in self.appmgr.online_packages.items() if 'host' in v] + list(self.conf['apps'].keys())))
|
||||||
return self.render_template('setup-apps.html', request, all_apps=all_apps, online_packages=self.pkgmgr.online_packages)
|
return self.render_template('setup-apps.html', request, all_apps=all_apps, online_packages=self.appmgr.online_packages)
|
||||||
|
|
||||||
def render_setup_apps_row(self, request, app, app_title, total_size=None, install_error=False):
|
def render_setup_apps_row(self, request, app, app_title, item):
|
||||||
|
lang = request.session.lang
|
||||||
|
actions = '<div class="loader"></div>'
|
||||||
|
if item.action == 'start_app':
|
||||||
|
if not item.started:
|
||||||
|
status = 'Spouští se (ve frontě)'
|
||||||
|
elif not item.finished:
|
||||||
|
status = 'Spouští se'
|
||||||
|
elif isinstance(item.data, BaseException):
|
||||||
|
status = '<span class="error">{}</span>'.format(lang.stop_start_error())
|
||||||
|
else:
|
||||||
|
status = '<span class="info">Spuštěna</span>'
|
||||||
|
actions = '<a href="#" class="app-stop">Zastavit</a>'
|
||||||
|
elif item.action == 'stop_app':
|
||||||
|
if not item.started:
|
||||||
|
status = 'Zastavuje se (ve frontě)'
|
||||||
|
elif not item.finished:
|
||||||
|
status = 'Zastavuje se'
|
||||||
|
elif isinstance(item.data, BaseException):
|
||||||
|
status = '<span class="error">{}</span>'.format(lang.stop_start_error())
|
||||||
|
else:
|
||||||
|
status = '<span class="error">Zastavena</span>'
|
||||||
|
actions = '<a href="#" class="app-start">Spustit</a>, <a href="#" class="app-uninstall">Odinstalovat</a>'
|
||||||
|
elif item.action == 'install_app':
|
||||||
|
if not item.started:
|
||||||
|
status = 'Stahuje se (ve frontě)'
|
||||||
|
elif not item.finished:
|
||||||
|
if item.data.stage == 0:
|
||||||
|
status = 'Stahuje se ({} %)'.format(item.data)
|
||||||
|
elif item.data.stage == 1:
|
||||||
|
status = 'Instalují se závislosti'
|
||||||
|
else:
|
||||||
|
status = 'Instaluje se'
|
||||||
|
elif isinstance(item.data, BaseException):
|
||||||
|
status = '<span class="error">{}</span>'.format(lang.package_manager_error())
|
||||||
|
else:
|
||||||
|
status = '<span class="error">Zastavena</span>'
|
||||||
|
actions = '<a href="#" class="app-start">Spustit</a>, <a href="#" class="app-uninstall">Odinstalovat</a>'
|
||||||
|
elif item.action == 'uninstall_app':
|
||||||
|
if not item.started:
|
||||||
|
status = 'Odinstalovává se (ve frontě)'
|
||||||
|
elif not item.finished:
|
||||||
|
status = 'Odinstalovává se'
|
||||||
|
elif isinstance(item.data, BaseException):
|
||||||
|
status = '<span class="error">{}</span>'.format(lang.package_manager_error())
|
||||||
|
else:
|
||||||
|
status = 'Není nainstalována'
|
||||||
|
actions = '<a href="#" class="app-install">Instalovat</a>'
|
||||||
t = self.jinja_env.get_template('setup-apps-row.html')
|
t = self.jinja_env.get_template('setup-apps-row.html')
|
||||||
return t.render({'conf': self.conf, 'session': request.session, 'app': app, 'app_title': app_title, 'total_size': total_size, 'install_error': install_error})
|
return t.render({'conf': self.conf, 'session': request.session, 'app': app, 'app_title': app_title, 'status': status, 'actions': actions})
|
||||||
|
|
||||||
def update_host_action(self, request):
|
def update_host_action(self, request):
|
||||||
# Update domain and port, then restart nginx
|
# Update domain and port, then restart nginx
|
||||||
@ -277,70 +324,47 @@ class WSGIApp(object):
|
|||||||
return self.render_json({'error': request.session.lang.malformed_request()})
|
return self.render_json({'error': request.session.lang.malformed_request()})
|
||||||
return self.render_json({'ok': 'ok'})
|
return self.render_json({'ok': 'ok'})
|
||||||
|
|
||||||
def start_app_action(self, request):
|
def enqueue_action(self, request, action):
|
||||||
# Starts application along with its dependencies
|
|
||||||
try:
|
try:
|
||||||
app = request.form['app']
|
app = request.form['app']
|
||||||
self.vmmgr.start_app(app)
|
except BadRequest:
|
||||||
except (BadRequest, InvalidValueException):
|
|
||||||
return self.render_json({'error': request.session.lang.malformed_request()})
|
return self.render_json({'error': request.session.lang.malformed_request()})
|
||||||
except:
|
app_title = self.conf['apps'][app]['title'] if app in self.conf['apps'] else self.appmgr.online_packages[app]['title']
|
||||||
return self.render_json({'error': request.session.lang.stop_start_error()})
|
id,item = self.appmgr.enqueue_action(action, app)
|
||||||
app_title = self.conf['apps'][app]['title']
|
response = self.render_json({'html': self.render_setup_apps_row(request, app, app_title, item), 'id': id})
|
||||||
return self.render_json({'ok': self.render_setup_apps_row(request, app, app_title)})
|
response.call_on_close(lambda: self.appmgr.process_action(id))
|
||||||
|
|
||||||
def stop_app_action(self, request):
|
|
||||||
# Stops application along with its dependencies
|
|
||||||
try:
|
|
||||||
app = request.form['app']
|
|
||||||
if tools.is_service_started(app):
|
|
||||||
self.vmmgr.stop_app(app)
|
|
||||||
except (BadRequest, InvalidValueException):
|
|
||||||
return self.render_json({'error': request.session.lang.malformed_request()})
|
|
||||||
except:
|
|
||||||
return self.render_json({'error': request.session.lang.stop_start_error()})
|
|
||||||
app_title = self.conf['apps'][app]['title']
|
|
||||||
return self.render_json({'ok': self.render_setup_apps_row(request, app, app_title)})
|
|
||||||
|
|
||||||
def install_app_action(self, request):
|
|
||||||
# Registers the application installation as pending
|
|
||||||
if self.pkgmgr.bytes_downloaded > 0:
|
|
||||||
return self.render_json({'error': request.session.lang.installation_in_progress()})
|
|
||||||
try:
|
|
||||||
app = request.form['app']
|
|
||||||
total_size = self.pkgmgr.register_pending_installation(app)
|
|
||||||
except (BadRequest, InvalidValueException):
|
|
||||||
return self.render_json({'error': request.session.lang.malformed_request()})
|
|
||||||
except:
|
|
||||||
return self.render_json({'error': request.session.lang.package_manager_error()})
|
|
||||||
app_title = self.pkgmgr.online_packages[app]['title']
|
|
||||||
response = self.render_json({'ok': self.render_setup_apps_row(request, app, app_title, total_size)})
|
|
||||||
response.call_on_close(lambda: self.pkgmgr.install_package(app))
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def get_install_progress_action(self, request):
|
def start_app_action(self, request):
|
||||||
# Gets pending installation status
|
# Queues application start along with its dependencies
|
||||||
if self.pkgmgr.bytes_downloaded > 0:
|
return self.enqueue_action(request, 'start_app')
|
||||||
return self.render_json({'progress': self.pkgmgr.bytes_downloaded})
|
|
||||||
app = request.form['app']
|
def stop_app_action(self, request):
|
||||||
# In case of installation error, we need to get the name from online_packages as the app is not yet registered
|
# Queues application stop along with its dependencies
|
||||||
app_title = self.conf['apps'][app]['title'] if app in self.conf['apps'] else self.pkgmgr.online_packages[app]['title']
|
return self.enqueue_action(request, 'stop_app')
|
||||||
install_error = True if self.pkgmgr.bytes_downloaded == -1 else False
|
|
||||||
return self.render_json({'ok': self.render_setup_apps_row(request, app, app_title, None, install_error)})
|
def install_app_action(self, request):
|
||||||
|
# Queues application installation
|
||||||
|
return self.enqueue_action(request, 'install_app')
|
||||||
|
|
||||||
def uninstall_app_action(self, request):
|
def uninstall_app_action(self, request):
|
||||||
# Uninstalls application
|
# Queues application uninstallation
|
||||||
if self.pkgmgr.bytes_downloaded > 0:
|
return self.enqueue_action(request, 'uninstall_app')
|
||||||
return self.render_json({'error': request.session.lang.installation_in_progress()})
|
|
||||||
|
def get_progress_action(self, request):
|
||||||
|
# Gets appmgr queue status for given ids
|
||||||
|
json = {}
|
||||||
try:
|
try:
|
||||||
app = request.form['app']
|
ids = request.form.getlist('ids[]')
|
||||||
app_title = self.conf['apps'][app]['title']
|
except BadRequest:
|
||||||
self.pkgmgr.uninstall_package(app)
|
|
||||||
except (BadRequest, InvalidValueException):
|
|
||||||
return self.render_json({'error': request.session.lang.malformed_request()})
|
return self.render_json({'error': request.session.lang.malformed_request()})
|
||||||
except:
|
actions = self.appmgr.get_actions(ids)
|
||||||
return self.render_json({'error': request.session.lang.package_manager_error()})
|
for id,item in actions.items():
|
||||||
return self.render_json({'ok': self.render_setup_apps_row(request, app, app_title)})
|
app = item.app
|
||||||
|
# In case of installation error, we need to get the name from online_packages as the app is not yet registered
|
||||||
|
app_title = self.conf['apps'][app]['title'] if app in self.conf['apps'] else self.appmgr.online_packages[app]['title']
|
||||||
|
json[id] = {'html': self.render_setup_apps_row(request, app, app_title, item), 'last': item.finished}
|
||||||
|
return self.render_json(json)
|
||||||
|
|
||||||
def update_password_action(self, request):
|
def update_password_action(self, request):
|
||||||
# Updates password for both HDD encryption (LUKS-on-LVM) and web interface admin account
|
# Updates password for both HDD encryption (LUKS-on-LVM) and web interface admin account
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
var action_queue = [];
|
||||||
|
|
||||||
$(function() {
|
$(function() {
|
||||||
$('#update-host').on('submit', update_host);
|
$('#update-host').on('submit', update_host);
|
||||||
$('#verify-dns').on('click', verify_dns);
|
$('#verify-dns').on('click', verify_dns);
|
||||||
@ -16,7 +18,7 @@ $(function() {
|
|||||||
$('#update-password').on('submit', update_password);
|
$('#update-password').on('submit', update_password);
|
||||||
$('#reboot-vm').on('click', reboot_vm);
|
$('#reboot-vm').on('click', reboot_vm);
|
||||||
$('#shutdown-vm').on('click', shutdown_vm);
|
$('#shutdown-vm').on('click', shutdown_vm);
|
||||||
window.setInterval(check_progress, 2000);
|
window.setInterval(check_progress, 1000);
|
||||||
});
|
});
|
||||||
|
|
||||||
function update_host() {
|
function update_host() {
|
||||||
@ -147,7 +149,8 @@ function _do_app(action, ev) {
|
|||||||
if (data.error) {
|
if (data.error) {
|
||||||
td.attr('class','error').html(data.error);
|
td.attr('class','error').html(data.error);
|
||||||
} else if (action) {
|
} else if (action) {
|
||||||
tr.replaceWith(data.ok);
|
tr.html(data.html);
|
||||||
|
action_queue.push(data.id);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return false;
|
return false;
|
||||||
@ -174,20 +177,16 @@ function uninstall_app(ev) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function check_progress() {
|
function check_progress() {
|
||||||
var progress = $('#install-progress');
|
if (action_queue.length) {
|
||||||
if (progress.length) {
|
$.post('/get-progress', {'ids': action_queue}, function(data) {
|
||||||
var td = progress.closest('td');
|
for (id in data) {
|
||||||
var tr = progress.closest('tr');
|
var app = id.split(':')[0];
|
||||||
$.post('/get-install-progress', {'app': tr.data('app')}, function(data) {
|
$('#app-manager tr[data-app="'+app+'"]').html(data[id].html);
|
||||||
if (data.progress) {
|
if (data[id].last) {
|
||||||
var value = parseInt(Math.max(1, data.progress / progress.data('total') * 100));
|
action_queue = action_queue.filter(function(item) {
|
||||||
if (value < 100) {
|
return item !== id
|
||||||
progress.text(parseInt(value));
|
});
|
||||||
} else {
|
|
||||||
td.html('<span id="install-progress">Instaluje se</span>')
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
tr.replaceWith(data.ok);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,22 +1,19 @@
|
|||||||
<tr data-app="{{ app }}">
|
{% set not_installed = app not in conf['apps'] %}
|
||||||
<td>{{ app_title }}</td>
|
{% if not status %}
|
||||||
{% set not_installed = app not in conf['apps'] %}
|
{% if not_installed: %}
|
||||||
<td class="center"><input type="checkbox" class="app-visible"{% if not_installed %} disabled{% elif conf['apps'][app]['visible'] %} checked{% endif %}></td>
|
{% set status = 'Není nainstalována' %}
|
||||||
<td class="center"><input type="checkbox" class="app-autostart"{% if not_installed %} disabled{% elif is_service_autostarted(app) %} checked{% endif %}></td>
|
{% set actions = '<a href="#" class="app-install">Instalovat</a>' %}
|
||||||
{% if install_error %}
|
{% elif is_service_started(app): %}
|
||||||
<td>Není nainstalována</td>
|
{% set status = '<span class="info">Spuštěna</span>' %}
|
||||||
<td><span class="error">{{ session.lang.package_manager_error() }}</span></td>
|
{% set actions = '<a href="#" class="app-stop">Zastavit</a>' %}
|
||||||
{% elif total_size %}
|
{% else: %}
|
||||||
<td>Stahuje se (<span id="install-progress" data-total="{{ total_size }}">1</span> %)</td>
|
{% set status = '<span class="error">Zastavena</span>' %}
|
||||||
<td><div class="loader"></div></td>
|
{% set actions = '<a href="#" class="app-start">Spustit</a>, <a href="#" class="app-uninstall">Odinstalovat</a>' %}
|
||||||
{% elif not_installed %}
|
|
||||||
<td>Není nainstalována</td>
|
|
||||||
<td><a href="#" class="app-install">Instalovat</a></td>
|
|
||||||
{% elif is_service_started(app) %}
|
|
||||||
<td><span class="info">Spuštěna</span></td>
|
|
||||||
<td><a href="#" class="app-stop">Zastavit</a></td>
|
|
||||||
{% else %}
|
|
||||||
<td><span class="error">Zastavena</span></td>
|
|
||||||
<td><a href="#" class="app-start">Spustit</a>, <a href="#" class="app-uninstall">Odinstalovat</a></td>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</tr>
|
{% endif %}
|
||||||
|
|
||||||
|
<td>{{ app_title }}</td>
|
||||||
|
<td class="center"><input type="checkbox" class="app-visible"{% if not_installed %} disabled{% elif conf['apps'][app]['visible'] %} checked{% endif %}></td>
|
||||||
|
<td class="center"><input type="checkbox" class="app-autostart"{% if not_installed %} disabled{% elif is_service_autostarted(app) %} checked{% endif %}></td>
|
||||||
|
<td>{{ status|safe }}</td>
|
||||||
|
<td>{{ actions|safe }}</td>
|
||||||
|
@ -17,7 +17,9 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
{% for app in all_apps %}
|
{% for app in all_apps %}
|
||||||
{% set app_title = conf['apps'][app]['title'] if app in conf['apps'] else online_packages[app]['title'] %}
|
{% set app_title = conf['apps'][app]['title'] if app in conf['apps'] else online_packages[app]['title'] %}
|
||||||
{% include 'setup-apps-row.html' %}
|
<tr data-app="{{ app }}">
|
||||||
|
{% include 'setup-apps-row.html' %}
|
||||||
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
Loading…
Reference in New Issue
Block a user