Skip to content

Commit

Permalink
Add UI for compatibility warning
Browse files Browse the repository at this point in the history
  • Loading branch information
gzdunek committed Feb 27, 2025
1 parent 49d1172 commit 3fd1f1e
Show file tree
Hide file tree
Showing 8 changed files with 303 additions and 60 deletions.
7 changes: 7 additions & 0 deletions web/packages/teleterm/src/services/config/appConfigSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export const CUSTOM_SHELL_ID = 'custom' as const;
*/
export const CONFIG_MODIFIABLE_FROM_RENDERER: (keyof AppConfig)[] = [
'usageReporting.enabled',
'skipVersionCheck',
];

export const createAppConfigSchema = (settings: RuntimeSettings) => {
Expand All @@ -56,6 +57,12 @@ export const createAppConfigSchema = (settings: RuntimeSettings) => {
.enum(['light', 'dark', 'system'])
.default('system')
.describe('Color theme for the app.'),
skipVersionCheck: z
.boolean()
.default(false)
.describe(
'Skips version checks between the Teleport cluster and Teleport Connect.'
),
/**
* This value can be provided by the user and is unsanitized. This means that it cannot be directly interpolated
* in a styled component or used in CSS, as it may inject malicious CSS code.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,29 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { Meta } from '@storybook/react';

import { makeErrorAttempt, makeProcessingAttempt } from 'shared/hooks/useAsync';

import { ClusterLoginPresentation } from './ClusterLogin';
import { makeProps, TestContainer } from './storyHelpers';

export default {
import {
compatibilityArgType,
makeProps,
StoryProps,
TestContainer,
} from './storyHelpers';

const meta: Meta<StoryProps> = {
title: 'Teleterm/ModalsHost/ClusterLogin',
argTypes: compatibilityArgType,
args: {
compatibility: 'compatible',
},
};
export default meta;

export const LocalOnly = () => {
const props = makeProps();
export const LocalOnly = (storyProps: StoryProps) => {
const props = makeProps(storyProps);
props.initAttempt.data.allowPasswordless = false;

return (
Expand All @@ -35,8 +47,8 @@ export const LocalOnly = () => {
);
};

export const InitErr = () => {
const props = makeProps();
export const InitErr = (storyProps: StoryProps) => {
const props = makeProps(storyProps);
props.initAttempt = makeErrorAttempt(new Error('some error message'));

return (
Expand All @@ -46,8 +58,8 @@ export const InitErr = () => {
);
};

export const InitProcessing = () => {
const props = makeProps();
export const InitProcessing = (storyProps: StoryProps) => {
const props = makeProps(storyProps);
props.initAttempt.status = 'processing';

return (
Expand All @@ -57,8 +69,8 @@ export const InitProcessing = () => {
);
};

export const LocalDisabled = () => {
const props = makeProps();
export const LocalDisabled = (storyProps: StoryProps) => {
const props = makeProps(storyProps);
props.initAttempt.data.localAuthEnabled = false;

return (
Expand All @@ -71,8 +83,8 @@ export const LocalDisabled = () => {
// The password field is empty in this story as there's no way to change the value of a controlled
// input without touching the internals of React.
// https://stackoverflow.com/questions/23892547/what-is-the-best-way-to-trigger-change-or-input-event-in-react-js
export const LocalProcessing = () => {
const props = makeProps();
export const LocalProcessing = (storyProps: StoryProps) => {
const props = makeProps(storyProps);
props.initAttempt.data.allowPasswordless = false;
props.loginAttempt = makeProcessingAttempt();
props.loggedInUserName = 'alice';
Expand All @@ -84,8 +96,8 @@ export const LocalProcessing = () => {
);
};

export const LocalError = () => {
const props = makeProps();
export const LocalError = (storyProps: StoryProps) => {
const props = makeProps(storyProps);
props.initAttempt.data.allowPasswordless = false;
props.loginAttempt = makeErrorAttempt(new Error('invalid credentials'));
props.loggedInUserName = 'alice';
Expand All @@ -102,8 +114,8 @@ const authProviders = [
{ type: 'saml', name: 'microsoft', displayName: 'Microsoft' },
];

export const SsoOnly = () => {
const props = makeProps();
export const SsoOnly = (storyProps: StoryProps) => {
const props = makeProps(storyProps);
props.initAttempt.data.localAuthEnabled = false;
props.initAttempt.data.authType = 'github';
props.initAttempt.data.authProviders = authProviders;
Expand All @@ -115,8 +127,8 @@ export const SsoOnly = () => {
);
};

export const SsoPrompt = () => {
const props = makeProps();
export const SsoPrompt = (storyProps: StoryProps) => {
const props = makeProps(storyProps);
props.loginAttempt.status = 'processing';
props.shouldPromptSsoStatus = true;
return (
Expand All @@ -126,8 +138,8 @@ export const SsoPrompt = () => {
);
};

export const SsoError = () => {
const props = makeProps();
export const SsoError = (storyProps: StoryProps) => {
const props = makeProps(storyProps);
props.initAttempt.data.localAuthEnabled = false;
props.initAttempt.data.authType = 'github';
props.initAttempt.data.authProviders = authProviders;
Expand All @@ -141,16 +153,16 @@ export const SsoError = () => {
);
};

export const LocalWithPasswordless = () => {
export const LocalWithPasswordless = (storyProps: StoryProps) => {
return (
<TestContainer>
<ClusterLoginPresentation {...makeProps()} />
<ClusterLoginPresentation {...makeProps(storyProps)} />
</TestContainer>
);
};

export const LocalLoggedInUserWithPasswordless = () => {
const props = makeProps();
export const LocalLoggedInUserWithPasswordless = (storyProps: StoryProps) => {
const props = makeProps(storyProps);
props.loggedInUserName = 'llama';

return (
Expand All @@ -160,8 +172,8 @@ export const LocalLoggedInUserWithPasswordless = () => {
);
};

export const LocalWithSso = () => {
const props = makeProps();
export const LocalWithSso = (storyProps: StoryProps) => {
const props = makeProps(storyProps);
props.initAttempt.data.authProviders = authProviders;

return (
Expand All @@ -171,8 +183,8 @@ export const LocalWithSso = () => {
);
};

export const PasswordlessWithLocal = () => {
const props = makeProps();
export const PasswordlessWithLocal = (storyProps: StoryProps) => {
const props = makeProps(storyProps);
props.initAttempt.data.localConnectorName = 'passwordless';

return (
Expand All @@ -182,8 +194,8 @@ export const PasswordlessWithLocal = () => {
);
};

export const PasswordlessWithLocalLoggedInUser = () => {
const props = makeProps();
export const PasswordlessWithLocalLoggedInUser = (storyProps: StoryProps) => {
const props = makeProps(storyProps);
props.initAttempt.data.localConnectorName = 'passwordless';
props.loggedInUserName = 'llama';

Expand All @@ -194,8 +206,8 @@ export const PasswordlessWithLocalLoggedInUser = () => {
);
};

export const SsoWithLocalAndPasswordless = () => {
const props = makeProps();
export const SsoWithLocalAndPasswordless = (storyProps: StoryProps) => {
const props = makeProps(storyProps);
props.initAttempt.data.authType = 'github';
props.initAttempt.data.authProviders = authProviders;

Expand All @@ -206,8 +218,8 @@ export const SsoWithLocalAndPasswordless = () => {
);
};

export const SsoWithNoProvidersConfigured = () => {
const props = makeProps();
export const SsoWithNoProvidersConfigured = (storyProps: StoryProps) => {
const props = makeProps(storyProps);
props.initAttempt.data.authType = 'github';

return (
Expand All @@ -217,8 +229,8 @@ export const SsoWithNoProvidersConfigured = () => {
);
};

export const HardwareTapPrompt = () => {
const props = makeProps();
export const HardwareTapPrompt = (storyProps: StoryProps) => {
const props = makeProps(storyProps);
props.loginAttempt.status = 'processing';
props.passwordlessLoginState = {
prompt: 'tap',
Expand All @@ -230,8 +242,8 @@ export const HardwareTapPrompt = () => {
);
};

export const HardwarePinPrompt = () => {
const props = makeProps();
export const HardwarePinPrompt = (storyProps: StoryProps) => {
const props = makeProps(storyProps);
props.loginAttempt.status = 'processing';
props.passwordlessLoginState = {
prompt: 'pin',
Expand All @@ -243,8 +255,8 @@ export const HardwarePinPrompt = () => {
);
};

export const HardwareRetapPrompt = () => {
const props = makeProps();
export const HardwareRetapPrompt = (storyProps: StoryProps) => {
const props = makeProps(storyProps);
props.loginAttempt.status = 'processing';
props.passwordlessLoginState = {
prompt: 'retap',
Expand All @@ -256,8 +268,8 @@ export const HardwareRetapPrompt = () => {
);
};

export const HardwareCredentialPrompt = () => {
const props = makeProps();
export const HardwareCredentialPrompt = (storyProps: StoryProps) => {
const props = makeProps(storyProps);
props.loginAttempt.status = 'processing';
props.passwordlessLoginState = {
prompt: 'credential',
Expand All @@ -278,8 +290,8 @@ export const HardwareCredentialPrompt = () => {
);
};

export const HardwareCredentialPromptProcessing = () => {
const props = makeProps();
export const HardwareCredentialPromptProcessing = (storyProps: StoryProps) => {
const props = makeProps(storyProps);
props.loginAttempt.status = 'processing';
props.passwordlessLoginState = {
prompt: 'credential',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ export function ClusterLoginPresentation({
shouldPromptSsoStatus,
passwordlessLoginState,
reason,
shouldSkipVersionCheck,
disableVersionCheck,
platform,
}: ClusterLoginPresentationProps) {
return (
<>
Expand Down Expand Up @@ -119,6 +122,9 @@ export function ClusterLoginPresentation({
clearLoginAttempt={clearLoginAttempt}
shouldPromptSsoStatus={shouldPromptSsoStatus}
passwordlessLoginState={passwordlessLoginState}
shouldSkipVersionCheck={shouldSkipVersionCheck}
disableVersionCheck={disableVersionCheck}
platform={platform}
/>
)}
</DialogContent>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { Meta } from '@storybook/react';

import {
makeApp,
makeDatabaseGateway,
Expand All @@ -23,14 +25,24 @@ import {
import { routing } from 'teleterm/ui/uri';

import { ClusterLoginPresentation } from './ClusterLogin';
import { makeProps, TestContainer } from './storyHelpers';
import {
compatibilityArgType,
makeProps,
StoryProps,
TestContainer,
} from './storyHelpers';

export default {
const meta: Meta<StoryProps> = {
title: 'Teleterm/ModalsHost/ClusterLogin/Reason',
argTypes: compatibilityArgType,
args: {
compatibility: 'compatible',
},
};
export default meta;

export const GatewayCertExpiredWithDbGateway = () => {
const props = makeProps();
export const GatewayCertExpiredWithDbGateway = (storyProps: StoryProps) => {
const props = makeProps(storyProps);
props.initAttempt.data.allowPasswordless = false;
props.reason = {
kind: 'reason.gateway-cert-expired',
Expand All @@ -45,8 +57,8 @@ export const GatewayCertExpiredWithDbGateway = () => {
);
};

export const GatewayCertExpiredWithKubeGateway = () => {
const props = makeProps();
export const GatewayCertExpiredWithKubeGateway = (storyProps: StoryProps) => {
const props = makeProps(storyProps);
props.initAttempt.data.allowPasswordless = false;
props.reason = {
kind: 'reason.gateway-cert-expired',
Expand All @@ -61,8 +73,8 @@ export const GatewayCertExpiredWithKubeGateway = () => {
);
};

export const GatewayCertExpiredWithoutGateway = () => {
const props = makeProps();
export const GatewayCertExpiredWithoutGateway = (storyProps: StoryProps) => {
const props = makeProps(storyProps);
props.initAttempt.data.allowPasswordless = false;
props.reason = {
kind: 'reason.gateway-cert-expired',
Expand All @@ -77,9 +89,9 @@ export const GatewayCertExpiredWithoutGateway = () => {
);
};

export const VnetCertExpired = () => {
export const VnetCertExpired = (storyProps: StoryProps) => {
const app = makeApp();
const props = makeProps();
const props = makeProps(storyProps);
props.initAttempt.data.allowPasswordless = false;
props.reason = {
kind: 'reason.vnet-cert-expired',
Expand All @@ -100,12 +112,12 @@ export const VnetCertExpired = () => {
);
};

export const VnetCertExpiredMultiPort = () => {
export const VnetCertExpiredMultiPort = (storyProps: StoryProps) => {
const app = makeApp({
endpointUri: 'tcp://localhost',
tcpPorts: [{ port: 1337, endPort: 0 }],
});
const props = makeProps();
const props = makeProps(storyProps);
props.initAttempt.data.allowPasswordless = false;
props.reason = {
kind: 'reason.vnet-cert-expired',
Expand Down
Loading

0 comments on commit 3fd1f1e

Please sign in to comment.