Adding server-side user preferences.
More work still needs to get done to allow client to process server-pushed preferences updates. Change-Id: I6e80e3f3677285cb19cfa3b6240c1b13aac56622
Showing
19 changed files
with
394 additions
and
122 deletions
| ... | @@ -23,6 +23,13 @@ import com.fasterxml.jackson.databind.node.ObjectNode; | ... | @@ -23,6 +23,13 @@ import com.fasterxml.jackson.databind.node.ObjectNode; |
| 23 | public interface UiConnection { | 23 | public interface UiConnection { |
| 24 | 24 | ||
| 25 | /** | 25 | /** |
| 26 | + * Returns the name of the logged-in user for which this connection exists. | ||
| 27 | + * | ||
| 28 | + * @return logged in user name | ||
| 29 | + */ | ||
| 30 | + String userName(); | ||
| 31 | + | ||
| 32 | + /** | ||
| 26 | * Sends the specified JSON message to the user interface client. | 33 | * Sends the specified JSON message to the user interface client. |
| 27 | * | 34 | * |
| 28 | * @param message message to send | 35 | * @param message message to send | ... | ... |
| 1 | +/* | ||
| 2 | + * Copyright 2016 Open Networking Laboratory | ||
| 3 | + * | ||
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 5 | + * you may not use this file except in compliance with the License. | ||
| 6 | + * You may obtain a copy of the License at | ||
| 7 | + * | ||
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 9 | + * | ||
| 10 | + * Unless required by applicable law or agreed to in writing, software | ||
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 13 | + * See the License for the specific language governing permissions and | ||
| 14 | + * limitations under the License. | ||
| 15 | + */ | ||
| 16 | + | ||
| 17 | +package org.onosproject.ui; | ||
| 18 | + | ||
| 19 | +import com.fasterxml.jackson.databind.node.ObjectNode; | ||
| 20 | + | ||
| 21 | +import java.util.Map; | ||
| 22 | +import java.util.Set; | ||
| 23 | + | ||
| 24 | +/** | ||
| 25 | + * Service for tracking user interface preferences. | ||
| 26 | + */ | ||
| 27 | +public interface UiPreferencesService { | ||
| 28 | + | ||
| 29 | + /** | ||
| 30 | + * Returns the list of user names that have user preferences available. | ||
| 31 | + * | ||
| 32 | + * @return list of user names | ||
| 33 | + */ | ||
| 34 | + Set<String> getUserNames(); | ||
| 35 | + | ||
| 36 | + /** | ||
| 37 | + * Returns an immutable copy of the preferences for the specified user. | ||
| 38 | + * | ||
| 39 | + * @param userName user name | ||
| 40 | + * @return map of user preferences | ||
| 41 | + */ | ||
| 42 | + Map<String, ObjectNode> getPreferences(String userName); | ||
| 43 | + | ||
| 44 | + /** | ||
| 45 | + * Sets the named preference for the specified user. | ||
| 46 | + * | ||
| 47 | + * @param userName user name | ||
| 48 | + * @param preference name of the user preference | ||
| 49 | + * @param value preference value | ||
| 50 | + */ | ||
| 51 | + void setPreference(String userName, String preference, ObjectNode value); | ||
| 52 | + | ||
| 53 | +} |
| ... | @@ -54,6 +54,11 @@ | ... | @@ -54,6 +54,11 @@ |
| 54 | </dependency> | 54 | </dependency> |
| 55 | <dependency> | 55 | <dependency> |
| 56 | <groupId>org.onosproject</groupId> | 56 | <groupId>org.onosproject</groupId> |
| 57 | + <artifactId>onos-core-serializers</artifactId> | ||
| 58 | + <version>${project.version}</version> | ||
| 59 | + </dependency> | ||
| 60 | + <dependency> | ||
| 61 | + <groupId>org.onosproject</groupId> | ||
| 57 | <artifactId>onos-incubator-api</artifactId> | 62 | <artifactId>onos-incubator-api</artifactId> |
| 58 | </dependency> | 63 | </dependency> |
| 59 | <dependency> | 64 | <dependency> | ... | ... |
| ... | @@ -15,17 +15,22 @@ | ... | @@ -15,17 +15,22 @@ |
| 15 | */ | 15 | */ |
| 16 | package org.onosproject.ui.impl; | 16 | package org.onosproject.ui.impl; |
| 17 | 17 | ||
| 18 | +import com.fasterxml.jackson.databind.node.ObjectNode; | ||
| 18 | import com.google.common.collect.ImmutableList; | 19 | import com.google.common.collect.ImmutableList; |
| 19 | import com.google.common.collect.ImmutableList.Builder; | 20 | import com.google.common.collect.ImmutableList.Builder; |
| 20 | import org.onlab.osgi.ServiceNotFoundException; | 21 | import org.onlab.osgi.ServiceNotFoundException; |
| 21 | import org.onosproject.rest.AbstractInjectionResource; | 22 | import org.onosproject.rest.AbstractInjectionResource; |
| 22 | import org.onosproject.ui.UiExtensionService; | 23 | import org.onosproject.ui.UiExtensionService; |
| 24 | +import org.onosproject.ui.UiPreferencesService; | ||
| 23 | 25 | ||
| 24 | import javax.ws.rs.GET; | 26 | import javax.ws.rs.GET; |
| 25 | import javax.ws.rs.Path; | 27 | import javax.ws.rs.Path; |
| 26 | import javax.ws.rs.Produces; | 28 | import javax.ws.rs.Produces; |
| 29 | +import javax.ws.rs.core.Context; | ||
| 27 | import javax.ws.rs.core.MediaType; | 30 | import javax.ws.rs.core.MediaType; |
| 28 | import javax.ws.rs.core.Response; | 31 | import javax.ws.rs.core.Response; |
| 32 | +import javax.ws.rs.core.SecurityContext; | ||
| 33 | +import java.io.ByteArrayInputStream; | ||
| 29 | import java.io.IOException; | 34 | import java.io.IOException; |
| 30 | import java.io.InputStream; | 35 | import java.io.InputStream; |
| 31 | import java.io.SequenceInputStream; | 36 | import java.io.SequenceInputStream; |
| ... | @@ -42,12 +47,21 @@ public class MainIndexResource extends AbstractInjectionResource { | ... | @@ -42,12 +47,21 @@ public class MainIndexResource extends AbstractInjectionResource { |
| 42 | private static final String INDEX = "index.html"; | 47 | private static final String INDEX = "index.html"; |
| 43 | private static final String NOT_READY = "not-ready.html"; | 48 | private static final String NOT_READY = "not-ready.html"; |
| 44 | 49 | ||
| 50 | + private static final String INJECT_USER_START = "<!-- {INJECTED-USER-START} -->"; | ||
| 51 | + private static final String INJECT_USER_END = "<!-- {INJECTED-USER-END} -->"; | ||
| 52 | + | ||
| 45 | private static final String INJECT_CSS_START = "<!-- {INJECTED-STYLESHEETS-START} -->"; | 53 | private static final String INJECT_CSS_START = "<!-- {INJECTED-STYLESHEETS-START} -->"; |
| 46 | private static final String INJECT_CSS_END = "<!-- {INJECTED-STYLESHEETS-END} -->"; | 54 | private static final String INJECT_CSS_END = "<!-- {INJECTED-STYLESHEETS-END} -->"; |
| 47 | 55 | ||
| 48 | private static final String INJECT_JS_START = "<!-- {INJECTED-JAVASCRIPT-START} -->"; | 56 | private static final String INJECT_JS_START = "<!-- {INJECTED-JAVASCRIPT-START} -->"; |
| 49 | private static final String INJECT_JS_END = "<!-- {INJECTED-JAVASCRIPT-END} -->"; | 57 | private static final String INJECT_JS_END = "<!-- {INJECTED-JAVASCRIPT-END} -->"; |
| 50 | 58 | ||
| 59 | + private static final byte[] SCRIPT_START = "\n<script>\n".getBytes(); | ||
| 60 | + private static final byte[] SCRIPT_END = "\n</script>\n\n".getBytes(); | ||
| 61 | + | ||
| 62 | + @Context | ||
| 63 | + private SecurityContext ctx; | ||
| 64 | + | ||
| 51 | @GET | 65 | @GET |
| 52 | @Produces(MediaType.TEXT_HTML) | 66 | @Produces(MediaType.TEXT_HTML) |
| 53 | public Response getMainIndex() throws IOException { | 67 | public Response getMainIndex() throws IOException { |
| ... | @@ -62,14 +76,25 @@ public class MainIndexResource extends AbstractInjectionResource { | ... | @@ -62,14 +76,25 @@ public class MainIndexResource extends AbstractInjectionResource { |
| 62 | InputStream indexTemplate = classLoader.getResourceAsStream(INDEX); | 76 | InputStream indexTemplate = classLoader.getResourceAsStream(INDEX); |
| 63 | String index = new String(toByteArray(indexTemplate)); | 77 | String index = new String(toByteArray(indexTemplate)); |
| 64 | 78 | ||
| 65 | - int p1s = split(index, 0, INJECT_JS_START) - INJECT_JS_START.length(); | 79 | + int p0s = split(index, 0, INJECT_USER_START) - INJECT_USER_START.length(); |
| 80 | + int p0e = split(index, p0s, INJECT_USER_END); | ||
| 81 | + int p1s = split(index, p0e, INJECT_JS_START) - INJECT_JS_START.length(); | ||
| 66 | int p1e = split(index, p1s, INJECT_JS_END); | 82 | int p1e = split(index, p1s, INJECT_JS_END); |
| 67 | int p2s = split(index, p1e, INJECT_CSS_START) - INJECT_CSS_START.length(); | 83 | int p2s = split(index, p1e, INJECT_CSS_START) - INJECT_CSS_START.length(); |
| 68 | int p2e = split(index, p2s, INJECT_CSS_END); | 84 | int p2e = split(index, p2s, INJECT_CSS_END); |
| 69 | int p3s = split(index, p2e, null); | 85 | int p3s = split(index, p2e, null); |
| 70 | 86 | ||
| 87 | + // FIXME: use global opaque auth token to allow secure failover | ||
| 88 | + String userName = ctx.getUserPrincipal().getName(); | ||
| 89 | + String auth = "var onosAuth='" + userName + "';\n"; | ||
| 90 | + | ||
| 71 | StreamEnumeration streams = | 91 | StreamEnumeration streams = |
| 72 | - new StreamEnumeration(of(stream(index, 0, p1s), | 92 | + new StreamEnumeration(of(stream(index, 0, p0s), |
| 93 | + new ByteArrayInputStream(SCRIPT_START), | ||
| 94 | + stream(auth, 0, auth.length()), | ||
| 95 | + userPreferences(userName), | ||
| 96 | + new ByteArrayInputStream(SCRIPT_END), | ||
| 97 | + stream(index, p0e, p1s), | ||
| 73 | includeJs(service), | 98 | includeJs(service), |
| 74 | stream(index, p1e, p2s), | 99 | stream(index, p1e, p2s), |
| 75 | includeCss(service), | 100 | includeCss(service), |
| ... | @@ -78,6 +103,15 @@ public class MainIndexResource extends AbstractInjectionResource { | ... | @@ -78,6 +103,15 @@ public class MainIndexResource extends AbstractInjectionResource { |
| 78 | return Response.ok(new SequenceInputStream(streams)).build(); | 103 | return Response.ok(new SequenceInputStream(streams)).build(); |
| 79 | } | 104 | } |
| 80 | 105 | ||
| 106 | + // Produces an input stream including user preferences. | ||
| 107 | + private InputStream userPreferences(String userName) { | ||
| 108 | + UiPreferencesService service = get(UiPreferencesService.class); | ||
| 109 | + ObjectNode prefs = mapper().createObjectNode(); | ||
| 110 | + service.getPreferences(userName).forEach(prefs::set); | ||
| 111 | + String string = "var userPrefs = " + prefs.toString() + ";"; | ||
| 112 | + return new ByteArrayInputStream(string.getBytes()); | ||
| 113 | + } | ||
| 114 | + | ||
| 81 | // Produces an input stream including JS injections from all extensions. | 115 | // Produces an input stream including JS injections from all extensions. |
| 82 | private InputStream includeJs(UiExtensionService service) { | 116 | private InputStream includeJs(UiExtensionService service) { |
| 83 | Builder<InputStream> builder = ImmutableList.builder(); | 117 | Builder<InputStream> builder = ImmutableList.builder(); | ... | ... |
| ... | @@ -16,7 +16,19 @@ | ... | @@ -16,7 +16,19 @@ |
| 16 | package org.onosproject.ui.impl; | 16 | package org.onosproject.ui.impl; |
| 17 | 17 | ||
| 18 | import com.fasterxml.jackson.databind.JsonNode; | 18 | import com.fasterxml.jackson.databind.JsonNode; |
| 19 | +import com.fasterxml.jackson.databind.ObjectMapper; | ||
| 20 | +import com.fasterxml.jackson.databind.node.ArrayNode; | ||
| 21 | +import com.fasterxml.jackson.databind.node.BooleanNode; | ||
| 22 | +import com.fasterxml.jackson.databind.node.DoubleNode; | ||
| 23 | +import com.fasterxml.jackson.databind.node.IntNode; | ||
| 24 | +import com.fasterxml.jackson.databind.node.JsonNodeFactory; | ||
| 25 | +import com.fasterxml.jackson.databind.node.LongNode; | ||
| 26 | +import com.fasterxml.jackson.databind.node.NullNode; | ||
| 27 | +import com.fasterxml.jackson.databind.node.ObjectNode; | ||
| 28 | +import com.fasterxml.jackson.databind.node.ShortNode; | ||
| 29 | +import com.fasterxml.jackson.databind.node.TextNode; | ||
| 19 | import com.google.common.collect.ImmutableList; | 30 | import com.google.common.collect.ImmutableList; |
| 31 | +import com.google.common.collect.ImmutableMap; | ||
| 20 | import com.google.common.collect.ImmutableSet; | 32 | import com.google.common.collect.ImmutableSet; |
| 21 | import com.google.common.collect.Lists; | 33 | import com.google.common.collect.Lists; |
| 22 | import com.google.common.collect.Maps; | 34 | import com.google.common.collect.Maps; |
| ... | @@ -26,40 +38,49 @@ import org.apache.felix.scr.annotations.Deactivate; | ... | @@ -26,40 +38,49 @@ import org.apache.felix.scr.annotations.Deactivate; |
| 26 | import org.apache.felix.scr.annotations.Reference; | 38 | import org.apache.felix.scr.annotations.Reference; |
| 27 | import org.apache.felix.scr.annotations.ReferenceCardinality; | 39 | import org.apache.felix.scr.annotations.ReferenceCardinality; |
| 28 | import org.apache.felix.scr.annotations.Service; | 40 | import org.apache.felix.scr.annotations.Service; |
| 41 | +import org.onlab.util.KryoNamespace; | ||
| 29 | import org.onosproject.mastership.MastershipService; | 42 | import org.onosproject.mastership.MastershipService; |
| 43 | +import org.onosproject.store.serializers.KryoNamespaces; | ||
| 44 | +import org.onosproject.store.service.EventuallyConsistentMap; | ||
| 45 | +import org.onosproject.store.service.EventuallyConsistentMapEvent; | ||
| 46 | +import org.onosproject.store.service.EventuallyConsistentMapListener; | ||
| 47 | +import org.onosproject.store.service.StorageService; | ||
| 48 | +import org.onosproject.store.service.WallClockTimestamp; | ||
| 30 | import org.onosproject.ui.UiExtension; | 49 | import org.onosproject.ui.UiExtension; |
| 31 | import org.onosproject.ui.UiExtensionService; | 50 | import org.onosproject.ui.UiExtensionService; |
| 32 | import org.onosproject.ui.UiMessageHandlerFactory; | 51 | import org.onosproject.ui.UiMessageHandlerFactory; |
| 52 | +import org.onosproject.ui.UiPreferencesService; | ||
| 33 | import org.onosproject.ui.UiTopoOverlayFactory; | 53 | import org.onosproject.ui.UiTopoOverlayFactory; |
| 34 | import org.onosproject.ui.UiView; | 54 | import org.onosproject.ui.UiView; |
| 35 | import org.onosproject.ui.UiViewHidden; | 55 | import org.onosproject.ui.UiViewHidden; |
| 36 | import org.slf4j.Logger; | 56 | import org.slf4j.Logger; |
| 37 | import org.slf4j.LoggerFactory; | 57 | import org.slf4j.LoggerFactory; |
| 38 | 58 | ||
| 59 | +import java.util.LinkedHashMap; | ||
| 39 | import java.util.List; | 60 | import java.util.List; |
| 40 | import java.util.Map; | 61 | import java.util.Map; |
| 41 | import java.util.Set; | 62 | import java.util.Set; |
| 42 | 63 | ||
| 43 | import static com.google.common.collect.ImmutableList.of; | 64 | import static com.google.common.collect.ImmutableList.of; |
| 44 | import static java.util.stream.Collectors.toSet; | 65 | import static java.util.stream.Collectors.toSet; |
| 45 | -import static org.onosproject.ui.UiView.Category.NETWORK; | ||
| 46 | -import static org.onosproject.ui.UiView.Category.PLATFORM; | ||
| 47 | - | ||
| 48 | import static org.onosproject.security.AppGuard.checkPermission; | 66 | import static org.onosproject.security.AppGuard.checkPermission; |
| 49 | import static org.onosproject.security.AppPermission.Type.UI_READ; | 67 | import static org.onosproject.security.AppPermission.Type.UI_READ; |
| 50 | import static org.onosproject.security.AppPermission.Type.UI_WRITE; | 68 | import static org.onosproject.security.AppPermission.Type.UI_WRITE; |
| 69 | +import static org.onosproject.ui.UiView.Category.NETWORK; | ||
| 70 | +import static org.onosproject.ui.UiView.Category.PLATFORM; | ||
| 51 | 71 | ||
| 52 | /** | 72 | /** |
| 53 | * Manages the user interface extensions. | 73 | * Manages the user interface extensions. |
| 54 | */ | 74 | */ |
| 55 | @Component(immediate = true) | 75 | @Component(immediate = true) |
| 56 | @Service | 76 | @Service |
| 57 | -public class UiExtensionManager implements UiExtensionService, SpriteService { | 77 | +public class UiExtensionManager implements UiExtensionService, UiPreferencesService, SpriteService { |
| 58 | 78 | ||
| 59 | private static final ClassLoader CL = UiExtensionManager.class.getClassLoader(); | 79 | private static final ClassLoader CL = UiExtensionManager.class.getClassLoader(); |
| 60 | private static final String CORE = "core"; | 80 | private static final String CORE = "core"; |
| 61 | private static final String GUI_ADDED = "guiAdded"; | 81 | private static final String GUI_ADDED = "guiAdded"; |
| 62 | private static final String GUI_REMOVED = "guiRemoved"; | 82 | private static final String GUI_REMOVED = "guiRemoved"; |
| 83 | + private static final String UPDATE_PREFS = "updatePrefs"; | ||
| 63 | 84 | ||
| 64 | private final Logger log = LoggerFactory.getLogger(getClass()); | 85 | private final Logger log = LoggerFactory.getLogger(getClass()); |
| 65 | 86 | ||
| ... | @@ -72,10 +93,19 @@ public class UiExtensionManager implements UiExtensionService, SpriteService { | ... | @@ -72,10 +93,19 @@ public class UiExtensionManager implements UiExtensionService, SpriteService { |
| 72 | // Core views & core extension | 93 | // Core views & core extension |
| 73 | private final UiExtension core = createCoreExtension(); | 94 | private final UiExtension core = createCoreExtension(); |
| 74 | 95 | ||
| 75 | - | ||
| 76 | @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) | 96 | @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) |
| 77 | protected MastershipService mastershipService; | 97 | protected MastershipService mastershipService; |
| 78 | 98 | ||
| 99 | + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) | ||
| 100 | + protected StorageService storageService; | ||
| 101 | + | ||
| 102 | + // User preferences | ||
| 103 | + private EventuallyConsistentMap<String, ObjectNode> prefs; | ||
| 104 | + private final EventuallyConsistentMapListener<String, ObjectNode> prefsListener = | ||
| 105 | + new InternalPrefsListener(); | ||
| 106 | + | ||
| 107 | + private final ObjectMapper mapper = new ObjectMapper(); | ||
| 108 | + | ||
| 79 | // Creates core UI extension | 109 | // Creates core UI extension |
| 80 | private UiExtension createCoreExtension() { | 110 | private UiExtension createCoreExtension() { |
| 81 | List<UiView> coreViews = of( | 111 | List<UiView> coreViews = of( |
| ... | @@ -98,6 +128,7 @@ public class UiExtensionManager implements UiExtensionService, SpriteService { | ... | @@ -98,6 +128,7 @@ public class UiExtensionManager implements UiExtensionService, SpriteService { |
| 98 | 128 | ||
| 99 | UiMessageHandlerFactory messageHandlerFactory = | 129 | UiMessageHandlerFactory messageHandlerFactory = |
| 100 | () -> ImmutableList.of( | 130 | () -> ImmutableList.of( |
| 131 | + new UserPreferencesMessageHandler(), | ||
| 101 | new TopologyViewMessageHandler(), | 132 | new TopologyViewMessageHandler(), |
| 102 | new DeviceViewMessageHandler(), | 133 | new DeviceViewMessageHandler(), |
| 103 | new LinkViewMessageHandler(), | 134 | new LinkViewMessageHandler(), |
| ... | @@ -128,12 +159,28 @@ public class UiExtensionManager implements UiExtensionService, SpriteService { | ... | @@ -128,12 +159,28 @@ public class UiExtensionManager implements UiExtensionService, SpriteService { |
| 128 | 159 | ||
| 129 | @Activate | 160 | @Activate |
| 130 | public void activate() { | 161 | public void activate() { |
| 162 | + KryoNamespace.Builder kryoBuilder = new KryoNamespace.Builder() | ||
| 163 | + .register(KryoNamespaces.API) | ||
| 164 | + .register(ObjectNode.class, ArrayNode.class, | ||
| 165 | + JsonNodeFactory.class, LinkedHashMap.class, | ||
| 166 | + TextNode.class, BooleanNode.class, | ||
| 167 | + LongNode.class, DoubleNode.class, ShortNode.class, | ||
| 168 | + IntNode.class, NullNode.class); | ||
| 169 | + | ||
| 170 | + prefs = storageService.<String, ObjectNode>eventuallyConsistentMapBuilder() | ||
| 171 | + .withName("onos-user-preferences") | ||
| 172 | + .withSerializer(kryoBuilder) | ||
| 173 | + .withTimestampProvider((k, v) -> new WallClockTimestamp()) | ||
| 174 | + .withPersistence() | ||
| 175 | + .build(); | ||
| 176 | + prefs.addListener(prefsListener); | ||
| 131 | register(core); | 177 | register(core); |
| 132 | log.info("Started"); | 178 | log.info("Started"); |
| 133 | } | 179 | } |
| 134 | 180 | ||
| 135 | @Deactivate | 181 | @Deactivate |
| 136 | public void deactivate() { | 182 | public void deactivate() { |
| 183 | + prefs.removeListener(prefsListener); | ||
| 137 | UiWebSocketServlet.closeAll(); | 184 | UiWebSocketServlet.closeAll(); |
| 138 | unregister(core); | 185 | unregister(core); |
| 139 | log.info("Stopped"); | 186 | log.info("Stopped"); |
| ... | @@ -171,6 +218,27 @@ public class UiExtensionManager implements UiExtensionService, SpriteService { | ... | @@ -171,6 +218,27 @@ public class UiExtensionManager implements UiExtensionService, SpriteService { |
| 171 | return views.get(viewId); | 218 | return views.get(viewId); |
| 172 | } | 219 | } |
| 173 | 220 | ||
| 221 | + @Override | ||
| 222 | + public Set<String> getUserNames() { | ||
| 223 | + ImmutableSet.Builder<String> builder = ImmutableSet.builder(); | ||
| 224 | + prefs.keySet().forEach(k -> builder.add(userName(k))); | ||
| 225 | + return builder.build(); | ||
| 226 | + } | ||
| 227 | + | ||
| 228 | + @Override | ||
| 229 | + public Map<String, ObjectNode> getPreferences(String userName) { | ||
| 230 | + ImmutableMap.Builder<String, ObjectNode> builder = ImmutableMap.builder(); | ||
| 231 | + prefs.entrySet().stream() | ||
| 232 | + .filter(e -> e.getKey().startsWith(userName + "/")) | ||
| 233 | + .forEach(e -> builder.put(keyName(e.getKey()), e.getValue())); | ||
| 234 | + return builder.build(); | ||
| 235 | + } | ||
| 236 | + | ||
| 237 | + @Override | ||
| 238 | + public void setPreference(String userName, String preference, ObjectNode value) { | ||
| 239 | + prefs.put(key(userName, preference), value); | ||
| 240 | + } | ||
| 241 | + | ||
| 174 | // ===================================================================== | 242 | // ===================================================================== |
| 175 | // Provisional tracking of sprite definitions | 243 | // Provisional tracking of sprite definitions |
| 176 | 244 | ||
| ... | @@ -192,4 +260,33 @@ public class UiExtensionManager implements UiExtensionService, SpriteService { | ... | @@ -192,4 +260,33 @@ public class UiExtensionManager implements UiExtensionService, SpriteService { |
| 192 | return sprites.get(name); | 260 | return sprites.get(name); |
| 193 | } | 261 | } |
| 194 | 262 | ||
| 263 | + private String key(String userName, String keyName) { | ||
| 264 | + return userName + "/" + keyName; | ||
| 265 | + } | ||
| 266 | + | ||
| 267 | + private String userName(String key) { | ||
| 268 | + return key.split("/")[0]; | ||
| 269 | + } | ||
| 270 | + | ||
| 271 | + private String keyName(String key) { | ||
| 272 | + return key.split("/")[1]; | ||
| 273 | + } | ||
| 274 | + | ||
| 275 | + // Auxiliary listener to preference map events. | ||
| 276 | + private class InternalPrefsListener | ||
| 277 | + implements EventuallyConsistentMapListener<String, ObjectNode> { | ||
| 278 | + @Override | ||
| 279 | + public void event(EventuallyConsistentMapEvent<String, ObjectNode> event) { | ||
| 280 | + String userName = userName(event.key()); | ||
| 281 | + if (event.type() == EventuallyConsistentMapEvent.Type.PUT) { | ||
| 282 | + UiWebSocketServlet.sendToUser(userName, UPDATE_PREFS, jsonPrefs()); | ||
| 283 | + } | ||
| 284 | + } | ||
| 285 | + | ||
| 286 | + private ObjectNode jsonPrefs() { | ||
| 287 | + ObjectNode json = mapper.createObjectNode(); | ||
| 288 | + prefs.entrySet().forEach(e -> json.set(keyName(e.getKey()), e.getValue())); | ||
| 289 | + return json; | ||
| 290 | + } | ||
| 291 | + } | ||
| 195 | } | 292 | } | ... | ... |
| ... | @@ -54,6 +54,7 @@ public class UiWebSocket | ... | @@ -54,6 +54,7 @@ public class UiWebSocket |
| 54 | 54 | ||
| 55 | private Connection connection; | 55 | private Connection connection; |
| 56 | private FrameConnection control; | 56 | private FrameConnection control; |
| 57 | + private String userName; | ||
| 57 | 58 | ||
| 58 | private final ObjectMapper mapper = new ObjectMapper(); | 59 | private final ObjectMapper mapper = new ObjectMapper(); |
| 59 | 60 | ||
| ... | @@ -66,9 +67,16 @@ public class UiWebSocket | ... | @@ -66,9 +67,16 @@ public class UiWebSocket |
| 66 | * Creates a new web-socket for serving data to GUI. | 67 | * Creates a new web-socket for serving data to GUI. |
| 67 | * | 68 | * |
| 68 | * @param directory service directory | 69 | * @param directory service directory |
| 70 | + * @param userName user name of the logged-in user | ||
| 69 | */ | 71 | */ |
| 70 | - public UiWebSocket(ServiceDirectory directory) { | 72 | + public UiWebSocket(ServiceDirectory directory, String userName) { |
| 71 | this.directory = directory; | 73 | this.directory = directory; |
| 74 | + this.userName = userName; | ||
| 75 | + } | ||
| 76 | + | ||
| 77 | + @Override | ||
| 78 | + public String userName() { | ||
| 79 | + return userName; | ||
| 72 | } | 80 | } |
| 73 | 81 | ||
| 74 | /** | 82 | /** | ... | ... |
| ... | @@ -70,7 +70,10 @@ public class UiWebSocketServlet extends WebSocketServlet { | ... | @@ -70,7 +70,10 @@ public class UiWebSocketServlet extends WebSocketServlet { |
| 70 | if (isStopped) { | 70 | if (isStopped) { |
| 71 | return null; | 71 | return null; |
| 72 | } | 72 | } |
| 73 | - UiWebSocket socket = new UiWebSocket(directory); | 73 | + |
| 74 | + // FIXME: Replace this with globally shared opaque token to allow secure failover | ||
| 75 | + String userName = request.getUserPrincipal().getName(); | ||
| 76 | + UiWebSocket socket = new UiWebSocket(directory, userName); | ||
| 74 | synchronized (sockets) { | 77 | synchronized (sockets) { |
| 75 | sockets.add(socket); | 78 | sockets.add(socket); |
| 76 | } | 79 | } |
| ... | @@ -89,6 +92,20 @@ public class UiWebSocketServlet extends WebSocketServlet { | ... | @@ -89,6 +92,20 @@ public class UiWebSocketServlet extends WebSocketServlet { |
| 89 | } | 92 | } |
| 90 | } | 93 | } |
| 91 | 94 | ||
| 95 | + /** | ||
| 96 | + * Sends the specified message to all the GUI clients of the specified user. | ||
| 97 | + * | ||
| 98 | + * @param userName user name | ||
| 99 | + * @param type message type | ||
| 100 | + * @param payload message payload | ||
| 101 | + */ | ||
| 102 | + static void sendToUser(String userName, String type, ObjectNode payload) { | ||
| 103 | + if (instance != null) { | ||
| 104 | + instance.sockets.stream().filter(ws -> userName.equals(ws.userName())) | ||
| 105 | + .forEach(ws -> ws.sendMessage(type, 0, payload)); | ||
| 106 | + } | ||
| 107 | + } | ||
| 108 | + | ||
| 92 | // Task for pruning web-sockets that are idle. | 109 | // Task for pruning web-sockets that are idle. |
| 93 | private class Pruner extends TimerTask { | 110 | private class Pruner extends TimerTask { |
| 94 | @Override | 111 | @Override | ... | ... |
| 1 | +/* | ||
| 2 | + * Copyright 2016 Open Networking Laboratory | ||
| 3 | + * | ||
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 5 | + * you may not use this file except in compliance with the License. | ||
| 6 | + * You may obtain a copy of the License at | ||
| 7 | + * | ||
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 9 | + * | ||
| 10 | + * Unless required by applicable law or agreed to in writing, software | ||
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 13 | + * See the License for the specific language governing permissions and | ||
| 14 | + * limitations under the License. | ||
| 15 | + */ | ||
| 16 | + | ||
| 17 | +package org.onosproject.ui.impl; | ||
| 18 | + | ||
| 19 | +import com.fasterxml.jackson.databind.node.ObjectNode; | ||
| 20 | +import com.google.common.collect.ImmutableSet; | ||
| 21 | +import org.onosproject.ui.RequestHandler; | ||
| 22 | +import org.onosproject.ui.UiMessageHandler; | ||
| 23 | +import org.onosproject.ui.UiPreferencesService; | ||
| 24 | + | ||
| 25 | +import java.util.Collection; | ||
| 26 | + | ||
| 27 | +import static com.google.common.base.Strings.isNullOrEmpty; | ||
| 28 | + | ||
| 29 | +/** | ||
| 30 | + * Message handler for intercepting user preferences messages. | ||
| 31 | + */ | ||
| 32 | +class UserPreferencesMessageHandler extends UiMessageHandler { | ||
| 33 | + | ||
| 34 | + private static final String UPDATE_PREFS_REQ = "updatePrefReq"; | ||
| 35 | + private static final String KEY = "key"; | ||
| 36 | + private static final String VALUE = "value"; | ||
| 37 | + | ||
| 38 | + @Override | ||
| 39 | + protected Collection<RequestHandler> createRequestHandlers() { | ||
| 40 | + return ImmutableSet.of(new UpdatePreferencesRequest()); | ||
| 41 | + } | ||
| 42 | + | ||
| 43 | + private final class UpdatePreferencesRequest extends RequestHandler { | ||
| 44 | + private UpdatePreferencesRequest() { | ||
| 45 | + super(UPDATE_PREFS_REQ); | ||
| 46 | + } | ||
| 47 | + | ||
| 48 | + @Override | ||
| 49 | + public void process(long sid, ObjectNode payload) { | ||
| 50 | + if (!isNullOrEmpty(connection().userName())) { | ||
| 51 | + UiPreferencesService service = get(UiPreferencesService.class); | ||
| 52 | + service.setPreference(connection().userName(), | ||
| 53 | + payload.get(KEY).asText(), | ||
| 54 | + (ObjectNode) payload.get(VALUE)); | ||
| 55 | + } | ||
| 56 | + } | ||
| 57 | + } | ||
| 58 | +} |
| ... | @@ -122,9 +122,9 @@ | ... | @@ -122,9 +122,9 @@ |
| 122 | 122 | ||
| 123 | angular.module('onosLayer') | 123 | angular.module('onosLayer') |
| 124 | .factory('LoadingService', | 124 | .factory('LoadingService', |
| 125 | - ['$log', '$timeout', 'ThemeService', 'FnService', | 125 | + ['$log', '$timeout', 'ThemeService', 'FnService', 'WebSocketService', |
| 126 | 126 | ||
| 127 | - function (_$log_, _$timeout_, _ts_, _fs_) { | 127 | + function (_$log_, _$timeout_, _ts_, _fs_, wss) { |
| 128 | $log = _$log_; | 128 | $log = _$log_; |
| 129 | $timeout = _$timeout_; | 129 | $timeout = _$timeout_; |
| 130 | ts = _ts_; | 130 | ts = _ts_; |
| ... | @@ -132,11 +132,13 @@ | ... | @@ -132,11 +132,13 @@ |
| 132 | 132 | ||
| 133 | preloadImages(); | 133 | preloadImages(); |
| 134 | 134 | ||
| 135 | - return { | 135 | + var self = { |
| 136 | start: start, | 136 | start: start, |
| 137 | stop: stop, | 137 | stop: stop, |
| 138 | waiting: waiting | 138 | waiting: waiting |
| 139 | }; | 139 | }; |
| 140 | + wss._setLoadingDelegate(self); | ||
| 141 | + return self; | ||
| 140 | }]); | 142 | }]); |
| 141 | 143 | ||
| 142 | }()); | 144 | }()); |
| ... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
| ... | @@ -79,21 +79,23 @@ | ... | @@ -79,21 +79,23 @@ |
| 79 | 79 | ||
| 80 | angular.module('onosLayer') | 80 | angular.module('onosLayer') |
| 81 | .factory('VeilService', | 81 | .factory('VeilService', |
| 82 | - ['$log', '$route', 'FnService', 'KeyService', 'GlyphService', | 82 | + ['$log', '$route', 'FnService', 'KeyService', 'GlyphService', 'WebSocketService', |
| 83 | 83 | ||
| 84 | - function (_$log_, _$route_, _fs_, _ks_, _gs_) { | 84 | + function (_$log_, _$route_, _fs_, _ks_, _gs_, wss) { |
| 85 | $log = _$log_; | 85 | $log = _$log_; |
| 86 | $route = _$route_; | 86 | $route = _$route_; |
| 87 | fs = _fs_; | 87 | fs = _fs_; |
| 88 | ks = _ks_; | 88 | ks = _ks_; |
| 89 | gs = _gs_; | 89 | gs = _gs_; |
| 90 | 90 | ||
| 91 | - return { | 91 | + var self = { |
| 92 | init: init, | 92 | init: init, |
| 93 | show: show, | 93 | show: show, |
| 94 | hide: hide, | 94 | hide: hide, |
| 95 | lostServer: lostServer | 95 | lostServer: lostServer |
| 96 | }; | 96 | }; |
| 97 | + wss._setVeilDelegate(self); | ||
| 98 | + return self; | ||
| 97 | }]); | 99 | }]); |
| 98 | 100 | ||
| 99 | }()); | 101 | }()); | ... | ... |
| ... | @@ -59,7 +59,7 @@ | ... | @@ -59,7 +59,7 @@ |
| 59 | 59 | ||
| 60 | function handleOpen() { | 60 | function handleOpen() { |
| 61 | $log.info('Web socket open - ', url); | 61 | $log.info('Web socket open - ', url); |
| 62 | - vs.hide(); | 62 | + vs && vs.hide(); |
| 63 | 63 | ||
| 64 | if (fs.debugOn('txrx')) { | 64 | if (fs.debugOn('txrx')) { |
| 65 | $log.debug('Sending ' + pendingEvents.length + ' pending event(s)...'); | 65 | $log.debug('Sending ' + pendingEvents.length + ' pending event(s)...'); |
| ... | @@ -105,14 +105,14 @@ | ... | @@ -105,14 +105,14 @@ |
| 105 | var gsucc; | 105 | var gsucc; |
| 106 | 106 | ||
| 107 | $log.info('Web socket closed'); | 107 | $log.info('Web socket closed'); |
| 108 | - ls.stop(); | 108 | + ls && ls.stop(); |
| 109 | wsUp = false; | 109 | wsUp = false; |
| 110 | 110 | ||
| 111 | if (gsucc = findGuiSuccessor()) { | 111 | if (gsucc = findGuiSuccessor()) { |
| 112 | createWebSocket(webSockOpts, gsucc); | 112 | createWebSocket(webSockOpts, gsucc); |
| 113 | } else { | 113 | } else { |
| 114 | // If no controllers left to contact, show the Veil... | 114 | // If no controllers left to contact, show the Veil... |
| 115 | - vs.show([ | 115 | + vs && vs.show([ |
| 116 | 'Oops!', | 116 | 'Oops!', |
| 117 | 'Web-socket connection to server closed...', | 117 | 'Web-socket connection to server closed...', |
| 118 | 'Try refreshing the page.' | 118 | 'Try refreshing the page.' |
| ... | @@ -296,22 +296,29 @@ | ... | @@ -296,22 +296,29 @@ |
| 296 | } | 296 | } |
| 297 | } | 297 | } |
| 298 | 298 | ||
| 299 | + // Binds the veil service as a delegate | ||
| 300 | + function setVeilDelegate(vd) { | ||
| 301 | + vs = vd; | ||
| 302 | + } | ||
| 303 | + | ||
| 304 | + // Binds the loading service as a delegate | ||
| 305 | + function setLoadingDelegate(ld) { | ||
| 306 | + ls = ld; | ||
| 307 | + } | ||
| 308 | + | ||
| 299 | 309 | ||
| 300 | // ============================ | 310 | // ============================ |
| 301 | // ===== Definition of module | 311 | // ===== Definition of module |
| 302 | angular.module('onosRemote') | 312 | angular.module('onosRemote') |
| 303 | .factory('WebSocketService', | 313 | .factory('WebSocketService', |
| 304 | ['$log', '$location', 'FnService', 'UrlFnService', 'WSock', | 314 | ['$log', '$location', 'FnService', 'UrlFnService', 'WSock', |
| 305 | - 'VeilService', 'LoadingService', | ||
| 306 | 315 | ||
| 307 | - function (_$log_, _$loc_, _fs_, _ufs_, _wsock_, _vs_, _ls_) { | 316 | + function (_$log_, _$loc_, _fs_, _ufs_, _wsock_) { |
| 308 | $log = _$log_; | 317 | $log = _$log_; |
| 309 | $loc = _$loc_; | 318 | $loc = _$loc_; |
| 310 | fs = _fs_; | 319 | fs = _fs_; |
| 311 | ufs = _ufs_; | 320 | ufs = _ufs_; |
| 312 | wsock = _wsock_; | 321 | wsock = _wsock_; |
| 313 | - vs = _vs_; | ||
| 314 | - ls = _ls_; | ||
| 315 | 322 | ||
| 316 | bindHandlers(builtinHandlers); | 323 | bindHandlers(builtinHandlers); |
| 317 | 324 | ||
| ... | @@ -324,7 +331,10 @@ | ... | @@ -324,7 +331,10 @@ |
| 324 | addOpenListener: addOpenListener, | 331 | addOpenListener: addOpenListener, |
| 325 | removeOpenListener: removeOpenListener, | 332 | removeOpenListener: removeOpenListener, |
| 326 | sendEvent: sendEvent, | 333 | sendEvent: sendEvent, |
| 327 | - isConnected: function () { return wsUp; } | 334 | + isConnected: function () { return wsUp; }, |
| 335 | + | ||
| 336 | + _setVeilDelegate: setVeilDelegate, | ||
| 337 | + _setLoadingDelegate: setLoadingDelegate | ||
| 328 | }; | 338 | }; |
| 329 | } | 339 | } |
| 330 | ]); | 340 | ]); | ... | ... |
| ... | @@ -21,54 +21,14 @@ | ... | @@ -21,54 +21,14 @@ |
| 21 | 'use strict'; | 21 | 'use strict'; |
| 22 | 22 | ||
| 23 | // injected refs | 23 | // injected refs |
| 24 | - var $log, $cookies, fs; | 24 | + var $log, fs, wss; |
| 25 | 25 | ||
| 26 | // internal state | 26 | // internal state |
| 27 | var cache = {}; | 27 | var cache = {}; |
| 28 | 28 | ||
| 29 | - // NOTE: in Angular 1.3.5, $cookies is just a simple object, and | 29 | + // returns the preference by the specified name |
| 30 | - // cookie values are just strings. From the 1.3.5 docs: | 30 | + function getPrefs(name, defaults) { |
| 31 | - // | 31 | + return cache[name] || defaults; |
| 32 | - // "Only a simple Object is exposed and by adding or removing | ||
| 33 | - // properties to/from this object, new cookies are created/deleted | ||
| 34 | - // at the end of current $eval. The object's properties can only | ||
| 35 | - // be strings." | ||
| 36 | - // | ||
| 37 | - // We may want to upgrade the version of Angular sometime soon | ||
| 38 | - // since later version support objects as cookie values. | ||
| 39 | - | ||
| 40 | - // NOTE: prefs represented as simple name/value pairs | ||
| 41 | - // => a temporary restriction while we are encoding into cookies | ||
| 42 | - /* | ||
| 43 | - { | ||
| 44 | - foo: 1, | ||
| 45 | - bar: 0, | ||
| 46 | - goo: 2 | ||
| 47 | - } | ||
| 48 | - | ||
| 49 | - stored as "foo:1,bar:0,goo:2" | ||
| 50 | - */ | ||
| 51 | - | ||
| 52 | - // reads cookie with given name and returns an object rep of its value | ||
| 53 | - // or null if no such cookie is set | ||
| 54 | - function getPrefs(name) { | ||
| 55 | - var cook = $cookies[name], | ||
| 56 | - bits, | ||
| 57 | - obj = {}; | ||
| 58 | - | ||
| 59 | - if (cook) { | ||
| 60 | - bits = cook.split(','); | ||
| 61 | - bits.forEach(function (value) { | ||
| 62 | - var x = value.split(':'); | ||
| 63 | - obj[x[0]] = x[1]; | ||
| 64 | - }); | ||
| 65 | - | ||
| 66 | - // update the cache | ||
| 67 | - cache[name] = obj; | ||
| 68 | - return obj; | ||
| 69 | - } | ||
| 70 | - // perhaps we have a cached copy.. | ||
| 71 | - return cache[name]; | ||
| 72 | } | 32 | } |
| 73 | 33 | ||
| 74 | // converts string values to numbers for selected (or all) keys | 34 | // converts string values to numbers for selected (or all) keys |
| ... | @@ -89,34 +49,28 @@ | ... | @@ -89,34 +49,28 @@ |
| 89 | } | 49 | } |
| 90 | 50 | ||
| 91 | function setPrefs(name, obj) { | 51 | function setPrefs(name, obj) { |
| 92 | - var bits = [], | 52 | + // keep a cached copy of the object and send an update to server |
| 93 | - str; | ||
| 94 | - | ||
| 95 | - angular.forEach(obj, function (value, key) { | ||
| 96 | - bits.push(key + ':' + value); | ||
| 97 | - }); | ||
| 98 | - str = bits.join(','); | ||
| 99 | - | ||
| 100 | - // keep a cached copy of the object | ||
| 101 | cache[name] = obj; | 53 | cache[name] = obj; |
| 102 | - | 54 | + wss.sendEvent('updatePrefReq', { key: name, value: obj }); |
| 103 | - // The angular way of doing this... | 55 | + } |
| 104 | - // $cookies[name] = str; | 56 | + |
| 105 | - // ...but it appears that this gets delayed, and doesn't 'stick' ?? | 57 | + function updatePrefs(data) { |
| 106 | - | 58 | + $log.info('User properties updated'); |
| 107 | - // FORCE cookie to be set by writing directly to document.cookie... | 59 | + cache[data.key] = data.value; |
| 108 | - document.cookie = name + '=' + encodeURIComponent(str); | ||
| 109 | - if (fs.debugOn('prefs')) { | ||
| 110 | - $log.debug('<<>> Wrote cookie <'+name+'>:', str); | ||
| 111 | - } | ||
| 112 | } | 60 | } |
| 113 | 61 | ||
| 114 | angular.module('onosUtil') | 62 | angular.module('onosUtil') |
| 115 | - .factory('PrefsService', ['$log', '$cookies', 'FnService', | 63 | + .factory('PrefsService', ['$log', 'FnService', 'WebSocketService', |
| 116 | - function (_$log_, _$cookies_, _fs_) { | 64 | + function (_$log_, _fs_, _wss_) { |
| 117 | $log = _$log_; | 65 | $log = _$log_; |
| 118 | - $cookies = _$cookies_; | ||
| 119 | fs = _fs_; | 66 | fs = _fs_; |
| 67 | + wss = _wss_; | ||
| 68 | + | ||
| 69 | + cache = userPrefs; | ||
| 70 | + | ||
| 71 | + wss.bindHandlers({ | ||
| 72 | + updatePrefs: updatePrefs | ||
| 73 | + }); | ||
| 120 | 74 | ||
| 121 | return { | 75 | return { |
| 122 | getPrefs: getPrefs, | 76 | getPrefs: getPrefs, | ... | ... |
| ... | @@ -20,7 +20,7 @@ | ... | @@ -20,7 +20,7 @@ |
| 20 | (function () { | 20 | (function () { |
| 21 | 'use strict'; | 21 | 'use strict'; |
| 22 | 22 | ||
| 23 | - var $log, fs; | 23 | + var $log, fs, ps; |
| 24 | 24 | ||
| 25 | var themes = ['light', 'dark'], | 25 | var themes = ['light', 'dark'], |
| 26 | themeStr = themes.join(' '), | 26 | themeStr = themes.join(' '), |
| ... | @@ -29,7 +29,7 @@ | ... | @@ -29,7 +29,7 @@ |
| 29 | nextListenerId = 1; | 29 | nextListenerId = 1; |
| 30 | 30 | ||
| 31 | function init() { | 31 | function init() { |
| 32 | - thidx = 0; | 32 | + thidx = ps.getPrefs('theme', { idx: 0 }).idx; |
| 33 | updateBodyClass(); | 33 | updateBodyClass(); |
| 34 | } | 34 | } |
| 35 | 35 | ||
| ... | @@ -37,10 +37,11 @@ | ... | @@ -37,10 +37,11 @@ |
| 37 | return themes[thidx]; | 37 | return themes[thidx]; |
| 38 | } | 38 | } |
| 39 | 39 | ||
| 40 | - function setTheme(t) { | 40 | + function setTheme(t, force) { |
| 41 | var idx = themes.indexOf(t); | 41 | var idx = themes.indexOf(t); |
| 42 | - if (idx > -1 && idx !== thidx) { | 42 | + if (force || idx > -1 && idx !== thidx) { |
| 43 | thidx = idx; | 43 | thidx = idx; |
| 44 | + ps.setPrefs('theme', { idx: thidx }); | ||
| 44 | updateBodyClass(); | 45 | updateBodyClass(); |
| 45 | themeEvent('set'); | 46 | themeEvent('set'); |
| 46 | } | 47 | } |
| ... | @@ -49,6 +50,7 @@ | ... | @@ -49,6 +50,7 @@ |
| 49 | function toggleTheme() { | 50 | function toggleTheme() { |
| 50 | var i = thidx + 1; | 51 | var i = thidx + 1; |
| 51 | thidx = (i===themes.length) ? 0 : i; | 52 | thidx = (i===themes.length) ? 0 : i; |
| 53 | + ps.setPrefs('theme', { idx: thidx }); | ||
| 52 | updateBodyClass(); | 54 | updateBodyClass(); |
| 53 | themeEvent('toggle'); | 55 | themeEvent('toggle'); |
| 54 | return getTheme(); | 56 | return getTheme(); |
| ... | @@ -97,11 +99,11 @@ | ... | @@ -97,11 +99,11 @@ |
| 97 | } | 99 | } |
| 98 | 100 | ||
| 99 | angular.module('onosUtil') | 101 | angular.module('onosUtil') |
| 100 | - .factory('ThemeService', ['$log', 'FnService', | 102 | + .factory('ThemeService', ['$log', 'FnService', 'PrefsService', |
| 101 | - function (_$log_, _fs_) { | 103 | + function (_$log_, _fs_, _ps_) { |
| 102 | $log = _$log_; | 104 | $log = _$log_; |
| 103 | fs = _fs_; | 105 | fs = _fs_; |
| 104 | - thidx = 0; | 106 | + ps = _ps_; |
| 105 | 107 | ||
| 106 | return { | 108 | return { |
| 107 | init: init, | 109 | init: init, | ... | ... |
| ... | @@ -219,6 +219,10 @@ | ... | @@ -219,6 +219,10 @@ |
| 219 | } | 219 | } |
| 220 | } | 220 | } |
| 221 | 221 | ||
| 222 | + function isVisible() { | ||
| 223 | + return panel.isVisible(); | ||
| 224 | + } | ||
| 225 | + | ||
| 222 | return { | 226 | return { |
| 223 | addButton: addButton, | 227 | addButton: addButton, |
| 224 | addToggle: addToggle, | 228 | addToggle: addToggle, |
| ... | @@ -228,7 +232,8 @@ | ... | @@ -228,7 +232,8 @@ |
| 228 | 232 | ||
| 229 | show: show, | 233 | show: show, |
| 230 | hide: hide, | 234 | hide: hide, |
| 231 | - toggle: toggle | 235 | + toggle: toggle, |
| 236 | + isVisible: isVisible | ||
| 232 | }; | 237 | }; |
| 233 | } | 238 | } |
| 234 | 239 | ... | ... |
| ... | @@ -30,7 +30,7 @@ | ... | @@ -30,7 +30,7 @@ |
| 30 | 30 | ||
| 31 | // references to injected services | 31 | // references to injected services |
| 32 | var $scope, $log, $cookies, fs, ks, zs, gs, ms, sus, flash, wss, ps, th, | 32 | var $scope, $log, $cookies, fs, ks, zs, gs, ms, sus, flash, wss, ps, th, |
| 33 | - tds, tes, tfs, tps, tis, tss, tls, tts, tos, fltr, ttbs, tspr, ttip, tov; | 33 | + tds, t3s, tes, tfs, tps, tis, tss, tls, tts, tos, fltr, ttbs, tspr, ttip, tov; |
| 34 | 34 | ||
| 35 | // DOM elements | 35 | // DOM elements |
| 36 | var ovtopo, svg, defs, zoomLayer, mapG, spriteG, forceG, noDevsLayer; | 36 | var ovtopo, svg, defs, zoomLayer, mapG, spriteG, forceG, noDevsLayer; |
| ... | @@ -369,16 +369,14 @@ | ... | @@ -369,16 +369,14 @@ |
| 369 | 369 | ||
| 370 | function setUpMap($loc) { | 370 | function setUpMap($loc) { |
| 371 | var qp = $loc.search(), | 371 | var qp = $loc.search(), |
| 372 | - pr = ps.getPrefs('topo_mapid'), | 372 | + pr = ps.getPrefs('topo_mapid', { |
| 373 | - mi1 = qp.mapid, | 373 | + id: qp.mapid || 'usa', |
| 374 | - mi2 = pr && pr.id, | 374 | + scale: qp.mapscale || 1, |
| 375 | - mapId = mi1 || mi2 || 'usa', | 375 | + tint: qp.tint || 'off' |
| 376 | - ms1 = qp.mapscale, | 376 | + }), |
| 377 | - ms2 = pr && pr.scale, | 377 | + mapId = pr.id, |
| 378 | - mapScale = ms1 || ms2 || 1, | 378 | + mapScale = pr.scale, |
| 379 | - t1 = qp.tint, | 379 | + tint = pr.tint, |
| 380 | - t2 = pr && pr.tint, | ||
| 381 | - tint = t1 || t2 || 'off', | ||
| 382 | promise, | 380 | promise, |
| 383 | cfilter; | 381 | cfilter; |
| 384 | 382 | ||
| ... | @@ -441,8 +439,8 @@ | ... | @@ -441,8 +439,8 @@ |
| 441 | 439 | ||
| 442 | function setUpSprites($loc, tspr) { | 440 | function setUpSprites($loc, tspr) { |
| 443 | var s1 = $loc.search().sprites, | 441 | var s1 = $loc.search().sprites, |
| 444 | - s2 = ps.getPrefs('topo_sprites'), | 442 | + s2 = ps.getPrefs('topo_sprites', { id: s1 }), |
| 445 | - sprId = s1 || (s2 && s2.id); | 443 | + sprId = s2.id; |
| 446 | 444 | ||
| 447 | spriteG = zoomLayer.append ('g').attr('id', 'topo-sprites'); | 445 | spriteG = zoomLayer.append ('g').attr('id', 'topo-sprites'); |
| 448 | if (sprId) { | 446 | if (sprId) { |
| ... | @@ -463,7 +461,7 @@ | ... | @@ -463,7 +461,7 @@ |
| 463 | 461 | ||
| 464 | function restoreConfigFromPrefs() { | 462 | function restoreConfigFromPrefs() { |
| 465 | // NOTE: toolbar will have set this for us.. | 463 | // NOTE: toolbar will have set this for us.. |
| 466 | - prefsState = ps.asNumbers(ps.getPrefs('topo_prefs')); | 464 | + prefsState = ps.asNumbers(ps.getPrefs('topo_prefs', ttbs.defaultPrefs)); |
| 467 | 465 | ||
| 468 | $log.debug('TOPO- Prefs State:', prefsState); | 466 | $log.debug('TOPO- Prefs State:', prefsState); |
| 469 | 467 | ||
| ... | @@ -476,6 +474,7 @@ | ... | @@ -476,6 +474,7 @@ |
| 476 | togglePorts(prefsState.porthl); | 474 | togglePorts(prefsState.porthl); |
| 477 | toggleMap(prefsState.bg); | 475 | toggleMap(prefsState.bg); |
| 478 | toggleSprites(prefsState.spr); | 476 | toggleSprites(prefsState.spr); |
| 477 | + t3s.setDevLabIndex(prefsState.dlbls); | ||
| 479 | flash.enable(true); | 478 | flash.enable(true); |
| 480 | } | 479 | } |
| 481 | 480 | ||
| ... | @@ -484,7 +483,7 @@ | ... | @@ -484,7 +483,7 @@ |
| 484 | // have opened the websocket to the server; hence this extra function | 483 | // have opened the websocket to the server; hence this extra function |
| 485 | // invoked after tes.start() | 484 | // invoked after tes.start() |
| 486 | function restoreSummaryFromPrefs() { | 485 | function restoreSummaryFromPrefs() { |
| 487 | - prefsState = ps.asNumbers(ps.getPrefs('topo_prefs')); | 486 | + prefsState = ps.asNumbers(ps.getPrefs('topo_prefs', ttbs.defaultPrefs)); |
| 488 | $log.debug('TOPO- Prefs SUMMARY State:', prefsState.summary); | 487 | $log.debug('TOPO- Prefs SUMMARY State:', prefsState.summary); |
| 489 | 488 | ||
| 490 | flash.enable(false); | 489 | flash.enable(false); |
| ... | @@ -506,7 +505,7 @@ | ... | @@ -506,7 +505,7 @@ |
| 506 | '$cookies', 'FnService', 'MastService', 'KeyService', 'ZoomService', | 505 | '$cookies', 'FnService', 'MastService', 'KeyService', 'ZoomService', |
| 507 | 'GlyphService', 'MapService', 'SvgUtilService', 'FlashService', | 506 | 'GlyphService', 'MapService', 'SvgUtilService', 'FlashService', |
| 508 | 'WebSocketService', 'PrefsService', 'ThemeService', | 507 | 'WebSocketService', 'PrefsService', 'ThemeService', |
| 509 | - 'TopoDialogService', | 508 | + 'TopoDialogService', 'TopoD3Service', |
| 510 | 'TopoEventService', 'TopoForceService', 'TopoPanelService', | 509 | 'TopoEventService', 'TopoForceService', 'TopoPanelService', |
| 511 | 'TopoInstService', 'TopoSelectService', 'TopoLinkService', | 510 | 'TopoInstService', 'TopoSelectService', 'TopoLinkService', |
| 512 | 'TopoTrafficService', 'TopoObliqueService', 'TopoFilterService', | 511 | 'TopoTrafficService', 'TopoObliqueService', 'TopoFilterService', |
| ... | @@ -515,7 +514,7 @@ | ... | @@ -515,7 +514,7 @@ |
| 515 | 514 | ||
| 516 | function (_$scope_, _$log_, $loc, $timeout, _$cookies_, _fs_, mast, _ks_, | 515 | function (_$scope_, _$log_, $loc, $timeout, _$cookies_, _fs_, mast, _ks_, |
| 517 | _zs_, _gs_, _ms_, _sus_, _flash_, _wss_, _ps_, _th_, | 516 | _zs_, _gs_, _ms_, _sus_, _flash_, _wss_, _ps_, _th_, |
| 518 | - _tds_, _tes_, | 517 | + _tds_, _t3s_, _tes_, |
| 519 | _tfs_, _tps_, _tis_, _tss_, _tls_, _tts_, _tos_, _fltr_, | 518 | _tfs_, _tps_, _tis_, _tss_, _tls_, _tts_, _tos_, _fltr_, |
| 520 | _ttbs_, _tspr_, _ttip_, _tov_) { | 519 | _ttbs_, _tspr_, _ttip_, _tov_) { |
| 521 | var params = $loc.search(), | 520 | var params = $loc.search(), |
| ... | @@ -545,6 +544,7 @@ | ... | @@ -545,6 +544,7 @@ |
| 545 | ps = _ps_; | 544 | ps = _ps_; |
| 546 | th = _th_; | 545 | th = _th_; |
| 547 | tds = _tds_; | 546 | tds = _tds_; |
| 547 | + t3s = _t3s_; | ||
| 548 | tes = _tes_; | 548 | tes = _tes_; |
| 549 | tfs = _tfs_; | 549 | tfs = _tfs_; |
| 550 | // TODO: consider funnelling actions through TopoForceService... | 550 | // TODO: consider funnelling actions through TopoForceService... |
| ... | @@ -601,7 +601,7 @@ | ... | @@ -601,7 +601,7 @@ |
| 601 | setUpNoDevs(); | 601 | setUpNoDevs(); |
| 602 | setUpMap($loc).then( | 602 | setUpMap($loc).then( |
| 603 | function (proj) { | 603 | function (proj) { |
| 604 | - var z = ps.getPrefs('topo_zoom') || {tx:0, ty:0, sc:1}; | 604 | + var z = ps.getPrefs('topo_zoom', { tx:0, ty:0, sc:1 }); |
| 605 | zoomer.panZoom([z.tx, z.ty], z.sc); | 605 | zoomer.panZoom([z.tx, z.ty], z.sc); |
| 606 | $log.debug('** Zoom restored:', z); | 606 | $log.debug('** Zoom restored:', z); |
| 607 | 607 | ... | ... |
| ... | @@ -23,7 +23,7 @@ | ... | @@ -23,7 +23,7 @@ |
| 23 | 'use strict'; | 23 | 'use strict'; |
| 24 | 24 | ||
| 25 | // injected refs | 25 | // injected refs |
| 26 | - var $log, fs, sus, is, ts; | 26 | + var $log, fs, sus, is, ts, ps, ttbs; |
| 27 | 27 | ||
| 28 | // api to topoForce | 28 | // api to topoForce |
| 29 | var api; | 29 | var api; |
| ... | @@ -159,7 +159,7 @@ | ... | @@ -159,7 +159,7 @@ |
| 159 | // ==== | 159 | // ==== |
| 160 | 160 | ||
| 161 | function incDevLabIndex() { | 161 | function incDevLabIndex() { |
| 162 | - deviceLabelIndex = (deviceLabelIndex+1) % 3; | 162 | + setDevLabIndex(deviceLabelIndex+1); |
| 163 | switch(deviceLabelIndex) { | 163 | switch(deviceLabelIndex) { |
| 164 | case 0: return 'Hide device labels'; | 164 | case 0: return 'Hide device labels'; |
| 165 | case 1: return 'Show friendly device labels'; | 165 | case 1: return 'Show friendly device labels'; |
| ... | @@ -167,6 +167,13 @@ | ... | @@ -167,6 +167,13 @@ |
| 167 | } | 167 | } |
| 168 | } | 168 | } |
| 169 | 169 | ||
| 170 | + function setDevLabIndex(mode) { | ||
| 171 | + deviceLabelIndex = mode % 3; | ||
| 172 | + var p = ps.getPrefs('topo_prefs', ttbs.defaultPrefs); | ||
| 173 | + p.dlbls = deviceLabelIndex; | ||
| 174 | + ps.setPrefs('topo_prefs', p); | ||
| 175 | + } | ||
| 176 | + | ||
| 170 | // Returns the newly computed bounding box of the rectangle | 177 | // Returns the newly computed bounding box of the rectangle |
| 171 | function adjustRectToFitText(n) { | 178 | function adjustRectToFitText(n) { |
| 172 | var text = n.select('text'), | 179 | var text = n.select('text'), |
| ... | @@ -599,13 +606,16 @@ | ... | @@ -599,13 +606,16 @@ |
| 599 | angular.module('ovTopo') | 606 | angular.module('ovTopo') |
| 600 | .factory('TopoD3Service', | 607 | .factory('TopoD3Service', |
| 601 | ['$log', 'FnService', 'SvgUtilService', 'IconService', 'ThemeService', | 608 | ['$log', 'FnService', 'SvgUtilService', 'IconService', 'ThemeService', |
| 609 | + 'PrefsService', 'TopoToolbarService', | ||
| 602 | 610 | ||
| 603 | - function (_$log_, _fs_, _sus_, _is_, _ts_) { | 611 | + function (_$log_, _fs_, _sus_, _is_, _ts_, _ps_, _ttbs_) { |
| 604 | $log = _$log_; | 612 | $log = _$log_; |
| 605 | fs = _fs_; | 613 | fs = _fs_; |
| 606 | sus = _sus_; | 614 | sus = _sus_; |
| 607 | is = _is_; | 615 | is = _is_; |
| 608 | ts = _ts_; | 616 | ts = _ts_; |
| 617 | + ps = _ps_; | ||
| 618 | + ttbs = _ttbs_; | ||
| 609 | 619 | ||
| 610 | icfg = is.iconConfig(); | 620 | icfg = is.iconConfig(); |
| 611 | 621 | ||
| ... | @@ -620,6 +630,7 @@ | ... | @@ -620,6 +630,7 @@ |
| 620 | destroyD3: destroyD3, | 630 | destroyD3: destroyD3, |
| 621 | 631 | ||
| 622 | incDevLabIndex: incDevLabIndex, | 632 | incDevLabIndex: incDevLabIndex, |
| 633 | + setDevLabIndex: setDevLabIndex, | ||
| 623 | adjustRectToFitText: adjustRectToFitText, | 634 | adjustRectToFitText: adjustRectToFitText, |
| 624 | hostLabel: hostLabel, | 635 | hostLabel: hostLabel, |
| 625 | deviceLabel: deviceLabel, | 636 | deviceLabel: deviceLabel, | ... | ... |
| ... | @@ -529,6 +529,7 @@ | ... | @@ -529,6 +529,7 @@ |
| 529 | 529 | ||
| 530 | showSummary: showSummary, | 530 | showSummary: showSummary, |
| 531 | toggleSummary: toggleSummary, | 531 | toggleSummary: toggleSummary, |
| 532 | + hideSummary: hideSummaryPanel, | ||
| 532 | 533 | ||
| 533 | toggleUseDetailsFlag: toggleUseDetailsFlag, | 534 | toggleUseDetailsFlag: toggleUseDetailsFlag, |
| 534 | displaySingle: displaySingle, | 535 | displaySingle: displaySingle, |
| ... | @@ -538,8 +539,6 @@ | ... | @@ -538,8 +539,6 @@ |
| 538 | displaySomething: displaySomething, | 539 | displaySomething: displaySomething, |
| 539 | addAction: addAction, | 540 | addAction: addAction, |
| 540 | 541 | ||
| 541 | - hideSummaryPanel: hideSummaryPanel, | ||
| 542 | - | ||
| 543 | detailVisible: function () { return detail.panel().isVisible(); }, | 542 | detailVisible: function () { return detail.panel().isVisible(); }, |
| 544 | summaryVisible: function () { return summary.panel().isVisible(); } | 543 | summaryVisible: function () { return summary.panel().isVisible(); } |
| 545 | }; | 544 | }; | ... | ... |
| ... | @@ -70,11 +70,12 @@ | ... | @@ -70,11 +70,12 @@ |
| 70 | 70 | ||
| 71 | // initial toggle state: default settings and tag to key mapping | 71 | // initial toggle state: default settings and tag to key mapping |
| 72 | var defaultPrefsState = { | 72 | var defaultPrefsState = { |
| 73 | - summary: 1, | ||
| 74 | insts: 1, | 73 | insts: 1, |
| 74 | + summary: 1, | ||
| 75 | detail: 1, | 75 | detail: 1, |
| 76 | hosts: 0, | 76 | hosts: 0, |
| 77 | offdev: 1, | 77 | offdev: 1, |
| 78 | + dlbls: 0, | ||
| 78 | porthl: 1, | 79 | porthl: 1, |
| 79 | bg: 0, | 80 | bg: 0, |
| 80 | spr: 0, | 81 | spr: 0, |
| ... | @@ -104,7 +105,7 @@ | ... | @@ -104,7 +105,7 @@ |
| 104 | } | 105 | } |
| 105 | 106 | ||
| 106 | function setInitToggleState() { | 107 | function setInitToggleState() { |
| 107 | - cachedState = ps.asNumbers(ps.getPrefs(cooktag)); | 108 | + cachedState = ps.asNumbers(ps.getPrefs(cooktag, defaultPrefsState)); |
| 108 | $log.debug('TOOLBAR---- read prefs state:', cachedState); | 109 | $log.debug('TOOLBAR---- read prefs state:', cachedState); |
| 109 | 110 | ||
| 110 | if (!cachedState) { | 111 | if (!cachedState) { |
| ... | @@ -264,6 +265,9 @@ | ... | @@ -264,6 +265,9 @@ |
| 264 | 265 | ||
| 265 | function toggleToolbar() { | 266 | function toggleToolbar() { |
| 266 | toolbar.toggle(); | 267 | toolbar.toggle(); |
| 268 | + var prefs = ps.getPrefs(cooktag, defaultPrefsState); | ||
| 269 | + prefs.toolbar = !prefs.toolbar; | ||
| 270 | + ps.setPrefs('topo_prefs', prefs); | ||
| 267 | } | 271 | } |
| 268 | 272 | ||
| 269 | function setDefaultOverlay() { | 273 | function setDefaultOverlay() { |
| ... | @@ -298,6 +302,7 @@ | ... | @@ -298,6 +302,7 @@ |
| 298 | keyListener: keyListener, | 302 | keyListener: keyListener, |
| 299 | toggleToolbar: toggleToolbar, | 303 | toggleToolbar: toggleToolbar, |
| 300 | setDefaultOverlay: setDefaultOverlay, | 304 | setDefaultOverlay: setDefaultOverlay, |
| 305 | + defaultPrefs: defaultPrefsState, | ||
| 301 | fnkey: fnkey | 306 | fnkey: fnkey |
| 302 | }; | 307 | }; |
| 303 | }]); | 308 | }]); | ... | ... |
| ... | @@ -38,6 +38,9 @@ | ... | @@ -38,6 +38,9 @@ |
| 38 | <script src="tp/Chart.min.js"></script> | 38 | <script src="tp/Chart.min.js"></script> |
| 39 | <script src="tp/angular-chart.min.js"></script> | 39 | <script src="tp/angular-chart.min.js"></script> |
| 40 | 40 | ||
| 41 | + <!-- {INJECTED-USER-START} --> | ||
| 42 | + <!-- {INJECTED-USER-END} --> | ||
| 43 | + | ||
| 41 | <!-- ONOS UI Framework included here --> | 44 | <!-- ONOS UI Framework included here --> |
| 42 | <!-- TODO: use a single catenated-minified file here --> | 45 | <!-- TODO: use a single catenated-minified file here --> |
| 43 | <script src="onos.js"></script> | 46 | <script src="onos.js"></script> | ... | ... |
-
Please register or login to post a comment