Revision 0156e40c

b/snf-astakos-app/astakos/im/api/__init__.py
65 65

  
66 66
def get_services_dict():
67 67
    """Return dictionary with information about available Services."""
68
    return list(Service.objects.values("id", "name", "url", "icon"))
68
    return Service.catalog().values()
69 69

  
70 70

  
71 71
@api_method(http_method=None)
b/snf-astakos-app/astakos/im/management/commands/service-add.py
36 36
from astakos.im.api.callpoint import AstakosCallpoint
37 37

  
38 38
class Command(BaseCommand):
39
    args = "<name> <url> [<icon>]"
39
    args = "<name> <api_url>"
40 40
    help = "Register a service"
41 41

  
42 42
    def handle(self, *args, **options):
43 43
        if len(args) < 2:
44 44
            raise CommandError("Invalid number of arguments")
45 45

  
46
        s = {'name':args[0], 'url':args[1]}
46
        s = {'name':args[0], 'api_url':args[1]}
47 47
        if len(args) == 3:
48 48
            s['icon'] = args[2]
49 49
        try:
b/snf-astakos-app/astakos/im/management/commands/service-list.py
52 52
    def handle_noargs(self, **options):
53 53
        services = Service.objects.all().order_by('id')
54 54

  
55
        labels = ('id', 'order', 'name', 'url', 'auth_token', 'icon')
56
        columns = (3, 3, 12, 40, 20, 20)
55
        labels = ('id', 'name', 'API url', 'auth_token')
56
        columns = (3, 12, 70, 20)
57 57

  
58 58
        if not options['csv']:
59 59
            line = ' '.join(l.rjust(w) for l, w in zip(labels, columns))
......
62 62
            self.stdout.write(sep + '\n')
63 63

  
64 64
        for service in services:
65
            fields = (str(service.id), str(service.order), service.name,
66
                      service.url,
67
                      service.auth_token or '',
68
                      service.icon)
65
            fields = (str(service.id), service.name,
66
                      service.api_url,
67
                      service.auth_token or '')
69 68

  
70 69
            if options['csv']:
71 70
                line = '|'.join(fields)
b/snf-astakos-app/astakos/im/management/commands/service-update.py
46 46
    help = "Modify service attributes"
47 47

  
48 48
    option_list = BaseCommand.option_list + (
49
        make_option('--order',
50
                    dest='order',
51
                    metavar='NUM',
52
                    default=None,
53
                    help="Set service order"),
54 49
        make_option('--name',
55 50
                    dest='name',
56 51
                    default=None,
57 52
                    help="Set service name"),
58
        make_option('--url',
59
                    dest='url',
60
                    default=None,
61
                    help="Set service url"),
62
        make_option('--icon',
63
                    dest='icon',
53
        make_option('--api-url',
54
                    dest='api_url',
64 55
                    default=None,
65
                    help="Set service icon (displayed by cloudbar)"),
56
                    help="Set service API url"),
66 57
        make_option('--auth-token',
67 58
                    dest='auth_token',
68 59
                    default=None,
......
84 75
            raise CommandError("Service does not exist. You may run snf-mange "
85 76
                               "service-list for available service IDs.")
86 77

  
87
        order = options.get('order')
88 78
        name = options.get('name')
89
        url = options.get('url')
90
        icon = options.get('icon')
79
        api_url = options.get('api_url')
91 80
        auth_token = options.get('auth_token')
92 81
        renew_token = options.get('renew_token')
93 82

  
94
        if order != None:
95
            service.order = order
96

  
97 83
        if name:
98 84
            service.name = name
99 85

  
100
        if url:
101
            service.url = url
102

  
103
        if icon:
104
            service.icon = icon
86
        if api_url:
87
            service.api_url = api_url
105 88

  
106 89
        if auth_token:
107 90
            service.auth_token = auth_token
b/snf-astakos-app/astakos/im/models.py
75 75

  
76 76
import astakos.im.messages as astakos_messages
77 77
from synnefo.lib.db.managers import ForUpdateManager
78
from synnefo.lib.ordereddict import OrderedDict
78 79

  
79 80
from astakos.quotaholder.api import QH_PRACTICALLY_INFINITE
80 81
from synnefo.lib.db.intdecimalfield import intDecimalField
......
86 87
DEFAULT_CONTENT_TYPE = None
87 88
_content_type = None
88 89

  
90

  
89 91
def get_content_type():
90 92
    global _content_type
91 93
    if _content_type is not None:
92 94
        return _content_type
93 95

  
94 96
    try:
95
        content_type = ContentType.objects.get(app_label='im', model='astakosuser')
97
        content_type = ContentType.objects.get(app_label='im',
98
                                               model='astakosuser')
96 99
    except:
97 100
        content_type = DEFAULT_CONTENT_TYPE
98 101
    _content_type = content_type
......
100 103

  
101 104
inf = float('inf')
102 105

  
106

  
107
def dict_merge(a, b):
108
    """
109
    http://www.xormedia.com/recursively-merge-dictionaries-in-python/
110
    """
111
    if not isinstance(b, dict):
112
        return b
113
    result = copy.deepcopy(a)
114
    for k, v in b.iteritems():
115
        if k in result and isinstance(result[k], dict):
116
                result[k] = dict_merge(result[k], v)
117
        else:
118
            result[k] = copy.deepcopy(v)
119
    return result
120

  
121

  
103 122
class Service(models.Model):
104
    name = models.CharField(_('Name'), max_length=255, unique=True, db_index=True)
105
    url = models.FilePathField()
106
    icon = models.FilePathField(blank=True)
123
    name = models.CharField(_('Name'), max_length=255, unique=True,
124
                            db_index=True)
125
    api_url = models.CharField(_('Service API url'), max_length=255)
107 126
    auth_token = models.CharField(_('Authentication Token'), max_length=32,
108 127
                                  null=True, blank=True)
109
    auth_token_created = models.DateTimeField(_('Token creation date'), null=True)
110
    auth_token_expires = models.DateTimeField(
111
        _('Token expiration date'), null=True)
112
    order = models.PositiveIntegerField(default=0)
113

  
114
    class Meta:
115
        ordering = ('order', )
128
    auth_token_created = models.DateTimeField(_('Token creation date'),
129
                                              null=True)
130
    auth_token_expires = models.DateTimeField(_('Token expiration date'),
131
                                              null=True)
116 132

  
117 133
    def renew_token(self, expiration_date=None):
118 134
        md5 = hashlib.md5()
119 135
        md5.update(self.name.encode('ascii', 'ignore'))
120
        md5.update(self.url.encode('ascii', 'ignore'))
136
        md5.update(self.api_url.encode('ascii', 'ignore'))
121 137
        md5.update(asctime())
122 138

  
123 139
        self.auth_token = b64encode(md5.digest())
......
130 146
    def __str__(self):
131 147
        return self.name
132 148

  
149
    @classmethod
150
    def catalog(cls, orderfor=None):
151
        catalog = {}
152
        services = list(cls.objects.all())
153
        metadata = presentation.SERVICES
154
        metadata = dict_merge(presentation.SERVICES,
155
                              astakos_settings.SERVICES_META)
156
        for service in services:
157
            if service.name in metadata:
158
                d = {'api_url': service.api_url, 'name': service.name}
159
                metadata[service.name].update(d)
160

  
161
        def service_by_order(s):
162
            return s[1].get('order')
163

  
164
        def service_by_dashbaord_order(s):
165
            return s[1].get('dashboard').get('order')
166

  
167
        for service, info in metadata.iteritems():
168
            default_meta = presentation.service_defaults(service)
169
            base_meta = metadata.get(service, {})
170
            settings_meta = astakos_settings.SERVICES_META.get(service, {})
171
            service_meta = dict_merge(default_meta, base_meta)
172
            meta = dict_merge(service_meta, settings_meta)
173
            catalog[service] = meta
174

  
175
        order_key = service_by_order
176
        if orderfor == 'dashboard':
177
            order_key = service_by_dashbaord_order
178

  
179
        ordered_catalog = OrderedDict(sorted(catalog.iteritems(),
180
                                             key=order_key))
181
        return ordered_catalog
182

  
133 183

  
134 184
_presentation_data = {}
135 185
def get_presentation(resource):
b/snf-astakos-app/astakos/im/presentation.py
143 143
                        'cyclades.network.private'
144 144
                        ]
145 145
}
146

  
147

  
148
def service_defaults(service_name):
149
    """
150
    Metadata for unkown services
151
    """
152
    return {
153
        'name': service_name,
154
        'order': 1000,
155
        'verbose_name': service_name.title(),
156
        'cloudbar': {
157
            'show': True,
158
            'title': service_name
159
        },
160
        'dashboard': {
161
            'show': True,
162
            'order': 1000,
163
            'description': '%s service' % service_name
164
        }
165
    }
166

  
167

  
168
SERVICES = {
169
    'astakos': {
170
        'url': '/im/landing',
171
        'order': 1,
172
        'dashboard': {
173
            'order': 3,
174
            'show': True,
175
            'description': "Access the dashboard from the top right corner "
176
                           "of your screen. Here you can manage your profile, "
177
                           "see the usage of your resources and manage "
178
                           "projects to share virtual resources with "
179
                           "colleagues."
180
        },
181
        'cloudbar': {
182
            'show': False
183
        }
184
    },
185
    'pithos': {
186
        'url': '/pithos/ui/',
187
        'order': 2,
188
        'dashboard': {
189
            'order': 1,
190
            'show': True,
191
            'description': "Pithos is the File Storage service. "
192
                           "Click to start uploading and managing your "
193
                           "files on the cloud."
194
        },
195
        'cloudbar': {
196
            'show': True
197
        }
198
    },
199
    'cyclades': {
200
        'url': '/cyclades/ui/',
201
        'order': 3,
202
        'dashboard': {
203
            'order': 2,
204
            'show': True,
205
            'description': "Cyclades is the Compute and Network Service. "
206
                           "Click to start creating Virtual Machines and "
207
                           "connect them to arbitrary Networks."
208
        },
209
        'cloudbar': {
210
            'show': True
211
        }
212
    }
213
}
b/snf-astakos-app/astakos/im/settings.py
235 235

  
236 236
# Whether or not to display projects in astakos menu
237 237
PROJECTS_VISIBLE = getattr(settings, 'ASTAKOS_PROJECTS_VISIBLE', False)
238

  
239
# A way to extend the settings presentation metadata
240
SERVICES_META = getattr(settings, 'ASTAKOS_SERVICES_META', False)
b/snf-astakos-app/astakos/im/static/im/cloudbar/cloudbar.js
55 55
            $.each(data, function(i, el){
56 56
            var sli = $("<li>");
57 57
            var slink = $("<a>");
58
            if (el.icon) {
59
                slink.append($('<img src="'+cssloc+el.icon+'"/>'));
58
            if (!el.cloudbar) { el.cloudbar = {} }
59
            var title = el.cloudbar.name || el.verbose_name || el.name;
60
            if (!el.cloudbar.show) { return }
61
            if (el.cloudbar.icon) {
62
                slink.append($('<img alt="'+title+'" src="'+cssloc+el.cloudbar.icon+'"/>'));
60 63
                slink.addClass("with-icon");
61 64
            } else {
62
                slink.text(el.name);
65
                slink.html(title);
63 66
            }
64 67
            slink.attr('href', el.url);
65 68
            slink.attr('title', el.name);
b/snf-astakos-app/astakos/im/templates/im/landing.html
4 4

  
5 5
{% block page.body %}
6 6
<div class="landing-page">
7
	
8
	<div class="two-cols clearfix dotted pithos">
9
		<div class="rt">
10
    		<a href="/pithos/ui/"><img class="pic" src="{{ IM_STATIC_URL }}images/landing-pithos.png" /></a>
11
		</div>
12
		<div class="lt">
13
			<a href="/pithos/ui/">Pithos</a> is the File Storage service. Click to start
14
uploading and managing your files on the cloud.
15

  
16
		</div>
17
	</div>
18
	<div class="two-cols clearfix dotted cyclades">
19
		<div class="rt">
20
  			<a href="/ui/"><img class="pic" src="{{ IM_STATIC_URL }}images/landing-cyclades.png" /></a>
21
		</div>
22
		<div class="lt">
23
			<a href="/ui/">Cyclades</a> is the Compute and Network Service. Click to start
24
creating Virtual Machines and connect them to arbitrary Networks.
25

  
26
		</div>
27
	</div>
28
	<div class="two-cols clearfix dotted dashboard">
29
		<div class="rt">
30
    		<a href="{% url astakos.im.views.edit_profile %}"><img class="pic" src="{{ IM_STATIC_URL }}images/landing-dashboard.png" /></a>
31
		</div>
32
		<div class="lt">
33
			Access the <a href="{% url astakos.im.views.edit_profile %}">dashboard</a> from
34
the top right corner of your screen. Here you can manage your profile, see the usage of your resources
35
and manage projects to share virtual resources with colleagues.
36

  
37
		</div>
38
	</div>
39
	<div class="two-cols clearfix dotted cms">
40
		<div class="rt">
41
    		<a href=""><img class="pic" src="{{ IM_STATIC_URL }}images/landing-cms.png" /></a>
42
		</div>
43
		<div class="lt">
44
			Click on the top left logo icon, to go back to the homepage.
45
 
46
		</div>
47
	</div>
7
    {% for id, service in services.items %}
8
    {% if service.dashboard.show %}
9
        <div class="two-cols clearfix dotted {{ id }}">
10
            <div class="rt">
11
                {% if service.url %}
12
                <a href="{{ service.url }}">
13
                {% endif %}
14
                <img class="pic" src="{{ IM_STATIC_URL }}images/landing-{{ id }}.png" />
15
                {% if service.url %}
16
                </a>
17
                {% endif %}
18
            </div>
19
            <div class="lt">
20
                {{ service.dashboard.description }}
21
            </div>
22
        </div>
23
    {% endif %}
24
    {% endfor %}
48 25
</div>
49 26
<script type="text/javascript">
50 27
	$(document).ready(function() {
b/snf-astakos-app/astakos/im/templatetags/astakos_tags.py
268 268

  
269 269
    content = render_to_string(template, tpl_context)
270 270
    return content
271

  
b/snf-astakos-app/astakos/im/templatetags/filters.py
41 41
from django.core.paginator import Paginator, EmptyPage
42 42
from django.db.models.query import QuerySet
43 43

  
44
from synnefo.lib.ordereddict import OrderedDict
44 45

  
45 46
from astakos.im import settings
46 47
from astakos.im.models import ProjectResourceGrant
b/snf-astakos-app/astakos/im/views.py
79 79
from astakos.im.models import (
80 80
    AstakosUser, ApprovalTerms,
81 81
    EmailChange, AstakosUserAuthProvider, PendingThirdPartyUser,
82
    ProjectApplication, ProjectMembership, Project)
82
    ProjectApplication, ProjectMembership, Project, Service)
83 83
from astakos.im.util import (
84 84
    get_context, prepare_response, get_query, restrict_next)
85 85
from astakos.im.forms import (
......
1053 1053
    # presentation data
1054 1054
    resource_groups = presentation.RESOURCES.get('groups', {})
1055 1055
    resource_catalog = ()
1056
    resource_keys = []
1056 1057

  
1057 1058
    # resources in database
1058 1059
    result = callpoint.list_resources()
......
1647 1648
@login_required
1648 1649
@signed_terms_required
1649 1650
def landing(request):
1651
    context = {'services': Service.catalog(orderfor='dashboard')}
1650 1652
    return render_response(
1651 1653
        'im/landing.html',
1652
        context_instance=get_context(request))
1654
        context_instance=get_context(request), **context)
1653 1655

  
1654 1656

  
1655 1657
def api_access(request):

Also available in: Unified diff