mirror of https://github.com/apache/cloudstack.git
[GSOC] Angular based UI
Signed-off-by: Sebastien Goasguen <runseb@gmail.com>
This commit is contained in:
parent
79419d4373
commit
ba83cd7092
|
|
@ -0,0 +1,2 @@
|
|||
#UI for CloudStack using Angular.js
|
||||
And a flask wrapper on top CloudStack API to make things easy on the client side.
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
from requester import make_request
|
||||
from precache import apicache
|
||||
from config import *
|
||||
import re
|
||||
|
||||
def get_error_code(error):
|
||||
return int(re.findall("\d{3}",error)[0]) #Find the error code by regular expression
|
||||
# return int(error[11:14]) #Ugly
|
||||
|
||||
def get_command(verb, subject):
|
||||
commandlist = apicache.get(verb, None)
|
||||
if commandlist is not None:
|
||||
command = commandlist.get(subject, None)
|
||||
if command is not None:
|
||||
return command["name"]
|
||||
return None
|
||||
|
||||
def apicall(command, data ):
|
||||
response, error = make_request(command, data, None, host, port, apikey, secretkey, protocol, path)
|
||||
if error is not None:
|
||||
return error, get_error_code(error)
|
||||
return response
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
from flask import Flask, url_for, render_template, request, json, abort, send_from_directory
|
||||
from api import apicall
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
def get_args(multidict):
|
||||
"""Default type of request.args or request.json is multidict. Converts it to dict so that can be passed to make_request"""
|
||||
data = {}
|
||||
for key in multidict.keys():
|
||||
data[key] = multidict.get(key)
|
||||
return data
|
||||
|
||||
@app.route('/api/<command>', methods=['GET'])
|
||||
def rawapi(command):
|
||||
if request.method == 'GET':
|
||||
return apicall(command, get_args(request.args))
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
return send_from_directory("templates", "index.html")
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(debug=True)
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
apikey='DNi_vTVLPNfTEFuqu5F9MrPI3iecf8iRQ3QtGUH1IM2Nd96wNwNlf7BzmF1W8aw6cE2ejZCgyE53wT5VpzauuA'
|
||||
secretkey='x4jM12uE4LNho3ZNJa8J-Ve6WsgEXd8df1mGGfeuJHMtolkaSBkD5pLX0tvj8YrWhBgtZbKgYsTB00kb7z_3BA'
|
||||
path='/client/api'
|
||||
host='localhost'
|
||||
port='8080'
|
||||
protocol='http'
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,153 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
try:
|
||||
import base64
|
||||
import hashlib
|
||||
import hmac
|
||||
import httplib
|
||||
import json
|
||||
import os
|
||||
import pdb
|
||||
import re
|
||||
import shlex
|
||||
import sys
|
||||
import time
|
||||
import types
|
||||
import urllib
|
||||
import urllib2
|
||||
|
||||
except ImportError, e:
|
||||
print "Import error in %s : %s" % (__name__, e)
|
||||
import sys
|
||||
sys.exit()
|
||||
|
||||
|
||||
def logger_debug(logger, message):
|
||||
if logger is not None:
|
||||
logger.debug(message)
|
||||
|
||||
|
||||
def make_request(command, args, logger, host, port,
|
||||
apikey, secretkey, protocol, path):
|
||||
response = None
|
||||
error = None
|
||||
|
||||
if protocol != 'http' and protocol != 'https':
|
||||
error = "Protocol must be 'http' or 'https'"
|
||||
return None, error
|
||||
|
||||
if args is None:
|
||||
args = {}
|
||||
|
||||
args["command"] = command
|
||||
args["apiKey"] = apikey
|
||||
args["response"] = "json"
|
||||
request = zip(args.keys(), args.values())
|
||||
request.sort(key=lambda x: x[0].lower())
|
||||
|
||||
request_url = "&".join(["=".join([r[0], urllib.quote_plus(str(r[1]))])
|
||||
for r in request])
|
||||
hashStr = "&".join(["=".join([r[0].lower(),
|
||||
str.lower(urllib.quote_plus(str(r[1]))).replace("+",
|
||||
"%20")]) for r in request])
|
||||
|
||||
sig = urllib.quote_plus(base64.encodestring(hmac.new(secretkey, hashStr,
|
||||
hashlib.sha1).digest()).strip())
|
||||
request_url += "&signature=%s" % sig
|
||||
request_url = "%s://%s:%s%s?%s" % (protocol, host, port, path, request_url)
|
||||
|
||||
try:
|
||||
logger_debug(logger, "Request sent: %s" % request_url)
|
||||
connection = urllib2.urlopen(request_url)
|
||||
response = connection.read()
|
||||
except Exception, e:
|
||||
error = str(e)
|
||||
|
||||
logger_debug(logger, "Response received: %s" % response)
|
||||
if error is not None:
|
||||
logger_debug(logger, error)
|
||||
|
||||
return response, error
|
||||
|
||||
|
||||
def monkeyrequest(command, args, isasync, asyncblock, logger, host, port,
|
||||
apikey, secretkey, timeout, protocol, path):
|
||||
|
||||
response = None
|
||||
error = None
|
||||
logger_debug(logger, "======== START Request ========")
|
||||
logger_debug(logger, "Requesting command=%s, args=%s" % (command, args))
|
||||
response, error = make_request(command, args, logger, host, port,
|
||||
apikey, secretkey, protocol, path)
|
||||
logger_debug(logger, "======== END Request ========\n")
|
||||
|
||||
if error is not None:
|
||||
return response, error
|
||||
|
||||
def process_json(response):
|
||||
try:
|
||||
response = json.loads(str(response))
|
||||
except ValueError, e:
|
||||
error = "Error processing json response, %s" % e
|
||||
logger_debug(logger, "Error processing json", e)
|
||||
return response
|
||||
|
||||
response = process_json(response)
|
||||
if response is None:
|
||||
return response, error
|
||||
|
||||
isasync = isasync and (asyncblock == "true")
|
||||
responsekey = filter(lambda x: 'response' in x, response.keys())[0]
|
||||
|
||||
if isasync and 'jobid' in response[responsekey]:
|
||||
jobid = response[responsekey]['jobid']
|
||||
command = "queryAsyncJobResult"
|
||||
request = {'jobid': jobid}
|
||||
timeout = int(timeout)
|
||||
pollperiod = 3
|
||||
progress = 1
|
||||
while timeout > 0:
|
||||
print '\r' + '.' * progress,
|
||||
time.sleep(pollperiod)
|
||||
timeout = timeout - pollperiod
|
||||
progress += 1
|
||||
logger_debug(logger, "Job %s to timeout in %ds" % (jobid, timeout))
|
||||
sys.stdout.flush()
|
||||
response, error = monkeyrequest(command, request, isasync,
|
||||
asyncblock, logger,
|
||||
host, port, apikey, secretkey,
|
||||
timeout, protocol, path)
|
||||
response = process_json(response)
|
||||
responsekeys = filter(lambda x: 'response' in x, response.keys())
|
||||
if len(responsekeys) < 1:
|
||||
continue
|
||||
result = response[responsekeys[0]]
|
||||
jobstatus = result['jobstatus']
|
||||
if jobstatus == 2:
|
||||
jobresult = result["jobresult"]
|
||||
error = "\rAsync job %s failed\nError %s, %s" % (jobid,
|
||||
jobresult["errorcode"], jobresult["errortext"])
|
||||
return response, error
|
||||
elif jobstatus == 1:
|
||||
print '\r',
|
||||
return response, error
|
||||
error = "Error: Async query timeout occurred for jobid %s" % jobid
|
||||
|
||||
return response, error
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
Binary file not shown.
|
After Width: | Height: | Size: 8.6 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,27 @@
|
|||
body {
|
||||
padding-top: 60px; /* 60px to make the container go all the way to the bottom of the topbar */
|
||||
}
|
||||
|
||||
.sidebar-nav {
|
||||
padding: 9px 0;
|
||||
}
|
||||
|
||||
[ng\:cloak], [ng-cloak], .ng-cloak {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
div.loading {
|
||||
position: fixed;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
background-image: url('/static/images/ajax-loader.gif');
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-color: white;
|
||||
border: 1px solid green;
|
||||
-moz-border-radius: 10px;
|
||||
-webkit-border-radius: 10px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 6.3 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.1 KiB |
|
|
@ -0,0 +1,85 @@
|
|||
angular.module('accounts', ['resources.accounts', 'resources.domains', 'services.breadcrumbs']).
|
||||
config(['$routeProvider', function($routeProvider){
|
||||
$routeProvider.
|
||||
when('/accounts', {
|
||||
controller: 'AccountsListCtrl',
|
||||
templateUrl: '/static/js/app/accounts/accounts.tpl.html',
|
||||
resolve: {
|
||||
accounts: function(Accounts){
|
||||
return Accounts.getAll();
|
||||
}
|
||||
}
|
||||
})
|
||||
}]);
|
||||
|
||||
angular.module('accounts').controller('AccountsListCtrl', ['$scope', 'accounts', 'Breadcrumbs', 'Accounts', 'Domains',
|
||||
function($scope, accounts, Breadcrumbs, Accounts, Domains){
|
||||
Breadcrumbs.refresh();
|
||||
Breadcrumbs.push('Accounts', '/#/accounts');
|
||||
$scope.collection = accounts;
|
||||
$scope.toDisplay = ['name', 'domain', 'state'];
|
||||
|
||||
$scope.addAccountForm = {
|
||||
title: 'Add Account',
|
||||
onSubmit: Accounts.create,
|
||||
fields: [
|
||||
{
|
||||
model: 'username',
|
||||
type: 'input-text',
|
||||
label: 'username'
|
||||
},
|
||||
{
|
||||
model: 'password',
|
||||
type: 'input-password',
|
||||
label: 'password'
|
||||
},
|
||||
{
|
||||
model: 'email',
|
||||
type: 'input-text',
|
||||
label: 'email'
|
||||
},
|
||||
{
|
||||
model: 'firstname',
|
||||
type: 'input-text',
|
||||
label: 'firstname'
|
||||
},
|
||||
{
|
||||
model: 'lastname',
|
||||
type: 'input-text',
|
||||
label: 'lastname'
|
||||
},
|
||||
{
|
||||
model: 'domainid',
|
||||
type: 'select',
|
||||
label: 'domain',
|
||||
options: Domains.fetch,
|
||||
getName: function(model){
|
||||
return model.name;
|
||||
},
|
||||
getValue: function(model){
|
||||
return model.id;
|
||||
}
|
||||
},
|
||||
{
|
||||
model: 'account',
|
||||
type: 'input-text',
|
||||
label: 'account'
|
||||
},
|
||||
{
|
||||
model: 'accounttype',
|
||||
type: 'select',
|
||||
label: 'type',
|
||||
options: function(){
|
||||
return ['User', 'Admin']
|
||||
},
|
||||
getName: function(model){
|
||||
return model;
|
||||
},
|
||||
getValue: function(model){
|
||||
//return 0 if user, else 1
|
||||
return model === 'User'?0:1;
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}]);
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
<div class="well well-small form-inline">
|
||||
<input type="text" placeholder="Search" class="input-medium search-query" ng-model="search.name">
|
||||
<modal-form form-details="addAccountForm">
|
||||
<button class="btn">Add Account</button>
|
||||
</modal-form>
|
||||
</div>
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th ng-repeat="attribute in toDisplay"> {{dictionary.labels[attribute]}} </th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="model in collection">
|
||||
<td ng-repeat="attribute in toDisplay">{{model[attribute]}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
angular.module('cloudstack', [
|
||||
'ui.bootstrap',
|
||||
'instances',
|
||||
'storage',
|
||||
'networks',
|
||||
'templates',
|
||||
'events',
|
||||
'accounts',
|
||||
'domains',
|
||||
'projects',
|
||||
'globalsettings',
|
||||
'serviceofferings',
|
||||
'services.breadcrumbs',
|
||||
'services.notifications',
|
||||
'directives.confirm',
|
||||
'directives.modalForm',
|
||||
'directives.label',
|
||||
'directives.editInPlace',
|
||||
]).
|
||||
config(["$routeProvider", function($routeProvider){
|
||||
$routeProvider.
|
||||
when('/',{
|
||||
controller: "DefaultCtrl",
|
||||
templateUrl: "default.html"
|
||||
}).
|
||||
otherwise({
|
||||
redirectTo: '/'
|
||||
})
|
||||
}]);
|
||||
|
||||
angular.module("cloudstack").controller("DefaultCtrl", ["$scope", "Breadcrumbs", function($scope, Breadcrumbs){
|
||||
Breadcrumbs.refresh();
|
||||
}]);
|
||||
|
||||
angular.module("cloudstack").controller("AppCtrl", ["$scope", "Breadcrumbs", "Notifications", "Dictionary", "$rootScope",
|
||||
function($scope, Breadcrumbs, Notifications, Dictionary, $rootScope){
|
||||
$scope.breadcrumbs = Breadcrumbs;
|
||||
$scope.dictionary = Dictionary;
|
||||
$scope.notifications = Notifications;
|
||||
|
||||
$scope.loading = false;
|
||||
|
||||
$rootScope.$on("$routeChangeStart", function(event, next, current){
|
||||
$scope.loading = true;
|
||||
});
|
||||
|
||||
$rootScope.$on("$routeChangeSuccess", function(event, current, previous){
|
||||
$scope.loading = false;
|
||||
});
|
||||
}]);
|
||||
|
||||
angular.module("cloudstack").controller("HeaderCtrl", ["$scope", function($scope){
|
||||
}]);
|
||||
|
||||
angular.module("cloudstack").controller("NavCtrl", ["$scope", "$location", function($scope, $location){
|
||||
$scope.isActive = function(page){
|
||||
if($location.path() === '/' && page === '/') return 'active'; //home page
|
||||
return $location.path().split('/')[1] === page? 'active': '';
|
||||
}
|
||||
}]);
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
angular.module('domains', ['resources.domains', 'services.breadcrumbs']).
|
||||
config(['$routeProvider', function($routeProvider){
|
||||
$routeProvider.
|
||||
when('/domains',{
|
||||
controller: 'DomainsListCtrl',
|
||||
templateUrl: 'table.html',
|
||||
resolve: {
|
||||
domains: function(Domains){
|
||||
return Domains.fetch();
|
||||
}
|
||||
}
|
||||
})
|
||||
}]);
|
||||
|
||||
var DomainsListCtrl = angular.module('domains').controller('DomainsListCtrl', ['$scope', 'domains', 'Breadcrumbs', function($scope, domains, Breadcrumbs){
|
||||
Breadcrumbs.refresh();
|
||||
Breadcrumbs.push('domains', '/#/domains');
|
||||
$scope.collection = domains;
|
||||
$scope.toDisplay = ['id', 'name'];
|
||||
}]);
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
angular.module('events', ['resources.events', 'services.breadcrumbs']).
|
||||
config(['$routeProvider', function($routeProvider){
|
||||
$routeProvider.
|
||||
when('/events', {
|
||||
controller: 'EventsListCtrl',
|
||||
templateUrl: 'table.html',
|
||||
resolve: {
|
||||
events: function(Events){
|
||||
return Events.fetch();
|
||||
}
|
||||
}
|
||||
})
|
||||
}]);
|
||||
|
||||
angular.module('events').controller('EventsListCtrl', ['$scope', 'events', 'Breadcrumbs', function($scope, events, Breadcrumbs){
|
||||
Breadcrumbs.refresh();
|
||||
Breadcrumbs.push('events', '/#/events');
|
||||
$scope.collection = events;
|
||||
$scope.toDisplay = ['type', 'description', 'account', 'created'];
|
||||
}]);
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
angular.module('globalsettings', ['resources.configurations', 'services.breadcrumbs', 'services.notifications']).
|
||||
config(['$routeProvider', function($routeProvider){
|
||||
$routeProvider.
|
||||
when('/configurations', {
|
||||
controller: 'ConfigurationsListCtrl',
|
||||
templateUrl: '/static/js/app/globalsettings/globalsettings.tpl.html',
|
||||
resolve: {
|
||||
configurations: function(Configurations){
|
||||
return Configurations.getAll();
|
||||
}
|
||||
}
|
||||
})
|
||||
}]);
|
||||
|
||||
angular.module('globalsettings').controller('ConfigurationsListCtrl', ['$scope', 'configurations', 'Breadcrumbs', 'Notifications',
|
||||
function($scope, configurations, Breadcrumbs, Notifications){
|
||||
Breadcrumbs.refresh();
|
||||
Breadcrumbs.push('Configurations', '/#/configurations');
|
||||
$scope.collection = configurations;
|
||||
$scope.toDisplay = ['name', 'description', 'value'];
|
||||
}]);
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
<div class="well well-small form-inline">
|
||||
<input type="text" placeholder="Search" class="input-medium search-query" ng-model="search.name">
|
||||
</div>
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{dictionary.labels.name}} </th>
|
||||
<th>{{dictionary.labels.value}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="model in collection | filter:search">
|
||||
<td>
|
||||
<span tooltip="{{model.description}}">{{model.name}}</span>
|
||||
</td>
|
||||
<td><edit-in-place model="model" attribute="value" on-save="model.update()"></edit-in-place></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
<table class="table table-bordered">
|
||||
<tr ng-repeat="(attribute, value) in model">
|
||||
<td>{{attribute}}</td>
|
||||
<td>{{value}}</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
angular.module("instances", ['resources.virtualmachines', 'services.breadcrumbs', 'services.notifications']).
|
||||
config(['$routeProvider', function($routeProvider){
|
||||
$routeProvider.
|
||||
when('/instances', {
|
||||
controller: 'VirtualMachinesListCtrl',
|
||||
templateUrl: '/static/js/app/instances/instances.tpl.html',
|
||||
resolve:{
|
||||
virtualmachines : function(VirtualMachines){
|
||||
return VirtualMachines.getAll();
|
||||
}
|
||||
}
|
||||
}).
|
||||
when('/instances/:id', {
|
||||
controller: 'VirtualMachineDetailCtrl',
|
||||
templateUrl: '/static/js/app/instances/instance-details.tpl.html',
|
||||
resolve: {
|
||||
virtualmachine: function($route, VirtualMachines){
|
||||
return VirtualMachines.getById($route.current.params.id);
|
||||
}
|
||||
}
|
||||
})
|
||||
}]);
|
||||
|
||||
angular.module("instances").controller("VirtualMachinesListCtrl",
|
||||
["$scope", "virtualmachines", "Breadcrumbs", "Notifications", function($scope, virtualmachines, Breadcrumbs, Notifications){
|
||||
Breadcrumbs.refresh();
|
||||
Breadcrumbs.push('Instances', '/#/instances');
|
||||
$scope.collection = virtualmachines;
|
||||
$scope.toDisplay = ["displayname", "instancename", "zonename", "state"];
|
||||
}]);
|
||||
|
||||
angular.module("instances").controller("VirtualMachineDetailCtrl", ["$scope", "virtualmachine", "Breadcrumbs", function($scope, virtualmachine, Breadcrumbs){
|
||||
Breadcrumbs.refresh();
|
||||
Breadcrumbs.push('Instances', '/#/instances');
|
||||
Breadcrumbs.push(virtualmachine.displayname, '/#/instances/'+ virtualmachine.id);
|
||||
$scope.model = virtualmachine;
|
||||
}]);
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
<div class="well well-small form-inline">
|
||||
<input type="text" placeholder="Search" class="input-medium search-query" ng-model="search.displayname">
|
||||
<label>
|
||||
Display only:
|
||||
<select ng-model="search.state">
|
||||
<option value="">All</option>
|
||||
<option value="Stopped">Stopped</option>
|
||||
<option value="Running">Running</option>
|
||||
<option value="Destroyed">Destroyed</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th ng-repeat="attribute in toDisplay"> {{dictionary.labels[attribute]}} </th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="model in collection | filter:search">
|
||||
<td>
|
||||
<a href="{{'/#/instances/' + model.id}}">{{model.displayname}}</a>
|
||||
</td>
|
||||
<td>{{model.instancename}}</td>
|
||||
<td>{{model.zonename}}</td>
|
||||
<td><vm-state-label vm="model"></vm-state-label></td>
|
||||
<td>
|
||||
<confirm on-ok="model.start()" action="Start VirtualMachine"><i class="icon-play"></i></confirm>
|
||||
<confirm on-ok="model.stop()" action="Stop VirtualMachine"><i class="icon-ban-circle"></i></confirm>
|
||||
<confirm on-ok="model.reboot()" action="Reboot VirtualMachine"><i class="icon-repeat"></i></confirm>
|
||||
<confirm on-ok="model.destroy()" action="Destroy VirtualMachine"><i class="icon-remove"></i></confirm>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
angular.module('networks', ['resources.networks', 'services.breadcrumbs']).
|
||||
config(['$routeProvider', function($routeProvider){
|
||||
$routeProvider.
|
||||
when('/networks',{
|
||||
controller: 'NetworksListCtrl',
|
||||
templateUrl: 'table.html',
|
||||
resolve: {
|
||||
networks: function(Networks){
|
||||
return Networks.fetch();
|
||||
}
|
||||
}
|
||||
})
|
||||
}]);
|
||||
|
||||
angular.module('networks').controller('NetworksListCtrl', ['$scope', 'networks', 'Breadcrumbs', function($scope, networks, Breadcrumbs){
|
||||
Breadcrumbs.refresh();
|
||||
Breadcrumbs.push('networks', '/#/networks');
|
||||
$scope.collection = networks;
|
||||
$scope.toDisplay = ['name', 'type', 'zonename'];
|
||||
}]);
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
angular.module('projects', ['resources.projects', 'services.breadcrumbs']).
|
||||
config(['$routeProvider', function($routeProvider){
|
||||
$routeProvider.
|
||||
when('/projects', {
|
||||
controller: 'ProjectsListCtrl',
|
||||
templateUrl: 'table.html',
|
||||
resolve: {
|
||||
projects: function(Projects){
|
||||
return Projects.fetch();
|
||||
}
|
||||
}
|
||||
})
|
||||
}]);
|
||||
|
||||
angular.module('projects').controller('ProjectsListCtrl', ['$scope', 'projects', 'Breadcrumbs', function($scope, projects, Breadcrumbs){
|
||||
Breadcrumbs.refresh();
|
||||
Breadcrumbs.push('projects', '/#/projects');
|
||||
$scope.collection = projects;
|
||||
$scope.toDisplay = ['name', 'displaytext', 'domain', 'account', 'state']
|
||||
}]);
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
angular.module('serviceofferings', ['resources.serviceofferings', 'services.breadcrumbs']).
|
||||
config(['$routeProvider', function($routeProvider){
|
||||
$routeProvider.
|
||||
when('/serviceofferings', {
|
||||
controller: 'ServiceOfferingsListCtrl',
|
||||
templateUrl: 'table.html',
|
||||
resolve: {
|
||||
serviceofferings: function(ServiceOfferings){
|
||||
return ServiceOfferings.fetch();
|
||||
}
|
||||
}
|
||||
})
|
||||
}]);
|
||||
|
||||
angular.module('serviceofferings').controller('ServiceOfferingsListCtrl', ['$scope', 'serviceofferings', 'Breadcrumbs', function($scope, serviceofferings, Breadcrumbs){
|
||||
Breadcrumbs.refresh();
|
||||
Breadcrumbs.push('serviceofferings', '/#/serviceofferings');
|
||||
$scope.collection = serviceofferings
|
||||
$scope.toDisplay = ['name', 'displaytext'];
|
||||
}]);
|
||||
|
|
@ -0,0 +1,136 @@
|
|||
angular.module("storage", ["resources.volumes", "resources.snapshots", "resources.zones", "resources.diskofferings", "services.breadcrumbs"]).
|
||||
config(['$routeProvider', function($routeProvider){
|
||||
$routeProvider.
|
||||
when('/volumes',{
|
||||
controller: 'VolumesListCtrl',
|
||||
templateUrl: '/static/js/app/storage/storage.tpl.html',
|
||||
resolve: {
|
||||
volumes: function(Volumes){
|
||||
return Volumes.getAll();
|
||||
}
|
||||
}
|
||||
}).
|
||||
when('/snapshots', {
|
||||
controller: 'SnapshotsListCtrl',
|
||||
templateUrl: 'table.html',
|
||||
resolve:{
|
||||
snapshots: function(Snapshots){
|
||||
return Snapshots.getAll();
|
||||
}
|
||||
}
|
||||
})
|
||||
}]);
|
||||
|
||||
angular.module("storage").controller("VolumesListCtrl", ["$scope", "$location", "volumes", "Breadcrumbs", "Volumes", "Zones", "DiskOfferings",
|
||||
function($scope, $location, volumes, Breadcrumbs, Volumes, Zones, DiskOfferings){
|
||||
Breadcrumbs.refresh();
|
||||
Breadcrumbs.push('Volumes', '/#/volumes');
|
||||
$scope.collection = volumes;
|
||||
$scope.view = 'volumes';
|
||||
$scope.toDisplay = ['name', 'type', 'hypervisor', 'vmdisplayname'];
|
||||
|
||||
$scope.addVolumeForm = {
|
||||
title: 'Add Volume',
|
||||
onSubmit: Volumes.getAll,
|
||||
fields: [
|
||||
{
|
||||
model: 'name',
|
||||
type: 'input-text',
|
||||
label: 'name',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
model: 'zoneid',
|
||||
type: 'select',
|
||||
label: 'availabilityZone',
|
||||
options: Zones.getAll,
|
||||
getValue: function(model){
|
||||
return model.id;
|
||||
},
|
||||
getName: function(model){
|
||||
return model.name;
|
||||
}
|
||||
},
|
||||
{
|
||||
model: 'diskofferingid',
|
||||
type: 'select',
|
||||
label: 'diskoffering',
|
||||
options: DiskOfferings.getAll,
|
||||
getValue: function(model){
|
||||
return model.id;
|
||||
},
|
||||
getName: function(model){
|
||||
return model.name;
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
$scope.uploadVolumeForm = {
|
||||
title: 'Upload Volume',
|
||||
onSubmit: Volumes.getAll,
|
||||
fields: [
|
||||
{
|
||||
model: 'name',
|
||||
type: 'input-text',
|
||||
label: 'name',
|
||||
},
|
||||
{
|
||||
model: 'zoneid',
|
||||
type: 'select',
|
||||
label: 'availabilityZone',
|
||||
options: Zones.getAll,
|
||||
getValue: function(model){
|
||||
return model.id;
|
||||
},
|
||||
getName: function(model){
|
||||
return model.name;
|
||||
}
|
||||
},
|
||||
{
|
||||
model: 'format',
|
||||
type: 'select',
|
||||
label: 'format',
|
||||
options: function(){
|
||||
return ['RAW', 'VHD', 'OVA', 'QCOW2'];
|
||||
},
|
||||
getValue: function(model){
|
||||
return model;
|
||||
},
|
||||
getName: function(model){
|
||||
return model;
|
||||
}
|
||||
},
|
||||
{
|
||||
model: 'url',
|
||||
type: 'input-text',
|
||||
label: 'url'
|
||||
},
|
||||
{
|
||||
model: 'checksum',
|
||||
type: 'input-text',
|
||||
label: 'checksum'
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
$scope.$watch('view', function(newVal, oldVal){
|
||||
if(newVal === oldVal) return;
|
||||
if(newVal === 'volumes') return;
|
||||
else $location.path('/snapshots');
|
||||
});
|
||||
}]);
|
||||
|
||||
angular.module("storage").controller("SnapshotsListCtrl", ["$scope", "$location", "snapshots", "Breadcrumbs", function($scope, $location, snapshots, Breadcrumbs){
|
||||
Breadcrumbs.refresh();
|
||||
Breadcrumbs.push('Snapshots', '/#/snapshots');
|
||||
$scope.collection = snapshots;
|
||||
$scope.view = "snapshots";
|
||||
$scope.toDisplay = ['volumename', 'intervaltype', 'created', 'state'];
|
||||
|
||||
$scope.$watch('view', function(newVal, oldVal){
|
||||
if(newVal === oldVal) return;
|
||||
if(newVal === 'snapshots') return;
|
||||
else $location.path('/volumes');
|
||||
});
|
||||
}]);
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
<div class="well well-small form-inline">
|
||||
<input type="text" placeholder="Search" class="input-medium search-query" ng-model="search.name">
|
||||
<label>
|
||||
Select view:
|
||||
<select ng-model="view">
|
||||
<option value="volumes">Volumes</option>
|
||||
<option value="snapshots">Snapshots</option>
|
||||
</select>
|
||||
</label>
|
||||
<modal-form form-details="addVolumeForm">
|
||||
<button class="btn">Add Volume</button>
|
||||
</modal-form>
|
||||
<modal-form form-details="uploadVolumeForm">
|
||||
<button class="btn">Upload Volume</button>
|
||||
</modal-form>
|
||||
</div>
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th ng-repeat="attribute in toDisplay"> {{dictionary.labels[attribute]}} </th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="model in collection | filter:search">
|
||||
<td>{{model.name}}</td>
|
||||
<td>{{model.type}}</td>
|
||||
<td>{{model.hypervisor}}</td>
|
||||
<td>{{model.vmdisplayname}}</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
angular.module('templates', ['resources.templates', 'services.breadcrumbs']).
|
||||
config(['$routeProvider', function($routeProvider){
|
||||
$routeProvider.
|
||||
when('/templates', {
|
||||
controller: 'TemplatesListCtrl',
|
||||
templateUrl: 'table.html',
|
||||
resolve: {
|
||||
templates: function(Templates){
|
||||
return Templates.getAll();
|
||||
}
|
||||
}
|
||||
})
|
||||
}]);
|
||||
|
||||
angular.module('templates').controller('TemplatesListCtrl', ['$scope', 'templates', 'Breadcrumbs', function($scope, templates, Breadcrumbs){
|
||||
Breadcrumbs.refresh();
|
||||
Breadcrumbs.push('Templates', '/#/templates');
|
||||
$scope.collection = templates;
|
||||
$scope.toDisplay = ['name', 'domain', 'hypervisor'];
|
||||
}]);
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
angular.module('cloudstack').factory("Dictionary", function(){
|
||||
var dictionary = {
|
||||
labels: {
|
||||
id : 'ID',
|
||||
username : 'Username',
|
||||
account : 'Account',
|
||||
domain : 'Domain',
|
||||
state : 'State',
|
||||
displayname : 'Display Name',
|
||||
instancename : 'Instance Name',
|
||||
zonename : 'Zone Name',
|
||||
type : 'Type',
|
||||
description : 'Description',
|
||||
created : 'Created',
|
||||
name : 'Name',
|
||||
value : 'Value',
|
||||
displaytext : 'Description',
|
||||
networktype : 'Network Type',
|
||||
allocationstate : 'Allocation State',
|
||||
vmdisplayname: 'VM display name',
|
||||
hypervisor : 'Hypervisor',
|
||||
virtualmachine: 'Virtual Machine',
|
||||
virtualmachines: 'Virtual Machines',
|
||||
network: 'Network',
|
||||
networks: 'Networks',
|
||||
instances: 'Instances',
|
||||
event: 'Event',
|
||||
events: 'Events',
|
||||
globalsettings: 'Global Settings',
|
||||
accounts: 'Accounts',
|
||||
domains: 'Domains',
|
||||
storage: 'Storage',
|
||||
configurations: 'Global Settings',
|
||||
serviceofferings: 'Service Offerings',
|
||||
home: 'Home',
|
||||
projects: 'Projects',
|
||||
volumename: 'Volume',
|
||||
intervaltype: 'Interval Type',
|
||||
availabilityZone: 'Availability Zone',
|
||||
diskoffering: 'Disk Offering',
|
||||
format: 'Format',
|
||||
url: 'URL',
|
||||
checksum: 'MD5 Checksum',
|
||||
password: 'Password',
|
||||
email: 'Email',
|
||||
firstname: 'First Name',
|
||||
lastname: 'Last Name',
|
||||
}
|
||||
};
|
||||
return dictionary;
|
||||
});
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
angular.module('directives.confirm', ['ui.bootstrap.dialog']);
|
||||
angular.module('directives.confirm').directive('confirm',['$dialog', function($dialog){
|
||||
return{
|
||||
restrict: 'E',
|
||||
transclude: true,
|
||||
template: '<span ng-transclude></span>',
|
||||
link: function(scope, element, attrs){
|
||||
element.css('cursor', 'pointer');
|
||||
element.bind('click', function(){
|
||||
var message = attrs.message || 'Are you sure?';
|
||||
var action = attrs.action;
|
||||
var msgbox = $dialog.messageBox(action, message, [{label:'Yes', result: 'yes'},{label:'No', result: 'no'}]);
|
||||
scope.$apply(function(){
|
||||
msgbox.open().then(function(result){
|
||||
if(result === 'yes'){
|
||||
if(attrs.onOk) scope.$eval(attrs.onOk);
|
||||
}
|
||||
if(result === 'no'){
|
||||
if(attrs.onCancel) scope.$eval(attrs.onCancel);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
}
|
||||
}]);
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
angular.module('directives.editInPlace', []);
|
||||
angular.module('directives.editInPlace').directive('editInPlace', function(){
|
||||
return {
|
||||
restrict: 'E',
|
||||
replace: true,
|
||||
scope: {
|
||||
model: '=',
|
||||
attribute: '@',
|
||||
onSave: '@'
|
||||
},
|
||||
templateUrl: '/static/js/common/directives/edit-in-place.tpl.html',
|
||||
link: function(scope, element, attrs){
|
||||
var modelBackup;
|
||||
scope.editing = false;
|
||||
|
||||
scope.edit = function(){
|
||||
scope.editing = true;
|
||||
modelBackup = angular.copy(scope.model);
|
||||
}
|
||||
|
||||
scope.save = function(){
|
||||
scope.$eval(attrs.onSave);
|
||||
scope.editing = false;
|
||||
}
|
||||
|
||||
scope.cancel = function(){
|
||||
scope.model[scope.attribute] = modelBackup[scope.attribute];
|
||||
scope.editing = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
<span>
|
||||
<span ng-hide="editing">{{model[attribute]}}<button class="btn pull-right" ng-click="edit()"><i class="icon-edit"></i>Edit</button></span>
|
||||
<span ng-show="editing" class="form-inline">
|
||||
<input type="text" ng-model="model[attribute]">
|
||||
<button class="btn btn-success" ng-click="save()"><i class="icon-ok icon-white"></i></button>
|
||||
<button class="btn" ng-click="cancel()"><i class="icon-ban-circle"></i></button>
|
||||
</span>
|
||||
</span>
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
angular.module('directives.label', []);
|
||||
angular.module('directives.label').directive('vmStateLabel', function(){
|
||||
return {
|
||||
restrict: 'E',
|
||||
replace: true,
|
||||
scope: {
|
||||
vm: '=',
|
||||
},
|
||||
template : '<span ng-class="class">{{vm.state}}</span>',
|
||||
link: function(scope, element, attrs){
|
||||
var setClass = function(){
|
||||
if(scope.vm.state === "Running") scope.class="label label-success";
|
||||
else if (scope.vm.state === "Stopped") scope.class="label label-important";
|
||||
else if(scope.vm.state === "Destroyed") scope.class="label label-inverse";
|
||||
else scope.class="label label-info";
|
||||
}
|
||||
|
||||
setClass();
|
||||
|
||||
scope.$watch('vm', function(){
|
||||
setClass();
|
||||
}, true);
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
angular.module('directives.modalForm', ['ui.bootstrap.dialog']);
|
||||
angular.module('directives.modalForm').directive('modalForm', ['$dialog', function($dialog){
|
||||
return {
|
||||
restrict: 'EA',
|
||||
transclude: true,
|
||||
template: '<span ng-transclude></span>',
|
||||
scope: {
|
||||
onSubmit: '&',
|
||||
template: '@',
|
||||
formDetails: '='
|
||||
},
|
||||
link: function(scope, element, attrs){
|
||||
var opts = {
|
||||
backdrop: true,
|
||||
backdropClick: true,
|
||||
backdropFade: true,
|
||||
templateUrl: '/static/js/common/directives/modal-form.tpl.html',
|
||||
resolve: {
|
||||
formDetails: function(){
|
||||
return scope.formDetails;
|
||||
}
|
||||
},
|
||||
controller: 'FormCtrl',
|
||||
}
|
||||
element.bind('click', function(){
|
||||
var formDialog = $dialog.dialog(opts);
|
||||
var dialogPromise;
|
||||
scope.$apply(function(){
|
||||
dialogPromise = formDialog.open()
|
||||
});
|
||||
dialogPromise.then(function(result){
|
||||
if(result) scope.formDetails.onSubmit(result);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}]);
|
||||
|
||||
angular.module('directives.modalForm').controller('FormCtrl', ['$scope', 'dialog', 'formDetails', 'Dictionary',
|
||||
function TestDialogController($scope, dialog, formDetails, Dictionary){
|
||||
$scope.dictionary = Dictionary;
|
||||
//formObject will be passed into onSubmit when submit is clicked
|
||||
$scope.formObject = {};
|
||||
$scope.template = 'table.html';
|
||||
$scope.formDetails = formDetails;
|
||||
$scope.title = formDetails.title;
|
||||
$scope.close = function(){
|
||||
dialog.close();
|
||||
};
|
||||
$scope.submit = function(){
|
||||
dialog.close($scope.formObject);
|
||||
};
|
||||
}]);
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
<div class="modal-header">
|
||||
<h3>{{title}}</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div>
|
||||
<form novalidate class="form-horizontal">
|
||||
<div class="control-group" ng-repeat="field in formDetails.fields" ng-init="optionsCache = {}">
|
||||
<label class="control-label">{{dictionary.labels[field.label]}}</label>
|
||||
<div class="controls" ng-switch on="field.type">
|
||||
<input ng-switch-when="input-text" type="text" ng-model="formObject[field.model]">
|
||||
<input ng-switch-when="input-checkbox" type="checkbox" ng-model="formObject[field.model]">
|
||||
<input ng-switch-when="input-password" type="password" ng-model="formObject[field.model]">
|
||||
<select ng-switch-when="select" ng-init="optionsCache[field.model] = field.options()" ng-model="formObject[field.model]">
|
||||
<option ng-repeat="option in optionsCache[field.model]" value="{{field.getValue(option)}}">{{field.getName(option)}}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button ng-click="close()" class="btn">{{cancelText || "Cancel"}}</button>
|
||||
<button ng-click="submit(formObject)" class="btn btn-primary">{{okButtonText || "Submit"}}</button>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
angular.module('resources.accounts', ['services.helperfunctions', 'services.requester']);
|
||||
angular.module('resources.accounts').factory('Accounts', ['Account', 'requester', 'makeArray', 'makeInstance', function(Account, requester, makeArray, makeInstance){
|
||||
var Accounts = {};
|
||||
|
||||
Accounts.getAll = function(){
|
||||
return requester.get('listAccounts').then(function(response){
|
||||
return response.data.listaccountsresponse.account;
|
||||
}).then(makeArray(Account));
|
||||
};
|
||||
|
||||
Accounts.create = function(details){
|
||||
return requester.get('createAccount', details).then(function(response){
|
||||
return response.data.createaccountresponse.account;
|
||||
}).then(makeInstance(Account));
|
||||
}
|
||||
return Accounts;
|
||||
}]);
|
||||
|
||||
angular.module('resources.accounts').factory('Account', function(){
|
||||
var Account = function(attrs){
|
||||
angular.extend(this, attrs);
|
||||
};
|
||||
return Account;
|
||||
});
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
angular.module('resources.configurations', ['services.helperfunctions', 'services.requester', 'services.notifications']);
|
||||
angular.module('resources.configurations').factory('Configurations', ['$http', 'Configuration', 'makeArray', 'requester', function($http, Configuration, makeArray, requester){
|
||||
var Configurations = {};
|
||||
|
||||
Configurations.getAll = function(){
|
||||
return requester.get('listConfigurations').then(function(response){
|
||||
return response.data.listconfigurationsresponse.configuration;
|
||||
}).then(makeArray(Configuration));
|
||||
}
|
||||
|
||||
return Configurations;
|
||||
}]);
|
||||
|
||||
angular.module('resources.configurations').factory('Configuration', ['requester', 'Notifications', function(requester, Notifications){
|
||||
var Configuration = function(attrs){
|
||||
angular.extend(this, attrs);
|
||||
}
|
||||
|
||||
Configuration.prototype.update = function(){
|
||||
return requester.get('updateConfiguration', {name: this.name, value: this.value}).then(function(response){
|
||||
return response.data.updateconfigurationresponse.configuration;
|
||||
}).then(function(response){
|
||||
Notifications.push('success', 'Updated ' + response.name + '. Please restart management server(s) for new settings to take effect');
|
||||
});
|
||||
};
|
||||
return Configuration;
|
||||
}]);
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
angular.module('resources.diskofferings', ['services.helperfunctions', 'services.requester']);
|
||||
angular.module('resources.diskofferings').factory('DiskOfferings', ['DiskOffering', 'makeArray', 'requester', function(DiskOffering, makeArray, requester){
|
||||
this.getAll = function(){
|
||||
return requester.get('listDiskOfferings').then(function(response){
|
||||
return response.data.listdiskofferingsresponse.diskoffering
|
||||
}).then(makeArray(DiskOffering));
|
||||
};
|
||||
return this;
|
||||
}]);
|
||||
|
||||
angular.module('resources.diskofferings').factory('DiskOffering', function(){
|
||||
var DiskOffering = function(attrs){
|
||||
angular.extend(this, attrs);
|
||||
};
|
||||
return DiskOffering;
|
||||
});
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
angular.module('resources.domains', ['services.helperfunctions', 'services.requester']);
|
||||
angular.module('resources.domains').factory('Domains', ['$http', 'Domain', 'makeArray', 'requester', function($http, Domain, makeArray, requester){
|
||||
this.fetch = function(){
|
||||
return requester.get('listDomains').then(function(response){
|
||||
return response.data.listdomainsresponse.domain;
|
||||
}).then(makeArray(Domain));
|
||||
};
|
||||
return this;
|
||||
}]);
|
||||
|
||||
angular.module('resources.domains').factory('Domain', function(){
|
||||
var Domain = function(attrs){
|
||||
angular.extend(this, attrs);
|
||||
}
|
||||
return Domain;
|
||||
});
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
angular.module('resources.events', ['services.helperfunctions', 'services.requester']);
|
||||
angular.module('resources.events').factory('Events', ['$http', 'Event', 'makeArray', 'requester', function($http, Event, makeArray, requester){
|
||||
this.fetch = function(){
|
||||
return requester.get('listEvents').then(function(response){
|
||||
return response.data.listeventsresponse.event;
|
||||
}).then(makeArray(Event));
|
||||
}
|
||||
return this;
|
||||
}]);
|
||||
|
||||
angular.module('resources.events').factory('Event', function(){
|
||||
var Event = function(attrs){
|
||||
angular.extend(this, attrs);
|
||||
}
|
||||
return Event;
|
||||
});
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
angular.module('resources.networks',['services.helperfunctions', 'services.requester']);
|
||||
angular.module('resources.networks').factory('Networks', ['$http', 'Network', 'makeArray', 'requester', function($http, Network, makeArray, requester){
|
||||
this.fetch = function(){
|
||||
return requester.get('listNetworks').then(function(response){
|
||||
return response.data.listnetworksresponse.network;
|
||||
}).then(makeArray(Network));
|
||||
};
|
||||
return this;
|
||||
}]);
|
||||
|
||||
angular.module('resources.networks').factory('Network', function(){
|
||||
var Network = function(attrs){
|
||||
angular.extend(this, attrs);
|
||||
};
|
||||
return Network;
|
||||
});
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
angular.module('resources.projects', ['services.helperfunctions', 'services.requester']);
|
||||
angular.module('resources.projects').factory('Projects', ['Project', 'makeArray', 'requester', function(Project, makeArray, requester){
|
||||
this.fetch = function(){
|
||||
return requester.get('listProjects').then(function(response){
|
||||
return response.data.listprojectsresponse.project;
|
||||
}).then(makeArray(Project));
|
||||
};
|
||||
return this;
|
||||
}]);
|
||||
|
||||
angular.module('resources.projects').factory('Project', function(){
|
||||
var Project = function(attrs){
|
||||
angular.extend(this, attrs);
|
||||
};
|
||||
return Project;
|
||||
});
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
angular.module('resources.serviceofferings', ['services.helperfunctions', 'services.requester']);
|
||||
angular.module('resources.serviceofferings').factory('ServiceOfferings', ['$http', 'ServiceOffering', 'makeArray', 'requester', function($http, ServiceOffering, makeArray, requester){
|
||||
this.fetch = function(){
|
||||
return requester.get('listServiceOfferings').then(function(response){
|
||||
return response.data.listserviceofferingsresponse.serviceoffering;
|
||||
}).then(makeArray(ServiceOffering));
|
||||
};
|
||||
return this;
|
||||
}]);
|
||||
|
||||
angular.module('resources.serviceofferings').factory('ServiceOffering', function(){
|
||||
var ServiceOffering = function(attrs){
|
||||
angular.extend(this, attrs);
|
||||
}
|
||||
return ServiceOffering;
|
||||
});
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
angular.module('resources.snapshots', ['services.helperfunctions', 'services.requester']);
|
||||
angular.module('resources.snapshots').factory('Snapshots', ['Snapshot', 'makeArray', 'requester', function(Snapshot, makeArray, requester){
|
||||
this.getAll = function(){
|
||||
return requester.get('listSnapshots').then(function(response){
|
||||
return response.data.listsnapshotsresponse.snapshot;
|
||||
}).then(makeArray(Snapshot));
|
||||
};
|
||||
return this;
|
||||
}]);
|
||||
|
||||
angular.module('resources.snapshots').factory('Snapshot', function(){
|
||||
var Snapshot = function(attrs){
|
||||
angular.extend(this, attrs);
|
||||
};
|
||||
return Snapshot;
|
||||
});
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
angular.module('resources.templates', ['services.helperfunctions', 'services.requester']);
|
||||
angular.module('resources.templates').factory('Templates', ['Template', 'makeArray', 'requester', function(Template, makeArray, requester){
|
||||
this.getAll = function(){
|
||||
return requester.get('listTemplates', {templatefilter: 'all'}).then(function(response){
|
||||
return response.data.listtemplatesresponse.template;
|
||||
}).then(makeArray(Template));
|
||||
};
|
||||
return this;
|
||||
}]);
|
||||
|
||||
angular.module('resources.templates').factory('Template', function(){
|
||||
var Template = function(attrs){
|
||||
angular.extend(this, attrs);
|
||||
};
|
||||
return Template;
|
||||
});
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
angular.module('resources.users', ['services.helperfunctions', 'services.requester']);
|
||||
angular.module('resources.users').factory('Users', ['User', 'makeArray', 'requester', function(User, makeArray, requester){
|
||||
this.getAll = function(){
|
||||
return requester.get('listUsers').then(function(response){
|
||||
return response.data.listusersresponse.user;
|
||||
}).then(makeArray(User));
|
||||
};
|
||||
|
||||
this.getByDomain(id) = function(id){
|
||||
return requester.get('listUsers').then(function(response){
|
||||
return response.data.listusersresponse.user;
|
||||
}).then(makeArray(User));
|
||||
};
|
||||
|
||||
return this;
|
||||
}]);
|
||||
|
||||
angular.module('resources.users').factory('User', function(){
|
||||
var User = function(attrs){
|
||||
angular.extend(this, attrs);
|
||||
};
|
||||
return User;
|
||||
});
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
angular.module('resources.virtualmachines',['services.helperfunctions', 'services.requester']);
|
||||
angular.module('resources.virtualmachines').factory('VirtualMachines',
|
||||
['$http', 'VirtualMachine', 'makeArray', 'makeInstance', 'requester', function($http, VirtualMachine, makeArray, makeInstance, requester){
|
||||
this.getAll = function(){
|
||||
return requester.get('listVirtualMachines').then(function(response){
|
||||
return response.data.listvirtualmachinesresponse.virtualmachine;
|
||||
}).then(makeArray(VirtualMachine));
|
||||
};
|
||||
|
||||
this.getById = function(id){
|
||||
return requester.get('listVirtualMachines', {id: id}).then(function(response){
|
||||
return response.data.listvirtualmachinesresponse.virtualmachine[0];
|
||||
}).then(makeInstance(VirtualMachine));
|
||||
};
|
||||
|
||||
return this;
|
||||
}]);
|
||||
|
||||
angular.module('resources.virtualmachines').factory('VirtualMachine', ['requester', function (requester){
|
||||
var VirtualMachine = function(attrs){
|
||||
angular.extend(this, attrs);
|
||||
};
|
||||
VirtualMachine.prototype.start = function(){
|
||||
var self = this;
|
||||
self.state = 'Starting';
|
||||
requester.async('startVirtualMachine', {id : self.id}).then(function(response){
|
||||
self.state = 'Running';
|
||||
});
|
||||
};
|
||||
VirtualMachine.prototype.stop = function(){
|
||||
var self = this;
|
||||
self.state = 'Stopping'
|
||||
requester.async('stopVirtualMachine', {id : self.id}).then(function(response){
|
||||
self.state = 'Stopped';
|
||||
});
|
||||
};
|
||||
VirtualMachine.prototype.reboot = function(){
|
||||
var self = this;
|
||||
self.state = 'Rebooting';
|
||||
requester.async('rebootVirtualMachine', {id: self.id}).then(function(response){
|
||||
self.state = 'Running';
|
||||
});
|
||||
};
|
||||
VirtualMachine.prototype.destroy = function(){
|
||||
var self = this;
|
||||
requester.async('destroyVirtualMachine', {id: self.id}).then(function(response){
|
||||
self.state = 'Destroyed';
|
||||
});
|
||||
};
|
||||
VirtualMachine.prototype.restore = function(){
|
||||
var self = this;
|
||||
self.state = "Restoring";
|
||||
requester.async('restoreVirtualMachine', {id: self.id}).then(function(response){
|
||||
self.state = "Stopped";
|
||||
});
|
||||
};
|
||||
return VirtualMachine;
|
||||
}]);
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
angular.module('resources.volumes', ['services.helperfunctions', 'services.requester']);
|
||||
angular.module('resources.volumes').factory('Volumes', ['$http', 'Volume', 'makeArray', 'requester', function($http, Volume, makeArray, requester){
|
||||
this.getAll = function(){
|
||||
return requester.get('listVolumes').then(function(response){
|
||||
return response.data.listvolumesresponse.volume;
|
||||
}).then(makeArray(Volume));
|
||||
};
|
||||
return this;
|
||||
}]);
|
||||
|
||||
angular.module('resources.volumes').factory('Volume', function(){
|
||||
var Volume = function(attrs){
|
||||
angular.extend(this, attrs);
|
||||
}
|
||||
return Volume;
|
||||
});
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
angular.module('resources.zones', ['services.helperfunctions', 'services.requester']);
|
||||
angular.module('resources.zones').factory('Zones', ['Zone', 'makeArray', 'requester', function(Zone, makeArray, requester){
|
||||
this.getAll = function(){
|
||||
return requester.get('listZones').then(function(response){
|
||||
return response.data.listzonesresponse.zone;
|
||||
}).then(makeArray(Zone));
|
||||
};
|
||||
return this;
|
||||
}]);
|
||||
|
||||
angular.module('resources.zones').factory('Zone', function(){
|
||||
var Zone = function(attrs){
|
||||
angular.extend(this, attrs);
|
||||
};
|
||||
return Zone;
|
||||
});
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
angular.module('services.breadcrumbs', []);
|
||||
angular.module('services.breadcrumbs').factory('Breadcrumbs', ['$rootScope', '$location', function($rootScope, $location){
|
||||
var breadcrumbs = [{id:'home', url:'/#/'}];
|
||||
var Breadcrumbs = {};
|
||||
Breadcrumbs.refresh = function(){
|
||||
breadcrumbs = [{name:'Home', url:'/#/'}];
|
||||
};
|
||||
Breadcrumbs.push = function(name, url){
|
||||
breadcrumbs.push({name: name, url: url})
|
||||
};
|
||||
Breadcrumbs.getAll = function(){
|
||||
return breadcrumbs;
|
||||
};
|
||||
return Breadcrumbs;
|
||||
}]);
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
angular.module('services.helperfunctions', []);
|
||||
angular.module('services.helperfunctions').factory('makeArray', function(){
|
||||
var makeArray = function(Type){
|
||||
return function(response){
|
||||
var collection = [];
|
||||
angular.forEach(response, function(data){
|
||||
collection.push(new Type(data));
|
||||
});
|
||||
return collection;
|
||||
}
|
||||
}
|
||||
return makeArray;
|
||||
});
|
||||
|
||||
angular.module('services.helperfunctions').factory('makeInstance', function(){
|
||||
var makeInstance = function(Type){
|
||||
return function(response){
|
||||
return new Type(response);
|
||||
}
|
||||
}
|
||||
return makeInstance;
|
||||
});
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
angular.module('services.notifications', []);
|
||||
angular.module('services.notifications').factory('Notifications', function(){
|
||||
var notifications = [];
|
||||
var Notifications = {};
|
||||
Notifications.push = function(type, msg){
|
||||
notifications.push({type: type, msg: msg});
|
||||
};
|
||||
Notifications.getAll = function(){
|
||||
return notifications;
|
||||
};
|
||||
Notifications.remove = function(notification){
|
||||
var index = notifications.indexOf(notification);
|
||||
notifications.splice(index, 1);//remove element from the array, ugly
|
||||
};
|
||||
|
||||
return Notifications;
|
||||
});
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
angular.module('services.requester', [])
|
||||
angular.module('services.requester').factory('requester', ['$http', '$timeout', '$q', function($http, $timeout, $q){
|
||||
var baseURL = '/api/'; //make a provider
|
||||
var requester = {};
|
||||
requester.get = function(command, params){
|
||||
return $http.get(baseURL + command, {params: params});
|
||||
};
|
||||
requester.async = function(command, params){
|
||||
var deferred = $q.defer();
|
||||
$http.get(baseURL + command, {params : params}).then(function(response){
|
||||
var responseName = command.toLowerCase() + 'response';
|
||||
var jobId = response.data[responseName]['jobid'];
|
||||
var poll = function(){
|
||||
$timeout(function(){
|
||||
$http.get(baseURL + 'queryAsyncJobResult', {params : {jobId: jobId}}).then(function(response){
|
||||
if(response.data.queryasyncjobresultresponse.jobstatus){
|
||||
deferred.resolve(response.data.queryasyncjobresultresponse.jobresult);
|
||||
}
|
||||
else{
|
||||
poll();
|
||||
}
|
||||
})
|
||||
}, 5000, false);
|
||||
};
|
||||
poll();
|
||||
})
|
||||
return deferred.promise;
|
||||
};
|
||||
return requester;
|
||||
}]);
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,127 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en" ng-app="cloudstack">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>CloudStack</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="">
|
||||
<meta name="author" content="">
|
||||
|
||||
<!-- Le styles -->
|
||||
<link href="static/bootstrap/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="static/css/app.css" rel="stylesheet">
|
||||
<link href="static/bootstrap/css/bootstrap-responsive.min.css" rel="stylesheet">
|
||||
|
||||
<!-- Le HTML5 shim, for IE6-8 support of HTML5 elements -->
|
||||
<!--[if lt IE 9]>
|
||||
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
|
||||
<![endif]-->
|
||||
|
||||
</head>
|
||||
|
||||
<body ng-controller="AppCtrl" ng-cloak>
|
||||
<!-- Temporary ajax loader-->
|
||||
<div class="navbar navbar-inverse navbar-fixed-top">
|
||||
<div class="navbar-inner">
|
||||
<div class="container-fluid">
|
||||
<a class="brand" ng-href="/#/">
|
||||
<span>CloudStack <img ng-show="loading" src="static/images/ajax-inverse.gif" width="17px" height="17px"></span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container-fluid">
|
||||
<div class="row-fluid">
|
||||
<div class="span2" ng-controller="NavCtrl">
|
||||
<ul class="nav nav-tabs nav-stacked">
|
||||
<li ng-class="isActive('/')"><a href="/#/" >Dashboard</a></li>
|
||||
<li ng-class="isActive('instances')"><a href="/#/instances">Instances</a></li>
|
||||
<li ng-class="isActive('volumes')"><a href="/#/volumes">Storage</a></li>
|
||||
<li ng-class="isActive('networks')"><a href="/#/networks">Networks</a></li>
|
||||
<li ng-class="isActive('templates')"><a href="/#/templates">Templates</a></li>
|
||||
<li ng-class="isActive('events')"><a href="/#/events">Events</a></li>
|
||||
<li ng-class="isActive('accounts')"><a href="/#/accounts">Accounts</a></li>
|
||||
<li ng-class="isActive('domains')"><a href="/#/domains">Domains</a></li>
|
||||
<li ng-class="isActive('infrastructure')"><a href="/#/infrastructure">Infrastructure</a></li>
|
||||
<li ng-class="isActive('projects')"><a href="/#/projects">Projects</a></li>
|
||||
<li ng-class="isActive('configurations')"><a href="/#/configurations">Global Settings</a></li>
|
||||
<li ng-class="isActive('serviceofferings')"><a href="/#/serviceofferings">Service Offerings</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="span10">
|
||||
<div class="notifications">
|
||||
<alert ng-repeat="notification in notifications.getAll()" type="notification.type" close="notifications.remove(notification)">{{notification.msg}}</alert>
|
||||
</div>
|
||||
<ul class="breadcrumb">
|
||||
<!--breadcrumbs is in AppCtrl-->
|
||||
<li ng-repeat="breadcrumb in breadcrumbs.getAll()">
|
||||
<a ng-hide="$last" href="{{breadcrumb.url}}">{{breadcrumb.name}}</a>
|
||||
<span ng-show="$last">{{breadcrumb.name}}</span>
|
||||
<span class="divider" ng-hide="$last">/</span>
|
||||
</li>
|
||||
</ul>
|
||||
<div id="main" ng-view>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div> <!-- /container -->
|
||||
|
||||
<script type="text/ng-template" id="default.html">
|
||||
<div style="text-align:center">
|
||||
<img src="http://cloudstack.apache.org/images/cloudmonkey-fp.png" style="margin-top: 100px;margin-bottom: 100px;" alt="CloudStack Logo">
|
||||
<h3>CloudStack UI using Angular.js and Twitter Bootstrap</h3>
|
||||
</div>
|
||||
</script>
|
||||
<script type="text/ng-template" id="table.html">
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th ng-repeat="attribute in toDisplay"> {{dictionary.labels[attribute]}} </th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="model in collection">
|
||||
<td ng-repeat="attribute in toDisplay">{{model[attribute]}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript" src="static/js/lib/jquery-1.7.2.js"></script>
|
||||
<script type="text/javascript" src="static/js/lib/angular.js"></script>
|
||||
<script type="text/javascript" src="static/js/app/app.js"></script>
|
||||
<script type="text/javascript" src="static/js/common/resources/virtualmachines.js"></script>
|
||||
<script type="text/javascript" src="static/js/app/instances/instances.js"></script>
|
||||
<script type="text/javascript" src="static/js/common/resources/volumes.js"></script>
|
||||
<script type="text/javascript" src="static/js/common/resources/snapshots.js"></script>
|
||||
<script type="text/javascript" src="static/js/app/storage/storage.js"></script>
|
||||
<script type="text/javascript" src="static/js/common/resources/networks.js"></script>
|
||||
<script type="text/javascript" src="static/js/app/networks/networks.js"></script>
|
||||
<script type="text/javascript" src="static/js/common/resources/templates.js"></script>
|
||||
<script type="text/javascript" src="static/js/app/templates/templates.js"></script>
|
||||
<script type="text/javascript" src="static/js/common/resources/events.js"></script>
|
||||
<script type="text/javascript" src="static/js/app/events/events.js"></script>
|
||||
<script type="text/javascript" src="static/js/common/resources/accounts.js"></script>
|
||||
<script type="text/javascript" src="static/js/app/accounts/accounts.js"></script>
|
||||
<script type="text/javascript" src="static/js/common/resources/domains.js"></script>
|
||||
<script type="text/javascript" src="static/js/app/domains/domains.js"></script>
|
||||
<script type="text/javascript" src="static/js/app/globalsettings/globalsettings.js"></script>
|
||||
<script type="text/javascript" src="static/js/app/serviceofferings/serviceofferings.js"></script>
|
||||
<script type="text/javascript" src="static/js/common/resources/serviceofferings.js"></script>
|
||||
<script type="text/javascript" src="static/js/common/resources/projects.js"></script>
|
||||
<script type="text/javascript" src="static/js/app/projects/projects.js"></script>
|
||||
<script type="text/javascript" src="static/js/common/resources/configurations.js"></script>
|
||||
<script type="text/javascript" src="static/js/common/services/breadcrumbs.js"></script>
|
||||
<script type="text/javascript" src="static/js/common/services/helperfunctions.js"></script>
|
||||
<script type="text/javascript" src="static/js/common/services/requester.js"></script>
|
||||
<script type="text/javascript" src="static/js/common/services/notifications.js"></script>
|
||||
<script type="text/javascript" src="static/js/common/directives/confirm.js"></script>
|
||||
<script type="text/javascript" src="static/js/common/directives/modal-form.js"></script>
|
||||
<script type="text/javascript" src="static/js/common/directives/label.js"></script>
|
||||
<script type="text/javascript" src="static/js/common/directives/edit-in-place.js"></script>
|
||||
<script type="text/javascript" src="static/js/common/dictionary.js"></script>
|
||||
<script type="text/javascript" src="static/js/common/resources/zones.js"></script>
|
||||
<script type="text/javascript" src="static/js/common/resources/diskofferings.js"></script>
|
||||
<script type="text/javascript" src="static/js/lib/angular-ui.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Reference in New Issue