Skip to content

Commit ba83cd7

Browse files
shivatejasebgoa
authored andcommitted
[GSOC] Angular based UI
Signed-off-by: Sebastien Goasguen <runseb@gmail.com>
1 parent 79419d4 commit ba83cd7

65 files changed

Lines changed: 36403 additions & 0 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

tools/ngui/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
#UI for CloudStack using Angular.js
2+
And a flask wrapper on top CloudStack API to make things easy on the client side.

tools/ngui/api.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
from requester import make_request
2+
from precache import apicache
3+
from config import *
4+
import re
5+
6+
def get_error_code(error):
7+
return int(re.findall("\d{3}",error)[0]) #Find the error code by regular expression
8+
# return int(error[11:14]) #Ugly
9+
10+
def get_command(verb, subject):
11+
commandlist = apicache.get(verb, None)
12+
if commandlist is not None:
13+
command = commandlist.get(subject, None)
14+
if command is not None:
15+
return command["name"]
16+
return None
17+
18+
def apicall(command, data ):
19+
response, error = make_request(command, data, None, host, port, apikey, secretkey, protocol, path)
20+
if error is not None:
21+
return error, get_error_code(error)
22+
return response

tools/ngui/app.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
from flask import Flask, url_for, render_template, request, json, abort, send_from_directory
2+
from api import apicall
3+
4+
app = Flask(__name__)
5+
6+
def get_args(multidict):
7+
"""Default type of request.args or request.json is multidict. Converts it to dict so that can be passed to make_request"""
8+
data = {}
9+
for key in multidict.keys():
10+
data[key] = multidict.get(key)
11+
return data
12+
13+
@app.route('/api/<command>', methods=['GET'])
14+
def rawapi(command):
15+
if request.method == 'GET':
16+
return apicall(command, get_args(request.args))
17+
18+
@app.route('/')
19+
def index():
20+
return send_from_directory("templates", "index.html")
21+
22+
if __name__ == '__main__':
23+
app.run(debug=True)

tools/ngui/config.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
apikey='DNi_vTVLPNfTEFuqu5F9MrPI3iecf8iRQ3QtGUH1IM2Nd96wNwNlf7BzmF1W8aw6cE2ejZCgyE53wT5VpzauuA'
2+
secretkey='x4jM12uE4LNho3ZNJa8J-Ve6WsgEXd8df1mGGfeuJHMtolkaSBkD5pLX0tvj8YrWhBgtZbKgYsTB00kb7z_3BA'
3+
path='/client/api'
4+
host='localhost'
5+
port='8080'
6+
protocol='http'

tools/ngui/precache.py

Lines changed: 19 additions & 0 deletions
Large diffs are not rendered by default.

tools/ngui/requester.py

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
#!/usr/bin/python
2+
# -*- coding: utf-8 -*-
3+
# Licensed to the Apache Software Foundation (ASF) under one
4+
# or more contributor license agreements. See the NOTICE file
5+
# distributed with this work for additional information
6+
# regarding copyright ownership. The ASF licenses this file
7+
# to you under the Apache License, Version 2.0 (the
8+
# "License"); you may not use this file except in compliance
9+
# with the License. You may obtain a copy of the License at
10+
#
11+
# http://www.apache.org/licenses/LICENSE-2.0
12+
#
13+
# Unless required by applicable law or agreed to in writing,
14+
# software distributed under the License is distributed on an
15+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16+
# KIND, either express or implied. See the License for the
17+
# specific language governing permissions and limitations
18+
# under the License.
19+
20+
try:
21+
import base64
22+
import hashlib
23+
import hmac
24+
import httplib
25+
import json
26+
import os
27+
import pdb
28+
import re
29+
import shlex
30+
import sys
31+
import time
32+
import types
33+
import urllib
34+
import urllib2
35+
36+
except ImportError, e:
37+
print "Import error in %s : %s" % (__name__, e)
38+
import sys
39+
sys.exit()
40+
41+
42+
def logger_debug(logger, message):
43+
if logger is not None:
44+
logger.debug(message)
45+
46+
47+
def make_request(command, args, logger, host, port,
48+
apikey, secretkey, protocol, path):
49+
response = None
50+
error = None
51+
52+
if protocol != 'http' and protocol != 'https':
53+
error = "Protocol must be 'http' or 'https'"
54+
return None, error
55+
56+
if args is None:
57+
args = {}
58+
59+
args["command"] = command
60+
args["apiKey"] = apikey
61+
args["response"] = "json"
62+
request = zip(args.keys(), args.values())
63+
request.sort(key=lambda x: x[0].lower())
64+
65+
request_url = "&".join(["=".join([r[0], urllib.quote_plus(str(r[1]))])
66+
for r in request])
67+
hashStr = "&".join(["=".join([r[0].lower(),
68+
str.lower(urllib.quote_plus(str(r[1]))).replace("+",
69+
"%20")]) for r in request])
70+
71+
sig = urllib.quote_plus(base64.encodestring(hmac.new(secretkey, hashStr,
72+
hashlib.sha1).digest()).strip())
73+
request_url += "&signature=%s" % sig
74+
request_url = "%s://%s:%s%s?%s" % (protocol, host, port, path, request_url)
75+
76+
try:
77+
logger_debug(logger, "Request sent: %s" % request_url)
78+
connection = urllib2.urlopen(request_url)
79+
response = connection.read()
80+
except Exception, e:
81+
error = str(e)
82+
83+
logger_debug(logger, "Response received: %s" % response)
84+
if error is not None:
85+
logger_debug(logger, error)
86+
87+
return response, error
88+
89+
90+
def monkeyrequest(command, args, isasync, asyncblock, logger, host, port,
91+
apikey, secretkey, timeout, protocol, path):
92+
93+
response = None
94+
error = None
95+
logger_debug(logger, "======== START Request ========")
96+
logger_debug(logger, "Requesting command=%s, args=%s" % (command, args))
97+
response, error = make_request(command, args, logger, host, port,
98+
apikey, secretkey, protocol, path)
99+
logger_debug(logger, "======== END Request ========\n")
100+
101+
if error is not None:
102+
return response, error
103+
104+
def process_json(response):
105+
try:
106+
response = json.loads(str(response))
107+
except ValueError, e:
108+
error = "Error processing json response, %s" % e
109+
logger_debug(logger, "Error processing json", e)
110+
return response
111+
112+
response = process_json(response)
113+
if response is None:
114+
return response, error
115+
116+
isasync = isasync and (asyncblock == "true")
117+
responsekey = filter(lambda x: 'response' in x, response.keys())[0]
118+
119+
if isasync and 'jobid' in response[responsekey]:
120+
jobid = response[responsekey]['jobid']
121+
command = "queryAsyncJobResult"
122+
request = {'jobid': jobid}
123+
timeout = int(timeout)
124+
pollperiod = 3
125+
progress = 1
126+
while timeout > 0:
127+
print '\r' + '.' * progress,
128+
time.sleep(pollperiod)
129+
timeout = timeout - pollperiod
130+
progress += 1
131+
logger_debug(logger, "Job %s to timeout in %ds" % (jobid, timeout))
132+
sys.stdout.flush()
133+
response, error = monkeyrequest(command, request, isasync,
134+
asyncblock, logger,
135+
host, port, apikey, secretkey,
136+
timeout, protocol, path)
137+
response = process_json(response)
138+
responsekeys = filter(lambda x: 'response' in x, response.keys())
139+
if len(responsekeys) < 1:
140+
continue
141+
result = response[responsekeys[0]]
142+
jobstatus = result['jobstatus']
143+
if jobstatus == 2:
144+
jobresult = result["jobresult"]
145+
error = "\rAsync job %s failed\nError %s, %s" % (jobid,
146+
jobresult["errorcode"], jobresult["errortext"])
147+
return response, error
148+
elif jobstatus == 1:
149+
print '\r',
150+
return response, error
151+
error = "Error: Async query timeout occurred for jobid %s" % jobid
152+
153+
return response, error

0 commit comments

Comments
 (0)