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