mirror of
https://github.com/appwrite/console.git
synced 2026-06-06 19:27:48 +00:00
Merge branch 'main' of github.com:appwrite/console into feat-databases-v2
This commit is contained in:
@@ -141,6 +141,8 @@ export enum Submit {
|
||||
TeamUpdateName = 'submit_team_update_name',
|
||||
AuthLimitUpdate = 'submit_auth_limit_update',
|
||||
AuthStatusUpdate = 'submit_auth_status_update',
|
||||
AuthPasswordHistoryUpdate = 'submit_auth_password_history_limit_update',
|
||||
AuthPasswordDictionaryUpdate = 'submit_auth_password_dictionary_update',
|
||||
SessionsLengthUpdate = 'submit_sessions_length_update',
|
||||
SessionsLimitUpdate = 'submit_sessions_limit_update',
|
||||
SessionDelete = 'submit_session_delete',
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
export let maxlength: number = null;
|
||||
|
||||
// https://www.geeksforgeeks.org/how-to-validate-a-domain-name-using-regular-expression/
|
||||
const pattern = String.raw`^(?!-)[A-Za-z0-9-]+([\-\.]{1}[a-z0-9]+)*\.[A-Za-z]{2,6}$`;
|
||||
const pattern = String.raw`^(?!-)[A-Za-z0-9-]+([\-\.]{1}[a-z0-9]+)*\.[A-Za-z]{2,18}$`;
|
||||
|
||||
let element: HTMLInputElement;
|
||||
let error: string;
|
||||
|
||||
@@ -163,7 +163,7 @@
|
||||
</div>
|
||||
|
||||
{#if subNavigation}
|
||||
<div class="side-nav-level-2 is-open" transition:slide={{ axis: 'x', duration: 250 }}>
|
||||
<div class="side-nav-level-2 is-open" transition:slide|local={{ axis: 'x', duration: 250 }}>
|
||||
<div class="side-nav-main">
|
||||
<svelte:component this={subNavigation} />
|
||||
</div>
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
<script lang="ts">
|
||||
import { navigating, page } from '$app/stores';
|
||||
import { browser } from '$app/environment';
|
||||
import { wizard } from '$lib/stores/wizard';
|
||||
import { log } from '$lib/stores/logs';
|
||||
import { beforeNavigate } from '$app/navigation';
|
||||
import { page } from '$app/stores';
|
||||
import { log } from '$lib/stores/logs';
|
||||
import { wizard } from '$lib/stores/wizard';
|
||||
|
||||
export let isOpen = false;
|
||||
export let showSideNavigation = false;
|
||||
|
||||
let y: number;
|
||||
|
||||
navigating.subscribe(() => {
|
||||
if (isOpen) isOpen = false;
|
||||
page.subscribe(({ url }) => {
|
||||
isOpen = url.searchParams.get('openNavbar') === 'true';
|
||||
});
|
||||
|
||||
const toggleMenu = () => {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { Container } from '$lib/layout';
|
||||
import UpdatePasswordDictionary from './updatePasswordDictionary.svelte';
|
||||
import UpdatePasswordHistory from './updatePasswordHistory.svelte';
|
||||
import UpdateSessionLength from './updateSessionLength.svelte';
|
||||
import UpdateSessionsLimit from './updateSessionsLimit.svelte';
|
||||
import UpdateUsersLimit from './updateUsersLimit.svelte';
|
||||
@@ -9,4 +11,6 @@
|
||||
<UpdateUsersLimit />
|
||||
<UpdateSessionLength />
|
||||
<UpdateSessionsLimit />
|
||||
<UpdatePasswordHistory />
|
||||
<UpdatePasswordDictionary />
|
||||
</Container>
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
<script lang="ts">
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
|
||||
import { CardGrid, Heading } from '$lib/components';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { Button, Form, FormList, InputSwitch } from '$lib/elements/forms';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { project } from '../../store';
|
||||
|
||||
const projectId = $project.$id;
|
||||
let passwordDictionary = $project.authPasswordDictionary ?? false;
|
||||
|
||||
async function updatePasswordDictionary() {
|
||||
try {
|
||||
await sdk.forConsole.projects.updateAuthPasswordDictionary(
|
||||
projectId,
|
||||
passwordDictionary
|
||||
);
|
||||
await invalidate(Dependencies.PROJECT);
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: 'Updated password dictionary check.'
|
||||
});
|
||||
trackEvent(Submit.AuthPasswordDictionaryUpdate);
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: error.message
|
||||
});
|
||||
trackError(error, Submit.AuthPasswordDictionaryUpdate);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Form onSubmit={updatePasswordDictionary}>
|
||||
<CardGrid>
|
||||
<Heading tag="h2" size="6">Password Dictionary</Heading>
|
||||
<svelte:fragment slot="aside">
|
||||
<FormList>
|
||||
<InputSwitch
|
||||
bind:value={passwordDictionary}
|
||||
id="passwordDictionary"
|
||||
label="Password Dictionary" />
|
||||
</FormList>
|
||||
<p class="text">
|
||||
Enabling this option prevent users from setting insecure passwords by comparing the
|
||||
user's password with the <a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="link"
|
||||
href="https://github.com/danielmiessler/SecLists/blob/master/Passwords/Common-Credentials/10k-most-common.txt"
|
||||
>10k most commonly used passwords.</a>
|
||||
</p>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button disabled={passwordDictionary === $project.authPasswordDictionary} submit
|
||||
>Update</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
</Form>
|
||||
@@ -0,0 +1,76 @@
|
||||
<script lang="ts">
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
|
||||
import { CardGrid, Heading } from '$lib/components';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { Button, Form, InputNumber, InputSwitch } from '$lib/elements/forms';
|
||||
import FormList from '$lib/elements/forms/formList.svelte';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { project } from '../../store';
|
||||
|
||||
const projectId = $project.$id;
|
||||
let passwordHistory = $project.authPasswordHistory < 1 ? 5 : $project.authPasswordHistory;
|
||||
let passwordHistoryEnabled = ($project.authPasswordHistory ?? 0) != 0;
|
||||
let initialPasswordHistoryEnabled = passwordHistoryEnabled;
|
||||
|
||||
async function updatePasswordHistoryLimit() {
|
||||
try {
|
||||
await sdk.forConsole.projects.updateAuthPasswordHistory(
|
||||
projectId,
|
||||
passwordHistoryEnabled ? passwordHistory : 0
|
||||
);
|
||||
await invalidate(Dependencies.PROJECT);
|
||||
initialPasswordHistoryEnabled = passwordHistoryEnabled;
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: 'Updated password history limit.'
|
||||
});
|
||||
trackEvent(Submit.AuthPasswordHistoryUpdate);
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: error.message
|
||||
});
|
||||
trackError(error, Submit.AuthPasswordHistoryUpdate);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Form onSubmit={updatePasswordHistoryLimit}>
|
||||
<CardGrid>
|
||||
<Heading tag="h2" size="6">Password History</Heading>
|
||||
<svelte:fragment slot="aside">
|
||||
<FormList>
|
||||
<InputSwitch
|
||||
bind:value={passwordHistoryEnabled}
|
||||
id="passwordHisotryEnabled"
|
||||
label="Password History" />
|
||||
</FormList>
|
||||
<p class="text">
|
||||
Enabling this option prevents users from reusing recent passwords by comparing the
|
||||
new password with their password history.
|
||||
</p>
|
||||
<p class="text">
|
||||
Set the maximum number of passwords saved per user. Maximum 20 passwords.
|
||||
</p>
|
||||
<FormList>
|
||||
<InputNumber
|
||||
max={20}
|
||||
min={1}
|
||||
id="max-session"
|
||||
label="Limit"
|
||||
disabled={!passwordHistoryEnabled}
|
||||
bind:value={passwordHistory} />
|
||||
</FormList>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button
|
||||
disabled={(passwordHistory === $project.authPasswordHistory ||
|
||||
$project.authPasswordHistory === 0) &&
|
||||
initialPasswordHistoryEnabled === passwordHistoryEnabled}
|
||||
submit>Update</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
</Form>
|
||||
+1
-1
@@ -17,7 +17,7 @@
|
||||
<section class="drop-section u-flex-vertical u-gap-8">
|
||||
<a
|
||||
class="u-flex u-cross-center u-sep-block-end u-padding-block-12 is-not-desktop"
|
||||
href={`${base}/console/project-${project}/databases/database-${databaseId}`}>
|
||||
href={`${base}/console/project-${project}/databases/database-${databaseId}?openNavbar=true`}>
|
||||
<span class="icon-cheveron-left" aria-hidden="true" />
|
||||
<h5 class="eyebrow-heading-3 u-margin-inline-auto">Collections</h5>
|
||||
</a>
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
import '@testing-library/jest-dom';
|
||||
import { render } from '@testing-library/svelte';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { InputDomain } from '../../../src/lib/elements/forms';
|
||||
|
||||
const validStrings = ['example.com', 'i-need.travelersinsurance', 'sub.domain.com'];
|
||||
const invalidStrings = ['not-a-domain', 'tld-too-short.a', 'tld-too-long.overeighteencharacters'];
|
||||
|
||||
test('shows domain input', () => {
|
||||
const { getByText, getByLabelText } = render(InputDomain, { id: 'input', label: 'input' });
|
||||
const input = getByLabelText('input');
|
||||
|
||||
expect(getByText('input')).toBeInTheDocument();
|
||||
expect(input).toBeInTheDocument();
|
||||
expect(input).toHaveAttribute('type', 'text');
|
||||
});
|
||||
|
||||
test('shows domain input - required', () => {
|
||||
const { getByLabelText } = render(InputDomain, { id: 'input', label: 'input', required: true });
|
||||
|
||||
expect(getByLabelText('input')).toBeRequired();
|
||||
});
|
||||
|
||||
test('shows domain input - disabled', () => {
|
||||
const { getByLabelText } = render(InputDomain, { id: 'input', label: 'input', disabled: true });
|
||||
|
||||
expect(getByLabelText('input')).toBeDisabled();
|
||||
});
|
||||
|
||||
test('shows domain input - autofocus', () => {
|
||||
const { getByLabelText } = render(InputDomain, {
|
||||
id: 'input',
|
||||
label: 'input',
|
||||
autofocus: true
|
||||
});
|
||||
|
||||
expect(getByLabelText('input')).toHaveFocus();
|
||||
});
|
||||
|
||||
test('shows domain input - placeholder', () => {
|
||||
const { getByPlaceholderText } = render(InputDomain, {
|
||||
id: 'input',
|
||||
label: 'input',
|
||||
placeholder: 'find me'
|
||||
});
|
||||
|
||||
expect(getByPlaceholderText('find me')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('shows domain input - hide label', () => {
|
||||
render(InputDomain, {
|
||||
id: 'input',
|
||||
label: 'label',
|
||||
showLabel: false
|
||||
});
|
||||
|
||||
const label = document.querySelector('label');
|
||||
expect(label).toHaveClass('u-hide');
|
||||
});
|
||||
|
||||
test('shows domain input - autocomplete', () => {
|
||||
const { getByLabelText } = render(InputDomain, {
|
||||
id: 'input',
|
||||
label: 'input',
|
||||
autocomplete: true
|
||||
});
|
||||
|
||||
expect(getByLabelText('input')).toHaveAttribute('autocomplete', 'on');
|
||||
});
|
||||
|
||||
test('shows domain input - maxlength', () => {
|
||||
const { getByLabelText } = render(InputDomain, {
|
||||
id: 'input',
|
||||
label: 'input',
|
||||
maxlength: 2
|
||||
});
|
||||
|
||||
expect(getByLabelText('input')).toHaveAttribute('maxlength', '2');
|
||||
});
|
||||
|
||||
test('state', async () => {
|
||||
const { component, getByLabelText } = render(InputDomain, {
|
||||
id: 'input',
|
||||
label: 'input',
|
||||
value: ''
|
||||
});
|
||||
const input = getByLabelText('input');
|
||||
|
||||
expect(component.value).toEqual('');
|
||||
await userEvent.type(input, 'lorem');
|
||||
expect(component.value).toEqual('lorem');
|
||||
});
|
||||
|
||||
validStrings.forEach((validString) => {
|
||||
test(`validates ${validString} as valid`, () => {
|
||||
const { getByLabelText } = render(InputDomain, {
|
||||
id: 'input',
|
||||
label: 'input',
|
||||
value: validString
|
||||
});
|
||||
const input = getByLabelText('input') as HTMLInputElement;
|
||||
expect(input.checkValidity()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
invalidStrings.forEach((invalidString) => {
|
||||
test(`validates ${invalidString} as invalid`, () => {
|
||||
const { getByLabelText } = render(InputDomain, {
|
||||
id: 'input',
|
||||
label: 'input',
|
||||
value: invalidString
|
||||
});
|
||||
const input = getByLabelText('input') as HTMLInputElement;
|
||||
expect(input.checkValidity()).toBe(false);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user