blob: 49af3a90cc63d48dda2d6726468238f452b82c87 [file] [log] [blame]
// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
var mercury = require('mercury');
var insertCss = require('insert-css');
var methodNameToVarHashKey = require('./methodNameToVarHashKey');
var displayServerDetails = require('./display-server-details');
var methodForm = require('../method-form');
var FieldItem = require('../field-item');
var ErrorBox = require('../../../error/error-box');
var css = require('./index.css');
var h = mercury.h;
module.exports = create;
module.exports.render = render;
module.exports.displayServerDetails = displayServerDetails;
/*
* ServerDetails component provides user interfaces for displaying details for
* a server such is its objectAddresses, signature, remote blessings, etc..
*/
function create() {
var state = mercury.varhash({
/*
* namespace item to display details for.
* @see services/namespace/item
* @type {namespaceitem}
*/
item: mercury.value(null),
/*
* Name of the item.
* @type {string}
*/
itemName: mercury.value(''),
/*
* Any fatal error while getting the details.
* Note: will be displayed to user.
* @type Error
*/
error: mercury.value(null),
/*
* remoteBlessings for the item. []string
* @type {[]string}
*/
remoteBlessings: mercury.value(null),
/*
* signature for the item. []interface
* @see services/namespace/interface-util
* @type {vanadium.vdl.signature}
*/
signature: mercury.value(null),
/*
* An associative array from item names to method outputs.
* The outputs are in RPC call-order and store render information.
* @type {map[string]Array<Object>}
*/
methodOutputs: mercury.varhash(),
/*
* Each method form corresponds to an interface in the signature. It is a
* map from method names to the relevant state used in the method-form
* component.
* @type {[]map[string]mercury.struct}
*/
methodForms: mercury.array([]),
/*
* Each method form could be open or closed. All start out open, except
* for __Reserved, which is explicitly set to closed.
* @type {[]boolean}
*/
methodFormsOpen: mercury.array([]),
/*
* Whether a loading indicator should be displayed instead of content
* @type {mercury.value<boolean>}
*/
showLoadingIndicator: mercury.value(false),
/*
* Whether item is bookmarked
* @type {mercury.value<boolean>}
*/
isBookmarked: mercury.value(false),
/*
* List of objectAddresses for the name.
* @type {mercury.array<string>}
*/
objectAddresses: mercury.array([])
});
var events = mercury.input([
'methodForms',
'toggleMethodForm',
'toast'
]);
wireUpEvents(state, events);
// events.methodForms is []events; the index order matches state.methodForms
// and state.methodFormsOpen.
events.methodForms = [];
return {
state: state,
events: events
};
}
/*
* Render the server details page.
*/
function render(state, events, browseState, navEvents) {
insertCss(css);
var content = [];
// Details can only be shown if there is an item.
if (state.item) {
var detailsContent = renderDetailsContent(state, events);
content.push(detailsContent);
}
// The method forms can only be shown under these conditions.
if (state.item && state.item.hasServer && state.signature) {
var methodsContent = renderMethodsContent(state, events);
content.push(methodsContent);
}
// Show any errors from getting the details
if (state.error) {
var errorTitle = 'Unable to connect to ' + state.itemName;
content.push(ErrorBox.render(errorTitle, state.error.toString()));
}
// Show the loading indicator if it's available.
if (state.showLoadingIndicator) {
content.push(h('paper-spinner', {
attributes: {
'active': true,
'aria-label': 'Loading'
}
}));
}
return content;
}
/*
* Renders details about the current service object.
* Assumes that there is a state.item
* Note: Currently renders in the same tab as renderMethodsContent.
*/
function renderDetailsContent(state, events) {
var displayItems = [
renderNameField(state)
];
if (state.item.hasServer) {
displayItems.push(renderObjectAddressesFieldItem(state));
if (state.remoteBlessings) {
displayItems.push(renderRemoteBlessingsFieldItem(state));
}
}
return [
h('div', displayItems)
];
}
/*
* Renders the ObjectAddresses Field Item, a simple listing of the server's
* objectAddresses.
*/
function renderObjectAddressesFieldItem(state) {
var objectAddressDivs;
if (!state.objectAddresses || state.objectAddresses.length === 0) {
objectAddressDivs = [
h('div', h('span', 'No Object Addresses Found'))
];
} else {
// Show 1 div per server objectAddress.
objectAddressDivs = state.objectAddresses.map(function(objectAddress) {
return h('div', h('span', objectAddress));
});
}
return FieldItem.render('Service Object Addresses', h('div', {
attributes: {
'vertical': true,
'layout': true
}
}, objectAddressDivs));
}
/*
* Renders the Full Name field
*/
function renderNameField(state) {
return FieldItem.render('Full Name', (state.itemName || '<Home>'));
}
/*
* Renders the Remote Blessings Field Item, a list of the server's blessings.
* Does not appear until the data has been fetched.
*/
function renderRemoteBlessingsFieldItem(state) {
var remoteBlessings = state.remoteBlessings;
var blessingDivs;
if (remoteBlessings.length === 0) {
blessingDivs = [
h('div', h('span', 'No blessings found'))
];
} else {
// Show 1 div per blessing.
blessingDivs = remoteBlessings.map(function(blessing) {
return h('div', h('span', blessing));
});
}
return FieldItem.render('Remote Blessings', h('div', {
attributes: {
'vertical': true,
'layout': true
}
}, blessingDivs));
}
/*
* Renders the method signature forms and the RPC output area.
* Does not appear until the data has been fetched.
*/
function renderMethodsContent(state, events) {
var sig = state.signature;
if (!sig || sig.size === 0) {
return FieldItem.render('Methods',
h('div', h('span', 'No method signature')));
}
// Render each interface and an output region.
var content = state.signature.map(function(interface, interfaceIndex) {
var label = interface.pkgPath + '.' + interface.name;
var content;
var open = state.methodFormsOpen[interfaceIndex];
var options = {
labelTooltip: interface.doc,
collapsed: !open,
callback: events.toggleMethodForm.bind(null, {
index: interfaceIndex,
value: !open
})
};
if (!open) {
content = h('span');
} else {
content = renderMethodInterface(state, events, interfaceIndex);
}
return FieldItem.render(label, content, options);
});
content.push(FieldItem.render('Output', renderMethodOutput(state)));
return h('div', content);
}
/*
* Renders each method signature belonging to one of the interfaces of the
* service. Each form allows RPCs to be made to the associated service.
*/
function renderMethodInterface(state, events, interfaceIndex) {
var methods = [];
// Render all the methods in alphabetical order.
// ES6 Map iterates in the order values were added, so we must sort them.
var methodNames = [];
state.signature[interfaceIndex].methods.forEach(
function(methodData) {
methodNames.push(methodData.name);
}
);
methodNames.sort().forEach(function(methodName) {
var methodKey = methodNameToVarHashKey(methodName);
methods.push(methodForm.render(
state.methodForms[interfaceIndex][methodKey],
events.methodForms[interfaceIndex][methodKey]
));
});
return h('div', methods); // Note: allows 0 method signatures
}
/*
* Renders the method outputs received by the current service object.
* Prints each output received in reverse order; most recent is on top.
*/
function renderMethodOutput(state) {
var outputs = state.methodOutputs[state.item.objectName];
if (outputs === undefined) {
return h('div.method-output', h('span', 'No method output'));
}
var outputRows = [h('tr', [
h('th', '#'),
h('th', 'Method'),
h('th', 'Output')
])];
for (var i = outputs.length - 1; i >= 0; i--) {
var output = outputs[i];
if (output.shouldShow) {
outputRows.push(
h('tr', [
h('td', {
'scope': 'row'
}, '' + (i + 1)),
h('td', h('pre', output.method)),
h('td', h('pre', output.result))
])
);
}
}
var outputTable = h('table', {
attributes: {
'summary': 'Table showing the outputs of methods run on' +
'the service. The results are shown in reverse order.'
}
}, outputRows);
return h('div.method-output', outputTable);
}
// Wire up events that we know how to handle
function wireUpEvents(state, events) {
events.toggleMethodForm(function(data) {
state.methodFormsOpen.put(data.index, data.value);
});
}