feat(settings): migrate SessionLifetimeSelect to React [BE-12583] (#1829)

Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Chaim Lev-Ari
2026-02-26 15:39:08 +02:00
committed by GitHub
parent a01dd005fd
commit 56a840e207
7 changed files with 104 additions and 43 deletions

View File

@@ -15,10 +15,15 @@ import { HiddenContainersPanel } from '@/react/portainer/settings/SettingsView/H
import { SSLSettingsPanelWrapper } from '@/react/portainer/settings/SettingsView/SSLSettingsPanel/SSLSettingsPanel';
import { AuthStyleField } from '@/react/portainer/settings/AuthenticationView/OAuth';
import { AutoUserProvisionToggle } from '@/react/portainer/settings/AuthenticationView/AutoUserProvisionToggle/AutoUserProvisionToggle';
import { SessionLifetimeSelect } from '@/react/portainer/settings/AuthenticationView/SessionLifetimeSelect';
export const settingsModule = angular
.module('portainer.app.react.components.settings', [])
.component('settingsOpenAmt', r2a(SettingsOpenAMT, ['onSubmit', 'settings']))
.component(
'sessionLifetimeSelect',
r2a(SessionLifetimeSelect, ['value', 'onChange'])
)
.component(
'internalAuth',
r2a(InternalAuth, ['onSaveSettings', 'isLoading', 'value', 'onChange'])

View File

@@ -144,7 +144,7 @@ BE-6604 (Parent: Authentication Settings Migration)
### Session & Page Structure (Parallel - No Dependencies)
- [ ] PR 1: [BE-12583](https://linear.app/portainer/issue/BE-12583) SessionLifetimeSelect → react2angular bridge
- [x] PR 1: [BE-12583](https://linear.app/portainer/issue/BE-12583) SessionLifetimeSelect → react2angular bridge
- [x] PR 2: [BE-12584](https://linear.app/portainer/issue/BE-12584) AuthenticationMethodSelector wrapper (BoxSelector is already React)
### Internal Auth (Already Done!)

View File

@@ -7,21 +7,12 @@
<rd-widget-body>
<form class="form-horizontal" name="authSettingsForm">
<div class="col-sm-12 form-section-title"> Configuration </div>
<div class="form-group">
<label for="user_timeout" class="col-sm-2 control-label text-left">
Session lifetime
<portainer-tooltip message="'Time before users are forced to relogin.'"></portainer-tooltip>
</label>
<div class="col-sm-10">
<select
id="user_timeout"
data-cy="user-timeout-select"
class="form-control"
ng-model="settings.UserSessionTimeout"
ng-options="opt.value as opt.key for opt in state.availableUserSessionTimeoutOptions"
></select>
</div>
</div>
<session-lifetime-select
value="settings.UserSessionTimeout"
on-change="(onChangeSessionLifetime)">
</session-lifetime-select>
<div class="form-group">
<span class="col-sm-12 text-muted small vertical-center">
<pr-icon icon="'alert-triangle'" class-name="'icon-sm icon-yellow'"></pr-icon>

View File

@@ -4,6 +4,7 @@ import _ from 'lodash-es';
import { buildLdapSettingsModel, buildAdSettingsModel } from '@/portainer/settings/authentication/ldap/ldap-settings.model';
import { SERVER_TYPES } from '@/react/portainer/settings/AuthenticationView/ldap-options';
import { AuthenticationMethod } from '@/react/portainer/settings/types';
import { getDefaultValue as getDefaultSessionValue } from '@/react/portainer/settings/AuthenticationView/SessionLifetimeSelect';
angular.module('portainer.app').controller('SettingsAuthenticationController', SettingsAuthenticationController);
@@ -13,36 +14,10 @@ function SettingsAuthenticationController($q, $scope, $state, Notifications, Set
$scope.state = {
uploadInProgress: false,
actionInProgress: false,
availableUserSessionTimeoutOptions: [
{
key: '30 minutes',
value: '30m',
},
{
key: '1 hour',
value: '1h',
},
{
key: '4 hours',
value: '4h',
},
{
key: '8 hours',
value: '8h',
},
{
key: '24 hours',
value: '24h',
},
{ key: '1 week', value: `${24 * 7}h` },
{ key: '1 month', value: `${24 * 30}h` },
{ key: '6 months', value: `${24 * 30 * 6}h` },
{ key: '1 year', value: `${24 * 30 * 12}h` },
],
};
$scope.formValues = {
UserSessionTimeout: $scope.state.availableUserSessionTimeoutOptions[0],
UserSessionTimeout: getDefaultSessionValue(),
TLSCACert: '',
ldap: {
serverType: 0,
@@ -76,6 +51,12 @@ function SettingsAuthenticationController($q, $scope, $state, Notifications, Set
});
};
$scope.onChangeSessionLifetime = function onChangeSessionLifetime(value) {
$scope.$evalAsync(() => {
$scope.settings.UserSessionTimeout = value;
});
};
$scope.authenticationMethodSelected = function authenticationMethodSelected(value) {
if (!$scope.settings) {
return false;

View File

@@ -0,0 +1,24 @@
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { SessionLifetimeSelect } from './SessionLifetimeSelect';
describe('SessionLifetimeSelect', () => {
test('should render with the correct label and tooltip', () => {
const onChange = vi.fn();
render(<SessionLifetimeSelect value="1h" onChange={onChange} />);
expect(screen.getByLabelText('Session lifetime')).toBeInTheDocument();
});
test('should call onChange when selecting a new value', async () => {
const user = userEvent.setup();
const onChange = vi.fn();
render(<SessionLifetimeSelect value="1h" onChange={onChange} />);
const select = screen.getByRole('combobox');
await user.selectOptions(select, '8h');
expect(onChange).toHaveBeenCalledWith('8h');
});
});

View File

@@ -0,0 +1,60 @@
import { FormControl } from '@@/form-components/FormControl';
import { Select } from '@@/form-components/Input';
const sessionLifetimeOptions = [
{
key: '30 minutes',
value: '30m',
},
{
key: '1 hour',
value: '1h',
},
{
key: '4 hours',
value: '4h',
},
{
key: '8 hours',
value: '8h',
},
{
key: '24 hours',
value: '24h',
},
{ key: '1 week', value: `${24 * 7}h` },
{ key: '1 month', value: `${24 * 30}h` },
{ key: '6 months', value: `${24 * 30 * 6}h` },
{ key: '1 year', value: `${24 * 30 * 12}h` },
] as const;
export function getDefaultValue() {
return sessionLifetimeOptions[0];
}
interface Props {
value: string;
onChange(value: string): void;
}
export function SessionLifetimeSelect({ value, onChange }: Props) {
return (
<FormControl
inputId="user_timeout"
label="Session lifetime"
tooltip="Time before users are forced to relogin."
>
<Select
id="user_timeout"
data-cy="user-timeout-select"
name="user_timeout"
value={value}
onChange={(e) => onChange(e.target.value)}
options={sessionLifetimeOptions.map((opt) => ({
label: opt.key,
value: opt.value,
}))}
/>
</FormControl>
);
}