First version of dashboard.
This commit is contained in:
parent
cf771d7f14
commit
1709208872
@ -1,9 +1,11 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
from __future__ import absolute_import
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
from collections import defaultdict
|
||||||
import functools
|
import functools
|
||||||
import inspect
|
import inspect
|
||||||
import re
|
import re
|
||||||
|
import six
|
||||||
|
|
||||||
from moto import settings
|
from moto import settings
|
||||||
from moto.packages.responses import responses
|
from moto.packages.responses import responses
|
||||||
@ -208,12 +210,38 @@ class Model(type):
|
|||||||
return dec
|
return dec
|
||||||
|
|
||||||
|
|
||||||
|
model_data = defaultdict(dict)
|
||||||
|
class InstanceTrackerMeta(type):
|
||||||
|
def __new__(meta, name, bases, dct):
|
||||||
|
cls = super(InstanceTrackerMeta, meta).__new__(meta, name, bases, dct)
|
||||||
|
if name == 'BaseModel':
|
||||||
|
return cls
|
||||||
|
|
||||||
|
service = cls.__module__.split(".")[1]
|
||||||
|
if name not in model_data[service]:
|
||||||
|
model_data[service][name] = cls
|
||||||
|
cls.instances = []
|
||||||
|
return cls
|
||||||
|
|
||||||
|
@six.add_metaclass(InstanceTrackerMeta)
|
||||||
|
class BaseModel(object):
|
||||||
|
def __new__(cls, *args, **kwargs):
|
||||||
|
instance = super(BaseModel, cls).__new__(cls, *args, **kwargs)
|
||||||
|
cls.instances.append(instance)
|
||||||
|
return instance
|
||||||
|
|
||||||
|
|
||||||
class BaseBackend(object):
|
class BaseBackend(object):
|
||||||
|
|
||||||
def reset(self):
|
def reset(self):
|
||||||
self.__dict__ = {}
|
self.__dict__ = {}
|
||||||
self.__init__()
|
self.__init__()
|
||||||
|
|
||||||
|
def get_models(self):
|
||||||
|
import pdb;pdb.set_trace()
|
||||||
|
models = getattr(backend.__class__, '__models__', {})
|
||||||
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _url_module(self):
|
def _url_module(self):
|
||||||
backend_module = self.__class__.__module__
|
backend_module = self.__class__.__module__
|
||||||
|
@ -12,6 +12,7 @@ from jinja2 import Environment, DictLoader, TemplateNotFound
|
|||||||
import six
|
import six
|
||||||
from six.moves.urllib.parse import parse_qs, urlparse
|
from six.moves.urllib.parse import parse_qs, urlparse
|
||||||
|
|
||||||
|
from flask import render_template
|
||||||
import xmltodict
|
import xmltodict
|
||||||
from pkg_resources import resource_filename
|
from pkg_resources import resource_filename
|
||||||
from werkzeug.exceptions import HTTPException
|
from werkzeug.exceptions import HTTPException
|
||||||
@ -350,6 +351,32 @@ class MotoAPIResponse(BaseResponse):
|
|||||||
return 200, {}, json.dumps({"status": "ok"})
|
return 200, {}, json.dumps({"status": "ok"})
|
||||||
return 400, {}, json.dumps({"Error": "Need to POST to reset Moto"})
|
return 400, {}, json.dumps({"Error": "Need to POST to reset Moto"})
|
||||||
|
|
||||||
|
def model_data(self, request, full_url, headers):
|
||||||
|
from moto.core.models import model_data
|
||||||
|
|
||||||
|
results = {}
|
||||||
|
for service in sorted(model_data):
|
||||||
|
models = model_data[service]
|
||||||
|
results[service] = {}
|
||||||
|
for name in sorted(models):
|
||||||
|
model = models[name]
|
||||||
|
results[service][name] = []
|
||||||
|
for instance in model.instances:
|
||||||
|
inst_result = {}
|
||||||
|
for attr in dir(instance):
|
||||||
|
if not attr.startswith("_"):
|
||||||
|
try:
|
||||||
|
json.dumps(getattr(instance, attr))
|
||||||
|
except TypeError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
inst_result[attr] = getattr(instance, attr)
|
||||||
|
results[service][name].append(inst_result)
|
||||||
|
return 200, {"Content-Type": "application/javascript"}, json.dumps(results)
|
||||||
|
|
||||||
|
def dashboard(self, request, full_url, headers):
|
||||||
|
return render_template('dashboard.html')
|
||||||
|
|
||||||
|
|
||||||
class _RecursiveDictRef(object):
|
class _RecursiveDictRef(object):
|
||||||
"""Store a recursive reference to dict."""
|
"""Store a recursive reference to dict."""
|
||||||
|
@ -8,5 +8,7 @@ url_bases = [
|
|||||||
response_instance = MotoAPIResponse()
|
response_instance = MotoAPIResponse()
|
||||||
|
|
||||||
url_paths = {
|
url_paths = {
|
||||||
|
'{0}/moto-api/$': response_instance.dashboard,
|
||||||
|
'{0}/moto-api/data.json': response_instance.model_data,
|
||||||
'{0}/moto-api/reset': response_instance.reset_response,
|
'{0}/moto-api/reset': response_instance.reset_response,
|
||||||
}
|
}
|
||||||
|
@ -122,7 +122,10 @@ class convert_flask_to_httpretty_response(object):
|
|||||||
|
|
||||||
result = self.callback(request, request.url, {})
|
result = self.callback(request, request.url, {})
|
||||||
# result is a status, headers, response tuple
|
# result is a status, headers, response tuple
|
||||||
status, headers, content = result
|
if len(result) == 3:
|
||||||
|
status, headers, content = result
|
||||||
|
else:
|
||||||
|
status, headers, content = 200, {}, result
|
||||||
|
|
||||||
response = Response(response=content, status=status, headers=headers)
|
response = Response(response=content, status=status, headers=headers)
|
||||||
if request.method == "HEAD" and 'content-length' in headers:
|
if request.method == "HEAD" and 'content-length' in headers:
|
||||||
|
@ -13,7 +13,7 @@ from boto.ec2.spotinstancerequest import SpotInstanceRequest as BotoSpotRequest
|
|||||||
from boto.ec2.launchspecification import LaunchSpecification
|
from boto.ec2.launchspecification import LaunchSpecification
|
||||||
|
|
||||||
from moto.core import BaseBackend
|
from moto.core import BaseBackend
|
||||||
from moto.core.models import Model
|
from moto.core.models import Model, BaseModel
|
||||||
from moto.core.utils import iso_8601_datetime_with_milliseconds, camelcase_to_underscores
|
from moto.core.utils import iso_8601_datetime_with_milliseconds, camelcase_to_underscores
|
||||||
from .exceptions import (
|
from .exceptions import (
|
||||||
EC2ClientError,
|
EC2ClientError,
|
||||||
@ -129,7 +129,7 @@ class StateReason(object):
|
|||||||
self.code = code
|
self.code = code
|
||||||
|
|
||||||
|
|
||||||
class TaggedEC2Resource(object):
|
class TaggedEC2Resource(BaseModel):
|
||||||
|
|
||||||
def get_tags(self, *args, **kwargs):
|
def get_tags(self, *args, **kwargs):
|
||||||
tags = self.ec2_backend.describe_tags(
|
tags = self.ec2_backend.describe_tags(
|
||||||
@ -2612,7 +2612,7 @@ class InternetGatewayBackend(object):
|
|||||||
return self.describe_internet_gateways(internet_gateway_ids=igw_ids)[0]
|
return self.describe_internet_gateways(internet_gateway_ids=igw_ids)[0]
|
||||||
|
|
||||||
|
|
||||||
class VPCGatewayAttachment(object):
|
class VPCGatewayAttachment(BaseModel):
|
||||||
|
|
||||||
def __init__(self, gateway_id, vpc_id):
|
def __init__(self, gateway_id, vpc_id):
|
||||||
self.gateway_id = gateway_id
|
self.gateway_id = gateway_id
|
||||||
|
@ -47,7 +47,7 @@ class DomainDispatcherApplication(object):
|
|||||||
|
|
||||||
def get_application(self, environ):
|
def get_application(self, environ):
|
||||||
path_info = environ.get('PATH_INFO', '')
|
path_info = environ.get('PATH_INFO', '')
|
||||||
if path_info.startswith("/moto-api"):
|
if path_info.startswith("/moto-api") or path_info == "/favicon.ico":
|
||||||
host = "moto_api"
|
host = "moto_api"
|
||||||
elif path_info.startswith("/latest/meta-data/"):
|
elif path_info.startswith("/latest/meta-data/"):
|
||||||
host = "instance_metadata"
|
host = "instance_metadata"
|
||||||
|
@ -7,6 +7,7 @@ from xml.sax.saxutils import escape
|
|||||||
import boto.sqs
|
import boto.sqs
|
||||||
|
|
||||||
from moto.core import BaseBackend
|
from moto.core import BaseBackend
|
||||||
|
from moto.core.models import BaseModel
|
||||||
from moto.core.utils import camelcase_to_underscores, get_random_message_id, unix_time, unix_time_millis
|
from moto.core.utils import camelcase_to_underscores, get_random_message_id, unix_time, unix_time_millis
|
||||||
from .utils import generate_receipt_handle
|
from .utils import generate_receipt_handle
|
||||||
from .exceptions import (
|
from .exceptions import (
|
||||||
@ -18,7 +19,7 @@ DEFAULT_ACCOUNT_ID = 123456789012
|
|||||||
DEFAULT_SENDER_ID = "AIDAIT2UOQQY3AUEKVGXU"
|
DEFAULT_SENDER_ID = "AIDAIT2UOQQY3AUEKVGXU"
|
||||||
|
|
||||||
|
|
||||||
class Message(object):
|
class Message(BaseModel):
|
||||||
|
|
||||||
def __init__(self, message_id, body):
|
def __init__(self, message_id, body):
|
||||||
self.id = message_id
|
self.id = message_id
|
||||||
@ -93,7 +94,7 @@ class Message(object):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
class Queue(object):
|
class Queue(BaseModel):
|
||||||
camelcase_attributes = ['ApproximateNumberOfMessages',
|
camelcase_attributes = ['ApproximateNumberOfMessages',
|
||||||
'ApproximateNumberOfMessagesDelayed',
|
'ApproximateNumberOfMessagesDelayed',
|
||||||
'ApproximateNumberOfMessagesNotVisible',
|
'ApproximateNumberOfMessagesNotVisible',
|
||||||
|
169
moto/templates/dashboard.html
Normal file
169
moto/templates/dashboard.html
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
|
||||||
|
<title>Moto</title>
|
||||||
|
|
||||||
|
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" rel="stylesheet">
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
padding-top: 70px;
|
||||||
|
padding-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-dropdown .dropdown-menu {
|
||||||
|
position: static;
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-showcase > p > .btn {
|
||||||
|
margin: 5px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-showcase .navbar .container {
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<nav class="navbar navbar-inverse navbar-fixed-top">
|
||||||
|
<div class="container">
|
||||||
|
<div class="navbar-header">
|
||||||
|
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
|
||||||
|
<span class="sr-only">Toggle navigation</span>
|
||||||
|
<span class="icon-bar"></span>
|
||||||
|
<span class="icon-bar"></span>
|
||||||
|
<span class="icon-bar"></span>
|
||||||
|
</button>
|
||||||
|
<a class="navbar-brand" href="#">Moto</a>
|
||||||
|
</div>
|
||||||
|
<div id="navbar" class="navbar-collapse collapse">
|
||||||
|
<ul class="nav navbar-nav">
|
||||||
|
<li class="active"><a href="#">Home</a></li>
|
||||||
|
<li><a href="#about">About</a></li>
|
||||||
|
<!-- <li class="dropdown">
|
||||||
|
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Region <span class="caret"></span></a>
|
||||||
|
<ul class="dropdown-menu">
|
||||||
|
<li><a href="#">us-east-1</a></li>
|
||||||
|
<li><a href="#">us-west-1</a></li>
|
||||||
|
<li><a href="#">us-west-2</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
--> </ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div class="container theme-showcase" role="main" id="main">
|
||||||
|
|
||||||
|
<div class="jumbotron">
|
||||||
|
<h1>Moto Dashboard</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
|
||||||
|
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/4.0.6/handlebars.js"></script>
|
||||||
|
|
||||||
|
|
||||||
|
{% raw %}
|
||||||
|
<script id="template" type="text/x-handlebars-template">
|
||||||
|
|
||||||
|
<ul id="myTab" class="nav nav-tabs">
|
||||||
|
{{#each data}}
|
||||||
|
<li {{#equal @index 0}}class="active"{{/equal}}><a href="#{{@key}}" data-toggle="tab">{{@key}}</a></li>
|
||||||
|
{{/each}}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div id="myTabContent" class="tab-content">
|
||||||
|
{{#each data}}
|
||||||
|
<div class="tab-pane fade {{#equal @index 0}}in active{{/equal}}" id="{{@key}}">
|
||||||
|
|
||||||
|
{{#each this}}
|
||||||
|
<div class="page-header">
|
||||||
|
<h1>{{@key}}</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<table class="table table-striped table-bordered table-condensed">
|
||||||
|
{{#each this}}
|
||||||
|
<tr>
|
||||||
|
{{#each this}}
|
||||||
|
<td>{{@key}}: {{this}}</td>
|
||||||
|
{{/each}}
|
||||||
|
</tr>
|
||||||
|
{{/each}}
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{/each}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{/each}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</script>
|
||||||
|
<script>
|
||||||
|
Handlebars.registerHelper('equal', function(lvalue, rvalue, options) {
|
||||||
|
if (arguments.length < 3)
|
||||||
|
throw new Error("Handlebars Helper equal needs 2 parameters");
|
||||||
|
if( lvalue!=rvalue ) {
|
||||||
|
return options.inverse(this);
|
||||||
|
} else {
|
||||||
|
return options.fn(this);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$(document).ready(function (){
|
||||||
|
|
||||||
|
|
||||||
|
$.getJSON("/moto-api/data.json", function(data) {
|
||||||
|
|
||||||
|
var source = $('#template').html();
|
||||||
|
var template = Handlebars.compile(source);
|
||||||
|
$('#main').append(template({"data": data}));
|
||||||
|
|
||||||
|
|
||||||
|
// $.each(data, function(model_type, instances) {
|
||||||
|
// $.each(instances, function(index) {
|
||||||
|
// instance = instances[index];
|
||||||
|
|
||||||
|
// if (index == 0) {
|
||||||
|
// var row = "<thead>";
|
||||||
|
// $.each(instance, function(attr, attr_val) {
|
||||||
|
// row += "<th>" + attr + "</th>";
|
||||||
|
// })
|
||||||
|
// row += "</thead><tbody>";
|
||||||
|
// $("#my_table").append(row);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// var row = "<tr>";
|
||||||
|
// $.each(instance, function(attr, attr_val) {
|
||||||
|
// row += "<td>" + attr_val + "</td>";
|
||||||
|
// });
|
||||||
|
// row += "</tr>";
|
||||||
|
// $("#my_table").append(row);
|
||||||
|
// });
|
||||||
|
// $("#my_table").append("</tbody>");
|
||||||
|
// });
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
{% endraw %}
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -19,3 +19,15 @@ def test_reset_api():
|
|||||||
res.content.should.equal(b'{"status": "ok"}')
|
res.content.should.equal(b'{"status": "ok"}')
|
||||||
|
|
||||||
conn.list_queues().shouldnt.contain('QueueUrls') # No more queues
|
conn.list_queues().shouldnt.contain('QueueUrls') # No more queues
|
||||||
|
|
||||||
|
|
||||||
|
@mock_sqs
|
||||||
|
def test_data_api():
|
||||||
|
conn = boto3.client("sqs", region_name='us-west-1')
|
||||||
|
conn.create_queue(QueueName="queue1")
|
||||||
|
|
||||||
|
res = requests.post("{base_url}/moto-api/data.json".format(base_url=base_url))
|
||||||
|
queues = res.json()['sqs']['Queue']
|
||||||
|
len(queues).should.equal(1)
|
||||||
|
queue = queues[0]
|
||||||
|
queue['name'].should.equal("queue1")
|
||||||
|
Loading…
Reference in New Issue
Block a user