Commit 30713232 authored by Kostas Papadimitriou's avatar Kostas Papadimitriou Committed by Giorgos Korfiatis

Services api/presentation logic cleanup

- Remove presentation information from Service model (order, icon)
- To access the list of services filled with both presentation and api
  data use Service.catalog(). The method fills a dict with service
  identifiers and service metadata through the following steps
    - Initialize a service dict using
      astakos.im.presentation.service_defaults
    - Extend (merge and overwrite) it with the correspoding entry in
      astakos.im.presentation.SERVICES if exists.
    - Merge once again if user has set service metadata in
      ASTAKOS_SERVICE_META setting. This way user can change only the
      desired service parameters.

- An example of ASTAKOS_SERVICE_META containing keys which

    ASTAKOS_SERVICE_META = {
        'myservice': {
            'name': 'service_identifier',
            'url': 'https://service.url/service/ui/',
            'verbose_name': 'My service name',
            'order': 1,

            # cloudbar specific parameters
            'cloudbar': {
                'show': True,
                'title': 'My service name in cloudbar'
                # defaults to verbose_name
            },

            # dashboard specific parameters
            'dashboard': {
                'show': True,
                # we use different ordering in dashboard
                'order': 100,
                'description': 'My service description'
            }
        }
    }

- Updated dashboard to use Service.catalog() instead of hardcoded
  html.
- Updated service-* commands to keep up with model changes
parent 2c82dc58
......@@ -65,7 +65,7 @@ api_method = partial(api.api_method, user_required=False,
def get_services_dict():
"""Return dictionary with information about available Services."""
return list(Service.objects.values("id", "name", "url", "icon"))
return Service.catalog().values()
@api_method(http_method=None)
......
......@@ -36,14 +36,14 @@ from django.core.management.base import BaseCommand, CommandError
from astakos.im.api.callpoint import AstakosCallpoint
class Command(BaseCommand):
args = "<name> <url> [<icon>]"
args = "<name> <api_url>"
help = "Register a service"
def handle(self, *args, **options):
if len(args) < 2:
raise CommandError("Invalid number of arguments")
s = {'name':args[0], 'url':args[1]}
s = {'name':args[0], 'api_url':args[1]}
if len(args) == 3:
s['icon'] = args[2]
try:
......
......@@ -52,8 +52,8 @@ class Command(NoArgsCommand):
def handle_noargs(self, **options):
services = Service.objects.all().order_by('id')
labels = ('id', 'order', 'name', 'url', 'auth_token', 'icon')
columns = (3, 3, 12, 40, 20, 20)
labels = ('id', 'name', 'API url', 'auth_token')
columns = (3, 12, 70, 20)
if not options['csv']:
line = ' '.join(l.rjust(w) for l, w in zip(labels, columns))
......@@ -62,10 +62,9 @@ class Command(NoArgsCommand):
self.stdout.write(sep + '\n')
for service in services:
fields = (str(service.id), str(service.order), service.name,
service.url,
service.auth_token or '',
service.icon)
fields = (str(service.id), service.name,
service.api_url,
service.auth_token or '')
if options['csv']:
line = '|'.join(fields)
......
......@@ -46,23 +46,14 @@ class Command(BaseCommand):
help = "Modify service attributes"
option_list = BaseCommand.option_list + (
make_option('--order',
dest='order',
metavar='NUM',
default=None,
help="Set service order"),
make_option('--name',
dest='name',
default=None,
help="Set service name"),
make_option('--url',
dest='url',
default=None,
help="Set service url"),
make_option('--icon',
dest='icon',
make_option('--api-url',
dest='api_url',
default=None,
help="Set service icon (displayed by cloudbar)"),
help="Set service API url"),
make_option('--auth-token',
dest='auth_token',
default=None,
......@@ -84,24 +75,16 @@ class Command(BaseCommand):
raise CommandError("Service does not exist. You may run snf-mange "
"service-list for available service IDs.")
order = options.get('order')
name = options.get('name')
url = options.get('url')
icon = options.get('icon')
api_url = options.get('api_url')
auth_token = options.get('auth_token')
renew_token = options.get('renew_token')
if order != None:
service.order = order
if name:
service.name = name
if url:
service.url = url
if icon:
service.icon = icon
if api_url:
service.api_url = api_url
if auth_token:
service.auth_token = auth_token
......
......@@ -75,6 +75,7 @@ from astakos.im import auth_providers as auth
import astakos.im.messages as astakos_messages
from synnefo.lib.db.managers import ForUpdateManager
from synnefo.lib.ordereddict import OrderedDict
from astakos.quotaholder.api import QH_PRACTICALLY_INFINITE
from synnefo.lib.db.intdecimalfield import intDecimalField
......@@ -86,13 +87,15 @@ logger = logging.getLogger(__name__)
DEFAULT_CONTENT_TYPE = None
_content_type = None
def get_content_type():
global _content_type
if _content_type is not None:
return _content_type
try:
content_type = ContentType.objects.get(app_label='im', model='astakosuser')
content_type = ContentType.objects.get(app_label='im',
model='astakosuser')
except:
content_type = DEFAULT_CONTENT_TYPE
_content_type = content_type
......@@ -100,24 +103,37 @@ def get_content_type():
inf = float('inf')
def dict_merge(a, b):
"""
http://www.xormedia.com/recursively-merge-dictionaries-in-python/
"""
if not isinstance(b, dict):
return b
result = copy.deepcopy(a)
for k, v in b.iteritems():
if k in result and isinstance(result[k], dict):
result[k] = dict_merge(result[k], v)
else:
result[k] = copy.deepcopy(v)
return result
class Service(models.Model):
name = models.CharField(_('Name'), max_length=255, unique=True, db_index=True)
url = models.FilePathField()
icon = models.FilePathField(blank=True)
name = models.CharField(_('Name'), max_length=255, unique=True,
db_index=True)
api_url = models.CharField(_('Service API url'), max_length=255)
auth_token = models.CharField(_('Authentication Token'), max_length=32,
null=True, blank=True)
auth_token_created = models.DateTimeField(_('Token creation date'), null=True)
auth_token_expires = models.DateTimeField(
_('Token expiration date'), null=True)
order = models.PositiveIntegerField(default=0)
class Meta:
ordering = ('order', )
auth_token_created = models.DateTimeField(_('Token creation date'),
null=True)
auth_token_expires = models.DateTimeField(_('Token expiration date'),
null=True)
def renew_token(self, expiration_date=None):
md5 = hashlib.md5()
md5.update(self.name.encode('ascii', 'ignore'))
md5.update(self.url.encode('ascii', 'ignore'))
md5.update(self.api_url.encode('ascii', 'ignore'))
md5.update(asctime())
self.auth_token = b64encode(md5.digest())
......@@ -130,6 +146,40 @@ class Service(models.Model):
def __str__(self):
return self.name
@classmethod
def catalog(cls, orderfor=None):
catalog = {}
services = list(cls.objects.all())
metadata = presentation.SERVICES
metadata = dict_merge(presentation.SERVICES,
astakos_settings.SERVICES_META)
for service in services:
if service.name in metadata:
d = {'api_url': service.api_url, 'name': service.name}
metadata[service.name].update(d)
def service_by_order(s):
return s[1].get('order')
def service_by_dashbaord_order(s):
return s[1].get('dashboard').get('order')
for service, info in metadata.iteritems():
default_meta = presentation.service_defaults(service)
base_meta = metadata.get(service, {})
settings_meta = astakos_settings.SERVICES_META.get(service, {})
service_meta = dict_merge(default_meta, base_meta)
meta = dict_merge(service_meta, settings_meta)
catalog[service] = meta
order_key = service_by_order
if orderfor == 'dashboard':
order_key = service_by_dashbaord_order
ordered_catalog = OrderedDict(sorted(catalog.iteritems(),
key=order_key))
return ordered_catalog
_presentation_data = {}
def get_presentation(resource):
......
......@@ -143,3 +143,71 @@ RESOURCES = {
'cyclades.network.private'
]
}
def service_defaults(service_name):
"""
Metadata for unkown services
"""
return {
'name': service_name,
'order': 1000,
'verbose_name': service_name.title(),
'cloudbar': {
'show': True,
'title': service_name
},
'dashboard': {
'show': True,
'order': 1000,
'description': '%s service' % service_name
}
}
SERVICES = {
'astakos': {
'url': '/im/landing',
'order': 1,
'dashboard': {
'order': 3,
'show': True,
'description': "Access the dashboard from the top right corner "
"of your screen. Here you can manage your profile, "
"see the usage of your resources and manage "
"projects to share virtual resources with "
"colleagues."
},
'cloudbar': {
'show': False
}
},
'pithos': {
'url': '/pithos/ui/',
'order': 2,
'dashboard': {
'order': 1,
'show': True,
'description': "Pithos is the File Storage service. "
"Click to start uploading and managing your "
"files on the cloud."
},
'cloudbar': {
'show': True
}
},
'cyclades': {
'url': '/cyclades/ui/',
'order': 3,
'dashboard': {
'order': 2,
'show': True,
'description': "Cyclades is the Compute and Network Service. "
"Click to start creating Virtual Machines and "
"connect them to arbitrary Networks."
},
'cloudbar': {
'show': True
}
}
}
......@@ -235,3 +235,6 @@ LOGIN_SUCCESS_URL = getattr(settings, 'ASTAKOS_LOGIN_SUCCESS_URL',
# Whether or not to display projects in astakos menu
PROJECTS_VISIBLE = getattr(settings, 'ASTAKOS_PROJECTS_VISIBLE', False)
# A way to extend the settings presentation metadata
SERVICES_META = getattr(settings, 'ASTAKOS_SERVICES_META', False)
......@@ -55,11 +55,14 @@ $(document).ready(function(){
$.each(data, function(i, el){
var sli = $("<li>");
var slink = $("<a>");
if (el.icon) {
slink.append($('<img src="'+cssloc+el.icon+'"/>'));
if (!el.cloudbar) { el.cloudbar = {} }
var title = el.cloudbar.name || el.verbose_name || el.name;
if (!el.cloudbar.show) { return }
if (el.cloudbar.icon) {
slink.append($('<img alt="'+title+'" src="'+cssloc+el.cloudbar.icon+'"/>'));
slink.addClass("with-icon");
} else {
slink.text(el.name);
slink.html(title);
}
slink.attr('href', el.url);
slink.attr('title', el.name);
......
......@@ -4,47 +4,24 @@
{% block page.body %}
<div class="landing-page">
<div class="two-cols clearfix dotted pithos">
<div class="rt">
<a href="/pithos/ui/"><img class="pic" src="{{ IM_STATIC_URL }}images/landing-pithos.png" /></a>
</div>
<div class="lt">
<a href="/pithos/ui/">Pithos</a> is the File Storage service. Click to start
uploading and managing your files on the cloud.
</div>
</div>
<div class="two-cols clearfix dotted cyclades">
<div class="rt">
<a href="/ui/"><img class="pic" src="{{ IM_STATIC_URL }}images/landing-cyclades.png" /></a>
</div>
<div class="lt">
<a href="/ui/">Cyclades</a> is the Compute and Network Service. Click to start
creating Virtual Machines and connect them to arbitrary Networks.
</div>
</div>
<div class="two-cols clearfix dotted dashboard">
<div class="rt">
<a href="{% url astakos.im.views.edit_profile %}"><img class="pic" src="{{ IM_STATIC_URL }}images/landing-dashboard.png" /></a>
</div>
<div class="lt">
Access the <a href="{% url astakos.im.views.edit_profile %}">dashboard</a> from
the top right corner of your screen. Here you can manage your profile, see the usage of your resources
and manage projects to share virtual resources with colleagues.
</div>
</div>
<div class="two-cols clearfix dotted cms">
<div class="rt">
<a href=""><img class="pic" src="{{ IM_STATIC_URL }}images/landing-cms.png" /></a>
</div>
<div class="lt">
Click on the top left logo icon, to go back to the homepage.
</div>
</div>
{% for id, service in services.items %}
{% if service.dashboard.show %}
<div class="two-cols clearfix dotted {{ id }}">
<div class="rt">
{% if service.url %}
<a href="{{ service.url }}">
{% endif %}
<img class="pic" src="{{ IM_STATIC_URL }}images/landing-{{ id }}.png" />
{% if service.url %}
</a>
{% endif %}
</div>
<div class="lt">
{{ service.dashboard.description }}
</div>
</div>
{% endif %}
{% endfor %}
</div>
<script type="text/javascript">
$(document).ready(function() {
......
......@@ -268,4 +268,3 @@ def confirm_link(context, title, prompt='', url=None, urlarg=None,
content = render_to_string(template, tpl_context)
return content
......@@ -41,6 +41,7 @@ from django import template
from django.core.paginator import Paginator, EmptyPage
from django.db.models.query import QuerySet
from synnefo.lib.ordereddict import OrderedDict
from astakos.im import settings
from astakos.im.models import ProjectResourceGrant
......
......@@ -79,7 +79,7 @@ from astakos.im import tables
from astakos.im.models import (
AstakosUser, ApprovalTerms,
EmailChange, AstakosUserAuthProvider, PendingThirdPartyUser,
ProjectApplication, ProjectMembership, Project)
ProjectApplication, ProjectMembership, Project, Service)
from astakos.im.util import (
get_context, prepare_response, get_query, restrict_next)
from astakos.im.forms import (
......@@ -1053,6 +1053,7 @@ def _resources_catalog(request):
# presentation data
resource_groups = presentation.RESOURCES.get('groups', {})
resource_catalog = ()
resource_keys = []
# resources in database
result = callpoint.list_resources()
......@@ -1647,9 +1648,10 @@ def _project_app_dismiss(request, application_id):
@login_required
@signed_terms_required
def landing(request):
context = {'services': Service.catalog(orderfor='dashboard')}
return render_response(
'im/landing.html',
context_instance=get_context(request))
context_instance=get_context(request), **context)
def api_access(request):
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment