Thomas Vachuska

Adding server-side user preferences.

More work still needs to get done to allow client to process
server-pushed preferences updates.

Change-Id: I6e80e3f3677285cb19cfa3b6240c1b13aac56622
......@@ -23,6 +23,13 @@ import com.fasterxml.jackson.databind.node.ObjectNode;
public interface UiConnection {
/**
* Returns the name of the logged-in user for which this connection exists.
*
* @return logged in user name
*/
String userName();
/**
* Sends the specified JSON message to the user interface client.
*
* @param message message to send
......
/*
* Copyright 2016 Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onosproject.ui;
import com.fasterxml.jackson.databind.node.ObjectNode;
import java.util.Map;
import java.util.Set;
/**
* Service for tracking user interface preferences.
*/
public interface UiPreferencesService {
/**
* Returns the list of user names that have user preferences available.
*
* @return list of user names
*/
Set<String> getUserNames();
/**
* Returns an immutable copy of the preferences for the specified user.
*
* @param userName user name
* @return map of user preferences
*/
Map<String, ObjectNode> getPreferences(String userName);
/**
* Sets the named preference for the specified user.
*
* @param userName user name
* @param preference name of the user preference
* @param value preference value
*/
void setPreference(String userName, String preference, ObjectNode value);
}
......@@ -54,6 +54,11 @@
</dependency>
<dependency>
<groupId>org.onosproject</groupId>
<artifactId>onos-core-serializers</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.onosproject</groupId>
<artifactId>onos-incubator-api</artifactId>
</dependency>
<dependency>
......
......@@ -15,17 +15,22 @@
*/
package org.onosproject.ui.impl;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableList.Builder;
import org.onlab.osgi.ServiceNotFoundException;
import org.onosproject.rest.AbstractInjectionResource;
import org.onosproject.ui.UiExtensionService;
import org.onosproject.ui.UiPreferencesService;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.SecurityContext;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.SequenceInputStream;
......@@ -42,12 +47,21 @@ public class MainIndexResource extends AbstractInjectionResource {
private static final String INDEX = "index.html";
private static final String NOT_READY = "not-ready.html";
private static final String INJECT_USER_START = "<!-- {INJECTED-USER-START} -->";
private static final String INJECT_USER_END = "<!-- {INJECTED-USER-END} -->";
private static final String INJECT_CSS_START = "<!-- {INJECTED-STYLESHEETS-START} -->";
private static final String INJECT_CSS_END = "<!-- {INJECTED-STYLESHEETS-END} -->";
private static final String INJECT_JS_START = "<!-- {INJECTED-JAVASCRIPT-START} -->";
private static final String INJECT_JS_END = "<!-- {INJECTED-JAVASCRIPT-END} -->";
private static final byte[] SCRIPT_START = "\n<script>\n".getBytes();
private static final byte[] SCRIPT_END = "\n</script>\n\n".getBytes();
@Context
private SecurityContext ctx;
@GET
@Produces(MediaType.TEXT_HTML)
public Response getMainIndex() throws IOException {
......@@ -62,14 +76,25 @@ public class MainIndexResource extends AbstractInjectionResource {
InputStream indexTemplate = classLoader.getResourceAsStream(INDEX);
String index = new String(toByteArray(indexTemplate));
int p1s = split(index, 0, INJECT_JS_START) - INJECT_JS_START.length();
int p0s = split(index, 0, INJECT_USER_START) - INJECT_USER_START.length();
int p0e = split(index, p0s, INJECT_USER_END);
int p1s = split(index, p0e, INJECT_JS_START) - INJECT_JS_START.length();
int p1e = split(index, p1s, INJECT_JS_END);
int p2s = split(index, p1e, INJECT_CSS_START) - INJECT_CSS_START.length();
int p2e = split(index, p2s, INJECT_CSS_END);
int p3s = split(index, p2e, null);
// FIXME: use global opaque auth token to allow secure failover
String userName = ctx.getUserPrincipal().getName();
String auth = "var onosAuth='" + userName + "';\n";
StreamEnumeration streams =
new StreamEnumeration(of(stream(index, 0, p1s),
new StreamEnumeration(of(stream(index, 0, p0s),
new ByteArrayInputStream(SCRIPT_START),
stream(auth, 0, auth.length()),
userPreferences(userName),
new ByteArrayInputStream(SCRIPT_END),
stream(index, p0e, p1s),
includeJs(service),
stream(index, p1e, p2s),
includeCss(service),
......@@ -78,6 +103,15 @@ public class MainIndexResource extends AbstractInjectionResource {
return Response.ok(new SequenceInputStream(streams)).build();
}
// Produces an input stream including user preferences.
private InputStream userPreferences(String userName) {
UiPreferencesService service = get(UiPreferencesService.class);
ObjectNode prefs = mapper().createObjectNode();
service.getPreferences(userName).forEach(prefs::set);
String string = "var userPrefs = " + prefs.toString() + ";";
return new ByteArrayInputStream(string.getBytes());
}
// Produces an input stream including JS injections from all extensions.
private InputStream includeJs(UiExtensionService service) {
Builder<InputStream> builder = ImmutableList.builder();
......
......@@ -16,7 +16,19 @@
package org.onosproject.ui.impl;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.BooleanNode;
import com.fasterxml.jackson.databind.node.DoubleNode;
import com.fasterxml.jackson.databind.node.IntNode;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.LongNode;
import com.fasterxml.jackson.databind.node.NullNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.ShortNode;
import com.fasterxml.jackson.databind.node.TextNode;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
......@@ -26,40 +38,49 @@ import org.apache.felix.scr.annotations.Deactivate;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.ReferenceCardinality;
import org.apache.felix.scr.annotations.Service;
import org.onlab.util.KryoNamespace;
import org.onosproject.mastership.MastershipService;
import org.onosproject.store.serializers.KryoNamespaces;
import org.onosproject.store.service.EventuallyConsistentMap;
import org.onosproject.store.service.EventuallyConsistentMapEvent;
import org.onosproject.store.service.EventuallyConsistentMapListener;
import org.onosproject.store.service.StorageService;
import org.onosproject.store.service.WallClockTimestamp;
import org.onosproject.ui.UiExtension;
import org.onosproject.ui.UiExtensionService;
import org.onosproject.ui.UiMessageHandlerFactory;
import org.onosproject.ui.UiPreferencesService;
import org.onosproject.ui.UiTopoOverlayFactory;
import org.onosproject.ui.UiView;
import org.onosproject.ui.UiViewHidden;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static com.google.common.collect.ImmutableList.of;
import static java.util.stream.Collectors.toSet;
import static org.onosproject.ui.UiView.Category.NETWORK;
import static org.onosproject.ui.UiView.Category.PLATFORM;
import static org.onosproject.security.AppGuard.checkPermission;
import static org.onosproject.security.AppPermission.Type.UI_READ;
import static org.onosproject.security.AppPermission.Type.UI_WRITE;
import static org.onosproject.ui.UiView.Category.NETWORK;
import static org.onosproject.ui.UiView.Category.PLATFORM;
/**
* Manages the user interface extensions.
*/
@Component(immediate = true)
@Service
public class UiExtensionManager implements UiExtensionService, SpriteService {
public class UiExtensionManager implements UiExtensionService, UiPreferencesService, SpriteService {
private static final ClassLoader CL = UiExtensionManager.class.getClassLoader();
private static final String CORE = "core";
private static final String GUI_ADDED = "guiAdded";
private static final String GUI_REMOVED = "guiRemoved";
private static final String UPDATE_PREFS = "updatePrefs";
private final Logger log = LoggerFactory.getLogger(getClass());
......@@ -72,10 +93,19 @@ public class UiExtensionManager implements UiExtensionService, SpriteService {
// Core views & core extension
private final UiExtension core = createCoreExtension();
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected MastershipService mastershipService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected StorageService storageService;
// User preferences
private EventuallyConsistentMap<String, ObjectNode> prefs;
private final EventuallyConsistentMapListener<String, ObjectNode> prefsListener =
new InternalPrefsListener();
private final ObjectMapper mapper = new ObjectMapper();
// Creates core UI extension
private UiExtension createCoreExtension() {
List<UiView> coreViews = of(
......@@ -98,6 +128,7 @@ public class UiExtensionManager implements UiExtensionService, SpriteService {
UiMessageHandlerFactory messageHandlerFactory =
() -> ImmutableList.of(
new UserPreferencesMessageHandler(),
new TopologyViewMessageHandler(),
new DeviceViewMessageHandler(),
new LinkViewMessageHandler(),
......@@ -128,12 +159,28 @@ public class UiExtensionManager implements UiExtensionService, SpriteService {
@Activate
public void activate() {
KryoNamespace.Builder kryoBuilder = new KryoNamespace.Builder()
.register(KryoNamespaces.API)
.register(ObjectNode.class, ArrayNode.class,
JsonNodeFactory.class, LinkedHashMap.class,
TextNode.class, BooleanNode.class,
LongNode.class, DoubleNode.class, ShortNode.class,
IntNode.class, NullNode.class);
prefs = storageService.<String, ObjectNode>eventuallyConsistentMapBuilder()
.withName("onos-user-preferences")
.withSerializer(kryoBuilder)
.withTimestampProvider((k, v) -> new WallClockTimestamp())
.withPersistence()
.build();
prefs.addListener(prefsListener);
register(core);
log.info("Started");
}
@Deactivate
public void deactivate() {
prefs.removeListener(prefsListener);
UiWebSocketServlet.closeAll();
unregister(core);
log.info("Stopped");
......@@ -171,6 +218,27 @@ public class UiExtensionManager implements UiExtensionService, SpriteService {
return views.get(viewId);
}
@Override
public Set<String> getUserNames() {
ImmutableSet.Builder<String> builder = ImmutableSet.builder();
prefs.keySet().forEach(k -> builder.add(userName(k)));
return builder.build();
}
@Override
public Map<String, ObjectNode> getPreferences(String userName) {
ImmutableMap.Builder<String, ObjectNode> builder = ImmutableMap.builder();
prefs.entrySet().stream()
.filter(e -> e.getKey().startsWith(userName + "/"))
.forEach(e -> builder.put(keyName(e.getKey()), e.getValue()));
return builder.build();
}
@Override
public void setPreference(String userName, String preference, ObjectNode value) {
prefs.put(key(userName, preference), value);
}
// =====================================================================
// Provisional tracking of sprite definitions
......@@ -192,4 +260,33 @@ public class UiExtensionManager implements UiExtensionService, SpriteService {
return sprites.get(name);
}
private String key(String userName, String keyName) {
return userName + "/" + keyName;
}
private String userName(String key) {
return key.split("/")[0];
}
private String keyName(String key) {
return key.split("/")[1];
}
// Auxiliary listener to preference map events.
private class InternalPrefsListener
implements EventuallyConsistentMapListener<String, ObjectNode> {
@Override
public void event(EventuallyConsistentMapEvent<String, ObjectNode> event) {
String userName = userName(event.key());
if (event.type() == EventuallyConsistentMapEvent.Type.PUT) {
UiWebSocketServlet.sendToUser(userName, UPDATE_PREFS, jsonPrefs());
}
}
private ObjectNode jsonPrefs() {
ObjectNode json = mapper.createObjectNode();
prefs.entrySet().forEach(e -> json.set(keyName(e.getKey()), e.getValue()));
return json;
}
}
}
......
......@@ -54,6 +54,7 @@ public class UiWebSocket
private Connection connection;
private FrameConnection control;
private String userName;
private final ObjectMapper mapper = new ObjectMapper();
......@@ -66,9 +67,16 @@ public class UiWebSocket
* Creates a new web-socket for serving data to GUI.
*
* @param directory service directory
* @param userName user name of the logged-in user
*/
public UiWebSocket(ServiceDirectory directory) {
public UiWebSocket(ServiceDirectory directory, String userName) {
this.directory = directory;
this.userName = userName;
}
@Override
public String userName() {
return userName;
}
/**
......
......@@ -70,7 +70,10 @@ public class UiWebSocketServlet extends WebSocketServlet {
if (isStopped) {
return null;
}
UiWebSocket socket = new UiWebSocket(directory);
// FIXME: Replace this with globally shared opaque token to allow secure failover
String userName = request.getUserPrincipal().getName();
UiWebSocket socket = new UiWebSocket(directory, userName);
synchronized (sockets) {
sockets.add(socket);
}
......@@ -89,6 +92,20 @@ public class UiWebSocketServlet extends WebSocketServlet {
}
}
/**
* Sends the specified message to all the GUI clients of the specified user.
*
* @param userName user name
* @param type message type
* @param payload message payload
*/
static void sendToUser(String userName, String type, ObjectNode payload) {
if (instance != null) {
instance.sockets.stream().filter(ws -> userName.equals(ws.userName()))
.forEach(ws -> ws.sendMessage(type, 0, payload));
}
}
// Task for pruning web-sockets that are idle.
private class Pruner extends TimerTask {
@Override
......
/*
* Copyright 2016 Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onosproject.ui.impl;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.collect.ImmutableSet;
import org.onosproject.ui.RequestHandler;
import org.onosproject.ui.UiMessageHandler;
import org.onosproject.ui.UiPreferencesService;
import java.util.Collection;
import static com.google.common.base.Strings.isNullOrEmpty;
/**
* Message handler for intercepting user preferences messages.
*/
class UserPreferencesMessageHandler extends UiMessageHandler {
private static final String UPDATE_PREFS_REQ = "updatePrefReq";
private static final String KEY = "key";
private static final String VALUE = "value";
@Override
protected Collection<RequestHandler> createRequestHandlers() {
return ImmutableSet.of(new UpdatePreferencesRequest());
}
private final class UpdatePreferencesRequest extends RequestHandler {
private UpdatePreferencesRequest() {
super(UPDATE_PREFS_REQ);
}
@Override
public void process(long sid, ObjectNode payload) {
if (!isNullOrEmpty(connection().userName())) {
UiPreferencesService service = get(UiPreferencesService.class);
service.setPreference(connection().userName(),
payload.get(KEY).asText(),
(ObjectNode) payload.get(VALUE));
}
}
}
}
......@@ -122,9 +122,9 @@
angular.module('onosLayer')
.factory('LoadingService',
['$log', '$timeout', 'ThemeService', 'FnService',
['$log', '$timeout', 'ThemeService', 'FnService', 'WebSocketService',
function (_$log_, _$timeout_, _ts_, _fs_) {
function (_$log_, _$timeout_, _ts_, _fs_, wss) {
$log = _$log_;
$timeout = _$timeout_;
ts = _ts_;
......@@ -132,11 +132,13 @@
preloadImages();
return {
var self = {
start: start,
stop: stop,
waiting: waiting
};
wss._setLoadingDelegate(self);
return self;
}]);
}());
\ No newline at end of file
......
......@@ -79,21 +79,23 @@
angular.module('onosLayer')
.factory('VeilService',
['$log', '$route', 'FnService', 'KeyService', 'GlyphService',
['$log', '$route', 'FnService', 'KeyService', 'GlyphService', 'WebSocketService',
function (_$log_, _$route_, _fs_, _ks_, _gs_) {
function (_$log_, _$route_, _fs_, _ks_, _gs_, wss) {
$log = _$log_;
$route = _$route_;
fs = _fs_;
ks = _ks_;
gs = _gs_;
return {
var self = {
init: init,
show: show,
hide: hide,
lostServer: lostServer
};
wss._setVeilDelegate(self);
return self;
}]);
}());
......
......@@ -59,7 +59,7 @@
function handleOpen() {
$log.info('Web socket open - ', url);
vs.hide();
vs && vs.hide();
if (fs.debugOn('txrx')) {
$log.debug('Sending ' + pendingEvents.length + ' pending event(s)...');
......@@ -105,14 +105,14 @@
var gsucc;
$log.info('Web socket closed');
ls.stop();
ls && ls.stop();
wsUp = false;
if (gsucc = findGuiSuccessor()) {
createWebSocket(webSockOpts, gsucc);
} else {
// If no controllers left to contact, show the Veil...
vs.show([
vs && vs.show([
'Oops!',
'Web-socket connection to server closed...',
'Try refreshing the page.'
......@@ -296,22 +296,29 @@
}
}
// Binds the veil service as a delegate
function setVeilDelegate(vd) {
vs = vd;
}
// Binds the loading service as a delegate
function setLoadingDelegate(ld) {
ls = ld;
}
// ============================
// ===== Definition of module
angular.module('onosRemote')
.factory('WebSocketService',
['$log', '$location', 'FnService', 'UrlFnService', 'WSock',
'VeilService', 'LoadingService',
function (_$log_, _$loc_, _fs_, _ufs_, _wsock_, _vs_, _ls_) {
function (_$log_, _$loc_, _fs_, _ufs_, _wsock_) {
$log = _$log_;
$loc = _$loc_;
fs = _fs_;
ufs = _ufs_;
wsock = _wsock_;
vs = _vs_;
ls = _ls_;
bindHandlers(builtinHandlers);
......@@ -324,7 +331,10 @@
addOpenListener: addOpenListener,
removeOpenListener: removeOpenListener,
sendEvent: sendEvent,
isConnected: function () { return wsUp; }
isConnected: function () { return wsUp; },
_setVeilDelegate: setVeilDelegate,
_setLoadingDelegate: setLoadingDelegate
};
}
]);
......
......@@ -21,54 +21,14 @@
'use strict';
// injected refs
var $log, $cookies, fs;
var $log, fs, wss;
// internal state
var cache = {};
// NOTE: in Angular 1.3.5, $cookies is just a simple object, and
// cookie values are just strings. From the 1.3.5 docs:
//
// "Only a simple Object is exposed and by adding or removing
// properties to/from this object, new cookies are created/deleted
// at the end of current $eval. The object's properties can only
// be strings."
//
// We may want to upgrade the version of Angular sometime soon
// since later version support objects as cookie values.
// NOTE: prefs represented as simple name/value pairs
// => a temporary restriction while we are encoding into cookies
/*
{
foo: 1,
bar: 0,
goo: 2
}
stored as "foo:1,bar:0,goo:2"
*/
// reads cookie with given name and returns an object rep of its value
// or null if no such cookie is set
function getPrefs(name) {
var cook = $cookies[name],
bits,
obj = {};
if (cook) {
bits = cook.split(',');
bits.forEach(function (value) {
var x = value.split(':');
obj[x[0]] = x[1];
});
// update the cache
cache[name] = obj;
return obj;
}
// perhaps we have a cached copy..
return cache[name];
// returns the preference by the specified name
function getPrefs(name, defaults) {
return cache[name] || defaults;
}
// converts string values to numbers for selected (or all) keys
......@@ -89,34 +49,28 @@
}
function setPrefs(name, obj) {
var bits = [],
str;
angular.forEach(obj, function (value, key) {
bits.push(key + ':' + value);
});
str = bits.join(',');
// keep a cached copy of the object
// keep a cached copy of the object and send an update to server
cache[name] = obj;
// The angular way of doing this...
// $cookies[name] = str;
// ...but it appears that this gets delayed, and doesn't 'stick' ??
// FORCE cookie to be set by writing directly to document.cookie...
document.cookie = name + '=' + encodeURIComponent(str);
if (fs.debugOn('prefs')) {
$log.debug('<<>> Wrote cookie <'+name+'>:', str);
}
wss.sendEvent('updatePrefReq', { key: name, value: obj });
}
function updatePrefs(data) {
$log.info('User properties updated');
cache[data.key] = data.value;
}
angular.module('onosUtil')
.factory('PrefsService', ['$log', '$cookies', 'FnService',
function (_$log_, _$cookies_, _fs_) {
.factory('PrefsService', ['$log', 'FnService', 'WebSocketService',
function (_$log_, _fs_, _wss_) {
$log = _$log_;
$cookies = _$cookies_;
fs = _fs_;
wss = _wss_;
cache = userPrefs;
wss.bindHandlers({
updatePrefs: updatePrefs
});
return {
getPrefs: getPrefs,
......
......@@ -20,7 +20,7 @@
(function () {
'use strict';
var $log, fs;
var $log, fs, ps;
var themes = ['light', 'dark'],
themeStr = themes.join(' '),
......@@ -29,7 +29,7 @@
nextListenerId = 1;
function init() {
thidx = 0;
thidx = ps.getPrefs('theme', { idx: 0 }).idx;
updateBodyClass();
}
......@@ -37,10 +37,11 @@
return themes[thidx];
}
function setTheme(t) {
function setTheme(t, force) {
var idx = themes.indexOf(t);
if (idx > -1 && idx !== thidx) {
if (force || idx > -1 && idx !== thidx) {
thidx = idx;
ps.setPrefs('theme', { idx: thidx });
updateBodyClass();
themeEvent('set');
}
......@@ -49,6 +50,7 @@
function toggleTheme() {
var i = thidx + 1;
thidx = (i===themes.length) ? 0 : i;
ps.setPrefs('theme', { idx: thidx });
updateBodyClass();
themeEvent('toggle');
return getTheme();
......@@ -97,11 +99,11 @@
}
angular.module('onosUtil')
.factory('ThemeService', ['$log', 'FnService',
function (_$log_, _fs_) {
.factory('ThemeService', ['$log', 'FnService', 'PrefsService',
function (_$log_, _fs_, _ps_) {
$log = _$log_;
fs = _fs_;
thidx = 0;
ps = _ps_;
return {
init: init,
......
......@@ -219,6 +219,10 @@
}
}
function isVisible() {
return panel.isVisible();
}
return {
addButton: addButton,
addToggle: addToggle,
......@@ -228,7 +232,8 @@
show: show,
hide: hide,
toggle: toggle
toggle: toggle,
isVisible: isVisible
};
}
......
......@@ -30,7 +30,7 @@
// references to injected services
var $scope, $log, $cookies, fs, ks, zs, gs, ms, sus, flash, wss, ps, th,
tds, tes, tfs, tps, tis, tss, tls, tts, tos, fltr, ttbs, tspr, ttip, tov;
tds, t3s, tes, tfs, tps, tis, tss, tls, tts, tos, fltr, ttbs, tspr, ttip, tov;
// DOM elements
var ovtopo, svg, defs, zoomLayer, mapG, spriteG, forceG, noDevsLayer;
......@@ -369,16 +369,14 @@
function setUpMap($loc) {
var qp = $loc.search(),
pr = ps.getPrefs('topo_mapid'),
mi1 = qp.mapid,
mi2 = pr && pr.id,
mapId = mi1 || mi2 || 'usa',
ms1 = qp.mapscale,
ms2 = pr && pr.scale,
mapScale = ms1 || ms2 || 1,
t1 = qp.tint,
t2 = pr && pr.tint,
tint = t1 || t2 || 'off',
pr = ps.getPrefs('topo_mapid', {
id: qp.mapid || 'usa',
scale: qp.mapscale || 1,
tint: qp.tint || 'off'
}),
mapId = pr.id,
mapScale = pr.scale,
tint = pr.tint,
promise,
cfilter;
......@@ -441,8 +439,8 @@
function setUpSprites($loc, tspr) {
var s1 = $loc.search().sprites,
s2 = ps.getPrefs('topo_sprites'),
sprId = s1 || (s2 && s2.id);
s2 = ps.getPrefs('topo_sprites', { id: s1 }),
sprId = s2.id;
spriteG = zoomLayer.append ('g').attr('id', 'topo-sprites');
if (sprId) {
......@@ -463,7 +461,7 @@
function restoreConfigFromPrefs() {
// NOTE: toolbar will have set this for us..
prefsState = ps.asNumbers(ps.getPrefs('topo_prefs'));
prefsState = ps.asNumbers(ps.getPrefs('topo_prefs', ttbs.defaultPrefs));
$log.debug('TOPO- Prefs State:', prefsState);
......@@ -476,6 +474,7 @@
togglePorts(prefsState.porthl);
toggleMap(prefsState.bg);
toggleSprites(prefsState.spr);
t3s.setDevLabIndex(prefsState.dlbls);
flash.enable(true);
}
......@@ -484,7 +483,7 @@
// have opened the websocket to the server; hence this extra function
// invoked after tes.start()
function restoreSummaryFromPrefs() {
prefsState = ps.asNumbers(ps.getPrefs('topo_prefs'));
prefsState = ps.asNumbers(ps.getPrefs('topo_prefs', ttbs.defaultPrefs));
$log.debug('TOPO- Prefs SUMMARY State:', prefsState.summary);
flash.enable(false);
......@@ -506,7 +505,7 @@
'$cookies', 'FnService', 'MastService', 'KeyService', 'ZoomService',
'GlyphService', 'MapService', 'SvgUtilService', 'FlashService',
'WebSocketService', 'PrefsService', 'ThemeService',
'TopoDialogService',
'TopoDialogService', 'TopoD3Service',
'TopoEventService', 'TopoForceService', 'TopoPanelService',
'TopoInstService', 'TopoSelectService', 'TopoLinkService',
'TopoTrafficService', 'TopoObliqueService', 'TopoFilterService',
......@@ -515,7 +514,7 @@
function (_$scope_, _$log_, $loc, $timeout, _$cookies_, _fs_, mast, _ks_,
_zs_, _gs_, _ms_, _sus_, _flash_, _wss_, _ps_, _th_,
_tds_, _tes_,
_tds_, _t3s_, _tes_,
_tfs_, _tps_, _tis_, _tss_, _tls_, _tts_, _tos_, _fltr_,
_ttbs_, _tspr_, _ttip_, _tov_) {
var params = $loc.search(),
......@@ -545,6 +544,7 @@
ps = _ps_;
th = _th_;
tds = _tds_;
t3s = _t3s_;
tes = _tes_;
tfs = _tfs_;
// TODO: consider funnelling actions through TopoForceService...
......@@ -601,7 +601,7 @@
setUpNoDevs();
setUpMap($loc).then(
function (proj) {
var z = ps.getPrefs('topo_zoom') || {tx:0, ty:0, sc:1};
var z = ps.getPrefs('topo_zoom', { tx:0, ty:0, sc:1 });
zoomer.panZoom([z.tx, z.ty], z.sc);
$log.debug('** Zoom restored:', z);
......
......@@ -23,7 +23,7 @@
'use strict';
// injected refs
var $log, fs, sus, is, ts;
var $log, fs, sus, is, ts, ps, ttbs;
// api to topoForce
var api;
......@@ -159,7 +159,7 @@
// ====
function incDevLabIndex() {
deviceLabelIndex = (deviceLabelIndex+1) % 3;
setDevLabIndex(deviceLabelIndex+1);
switch(deviceLabelIndex) {
case 0: return 'Hide device labels';
case 1: return 'Show friendly device labels';
......@@ -167,6 +167,13 @@
}
}
function setDevLabIndex(mode) {
deviceLabelIndex = mode % 3;
var p = ps.getPrefs('topo_prefs', ttbs.defaultPrefs);
p.dlbls = deviceLabelIndex;
ps.setPrefs('topo_prefs', p);
}
// Returns the newly computed bounding box of the rectangle
function adjustRectToFitText(n) {
var text = n.select('text'),
......@@ -599,13 +606,16 @@
angular.module('ovTopo')
.factory('TopoD3Service',
['$log', 'FnService', 'SvgUtilService', 'IconService', 'ThemeService',
'PrefsService', 'TopoToolbarService',
function (_$log_, _fs_, _sus_, _is_, _ts_) {
function (_$log_, _fs_, _sus_, _is_, _ts_, _ps_, _ttbs_) {
$log = _$log_;
fs = _fs_;
sus = _sus_;
is = _is_;
ts = _ts_;
ps = _ps_;
ttbs = _ttbs_;
icfg = is.iconConfig();
......@@ -620,6 +630,7 @@
destroyD3: destroyD3,
incDevLabIndex: incDevLabIndex,
setDevLabIndex: setDevLabIndex,
adjustRectToFitText: adjustRectToFitText,
hostLabel: hostLabel,
deviceLabel: deviceLabel,
......
......@@ -529,6 +529,7 @@
showSummary: showSummary,
toggleSummary: toggleSummary,
hideSummary: hideSummaryPanel,
toggleUseDetailsFlag: toggleUseDetailsFlag,
displaySingle: displaySingle,
......@@ -538,8 +539,6 @@
displaySomething: displaySomething,
addAction: addAction,
hideSummaryPanel: hideSummaryPanel,
detailVisible: function () { return detail.panel().isVisible(); },
summaryVisible: function () { return summary.panel().isVisible(); }
};
......
......@@ -70,11 +70,12 @@
// initial toggle state: default settings and tag to key mapping
var defaultPrefsState = {
summary: 1,
insts: 1,
summary: 1,
detail: 1,
hosts: 0,
offdev: 1,
dlbls: 0,
porthl: 1,
bg: 0,
spr: 0,
......@@ -104,7 +105,7 @@
}
function setInitToggleState() {
cachedState = ps.asNumbers(ps.getPrefs(cooktag));
cachedState = ps.asNumbers(ps.getPrefs(cooktag, defaultPrefsState));
$log.debug('TOOLBAR---- read prefs state:', cachedState);
if (!cachedState) {
......@@ -264,6 +265,9 @@
function toggleToolbar() {
toolbar.toggle();
var prefs = ps.getPrefs(cooktag, defaultPrefsState);
prefs.toolbar = !prefs.toolbar;
ps.setPrefs('topo_prefs', prefs);
}
function setDefaultOverlay() {
......@@ -298,6 +302,7 @@
keyListener: keyListener,
toggleToolbar: toggleToolbar,
setDefaultOverlay: setDefaultOverlay,
defaultPrefs: defaultPrefsState,
fnkey: fnkey
};
}]);
......
......@@ -38,6 +38,9 @@
<script src="tp/Chart.min.js"></script>
<script src="tp/angular-chart.min.js"></script>
<!-- {INJECTED-USER-START} -->
<!-- {INJECTED-USER-END} -->
<!-- ONOS UI Framework included here -->
<!-- TODO: use a single catenated-minified file here -->
<script src="onos.js"></script>
......