Adding in the delete api and the delete and create UI.

This commit is contained in:
Charlton Austin 2016-10-11 15:09:38 -04:00
parent 43063272bb
commit 97d644d95d
12 changed files with 277 additions and 12 deletions

View file

@ -180,6 +180,8 @@ TEST=true python -m test.test_api_usage -f SuiteName
PYTHONPATH=. alembic upgrade head
# You can also rebuild your local sqlite db image from initdb.py using
# And once you have a migration you should do this and check in the
# changes to share your migration with others.
rm test/data/test.db
python initdb.py
```

View file

@ -776,6 +776,7 @@ class LogEntry(BaseModel):
class Messages(BaseModel):
content = TextField()
uuid = CharField(default=uuid_generator, index=True)
class RepositoryActionCount(BaseModel):
repository = ForeignKeyField(Repository)

View file

@ -0,0 +1,23 @@
"""Add uuid to messages
Revision ID: c156deb8845d
Revises: a3002f7638d5
Create Date: 2016-10-11 15:44:29.450181
"""
# revision identifiers, used by Alembic.
revision = 'c156deb8845d'
down_revision = 'a3002f7638d5'
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import mysql
def upgrade(tables):
op.add_column('messages', sa.Column('uuid', sa.String(length=36), nullable=True))
def downgrade(tables):
op.drop_column('messages', 'uuid')

View file

@ -11,3 +11,9 @@ def create(messages):
for message in messages:
inserted.append(Messages.create(content=message['content']))
return inserted
def delete_message(uuids):
"""Delete message from the database"""
if not uuids:
return
Messages.delete().where(Messages.uuid << uuids).execute()

View file

@ -820,12 +820,10 @@ class SuperUserServiceKeyApproval(ApiResource):
abort(403)
@resource('/v1/messages')
@show_if(features.SUPER_USERS)
class SuperUserMessages(ApiResource):
""" Resource for getting a list of super user messages """
schemas = {
'GetMessage': {
'id': 'GetMessage',
@ -838,8 +836,8 @@ class SuperUserMessages(ApiResource):
'itemType': {
'type': 'object',
'properties': {
'id': {
'type': 'integer',
'uuid': {
'type': 'string',
'description': 'The message id',
},
'content': {
@ -877,6 +875,7 @@ class SuperUserMessages(ApiResource):
'messages': [message_view(m) for m in model.message.get_messages()],
}
@require_fresh_login
@verify_not_prod
@nickname('createMessages')
@validate_json_request('CreateMessage')
@ -886,8 +885,29 @@ class SuperUserMessages(ApiResource):
if SuperUserPermission().can():
model.message.create([request.get_json()['message']])
return make_response('', 201)
abort(403)
@resource('/v1/message/<uuid>')
@show_if(features.SUPER_USERS)
class SuperUserMessage(ApiResource):
""" Resource for managing individual messages """
@require_fresh_login
@verify_not_prod
@nickname('deleteGlobalMessage')
@require_scope(scopes.SUPERUSER)
def delete(self, uuid):
""" Delete a message """
if SuperUserPermission().can():
model.message.delete_message([uuid])
return make_response('', 204)
abort(403)
def message_view(message):
return {'id': message.id, 'content': message.content}
return {
'uuid': message.uuid,
'content': message.content,
}

View file

@ -0,0 +1,90 @@
<!-- Messages tab -->
<div class="global-message-tab-element">
<div class="cor-loader" ng-show="!messages"></div>
<div ng-show="messages">
<div class="manager-header" header-title="Messages">
<button class="create-button btn btn-primary" ng-click="showCreateMessage()">
<i class="fa fa-plus" style="margin-right: 6px;"></i>Create Message
</button>
</div>
<table class="cor-table">
<thead>
<td>Message</td>
<td style="options-cols"></td>
</thead>
<tr ng-repeat="message in messages" class="user-row">
<td>
{{ message.content }}
</td>
<td class="options-col">
<span class="cor-options-menu">
<span class="cor-option" option-click="showDeleteMessage(message.uuid)">
<i class="fa fa-times"></i> Delete Message
</span>
</span>
</td>
</tr>
</table>
</div><!-- Messages tab -->
<!-- Modal delete message dialog -->
<div class="co-dialog modal fade" id="confirmDeleteMessageModal">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title">Delete Message?</h4>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-danger" ng-click="deleteMessage(messageToDelete)">Delete Message</button>
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->
<!-- Modal create message dialog -->
<div class="co-dialog modal fade" id="createMessageModal">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title">Create new message</h4>
</div>
<form name="createMessageForm" ng-submit="createNewMessage()">
<div class="modal-body" ng-show="createdMessage">
<table class="table">
<thead>
<th>Message</th>
</thead>
<tr class="user-row">
<td>{{ createdMessage.content }}</td>
</tr>
</table>
</div>
<div class="modal-body" ng-show="creatingMessage">
<div class="cor-loader"></div>
</div>
<div class="modal-body" ng-show="!creatingMessage && !createdMessage">
<div class="form-group">
<label>Message</label>
<input class="form-control" type="text" ng-model="newMessage.content" required>
</div>
</div>
<div class="modal-footer" ng-show="createdMessage">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</div>
<div class="modal-footer" ng-show="!creatingMessage && !createdMessage">
<button class="btn btn-primary" type="submit" ng-disabled="!createMessageForm.$valid">
Create Message
</button>
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
</div>
</form>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->
</div>

View file

@ -0,0 +1,81 @@
/**
* An element for managing global messages.
*/
angular.module('quay').directive('globalMessageTab', function () {
var directiveDefinitionObject = {
priority: 0,
templateUrl: '/static/directives/global-message-tab.html',
replace: false,
transclude: true,
restrict: 'C',
scope: {
'isEnabled': '=isEnabled'
},
controller: function ($scope, $element, ApiService) {
$scope.newMessage = {};
$scope.creatingMessage = false;
$scope.showCreateMessage = function () {
$scope.createdMessage = null;
$('#createMessageModal').modal('show');
};
$scope.createNewMessage = function () {
$scope.creatingMessage = true;
$scope.createdMessage = null;
var errorHandler = ApiService.errorDisplay('Cannot create message', function () {
$scope.creatingMessage = false;
$('#createMessageModal').modal('hide');
});
var data = {
message: $scope.newMessage
};
ApiService.createMessages(data, null).then(function (resp) {
$scope.creatingMessage = false;
$scope.createdMessage = {content: $scope.newMessage.content};
$scope.newMessage = {};
$scope.loadMessageInternal();
}, errorHandler)
};
$scope.showDeleteMessage = function (uuid) {
$scope.messageToDelete = uuid;
$('#confirmDeleteMessageModal').modal({});
};
$scope.deleteMessage = function (uuid) {
$('#confirmDeleteMessageModal').modal('hide');
ApiService.deleteGlobalMessage(null, {uuid: uuid}).then(function (resp) {
$scope.loadMessageInternal();
}, ApiService.errorDisplay('Can not delete message'));
};
$scope.loadMessageOfTheDay = function () {
if ($scope.messages) {
return;
}
$scope.loadMessageInternal();
};
$scope.loadMessageInternal = function () {
ApiService.getMessages().then(function (resp) {
$scope.messages = resp['messages'];
}, function (resp) {
$scope.messages = [];
$scope.messagesErrors = ApiService.getErrorMessage(resp);
});
};
$scope.$watch('isEnabled', function (value) {
if (value) {
$scope.loadMessageInternal();
}
});
}
};
return directiveDefinitionObject;
});

View file

@ -31,8 +31,12 @@
$scope.csrf_token = encodeURIComponent(window.__token);
$scope.currentConfig = null;
$scope.serviceKeysActive = false;
$scope.globalMessagesActive = false;
$scope.takeOwnershipInfo = null;
$scope.loadMessageOfTheDay = function () {
$scope.globalMessagesActive = true;
};
$scope.configurationSaved = function(config) {
$scope.currentConfig = config;
$scope.requiresRestart = true;
@ -69,7 +73,7 @@
var errorHandler = ApiService.errorDisplay('Cannot load system logs. Please contact support.',
function() {
callback(false);
})
});
ApiService.getSystemLogs(null, params, /* background */true).then(function(resp) {
$scope.debugLogs = resp['logs'];
@ -95,7 +99,7 @@
ApiService.getChangeLog().then(function(resp) {
$scope.changeLog = resp;
}, ApiService.errorDisplay('Cannot load change log. Please contact support.'))
}
};
$scope.loadUsageLogs = function() {
$scope.logsCounter++;

View file

@ -41,6 +41,10 @@
tab-init="loadConfig()">
<i class="fa fa-cog"></i>
</span>
<span class="cor-tab hidden-xs" tab-title="Globally visible user messages" tab-target="#message-of-the-day"
tab-init="loadMessageOfTheDay()">
<i class="fa fa-newspaper-o"></i>
</span>
</div> <!-- /cor-tabs -->
<div class="cor-tab-content">
@ -50,6 +54,12 @@
configuration-saved="configurationSaved(config)"></div>
</div>
<!-- Messages tab -->
<div id="message-of-the-day" class="tab-pane">
<div class="global-message-tab" is-enabled="globalMessagesActive"></div>
</div> <!-- Messages tab -->
<!-- Service keys tab -->
<div id="servicekeys" class="tab-pane">
<div class="service-keys-manager" is-enabled="serviceKeysActive"></div>
@ -269,7 +279,7 @@
</div><!-- /.modal -->
<!-- Modal message dialog -->
<!-- Modal create user dialog -->
<div class="co-dialog modal fade" id="createUserModal">
<div class="modal-dialog">
<div class="modal-content">

Binary file not shown.

View file

@ -51,7 +51,7 @@ from endpoints.api.superuser import (SuperUserLogs, SuperUserList, SuperUserMana
SuperUserOrganizationManagement, SuperUserOrganizationList,
SuperUserAggregateLogs, SuperUserServiceKeyManagement,
SuperUserServiceKey, SuperUserServiceKeyApproval,
SuperUserTakeOwnership, SuperUserMessages)
SuperUserTakeOwnership, SuperUserMessages, SuperUserMessage)
from endpoints.api.secscan import RepositoryImageSecurity
from endpoints.api.manifest import RepositoryManifestLabels, ManageRepositoryManifestLabel
@ -4200,6 +4200,7 @@ class TestSuperUserManagement(ApiTestCase):
def test_delete_devtable(self):
self._run_test('DELETE', 204, 'devtable', None)
class TestSuperUserMessages(ApiTestCase):
def setUp(self):
ApiTestCase.setUp(self)
@ -4219,7 +4220,7 @@ class TestSuperUserMessages(ApiTestCase):
def test_post_anonymous(self):
self._run_test('POST', 403, None, dict(message={"content": "new message"}))
self._run_test('POST', 401, None, dict(message={"content": "new message"}))
def test_post_freshuser(self):
self._run_test('POST', 403, 'freshuser', dict(message={"content": "new message"}))
@ -4231,6 +4232,24 @@ class TestSuperUserMessages(ApiTestCase):
self._run_test('POST', 201, 'devtable', dict(message={"content": "new message"}))
class TestSuperUserMessage(ApiTestCase):
def setUp(self):
ApiTestCase.setUp(self)
self._set_url(SuperUserMessage, uuid='1234')
def test_delete_anonymous(self):
self._run_test('DELETE', 401, None, None)
def test_delete_freshuser(self):
self._run_test('DELETE', 403, 'freshuser', None)
def test_delete_reader(self):
self._run_test('DELETE', 403, 'reader', None)
def test_delete_devtable(self):
self._run_test('DELETE', 204, 'devtable', None)
class TestUserInvoiceFieldList(ApiTestCase):
def setUp(self):
ApiTestCase.setUp(self)

View file

@ -66,7 +66,7 @@ from endpoints.api.permission import (RepositoryUserPermission, RepositoryTeamPe
from endpoints.api.superuser import (SuperUserLogs, SuperUserList, SuperUserManagement,
SuperUserServiceKeyManagement, SuperUserServiceKey,
SuperUserServiceKeyApproval, SuperUserTakeOwnership,
SuperUserMessages)
SuperUserMessages, SuperUserMessage)
from endpoints.api.secscan import RepositoryImageSecurity
from endpoints.api.suconfig import (SuperUserRegistryStatus, SuperUserConfig, SuperUserConfigFile,
SuperUserCreateInitialSuperUser)
@ -4290,7 +4290,16 @@ class TestSuperUserManagement(ApiTestCase):
self.assertEquals(len(json['messages']), 2)
self.assertEquals(json['messages'][1]["content"], "new message")
self.assertNotEqual(json['messages'][0]["content"], json['messages'][1]["content"])
self.assertEquals(json['messages'][1]["id"], 2)
self.assertTrue(json['messages'][1]["uuid"])
def test_delete_message(self):
self.login(ADMIN_ACCESS_USER)
json = self.getJsonResponse(SuperUserMessages)
self.deleteResponse(SuperUserMessage, {"uuid": json['messages'][0]['uuid']}, 204)
json = self.getJsonResponse(SuperUserMessages)
self.assertEquals(len(json['messages']), 0)
if __name__ == '__main__':
unittest.main()