v0.2.0
This commit is contained in:
parent
d5c8fe5668
commit
bdc878e1b6
38 changed files with 815 additions and 394 deletions
|
@ -1,3 +1,10 @@
|
|||
# 0.2.0
|
||||
- Added cluster groups
|
||||
- Added evacuate and restore options for cluster members
|
||||
- Added cluster members roles to tables
|
||||
- Added cluster members actions
|
||||
- Removed serializeJSON library in favor of jquery serialize
|
||||
|
||||
# 0.1.0
|
||||
- Corrected image unit size, changing GiB to MiB
|
||||
- Added option filter for storage volumes by adding filter=custom to URL
|
||||
|
|
|
@ -2,6 +2,7 @@ from flask import Flask, Blueprint
|
|||
from . import server
|
||||
from . import servers
|
||||
from . import certificates
|
||||
from . import cluster_groups
|
||||
from . import cluster_members
|
||||
from . import images
|
||||
from . import container
|
||||
|
@ -34,6 +35,7 @@ api.add_url_rule('/containers/<endpoint>', view_func=containers.api_containers_e
|
|||
api.add_url_rule('/servers/<endpoint>', view_func=servers.api_servers_endpoint, methods=['GET', 'POST'])
|
||||
api.add_url_rule('/server/<endpoint>', view_func=server.api_server_endpoint)
|
||||
api.add_url_rule('/certificates/<endpoint>', view_func=certificates.api_certificates_endpoint, methods=['GET', 'POST'])
|
||||
api.add_url_rule('/cluster-groups/<endpoint>', view_func=cluster_groups.api_cluster_groups_endpoint, methods=['GET', 'POST'])
|
||||
api.add_url_rule('/cluster-members/<endpoint>', view_func=cluster_members.api_cluster_members_endpoint, methods=['GET', 'POST'])
|
||||
api.add_url_rule('/images/<endpoint>', view_func=images.api_images_endpoint, methods=['GET', 'POST'])
|
||||
api.add_url_rule('/networks/<endpoint>', view_func=networks.api_networks_endpoint, methods=['GET', 'POST'])
|
||||
|
|
|
@ -11,6 +11,7 @@ def privilege_check(privilege, server_id = 0):
|
|||
'Auditor' : [
|
||||
#'add_access_control',
|
||||
#'add_certificate',
|
||||
#'add_cluster_group',
|
||||
#'add_cluster_member',
|
||||
#'add_group',
|
||||
#'add_server',
|
||||
|
@ -32,6 +33,7 @@ def privilege_check(privilege, server_id = 0):
|
|||
#'add_storage_volume',
|
||||
#'add_user',
|
||||
#'attach_instance_profile',
|
||||
#'change_cluster_member_state',
|
||||
#'change_instance_state',
|
||||
#'copy_instance',
|
||||
#'create_instance_backup',
|
||||
|
@ -39,6 +41,7 @@ def privilege_check(privilege, server_id = 0):
|
|||
#'create_instance_snapshot',
|
||||
#'delete_access_control',
|
||||
#'delete_certificate',
|
||||
#'delete_cluster_group',
|
||||
#'delete_cluster_member',
|
||||
#'delete_group',
|
||||
#'delete_image',
|
||||
|
@ -88,6 +91,7 @@ def privilege_check(privilege, server_id = 0):
|
|||
'is_cluster_member_enabled',
|
||||
'list_access_controls',
|
||||
'list_certificates',
|
||||
'list_cluster_groups',
|
||||
'list_cluster_members',
|
||||
'list_groups',
|
||||
'list_servers',
|
||||
|
@ -110,6 +114,7 @@ def privilege_check(privilege, server_id = 0):
|
|||
'list_storage_volumes',
|
||||
'list_users',
|
||||
'load_certificate',
|
||||
'load_cluster_group',
|
||||
'load_cluster_member',
|
||||
'load_image',
|
||||
'load_instance',
|
||||
|
@ -129,6 +134,7 @@ def privilege_check(privilege, server_id = 0):
|
|||
#'restore_instance_snapshot',
|
||||
#'update_access_control',
|
||||
#'update_certificate',
|
||||
#'update_cluster_group',
|
||||
#'update_cluster_member',
|
||||
#'update_group',
|
||||
#'update_server',
|
||||
|
@ -147,6 +153,7 @@ def privilege_check(privilege, server_id = 0):
|
|||
'User': [
|
||||
#'add_access_control',
|
||||
#'add_certificate',
|
||||
#'add_cluster_group',
|
||||
#'add_cluster_member',
|
||||
#'add_group',
|
||||
#'add_server',
|
||||
|
@ -168,6 +175,7 @@ def privilege_check(privilege, server_id = 0):
|
|||
#'add_storage_volume',
|
||||
#'add_user',
|
||||
#'attach_instance_profile',
|
||||
'change_cluster_member_state',
|
||||
'change_instance_state',
|
||||
'copy_instance',
|
||||
'create_instance_backup',
|
||||
|
@ -175,6 +183,7 @@ def privilege_check(privilege, server_id = 0):
|
|||
'create_instance_snapshot',
|
||||
#'delete_access_control',
|
||||
#'delete_certificate',
|
||||
#'delete_cluster_group',
|
||||
#'delete_cluster_member',
|
||||
#'delete_group',
|
||||
#'delete_image',
|
||||
|
@ -224,6 +233,7 @@ def privilege_check(privilege, server_id = 0):
|
|||
'is_cluster_member_enabled',
|
||||
'list_access_controls',
|
||||
'list_certificates',
|
||||
'list_cluster_groups',
|
||||
'list_cluster_members',
|
||||
'list_groups',
|
||||
'list_servers',
|
||||
|
@ -246,6 +256,7 @@ def privilege_check(privilege, server_id = 0):
|
|||
'list_storage_volumes',
|
||||
'list_users',
|
||||
'load_certificate',
|
||||
'load_cluster_group',
|
||||
'load_cluster_member',
|
||||
'load_image',
|
||||
'load_instance',
|
||||
|
@ -265,6 +276,7 @@ def privilege_check(privilege, server_id = 0):
|
|||
'restore_instance_snapshot',
|
||||
#'update_access_control',
|
||||
#'update_certificate',
|
||||
#'update_cluster_group',
|
||||
#'update_cluster_member',
|
||||
#'update_group',
|
||||
#'update_server',
|
||||
|
@ -283,6 +295,7 @@ def privilege_check(privilege, server_id = 0):
|
|||
'Operator': [
|
||||
#'add_access_control',
|
||||
'add_certificate',
|
||||
'add_cluster_group',
|
||||
'add_cluster_member',
|
||||
#'add_group',
|
||||
'add_server',
|
||||
|
@ -304,6 +317,7 @@ def privilege_check(privilege, server_id = 0):
|
|||
'add_storage_volume',
|
||||
#'add_user',
|
||||
'attach_instance_profile',
|
||||
'change_cluster_member_state',
|
||||
'change_instance_state',
|
||||
'copy_instance',
|
||||
'create_instance_backup',
|
||||
|
@ -311,6 +325,7 @@ def privilege_check(privilege, server_id = 0):
|
|||
'create_instance_snapshot',
|
||||
#'delete_access_control',
|
||||
'delete_certificate',
|
||||
'delete_cluster_group',
|
||||
'delete_cluster_member',
|
||||
#'delete_group',
|
||||
#'delete_image',
|
||||
|
@ -360,6 +375,7 @@ def privilege_check(privilege, server_id = 0):
|
|||
'is_cluster_member_enabled',
|
||||
'list_access_controls',
|
||||
'list_certificates',
|
||||
'list_cluster_groups',
|
||||
'list_cluster_members',
|
||||
'list_groups',
|
||||
'list_servers',
|
||||
|
@ -382,6 +398,7 @@ def privilege_check(privilege, server_id = 0):
|
|||
'list_storage_volumes',
|
||||
'list_users',
|
||||
'load_certificate',
|
||||
'load_cluster_group',
|
||||
'load_cluster_member',
|
||||
'load_image',
|
||||
'load_instance',
|
||||
|
@ -401,6 +418,7 @@ def privilege_check(privilege, server_id = 0):
|
|||
'restore_instance_snapshot',
|
||||
#'update_access_control',
|
||||
'update_certificate',
|
||||
'update_cluster_group',
|
||||
'update_cluster_member',
|
||||
#'update_group',
|
||||
'update_server',
|
||||
|
@ -419,6 +437,7 @@ def privilege_check(privilege, server_id = 0):
|
|||
'Administrator': [
|
||||
'add_access_control',
|
||||
'add_certificate',
|
||||
'add_cluster_group',
|
||||
'add_cluster_member',
|
||||
'add_group',
|
||||
'add_server',
|
||||
|
@ -440,6 +459,7 @@ def privilege_check(privilege, server_id = 0):
|
|||
'add_storage_volume',
|
||||
'add_user',
|
||||
'attach_instance_profile',
|
||||
'change_cluster_member_state',
|
||||
'change_instance_state',
|
||||
'copy_instance',
|
||||
'create_instance_backup',
|
||||
|
@ -447,6 +467,7 @@ def privilege_check(privilege, server_id = 0):
|
|||
'create_instance_snapshot',
|
||||
'delete_access_control',
|
||||
'delete_certificate',
|
||||
'delete_cluster_group',
|
||||
'delete_cluster_member',
|
||||
'delete_group',
|
||||
'delete_image',
|
||||
|
@ -496,6 +517,7 @@ def privilege_check(privilege, server_id = 0):
|
|||
'is_cluster_member_enabled',
|
||||
'list_access_controls',
|
||||
'list_certificates',
|
||||
'list_cluster_groups',
|
||||
'list_cluster_members',
|
||||
'list_groups',
|
||||
'list_servers',
|
||||
|
@ -518,6 +540,7 @@ def privilege_check(privilege, server_id = 0):
|
|||
'list_storage_volumes',
|
||||
'list_users',
|
||||
'load_certificate',
|
||||
'load_cluster_group',
|
||||
'load_cluster_member',
|
||||
'load_image',
|
||||
'load_instance',
|
||||
|
@ -537,6 +560,7 @@ def privilege_check(privilege, server_id = 0):
|
|||
'restore_instance_snapshot',
|
||||
'update_access_control',
|
||||
'update_certificate',
|
||||
'update_cluster_group',
|
||||
'update_cluster_member',
|
||||
'update_group',
|
||||
'update_server',
|
||||
|
|
118
lxconsole/api/cluster_groups.py
Normal file
118
lxconsole/api/cluster_groups.py
Normal file
|
@ -0,0 +1,118 @@
|
|||
from flask import jsonify, request
|
||||
import requests
|
||||
from lxconsole import db
|
||||
from lxconsole.models import Server
|
||||
from flask_login import login_required
|
||||
from lxconsole.api.access_controls import privilege_check
|
||||
|
||||
|
||||
def get_client_crt():
|
||||
return 'certs/client.crt'
|
||||
|
||||
def get_client_key():
|
||||
return 'certs/client.key'
|
||||
|
||||
@login_required
|
||||
def api_cluster_groups_endpoint(endpoint):
|
||||
|
||||
if not privilege_check(endpoint, request.args.get('id')):
|
||||
return jsonify({'data': [], 'metadata':[], 'error': 'not authorized', 'error_code': 403})
|
||||
|
||||
|
||||
if endpoint == 'add_cluster_group':
|
||||
id = request.args.get('id')
|
||||
project = request.args.get('project')
|
||||
server = Server.query.filter_by(id=id).first()
|
||||
url = 'https://' + server.addr + ':' + str(server.port) + '/1.0/cluster/groups?project=' + project
|
||||
client_cert = get_client_crt()
|
||||
client_key = get_client_key()
|
||||
|
||||
if request.form.get('json'):
|
||||
data = request.form.get('json')
|
||||
results = requests.post(url, verify=server.ssl_verify, cert=(client_cert, client_key), data=data)
|
||||
return jsonify(results.json())
|
||||
|
||||
data = {}
|
||||
data.update({'name': request.form.get('name')})
|
||||
data.update({'description': request.form.get('description')})
|
||||
data.update({'members': request.form.getlist('members')})
|
||||
results = requests.post(url, verify=server.ssl_verify, cert=(client_cert, client_key), json=data)
|
||||
|
||||
return jsonify(results.json())
|
||||
|
||||
|
||||
if endpoint == 'delete_cluster_group':
|
||||
id = request.args.get('id')
|
||||
project = request.args.get('project')
|
||||
name = request.form.get('name')
|
||||
server = Server.query.filter_by(id=id).first()
|
||||
url = 'https://' + server.addr + ':' + str(server.port) + '/1.0/cluster/groups/' + name + '?project=' + project
|
||||
client_cert = get_client_crt()
|
||||
client_key = get_client_key()
|
||||
results = requests.delete(url, verify=server.ssl_verify, cert=(client_cert, client_key))
|
||||
return jsonify(results.json())
|
||||
|
||||
|
||||
if endpoint == 'is_cluster_member_enabled':
|
||||
id = request.args.get('id')
|
||||
project = request.args.get('project')
|
||||
server = Server.query.filter_by(id=id).first()
|
||||
url = 'https://' + server.addr + ':' + str(server.port) + '/1.0/cluster?project=' + project
|
||||
client_cert = get_client_crt()
|
||||
client_key = get_client_key()
|
||||
results = requests.get(url, verify=server.ssl_verify, cert=(client_cert, client_key))
|
||||
data = results.json()['metadata']
|
||||
return str(data['enabled'])
|
||||
|
||||
|
||||
if endpoint == 'list_cluster_groups':
|
||||
if api_cluster_groups_endpoint('is_cluster_member_enabled') == 'True':
|
||||
id = request.args.get('id')
|
||||
project = request.args.get('project')
|
||||
server = Server.query.filter_by(id=id).first()
|
||||
recursion = request.args.get('recursion')
|
||||
if recursion == '1':
|
||||
url = 'https://' + server.addr + ':' + str(server.port) + '/1.0/cluster/groups?recursion=1&project=' + project
|
||||
else:
|
||||
url = 'https://' + server.addr + ':' + str(server.port) + '/1.0/cluster/groups?project=' + project
|
||||
client_cert = get_client_crt()
|
||||
client_key = get_client_key()
|
||||
results = requests.get(url, verify=server.ssl_verify, cert=(client_cert, client_key))
|
||||
return jsonify(results.json())
|
||||
data = { "metadata": []}
|
||||
return jsonify(data)
|
||||
|
||||
|
||||
if endpoint == 'load_cluster_group':
|
||||
id = request.args.get('id')
|
||||
project = request.args.get('project')
|
||||
name = request.form.get('name')
|
||||
server = Server.query.filter_by(id=id).first()
|
||||
url = 'https://' + server.addr + ':' + str(server.port) + '/1.0/cluster/groups/' + name + '?project=' + project
|
||||
client_cert = get_client_crt()
|
||||
client_key = get_client_key()
|
||||
results = requests.get(url, verify=server.ssl_verify, cert=(client_cert, client_key))
|
||||
return jsonify(results.json())
|
||||
|
||||
|
||||
if endpoint == 'update_cluster_group':
|
||||
id = request.args.get('id')
|
||||
project = request.args.get('project')
|
||||
name = request.args.get('name')
|
||||
server = Server.query.filter_by(id=id).first()
|
||||
url = 'https://' + server.addr + ':' + str(server.port) + '/1.0/cluster/groups/' + name + '?project=' + project
|
||||
client_cert = get_client_crt()
|
||||
client_key = get_client_key()
|
||||
|
||||
if request.form.get('json'):
|
||||
data = request.form.get('json')
|
||||
results = requests.put(url, verify=server.ssl_verify, cert=(client_cert, client_key), data=data)
|
||||
return jsonify(results.json())
|
||||
|
||||
if request.form.get('name'):
|
||||
data = {}
|
||||
data.update({'name': request.form.get('name')})
|
||||
results = requests.post(url, verify=server.ssl_verify, cert=(client_cert, client_key), json=data)
|
||||
return jsonify(results.json())
|
||||
return False
|
||||
|
|
@ -18,6 +18,36 @@ def api_cluster_members_endpoint(endpoint):
|
|||
if not privilege_check(endpoint, request.args.get('id')):
|
||||
return jsonify({'data': [], 'metadata':[], 'error': 'not authorized', 'error_code': 403})
|
||||
|
||||
|
||||
if endpoint == 'change_cluster_member_state':
|
||||
id = request.args.get('id')
|
||||
project = request.args.get('project')
|
||||
name = request.form.get('name')
|
||||
server = Server.query.filter_by(id=id).first()
|
||||
url = 'https://' + server.addr + ':' + str(server.port) + '/1.0/cluster/members/' + name + '/state?project=' + project
|
||||
client_cert = get_client_crt()
|
||||
client_key = get_client_key()
|
||||
data = {}
|
||||
data.update({'action': request.form.get('action')})
|
||||
results = requests.post(url, verify=server.ssl_verify, cert=(client_cert, client_key), json=data)
|
||||
return jsonify(results.json())
|
||||
|
||||
|
||||
if endpoint == 'delete_cluster_member':
|
||||
id = request.args.get('id')
|
||||
project = request.args.get('project')
|
||||
name = request.form.get('name')
|
||||
server = Server.query.filter_by(id=id).first()
|
||||
if request.form.get('force') == 'true':
|
||||
url = 'https://' + server.addr + ':' + str(server.port) + '/1.0/cluster/members/' + name + '?force=1&project=' + project
|
||||
else:
|
||||
url = 'https://' + server.addr + ':' + str(server.port) + '/1.0/cluster/members/' + name + '?project=' + project
|
||||
client_cert = get_client_crt()
|
||||
client_key = get_client_key()
|
||||
results = requests.delete(url, verify=server.ssl_verify, cert=(client_cert, client_key))
|
||||
return jsonify(results.json())
|
||||
|
||||
|
||||
if endpoint == 'is_cluster_member_enabled':
|
||||
id = request.args.get('id')
|
||||
project = request.args.get('project')
|
||||
|
@ -47,3 +77,36 @@ def api_cluster_members_endpoint(endpoint):
|
|||
data = { "metadata": []}
|
||||
return jsonify(data)
|
||||
|
||||
|
||||
if endpoint == 'load_cluster_member':
|
||||
id = request.args.get('id')
|
||||
project = request.args.get('project')
|
||||
name = request.form.get('name')
|
||||
server = Server.query.filter_by(id=id).first()
|
||||
url = 'https://' + server.addr + ':' + str(server.port) + '/1.0/cluster/members/' + name + '?project=' + project
|
||||
client_cert = get_client_crt()
|
||||
client_key = get_client_key()
|
||||
results = requests.get(url, verify=server.ssl_verify, cert=(client_cert, client_key))
|
||||
return jsonify(results.json())
|
||||
|
||||
|
||||
if endpoint == 'update_cluster_member':
|
||||
id = request.args.get('id')
|
||||
project = request.args.get('project')
|
||||
name = request.args.get('name')
|
||||
server = Server.query.filter_by(id=id).first()
|
||||
url = 'https://' + server.addr + ':' + str(server.port) + '/1.0/cluster/members/' + name + '?project=' + project
|
||||
client_cert = get_client_crt()
|
||||
client_key = get_client_key()
|
||||
|
||||
if request.form.get('json'):
|
||||
data = request.form.get('json')
|
||||
results = requests.put(url, verify=server.ssl_verify, cert=(client_cert, client_key), data=data)
|
||||
return jsonify(results.json())
|
||||
|
||||
if request.form.get('server_name'):
|
||||
data = {}
|
||||
data.update({'server_name': request.form.get('server_name')})
|
||||
results = requests.post(url, verify=server.ssl_verify, cert=(client_cert, client_key), json=data)
|
||||
return jsonify(results.json())
|
||||
return False
|
|
@ -49,6 +49,11 @@ def home():
|
|||
def certificates():
|
||||
return render_template('certificates.html', page_title='Certificates', page_user_id=current_user.id, page_username=current_user.username,)
|
||||
|
||||
@app.route("/cluster-groups")
|
||||
@login_required
|
||||
def cluster_groups():
|
||||
return render_template('cluster-groups.html', page_title='Cluster Groups', page_user_id=current_user.id, page_username=current_user.username,)
|
||||
|
||||
@app.route("/cluster-members")
|
||||
@login_required
|
||||
def cluster_members():
|
||||
|
|
|
@ -1,338 +0,0 @@
|
|||
/*!
|
||||
SerializeJSON jQuery plugin.
|
||||
https://github.com/marioizquierdo/jquery.serializeJSON
|
||||
version 3.2.1 (Feb, 2021)
|
||||
|
||||
Copyright (c) 2012-2021 Mario Izquierdo
|
||||
Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)
|
||||
and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses.
|
||||
*/
|
||||
(function (factory) {
|
||||
/* global define, require, module */
|
||||
if (typeof define === "function" && define.amd) { // AMD. Register as an anonymous module.
|
||||
define(["jquery"], factory);
|
||||
} else if (typeof exports === "object") { // Node/CommonJS
|
||||
var jQuery = require("jquery");
|
||||
module.exports = factory(jQuery);
|
||||
} else { // Browser globals (zepto supported)
|
||||
factory(window.jQuery || window.Zepto || window.$); // Zepto supported on browsers as well
|
||||
}
|
||||
|
||||
}(function ($) {
|
||||
"use strict";
|
||||
|
||||
var rCRLF = /\r?\n/g;
|
||||
var rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i;
|
||||
var rsubmittable = /^(?:input|select|textarea|keygen)/i;
|
||||
var rcheckableType = /^(?:checkbox|radio)$/i;
|
||||
|
||||
$.fn.serializeJSON = function (options) {
|
||||
var f = $.serializeJSON;
|
||||
var $form = this; // NOTE: the set of matched elements is most likely a form, but it could also be a group of inputs
|
||||
var opts = f.setupOpts(options); // validate options and apply defaults
|
||||
var typeFunctions = $.extend({}, opts.defaultTypes, opts.customTypes);
|
||||
|
||||
// Make a list with {name, value, el} for each input element
|
||||
var serializedArray = f.serializeArray($form, opts);
|
||||
|
||||
// Convert the serializedArray into a serializedObject with nested keys
|
||||
var serializedObject = {};
|
||||
$.each(serializedArray, function (_i, obj) {
|
||||
|
||||
var nameSansType = obj.name;
|
||||
var type = $(obj.el).attr("data-value-type");
|
||||
|
||||
if (!type && !opts.disableColonTypes) { // try getting the type from the input name
|
||||
var p = f.splitType(obj.name); // "foo:string" => ["foo", "string"]
|
||||
nameSansType = p[0];
|
||||
type = p[1];
|
||||
}
|
||||
if (type === "skip") {
|
||||
return; // ignore fields with type skip
|
||||
}
|
||||
if (!type) {
|
||||
type = opts.defaultType; // "string" by default
|
||||
}
|
||||
|
||||
var typedValue = f.applyTypeFunc(obj.name, obj.value, type, obj.el, typeFunctions); // Parse type as string, number, etc.
|
||||
|
||||
if (!typedValue && f.shouldSkipFalsy(obj.name, nameSansType, type, obj.el, opts)) {
|
||||
return; // ignore falsy inputs if specified in the options
|
||||
}
|
||||
|
||||
var keys = f.splitInputNameIntoKeysArray(nameSansType);
|
||||
f.deepSet(serializedObject, keys, typedValue, opts);
|
||||
});
|
||||
return serializedObject;
|
||||
};
|
||||
|
||||
// Use $.serializeJSON as namespace for the auxiliar functions
|
||||
// and to define defaults
|
||||
$.serializeJSON = {
|
||||
defaultOptions: {}, // reassign to override option defaults for all serializeJSON calls
|
||||
|
||||
defaultBaseOptions: { // do not modify, use defaultOptions instead
|
||||
checkboxUncheckedValue: undefined, // to include that value for unchecked checkboxes (instead of ignoring them)
|
||||
useIntKeysAsArrayIndex: false, // name="foo[2]" value="v" => {foo: [null, null, "v"]}, instead of {foo: ["2": "v"]}
|
||||
|
||||
skipFalsyValuesForTypes: [], // skip serialization of falsy values for listed value types
|
||||
skipFalsyValuesForFields: [], // skip serialization of falsy values for listed field names
|
||||
|
||||
disableColonTypes: false, // do not interpret ":type" suffix as a type
|
||||
customTypes: {}, // extends defaultTypes
|
||||
defaultTypes: {
|
||||
"string": function(str) { return String(str); },
|
||||
"number": function(str) { return Number(str); },
|
||||
"boolean": function(str) { var falses = ["false", "null", "undefined", "", "0"]; return falses.indexOf(str) === -1; },
|
||||
"null": function(str) { var falses = ["false", "null", "undefined", "", "0"]; return falses.indexOf(str) === -1 ? str : null; },
|
||||
"array": function(str) { return JSON.parse(str); },
|
||||
"object": function(str) { return JSON.parse(str); },
|
||||
"skip": null // skip is a special type used to ignore fields
|
||||
},
|
||||
defaultType: "string",
|
||||
},
|
||||
|
||||
// Validate and set defaults
|
||||
setupOpts: function(options) {
|
||||
if (options == null) options = {};
|
||||
var f = $.serializeJSON;
|
||||
|
||||
// Validate
|
||||
var validOpts = [
|
||||
"checkboxUncheckedValue",
|
||||
"useIntKeysAsArrayIndex",
|
||||
|
||||
"skipFalsyValuesForTypes",
|
||||
"skipFalsyValuesForFields",
|
||||
|
||||
"disableColonTypes",
|
||||
"customTypes",
|
||||
"defaultTypes",
|
||||
"defaultType"
|
||||
];
|
||||
for (var opt in options) {
|
||||
if (validOpts.indexOf(opt) === -1) {
|
||||
throw new Error("serializeJSON ERROR: invalid option '" + opt + "'. Please use one of " + validOpts.join(", "));
|
||||
}
|
||||
}
|
||||
|
||||
// Helper to get options or defaults
|
||||
return $.extend({}, f.defaultBaseOptions, f.defaultOptions, options);
|
||||
},
|
||||
|
||||
// Just like jQuery's serializeArray method, returns an array of objects with name and value.
|
||||
// but also includes the dom element (el) and is handles unchecked checkboxes if the option or data attribute are provided.
|
||||
serializeArray: function($form, opts) {
|
||||
if (opts == null) { opts = {}; }
|
||||
var f = $.serializeJSON;
|
||||
|
||||
return $form.map(function() {
|
||||
var elements = $.prop(this, "elements"); // handle propHook "elements" to filter or add form elements
|
||||
return elements ? $.makeArray(elements) : this;
|
||||
|
||||
}).filter(function() {
|
||||
var $el = $(this);
|
||||
var type = this.type;
|
||||
|
||||
// Filter with the standard W3C rules for successful controls: http://www.w3.org/TR/html401/interact/forms.html#h-17.13.2
|
||||
return this.name && // must contain a name attribute
|
||||
!$el.is(":disabled") && // must not be disable (use .is(":disabled") so that fieldset[disabled] works)
|
||||
rsubmittable.test(this.nodeName) && !rsubmitterTypes.test(type) && // only serialize submittable fields (and not buttons)
|
||||
(this.checked || !rcheckableType.test(type) || f.getCheckboxUncheckedValue($el, opts) != null); // skip unchecked checkboxes (unless using opts)
|
||||
|
||||
}).map(function(_i, el) {
|
||||
var $el = $(this);
|
||||
var val = $el.val();
|
||||
var type = this.type; // "input", "select", "textarea", "checkbox", etc.
|
||||
|
||||
if (val == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (rcheckableType.test(type) && !this.checked) {
|
||||
val = f.getCheckboxUncheckedValue($el, opts);
|
||||
}
|
||||
|
||||
if (isArray(val)) {
|
||||
return $.map(val, function(val) {
|
||||
return { name: el.name, value: val.replace(rCRLF, "\r\n"), el: el };
|
||||
} );
|
||||
}
|
||||
|
||||
return { name: el.name, value: val.replace(rCRLF, "\r\n"), el: el };
|
||||
|
||||
}).get();
|
||||
},
|
||||
|
||||
getCheckboxUncheckedValue: function($el, opts) {
|
||||
var val = $el.attr("data-unchecked-value");
|
||||
if (val == null) {
|
||||
val = opts.checkboxUncheckedValue;
|
||||
}
|
||||
return val;
|
||||
},
|
||||
|
||||
// Parse value with type function
|
||||
applyTypeFunc: function(name, strVal, type, el, typeFunctions) {
|
||||
var typeFunc = typeFunctions[type];
|
||||
if (!typeFunc) { // quick feedback to user if there is a typo or missconfiguration
|
||||
throw new Error("serializeJSON ERROR: Invalid type " + type + " found in input name '" + name + "', please use one of " + objectKeys(typeFunctions).join(", "));
|
||||
}
|
||||
return typeFunc(strVal, el);
|
||||
},
|
||||
|
||||
// Splits a field name into the name and the type. Examples:
|
||||
// "foo" => ["foo", ""]
|
||||
// "foo:boolean" => ["foo", "boolean"]
|
||||
// "foo[bar]:null" => ["foo[bar]", "null"]
|
||||
splitType : function(name) {
|
||||
var parts = name.split(":");
|
||||
if (parts.length > 1) {
|
||||
var t = parts.pop();
|
||||
return [parts.join(":"), t];
|
||||
} else {
|
||||
return [name, ""];
|
||||
}
|
||||
},
|
||||
|
||||
// Check if this input should be skipped when it has a falsy value,
|
||||
// depending on the options to skip values by name or type, and the data-skip-falsy attribute.
|
||||
shouldSkipFalsy: function(name, nameSansType, type, el, opts) {
|
||||
var skipFromDataAttr = $(el).attr("data-skip-falsy");
|
||||
if (skipFromDataAttr != null) {
|
||||
return skipFromDataAttr !== "false"; // any value is true, except the string "false"
|
||||
}
|
||||
|
||||
var optForFields = opts.skipFalsyValuesForFields;
|
||||
if (optForFields && (optForFields.indexOf(nameSansType) !== -1 || optForFields.indexOf(name) !== -1)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
var optForTypes = opts.skipFalsyValuesForTypes;
|
||||
if (optForTypes && optForTypes.indexOf(type) !== -1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
// Split the input name in programatically readable keys.
|
||||
// Examples:
|
||||
// "foo" => ["foo"]
|
||||
// "[foo]" => ["foo"]
|
||||
// "foo[inn][bar]" => ["foo", "inn", "bar"]
|
||||
// "foo[inn[bar]]" => ["foo", "inn", "bar"]
|
||||
// "foo[inn][arr][0]" => ["foo", "inn", "arr", "0"]
|
||||
// "arr[][val]" => ["arr", "", "val"]
|
||||
splitInputNameIntoKeysArray: function(nameWithNoType) {
|
||||
var keys = nameWithNoType.split("["); // split string into array
|
||||
keys = $.map(keys, function (key) { return key.replace(/\]/g, ""); }); // remove closing brackets
|
||||
if (keys[0] === "") { keys.shift(); } // ensure no opening bracket ("[foo][inn]" should be same as "foo[inn]")
|
||||
return keys;
|
||||
},
|
||||
|
||||
// Set a value in an object or array, using multiple keys to set in a nested object or array.
|
||||
// This is the main function of the script, that allows serializeJSON to use nested keys.
|
||||
// Examples:
|
||||
//
|
||||
// deepSet(obj, ["foo"], v) // obj["foo"] = v
|
||||
// deepSet(obj, ["foo", "inn"], v) // obj["foo"]["inn"] = v // Create the inner obj["foo"] object, if needed
|
||||
// deepSet(obj, ["foo", "inn", "123"], v) // obj["foo"]["arr"]["123"] = v //
|
||||
//
|
||||
// deepSet(obj, ["0"], v) // obj["0"] = v
|
||||
// deepSet(arr, ["0"], v, {useIntKeysAsArrayIndex: true}) // arr[0] = v
|
||||
// deepSet(arr, [""], v) // arr.push(v)
|
||||
// deepSet(obj, ["arr", ""], v) // obj["arr"].push(v)
|
||||
//
|
||||
// arr = [];
|
||||
// deepSet(arr, ["", v] // arr => [v]
|
||||
// deepSet(arr, ["", "foo"], v) // arr => [v, {foo: v}]
|
||||
// deepSet(arr, ["", "bar"], v) // arr => [v, {foo: v, bar: v}]
|
||||
// deepSet(arr, ["", "bar"], v) // arr => [v, {foo: v, bar: v}, {bar: v}]
|
||||
//
|
||||
deepSet: function (o, keys, value, opts) {
|
||||
if (opts == null) { opts = {}; }
|
||||
var f = $.serializeJSON;
|
||||
if (isUndefined(o)) { throw new Error("ArgumentError: param 'o' expected to be an object or array, found undefined"); }
|
||||
if (!keys || keys.length === 0) { throw new Error("ArgumentError: param 'keys' expected to be an array with least one element"); }
|
||||
|
||||
var key = keys[0];
|
||||
|
||||
// Only one key, then it's not a deepSet, just assign the value in the object or add it to the array.
|
||||
if (keys.length === 1) {
|
||||
if (key === "") { // push values into an array (o must be an array)
|
||||
o.push(value);
|
||||
} else {
|
||||
o[key] = value; // keys can be object keys (strings) or array indexes (numbers)
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var nextKey = keys[1]; // nested key
|
||||
var tailKeys = keys.slice(1); // list of all other nested keys (nextKey is first)
|
||||
|
||||
if (key === "") { // push nested objects into an array (o must be an array)
|
||||
var lastIdx = o.length - 1;
|
||||
var lastVal = o[lastIdx];
|
||||
|
||||
// if the last value is an object or array, and the new key is not set yet
|
||||
if (isObject(lastVal) && isUndefined(f.deepGet(lastVal, tailKeys))) {
|
||||
key = lastIdx; // then set the new value as a new attribute of the same object
|
||||
} else {
|
||||
key = lastIdx + 1; // otherwise, add a new element in the array
|
||||
}
|
||||
}
|
||||
|
||||
if (nextKey === "") { // "" is used to push values into the nested array "array[]"
|
||||
if (isUndefined(o[key]) || !isArray(o[key])) {
|
||||
o[key] = []; // define (or override) as array to push values
|
||||
}
|
||||
} else {
|
||||
if (opts.useIntKeysAsArrayIndex && isValidArrayIndex(nextKey)) { // if 1, 2, 3 ... then use an array, where nextKey is the index
|
||||
if (isUndefined(o[key]) || !isArray(o[key])) {
|
||||
o[key] = []; // define (or override) as array, to insert values using int keys as array indexes
|
||||
}
|
||||
} else { // nextKey is going to be the nested object's attribute
|
||||
if (isUndefined(o[key]) || !isObject(o[key])) {
|
||||
o[key] = {}; // define (or override) as object, to set nested properties
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Recursively set the inner object
|
||||
f.deepSet(o[key], tailKeys, value, opts);
|
||||
},
|
||||
|
||||
deepGet: function (o, keys) {
|
||||
var f = $.serializeJSON;
|
||||
if (isUndefined(o) || isUndefined(keys) || keys.length === 0 || (!isObject(o) && !isArray(o))) {
|
||||
return o;
|
||||
}
|
||||
var key = keys[0];
|
||||
if (key === "") { // "" means next array index (used by deepSet)
|
||||
return undefined;
|
||||
}
|
||||
if (keys.length === 1) {
|
||||
return o[key];
|
||||
}
|
||||
var tailKeys = keys.slice(1);
|
||||
return f.deepGet(o[key], tailKeys);
|
||||
}
|
||||
};
|
||||
|
||||
// polyfill Object.keys to get option keys in IE<9
|
||||
var objectKeys = function(obj) {
|
||||
if (Object.keys) {
|
||||
return Object.keys(obj);
|
||||
} else {
|
||||
var key, keys = [];
|
||||
for (key in obj) { keys.push(key); }
|
||||
return keys;
|
||||
}
|
||||
};
|
||||
|
||||
var isObject = function(obj) { return obj === Object(obj); }; // true for Objects and Arrays
|
||||
var isUndefined = function(obj) { return obj === void 0; }; // safe check for undefined values
|
||||
var isValidArrayIndex = function(val) { return /^[0-9]+$/.test(String(val)); }; // 1,2,3,4 ... are valid array indexes
|
||||
var isArray = Array.isArray || function(obj) { return Object.prototype.toString.call(obj) === "[object Array]"; };
|
||||
}));
|
|
@ -30,6 +30,7 @@ function configureNavbarForServers(){
|
|||
function configureSidebarForServers(){
|
||||
$("#clusterMembersLinkSidebar").hide()
|
||||
$("#clusterGroupsLinkSidebar").hide()
|
||||
$("#clusterGroupsLinkSidebar").hide()
|
||||
$("#instanceSidebarLinks").hide()
|
||||
$("#coreSidebarLinks").hide()
|
||||
$("#networkSidebarLinks").hide()
|
||||
|
@ -38,6 +39,7 @@ function configureSidebarForServers(){
|
|||
|
||||
function populateSidebarLinks(){
|
||||
$("#clusterMembersLinkSidebar").show()
|
||||
$("#clusterGroupsLinkSidebar").show()
|
||||
$("#instanceSidebarLinks").show()
|
||||
$("#coreSidebarLinks").show()
|
||||
$("#networkSidebarLinks").show()
|
||||
|
@ -60,6 +62,7 @@ function applySidebarLinks() {
|
|||
$("#networksLinkSidebar").attr("href", "networks?id=" + encodeURI(serverId) + "&project=" + encodeURI(project));
|
||||
$("#storagePoolsLinkSidebar").attr("href", "storage-pools?id=" + encodeURI(serverId) + "&project=" + encodeURI(project));
|
||||
$("#clusterMembersLinkSidebar").attr("href", "cluster-members?id=" + encodeURI(serverId) + "&project=" + encodeURI(project));
|
||||
$("#clusterGroupsLinkSidebar").attr("href", "cluster-groups?id=" + encodeURI(serverId) + "&project=" + encodeURI(project));
|
||||
$("#projectsLinkSidebar").attr("href", "projects?id=" + encodeURI(serverId) + "&project=" + encodeURI(project));
|
||||
$("#networkAclsLinkSidebar").attr("href", "network-acls?id=" + encodeURI(serverId) + "&project=" + encodeURI(project));
|
||||
$("#operationsLinkSidebar").attr("href", "operations?id=" + encodeURI(serverId) + "&project=" + encodeURI(project));
|
||||
|
|
|
@ -162,7 +162,7 @@
|
|||
// Add access control
|
||||
function addAccessControl(){
|
||||
console.log("Info: adding new group");
|
||||
data = $('#addForm').serializeJSON();
|
||||
data = $('#addForm').serialize();
|
||||
$.post("../api/access-controls/add_access_control", data, function (data) {
|
||||
console.log(data);
|
||||
if (data.error_code >= 400){
|
||||
|
@ -192,7 +192,7 @@
|
|||
// Update access control
|
||||
function updateAccessControl(){
|
||||
console.log("Info: adding new access control");
|
||||
data = $('#editForm').serializeJSON();
|
||||
data = $('#editForm').serialize();
|
||||
$.post("../api/access-controls/update_access_control", data, function (data) {
|
||||
console.log(data);
|
||||
if (data.error_code >= 400){
|
||||
|
|
|
@ -168,7 +168,7 @@
|
|||
|
||||
function addItem(){
|
||||
console.log("Info: adding new certificate");
|
||||
data = $('#addForm').serializeJSON();
|
||||
data = $('#addForm').serialize();
|
||||
$.post("../api/certificates/add_certificate?id="+serverId+"&project="+project, data, function (data) {
|
||||
console.log(data)
|
||||
if (data.error_code >= 400){
|
||||
|
|
266
lxconsole/templates/cluster-groups.html
Normal file
266
lxconsole/templates/cluster-groups.html
Normal file
|
@ -0,0 +1,266 @@
|
|||
{% extends "main.html" %}
|
||||
|
||||
{% block header %}
|
||||
<div class="row mb-2">
|
||||
<div class="col-sm-6">
|
||||
<h1>{{ page_title | safe }}</h1>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<a class="btn btn-outline-primary float-sm-right mr-4" href="#" data-toggle="modal" data-target="#addModal" title="Add Cluster Group" aria-hidden="true">
|
||||
<i class="fas fa-plus fa-sm fa-fw"></i> Cluster Group
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock header %}
|
||||
|
||||
{% block content %}
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Cluster Groups</h3>
|
||||
<div class="card-tools">
|
||||
<button type="button" class="btn btn-tool" onclick="reloadPageContent()" title="Refresh">
|
||||
<i class="fas fa-sync"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<table class="table table-hover" id="myDataTable" width="100%" cellspacing="0">
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock content %}
|
||||
|
||||
{% block modal %}
|
||||
{% include 'modals/cluster-groups.html' %}
|
||||
{% endblock modal %}
|
||||
|
||||
{% block script %}
|
||||
<script>
|
||||
var reloadTime = 10000;
|
||||
const queryString = window.location.search;
|
||||
const urlParams = new URLSearchParams(queryString);
|
||||
const serverId = urlParams.get('id');
|
||||
const project = urlParams.get('project');
|
||||
var editedClusterMember = ''
|
||||
applySidebarStyles();
|
||||
applySidebarLinks();
|
||||
populateSidebarLinks();
|
||||
populateNavbarLinks();
|
||||
|
||||
function reloadPageContent() {
|
||||
//Clear the automatic page reload
|
||||
clearTimeout(pageReloadTimeout);
|
||||
|
||||
//Reload the datatables content
|
||||
$('#myDataTable').DataTable().ajax.reload(null, false);
|
||||
|
||||
//Set the automatic page reload
|
||||
pageReloadTimeout = setTimeout(() => { reloadPageContent(); }, reloadTime);
|
||||
}
|
||||
|
||||
function loadPageContent(){
|
||||
|
||||
//Display the current project
|
||||
$("#selectedProject").text(project);
|
||||
|
||||
//Populate the Server dropdown
|
||||
$.getJSON("../api/servers/list_servers?id="+serverId, function (data) {
|
||||
data = data.data
|
||||
for (var index = 0; index < data.length; index++) {
|
||||
if (data[index].name == '')
|
||||
optionText = data[index].addr
|
||||
else
|
||||
optionText = data[index].name
|
||||
if (data[index].id == serverId)
|
||||
$('#serverListNav').append('<option value="' + data[index].id + '" selected="selected">' + optionText + '</option>');
|
||||
else
|
||||
$('#serverListNav').append('<option value="' + data[index].id + '">' + optionText + '</option>');
|
||||
}
|
||||
})
|
||||
|
||||
//Populate the Project dropdown
|
||||
$.getJSON("../api/projects/list_projects?id="+serverId+"&project="+project, function (data) {
|
||||
data = data.metadata
|
||||
for (var index = 0; index < data.length; index++) {
|
||||
optionText = data[index].replace('/1.0/projects/','');
|
||||
if (optionText == project)
|
||||
$('#projectListNav').append('<option value="' + optionText + '" selected="selected">' + optionText + '</option>');
|
||||
else
|
||||
$('#projectListNav').append('<option value="' + optionText + '">' + optionText + '</option>');
|
||||
}
|
||||
})
|
||||
|
||||
// Configure Datatable
|
||||
$('#myDataTable').DataTable({
|
||||
ajax: {
|
||||
url: "../api/cluster-groups/list_cluster_groups?id="+serverId+"&project=" + project + "&recursion=1",
|
||||
dataType: "json",
|
||||
dataSrc: "metadata",
|
||||
contentType: "application/json"
|
||||
},
|
||||
columns: [
|
||||
{ title: "Name", data: function (row, type, set) {
|
||||
if (row.hasOwnProperty('name')) {
|
||||
if (row.name)
|
||||
return row.name
|
||||
}
|
||||
return '-'
|
||||
},
|
||||
},
|
||||
{ title: "Description", data: function (row, type, set) {
|
||||
if (row.hasOwnProperty('description')) {
|
||||
if (row.description)
|
||||
return row.description
|
||||
}
|
||||
return '-'
|
||||
},
|
||||
},
|
||||
{ title: "Members", data: function (row, type, set) {
|
||||
if (row.hasOwnProperty('members')) {
|
||||
if (Object.keys(row.members).length > 0) {
|
||||
arr = []
|
||||
for (let i = 0; i < row.members.length; i++) {
|
||||
arr.push(row.members[i]);
|
||||
}
|
||||
return arr.join(", ")
|
||||
}
|
||||
}
|
||||
return '-'
|
||||
},
|
||||
},
|
||||
|
||||
{ title: "Actions", data: function (row, type, set) {
|
||||
links = ''
|
||||
if (row.hasOwnProperty('name')) {
|
||||
links += '<a href="#" onclick=editItem(\''+row.name+'\')><i class="fas fa-edit fa-lg" style="color:#ddd" title="Edit" aria-hidden="true"></i></a>' +
|
||||
' ' + ' ' +
|
||||
'<a href="#" onclick=confirmDeleteItem(\''+row.name+'\')><i class="fas fa-trash-alt fa-lg" style="color:#ddd" title="Delete" aria-hidden="true"></i></a>'
|
||||
}
|
||||
return links
|
||||
},
|
||||
},
|
||||
|
||||
],
|
||||
order: [],
|
||||
});
|
||||
|
||||
//Set reload page content
|
||||
pageReloadTimeout = setTimeout(() => { reloadPageContent(); }, reloadTime);
|
||||
|
||||
}
|
||||
|
||||
function addItem(){
|
||||
console.log("Info: adding new item");
|
||||
data = $('#addForm').serialize();
|
||||
$.post("../api/cluster-groups/add_cluster_group?id="+serverId+"&project="+project, data, function (data) {
|
||||
console.log(data)
|
||||
if (data.error_code >= 400){
|
||||
alert(data.error);
|
||||
}
|
||||
//Sync type
|
||||
reloadPageContent();
|
||||
});
|
||||
}
|
||||
|
||||
function confirmDeleteItem(name){
|
||||
console.log("Info: confirming deletion of item " + name);
|
||||
$("#deleteQuestionText").text("Are you sure you want to remove the cluster group " + name + "?");
|
||||
$("#clusterGroup").val(name);
|
||||
$("#deleteModal").modal('show');
|
||||
}
|
||||
|
||||
function createItemUsingJSON(){
|
||||
var json = $("#jsonCreateInput").val();
|
||||
console.log("Info: adding new cluster group");
|
||||
$.post("../api/cluster-groups/add_cluster_group?id="+serverId+"&project="+project, { json: json }, function (data) {
|
||||
console.log(data);
|
||||
if (data.error_code >= 400){
|
||||
alert(data.error);
|
||||
}
|
||||
//Sync type
|
||||
reloadPageContent();
|
||||
});
|
||||
}
|
||||
|
||||
function deleteItem(){
|
||||
name = $("#clusterGroup").val();
|
||||
console.log("Info: deleting item " + name);
|
||||
$.post("../api/cluster-groups/delete_cluster_group?id=" + serverId + "&project=" + project, { name: name }, function (data) {
|
||||
console.log(data);
|
||||
if (data.error_code >= 400){
|
||||
alert(data.error);
|
||||
}
|
||||
//Sync type
|
||||
reloadPageContent();
|
||||
});
|
||||
}
|
||||
|
||||
function editItem(name){
|
||||
editedClusterMember = name
|
||||
console.log("Info: loading cluster member " + name);
|
||||
$.post("../api/cluster-groups/load_cluster_group?id=" + serverId + "&project=" + project, { name: name }, function (data) {
|
||||
console.log(data);
|
||||
if (data.error_code >= 400){
|
||||
alert(data.error);
|
||||
}
|
||||
$("#storagePoolNameEditInput").text("Name: " + name);
|
||||
$("#jsonInput").val(JSON.stringify(data.metadata, null, 2));
|
||||
$("#editModal").modal('show');
|
||||
});
|
||||
}
|
||||
|
||||
function renameItem(){
|
||||
name = editedClusterMember
|
||||
console.log("Info: renaming cluster group");
|
||||
data = $('#renameForm').serialize();
|
||||
$.post("../api/cluster-groups/update_cluster_group?id=" + serverId + "&project=" + project + "&name=" + encodeURI(name), data, function (data) {
|
||||
console.log(data)
|
||||
if (data.error_code >= 400){
|
||||
alert(data.error);
|
||||
}
|
||||
//Sync type
|
||||
reloadPageContent();
|
||||
});
|
||||
}
|
||||
|
||||
function updateItem(){
|
||||
name = editedClusterMember
|
||||
var updatedJSON = $("#jsonInput").val();
|
||||
console.log("Info: updating cluster group");
|
||||
$.post("../api/cluster-groups/update_cluster_group?id=" + serverId + "&project=" + project + "&name=" + encodeURI(name), { json: updatedJSON }, function (data) {
|
||||
console.log(data);
|
||||
if (data.error_code >= 400){
|
||||
alert(data.error);
|
||||
}
|
||||
//Sync type
|
||||
reloadPageContent();
|
||||
});
|
||||
}
|
||||
|
||||
$(document).ready(function(){
|
||||
|
||||
//If id or project variables are missing redirect to servers page
|
||||
if (!serverId || !project) {
|
||||
window.location.href = 'servers';
|
||||
}
|
||||
else {
|
||||
loadPageContent()
|
||||
operationStatusCheck()
|
||||
|
||||
//Populate the Cluster Members Dropdown in Add Modal
|
||||
$.getJSON("../api/cluster-members/list_cluster_members?id="+serverId+"&project="+project, function (data) {
|
||||
data = data.metadata
|
||||
for (var index = 0; index < data.length; index++) {
|
||||
optionText = data[index].replace('/1.0/cluster/members/','');
|
||||
$('#clusterMembersInput').append('<option value="' + optionText + '">' + optionText + '</option>');
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
</script>
|
||||
{% endblock script %}
|
|
@ -119,6 +119,15 @@
|
|||
return '-'
|
||||
},
|
||||
},
|
||||
{ title: "Roles", data: function (row, type, set) {
|
||||
if (row.hasOwnProperty('roles')) {
|
||||
if (row.roles.length > 0){
|
||||
return row.roles.join(', <br />')
|
||||
}
|
||||
}
|
||||
return '-'
|
||||
},
|
||||
},
|
||||
{ title: "URL", data: function (row, type, set) {
|
||||
if (row.hasOwnProperty('url')) {
|
||||
if (row.url)
|
||||
|
@ -144,16 +153,24 @@
|
|||
},
|
||||
},
|
||||
|
||||
// { title: "Actions", data: function (row, type, set) {
|
||||
// links = ''
|
||||
// if (row.hasOwnProperty('server_name')) {
|
||||
// links += '<a href="#" onclick=editItem(\''+row.name+'\')><i class="fas fa-edit fa-lg" style="color:#ddd" title="Edit" aria-hidden="true"></i></a>' +
|
||||
// ' ' + ' ' +
|
||||
// '<a href="#" onclick=confirmDeleteItem(\''+row.name+'\')><i class="fas fa-trash-alt fa-lg" style="color:#ddd" title="Delete" aria-hidden="true"></i></a>'
|
||||
// }
|
||||
// return links
|
||||
// },
|
||||
// },
|
||||
{ title: "Actions", data: function (row, type, set) {
|
||||
links = ''
|
||||
if (row.hasOwnProperty('status')) {
|
||||
links += '<a href="#" onclick=editItem(\''+row.server_name+'\')><i class="fas fa-edit fa-lg" style="color:#ddd" title="Edit" aria-hidden="true"></i></a>'
|
||||
links += ' ' + ' '
|
||||
if (row.status == 'Online'){
|
||||
links += '<a href="#" onclick=changeItemState(\''+row.server_name+'\',\'evacuate\')><i class="fas fa-sign-out-alt fa-lg" style="color:#ddd" title="Evacuate" aria-hidden="true"></i></a>'
|
||||
links += ' ' + ' '
|
||||
}
|
||||
if (row.status == 'Evacuated'){
|
||||
links += '<a href="#" onclick=changeItemState(\''+row.server_name+'\',\'restore\')><i class="fas fa-sign-in-alt fa-lg" style="color:#ddd" title="Restore" aria-hidden="true"></i></a>'
|
||||
links += ' ' + ' '
|
||||
}
|
||||
links += '<a href="#" onclick=confirmDeleteItem(\''+row.server_name+'\')><i class="fas fa-trash-alt fa-lg" style="color:#ddd" title="Delete" aria-hidden="true"></i></a>'
|
||||
}
|
||||
return links
|
||||
},
|
||||
},
|
||||
|
||||
],
|
||||
order: [],
|
||||
|
@ -166,7 +183,7 @@
|
|||
|
||||
function addItem(){
|
||||
console.log("Info: adding new item");
|
||||
data = $('#addForm').serializeJSON();
|
||||
data = $('#addForm').serialize();
|
||||
$.post("../api/cluster-members/add_cluster_member?id="+serverId+"&project="+project, data, function (data) {
|
||||
console.log(data)
|
||||
if (data.error_code >= 400){
|
||||
|
@ -177,6 +194,22 @@
|
|||
});
|
||||
}
|
||||
|
||||
function changeItemState(name, action){
|
||||
console.log("Info: confirming " + action + " action of cluster member " + name);
|
||||
if (confirm("Are you sure you want to " + action + " cluster member " + name + "?") == true) {
|
||||
console.log("Info: " + name + " " + action + " action started");
|
||||
$.post("../api/cluster-members/change_cluster_member_state?id=" + serverId + "&project=" + project, { name: name, action: action }, function (data) {
|
||||
console.log(data);
|
||||
if (data.error_code >= 400){
|
||||
alert(data.error);
|
||||
}
|
||||
//Aync type
|
||||
setTimeout(() => { reloadPageContent(); }, 2000);
|
||||
operationStatusCheck()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function confirmDeleteItem(name){
|
||||
console.log("Info: confirming deletion of item " + name);
|
||||
$("#deleteQuestionText").text("Are you sure you want to remove " + name + " from the cluster?");
|
||||
|
@ -228,10 +261,25 @@
|
|||
});
|
||||
}
|
||||
|
||||
function renameItem(){
|
||||
name = editedClusterMember
|
||||
console.log("Info: renaming cluster group");
|
||||
data = $('#renameForm').serialize();
|
||||
$.post("../api/cluster-members/update_cluster_member?id=" + serverId + "&project=" + project + "&name=" + encodeURI(name), data, function (data) {
|
||||
console.log(data)
|
||||
if (data.error_code >= 400){
|
||||
alert(data.error);
|
||||
}
|
||||
//Sync type
|
||||
reloadPageContent();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function updateItem(){
|
||||
name = editedClusterMember
|
||||
var updatedJSON = $("#jsonInput").val();
|
||||
console.log("Info: updating cluster membere");
|
||||
console.log("Info: updating cluster member");
|
||||
$.post("../api/cluster-members/update_cluster_member?id=" + serverId + "&project=" + project + "&name=" + encodeURI(name), { json: updatedJSON }, function (data) {
|
||||
console.log(data);
|
||||
if (data.error_code >= 400){
|
||||
|
|
|
@ -3125,7 +3125,7 @@
|
|||
// Update the instance from the edit instance modal form
|
||||
function updateInstanceForm(){
|
||||
console.log("Info: updating instance " + instance);
|
||||
data = $('#editForm').serializeJSON();
|
||||
data = $('#editForm').serialize();
|
||||
$.post("../api/container/update_instance?id=" + serverId + "&project=" + project + "&instance=" + encodeURI(instance), data, function (data) {
|
||||
console.log(data);
|
||||
if (data.error_code >= 400){
|
||||
|
|
|
@ -116,6 +116,13 @@
|
|||
$('#containerLocationInput').append('<option value="' + optionText + '">' + optionText + '</option>');
|
||||
}
|
||||
})
|
||||
$.getJSON("../api/cluster-groups/list_cluster_groups?id="+serverId+"&project="+project, function (data) {
|
||||
data = data.metadata
|
||||
for (var index = 0; index < data.length; index++) {
|
||||
optionText = data[index].replace('/1.0/cluster/groups/','');
|
||||
$('#containerLocationInput').append('<option value="@' + optionText + '">@' + optionText + '</option>');
|
||||
}
|
||||
})
|
||||
|
||||
//Populate the modal Image dropdown
|
||||
$.getJSON("../api/images/list_images?id="+serverId+"&project="+project+"&recursion=1", function (data) {
|
||||
|
@ -264,7 +271,7 @@
|
|||
// Add instance
|
||||
function addItem(){
|
||||
console.log("Info: adding new container");
|
||||
data = $('#addForm').serializeJSON();
|
||||
data = $('#addForm').serialize();
|
||||
$.post("../api/containers/add_instance?id="+serverId+"&project="+project, data, function (data) {
|
||||
console.log(data);
|
||||
if (data.error_code >= 400){
|
||||
|
|
|
@ -104,7 +104,7 @@
|
|||
// Add group
|
||||
function addGroup(){
|
||||
console.log("Info: adding new group");
|
||||
data = $('#addForm').serializeJSON();
|
||||
data = $('#addForm').serialize();
|
||||
$.post("../api/groups/add_group", data, function (data) {
|
||||
console.log(data);
|
||||
if (data.error_code >= 400){
|
||||
|
@ -133,7 +133,7 @@
|
|||
// Update group
|
||||
function updateGroup(){
|
||||
console.log("Info: adding new group");
|
||||
data = $('#editForm').serializeJSON();
|
||||
data = $('#editForm').serialize();
|
||||
$.post("../api/groups/update_group", data, function (data) {
|
||||
console.log(data);
|
||||
if (data.error_code >= 400){
|
||||
|
|
|
@ -254,7 +254,7 @@
|
|||
|
||||
function addItem(){
|
||||
console.log("Info: downloading image");
|
||||
data = $('#addForm').serializeJSON();
|
||||
data = $('#addForm').serialize();
|
||||
$.post("../api/images/add_image?id="+serverId+"&project="+project, data, function (data) {
|
||||
console.log(data);
|
||||
if (data.error_code >= 400){
|
||||
|
|
|
@ -147,7 +147,6 @@
|
|||
<script src="../static/plugins/datatables-responsive/js/dataTables.responsive.min.js"></script>
|
||||
<script src="../static/plugins/datatables-responsive/js/responsive.bootstrap4.min.js"></script>
|
||||
<script src="../static/plugins/jquery-knob/jquery.knob.min.js"></script>
|
||||
<script src="../static/js/serializejson.js"></script>
|
||||
|
||||
<!-- AdminLTE App -->
|
||||
<script src="../static/dist/js/adminlte.min.js"></script>
|
||||
|
|
|
@ -40,7 +40,7 @@
|
|||
|
||||
<footer class="main-footer">
|
||||
<div class="float-right d-none d-sm-block">
|
||||
Version 0.1.0
|
||||
Version 0.2.0
|
||||
</div>
|
||||
Copyright © 2020-Present <a href="https://penninglabs.com">Penning Labs</a>. All rights reserved.
|
||||
</footer>
|
||||
|
@ -60,7 +60,6 @@
|
|||
<script src="../static/plugins/datatables-responsive/js/dataTables.responsive.min.js"></script>
|
||||
<script src="../static/plugins/datatables-responsive/js/responsive.bootstrap4.min.js"></script>
|
||||
<script src="../static/plugins/jquery-knob/jquery.knob.min.js"></script>
|
||||
<script src="../static/js/serializejson.js"></script>
|
||||
|
||||
<!-- AdminLTE App -->
|
||||
<script src="../static/dist/js/adminlte.min.js"></script>
|
||||
|
|
171
lxconsole/templates/modals/cluster-groups.html
Normal file
171
lxconsole/templates/modals/cluster-groups.html
Normal file
|
@ -0,0 +1,171 @@
|
|||
<!-- Add Modal-->
|
||||
<div class="modal fade" id="addModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="exampleModalLabel">Add Cluster Group</h5>
|
||||
<button class="close" type="button" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<ul class="nav nav-tabs" id="myTab" role="tablist">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" id="form-tab" data-toggle="tab" href="#form" role="tab" aria-controls="form" aria-selected="true">Form</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" id="json-tab" data-toggle="tab" href="#json" role="tab" aria-controls="json" aria-selected="false">JSON</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="tab-content" id="myTabContent">
|
||||
<div class="tab-pane fade show active" id="form" role="tabpanel" aria-labelledby="form-tab">
|
||||
<form id="addForm">
|
||||
<br />
|
||||
<div class="row">
|
||||
<label class="col-3 col-form-label text-right">Name: <span class="text-danger">*</span></label>
|
||||
<div class="col-7">
|
||||
<div class="form-group">
|
||||
<input type="text" class="form-control" placeholder="" name="name">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-1">
|
||||
<i class="far fa-sm fa-question-circle" title='(Required) - Enter in the name of the cluster group.'></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<label class="col-3 col-form-label text-right">Description: </label>
|
||||
<div class="col-7">
|
||||
<div class="form-group">
|
||||
<input type="text" class="form-control" placeholder="" name="description">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-1">
|
||||
<i class="far fa-sm fa-question-circle" title='Enter in a description for the cluster group.'></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<label class="col-3 col-form-label text-right">Cluster Members: </label>
|
||||
<div class="col-7">
|
||||
<div class="form-group">
|
||||
<select id="clusterMembersInput" class="select form-control" multiple name="members">
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-1">
|
||||
<i class="far fa-sm fa-question-circle" title='Select cluster members to add to this cluster group. Use Ctrl key for multiple select'></i>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-secondary" type="button" data-dismiss="modal">Cancel</button>
|
||||
<a class="btn btn-primary" href="#" onclick="addItem()" data-dismiss="modal">Submit</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-pane fade" id="json" role="tabpanel" aria-labelledby="json-tab">
|
||||
<br />
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="form-group text-right">
|
||||
<pre>
|
||||
<textarea name="json" class="form-control" id="jsonCreateInput" rows="16" placeholder="Enter JSON data"></textarea>
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-secondary" type="button" data-dismiss="modal">Cancel</button>
|
||||
<a class="btn btn-primary" href="#" onclick="createItemUsingJSON()" data-dismiss="modal">Submit</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Edit Modal-->
|
||||
<div class="modal fade" id="editModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="exampleModalLabel">Edit Cluster Group</h5>
|
||||
<button class="close" type="button" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<ul class="nav nav-tabs" id="myTab" role="tablist">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" id="json-edit-tab" data-toggle="tab" href="#json-edit" role="tab" aria-controls="json-edit" aria-selected="false">JSON</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" id="rename-edit-tab" data-toggle="tab" href="#rename-edit" role="tab" aria-controls="rename-edit" aria-selected="true">Rename</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="tab-content" id="myTabContent">
|
||||
<div class="tab-pane fade show active" id="json-edit" role="tabpanel" aria-labelledby="json-edit-tab">
|
||||
<br />
|
||||
<div class="row">
|
||||
<label class="col-4 col-form-label" id="itemNameEditInput"></label>
|
||||
<div class="col-12">
|
||||
<div class="form-group text-right">
|
||||
<pre>
|
||||
<textarea name="json" class="form-control" id="jsonInput" rows="16" ></textarea>
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-secondary" type="button" data-dismiss="modal">Cancel</button>
|
||||
<a class="btn btn-primary" href="#" onclick="updateItem()" data-dismiss="modal">Submit</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-pane fade" id="rename-edit" role="tabpanel" aria-labelledby="rename-edit-tab">
|
||||
<form id="renameForm">
|
||||
<br />
|
||||
<div class="row">
|
||||
<label class="col-2 col-form-label text-right">Name:</label>
|
||||
<div class="col-8">
|
||||
<div class="form-group">
|
||||
<input type="text" class="form-control" placeholder="" name="name">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-1">
|
||||
<i class="far fa-sm fa-question-circle" title='Enter in the new name of the cluster group.'></i>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-secondary" type="button" data-dismiss="modal">Cancel</button>
|
||||
<a class="btn btn-primary" href="#" onclick="renameItem()" data-dismiss="modal">Submit</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Delete Modal-->
|
||||
<div class="modal fade" id="deleteModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="exampleModalLabel">Delete Cluster Group</h5>
|
||||
<button class="close" type="button" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="mb-2" id="deleteQuestionText">
|
||||
Are you sure you want to delete this cluster group?
|
||||
</div>
|
||||
<input type="hidden" id="clusterGroup" class="form-control" name="server_name">
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-secondary" type="button" data-dismiss="modal">Cancel</button>
|
||||
<a class="btn btn-primary" href="#" onclick="deleteItem()" data-dismiss="modal">Yes</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -82,21 +82,55 @@
|
|||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="row">
|
||||
<label class="col-4 col-form-label" id="itemNameEditInput"></label>
|
||||
<div class="col-12">
|
||||
<div class="form-group text-right">
|
||||
<pre>
|
||||
<textarea name="json" class="form-control" id="jsonInput" rows="16" ></textarea>
|
||||
</pre>
|
||||
<ul class="nav nav-tabs" id="myTab" role="tablist">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" id="json-edit-tab" data-toggle="tab" href="#json-edit" role="tab" aria-controls="json-edit" aria-selected="false">JSON</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" id="rename-edit-tab" data-toggle="tab" href="#rename-edit" role="tab" aria-controls="rename-edit" aria-selected="true">Rename</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="tab-content" id="myTabContent">
|
||||
<div class="tab-pane fade show active" id="json-edit" role="tabpanel" aria-labelledby="json-edit-tab">
|
||||
<br />
|
||||
<div class="row">
|
||||
<label class="col-4 col-form-label" id="itemNameEditInput"></label>
|
||||
<div class="col-12">
|
||||
<div class="form-group text-right">
|
||||
<pre>
|
||||
<textarea name="json" class="form-control" id="jsonInput" rows="16" ></textarea>
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-secondary" type="button" data-dismiss="modal">Cancel</button>
|
||||
<a class="btn btn-primary" href="#" onclick="updateItem()" data-dismiss="modal">Submit</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-pane fade" id="rename-edit" role="tabpanel" aria-labelledby="rename-edit-tab">
|
||||
<form id="renameForm">
|
||||
<br />
|
||||
<div class="row">
|
||||
<label class="col-2 col-form-label text-right">Name:</label>
|
||||
<div class="col-8">
|
||||
<div class="form-group">
|
||||
<input type="text" class="form-control" placeholder="" name="server_name">
|
||||
</div>
|
||||
<span>After renaming a cluster member, restart the LXD service on the host to clear cached name information.</span>
|
||||
</div>
|
||||
<div class="col-1">
|
||||
<i class="far fa-sm fa-question-circle" title='Enter in the new name of the cluster member. Host alias will apply rename changes after LXD services have restarted on host.'></i>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-secondary" type="button" data-dismiss="modal">Cancel</button>
|
||||
<a class="btn btn-primary" href="#" onclick="renameItem()" data-dismiss="modal">Submit</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-secondary" type="button" data-dismiss="modal">Cancel</button>
|
||||
<a class="btn btn-primary" href="#" onclick="updateItem()" data-dismiss="modal">Submit</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -257,9 +257,10 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="col-1">
|
||||
<i class="far fa-sm fa-question-circle" title='Select the LXD cluster member to deploy this container on. Default: none'></i>
|
||||
<i class="far fa-sm fa-question-circle" title='Select the LXD cluster member or @group to deploy this container on. Default: none'></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class="mb-2">
|
||||
<nav>
|
||||
<div class="nav nav-pills justify-content-center" id="nav-tab" role="tablist">
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
<div class="col-12">
|
||||
<p>Lxconsole is an open source management console providing a web-based user interface capable of managing multiple LXD servers from a single location.</p>
|
||||
<p>
|
||||
<strong>Version</strong>: <span id="versionNumber">v0.1.0</span> <br />
|
||||
<strong>Version</strong>: <span id="versionNumber">v0.2.0</span> <br />
|
||||
<strong>License</strong>: AGPL-3.0 <br />
|
||||
<strong>URL</strong>: https://lxconsole.com <br />
|
||||
</p>
|
||||
|
|
|
@ -257,9 +257,10 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="col-1">
|
||||
<i class="far fa-sm fa-question-circle" title='Select the LXD cluster member to deploy this instance on. Default: none'></i>
|
||||
<i class="far fa-sm fa-question-circle" title='Select the LXD cluster member or @group to deploy this container on. Default: none'></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class="mb-2">
|
||||
<nav>
|
||||
<div class="nav nav-pills justify-content-center" id="nav-tab" role="tablist">
|
||||
|
|
|
@ -340,7 +340,7 @@
|
|||
|
||||
function addItem(){
|
||||
console.log("Info: adding new network-acl");
|
||||
data = $('#addForm').serializeJSON();
|
||||
data = $('#addForm').serialize();
|
||||
$.post("../api/network-acl/add_network_acl?id="+serverId+"&project="+project+"&acl="+acl, data, function (data) {
|
||||
console.log(data)
|
||||
if (data.error_code >= 400){
|
||||
|
|
|
@ -175,7 +175,7 @@
|
|||
|
||||
function addItem(){
|
||||
console.log("Info: adding new network-acl");
|
||||
data = $('#addForm').serializeJSON();
|
||||
data = $('#addForm').serialize();
|
||||
$.post("../api/network-acls/add_network_acl?id="+serverId+"&project="+project, data, function (data) {
|
||||
console.log(data)
|
||||
if (data.error_code >= 400){
|
||||
|
|
|
@ -190,7 +190,7 @@
|
|||
|
||||
function addItem(){
|
||||
console.log("Info: adding new network");
|
||||
data = $('#addForm').serializeJSON();
|
||||
data = $('#addForm').serialize();
|
||||
$.post("../api/networks/add_network?id="+serverId+"&project="+project, data, function (data) {
|
||||
console.log(data)
|
||||
if (data.error_code >= 400){
|
||||
|
|
|
@ -153,7 +153,7 @@
|
|||
|
||||
function addItem(){
|
||||
console.log("Info: adding new profile");
|
||||
data = $('#addForm').serializeJSON();
|
||||
data = $('#addForm').serialize();
|
||||
$.post("../api/profiles/add_profile?id="+serverId+"&project="+project, data, function (data) {
|
||||
console.log(data)
|
||||
if (data.error_code >= 400){
|
||||
|
|
|
@ -207,7 +207,7 @@
|
|||
|
||||
function addItem(){
|
||||
console.log("Info: adding new project");
|
||||
data = $('#addForm').serializeJSON();
|
||||
data = $('#addForm').serialize();
|
||||
$.post("../api/projects/add_project?id="+serverId+"&project="+project, data, function (data) {
|
||||
console.log(data)
|
||||
if (data.error_code >= 400){
|
||||
|
|
|
@ -165,7 +165,6 @@
|
|||
<script src="../static/plugins/datatables-responsive/js/dataTables.responsive.min.js"></script>
|
||||
<script src="../static/plugins/datatables-responsive/js/responsive.bootstrap4.min.js"></script>
|
||||
<script src="../static/plugins/jquery-knob/jquery.knob.min.js"></script>
|
||||
<script src="../static/js/serializejson.js"></script>
|
||||
|
||||
<!-- AdminLTE App -->
|
||||
<script src="../static/dist/js/adminlte.min.js"></script>
|
||||
|
|
|
@ -108,7 +108,7 @@
|
|||
// Add user
|
||||
function addRole(){
|
||||
console.log("Info: adding new role");
|
||||
data = $('#addForm').serializeJSON();
|
||||
data = $('#addForm').serialize();
|
||||
$.post("../api/roles/add_role", data, function (data) {
|
||||
console.log(data);
|
||||
if (data.error_code >= 400){
|
||||
|
|
|
@ -36,6 +36,14 @@
|
|||
</p>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" id="clusterGroupsLinkSidebar" href="cluster-groups" style="display: none;">
|
||||
<i id="clusterGroupsIcon" class="nav-icon fas fa-layer-group"></i>
|
||||
<p id="clusterGroupsSpan">
|
||||
Cluster Groups
|
||||
</p>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<ul id="instanceSidebarLinks" class="nav nav-pills nav-sidebar flex-column user-panel py-2" style="display: none;" data-widget="treeview" role="menu" data-accordion="false">
|
||||
|
|
|
@ -135,7 +135,7 @@
|
|||
|
||||
function addItem(){
|
||||
console.log("Info: adding new simplestreams repo");
|
||||
data = $('#addForm').serializeJSON();
|
||||
data = $('#addForm').serialize();
|
||||
$.post("../api/simplestreams/add_simplestream", data, function (data) {
|
||||
console.log(data)
|
||||
if (data.error_code >= 400){
|
||||
|
|
|
@ -174,7 +174,7 @@
|
|||
|
||||
function addItem(){
|
||||
console.log("Info: adding new storage pool");
|
||||
data = $('#addForm').serializeJSON();
|
||||
data = $('#addForm').serialize();
|
||||
$.post("../api/storage-pools/add_storage_pool?id="+serverId+"&project="+project, data, function (data) {
|
||||
console.log(data)
|
||||
if (data.error_code >= 400){
|
||||
|
|
|
@ -202,7 +202,7 @@
|
|||
|
||||
function addItem(){
|
||||
console.log("Info: adding new storage volume");
|
||||
data = $('#addForm').serializeJSON();
|
||||
data = $('#addForm').serialize();
|
||||
$.post("../api/storage-volumes/add_storage_volume?id="+serverId+"&project="+project+"&pool="+pool, data, function (data) {
|
||||
console.log(data)
|
||||
if (data.error_code >= 400){
|
||||
|
|
|
@ -133,7 +133,7 @@
|
|||
// Add user
|
||||
function addUser(){
|
||||
console.log("Info: adding new user");
|
||||
data = $('#addForm').serializeJSON();
|
||||
data = $('#addForm').serialize();
|
||||
$.post("../api/users/add_user", data, function (data) {
|
||||
console.log(data);
|
||||
if (data.error_code >= 400){
|
||||
|
@ -182,7 +182,7 @@
|
|||
// Update user
|
||||
function updateUser(){
|
||||
console.log("Info: adding new user");
|
||||
data = $('#editForm').serializeJSON();
|
||||
data = $('#editForm').serialize();
|
||||
$.post("../api/users/update_user", data, function (data) {
|
||||
console.log(data);
|
||||
if (data.error_code >= 400){
|
||||
|
@ -196,7 +196,7 @@
|
|||
// Update user group
|
||||
function updateUserGroup(){
|
||||
console.log("Info: editing user group");
|
||||
data = $('#editUserGroupsForm').serializeJSON();
|
||||
data = $('#editUserGroupsForm').serialize();
|
||||
$.post("../api/users/update_user", data, function (data) {
|
||||
console.log(data);
|
||||
if (data.error_code >= 400){
|
||||
|
@ -210,7 +210,7 @@
|
|||
// Update user password
|
||||
function updateUserPassword(){
|
||||
console.log("Info: editing user password");
|
||||
data = $('#editUserPasswordForm').serializeJSON();
|
||||
data = $('#editUserPasswordForm').serialize();
|
||||
$.post("../api/users/update_user", data, function (data) {
|
||||
console.log(data);
|
||||
if (data.error_code >= 400){
|
||||
|
|
|
@ -2832,7 +2832,7 @@
|
|||
// Update the instance from the edit instance modal form
|
||||
function updateInstanceForm(){
|
||||
console.log("Info: updating instance " + instance);
|
||||
data = $('#editForm').serializeJSON();
|
||||
data = $('#editForm').serialize();
|
||||
$.post("../api/virtual-machine/update_instance?id=" + serverId + "&project=" + project + "&instance=" + encodeURI(instance), data, function (data) {
|
||||
console.log(data);
|
||||
if (data.error_code >= 400){
|
||||
|
|
|
@ -116,6 +116,13 @@
|
|||
$('#virtualMachineLocationInput').append('<option value="' + optionText + '">' + optionText + '</option>');
|
||||
}
|
||||
})
|
||||
$.getJSON("../api/cluster-groups/list_cluster_groups?id="+serverId+"&project="+project, function (data) {
|
||||
data = data.metadata
|
||||
for (var index = 0; index < data.length; index++) {
|
||||
optionText = data[index].replace('/1.0/cluster/groups/','');
|
||||
$('#containerLocationInput').append('<option value="@' + optionText + '">@' + optionText + '</option>');
|
||||
}
|
||||
})
|
||||
|
||||
//Populate the modal Image dropdown
|
||||
$.getJSON("../api/images/list_images?id="+serverId+"&project="+project+"&recursion=1", function (data) {
|
||||
|
@ -260,7 +267,7 @@
|
|||
// Add instance
|
||||
function addItem(){
|
||||
console.log("Info: adding new virtual machine");
|
||||
data = $('#addForm').serializeJSON();
|
||||
data = $('#addForm').serialize();
|
||||
$.post("../api/virtual-machines/add_instance?id="+serverId+"&project="+project, data, function (data) {
|
||||
console.log(data);
|
||||
if (data.error_code >= 400){
|
||||
|
|
|
@ -1,6 +1,3 @@
|
|||
# 0.2.0
|
||||
- Add cluster groups
|
||||
|
||||
# 0.3.0
|
||||
- Add network-zones
|
||||
- Add network-forwards
|
||||
|
|
Loading…
Reference in a new issue