[GSOC] Angular based UI

Signed-off-by: Sebastien Goasguen <runseb@gmail.com>
This commit is contained in:
Shiva Teja 2013-07-29 13:32:45 -04:00 committed by Sebastien Goasguen
parent 79419d4373
commit ba83cd7092
65 changed files with 36403 additions and 0 deletions

2
tools/ngui/README.md Normal file
View File

@ -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.

22
tools/ngui/api.py Normal file
View File

@ -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

23
tools/ngui/app.py Normal file
View File

@ -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)

6
tools/ngui/config.py Normal file
View File

@ -0,0 +1,6 @@
apikey='DNi_vTVLPNfTEFuqu5F9MrPI3iecf8iRQ3QtGUH1IM2Nd96wNwNlf7BzmF1W8aw6cE2ejZCgyE53wT5VpzauuA'
secretkey='x4jM12uE4LNho3ZNJa8J-Ve6WsgEXd8df1mGGfeuJHMtolkaSBkD5pLX0tvj8YrWhBgtZbKgYsTB00kb7z_3BA'
path='/client/api'
host='localhost'
port='8080'
protocol='http'

19
tools/ngui/precache.py Normal file

File diff suppressed because one or more lines are too long

153
tools/ngui/requester.py Normal file
View File

@ -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

View File

@ -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

View File

@ -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;
}
}
]
}
}]);

View File

@ -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>

View File

@ -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': '';
}
}]);

View File

@ -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'];
}]);

View File

@ -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'];
}]);

View File

@ -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'];
}]);

View File

@ -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>

View File

@ -0,0 +1,6 @@
<table class="table table-bordered">
<tr ng-repeat="(attribute, value) in model">
<td>{{attribute}}</td>
<td>{{value}}</td>
</tr>
</table>

View File

@ -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;
}]);

View File

@ -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>

View File

@ -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'];
}]);

View File

@ -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']
}]);

View File

@ -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'];
}]);

View File

@ -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');
});
}]);

View File

@ -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>

View File

@ -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'];
}]);

View File

@ -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;
});

View File

@ -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);
}
});
});
});
},
}
}]);

View File

@ -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;
}
}
}
});

View File

@ -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>

View File

@ -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);
}
}
})

View File

@ -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);
};
}]);

View File

@ -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>

View File

@ -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;
});

View File

@ -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;
}]);

View File

@ -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;
});

View File

@ -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;
});

View File

@ -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;
});

View File

@ -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;
});

View File

@ -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;
});

View File

@ -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;
});

View File

@ -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;
});

View File

@ -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;
});

View File

@ -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;
});

View File

@ -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;
}]);

View File

@ -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;
});

View File

@ -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;
});

View File

@ -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;
}]);

View File

@ -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;
});

View File

@ -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;
});

View File

@ -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

14847
tools/ngui/static/js/lib/angular.js vendored Normal file

File diff suppressed because it is too large Load Diff

9404
tools/ngui/static/js/lib/jquery-1.7.2.js vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -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>