Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions cmd/bridge/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ func main() {
fLoadTestFactor := fs.Int("load-test-factor", 0, "DEV ONLY. The factor used to multiply k8s API list responses for load testing purposes.")

fDevCatalogCategories := fs.String("developer-catalog-categories", "", "Allow catalog categories customization. (JSON as string)")
fUserSettingsLocation := fs.String("user-settings-location", "configmap", "DEV ONLY. Define where the user settings should be stored. (configmap | localstorage).")

if err := serverconfig.Parse(fs, os.Args[1:], "BRIDGE"); err != nil {
fmt.Fprintln(os.Stderr, err.Error())
Expand Down Expand Up @@ -229,6 +230,7 @@ func main() {
LoadTestFactor: *fLoadTestFactor,
InactivityTimeout: *fInactivityTimeout,
DevCatalogCategories: *fDevCatalogCategories,
UserSettingsLocation: *fUserSettingsLocation,
}

// if !in-cluster (dev) we should not pass these values to the frontend
Expand Down
3 changes: 3 additions & 0 deletions contrib/oc-environment.sh
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,7 @@ export BRIDGE_K8S_AUTH
BRIDGE_K8S_AUTH_BEARER_TOKEN=$(oc whoami --show-token)
export BRIDGE_K8S_AUTH_BEARER_TOKEN

BRIDGE_USER_SETTINGS_LOCATION="localstorage"
export BRIDGE_USER_SETTINGS_LOCATION

echo "Using $BRIDGE_K8S_MODE_OFF_CLUSTER_ENDPOINT"
1 change: 1 addition & 0 deletions frontend/@types/console/declarations.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ declare interface Window {
GOOS: string;
graphqlBaseURL: string;
developerCatalogCategories: string;
userSettingsLocation: string;
};
windowError?: string;
__REDUX_DEVTOOLS_EXTENSION_COMPOSE__?: Function;
Expand Down
30 changes: 21 additions & 9 deletions frontend/packages/console-shared/src/hooks/useUserSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ import {
USER_SETTING_CONFIGMAP_NAMESPACE,
} from '../utils/user-settings';

const alwaysUseFallbackLocalStorage = window.SERVER_FLAGS.userSettingsLocation === 'localstorage';
if (alwaysUseFallbackLocalStorage) {
// eslint-disable-next-line no-console
console.info('user-settings will be stored in localstorage instead of configmap.');
}

const useCounterRef = (initialValue: number = 0): [boolean, () => void, () => void] => {
const counterRef = React.useRef<number>(initialValue);
const increment = React.useCallback(() => {
Expand All @@ -32,19 +38,22 @@ export const useUserSettings = <T>(
defaultValue?: T,
sync: boolean = false,
): [T, React.Dispatch<React.SetStateAction<T>>, boolean] => {
const defaultValueRef = React.useRef<T>(defaultValue);
const keyRef = React.useRef<string>(key);
const defaultValueRef = React.useRef<T>(defaultValue);
const [isRequestPending, increaseRequest, decreaseRequest] = useCounterRef();
const userUid = useSelector(
(state: RootState) => state.UI.get('user')?.metadata?.uid ?? 'kubeadmin',
);
const configMapResource = React.useMemo(
() => ({
kind: ConfigMapModel.kind,
namespace: USER_SETTING_CONFIGMAP_NAMESPACE,
isList: false,
name: `user-settings-${userUid}`,
}),
() =>
alwaysUseFallbackLocalStorage
? null
: {
kind: ConfigMapModel.kind,
namespace: USER_SETTING_CONFIGMAP_NAMESPACE,
isList: false,
name: `user-settings-${userUid}`,
},
[userUid],
);
const [cfData, cfLoaded, cfLoadError] = useK8sWatchResource<K8sResourceKind>(configMapResource);
Expand All @@ -53,11 +62,14 @@ export const useUserSettings = <T>(
settingsRef.current = settings;
const [loaded, setLoaded] = React.useState(false);

const [fallbackLocalStorage, setFallbackLocalStorage] = React.useState<boolean>(false);
const [fallbackLocalStorage, setFallbackLocalStorage] = React.useState<boolean>(
alwaysUseFallbackLocalStorage,
);
const [lsData, setLsDataCallback] = useUserSettingsLocalStorage(
alwaysUseFallbackLocalStorage ? 'console-user-settings' : `console-user-settings-${userUid}`,
keyRef.current,
defaultValueRef.current,
fallbackLocalStorage,
fallbackLocalStorage && sync,
);

React.useEffect(() => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,28 +1,18 @@
import * as React from 'react';
// FIXME upgrading redux types is causing many errors at this time
// eslint-disable-next-line @typescript-eslint/ban-ts-ignore
// @ts-ignore
import { useSelector } from 'react-redux';
import { RootState } from '@console/internal/redux';
import { deseralizeData, seralizeData } from '../utils/user-settings';

const CONFIGMAP_LS_KEY = 'console-user-settings';

export const useUserSettingsLocalStorage = <T>(
key: string,
localStorageKey: string,
userSettingsKey: string,
defaultValue: T,
watch: boolean = true,
sync: boolean = false,
): [T, React.Dispatch<React.SetStateAction<T>>] => {
const keyRef = React.useRef(key);
const keyRef = React.useRef(userSettingsKey);
const defaultValueRef = React.useRef(defaultValue);
const userUid = useSelector(
(state: RootState) => state.UI.get('user')?.metadata?.uid ?? 'kubeadmin',
);
const storageConfigNameRef = React.useRef(`${CONFIGMAP_LS_KEY}-${userUid}`);
const [lsData, setLsData] = React.useState(() => {
const valueInLocalStorage =
localStorage.getItem(storageConfigNameRef.current) !== null &&
deseralizeData(localStorage.getItem(storageConfigNameRef.current));
localStorage.getItem(localStorageKey) !== null &&
deseralizeData(localStorage.getItem(localStorageKey));
return valueInLocalStorage?.hasOwnProperty(keyRef.current) &&
valueInLocalStorage[keyRef.current]
? valueInLocalStorage[keyRef.current]
Expand All @@ -32,38 +22,36 @@ export const useUserSettingsLocalStorage = <T>(
lsDataRef.current = lsData;

const localStorageUpdated = React.useCallback(
(ev: StorageEvent) => {
if (ev.key === storageConfigNameRef.current) {
const lsConfigMapData = deseralizeData(localStorage.getItem(storageConfigNameRef.current));
if (
lsData !== undefined &&
lsConfigMapData?.[keyRef.current] &&
seralizeData(lsConfigMapData[keyRef.current]) !== seralizeData(lsData)
) {
setLsData(lsConfigMapData[keyRef.current]);
(event: StorageEvent) => {
if (event.key === localStorageKey) {
const lsConfigMapData = deseralizeData(event.newValue);
const newData = lsConfigMapData?.[keyRef.current];

if (newData && seralizeData(newData) !== seralizeData(lsDataRef.current)) {
setLsData(newData);
}
}
},
[lsData],
[localStorageKey],
);

React.useEffect(() => {
if (watch) {
if (sync) {
window.addEventListener('storage', localStorageUpdated);
}
return () => {
if (watch) {
if (sync) {
window.removeEventListener('storage', localStorageUpdated);
}
};
}, [localStorageUpdated, watch]);
}, [localStorageUpdated, sync]);

const updateLsData = React.useCallback<React.Dispatch<React.SetStateAction<T>>>(
(action: React.SetStateAction<T>) => {
const previousData = lsDataRef.current;
const data =
typeof action === 'function' ? (action as (prevState: T) => T)(previousData) : action;
const lsConfigMapData =
deseralizeData(localStorage.getItem(storageConfigNameRef.current)) ?? {};
const lsConfigMapData = deseralizeData(localStorage.getItem(localStorageKey)) ?? {};
if (
data !== undefined &&
seralizeData(data) !== seralizeData(lsConfigMapData?.[keyRef.current])
Expand All @@ -75,10 +63,26 @@ export const useUserSettingsLocalStorage = <T>(
[keyRef.current]: data,
},
};
localStorage.setItem(storageConfigNameRef.current, seralizeData(dataToUpdate));
const newValue = seralizeData(dataToUpdate);

// create a storage event to dispatch locally since browser windows do not fire the
// storage event if the change originated from the current window
const event = new StorageEvent('storage', {
storageArea: localStorage,
key: localStorageKey,
newValue,
oldValue: localStorage.getItem(localStorageKey),
url: window.location.toString(),
});

// update local storage
localStorage.setItem(localStorageKey, newValue);

// dispatch local event
window.dispatchEvent(event);
}
},
[],
[localStorageKey],
);

return [lsData, updateLsData];
Expand Down
3 changes: 3 additions & 0 deletions pkg/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ type jsGlobals struct {
GOOS string `json:"GOOS"`
GraphQLBaseURL string `json:"graphqlBaseURL"`
DevCatalogCategories string `json:"developerCatalogCategories"`
UserSettingsLocation string `json:"userSettingsLocation"`
}

type Server struct {
Expand Down Expand Up @@ -134,6 +135,7 @@ type Server struct {
PrometheusPublicURL *url.URL
ThanosPublicURL *url.URL
DevCatalogCategories string
UserSettingsLocation string
}

func (s *Server) authDisabled() bool {
Expand Down Expand Up @@ -489,6 +491,7 @@ func (s *Server) indexHandler(w http.ResponseWriter, r *http.Request) {
LoadTestFactor: s.LoadTestFactor,
GraphQLBaseURL: proxy.SingleJoiningSlash(s.BaseURL.Path, graphQLEndpoint),
DevCatalogCategories: s.DevCatalogCategories,
UserSettingsLocation: s.UserSettingsLocation,
}

if !s.authDisabled() {
Expand Down
5 changes: 5 additions & 0 deletions pkg/serverconfig/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,17 @@ import (
"flag"
"fmt"
"strings"

"github.com/openshift/console/pkg/bridge"
)

func Validate(fs *flag.FlagSet) error {
if _, err := validateDeveloperCatalogCategories(fs.Lookup("developer-catalog-categories").Value.String()); err != nil {
return err
}

bridge.ValidateFlagIs("user-settings-location", fs.Lookup("user-settings-location").Value.String(), "configmap", "localstorage")

return nil
}

Expand Down