mirror of
https://github.com/portainer/portainer.git
synced 2025-08-07 23:05:26 +02:00
feat(auth): save jwt in cookie [EE-5864] (#10527)
This commit is contained in:
parent
ecce501cf3
commit
436da01bce
51 changed files with 679 additions and 312 deletions
|
@ -1,7 +1,7 @@
|
|||
import $ from 'jquery';
|
||||
|
||||
/* @ngInject */
|
||||
export function onStartupAngular($rootScope, $state, LocalStorage, cfpLoadingBar, $transitions, HttpRequestHelper) {
|
||||
export function onStartupAngular($rootScope, $state, cfpLoadingBar, $transitions, HttpRequestHelper) {
|
||||
$rootScope.$state = $state;
|
||||
|
||||
// Workaround to prevent the loading bar from going backward
|
||||
|
@ -23,6 +23,7 @@ export function onStartupAngular($rootScope, $state, LocalStorage, cfpLoadingBar
|
|||
if (type && hasNoContentType) {
|
||||
jqXhr.setRequestHeader('Content-Type', 'application/json');
|
||||
}
|
||||
jqXhr.setRequestHeader('Authorization', 'Bearer ' + LocalStorage.getJWT());
|
||||
const csrfCookie = window.cookieStore.get('_gorilla_csrf');
|
||||
jqXhr.setRequestHeader('X-CSRF-Token', csrfCookie);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,24 +1,16 @@
|
|||
import { Terminal } from 'xterm';
|
||||
import * as fit from 'xterm/lib/addons/fit/fit';
|
||||
import { csrfInterceptor, csrfTokenReaderInterceptorAngular } from './portainer/services/csrf';
|
||||
import { agentInterceptor } from './portainer/services/axios';
|
||||
|
||||
/* @ngInject */
|
||||
export function configApp($urlRouterProvider, $httpProvider, localStorageServiceProvider, jwtOptionsProvider, $uibTooltipProvider, $compileProvider, cfpLoadingBarProvider) {
|
||||
export function configApp($urlRouterProvider, $httpProvider, localStorageServiceProvider, $uibTooltipProvider, $compileProvider, cfpLoadingBarProvider) {
|
||||
if (process.env.NODE_ENV === 'testing') {
|
||||
$compileProvider.debugInfoEnabled(false);
|
||||
}
|
||||
|
||||
localStorageServiceProvider.setPrefix('portainer');
|
||||
|
||||
jwtOptionsProvider.config({
|
||||
tokenGetter: /* @ngInject */ function tokenGetter(LocalStorage) {
|
||||
return LocalStorage.getJWT();
|
||||
},
|
||||
whiteListedDomains: ['localhost'],
|
||||
});
|
||||
|
||||
$httpProvider.interceptors.push('jwtInterceptor');
|
||||
|
||||
$httpProvider.defaults.headers.post['Content-Type'] = 'application/json';
|
||||
$httpProvider.defaults.headers.put['Content-Type'] = 'application/json';
|
||||
$httpProvider.defaults.headers.patch['Content-Type'] = 'application/json';
|
||||
|
@ -27,6 +19,11 @@ export function configApp($urlRouterProvider, $httpProvider, localStorageService
|
|||
request: agentInterceptor,
|
||||
}));
|
||||
|
||||
$httpProvider.interceptors.push(() => ({
|
||||
response: csrfTokenReaderInterceptorAngular,
|
||||
request: csrfInterceptor,
|
||||
}));
|
||||
|
||||
Terminal.applyAddon(fit);
|
||||
|
||||
$uibTooltipProvider.setTriggers({
|
||||
|
|
|
@ -168,8 +168,6 @@ angular.module('portainer.docker').controller('ContainerConsoleController', [
|
|||
url += '&nodeName=' + $transition$.params().nodeName;
|
||||
}
|
||||
|
||||
url += '&token=' + LocalStorage.getJWT();
|
||||
|
||||
if (url.indexOf('https') > -1) {
|
||||
url = url.replace('https://', 'wss://');
|
||||
} else {
|
||||
|
|
|
@ -34,7 +34,6 @@ angular
|
|||
'ngResource',
|
||||
'angularUtils.directives.dirPagination',
|
||||
'LocalStorageModule',
|
||||
'angular-jwt',
|
||||
'angular-json-tree',
|
||||
'angular-loading-bar',
|
||||
'angular-clipboard',
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
import _ from 'lodash-es';
|
||||
|
||||
import featureFlagModule from '@/react/portainer/feature-flags';
|
||||
|
||||
import './rbac';
|
||||
|
@ -12,35 +10,8 @@ import { reactModule } from './react';
|
|||
import { sidebarModule } from './react/views/sidebar';
|
||||
import environmentsModule from './environments';
|
||||
import { helpersModule } from './helpers';
|
||||
import { AXIOS_UNAUTHENTICATED } from './services/axios';
|
||||
|
||||
async function initAuthentication(authManager, Authentication, $rootScope, $state) {
|
||||
authManager.checkAuthOnRefresh();
|
||||
|
||||
function handleUnauthenticated(data, performReload) {
|
||||
if (!_.includes(data.config.url, '/v2/') && !_.includes(data.config.url, '/api/v4/') && isTransitionRequiresAuthentication($state.transition)) {
|
||||
$state.go('portainer.logout', { error: 'Your session has expired' });
|
||||
if (performReload) {
|
||||
window.location.reload();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The unauthenticated event is broadcasted by the jwtInterceptor when
|
||||
// hitting a 401. We're using this instead of the usual combination of
|
||||
// authManager.redirectWhenUnauthenticated() + unauthenticatedRedirector
|
||||
// to have more controls on which URL should trigger the unauthenticated state.
|
||||
$rootScope.$on('unauthenticated', function (event, data) {
|
||||
handleUnauthenticated(data, true);
|
||||
});
|
||||
|
||||
// the AXIOS_UNAUTHENTICATED event is emitted by axios when a request returns with a 401 code
|
||||
// the event contains the entire AxiosError in detail.err
|
||||
window.addEventListener(AXIOS_UNAUTHENTICATED, (event) => {
|
||||
const data = event.detail.err;
|
||||
handleUnauthenticated(data);
|
||||
});
|
||||
|
||||
async function initAuthentication(Authentication) {
|
||||
return await Authentication.init();
|
||||
}
|
||||
|
||||
|
@ -65,14 +36,14 @@ angular
|
|||
var root = {
|
||||
name: 'root',
|
||||
abstract: true,
|
||||
onEnter: /* @ngInject */ function onEnter($async, StateManager, Authentication, Notifications, authManager, $rootScope, $state) {
|
||||
onEnter: /* @ngInject */ function onEnter($async, StateManager, Authentication, Notifications, $state) {
|
||||
return $async(async () => {
|
||||
const appState = StateManager.getState();
|
||||
if (!appState.loading) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const loggedIn = await initAuthentication(authManager, Authentication, $rootScope, $state);
|
||||
const loggedIn = await initAuthentication(Authentication);
|
||||
await StateManager.initialize();
|
||||
if (!loggedIn && isTransitionRequiresAuthentication($state.transition)) {
|
||||
$state.go('portainer.logout');
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { getCurrentUser } from '../users/queries/useLoadCurrentUser';
|
||||
import { clear as clearSessionStorage } from './session-storage';
|
||||
|
||||
const DEFAULT_USER = 'admin';
|
||||
|
@ -7,13 +8,11 @@ angular.module('portainer.app').factory('Authentication', [
|
|||
'$async',
|
||||
'Auth',
|
||||
'OAuth',
|
||||
'jwtHelper',
|
||||
'LocalStorage',
|
||||
'StateManager',
|
||||
'EndpointProvider',
|
||||
'UserService',
|
||||
'ThemeManager',
|
||||
function AuthenticationFactory($async, Auth, OAuth, jwtHelper, LocalStorage, StateManager, EndpointProvider, UserService, ThemeManager) {
|
||||
function AuthenticationFactory($async, Auth, OAuth, LocalStorage, StateManager, EndpointProvider, ThemeManager) {
|
||||
'use strict';
|
||||
|
||||
var service = {};
|
||||
|
@ -29,11 +28,12 @@ angular.module('portainer.app').factory('Authentication', [
|
|||
|
||||
async function initAsync() {
|
||||
try {
|
||||
const jwt = LocalStorage.getJWT();
|
||||
if (!jwt || jwtHelper.isTokenExpired(jwt)) {
|
||||
return tryAutoLoginExtension();
|
||||
const userId = LocalStorage.getUserId();
|
||||
if (userId && user.ID === userId) {
|
||||
return true;
|
||||
}
|
||||
await setUser(jwt);
|
||||
await tryAutoLoginExtension();
|
||||
await loadUserData();
|
||||
return true;
|
||||
} catch (error) {
|
||||
return tryAutoLoginExtension();
|
||||
|
@ -62,16 +62,8 @@ angular.module('portainer.app').factory('Authentication', [
|
|||
}
|
||||
|
||||
async function OAuthLoginAsync(code) {
|
||||
const response = await OAuth.validate({ code: code }).$promise;
|
||||
const jwt = setJWTFromResponse(response);
|
||||
await setUser(jwt);
|
||||
}
|
||||
|
||||
function setJWTFromResponse(response) {
|
||||
const jwt = response.jwt;
|
||||
LocalStorage.storeJWT(jwt);
|
||||
|
||||
return response.jwt;
|
||||
await OAuth.validate({ code: code }).$promise;
|
||||
await loadUserData();
|
||||
}
|
||||
|
||||
function OAuthLogin(code) {
|
||||
|
@ -79,9 +71,8 @@ angular.module('portainer.app').factory('Authentication', [
|
|||
}
|
||||
|
||||
async function loginAsync(username, password) {
|
||||
const response = await Auth.login({ username: username, password: password }).$promise;
|
||||
const jwt = setJWTFromResponse(response);
|
||||
await setUser(jwt);
|
||||
await Auth.login({ username: username, password: password }).$promise;
|
||||
await loadUserData();
|
||||
}
|
||||
|
||||
function login(username, password) {
|
||||
|
@ -89,33 +80,31 @@ angular.module('portainer.app').factory('Authentication', [
|
|||
}
|
||||
|
||||
function isAuthenticated() {
|
||||
var jwt = LocalStorage.getJWT();
|
||||
return !!jwt && !jwtHelper.isTokenExpired(jwt);
|
||||
return !!user.ID;
|
||||
}
|
||||
|
||||
function getUserDetails() {
|
||||
return user;
|
||||
}
|
||||
|
||||
async function setUserTheme() {
|
||||
const data = await UserService.user(user.ID);
|
||||
async function loadUserData() {
|
||||
const userData = await getCurrentUser();
|
||||
user.username = userData.Username;
|
||||
user.ID = userData.Id;
|
||||
user.role = userData.Role;
|
||||
user.forceChangePassword = userData.forceChangePassword;
|
||||
user.endpointAuthorizations = userData.EndpointAuthorizations;
|
||||
user.portainerAuthorizations = userData.PortainerAuthorizations;
|
||||
|
||||
// Initialize user theme base on UserTheme from database
|
||||
const userTheme = data.ThemeSettings ? data.ThemeSettings.color : 'auto';
|
||||
const userTheme = userData.ThemeSettings ? userData.ThemeSettings.color : 'auto';
|
||||
if (userTheme === 'auto' || !userTheme) {
|
||||
ThemeManager.autoTheme();
|
||||
} else {
|
||||
ThemeManager.setTheme(userTheme);
|
||||
}
|
||||
}
|
||||
|
||||
async function setUser(jwt) {
|
||||
var tokenPayload = jwtHelper.decodeToken(jwt);
|
||||
user.username = tokenPayload.username;
|
||||
user.ID = tokenPayload.id;
|
||||
user.role = tokenPayload.role;
|
||||
user.forceChangePassword = tokenPayload.forceChangePassword;
|
||||
await setUserTheme();
|
||||
LocalStorage.storeUserId(userData.Id);
|
||||
}
|
||||
|
||||
function tryAutoLoginExtension() {
|
||||
|
|
|
@ -3,7 +3,6 @@ import { loadProgressBar } from 'axios-progress-bar';
|
|||
|
||||
import 'axios-progress-bar/dist/nprogress.css';
|
||||
import PortainerError from '@/portainer/error';
|
||||
import { get as localStorageGet } from '@/react/hooks/useLocalStorage';
|
||||
|
||||
import {
|
||||
portainerAgentManagerOperation,
|
||||
|
@ -16,17 +15,6 @@ loadProgressBar(undefined, axios);
|
|||
|
||||
export default axios;
|
||||
|
||||
axios.interceptors.request.use(async (config) => {
|
||||
const newConfig = { headers: config.headers || {}, ...config };
|
||||
|
||||
const jwt = localStorageGet('JWT', '');
|
||||
if (jwt) {
|
||||
newConfig.headers.Authorization = `Bearer ${jwt}`;
|
||||
}
|
||||
|
||||
return newConfig;
|
||||
});
|
||||
|
||||
export const agentTargetHeader = 'X-PortainerAgent-Target';
|
||||
|
||||
export function agentInterceptor(config: AxiosRequestConfig) {
|
||||
|
@ -49,7 +37,33 @@ export function agentInterceptor(config: AxiosRequestConfig) {
|
|||
|
||||
axios.interceptors.request.use(agentInterceptor);
|
||||
|
||||
export const AXIOS_UNAUTHENTICATED = '__axios__unauthenticated__';
|
||||
axios.interceptors.response.use(undefined, (error) => {
|
||||
if (
|
||||
error.response?.status === 401 &&
|
||||
!error.config.url.includes('/v2/') &&
|
||||
!error.config.url.includes('/api/v4/') &&
|
||||
isTransitionRequiresAuthentication()
|
||||
) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('Unauthorized request, logging out');
|
||||
window.location.hash = '/logout';
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
return Promise.reject(error);
|
||||
});
|
||||
|
||||
const UNAUTHENTICATED_ROUTES = [
|
||||
'/logout',
|
||||
'/internal-auth',
|
||||
'/auth',
|
||||
'/init/admin',
|
||||
];
|
||||
function isTransitionRequiresAuthentication() {
|
||||
return !UNAUTHENTICATED_ROUTES.some((route) =>
|
||||
window.location.hash.includes(route)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an Axios error and returns a PortainerError.
|
||||
|
@ -74,16 +88,6 @@ export function parseAxiosError(
|
|||
} else {
|
||||
resultMsg = msg || details;
|
||||
}
|
||||
// dispatch an event for unauthorized errors that AngularJS can catch
|
||||
if (err.response?.status === 401) {
|
||||
dispatchEvent(
|
||||
new CustomEvent(AXIOS_UNAUTHENTICATED, {
|
||||
detail: {
|
||||
err,
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return new PortainerError(resultMsg, resultErr);
|
||||
|
|
37
app/portainer/services/csrf.ts
Normal file
37
app/portainer/services/csrf.ts
Normal file
|
@ -0,0 +1,37 @@
|
|||
import { AxiosRequestConfig, AxiosResponse } from 'axios';
|
||||
import { IHttpResponse } from 'angular';
|
||||
|
||||
import axios from './axios';
|
||||
|
||||
axios.interceptors.response.use(csrfTokenReaderInterceptor);
|
||||
axios.interceptors.request.use(csrfInterceptor);
|
||||
|
||||
let csrfToken: string | null = null;
|
||||
|
||||
export function csrfTokenReaderInterceptor(config: AxiosResponse) {
|
||||
const csrfTokenHeader = config.headers['x-csrf-token'];
|
||||
if (csrfTokenHeader) {
|
||||
csrfToken = csrfTokenHeader;
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
export function csrfTokenReaderInterceptorAngular(
|
||||
config: IHttpResponse<unknown>
|
||||
) {
|
||||
const csrfTokenHeader = config.headers('x-csrf-token');
|
||||
if (csrfTokenHeader) {
|
||||
csrfToken = csrfTokenHeader;
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
export function csrfInterceptor(config: AxiosRequestConfig) {
|
||||
if (!csrfToken) {
|
||||
return config;
|
||||
}
|
||||
|
||||
const newConfig = { headers: config.headers || {}, ...config };
|
||||
newConfig.headers['X-CSRF-Token'] = csrfToken;
|
||||
return newConfig;
|
||||
}
|
|
@ -29,14 +29,14 @@ angular.module('portainer.app').factory('LocalStorage', [
|
|||
getUIState: function () {
|
||||
return localStorageService.get('UI_STATE');
|
||||
},
|
||||
storeJWT: function (jwt) {
|
||||
localStorageService.set('JWT', jwt);
|
||||
getUserId() {
|
||||
localStorageService.get('USER_ID');
|
||||
},
|
||||
getJWT: function () {
|
||||
return localStorageService.get('JWT');
|
||||
storeUserId: function (userId) {
|
||||
localStorageService.set('USER_ID', userId);
|
||||
},
|
||||
deleteJWT: function () {
|
||||
localStorageService.remove('JWT');
|
||||
deleteUserId: function () {
|
||||
localStorageService.remove('USER_ID');
|
||||
},
|
||||
storePaginationLimit: function (key, count) {
|
||||
localStorageService.set('datatable_pagination_' + key, count);
|
||||
|
@ -119,7 +119,7 @@ angular.module('portainer.app').factory('LocalStorage', [
|
|||
localStorageService.clearAll();
|
||||
},
|
||||
cleanAuthData() {
|
||||
localStorageService.remove('JWT', 'APPLICATION_STATE', 'LOGIN_STATE_UUID', 'ALLOWED_NAMESPACES');
|
||||
localStorageService.remove('USER_ID', 'APPLICATION_STATE', 'LOGIN_STATE_UUID', 'ALLOWED_NAMESPACES');
|
||||
},
|
||||
storeKubernetesSummaryToggle(value) {
|
||||
localStorageService.set('kubernetes_summary_expanded', value);
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import _ from 'lodash';
|
||||
import toastr from 'toastr';
|
||||
import sanitize from 'sanitize-html';
|
||||
import jwtDecode from 'jwt-decode';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
import { get as localStorageGet } from '@/react/hooks/useLocalStorage';
|
||||
|
@ -109,11 +108,8 @@ function saveNotification(title: string, text: string, type: string) {
|
|||
type,
|
||||
timeStamp: new Date(),
|
||||
};
|
||||
const jwt = localStorageGet('JWT', '');
|
||||
if (jwt !== '') {
|
||||
const { id } = jwtDecode(jwt) as { id: number };
|
||||
if (id) {
|
||||
addNotification(id, notif);
|
||||
}
|
||||
const userId = localStorageGet('USER_ID', '');
|
||||
if (userId !== '') {
|
||||
addNotification(userId, notif);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,4 +3,5 @@ import { UserId } from '../types';
|
|||
export const queryKeys = {
|
||||
base: () => ['users'] as const,
|
||||
user: (id: UserId) => [...queryKeys.base(), id] as const,
|
||||
me: () => [...queryKeys.base(), 'me'] as const,
|
||||
};
|
||||
|
|
32
app/portainer/users/queries/useLoadCurrentUser.ts
Normal file
32
app/portainer/users/queries/useLoadCurrentUser.ts
Normal file
|
@ -0,0 +1,32 @@
|
|||
import { useQuery } from 'react-query';
|
||||
|
||||
import axios, { parseAxiosError } from '@/portainer/services/axios';
|
||||
import { withError } from '@/react-tools/react-query';
|
||||
|
||||
import { buildUrl } from '../user.service';
|
||||
import { User } from '../types';
|
||||
|
||||
import { queryKeys } from './queryKeys';
|
||||
|
||||
interface CurrentUserResponse extends User {
|
||||
forceChangePassword: boolean;
|
||||
}
|
||||
|
||||
export function useLoadCurrentUser({ staleTime }: { staleTime?: number } = {}) {
|
||||
return useQuery(queryKeys.me(), () => getCurrentUser(), {
|
||||
...withError('Unable to retrieve user details'),
|
||||
staleTime,
|
||||
});
|
||||
}
|
||||
|
||||
export async function getCurrentUser() {
|
||||
try {
|
||||
const { data: user } = await axios.get<CurrentUserResponse>(
|
||||
buildUrl(undefined, 'me')
|
||||
);
|
||||
|
||||
return user;
|
||||
} catch (e) {
|
||||
throw parseAxiosError(e as Error, 'Unable to retrieve user details');
|
||||
}
|
||||
}
|
|
@ -1,4 +1,3 @@
|
|||
import jwtDecode from 'jwt-decode';
|
||||
import { useCurrentStateAndParams } from '@uirouter/react';
|
||||
import {
|
||||
createContext,
|
||||
|
@ -11,9 +10,7 @@ import {
|
|||
import { isAdmin } from '@/portainer/users/user.helpers';
|
||||
import { EnvironmentId } from '@/react/portainer/environments/types';
|
||||
import { User } from '@/portainer/users/types';
|
||||
import { useUser as useLoadUser } from '@/portainer/users/queries/useUser';
|
||||
|
||||
import { useLocalStorage } from './useLocalStorage';
|
||||
import { useLoadCurrentUser } from '@/portainer/users/queries/useLoadCurrentUser';
|
||||
|
||||
interface State {
|
||||
user?: User;
|
||||
|
@ -163,20 +160,14 @@ interface UserProviderProps {
|
|||
}
|
||||
|
||||
export function UserProvider({ children }: UserProviderProps) {
|
||||
const [jwt] = useLocalStorage('JWT', '');
|
||||
|
||||
const tokenPayload = useMemo(() => jwtDecode(jwt) as { id: number }, [jwt]);
|
||||
|
||||
const userQuery = useLoadUser(tokenPayload.id, {
|
||||
staleTime: Infinity, // should reload te user details only on page load
|
||||
});
|
||||
const userQuery = useLoadCurrentUser();
|
||||
|
||||
const providerState = useMemo(
|
||||
() => ({ user: userQuery.data }),
|
||||
[userQuery.data]
|
||||
);
|
||||
|
||||
if (jwt === '' || !providerState.user) {
|
||||
if (!providerState.user) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,6 @@ import { useCurrentStateAndParams } from '@uirouter/react';
|
|||
import { Terminal as TerminalIcon } from 'lucide-react';
|
||||
import { Terminal } from 'xterm';
|
||||
|
||||
import { get } from '@/react/hooks/useLocalStorage';
|
||||
import { baseHref } from '@/portainer/helpers/pathHelper';
|
||||
import { notifyError } from '@/portainer/services/notifications';
|
||||
|
||||
|
@ -169,10 +168,7 @@ export function ConsoleView() {
|
|||
);
|
||||
|
||||
function connectConsole() {
|
||||
const jwtToken = get('JWT', '');
|
||||
|
||||
const params: StringDictionary = {
|
||||
token: jwtToken,
|
||||
endpointId: environmentId,
|
||||
namespace,
|
||||
podName: podID,
|
||||
|
|
|
@ -11,7 +11,6 @@ import {
|
|||
} from '@/portainer/services/terminal-window';
|
||||
import { EnvironmentId } from '@/react/portainer/environments/types';
|
||||
import { error as notifyError } from '@/portainer/services/notifications';
|
||||
import { useLocalStorage } from '@/react/hooks/useLocalStorage';
|
||||
|
||||
import { Icon } from '@@/Icon';
|
||||
import { Button } from '@@/buttons';
|
||||
|
@ -40,8 +39,6 @@ export function KubeCtlShell({ environmentId, onClose }: Props) {
|
|||
|
||||
const terminalElem = useRef(null);
|
||||
|
||||
const [jwt] = useLocalStorage('JWT', '');
|
||||
|
||||
const handleClose = useCallback(() => {
|
||||
terminalClose(); // only css trick
|
||||
socket?.close();
|
||||
|
@ -103,7 +100,7 @@ export function KubeCtlShell({ environmentId, onClose }: Props) {
|
|||
|
||||
// on component load/destroy
|
||||
useEffect(() => {
|
||||
const socket = new WebSocket(buildUrl(jwt, environmentId));
|
||||
const socket = new WebSocket(buildUrl(environmentId));
|
||||
setShell((shell) => ({ ...shell, socket }));
|
||||
|
||||
terminal.onData((data) => socket.send(data));
|
||||
|
@ -122,7 +119,7 @@ export function KubeCtlShell({ environmentId, onClose }: Props) {
|
|||
}
|
||||
|
||||
return close;
|
||||
}, [environmentId, jwt, terminal]);
|
||||
}, [environmentId, terminal]);
|
||||
|
||||
return (
|
||||
<div className={clsx(styles.root, { [styles.minimized]: shell.minimized })}>
|
||||
|
@ -182,9 +179,8 @@ export function KubeCtlShell({ environmentId, onClose }: Props) {
|
|||
}
|
||||
}
|
||||
|
||||
function buildUrl(jwt: string, environmentId: EnvironmentId) {
|
||||
function buildUrl(environmentId: EnvironmentId) {
|
||||
const params = {
|
||||
token: jwt,
|
||||
endpointId: environmentId,
|
||||
};
|
||||
|
||||
|
|
|
@ -6,7 +6,6 @@ import 'angular-messages';
|
|||
import 'angular-resource';
|
||||
import 'angular-utils-pagination';
|
||||
import 'angular-local-storage';
|
||||
import 'angular-jwt';
|
||||
import 'angular-json-tree';
|
||||
import 'angular-loading-bar';
|
||||
import 'angular-clipboard';
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue