mirror of https://github.com/portainer/portainer
This reverts commit 2c032f1739
.
pull/10665/head
parent
2c032f1739
commit
ecce501cf3
|
@ -23,21 +23,3 @@ func (migrator *Migrator) updateAppTemplatesVersionForDB110() error {
|
|||
|
||||
return migrator.settingsService.UpdateSettings(settings)
|
||||
}
|
||||
|
||||
// setUseCacheForDB110 sets the user cache to true for all users
|
||||
func (migrator *Migrator) setUserCacheForDB110() error {
|
||||
users, err := migrator.userService.ReadAll()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i := range users {
|
||||
user := &users[i]
|
||||
user.UseCache = true
|
||||
if err := migrator.userService.Update(user.ID, user); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -231,7 +231,6 @@ func (m *Migrator) initMigrations() {
|
|||
|
||||
m.addMigrations("2.20",
|
||||
m.updateAppTemplatesVersionForDB110,
|
||||
m.setUserCacheForDB110,
|
||||
)
|
||||
|
||||
// Add new migrations below...
|
||||
|
|
|
@ -903,7 +903,6 @@
|
|||
"color": ""
|
||||
},
|
||||
"TokenIssueAt": 0,
|
||||
"UseCache": true,
|
||||
"Username": "admin"
|
||||
},
|
||||
{
|
||||
|
@ -933,11 +932,10 @@
|
|||
"color": ""
|
||||
},
|
||||
"TokenIssueAt": 0,
|
||||
"UseCache": true,
|
||||
"Username": "prabhat"
|
||||
}
|
||||
],
|
||||
"version": {
|
||||
"VERSION": "{\"SchemaVersion\":\"2.20.0\",\"MigratorCount\":2,\"Edition\":1,\"InstanceID\":\"463d5c47-0ea5-4aca-85b1-405ceefee254\"}"
|
||||
"VERSION": "{\"SchemaVersion\":\"2.20.0\",\"MigratorCount\":1,\"Edition\":1,\"InstanceID\":\"463d5c47-0ea5-4aca-85b1-405ceefee254\"}"
|
||||
}
|
||||
}
|
|
@ -24,7 +24,6 @@ type userUpdatePayload struct {
|
|||
Username string `validate:"required" example:"bob"`
|
||||
Password string `validate:"required" example:"cg9Wgky3"`
|
||||
NewPassword string `validate:"required" example:"asfj2emv"`
|
||||
UseCache *bool `validate:"required" example:"true"`
|
||||
Theme *themePayload
|
||||
|
||||
// User role (1 for administrator account and 2 for regular account)
|
||||
|
@ -148,10 +147,6 @@ func (handler *Handler) userUpdate(w http.ResponseWriter, r *http.Request) *http
|
|||
}
|
||||
}
|
||||
|
||||
if payload.UseCache != nil {
|
||||
user.UseCache = *payload.UseCache
|
||||
}
|
||||
|
||||
if payload.Role != 0 {
|
||||
user.Role = portainer.UserRole(payload.Role)
|
||||
user.TokenIssueAt = time.Now().Unix()
|
||||
|
|
|
@ -1314,10 +1314,9 @@ type (
|
|||
Username string `json:"Username" example:"bob"`
|
||||
Password string `json:"Password,omitempty" swaggerignore:"true"`
|
||||
// User role (1 for administrator account and 2 for regular account)
|
||||
Role UserRole `json:"Role" example:"1"`
|
||||
TokenIssueAt int64 `json:"TokenIssueAt" example:"1"`
|
||||
ThemeSettings UserThemeSettings `json:"ThemeSettings"`
|
||||
UseCache bool `json:"UseCache" example:"true"`
|
||||
Role UserRole `json:"Role" example:"1"`
|
||||
TokenIssueAt int64 `json:"TokenIssueAt" example:"1"`
|
||||
ThemeSettings UserThemeSettings
|
||||
|
||||
// Deprecated fields
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { Terminal } from 'xterm';
|
||||
import * as fit from 'xterm/lib/addons/fit/fit';
|
||||
import { agentInterceptor } from './portainer/services/axios';
|
||||
import { dispatchCacheRefreshEventIfNeeded } from './portainer/services/http-request.helper';
|
||||
|
||||
/* @ngInject */
|
||||
export function configApp($urlRouterProvider, $httpProvider, localStorageServiceProvider, jwtOptionsProvider, $uibTooltipProvider, $compileProvider, cfpLoadingBarProvider) {
|
||||
|
@ -9,14 +8,6 @@ export function configApp($urlRouterProvider, $httpProvider, localStorageService
|
|||
$compileProvider.debugInfoEnabled(false);
|
||||
}
|
||||
|
||||
// ask to clear cache on mutation
|
||||
$httpProvider.interceptors.push(() => ({
|
||||
request: (reqConfig) => {
|
||||
dispatchCacheRefreshEventIfNeeded(reqConfig);
|
||||
return reqConfig;
|
||||
},
|
||||
}));
|
||||
|
||||
localStorageServiceProvider.setPrefix('portainer');
|
||||
|
||||
jwtOptionsProvider.config({
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<page-header title="'Create Edge stack'" breadcrumbs="[{label:'Edge Stacks', link:'edge.stacks'}, 'Create Edge stack']" reload="true"> </page-header>
|
||||
<page-header title="'Create Edge stack'" breadcrumbs="[{label:'Edge Stacks', link:'edge.stacks'}, 'Create Edge stack']"> </page-header>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
|
|
|
@ -1,52 +1,13 @@
|
|||
import { EnvironmentStatus } from '@/react/portainer/environments/types';
|
||||
import { getSelfSubjectAccessReview } from '@/react/kubernetes/namespaces/getSelfSubjectAccessReview';
|
||||
|
||||
import { updateAxiosAdapter } from '@/portainer/services/axios';
|
||||
import { PortainerEndpointTypes } from 'Portainer/models/endpoint/models';
|
||||
import { CACHE_REFRESH_EVENT, CACHE_DURATION } from '../portainer/services/http-request.helper';
|
||||
import { cache } from '../portainer/services/axios';
|
||||
|
||||
import registriesModule from './registries';
|
||||
import customTemplateModule from './custom-templates';
|
||||
import { reactModule } from './react';
|
||||
import './views/kubernetes.css';
|
||||
|
||||
// The angular-cache npm package didn't have exclude options, so implement a custom cache
|
||||
// with an added check to only cache kubernetes requests
|
||||
class ExpirationCache {
|
||||
constructor() {
|
||||
this.store = new Map();
|
||||
this.timeout = CACHE_DURATION;
|
||||
}
|
||||
|
||||
get(key) {
|
||||
return this.store.get(key);
|
||||
}
|
||||
|
||||
put(key, val) {
|
||||
// only cache requests with 'kubernetes' in the url
|
||||
if (key.includes('kubernetes')) {
|
||||
this.store.set(key, val);
|
||||
// remove it once it's expired
|
||||
setTimeout(() => {
|
||||
this.remove(key);
|
||||
}, this.timeout);
|
||||
}
|
||||
}
|
||||
|
||||
remove(key) {
|
||||
this.store.delete(key);
|
||||
}
|
||||
|
||||
removeAll() {
|
||||
this.store = new Map();
|
||||
}
|
||||
|
||||
delete() {
|
||||
// skip because this is standalone, not a part of $cacheFactory
|
||||
}
|
||||
}
|
||||
|
||||
angular.module('portainer.kubernetes', ['portainer.app', registriesModule, customTemplateModule, reactModule]).config([
|
||||
'$stateRegistryProvider',
|
||||
function ($stateRegistryProvider) {
|
||||
|
@ -58,31 +19,8 @@ angular.module('portainer.kubernetes', ['portainer.app', registriesModule, custo
|
|||
parent: 'endpoint',
|
||||
abstract: true,
|
||||
|
||||
onEnter: /* @ngInject */ function onEnter(
|
||||
$async,
|
||||
$state,
|
||||
endpoint,
|
||||
KubernetesHealthService,
|
||||
KubernetesNamespaceService,
|
||||
Notifications,
|
||||
StateManager,
|
||||
$http,
|
||||
Authentication,
|
||||
UserService
|
||||
) {
|
||||
onEnter: /* @ngInject */ function onEnter($async, $state, endpoint, KubernetesHealthService, KubernetesNamespaceService, Notifications, StateManager) {
|
||||
return $async(async () => {
|
||||
// if the user wants to use front end cache for performance, set the angular caching settings
|
||||
const userDetails = Authentication.getUserDetails();
|
||||
const user = await UserService.user(userDetails.ID);
|
||||
updateAxiosAdapter(user.UseCache);
|
||||
if (user.UseCache) {
|
||||
$http.defaults.cache = new ExpirationCache();
|
||||
window.addEventListener(CACHE_REFRESH_EVENT, () => {
|
||||
$http.defaults.cache.removeAll();
|
||||
cache.store.clear();
|
||||
});
|
||||
}
|
||||
|
||||
const kubeTypes = [
|
||||
PortainerEndpointTypes.KubernetesLocalEnvironment,
|
||||
PortainerEndpointTypes.AgentOnKubernetesEnvironment,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<page-header title="'Create Custom template'" breadcrumbs="[{label:'Custom Templates', link:'kubernetes.templates.custom'}, 'Create Custom template']" reload="true"> </page-header>
|
||||
<page-header title="'Create Custom template'" breadcrumbs="[{label:'Custom Templates', link:'kubernetes.templates.custom'}, 'Create Custom template']"> </page-header>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<page-header title="'Registry access'" breadcrumbs="[{label:'Registries', link:'kubernetes.registries'}, $ctrl.registry.Name, 'Access management']" reload="true"> </page-header>
|
||||
<page-header title="'Registry access'" breadcrumbs="[{label:'Registries', link:'kubernetes.registries'}, $ctrl.registry.Name, 'Access management']"> </page-header>
|
||||
|
||||
<registry-details registry="$ctrl.registry" ng-if="$ctrl.registry"></registry-details>
|
||||
|
||||
|
|
|
@ -4,13 +4,11 @@ import { KubernetesCommonParams } from 'Kubernetes/models/common/params';
|
|||
import KubernetesNamespaceConverter from 'Kubernetes/converters/namespace';
|
||||
import { updateNamespaces } from 'Kubernetes/store/namespace';
|
||||
import $allSettled from 'Portainer/services/allSettled';
|
||||
import { getSelfSubjectAccessReview } from '@/react/kubernetes/namespaces/getSelfSubjectAccessReview';
|
||||
|
||||
class KubernetesNamespaceService {
|
||||
/* @ngInject */
|
||||
constructor($async, KubernetesNamespaces, LocalStorage, $state) {
|
||||
constructor($async, KubernetesNamespaces, LocalStorage) {
|
||||
this.$async = $async;
|
||||
this.$state = $state;
|
||||
this.KubernetesNamespaces = KubernetesNamespaces;
|
||||
this.LocalStorage = LocalStorage;
|
||||
|
||||
|
@ -68,10 +66,8 @@ class KubernetesNamespaceService {
|
|||
try {
|
||||
// get the list of all namespaces (RBAC allows users to see the list of namespaces)
|
||||
const data = await this.KubernetesNamespaces().get().$promise;
|
||||
// get the status of each namespace with accessReviews (to avoid failed forbidden responses, which aren't cached)
|
||||
const accessReviews = await Promise.all(data.items.map((namespace) => getSelfSubjectAccessReview(this.$state.params.endpointId, namespace.metadata.name)));
|
||||
const allowedNamespaceNames = accessReviews.filter((ar) => ar.status.allowed).map((ar) => ar.spec.resourceAttributes.namespace);
|
||||
const promises = allowedNamespaceNames.map((name) => this.KubernetesNamespaces().status({ id: name }).$promise);
|
||||
// get the status of each namespace (RBAC will give permission denied for status of unauthorised namespaces)
|
||||
const promises = data.items.map((item) => this.KubernetesNamespaces().status({ id: item.metadata.name }).$promise);
|
||||
const namespaces = await $allSettled(promises);
|
||||
// only return namespaces if the user has access to namespaces
|
||||
const allNamespaces = namespaces.fulfilled.map((item) => {
|
||||
|
|
|
@ -75,7 +75,7 @@ class KubernetesResourcePoolsController {
|
|||
|
||||
async getResourcePoolsAsync() {
|
||||
try {
|
||||
this.resourcePools = await this.KubernetesResourcePoolService.get();
|
||||
this.resourcePools = await this.KubernetesResourcePoolService.get('', { getQuota: true });
|
||||
} catch (err) {
|
||||
this.Notifications.error('Failure', err, 'Unable to retreive namespaces');
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { notifyError, notifySuccess } from '@/portainer/services/notifications';
|
||||
import { userQueryKeys } from '@/portainer/users/queries/queryKeys';
|
||||
import { queryKeys } from '@/portainer/users/queries/queryKeys';
|
||||
import { queryClient } from '@/react-tools/react-query';
|
||||
import { options } from '@/react/portainer/account/AccountView/theme-options';
|
||||
|
||||
|
@ -32,7 +32,7 @@ export default class ThemeSettingsController {
|
|||
try {
|
||||
if (!this.state.isDemo) {
|
||||
await this.UserService.updateUserTheme(this.state.userId, theme);
|
||||
await queryClient.invalidateQueries(userQueryKeys.user(this.state.userId));
|
||||
await queryClient.invalidateQueries(queryKeys.user(this.state.userId));
|
||||
}
|
||||
|
||||
notifySuccess('Success', 'User theme settings successfully updated');
|
||||
|
|
|
@ -12,7 +12,6 @@ export function UserViewModel(data) {
|
|||
}
|
||||
this.AuthenticationMethod = data.AuthenticationMethod;
|
||||
this.Checked = false;
|
||||
this.UseCache = data.UseCache;
|
||||
}
|
||||
|
||||
export function UserTokenModel(data) {
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
import angular from 'angular';
|
||||
|
||||
import { r2a } from '@/react-tools/react2angular';
|
||||
import { withCurrentUser } from '@/react-tools/withCurrentUser';
|
||||
import { withUIRouter } from '@/react-tools/withUIRouter';
|
||||
import { withReactQuery } from '@/react-tools/withReactQuery';
|
||||
import { ApplicationSettingsWidget } from '@/react/portainer/account/AccountView/ApplicationSettings';
|
||||
|
||||
export const accountModule = angular
|
||||
.module('portainer.app.react.components.account', [])
|
||||
.component(
|
||||
'applicationSettingsWidget',
|
||||
r2a(
|
||||
withUIRouter(withReactQuery(withCurrentUser(ApplicationSettingsWidget))),
|
||||
[]
|
||||
)
|
||||
).name;
|
|
@ -45,7 +45,6 @@ import { accessControlModule } from './access-control';
|
|||
import { environmentsModule } from './environments';
|
||||
import { envListModule } from './environments-list-view-components';
|
||||
import { registriesModule } from './registries';
|
||||
import { accountModule } from './account';
|
||||
|
||||
export const ngModule = angular
|
||||
.module('portainer.app.react.components', [
|
||||
|
@ -56,7 +55,6 @@ export const ngModule = angular
|
|||
gitFormModule,
|
||||
registriesModule,
|
||||
settingsModule,
|
||||
accountModule,
|
||||
])
|
||||
.component(
|
||||
'tagSelector',
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import axiosOrigin, { AxiosError, AxiosRequestConfig } from 'axios';
|
||||
import { setupCache } from 'axios-cache-adapter';
|
||||
import { loadProgressBar } from 'axios-progress-bar';
|
||||
|
||||
import 'axios-progress-bar/dist/nprogress.css';
|
||||
|
@ -7,48 +6,12 @@ import PortainerError from '@/portainer/error';
|
|||
import { get as localStorageGet } from '@/react/hooks/useLocalStorage';
|
||||
|
||||
import {
|
||||
CACHE_DURATION,
|
||||
dispatchCacheRefreshEventIfNeeded,
|
||||
portainerAgentManagerOperation,
|
||||
portainerAgentTargetHeader,
|
||||
} from './http-request.helper';
|
||||
|
||||
export const cache = setupCache({
|
||||
maxAge: CACHE_DURATION,
|
||||
debug: false, // set to true to print cache hits/misses
|
||||
exclude: {
|
||||
query: false, // include urls with query params
|
||||
methods: ['put', 'patch', 'delete'],
|
||||
filter: (req: AxiosRequestConfig) => {
|
||||
// exclude caching get requests unless the path contains 'kubernetes'
|
||||
if (!req.url?.includes('kubernetes') && req.method === 'get') {
|
||||
return true;
|
||||
}
|
||||
|
||||
// exclude caching post requests unless the path contains 'selfsubjectaccessreview'
|
||||
if (
|
||||
!req.url?.includes('selfsubjectaccessreview') &&
|
||||
req.method === 'post'
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
},
|
||||
// ask to clear cache on mutation
|
||||
invalidate: async (_, req) => {
|
||||
dispatchCacheRefreshEventIfNeeded(req);
|
||||
},
|
||||
});
|
||||
|
||||
// by default don't use the cache adapter
|
||||
const axios = axiosOrigin.create({ baseURL: 'api' });
|
||||
|
||||
// when entering a kubernetes environment, or updating user settings, update the cache adapter
|
||||
export function updateAxiosAdapter(useCache: boolean) {
|
||||
axios.defaults.adapter = useCache ? cache.adapter : undefined;
|
||||
}
|
||||
|
||||
loadProgressBar(undefined, axios);
|
||||
|
||||
export default axios;
|
||||
|
|
|
@ -1,31 +1,3 @@
|
|||
import { AxiosRequestConfig } from 'axios';
|
||||
|
||||
export const CACHE_DURATION = 5 * 60 * 1000; // 5m in ms
|
||||
// event emitted when cache need to be refreshed
|
||||
// used to sync $http + axios cache clear
|
||||
export const CACHE_REFRESH_EVENT = '__cache__refresh__event__';
|
||||
|
||||
// utility function to dispatch catch refresh event
|
||||
export function dispatchCacheRefreshEvent() {
|
||||
dispatchEvent(new CustomEvent(CACHE_REFRESH_EVENT, {}));
|
||||
}
|
||||
|
||||
// perform checks on config.method and config.url
|
||||
// to dispatch event in only specific scenarios
|
||||
export function dispatchCacheRefreshEventIfNeeded(req: AxiosRequestConfig) {
|
||||
if (
|
||||
req.method &&
|
||||
['post', 'patch', 'put', 'delete'].includes(req.method.toLowerCase()) &&
|
||||
// don't clear cache when we try to check for namespaces accesses
|
||||
// otherwise we will clear it on every page
|
||||
req.url &&
|
||||
!req.url.includes('selfsubjectaccessreviews') &&
|
||||
req.url.includes('kubernetes')
|
||||
) {
|
||||
dispatchCacheRefreshEvent();
|
||||
}
|
||||
}
|
||||
|
||||
interface Headers {
|
||||
agentTargetQueue: string[];
|
||||
agentManagerOperation: boolean;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { UserId } from '../types';
|
||||
|
||||
export const userQueryKeys = {
|
||||
export const queryKeys = {
|
||||
base: () => ['users'] as const,
|
||||
user: (id: UserId) => [...userQueryKeys.base(), id] as const,
|
||||
user: (id: UserId) => [...queryKeys.base(), id] as const,
|
||||
};
|
||||
|
|
|
@ -6,13 +6,13 @@ import { withError } from '@/react-tools/react-query';
|
|||
import { buildUrl } from '../user.service';
|
||||
import { User, UserId } from '../types';
|
||||
|
||||
import { userQueryKeys } from './queryKeys';
|
||||
import { queryKeys } from './queryKeys';
|
||||
|
||||
export function useUser(
|
||||
id: UserId,
|
||||
{ staleTime }: { staleTime?: number } = {}
|
||||
) {
|
||||
return useQuery(userQueryKeys.user(id), () => getUser(id), {
|
||||
return useQuery(queryKeys.user(id), () => getUser(id), {
|
||||
...withError('Unable to retrieve user details'),
|
||||
staleTime,
|
||||
});
|
||||
|
|
|
@ -20,7 +20,6 @@ export type User = {
|
|||
EndpointAuthorizations: {
|
||||
[endpointId: EnvironmentId]: AuthorizationMap;
|
||||
};
|
||||
UseCache: boolean;
|
||||
ThemeSettings: {
|
||||
color: 'dark' | 'light' | 'highcontrast' | 'auto';
|
||||
};
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<page-header title="'User settings'" breadcrumbs="['User settings']" reload="true"> </page-header>
|
||||
<page-header title="'User settings'" breadcrumbs="['User settings']"> </page-header>
|
||||
|
||||
<demo-feature-indicator ng-if="isDemoUser" content="'You cannot change the password of this account in the demo version of Portainer.'"> </demo-feature-indicator>
|
||||
|
||||
|
@ -16,16 +16,16 @@
|
|||
<form name="form" class="form-horizontal" style="margin-top: 15px">
|
||||
<!-- current-password-input -->
|
||||
<div class="form-group">
|
||||
<label for="current_password" class="col-sm-3 col-lg-2 control-label required text-left">Current password</label>
|
||||
<div class="col-sm-9 col-lg-10">
|
||||
<label for="current_password" class="col-sm-2 control-label required text-left">Current password</label>
|
||||
<div class="col-sm-8">
|
||||
<input type="password" class="form-control" ng-model="formValues.currentPassword" id="current_password" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- !current-password-input -->
|
||||
<!-- new-password-input -->
|
||||
<div class="form-group">
|
||||
<label for="new_password" class="col-sm-3 col-lg-2 control-label required text-left">New password</label>
|
||||
<div class="col-sm-9 col-lg-10">
|
||||
<label for="new_password" class="col-sm-2 control-label required text-left">New password</label>
|
||||
<div class="col-sm-8">
|
||||
<input type="password" class="form-control" ng-model="formValues.newPassword" ng-minlength="requiredPasswordLength" id="new_password" name="new_password" />
|
||||
</div>
|
||||
</div>
|
||||
|
@ -33,8 +33,8 @@
|
|||
|
||||
<!-- confirm-password-input -->
|
||||
<div class="form-group">
|
||||
<label for="confirm_password" class="col-sm-3 col-lg-2 control-label required text-left">Confirm password</label>
|
||||
<div class="col-sm-9 col-lg-10">
|
||||
<label for="confirm_password" class="col-sm-2 control-label required text-left">Confirm password</label>
|
||||
<div class="col-sm-8">
|
||||
<div class="input-group">
|
||||
<input type="password" class="form-control" ng-model="formValues.confirmPassword" id="confirm_password" />
|
||||
<span class="input-group-addon">
|
||||
|
@ -86,8 +86,6 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<application-settings-widget></application-settings-widget>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12 col-md-12 col-xs-12">
|
||||
<access-tokens-datatable
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<page-header title="'Create access token'" breadcrumbs="[{label:'User settings', link:'portainer.account'}, 'Add access token']" reload="true"> </page-header>
|
||||
<page-header title="'Create access token'" breadcrumbs="[{label:'User settings', link:'portainer.account'}, 'Add access token']"> </page-header>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<page-header title="'Create Custom template'" breadcrumbs="[{label:'Custom Templates', link:'docker.templates.custom'}, 'Create Custom template']" reload="true"> </page-header>
|
||||
<page-header title="'Create Custom template'" breadcrumbs="[{label:'Custom Templates', link:'docker.templates.custom'}, 'Create Custom template']"> </page-header>
|
||||
|
||||
<div class="row" ng-if="!$ctrl.state.loading">
|
||||
<div class="col-sm-12">
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<page-header title="'FDO Device Configuration'" breadcrumbs="[{label:'Environments', link:'portainer.endpoints'}, 'Import FDO Device']" reload="true"> </page-header>
|
||||
<page-header title="'FDO Device Configuration'" breadcrumbs="[{label:'Environments', link:'portainer.endpoints'}, 'Import FDO Device']"> </page-header>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<page-header title="'Create profile'" breadcrumbs="[{label:'Settings', link:'portainer.settings'}, 'Edge Compute']" reload="true"> </page-header>
|
||||
<page-header title="'Create profile'" breadcrumbs="[{label:'Settings', link:'portainer.settings'}, 'Edge Compute']"> </page-header>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<page-header title="'Edit profile'" breadcrumbs="[{label:'Settings', link:'portainer.settings'}, 'Edge Compute']" reload="true"> </page-header>
|
||||
<page-header title="'Edit profile'" breadcrumbs="[{label:'Settings', link:'portainer.settings'}, 'Edge Compute']"> </page-header>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
link: 'portainer.endpoints.endpoint',
|
||||
linkParams:{id: ctrl.endpoint.Id}
|
||||
}, 'Access management']"
|
||||
reload="true"
|
||||
>
|
||||
</page-header>
|
||||
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
},
|
||||
$state.deviceName,
|
||||
'KVM Control']"
|
||||
reload="true"
|
||||
>
|
||||
</page-header>
|
||||
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
link: 'portainer.groups.group',
|
||||
linkParams:{id: group.Id}
|
||||
}, 'Access management']"
|
||||
reload="true"
|
||||
>
|
||||
</page-header>
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<page-header title="'Create environment group'" breadcrumbs="[{label:'Environment groups', link:'portainer.groups'}, 'Add group']" reload="true"> </page-header>
|
||||
<page-header title="'Create environment group'" breadcrumbs="[{label:'Environment groups', link:'portainer.groups'}, 'Add group']"> </page-header>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<page-header title="'Environment group details'" breadcrumbs="[{label:'Groups', link:'portainer.groups'}, group.Name]" reload="true"> </page-header>
|
||||
<page-header title="'Environment group details'" breadcrumbs="[{label:'Groups', link:'portainer.groups'}, group.Name]"> </page-header>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<page-header title="'Create registry'" breadcrumbs="[{label:'Registries', link:'portainer.registries'}, 'Add registry']" reload="true"> </page-header>
|
||||
<page-header title="'Create registry'" breadcrumbs="[{label:'Registries', link:'portainer.registries'}, 'Add registry']"> </page-header>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<page-header title="'Registry details'" breadcrumbs="[{label:'Registries', link:'portainer.registries'}, $ctrl.registry.Name]" reload="true"> </page-header>
|
||||
<page-header title="'Registry details'" breadcrumbs="[{label:'Registries', link:'portainer.registries'}, $ctrl.registry.Name]"> </page-header>
|
||||
|
||||
<div class="row" ng-if="!$ctrl.state.loading">
|
||||
<div class="col-sm-12">
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<page-header title="'Authentication settings'" breadcrumbs="[{label:'Settings', link:'portainer.settings'}, 'Authentication']" reload="true"> </page-header>
|
||||
<page-header title="'Authentication settings'" breadcrumbs="[{label:'Settings', link:'portainer.settings'}, 'Authentication']"> </page-header>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<page-header title="'Settings'" breadcrumbs="[{label:'Settings', link:'portainer.settings'}, 'Edge Compute']" reload="true"> </page-header>
|
||||
<page-header title="'Settings'" breadcrumbs="[{label:'Settings', link:'portainer.settings'}, 'Edge Compute']"> </page-header>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12" ng-if="$ctrl.settings">
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<page-header title="'Create stack'" breadcrumbs="[{label:'Stacks', link:'docker.stacks'}, 'Add stack']" reload="true"> </page-header>
|
||||
<page-header title="'Create stack'" breadcrumbs="[{label:'Stacks', link:'docker.stacks'}, 'Add stack']"> </page-header>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<page-header title="'User details'" breadcrumbs="[{label:'Users', link:'portainer.users'}, formValues.username]" reload="true"> </page-header>
|
||||
<page-header title="'User details'" breadcrumbs="[{label:'Users', link:'portainer.users'}, formValues.username]"> </page-header>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12 col-md-12 col-xs-12">
|
||||
|
|
|
@ -17,7 +17,6 @@ export function createMockUsers(
|
|||
Checked: false,
|
||||
EndpointAuthorizations: {},
|
||||
PortainerAuthorizations: {},
|
||||
UseCache: false,
|
||||
ThemeSettings: {
|
||||
color: 'auto',
|
||||
},
|
||||
|
|
|
@ -27,7 +27,7 @@ export function DashboardView() {
|
|||
|
||||
return (
|
||||
<>
|
||||
<PageHeader title="Home" breadcrumbs={[{ label: 'Dashboard' }]} reload />
|
||||
<PageHeader title="Home" breadcrumbs={[{ label: 'Dashboard' }]} />
|
||||
|
||||
<div className="mx-4">
|
||||
{subscriptionsQuery.data && (
|
||||
|
|
|
@ -12,7 +12,6 @@ export function CreateView() {
|
|||
{ link: 'azure.containerinstances', label: 'Container instances' },
|
||||
{ label: 'Add container' },
|
||||
]}
|
||||
reload
|
||||
/>
|
||||
|
||||
<div className="row">
|
||||
|
|
|
@ -68,7 +68,6 @@ export function ItemView() {
|
|||
{ link: 'azure.containerinstances', label: 'Container instances' },
|
||||
{ label: container.name },
|
||||
]}
|
||||
reload
|
||||
/>
|
||||
|
||||
<div className="row">
|
||||
|
|
|
@ -32,9 +32,9 @@ export function ListView() {
|
|||
return (
|
||||
<>
|
||||
<PageHeader
|
||||
title="Container list"
|
||||
breadcrumbs="Container instances"
|
||||
reload
|
||||
title="Container list"
|
||||
/>
|
||||
|
||||
<ContainersDatatable
|
||||
|
|
|
@ -31,7 +31,6 @@ function Template({ title }: StoryProps) {
|
|||
{ label: 'bread3' },
|
||||
{ label: 'bread4' },
|
||||
]}
|
||||
reload
|
||||
/>
|
||||
</UserContext.Provider>
|
||||
);
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
import { useRouter } from '@uirouter/react';
|
||||
import { PropsWithChildren } from 'react';
|
||||
import { RefreshCw } from 'lucide-react';
|
||||
|
||||
import { dispatchCacheRefreshEvent } from '@/portainer/services/http-request.helper';
|
||||
import { PropsWithChildren } from 'react';
|
||||
|
||||
import { Button } from '../buttons';
|
||||
|
||||
|
@ -53,7 +51,6 @@ export function PageHeader({
|
|||
);
|
||||
|
||||
function onClickedRefresh() {
|
||||
dispatchCacheRefreshEvent();
|
||||
return onReload ? onReload() : router.stateService.reload();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@ export function createMockUser(id: number, username: string): UserViewModel {
|
|||
Username: username,
|
||||
Role: 2,
|
||||
EndpointAuthorizations: {},
|
||||
UseCache: false,
|
||||
PortainerAuthorizations: {
|
||||
PortainerDockerHubInspect: true,
|
||||
PortainerEndpointGroupInspect: true,
|
||||
|
|
|
@ -36,7 +36,6 @@ export function CreateView() {
|
|||
{ label: 'Containers', link: 'docker.containers' },
|
||||
'Add container',
|
||||
]}
|
||||
reload
|
||||
/>
|
||||
|
||||
<CreateForm />
|
||||
|
|
|
@ -61,7 +61,6 @@ export function ItemView() {
|
|||
label: networkQuery.data.Name,
|
||||
},
|
||||
]}
|
||||
reload
|
||||
/>
|
||||
<NetworkDetailsTable
|
||||
network={networkQuery.data}
|
||||
|
|
|
@ -19,7 +19,6 @@ function WaitingRoomView() {
|
|||
<PageHeader
|
||||
title="Waiting Room"
|
||||
breadcrumbs={[{ label: 'Waiting Room' }]}
|
||||
reload
|
||||
/>
|
||||
|
||||
<InformationPanel>
|
||||
|
|
|
@ -27,6 +27,7 @@ export function ConfigureView() {
|
|||
<>
|
||||
<PageHeader
|
||||
title="Kubernetes features configuration"
|
||||
reload
|
||||
breadcrumbs={[
|
||||
{ label: 'Environments', link: 'portainer.endpoints' },
|
||||
{
|
||||
|
@ -36,7 +37,6 @@ export function ConfigureView() {
|
|||
},
|
||||
'Kubernetes configuration',
|
||||
]}
|
||||
reload
|
||||
/>
|
||||
<div className="row">
|
||||
<div className="col-sm-12">
|
||||
|
|
|
@ -573,7 +573,6 @@ export function CreateIngressView() {
|
|||
label: isEdit ? 'Edit ingress' : 'Create ingress',
|
||||
},
|
||||
]}
|
||||
reload
|
||||
/>
|
||||
<div className="row ingress-rules">
|
||||
<div className="col-sm-12">
|
||||
|
|
|
@ -1,86 +0,0 @@
|
|||
import { Form, Formik } from 'formik';
|
||||
|
||||
import { useCurrentUser } from '@/react/hooks/useUser';
|
||||
import { notifySuccess } from '@/portainer/services/notifications';
|
||||
import { updateAxiosAdapter } from '@/portainer/services/axios';
|
||||
import { withError } from '@/react-tools/react-query';
|
||||
|
||||
import { TextTip } from '@@/Tip/TextTip';
|
||||
import { LoadingButton } from '@@/buttons';
|
||||
import { SwitchField } from '@@/form-components/SwitchField';
|
||||
|
||||
import { useUpdateUserMutation } from '../../useUpdateUserMutation';
|
||||
|
||||
type FormValues = {
|
||||
useCache: boolean;
|
||||
};
|
||||
|
||||
export function ApplicationSettingsForm() {
|
||||
const { user } = useCurrentUser();
|
||||
const updateSettingsMutation = useUpdateUserMutation();
|
||||
|
||||
const initialValues = {
|
||||
useCache: user.UseCache,
|
||||
};
|
||||
|
||||
return (
|
||||
<Formik<FormValues>
|
||||
initialValues={initialValues}
|
||||
onSubmit={handleSubmit}
|
||||
validateOnMount
|
||||
enableReinitialize
|
||||
>
|
||||
{({ isValid, dirty, values, setFieldValue }) => (
|
||||
<Form className="form-horizontal">
|
||||
<TextTip color="orange" className="mb-3">
|
||||
Enabling front-end data caching can mean that changes to Kubernetes
|
||||
clusters made by other users or outside of Portainer may take up to
|
||||
five minutes to show in your session. This caching only applies to
|
||||
Kubernetes environments.
|
||||
</TextTip>
|
||||
<SwitchField
|
||||
label="Enable front-end data caching for Kubernetes environments"
|
||||
checked={values.useCache}
|
||||
onChange={(value) => setFieldValue('useCache', value)}
|
||||
labelClass="col-lg-2 col-sm-3" // match the label width of the other fields in the page
|
||||
fieldClass="!mb-4"
|
||||
/>
|
||||
<div className="form-group">
|
||||
<div className="col-sm-12">
|
||||
<LoadingButton
|
||||
loadingText="Saving..."
|
||||
isLoading={updateSettingsMutation.isLoading}
|
||||
disabled={!isValid || !dirty}
|
||||
className="!ml-0"
|
||||
data-cy="account-applicationSettingsSaveButton"
|
||||
>
|
||||
Save
|
||||
</LoadingButton>
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
);
|
||||
|
||||
function handleSubmit(values: FormValues) {
|
||||
updateSettingsMutation.mutate(
|
||||
{
|
||||
Id: user.Id,
|
||||
UseCache: values.useCache,
|
||||
},
|
||||
{
|
||||
onSuccess() {
|
||||
updateAxiosAdapter(values.useCache);
|
||||
notifySuccess(
|
||||
'Success',
|
||||
'Successfully updated application settings.'
|
||||
);
|
||||
// a full reload is required to update the angular $http cache setting
|
||||
setTimeout(() => window.location.reload(), 2000); // allow 2s to show the success notification
|
||||
},
|
||||
...withError('Unable to update application settings'),
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
import { Settings } from 'lucide-react';
|
||||
|
||||
import { Widget, WidgetBody, WidgetTitle } from '@@/Widget';
|
||||
|
||||
import { ApplicationSettingsForm } from './ApplicationSettingsForm';
|
||||
|
||||
export function ApplicationSettingsWidget() {
|
||||
return (
|
||||
<div className="row">
|
||||
<div className="col-sm-12">
|
||||
<Widget>
|
||||
<WidgetTitle icon={Settings} title="Application settings" />
|
||||
<WidgetBody>
|
||||
<ApplicationSettingsForm />
|
||||
</WidgetBody>
|
||||
</Widget>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
export { ApplicationSettingsWidget } from './ApplicationSettingsWidget';
|
|
@ -12,7 +12,6 @@ export function CreateHelmRepositoriesView() {
|
|||
{ label: 'My account', link: 'portainer.account' },
|
||||
{ label: 'Create Helm repository' },
|
||||
]}
|
||||
reload
|
||||
/>
|
||||
|
||||
<div className="row">
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
import { useMutation } from 'react-query';
|
||||
|
||||
import axios, { parseAxiosError } from '@/portainer/services/axios';
|
||||
import { User } from '@/portainer/users/types';
|
||||
import {
|
||||
mutationOptions,
|
||||
withInvalidate,
|
||||
queryClient,
|
||||
} from '@/react-tools/react-query';
|
||||
import { userQueryKeys } from '@/portainer/users/queries/queryKeys';
|
||||
import { useCurrentUser } from '@/react/hooks/useUser';
|
||||
|
||||
export function useUpdateUserMutation() {
|
||||
const {
|
||||
user: { Id: userId },
|
||||
} = useCurrentUser();
|
||||
|
||||
return useMutation(
|
||||
(user: Partial<User>) => updateUser(user, userId),
|
||||
mutationOptions(withInvalidate(queryClient, [userQueryKeys.base()]))
|
||||
// error notification should be handled by the caller
|
||||
);
|
||||
}
|
||||
|
||||
async function updateUser(user: Partial<User>, userId: number) {
|
||||
try {
|
||||
const { data } = await axios.put(`/users/${userId}`, user);
|
||||
return data;
|
||||
} catch (error) {
|
||||
throw parseAxiosError(error);
|
||||
}
|
||||
}
|
|
@ -17,7 +17,6 @@ function EdgeAutoCreateScriptView() {
|
|||
{ label: 'Environments', link: 'portainer.endpoints' },
|
||||
'Automatic Edge Environment Creation',
|
||||
]}
|
||||
reload
|
||||
/>
|
||||
|
||||
<div className="mx-3">
|
||||
|
|
|
@ -54,7 +54,6 @@ function CreateView() {
|
|||
<PageHeader
|
||||
title="Update & Rollback"
|
||||
breadcrumbs="Edge agent update and rollback"
|
||||
reload
|
||||
/>
|
||||
|
||||
<BetaAlert
|
||||
|
|
|
@ -78,7 +78,6 @@ function ItemView() {
|
|||
{ label: 'Edge agent update and rollback', link: '^' },
|
||||
item.name,
|
||||
]}
|
||||
reload
|
||||
/>
|
||||
|
||||
<BetaAlert
|
||||
|
|
|
@ -56,8 +56,8 @@ export function ListView() {
|
|||
<>
|
||||
<PageHeader
|
||||
title="Update & Rollback"
|
||||
breadcrumbs="Update and rollback"
|
||||
reload
|
||||
breadcrumbs="Update and rollback"
|
||||
/>
|
||||
|
||||
<BetaAlert
|
||||
|
|
|
@ -27,7 +27,6 @@ export function EnvironmentTypeSelectView() {
|
|||
<PageHeader
|
||||
title="Quick Setup"
|
||||
breadcrumbs={[{ label: 'Environment Wizard' }]}
|
||||
reload
|
||||
/>
|
||||
|
||||
<div className="row">
|
||||
|
|
|
@ -71,7 +71,6 @@ export function EnvironmentCreationView() {
|
|||
<PageHeader
|
||||
title="Quick Setup"
|
||||
breadcrumbs={[{ label: 'Environment Wizard' }]}
|
||||
reload
|
||||
/>
|
||||
|
||||
<div className={styles.wizardWrapper}>
|
||||
|
|
|
@ -22,7 +22,6 @@ export function HomeView() {
|
|||
<PageHeader
|
||||
title="Quick Setup"
|
||||
breadcrumbs={[{ label: 'Environment Wizard' }]}
|
||||
reload
|
||||
/>
|
||||
|
||||
<div className="row">
|
||||
|
|
|
@ -18,7 +18,7 @@ import { ExperimentalFeatures } from './ExperimentalFeatures';
|
|||
export function SettingsView() {
|
||||
return (
|
||||
<>
|
||||
<PageHeader title="Settings" breadcrumbs="Settings" reload />
|
||||
<PageHeader title="Settings" breadcrumbs="Settings" />
|
||||
|
||||
<div className="mx-4 space-y-4">
|
||||
<ApplicationSettingsPanel onSuccess={handleSuccess} />
|
||||
|
|
|
@ -38,7 +38,6 @@ export function ItemView() {
|
|||
<PageHeader
|
||||
title="Team details"
|
||||
breadcrumbs={[{ label: 'Teams' }, { label: team.Name }]}
|
||||
reload
|
||||
/>
|
||||
|
||||
{membershipsQuery.data && (
|
||||
|
|
|
@ -42,7 +42,6 @@ export function mockExampleData() {
|
|||
RoleName: 'user',
|
||||
Checked: false,
|
||||
AuthenticationMethod: '',
|
||||
UseCache: false,
|
||||
},
|
||||
{
|
||||
Id: 13,
|
||||
|
@ -70,7 +69,6 @@ export function mockExampleData() {
|
|||
RoleName: 'user',
|
||||
Checked: false,
|
||||
AuthenticationMethod: '',
|
||||
UseCache: false,
|
||||
},
|
||||
];
|
||||
|
||||
|
|
|
@ -16,11 +16,7 @@ export function ListView() {
|
|||
|
||||
return (
|
||||
<>
|
||||
<PageHeader
|
||||
title="Teams"
|
||||
breadcrumbs={[{ label: 'Teams management' }]}
|
||||
reload
|
||||
/>
|
||||
<PageHeader title="Teams" breadcrumbs={[{ label: 'Teams management' }]} />
|
||||
|
||||
{isAdmin && usersQuery.data && teamsQuery.data && (
|
||||
<CreateTeamForm users={usersQuery.data} teams={teamsQuery.data} />
|
||||
|
|
|
@ -8,7 +8,6 @@ const mockUser: User = {
|
|||
Id: 1,
|
||||
Role: 1,
|
||||
Username: 'mock',
|
||||
UseCache: false,
|
||||
ThemeSettings: {
|
||||
color: 'auto',
|
||||
},
|
||||
|
|
|
@ -75,7 +75,6 @@
|
|||
"angularjs-slider": "^6.4.0",
|
||||
"angulartics": "^1.6.0",
|
||||
"axios": "^0.24.0",
|
||||
"axios-cache-adapter": "^2.7.3",
|
||||
"axios-progress-bar": "^1.2.0",
|
||||
"babel-plugin-angularjs-annotate": "^0.10.0",
|
||||
"bootstrap": "^3.4.0",
|
||||
|
|
13
yarn.lock
13
yarn.lock
|
@ -6475,14 +6475,6 @@ axe-core@^4.6.2:
|
|||
resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.7.0.tgz#34ba5a48a8b564f67e103f0aa5768d76e15bbbbf"
|
||||
integrity sha512-M0JtH+hlOL5pLQwHOLNYZaXuhqmvS8oExsqB1SBYgA4Dk7u/xx+YdGHXaK5pyUfed5mYXdlYiphWq3G8cRi5JQ==
|
||||
|
||||
axios-cache-adapter@^2.7.3:
|
||||
version "2.7.3"
|
||||
resolved "https://registry.yarnpkg.com/axios-cache-adapter/-/axios-cache-adapter-2.7.3.tgz#0d1eefa0f25b88f42a95c7528d7345bde688181d"
|
||||
integrity sha512-A+ZKJ9lhpjthOEp4Z3QR/a9xC4du1ALaAsejgRGrH9ef6kSDxdFrhRpulqsh9khsEnwXxGfgpUuDp1YXMNMEiQ==
|
||||
dependencies:
|
||||
cache-control-esm "1.0.0"
|
||||
md5 "^2.2.1"
|
||||
|
||||
axios-progress-bar@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/axios-progress-bar/-/axios-progress-bar-1.2.0.tgz#f9ee88dc9af977246be1ef07eedfa4c990c639c5"
|
||||
|
@ -7066,11 +7058,6 @@ c8@^7.6.0:
|
|||
yargs "^16.2.0"
|
||||
yargs-parser "^20.2.9"
|
||||
|
||||
cache-control-esm@1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/cache-control-esm/-/cache-control-esm-1.0.0.tgz#417647ecf1837a5e74155f55d5a4ae32a84e2581"
|
||||
integrity sha512-Fa3UV4+eIk4EOih8FTV6EEsVKO0W5XWtNs6FC3InTfVz+EjurjPfDXY5wZDo/lxjDxg5RjNcurLyxEJBcEUx9g==
|
||||
|
||||
call-bind@^1.0.0, call-bind@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c"
|
||||
|
|
Loading…
Reference in New Issue