1
0
mirror of https://github.com/dkmstr/openuds.git synced 2025-03-20 06:50:23 +03:00

Fixed 0024 migration to include also tags

Started accounts admin gui
This commit is contained in:
Adolfo Gómez García 2017-01-17 08:46:01 +01:00
parent 4401985f3c
commit 5897cf33f2
8 changed files with 563 additions and 11 deletions

View File

@ -0,0 +1,75 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2014 Virtual Cable S.L.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
'''
@itemor: Adolfo Gómez, dkmaster at dkmon dot com
'''
from __future__ import unicode_literals
from django.utils.translation import ugettext_lazy as _, ugettext
from uds.models import Account, AccountUsage
from uds.core.util import permissions
from uds.REST.model import ModelHandler
import logging
logger = logging.getLogger(__name__)
# Enclosed methods under /item path
class Accounts(ModelHandler):
'''
Processes REST requests about calendars
'''
model = Account
detail = {'usage': AccountUsage}
save_fields = ['name', 'comments', 'tags']
table_title = _('Accounts')
table_fields = [
{'name': {'title': _('Name'), 'visible': True, 'type': 'iconType'}},
{'comments': {'title': _('Comments')}},
{'tags': {'title': _('tags'), 'visible': False}},
]
def item_as_dict(self, calendar):
return {
'id': calendar.uuid,
'name': calendar.name,
'tags': [tag.tag for tag in calendar.tags.all()],
'comments': calendar.comments,
'permission': permissions.getEffectivePermission(self._user, calendar)
}
def getGui(self, type_):
return self.addDefaultFields([], ['name', 'comments', 'tags'])

View File

@ -33,7 +33,7 @@
from __future__ import unicode_literals
from django.utils.translation import ugettext, ugettext_lazy as _
from uds.models import DeployedService, OSManager, Service, Image, ServicesPoolGroup
from uds.models import DeployedService, OSManager, Service, Image, ServicesPoolGroup, Account
from uds.models.CalendarAction import CALENDAR_ACTION_INITIAL, CALENDAR_ACTION_MAX, CALENDAR_ACTION_CACHE_L1, CALENDAR_ACTION_CACHE_L2, CALENDAR_ACTION_PUBLISH
from uds.core.ui.images import DEFAULT_THUMB_BASE64
from uds.core.util.State import State
@ -69,7 +69,21 @@ class ServicesPools(ModelHandler):
'actions': ActionsCalendars
}
save_fields = ['name', 'comments', 'tags', 'service_id', 'osmanager_id', 'image_id', 'servicesPoolGroup_id', 'initial_srvs', 'cache_l1_srvs', 'cache_l2_srvs', 'max_srvs', 'show_transports']
save_fields = [
'name',
'comments',
'tags',
'service_id',
'osmanager_id',
'image_id',
'account_id',
'servicesPoolGroup_id',
'initial_srvs',
'cache_l1_srvs',
'cache_l2_srvs',
'max_srvs',
'show_transports'
]
remove_fields = ['osmanager_id', 'service_id']
table_title = _('Service Pools')
@ -116,10 +130,12 @@ class ServicesPools(ModelHandler):
'comments': item.comments,
'state': state,
'thumb': item.image.thumb64 if item.image is not None else DEFAULT_THUMB_BASE64,
'account': item.account.name if item.account is not None else '',
'service_id': item.service.uuid,
'provider_id': item.service.provider.uuid,
'image_id': item.image.uuid if item.image is not None else None,
'servicesPoolGroup_id': poolGroupId,
'account_id': item.account.uuid if item.account is not None else None,
'pool_group_name': poolGroupName,
'pool_group_thumb': poolGroupThumb,
'initial_srvs': item.initial_srvs,
@ -165,13 +181,20 @@ class ServicesPools(ModelHandler):
'type': gui.InputField.CHOICE_TYPE,
'rdonly': True,
'order': 101,
}, {
'name': 'account_id',
'values': [gui.choiceItem(-1, '')] + gui.sortedChoices([gui.choiceItem(v.uuid, v.name) for v in Account.objects.all()]),
'label': ugettext('Account'),
'tooltip': ugettext('Account associated to this service pool'),
'type': gui.InputField.CHOICE_TYPE,
'order': 102,
}, {
'name': 'image_id',
'values': [gui.choiceImage(-1, '--------', DEFAULT_THUMB_BASE64)] + gui.sortedChoices([gui.choiceImage(v.uuid, v.name, v.thumb64) for v in Image.objects.all()]),
'label': ugettext('Associated Image'),
'tooltip': ugettext('Image assocciated with this service'),
'type': gui.InputField.IMAGECHOICE_TYPE,
'order': 102,
'order': 105,
'tab': ugettext('Display'),
}, {
'name': 'servicesPoolGroup_id',
@ -179,7 +202,7 @@ class ServicesPools(ModelHandler):
'label': ugettext('Pool group'),
'tooltip': ugettext('Pool group for this pool (for pool clasify on display)'),
'type': gui.InputField.IMAGECHOICE_TYPE,
'order': 103,
'order': 106,
'tab': ugettext('Display'),
}, {
'name': 'initial_srvs',
@ -270,6 +293,18 @@ class ServicesPools(ModelHandler):
# If max < initial or cache_1 or cache_l2
fields['max_srvs'] = max((int(fields['initial_srvs']), int(fields['cache_l1_srvs']), int(fields['max_srvs'])))
accountId = fields['account_id']
fields['account_id'] = None
logger.debug('Account id: {}'.format(accountId))
if accountId != '-1':
try:
fields['account_id'] = Account.objects.get(uuid=processUuid(accountId)).id
except Exception:
logger.exception('Getting account ID')
imgId = fields['image_id']
fields['image_id'] = None
logger.debug('Image id: {}'.format(imgId))

View File

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10 on 2016-10-10 07:53
# Generated by Django 1.10.5 on 2017-01-17 08:45
from __future__ import unicode_literals
import datetime
@ -21,6 +21,7 @@ class Migration(migrations.Migration):
('uuid', models.CharField(default=None, max_length=50, null=True, unique=True)),
('name', models.CharField(db_index=True, max_length=128)),
('comments', models.CharField(max_length=256)),
('tags', models.ManyToManyField(to='uds.Tag')),
],
options={
'db_table': 'uds_accounts',

View File

@ -31,11 +31,12 @@
from __future__ import unicode_literals
__updated__ = '2016-09-21'
__updated__ = '2017-01-17'
from django.db import models
from uds.models.UUIDModel import UUIDModel
from uds.models.Tag import TaggingMixin
from uds.models.Util import getSqlDatetime
from django.db.models import signals
@ -44,11 +45,9 @@ import logging
logger = logging.getLogger(__name__)
class Account(UUIDModel):
class Account(UUIDModel, TaggingMixin):
'''
Account storing on DB model
This is intended for small images (i will limit them to 128x128), so storing at db is fine
'''
name = models.CharField(max_length=128, unique=False, db_index=True)
comments = models.CharField(max_length=256)

View File

@ -57,7 +57,7 @@ import six
import pickle
import logging
__updated__ = '2017-01-12'
__updated__ = '2017-01-17'
logger = logging.getLogger(__name__)
@ -368,7 +368,7 @@ class UserService(UUIDModel):
# 1.- If do not have any account associated, do nothing
# 2.- If called but already accounting, do nothing
# 3.- If called and not accounting, start accounting
if self.deployed_service.account is None or hasattr(self, 'accounting'):
if self.deployed_service.account is None or hasattr(self, 'accounting'): # accounting comes from AccountUsage, and is a OneToOneRelation with UserService
return
self.deployed_service.account.startUsageAccounting(self)

View File

@ -444,6 +444,7 @@ api.sPoolGroups = new BasicModelRest("gallery/servicespoolgroups")
api.system = new BasicModelRest("system")
api.reports = new BasicModelRest("reports") # Not fully used, but basic usage is common
api.calendars = new BasicModelRest("calendars")
api.accounts = new BasicModelRest("accounts")
# In fact, reports do not have any type
api.reports.types = (success_fnc, fail_fnc) ->

View File

@ -0,0 +1,439 @@
# jshint strict: true
gui.accounts = new GuiElement(api.accounts, "accounts")
gui.accounts.link = (event) ->
"use strict"
# Button definition to trigger "Test" action
testButton = testButton:
text: gettext("Test")
css: "btn-info"
# Clears the log of the detail, in this case, the log of "users"
# Memory saver :-)
detailLogTable = null
clearDetailLog = ->
if detailLogTable?
$tbl = $(detailLogTable).dataTable()
$tbl.fnClearTable()
$tbl.fnDestroy()
detailLogTable = null
$("#users-log-placeholder").empty()
return
# Clears the details
# Memory saver :-)
prevTables = []
clearDetails = ->
clearDetailLog()
$.each prevTables, (undefined_, tbl) ->
$tbl = $(tbl).dataTable()
$tbl.fnClearTable()
$tbl.fnDestroy()
return
$("#users-placeholder").empty()
$("#groups-placeholder").empty()
$("#logs-placeholder").empty()
$("#detail-placeholder").addClass "hidden"
prevTables = []
return
# Search button event generator for user/group
searchForm = (parentModalId, type, id, title, searchLabel, resultsLabel) ->
errorModal = gui.failRequestModalFnc(gettext("Search error"))
srcSelector = parentModalId + " input[name=\"name\"]"
$(parentModalId + " .button-search").on "click", ->
api.templates.get "search", (tmpl) -> # Get form template
modalId = gui.launchModal(title, api.templates.evaluate(tmpl,
search_label: searchLabel
results_label: resultsLabel
),
actionButton: "<button type=\"button\" class=\"btn btn-success button-accept\">" + gettext("Accept") + "</button>"
)
$searchInput = $(modalId + " input[name=\"search\"]")
$select = $(modalId + " select[name=\"results\"]")
$searchButton = $(modalId + " .button-do-search")
$saveButton = $(modalId + " .button-accept")
$searchInput.val $(srcSelector).val()
$saveButton.on "click", ->
value = $select.val()
if value
$(srcSelector).val value
$(modalId).modal "hide"
return
$searchButton.on "click", ->
$searchButton.addClass "disabled"
term = $searchInput.val()
api.accounts.search id, type, term, ((data) ->
$searchButton.removeClass "disabled"
$select.empty()
gui.doLog data
$.each data, (undefined_, value) ->
$select.append "<option value=\"" + value.id + "\">" + value.id + " (" + value.name + ")</option>"
return
return
), (jqXHR, textStatus, errorThrown) ->
$searchButton.removeClass "disabled"
errorModal jqXHR, textStatus, errorThrown
return
return
$(modalId + " form").submit (event) ->
event.preventDefault()
$searchButton.click()
return
$searchButton.click() if $searchInput.val() isnt ""
return
return
return
api.templates.get "accounts", (tmpl) ->
gui.clearWorkspace()
gui.appendToWorkspace api.templates.evaluate(tmpl,
auths: "auths-placeholder"
auths_info: "auths-info-placeholder"
users: "users-placeholder"
users_log: "users-log-placeholder"
groups: "groups-placeholder"
logs: "logs-placeholder"
)
gui.setLinksEvents()
# Append tabs click events
$(".bottom_tabs").on "click", (event) ->
gui.doLog event.target
setTimeout (->
$($(event.target).attr("href") + " span.fa-refresh").click()
return
), 10
return
tableId = gui.accounts.table(
icon: 'accounts'
container: "auths-placeholder"
rowSelect: "multi"
buttons: [
"new"
"edit"
"delete"
"xls"
"permissions"
]
onFoundUuid: (item) ->
# Invoked if our table has found a "desirable" item (uuid)
if gui.lookup2Uuid?
type = gui.lookup2Uuid[0]
gui.lookupUuid = gui.lookup2Uuid.substr(1)
gui.lookup2Uuid = null
setTimeout( () ->
if type == 'g'
$('a[href="#groups-placeholder"]').tab('show')
$("#groups-placeholder span.fa-refresh").click()
else
$('a[href="#users-placeholder_tab"]').tab('show')
$("#users-placeholder_tab span.fa-refresh").click()
, 500)
onRefresh: (tbl) ->
gui.doLog 'Refresh called for accounts'
clearDetails()
return
onRowDeselect: (deselected, dtable) ->
clearDetails()
return
onRowSelect: (selected) ->
clearDetails()
if selected.length > 1
return
# We can have lots of users, so memory can grow up rapidly if we do not keep thins clean
# To do so, we empty previous table contents before storing new table contents
# Anyway, TabletTools will keep "leaking" memory, but we can handle a little "leak" that will be fixed as soon as we change the section
$("#detail-placeholder").removeClass "hidden"
$('#detail-placeholder a[href="#auths-info-placeholder"]').tab('show')
gui.tools.blockUI()
# Load provider "info"
gui.methods.typedShow gui.accounts, selected[0], '#auths-info-placeholder .well', gettext('Error accessing data')
id = selected[0].id
type = gui.accounts.types[selected[0].type]
gui.doLog "Type", type
user = new GuiElement(api.accounts.detail(id, "users", { permission: selected[0].permission }), "users")
group = new GuiElement(api.accounts.detail(id, "groups", { permission: selected[0].permission }), "groups")
grpTable = group.table(
icon: 'groups'
container: "groups-placeholder"
doNotLoadData: true
rowSelect: "multi"
buttons: [
"new"
"edit"
"delete"
"xls"
]
onLoad: (k) ->
gui.tools.unblockUI()
return
onEdit: (value, event, table, refreshFnc) ->
exec = (groups_all) ->
gui.tools.blockUI()
api.templates.get "group", (tmpl) -> # Get form template
group.rest.item value.id, (item) -> # Get item to edit
# Creates modal
modalId = gui.launchModal(gettext("Edit group") + " <b>" + item.name + "</b>", api.templates.evaluate(tmpl,
id: item.id
type: item.type
meta_if_any: item.meta_if_any
groupname: item.name
groupname_label: type.groupNameLabel
comments: item.comments
state: item.state
external: type.isExternal
canSearchGroups: type.canSearchGroups
groups: item.groups
groups_all: groups_all
))
gui.tools.applyCustoms modalId
gui.tools.unblockUI()
$(modalId + " .button-accept").click ->
fields = gui.forms.read(modalId)
gui.doLog "Fields", fields
group.rest.save fields, ((data) -> # Success on put
$(modalId).modal "hide"
refreshFnc()
gui.notify gettext("Group saved"), "success"
return
), gui.failRequestModalFnc("Error saving group", true)
return
return
return
return
if value.type is "meta"
# Meta will get all groups
group.rest.overview (groups) ->
exec groups
return
else
exec()
return
onNew: (t, table, refreshFnc) ->
exec = (groups_all) ->
gui.tools.blockUI()
api.templates.get "group", (tmpl) -> # Get form template
# Creates modal
if t is "meta"
title = gettext("New meta group")
else
title = gettext("New group")
modalId = gui.launchModal(title, api.templates.evaluate(tmpl,
type: t
groupname_label: type.groupNameLabel
external: type.isExternal
canSearchGroups: type.canSearchGroups
groups: []
groups_all: groups_all
))
gui.tools.unblockUI()
gui.tools.applyCustoms modalId
searchForm modalId, "group", id, gettext("Search groups"), gettext("Group"), gettext("Groups found") # Enable search button click, if it exist ofc
$(modalId + " .button-accept").click ->
fields = gui.forms.read(modalId)
gui.doLog "Fields", fields
group.rest.create fields, ((data) -> # Success on put
$(modalId).modal "hide"
refreshFnc()
gui.notify gettext("Group saved"), "success"
return
), gui.failRequestModalFnc(gettext("Group saving error"), true)
return
return
return
if t is "meta"
# Meta will get all groups
group.rest.overview (groups) ->
exec groups
return
else
exec()
return
onDelete: gui.methods.del(group, gettext("Delete group"), gettext("Group deletion error"))
)
tmpLogTable = null
# New button will only be shown on accounts that can create new users
usrButtons = [
"edit"
"delete"
"xls"
]
usrButtons = ["new"].concat(usrButtons) if type.canCreateUsers # New is first button
usrTable = user.table(
icon: 'users'
container: "users-placeholder"
doNotLoadData: true
rowSelect: "multi"
onRowSelect: (uselected) ->
gui.doLog 'User row selected ', uselected
gui.tools.blockUI()
uId = uselected[0].id
clearDetailLog()
tmpLogTable = user.logTable(uId,
container: "users-log-placeholder"
onLoad: ->
detailLogTable = tmpLogTable
gui.tools.unblockUI()
return
)
return
onRowDeselect: ->
clearDetailLog()
return
buttons: usrButtons
deferedRender: true # Use defered rendering for users, this table can be "huge"
scrollToTable: false
onLoad: (k) ->
gui.tools.unblockUI()
return
onRefresh: ->
gui.doLog "Refreshing"
clearDetailLog()
return
onEdit: (value, event, table, refreshFnc) ->
password = "#æð~¬ŋ@æß”¢€~½¬@#~þ¬@|" # Garbage for password (to detect change)
gui.tools.blockUI()
api.templates.get "user", (tmpl) -> # Get form template
group.rest.overview (groups) -> # Get groups
user.rest.item value.id, (item) -> # Get item to edit
# Creates modal
modalId = gui.launchModal(gettext("Edit user") + " <b>" + value.name + "</b>", api.templates.evaluate(tmpl,
id: item.id
username: item.name
username_label: type.userNameLabel
realname: item.real_name
comments: item.comments
state: item.state
staff_member: item.staff_member
is_admin: item.is_admin
needs_password: type.needsPassword
password: (if type.needsPassword then password else undefined)
password_label: type.passwordLabel
groups_all: groups
groups: item.groups
external: type.isExternal
canSearchUsers: type.canSearchUsers
))
gui.tools.applyCustoms modalId
gui.tools.unblockUI()
$(modalId + " .button-accept").click ->
fields = gui.forms.read(modalId)
# If needs password, and password has changed
gui.doLog "passwords", type.needsPassword, password, fields.password
delete fields.password if fields.password is password if type.needsPassword
gui.doLog "Fields", fields
user.rest.save fields, ((data) -> # Success on put
$(modalId).modal "hide"
refreshFnc()
gui.notify gettext("User saved"), "success"
return
), gui.failRequestModalFnc(gettext("User saving error"), true)
return
return
return
return
return
onNew: (undefined_, table, refreshFnc) ->
gui.tools.blockUI()
api.templates.get "user", (tmpl) -> # Get form template
group.rest.overview (groups) -> # Get groups
# Creates modal
modalId = gui.launchModal(gettext("New user"), api.templates.evaluate(tmpl,
username_label: type.userNameLabel
needs_password: type.needsPassword
password_label: type.passwordLabel
groups_all: groups
groups: []
external: type.isExternal
canSearchUsers: type.canSearchUsers
))
gui.tools.applyCustoms modalId
gui.tools.unblockUI()
searchForm modalId, "user", id, gettext("Search users"), gettext("User"), gettext("Users found") # Enable search button click, if it exist ofc
$(modalId + " .button-accept").click ->
fields = gui.forms.read(modalId)
# If needs password, and password has changed
gui.doLog "Fields", fields
user.rest.create fields, ((data) -> # Success on put
$(modalId).modal "hide"
refreshFnc()
gui.notify gettext("User saved"), "success"
return
), gui.failRequestModalFnc(gettext("User saving error"), true)
return
return
return
return
onDelete: gui.methods.del(user, gettext("Delete user"), gettext("User deletion error"))
)
logTable = gui.accounts.logTable(id,
container: "logs-placeholder"
doNotLoadData: true
)
# So we can destroy the tables beforing adding new ones
prevTables.push grpTable
prevTables.push usrTable
prevTables.push logTable
false
onNew: gui.methods.typedNew(gui.accounts, gettext("New authenticator"), gettext("Authenticator creation error"), testButton)
onEdit: gui.methods.typedEdit(gui.accounts, gettext("Edit authenticator"), gettext("Authenticator saving error"), testButton)
onDelete: gui.methods.del(gui.accounts, gettext("Delete authenticator"), gettext("Authenticator deletion error"))
)
return
false

View File

@ -127,6 +127,8 @@
<script type="text/coffeescript" charset="utf-8" src="{% get_static_prefix %}adm/js/gui-d-servicespoolsgroup.coffee"></script>
<script type="text/coffeescript" charset="utf-8" src="{% get_static_prefix %}adm/js/gui-d-reports.coffee"></script>
<script type="text/coffeescript" charset="utf-8" src="{% get_static_prefix %}adm/js/gui-d-calendar.coffee"></script>
<script type="text/coffeescript" charset="utf-8" src="{% get_static_prefix %}adm/js/gui-d-account.coffee"></script>
<!-- base64 encoding -->
<script src="{% get_static_prefix %}adm/js/base64.js"></script>