1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-08-01 23:38:37 +00:00

new devices page

This commit is contained in:
lian 2016-03-15 18:18:45 +08:00
parent 585b48e3c4
commit a43b3f969e
23 changed files with 6120 additions and 178 deletions

View File

@ -10,3 +10,4 @@ django-statici18n==1.1.2
djangorestframework==3.3.1
git+git://github.com/haiwen/django-constance.git@bde7f7cdfd0ed1631a6817fd4cd76f37bf54fe35#egg=django-constance[database]
openpyxl==2.3.0
pytz==2015.7

View File

@ -2,7 +2,6 @@ import json
from django.core.paginator import EmptyPage, InvalidPage
from django.http import HttpResponse
from django.utils.dateformat import DateFormat
from rest_framework import status
from rest_framework.authentication import SessionAuthentication
@ -16,6 +15,7 @@ from seahub.api2.throttling import UserRateThrottle
from seahub.api2.utils import api_error
from seahub.group.models import GroupMessage
from seahub.utils.paginator import Paginator
from seahub.utils.timeutils import datetime_to_isoformat_timestr
from .utils import api_check_group
json_content_type = 'application/json; charset=utf-8'
@ -55,12 +55,13 @@ class GroupDiscussions(APIView):
msgs = []
for e in group_msgs:
isoformat_timestr = datetime_to_isoformat_timestr(e.timestamp)
msgs.append({
"group_id": group_id,
"discussion_id": e.pk,
"user": e.from_email,
"content": e.message,
"created_at": e.timestamp.strftime("%Y-%m-%dT%H:%M:%S") + DateFormat(e.timestamp).format('O'),
"created_at": isoformat_timestr,
})
return HttpResponse(json.dumps(msgs), status=200,
@ -79,10 +80,11 @@ class GroupDiscussions(APIView):
from_email=username,
message=content)
isoformat_timestr = datetime_to_isoformat_timestr(discuss.timestamp)
return Response({
"group_id": group_id,
"discussion_id": discuss.pk,
"user": username,
"content": discuss.message,
"created_at": discuss.timestamp.strftime("%Y-%m-%dT%H:%M:%S") + DateFormat(discuss.timestamp).format('O'),
"created_at": isoformat_timestr,
}, status=201)

View File

@ -1,6 +1,5 @@
import logging
from django.utils.dateformat import DateFormat
from django.utils.translation import ugettext as _
from django.template.defaultfilters import filesizeformat
@ -21,7 +20,7 @@ from seahub.avatar.settings import GROUP_AVATAR_DEFAULT_SIZE
from seahub.avatar.templatetags.group_avatar_tags import api_grp_avatar_url, \
get_default_group_avatar_url
from seahub.utils import is_org_context, is_valid_username
from seahub.utils.timeutils import dt, utc_to_local
from seahub.utils.timeutils import timestamp_to_isoformat_timestr
from seahub.group.utils import validate_group_name, check_group_name_conflict, \
is_group_member, is_group_admin, is_group_owner, is_group_admin_or_owner
from seahub.group.views import remove_group_common
@ -52,12 +51,12 @@ def get_group_info(request, group_id, avatar_size=GROUP_AVATAR_DEFAULT_SIZE):
logger.error(e)
avatar_url = get_default_group_avatar_url()
val = utc_to_local(dt(group.timestamp))
isoformat_timestr = timestamp_to_isoformat_timestr(group.timestamp)
group_info = {
"id": group.id,
"name": group.group_name,
"owner": group.creator_name,
"created_at": val.strftime("%Y-%m-%dT%H:%M:%S") + DateFormat(val).format('O'),
"created_at": isoformat_timestr,
"avatar_url": request.build_absolute_uri(avatar_url),
"admins": get_group_admins(group.id),
"wiki_enabled": is_wiki_mod_enabled_for_group(group_id)

View File

@ -2,7 +2,6 @@ import json
import os
from django.http import HttpResponse
from django.utils.dateformat import DateFormat
from rest_framework.authentication import SessionAuthentication
from rest_framework.permissions import IsAuthenticated
from rest_framework.views import APIView
@ -13,6 +12,7 @@ from seahub.api2.authentication import TokenAuthentication
from seahub.api2.throttling import UserRateThrottle
from seahub.share.models import UploadLinkShare
from seahub.utils import gen_shared_upload_link
from seahub.utils.timeutils import datetime_to_isoformat_timestr
json_content_type = 'application/json; charset=utf-8'
@ -45,7 +45,7 @@ class SharedUploadLinksView(APIView):
link.repo = r
if link.expire_date:
expire_date = link.expire_date.strftime("%Y-%m-%dT%H:%M:%S") + DateFormat(link.expire_date).format('O')
expire_date = datetime_to_isoformat_timestr(link.expire_date)
else:
expire_date = ""
@ -54,7 +54,7 @@ class SharedUploadLinksView(APIView):
"repo_id": link.repo_id,
"path": link.path,
"token": link.token,
"ctime": link.ctime.strftime("%Y-%m-%dT%H:%M:%S") + DateFormat(link.ctime).format('O'),
"ctime": datetime_to_isoformat_timestr(link.ctime),
"view_cnt": link.view_cnt,
"expire_date": expire_date,
})

View File

@ -61,6 +61,7 @@ urlpatterns = patterns('',
url(r'^repos/(?P<repo_id>[-0-9-a-f]{36})/dir/download/$', DirDownloadView.as_view(), name='api2-dir-download'),
url(r'^repos/(?P<repo_id>[-0-9-a-f]{36})/thumbnail/$', ThumbnailView.as_view(), name='api2-thumbnail'),
url(r'^starredfiles/', StarredFileView.as_view(), name='starredfiles'),
url(r'^devices/', DevicesView.as_view(), name='api2-devices'),
url(r'^shared-repos/$', SharedRepos.as_view(), name='sharedrepos'),
url(r'^shared-repos/(?P<repo_id>[-0-9-a-f]{36})/$', SharedRepo.as_view(), name='sharedrepo'),
url(r'^beshared-repos/$', BeShared.as_view(), name='beshared'),

View File

@ -29,7 +29,6 @@ from django.template.defaultfilters import filesizeformat
from django.shortcuts import render_to_response
from django.utils import timezone
from django.utils.translation import ugettext as _
from django.utils.dateformat import DateFormat
from .throttling import ScopedRateThrottle, AnonRateThrottle, UserRateThrottle
from .authentication import TokenAuthentication
@ -75,11 +74,12 @@ from seahub.utils import gen_file_get_url, gen_token, gen_file_upload_url, \
gen_file_share_link, gen_dir_share_link, is_org_context, gen_shared_link, \
get_org_user_events, calculate_repos_last_modify, send_perm_audit_msg, \
gen_shared_upload_link, convert_cmmt_desc_link, is_org_repo_creation_allowed
from seahub.utils.devices import get_user_devices, do_unlink_device
from seahub.utils.repo import get_sub_repo_abbrev_origin_path
from seahub.utils.star import star_file, unstar_file
from seahub.utils.file_types import IMAGE, DOCUMENT
from seahub.utils.file_size import get_file_size_unit
from seahub.utils.timeutils import utc_to_local
from seahub.utils.timeutils import utc_to_local, datetime_to_isoformat_timestr
from seahub.views import validate_owner, is_registered_user, check_file_lock, \
group_events_data, get_diff, create_default_library, get_owned_repo_list, \
list_inner_pub_repos, get_virtual_repos_by_owner, \
@ -1861,6 +1861,38 @@ class OwaFileView(APIView):
send_file_access_msg(request, repo, path, 'api')
return Response(wopi_dict)
class DevicesView(APIView):
"""List user devices"""
authentication_classes = (TokenAuthentication, SessionAuthentication)
permission_classes = (IsAuthenticated,)
throttle_classes = (UserRateThrottle, )
def get(self, request, format=None):
username = request.user.username
user_devices = get_user_devices(username)
return Response(user_devices)
def delete(self, request, format=None):
platform = request.data.get('platform', '')
device_id = request.data.get('device_id', '')
if not platform:
error_msg = 'platform invalid.'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
if not device_id:
error_msg = 'device_id invalid.'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
try:
do_unlink_device(request.user.username, platform, device_id)
except SearpcError as e:
logger.error(e)
error_msg = 'Internal Server Error'
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
return Response({'success': True})
class FileView(APIView):
"""
@ -4327,8 +4359,7 @@ class RepoDownloadSharedLinks(APIView):
shared_link['create_by'] = fs.username
shared_link['creator_name'] = email2nickname(fs.username)
# return time with time zone: 2016-01-18T15:03:10+0800
shared_link['create_time'] = fs.ctime.strftime("%Y-%m-%dT%H:%M:%S") + DateFormat(fs.ctime).format('O')
shared_link['create_time'] = datetime_to_isoformat_timestr(fs.ctime)
shared_link['token'] = fs.token
shared_link['path'] = path
shared_link['name'] = os.path.basename(path.rstrip('/')) if path != '/' else '/'
@ -4412,8 +4443,7 @@ class RepoUploadSharedLinks(APIView):
shared_link['create_by'] = fs.username
shared_link['creator_name'] = email2nickname(fs.username)
# return time with time zone: 2016-01-18T15:03:10+0800
shared_link['create_time'] = fs.ctime.strftime("%Y-%m-%dT%H:%M:%S") + DateFormat(fs.ctime).format('O')
shared_link['create_time'] = datetime_to_isoformat_timestr(fs.ctime)
shared_link['token'] = fs.token
shared_link['path'] = path
shared_link['name'] = os.path.basename(path.rstrip('/')) if path != '/' else '/'

View File

@ -1,120 +0,0 @@
{% extends "home_base.html" %}
{% load seahub_tags avatar_tags i18n %}
{% block sub_title %}{% trans "Devices" %} - {% endblock %}
{% block cur_devices %}tab-cur{% endblock %}
{% block right_panel %}
<h3 class="hd">{% trans "Devices" %}</h3>
{% if devices %}
<table class="client-list">
<tr>
<th width="13%">{% trans "Platform" %}</th>
<th width="25%">{% trans "Device Name" %}</th>
<th width="20%">{% trans "IP" %}</th>
<th width="17%">{% trans "Last Access" %}</th>
<th width="15%">{% trans "# Libraries" %}</th>
<th width="10%"></th>
</tr>
{% for device in devices %}
<tr data-platform="{{ device.platform }}" data-name="{{ device.device_name }}" data-device-id="{{ device.device_id }}">
<td>{{ device.platform }}</td>
<td>{{ device.device_name }}</td>
<td>{{ device.last_login_ip }}</td>
<td>{{ device.last_accessed | translate_seahub_time }}</td>
<td>
<span class="lib-num">{{ device.synced_repos|length }}{% if device.synced_repos %} <span class="dir-icon icon-caret-down"></span>{% endif %}</span>
{% if device.synced_repos %}
<ul class="lib-list hide">
{% for repo in device.synced_repos %}
<li><a href="{% url 'view_common_lib_dir' repo.repo_id '' %}">{{ repo.repo_name }}</a></li>
{% endfor %}
</ul>
{% endif %}
</td>
<td>
<div>
<span class="unlink-device op-icon sf2-icon-delete vh" title="{% trans "Unlink" %}"></span>
</div>
</td>
</tr>
{% endfor %}
</table>
<div class="op-confirm unsync-confirm hide" id="unsync-cfm-popup">
<p class="details">{% trans "Really want to unlink %s?" %}</p>
<p>{% trans "It will immediately stop syncing." %}</p>
<button class="yes">{% trans "Yes" %}</button>
<button class="no">{% trans "No" %}</button>
</div>
{% else %}
<div class="empty-tips">
<h2 class="alc">{% trans "You do not have connected devices" %}</h2>
<p>{% trans "Your clients (Desktop/Android/iOS) will be listed here." %}</p>
</div>
{% endif %}
{% endblock %}
{% block extra_script %}{{block.super}}
<script type="text/javascript">
$('.lib-num').click(function(){
var lib_list = $(this).next('.lib-list');
var dir_icon = $(this).children('.dir-icon');
if (lib_list.length > 0) {
lib_list.toggleClass('hide');
if (lib_list.hasClass('hide')) {
dir_icon.removeClass('icon-caret-up').addClass('icon-caret-down');
} else {
dir_icon.removeClass('icon-caret-down').addClass('icon-caret-up');
}
}
return false;
});
$('#main-panel').removeClass('ovhd');
$('.unlink-device').click(function() {
var op = $(this);
var cont = op.parent().css({'position': 'relative'}),
cfm;
var tr = op.parents('tr');
// only show 1 popup each time.
$('.unsync-confirm', op.parents('table')).addClass('hide');
if (cont.find('.unsync-confirm').length == 1) {
cfm = cont.find('.unsync-confirm');
} else {
cfm = $('#unsync-cfm-popup').clone().removeAttr('id');
cont.append(cfm);
cfm.css({'right':0, 'top': op.position().top + op.height() + 3, 'white-space':'nowrap'});
}
cfm.removeClass('hide');
var details = $('.details', cfm);
details.html(details.html().replace('%s', tr.attr('data-name')));
$('.no',cfm).unbind().click(function() {
cfm.addClass('hide');
});
$('.yes',cfm).unbind().click(function() {
cfm.addClass('hide');
$.ajax({
url: '{% url 'unlink_device' %}',
type: 'POST',
dataType: 'json',
beforeSend: prepareCSRFToken,
data: {
'platform': tr.data('platform'),
'device_id': tr.data('device-id')
},
success: function() {
tr.remove();
feedback("{% trans "Successfully unlinked." %}", 'success');
},
error: ajaxErrorHandler
});
});
return false;
});
</script>
{% endblock %}

View File

@ -50,7 +50,7 @@
<li class="tab {% block cur_personal_wiki %}{% endblock %}"><a href="{% url 'personal_wiki' %}"><span class="sf2-icon-wiki"></span>{% trans "Personal Wiki" %}</a></li>
{% endif %}
{% endfor %}
<li class="tab {% block cur_devices %}{% endblock %}"><a href="{% url 'devices' %}"><span class="sf2-icon-monitor"></span>{% trans "Devices" %}</a></li>
<li class="tab {% block cur_devices %}{% endblock %}"><a href="{{ SITE_ROOT }}#devices/"><span class="sf2-icon-monitor"></span>{% trans "Devices" %}</a></li>
</ul>
<h3 class="hd">{% trans "Share Admin" %}</h3>

View File

@ -666,7 +666,7 @@
<li class="tab"><a href="{% url 'personal_wiki' %}"><span class="sf2-icon-wiki"></span>{% trans "Personal Wiki" %}</a></li>
<% } %>
<% } %>
<li class="tab"><a href="{% url 'devices' %}"><span class="sf2-icon-monitor"></span>{% trans "Devices" %}</a></li>
<li class="tab<% if (cur_tab == 'devices') { %> tab-cur<% } %>"><a href="{{ SITE_ROOT }}#devices/"><span class="sf2-icon-monitor"></span>{% trans "Devices" %}</a></li>
</ul>
<h3 class="hd">{% trans "Share Admin" %}</h3>
@ -845,6 +845,27 @@
<span class="sf2-icon-delete unstar op-icon vh" title="{% trans "Unstar" %}"></span>
</td>
</script>
<script type="text/template" id="device-item-tmpl">
<td><%- platform %></td>
<td><%- device_name %></td>
<td><%- last_login_ip %></td>
<td><time title='<%- time %>'><%- time_from_now %></time></td>
<td>
<span class="lib-num cspt"><%- synced_repos_length %><% if (synced_repos.length > 0) { %> <span class="dir-icon icon-caret-down"></span><% } %></span>
<% if (synced_repos.length > 0) { %>
<ul class="lib-list hide">
<% for (var i = 0, len = synced_repos_length; i < len; i++) { %>
<li><a href="#my-libs/lib/<%- synced_repos[i].repo_id %>"><%- synced_repos[i].repo_name %></a></li>
<% } %>
</ul>
<% } %>
</td>
<td>
<div>
<span class="unlink-device op-icon sf2-icon-delete vh" title="{% trans "Unlink" %}"></span>
</div>
</td>
</script>
<script type="text/template" id="my-repos-hd-tmpl">
<tr>
<th width="4%"><!--icon--></th>

View File

@ -75,6 +75,28 @@
<button id="activities-more" class="hide">{% trans 'More' %}</button>
</div>
<div id="devices" class="hide">
<h3 class="hd">{% trans "Devices" %}</h3>
<table class="hide">
<thead>
<tr>
<th width="13%">{% trans "Platform" %}</th>
<th width="25%">{% trans "Device Name" %}</th>
<th width="20%">{% trans "IP" %}</th>
<th width="17%">{% trans "Last Access" %}</th>
<th width="15%">{% trans "# Libraries" %}</th>
<th width="10%"></th>
</tr>
</thead>
<tbody></tbody>
</table>
<span class="loading-icon loading-tip"></span>
<div class="empty-tips hide">
<h2 class="alc">{% trans "You do not have connected devices" %}</h2>
<p>{% trans "Your clients (Desktop/Android/iOS) will be listed here." %}</p>
</div>
</div>
<div id="guide-for-new" class="hide">
<span class="icon-lightbulb fleft"></span>
<div class="txt">
@ -232,6 +254,7 @@ app["pageOptions"] = {
enable_thumbnail: {% if enable_thumbnail %} true {% else %} false {% endif %},
thumbnail_default_size: {{ thumbnail_default_size }},
thumbnail_size_for_grid: {{ thumbnail_size_for_grid }},
language_code: "{{ LANGUAGE_CODE }}",
enable_encrypted_library: {% if enable_encrypted_library %} true {% else %} false {% endif %},
enable_repo_history_setting: {% if enable_repo_history_setting %} true {% else %} false {% endif %},
max_upload_file_size: {% if max_upload_file_size %} {{ max_upload_file_size }} {% else %} '' {% endif %},

View File

@ -56,9 +56,6 @@ urlpatterns = patterns(
url(r'^home/wiki_page_edit/(?P<page_name>[^/]+)$', personal_wiki_page_edit, name='personal_wiki_page_edit'),
url(r'^home/wiki_page_delete/(?P<page_name>[^/]+)$', personal_wiki_page_delete, name='personal_wiki_page_delete'),
url(r'^devices/$', devices, name='devices'),
url(r'^home/devices/unlink/$', unlink_device, name='unlink_device'),
# url(r'^home/public/reply/(?P<msg_id>[\d]+)/$', innerpub_msg_reply, name='innerpub_msg_reply'),
# url(r'^home/owner/(?P<owner_name>[^/]+)/$', ownerhome, name='ownerhome'),

View File

@ -3,6 +3,7 @@ import datetime
from seaserv import seafile_api
from seahub.api2.models import TokenV2, DESKTOP_PLATFORMS
from seahub.utils.timeutils import datetime_to_isoformat_timestr
logger = logging.getLogger(__name__)
@ -17,7 +18,6 @@ def _last_sync_time(repos):
def get_user_devices(username):
devices = TokenV2.objects.get_user_devices(username)
peer_repos_map = get_user_synced_repo_infos(username)
for device in devices:
@ -29,6 +29,7 @@ def get_user_devices(username):
device['last_accessed'] = max(device['last_accessed'],
_last_sync_time(repos))
device['last_accessed'] = datetime_to_isoformat_timestr(device['last_accessed'])
return devices
def get_user_synced_repo_infos(username):

View File

@ -1,3 +1,4 @@
import pytz
import datetime
from django.conf import settings
from django.utils import six
@ -11,7 +12,6 @@ def dt(value):
except ValueError:
# TODO: need a better way to handle 64 bits timestamp.
return datetime.datetime.utcfromtimestamp(value/1000000)
def value_to_db_datetime(value):
if value is None:
@ -33,3 +33,16 @@ def utc_to_local(dt):
utc = dt.replace(tzinfo=timezone.utc)
local = timezone.make_naive(utc, tz)
return local
def timestamp_to_isoformat_timestr(timestamp):
dt_obj = dt(timestamp)
dt_obj = dt_obj.replace(microsecond=0)
pytz_obj = pytz.timezone(settings.TIME_ZONE)
isoformat_timestr = pytz_obj.localize(dt_obj).isoformat()
return isoformat_timestr
def datetime_to_isoformat_timestr(datetime):
datetime = datetime.replace(microsecond=0)
pytz_obj = pytz.timezone(settings.TIME_ZONE)
isoformat_timestr = pytz_obj.localize(datetime).isoformat()
return isoformat_timestr

View File

@ -60,7 +60,6 @@ from seahub.utils.star import get_dir_starred_files
from seahub.utils.timeutils import utc_to_local
from seahub.views.modules import MOD_PERSONAL_WIKI, enable_mod_for_user, \
disable_mod_for_user
from seahub.utils.devices import get_user_devices, do_unlink_device
import seahub.settings as settings
from seahub.settings import FILE_PREVIEW_MAX_SIZE, INIT_PASSWD, USE_PDFJS, \
FILE_ENCODING_LIST, FILE_ENCODING_TRY_LIST, AVATAR_FILE_STORAGE, \
@ -886,37 +885,6 @@ def libraries(request):
'can_add_pub_repo': can_add_pub_repo,
}, context_instance=RequestContext(request))
@login_required
@user_mods_check
def devices(request):
"""List user devices"""
username = request.user.username
user_devices = get_user_devices(username)
return render_to_response('devices.html', {
"devices": user_devices,
}, context_instance=RequestContext(request))
@login_required_ajax
@require_POST
def unlink_device(request):
content_type = 'application/json; charset=utf-8'
platform = request.POST.get('platform', '')
device_id = request.POST.get('device_id', '')
if not platform or not device_id:
return HttpResponseBadRequest(json.dumps({'error': _(u'Argument missing')}),
content_type=content_type)
try:
do_unlink_device(request.user.username, platform, device_id)
except:
return HttpResponse(json.dumps({'error': _(u'Internal server error')}),
status=500, content_type=content_type)
return HttpResponse(json.dumps({'success': True}), content_type=content_type)
@login_required
@require_POST
def unsetinnerpub(request, repo_id):

View File

@ -0,0 +1,17 @@
define([
'underscore',
'backbone',
'common',
'app/models/device'
], function(_, Backbone, Common, Device) {
'use strict';
var DevicesCollection = Backbone.Collection.extend({
model: Device,
url: function () {
return Common.getUrl({name: 'devices'});
}
});
return DevicesCollection;
});

View File

@ -0,0 +1,10 @@
define([
'underscore',
'backbone'
], function(_, Backbone) {
'use strict';
var Device = Backbone.Model.extend({});
return Device;
});

View File

@ -29,6 +29,7 @@ define([
'common/lib/:repo_id(/*path)': 'showCommonDir',
'starred/': 'showStarredFile',
'activities/': 'showActivities',
'devices/': 'showDevices',
// Default
'*actions': 'showRepos'
},
@ -96,6 +97,12 @@ define([
this.sideNavView.setCurTab('starred');
},
showDevices: function() {
this.switchCurrentView(this.myHomeView);
this.myHomeView.showDevices();
this.sideNavView.setCurTab('devices');
},
showActivities: function() {
this.switchCurrentView(this.myHomeView);
this.myHomeView.showActivities();

View File

@ -0,0 +1,104 @@
define([
'jquery',
'underscore',
'backbone',
'common',
], function($, _, Backbone, Common) {
'use strict';
var DeviceView = Backbone.View.extend({
tagName: 'tr',
template: _.template($('#device-item-tmpl').html()),
events: {
'mouseenter': 'showAction',
'mouseleave': 'hideAction',
'click .unlink-device': 'unlinkDevice',
'click .lib-num': 'showSyncedRepos'
},
initialize: function() {
},
render: function () {
var data = this.model.toJSON();
if (typeof(data['synced_repos']) == 'undefined') {
data['synced_repos'] = new Array();
}
if (data['synced_repos']) {
data['synced_repos_length'] = data['synced_repos'].length;
} else {
data['synced_repos_length'] = 0;
}
// convert to human readable time
var now = new Date(),
last_accessed = Common.getMomentWithLocale(data['last_accessed']);
data['time'] = last_accessed.format('LLLL');
if (last_accessed - now > 0) {
data['time_from_now'] = gettext("Just now");
} else {
data['time_from_now'] = last_accessed.fromNow();
}
this.$el.html(this.template(data));
return this;
},
showAction: function() {
this.$el.addClass('hl');
this.$el.find('.op-icon').removeClass('vh');
},
hideAction: function() {
this.$el.removeClass('hl');
this.$el.find('.op-icon').addClass('vh');
},
showSyncedRepos: function(e) {
var $lib_num = $(e.currentTarget);
var lib_list = $lib_num.next('.lib-list');
var dir_icon = $lib_num.children('.dir-icon');
if (lib_list.length > 0) {
lib_list.toggleClass('hide');
if (lib_list.hasClass('hide')) {
dir_icon.removeClass('icon-caret-up').addClass('icon-caret-down');
} else {
dir_icon.removeClass('icon-caret-down').addClass('icon-caret-up');
}
}
},
unlinkDevice: function() {
var _this = this,
data = {
'platform': this.model.get('platform'),
'device_id': this.model.get('device_id')
};
$.ajax({
url: Common.getUrl({name: 'devices'}),
type: 'DELETE',
dataType: 'json',
beforeSend: Common.prepareCSRFToken,
data: data,
success: function() {
_this.remove();
Common.feedback(gettext("Success"), 'success');
},
error: function (xhr) {
Common.ajaxErrorHandler(xhr);
}
});
}
});
return DeviceView;
});

View File

@ -0,0 +1,58 @@
define([
'jquery',
'underscore',
'backbone',
'common',
'app/views/device',
'app/collections/devices',
], function($, _, Backbone, Common, Device, DevicesCollection) {
'use strict';
var DevicesView = Backbone.View.extend({
el: $('#devices'),
initialize: function() {
this.$table = this.$('table');
this.$tableBody = this.$('tbody');
this.$loadingTip = this.$('.loading-tip');
this.$emptyTip = this.$('.empty-tips');
this.devices = new DevicesCollection();
this.listenTo(this.devices, 'reset', this.reset);
},
addOne: function(device) {
var view = new Device({model: device});
this.$tableBody.append(view.render().el);
},
reset: function() {
this.$tableBody.empty();
this.$loadingTip.hide();
this.devices.each(this.addOne, this);
if (this.devices.length) {
this.$emptyTip.hide();
this.$table.show();
} else {
this.$emptyTip.show();
this.$table.hide();
}
},
hide: function() {
this.$el.hide();
},
show: function() {
this.$el.show();
this.$table.hide();
this.$loadingTip.show();
this.devices.fetch({reset: true});
}
});
return DevicesView;
});

View File

@ -6,9 +6,10 @@ define([
'app/views/myhome-repos',
'app/views/myhome-shared-repos',
'app/views/starred-file',
'app/views/devices',
'app/views/activities'
], function($, _, Backbone, Common, ReposView,
SharedReposView, StarredFileView, ActivitiesView) {
SharedReposView, StarredFileView, DevicesView, ActivitiesView) {
'use strict';
var MyHomeView = Backbone.View.extend({
@ -18,6 +19,7 @@ define([
this.reposView = new ReposView();
this.sharedReposView = new SharedReposView();
this.starredFileView = new StarredFileView();
this.devicesView = new DevicesView();
this.activitiesView = new ActivitiesView();
this.dirView = options.dirView;
@ -45,6 +47,12 @@ define([
this.currentView = this.starredFileView;
},
showDevices: function() {
this.currentView.hide();
this.devicesView.show();
this.currentView = this.devicesView;
},
showActivities: function() {
this.currentView.hide();
this.activitiesView.show();

View File

@ -41,6 +41,7 @@ require.config({
simplemodal: 'lib/jquery.simplemodal',
jstree: 'lib/jstree.1.0',
select2: 'lib/select2-3.5.2',
moment: 'lib/moment-with-locales',
underscore: 'lib/underscore',
backbone: 'lib/backbone',
@ -52,8 +53,9 @@ define([
'jquery',
'underscore',
'text', // Workaround for r.js, otherwise text.js will not be included
'pinyin-by-unicode'
], function($, _, text, PinyinByUnicode) {
'pinyin-by-unicode',
'moment',
], function($, _, text, PinyinByUnicode, Moment) {
return {
INFO_TIMEOUT: 10000, // 10 secs for info msg
SUCCESS_TIMEOUT: 3000, // 3 secs for success msg
@ -144,6 +146,7 @@ define([
case 'set_notice_seen_by_id': return siteRoot + 'ajax/set_notice_seen_by_id/';
case 'toggle_personal_modules': return siteRoot + 'ajax/toggle-personal-modules/';
case 'starred_files': return siteRoot + 'api2/starredfiles/';
case 'devices': return siteRoot + 'api2/devices/';
case 'events': return siteRoot + 'api2/events/';
case 'search_user': return siteRoot + 'api2/search-user/';
case 'user_profile': return siteRoot + 'profile/' + options.username + '/';
@ -191,6 +194,19 @@ define([
'default' : 'file.png'
},
getMomentWithLocale: function(time) {
var language_code;
if (app.pageOptions.language_code == 'en') {
language_code = 'en-gb';
} else if (app.pageOptions.language_code == 'es-ar' || app.pageOptions.language_code == 'es-mx') {
language_code = 'es';
} else {
language_code = app.pageOptions.language_code;
}
return Moment(time).locale(language_code);
},
getFileIconUrl: function(filename, size) {
if (size > 24) {
size = 192;

File diff suppressed because it is too large Load Diff

40
tests/api/test_devices.py Normal file
View File

@ -0,0 +1,40 @@
# -*- coding: utf-8 -*-
import json
from django.core.urlresolvers import reverse
from seahub.api2.models import TokenV2
from seahub.test_utils import BaseTestCase, Fixtures
class DevicesTest(BaseTestCase, Fixtures):
def setUp(self):
self.platform = 'android'
self.device_id = '4a0d62c1f27b3b74'
TokenV2.objects.get_or_create_token(self.user.username, self.platform,
self.device_id, u'PLK-AL10', u'2.0.3', u'5.0.2', '192.168.1.208')
def tearDown(self):
self.remove_repo()
def test_can_list(self):
self.login_as(self.user)
resp = self.client.get(reverse('api2-devices'))
self.assertEqual(200, resp.status_code)
json_resp = json.loads(resp.content)
assert json_resp[0]['platform'] == self.platform
assert json_resp[0]['device_id'] == self.device_id
def test_can_delete(self):
self.login_as(self.user)
data = 'platform=%s&device_id=%s' % (self.platform, self.device_id)
resp = self.client.delete(reverse('api2-devices'), data, 'application/x-www-form-urlencoded')
self.assertEqual(200, resp.status_code)
self.assertEqual(0, len(TokenV2.objects.all()))
def test_can_not_delete_with_invalid_args(self):
self.login_as(self.user)
resp = self.client.delete(reverse('api2-devices'))
self.assertEqual(400, resp.status_code)