Add UI/UX polish in prep for merge

* Bring UI/UX inline with recent changes
* Use select components as a stopgap for credential_types and orgs
* Add tabs to permissions view
* Add Organization model
This commit is contained in:
gconsidine 2017-06-16 15:23:18 -04:00
parent 861cfd3e13
commit fa330db9c5
26 changed files with 145 additions and 43 deletions

View File

@ -1,11 +1,14 @@
const DEFAULT_ORGANIZATION_PLACEHOLDER = 'SELECT AN ORGANIZATION';
function AddCredentialsController (models, $state) {
let vm = this || {};
let me = models.me;
let credential = models.credential;
let credentialType = models.credentialType;
let organization = models.organization;
vm.panelTitle = 'New Credential';
vm.panelTitle = 'NEW CREDENTIAL';
vm.tab = {
details: {
@ -20,6 +23,13 @@ function AddCredentialsController (models, $state) {
omit: ['user', 'team', 'inputs']
});
vm.form.organization._placeholder = DEFAULT_ORGANIZATION_PLACEHOLDER;
vm.form.organization._data = organization.get('results');
vm.form.organization._format = 'objects';
vm.form.organization._exp = 'org as org.name for org in state._data';
vm.form.organization._display = 'name';
vm.form.organization._key = 'id';
vm.form.credential_type._data = credentialType.get('results');
vm.form.credential_type._placeholder = 'SELECT A TYPE';
vm.form.credential_type._format = 'grouped-object';

View File

@ -10,7 +10,7 @@
<at-form state="vm.form">
<at-input-text col="4" tab="1" state="vm.form.name"></at-input-text>
<at-input-text col="4" tab="2" state="vm.form.description"></at-input-text>
<at-input-lookup col="4" tab="3" state="vm.form.organization"></at-input-lookup>
<at-input-select col="4" tab="3" state="vm.form.organization"></at-input-select>
<at-divider></at-divider>
@ -32,6 +32,11 @@
$state.current.name === 'credentials.edit.permissions.add'">
<at-panel-heading>Credentials Permissions</at-panel-heading>
<at-tab-group>
<at-tab state="vm.tab.details">Details</at-tab>
<at-tab state="vm.tab.permissions">Permissions</at-tab>
</at-tab-group>
<at-panel-body>
<div class="at-CredentialsPermissions" ui-view="related"></div>
</at-panel-body>

View File

@ -1,13 +1,18 @@
const DEFAULT_ORGANIZATION_PLACEHOLDER = 'SELECT AN ORGANIZATION';
function EditCredentialsController (models, $state, $scope) {
let vm = this || {};
let me = models.me;
let credential = models.credential;
let credentialType = models.credentialType;
let organization = models.organization;
vm.tab = {
details: {
_active: true
_active: true,
_go: 'credentials.edit',
_params: { credential_id: credential.get('id') }
},
permissions:{
_go: 'credentials.edit.permissions',
@ -15,6 +20,16 @@ function EditCredentialsController (models, $state, $scope) {
}
};
$scope.$watch('$state.current.name', (value) => {
if (value === 'credentials.edit') {
vm.tab.details._active = true;
vm.tab.details._permissions = false;
} else {
vm.tab.permissions._active = true;
vm.tab.details._active = false;
}
});
// Only exists for permissions compatibility
$scope.credential_obj = credential.get();
@ -24,6 +39,14 @@ function EditCredentialsController (models, $state, $scope) {
omit: ['user', 'team', 'inputs']
});
vm.form.organization._placeholder = DEFAULT_ORGANIZATION_PLACEHOLDER;
vm.form.organization._data = organization.get('results');
vm.form.organization._format = 'objects';
vm.form.organization._exp = 'org as org.name for org in state._data';
vm.form.organization._display = 'name';
vm.form.organization._key = 'id';
vm.form.organization._value = organization.getById(credential.get('organization'));
vm.form.credential_type._data = credentialType.get('results');
vm.form.credential_type._format = 'grouped-object';
vm.form.credential_type._display = 'name';

View File

@ -6,12 +6,13 @@ import AddController from './add-credentials.controller.js';
import EditController from './edit-credentials.controller.js';
import { N_ } from '../../src/i18n';
function CredentialsResolve ($q, $stateParams, Me, Credential, CredentialType) {
function CredentialsResolve ($q, $stateParams, Me, Credential, CredentialType, Organization) {
let id = $stateParams.credential_id;
let promises = {
me: new Me('get'),
credentialType: new CredentialType('get')
credentialType: new CredentialType('get'),
organization: new Organization('get')
};
if (id) {
@ -28,13 +29,13 @@ CredentialsResolve.$inject = [
'$stateParams',
'MeModel',
'CredentialModel',
'CredentialTypeModel'
'CredentialTypeModel',
'OrganizationModel'
];
function CredentialsConfig ($stateProvider, $stateExtenderProvider, stateDefinitionsProvider, pathServiceProvider) {
function CredentialsConfig ($stateProvider, $stateExtenderProvider, pathServiceProvider) {
let pathService = pathServiceProvider.$get();
let stateExtender = $stateExtenderProvider.$get();
let stateDefinitions = stateDefinitionsProvider.$get();
stateExtender.addState({
name: 'credentials',
@ -272,7 +273,6 @@ function CredentialsConfig ($stateProvider, $stateExtenderProvider, stateDefinit
CredentialsConfig.$inject = [
'$stateProvider',
'$stateExtenderProvider',
'stateDefinitionsProvider',
'PathServiceProvider'
];

View File

@ -7,7 +7,7 @@
color: @at-gray-dark-5x;
&, &:active {
border-color: @at-gray-dark-3x;
border-color: @at-gray-dark-2x;
}
&:focus {
@ -19,25 +19,33 @@
margin: 0;
padding: 0;
& > input[type=checkbox] {
height: @at-input-height;
margin: 0;
padding: 0;
& > label {
& > input[type=checkbox] {
height: @at-input-height;
margin: 0;
padding: 0;
}
& > p {
margin: 0;
padding: 0 0 0 @at-space-6x;
line-height: @at-line-height-tall;
}
}
}
.at-InputContainer {
margin-top: @at-space-6x;;
margin-top: @at-space-6x;
}
.at-Input-button {
min-width: @at-input-button-width;
display: block;
height: 100%;
height: @at-input-height;
&, &:active, &:hover, &:focus {
color: @at-gray-dark-3x;
border-color: @at-gray-dark-3x;
border-color: @at-gray-dark-2x;
background-color: @at-white;
cursor: pointer;
}
@ -76,12 +84,16 @@
position: absolute;
width: @at-inset-width;
height: 100%;
background: @at-gray;
background: @at-gray-dark;
left: -@at-inset-width;
}
.at-InputGroup-button {
height: 100%;
& > button {
height: 100%;
}
}
.at-InputGroup-title {

View File

@ -84,12 +84,14 @@ function BaseInputController () {
scope.state._enableToggle = true;
scope.state._value = scope.state._preEditValue;
scope.state._activeModel = '_displayValue';
scope.state._placeholder = 'ENCRYPTED';
} else {
scope.state._buttonText = 'REVERT';
scope.state._disabled = false;
scope.state._enableToggle = false;
scope.state._activeModel = '_value';
scope.state._value = '';
scope.state._placeholder = '';
}
};

View File

@ -14,6 +14,8 @@ function AtInputCheckboxController (baseInputController) {
vm.init = (scope, element, form) => {
baseInputController.call(vm, 'input', scope, element, form);
scope.label = scope.state.label;
scope.state.label = 'OPTIONS';
vm.check();
};

View File

@ -2,12 +2,15 @@
<div class="form-group at-u-flat">
<at-input-label></at-input-label>
<div class="checkbox at-InputCheckbox">
<input type="checkbox"
ng-class="{ 'at-Input--rejected': state.rejected }"
ng-model="state._value"
ng-attr-tabindex="{{ tab || undefined }}"
ng-change="vm.check()"
ng-disabled="state._disabled || form.disabled" />
<label>
<input type="checkbox"
ng-class="{ 'at-Input--rejected': state.rejected }"
ng-model="state._value"
ng-attr-tabindex="{{ tab || undefined }}"
ng-change="vm.check()"
ng-disabled="state._disabled || form.disabled" />
<p>{{ label }}</p>
</label>
</div>
<at-input-message></at-input-message>
</div>

View File

@ -8,7 +8,7 @@
<input type="checkbox"
ng-model="state._promptOnLaunch"
ng-change="vm.togglePromptOnLaunch()" />
<p>Prompt on launch?</p>
<p>Prompt on launch</p>
</label>
</div>
</label>

View File

@ -26,6 +26,7 @@ function AtInputSecretController (baseInputController) {
vm.toggle = vm.toggleShowHide;
} else {
scope.state._buttonText = 'REPLACE';
scope.state._placeholder = 'ENCRYPTED';
vm.toggle = vm.toggleRevertReplace;
}

View File

@ -3,7 +3,7 @@
<at-input-label></at-input-label>
<div class="input-group">
<span class="input-group-btn">
<span class="input-group-btn at-InputGroup-button">
<button class="btn at-ButtonHollow--white at-Input-button"
ng-disabled="!state._enableToggle && (state._disabled || form.disabled)"
ng-click="vm.toggle()">
@ -16,7 +16,7 @@
ng-class="{ 'at-Input--rejected': state._rejected }"
ng-attr-maxlength="{{ state.max_length || undefined }}"
ng-attr-tabindex="{{ tab || undefined }}"
ng-attr-placeholder="{{::state._placeholder || undefined }}"
ng-attr-placeholder="{{state._placeholder || undefined }}"
ng-change="vm.check()"
ng-disabled="state._disabled || form.disabled" />
</div>

View File

@ -1,3 +1,5 @@
const DEFAULT_EMPTY_PLACEHOLDER = 'NO OPTIONS AVAILABLE';
function atInputSelectLink (scope, element, attrs, controllers) {
let formController = controllers[0];
let inputController = controllers[1];
@ -25,6 +27,11 @@ function AtInputSelectController (baseInputController, eventService) {
input = element.find('input')[0];
select = element.find('select')[0];
if (!scope.state._data || scope.state._data.length === 0) {
scope.state._disabled = true;
scope.state._placeholder = DEFAULT_EMPTY_PLACEHOLDER;
}
vm.setListeners();
vm.check();
@ -55,6 +62,8 @@ function AtInputSelectController (baseInputController, eventService) {
vm.updateDisplayModel = () => {
if (scope.state._format === 'array') {
scope.displayModel = scope.state._data[scope.state._value];
} else if (scope.state._format === 'objects') {
scope.displayModel = scope.state._value[scope.state._display];
} else if (scope.state._format === 'grouped-object') {
scope.displayModel = scope.state._value[scope.state._display];
} else {

View File

@ -34,6 +34,7 @@ function AtInputTextareaSecretController (baseInputController, eventService) {
if (scope.state._value) {
scope.state._buttonText = 'REPLACE';
scope.state._placeholder = 'ENCRYPTED';
} else {
if (scope.state.format === 'ssh_private_key') {
vm.listeners = vm.setFileListeners(textarea, input);
@ -48,10 +49,12 @@ function AtInputTextareaSecretController (baseInputController, eventService) {
vm.toggleRevertReplace();
if (scope.state._isBeingReplaced) {
scope.state._placeholder = '';
scope.state._displayHint = true;
vm.listeners = vm.setFileListeners(textarea, input);
} else {
scope.state._displayHint = false;
scope.state._placeholder = 'ENCRYPTED';
eventService.remove(vm.listeners);
}
};

View File

@ -18,9 +18,10 @@
<textarea class="form-control at-Input at-InputTextarea"
ng-model="state[state._activeModel]"
ng-class="{ 'at-Input--rejected': state._rejected }"
ng-attr-rows="{{::state._rows || 6 }}"
ng-attr-maxlength="{{ state.max_length || undefined }}"
ng-attr-tabindex="{{ tab || undefined }}"
ng-attr-placeholder="{{::state._placeholder || undefined }}"
ng-attr-placeholder="{{state._placeholder || undefined }}"
ng-change="vm.check()"
ng-disabled="state._disabled || form.disabled" />
</textarea>

View File

@ -5,6 +5,7 @@
<textarea class="form-control at-Input at-InputTextarea"
ng-model="state._value"
ng-class="{ 'at-Input--rejected': state._rejected }"
ng-attr-rows="{{::state._rows || 6 }}"
ng-attr-maxlength="{{ state.max_length || undefined }}"
ng-attr-tabindex="{{ tab || undefined }}"
ng-attr-placeholder="{{::state._placeholder || undefined }}"

View File

@ -21,4 +21,5 @@
.at-Panel-headingTitle {
.at-mixin-Heading(@at-font-size-3x);
text-transform: none;
}

View File

@ -6,7 +6,7 @@
</div>
<div class="col-xs-2">
<div class="at-Panel-dismiss">
<i class="fa fa-times fa-lg" ng-click="dismiss()"></i>
<i class="fa fa-times-circle fa-lg" ng-click="dismiss()"></i>
</div>
</div>
</div>

View File

@ -39,7 +39,7 @@
}
.at-Popover-title {
.at-mixin-Heading(@at-font-size-2x);
.at-mixin-Heading(@at-font-size);
color: @at-white;
margin-bottom: @at-space-4x;
}
@ -47,5 +47,5 @@
.at-Popover-text {
margin: 0;
padding: 0;
line-height: 1;
font-size: @at-font-size;
}

View File

@ -4,13 +4,14 @@
.at-Tab {
margin: 0 @at-space-5x 0 0;
font-size: @at-font-size;
}
.at-Tab--active {
&, &:hover, &:active, &:focus {
color: @at-white;
background-color: @at-gray-dark-2x;
border-color: @at-gray-dark-2x;
background-color: @at-gray-dark-3x;
border-color: @at-gray-dark-3x;
cursor: default;
}
}
@ -18,8 +19,8 @@
.at-Tab--disabled {
&, &:hover, &:active, &:focus {
background-color: @at-white;
color: @at-gray-dark-3x;
border-color: @at-gray-dark-3x;
color: @at-gray-dark-2x;
border-color: @at-gray-dark-2x;
opacity: 0.65;
cursor: not-allowed;
}

View File

@ -128,10 +128,17 @@ function normalizePath (resource) {
return `${version}${resource}/`;
}
function getById (id) {
let item = this.get('results').filter(result => result.id === id);
return item ? item[0] : undefined;
}
function BaseModel (path) {
this.model = {};
this.get = get;
this.normalizePath = normalizePath;
this.getById = getById;
this.request = request;
this.http = {
get: httpGet.bind(this),

View File

@ -26,18 +26,11 @@ function mergeInputProperties (type) {
});
}
function getById (id) {
let type = this.get('results').filter(type => type.id === id);
return type ? type[0] : undefined;
}
function CredentialTypeModel (method, id) {
BaseModel.call(this, 'credential_types');
this.categorizeByKind = categorizeByKind.bind(this);
this.mergeInputProperties = mergeInputProperties.bind(this);
this.getById = getById.bind(this);
return this.request(method, id)
.then(() => this);

View File

@ -0,0 +1,18 @@
let BaseModel;
function OrganizationModel (method) {
BaseModel.call(this, 'organizations');
return this.request(method)
.then(() => this);
}
function OrganizationModelLoader (_BaseModel_) {
BaseModel = _BaseModel_;
return OrganizationModel;
}
OrganizationModelLoader.$inject = ['BaseModel'];
export default OrganizationModelLoader;

View File

@ -2,11 +2,13 @@ import Base from './Base';
import Credential from './Credential';
import CredentialType from './CredentialType';
import Me from './Me';
import Organization from './Organization';
angular
.module('at.lib.models', [])
.service('BaseModel', Base)
.service('CredentialModel', Credential)
.service('CredentialTypeModel', CredentialType)
.service('MeModel', Me);
.service('MeModel', Me)
.service('OrganizationModel', Organization);

View File

@ -10,6 +10,10 @@
.at-Button--green {
.at-mixin-Button();
.at-mixin-ButtonColor('at-green', 'at-white');
&[disabled] {
background: @at-gray-dark;
}
}
.at-Button--blue {
@ -25,6 +29,7 @@
.at-ButtonHollow--white {
.at-mixin-Button();
.at-mixin-ButtonHollow('at-gray-dark-3x', 'at-gray-dark-2x');
border-color: @at-gray-dark;
}
.at-ButtonIcon {

View File

@ -23,6 +23,7 @@
.at-mixin-Button () {
height: @at-input-height;
padding: @at-space-2x @at-space-4x;
font-size: @at-font-size;
}
.at-mixin-ButtonColor (@background, @color, @hover: '@{background}--hover') {

View File

@ -50,6 +50,7 @@
@at-font-weight-3x: 900;
@at-line-height-short: 0.9;
@at-line-height-tall: 2;
@at-line-height: 24px;
// 3. Layout --------------------------------------------------------------------------------------
@ -68,3 +69,4 @@
@at-border-radius: 5px;
@at-popover-width: 320px;
@at-inset-width: 5px;