feat: add new settings view for Keys
Currently the SSH and GPG keys are hardcoded. In a refactoring those will be passed down as prop to the component. But I would need to query Vervis to receive the real keys. Likewise I am not talking to Vervis when updating (remove, add) a key. Signed-off-by: André Jaenisch <andre.jaenisch@posteo.de>
This commit is contained in:
parent
03c8334338
commit
55602e359a
13 changed files with 415 additions and 9 deletions
10
package-lock.json
generated
10
package-lock.json
generated
|
@ -1,14 +1,15 @@
|
|||
{
|
||||
"name": "anvil",
|
||||
"version": "0.0.11",
|
||||
"version": "0.0.12",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "anvil",
|
||||
"version": "0.0.11",
|
||||
"version": "0.0.12",
|
||||
"dependencies": {
|
||||
"@floating-ui/dom": "1.6.8",
|
||||
"@fontsource/spline-sans-mono": "5.0.20",
|
||||
"axios": "1.7.2",
|
||||
"cheerio": "1.0.0-rc.12"
|
||||
},
|
||||
|
@ -2202,6 +2203,11 @@
|
|||
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.5.tgz",
|
||||
"integrity": "sha512-sTcG+QZ6fdEUObICavU+aB3Mp8HY4n14wYHdxK4fXjPmv3PXZZeY5RaguJmGyeH/CJQhX3fqKUtS4qc1LoHwhQ=="
|
||||
},
|
||||
"node_modules/@fontsource/spline-sans-mono": {
|
||||
"version": "5.0.20",
|
||||
"resolved": "https://registry.npmjs.org/@fontsource/spline-sans-mono/-/spline-sans-mono-5.0.20.tgz",
|
||||
"integrity": "sha512-4u23nOkNK1B6rkmNfiodQaMu9CKtKzrK/lTEAhBuQT08cTm6544uTQSoH8ssylDbdyYZZAWyVA2Mua/TdIuoxQ=="
|
||||
},
|
||||
"node_modules/@formatjs/ecma402-abstract": {
|
||||
"version": "1.18.2",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.18.2.tgz",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "anvil",
|
||||
"version": "0.0.11",
|
||||
"version": "0.0.12",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
|
@ -75,6 +75,7 @@
|
|||
"type": "module",
|
||||
"dependencies": {
|
||||
"@floating-ui/dom": "1.6.8",
|
||||
"@fontsource/spline-sans-mono": "5.0.20",
|
||||
"axios": "1.7.2",
|
||||
"cheerio": "1.0.0-rc.12"
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ You should have received a copy of the GNU Affero General Public License along w
|
|||
|
||||
<script>
|
||||
import SettingsAccount from './SettingsAccount.svelte';
|
||||
import SettingsKeys from './SettingsKeys.svelte';
|
||||
import SettingsSidebar from '../atoms/SettingsSidebar.svelte';
|
||||
import SettingsProfile from './SettingsProfile.svelte';
|
||||
|
||||
|
@ -30,5 +31,7 @@ You should have received a copy of the GNU Affero General Public License along w
|
|||
<SettingsProfile />
|
||||
{:else if activeSetting === 'account'}
|
||||
<SettingsAccount />
|
||||
{:else if activeSetting === 'ssh_gpg_keys'}
|
||||
<SettingsKeys />
|
||||
{/if}
|
||||
</div>
|
||||
|
|
111
src/lib/components/molecules/SettingsKeys.svelte
Normal file
111
src/lib/components/molecules/SettingsKeys.svelte
Normal file
|
@ -0,0 +1,111 @@
|
|||
<!--
|
||||
SettingsKeys molecule.
|
||||
Copyright (C) 2024 André Jaenisch
|
||||
SPDX-FileCopyrightText: 2024 André Jaenisch
|
||||
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
|
||||
|
||||
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/>.
|
||||
-->
|
||||
|
||||
<script>
|
||||
import '@fontsource/spline-sans-mono';
|
||||
import { _ } from 'svelte-i18n';
|
||||
import { Key24 } from 'svelte-octicons';
|
||||
|
||||
const gpg_keys = [
|
||||
{
|
||||
title: 'Work key',
|
||||
fingerprint: 'CB9E C70F 2421 AF06 7D72 F980 8287 6A15 311B 1F84'
|
||||
}
|
||||
];
|
||||
|
||||
const ssh_keys = [
|
||||
{
|
||||
title: 'MyLaptop',
|
||||
fingerprint: '25:c7:06:3f:87:49:5d:95:96:3d:a8'
|
||||
},
|
||||
{
|
||||
title: 'Desktop',
|
||||
fingerprint: '87:49:5d:95:96:3d:a8:25:c7:06:3f'
|
||||
}
|
||||
];
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col flex-1 gap-4 pe-8 pb-8 ps-8 pt-20 bg-surface-100">
|
||||
<div class="flex text-surface-500 text-xl font-semibold">
|
||||
{$_('settings.ssh_gpg_keys.headline')}
|
||||
</div>
|
||||
<div class="flex flex-col gap-6">
|
||||
<div class="flex flex-col gap-1">
|
||||
<div class="flex text-surface-500 font-semibold py-1">
|
||||
{$_('settings.ssh_gpg_keys.ssh.headline')}
|
||||
</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
<!-- Nesting required to trigger last: class -->
|
||||
{#each ssh_keys as key}
|
||||
<div
|
||||
class="flex border-b border-surface-200 last:border-b-0 items-center justify-between gap-4 pb-2"
|
||||
>
|
||||
<Key24 fill="currentColor" />
|
||||
<span class="text-surface-500 flex-1 shrink-0">{key.title}</span>
|
||||
<code class="text-surface-500 shrink-0">{key.fingerprint}</code>
|
||||
<button type="button" class="btn font-semibold variant-filled-warning"
|
||||
>{$_('settings.ssh_gpg_keys.ssh.remove')}</button
|
||||
>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
class="input bg-white placeholder:text-surface-300"
|
||||
placeholder={$_('settings.ssh_gpg_keys.ssh.title.placeholder')}
|
||||
/>
|
||||
<textarea
|
||||
placeholder={$_('settings.ssh_gpg_keys.ssh.key.placeholder')}
|
||||
rows="4"
|
||||
class="placeholder:text-surface-300"
|
||||
></textarea>
|
||||
<button type="button" class="btn font-semibold text-white variant-filled-success self-end"
|
||||
>{$_('settings.ssh_gpg_keys.ssh.add')}</button
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-1">
|
||||
<div class="flex text-surface-500 font-semibold py-1">
|
||||
{$_('settings.ssh_gpg_keys.gpg.headline')}
|
||||
</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
<!-- Nesting required to trigger last: class -->
|
||||
{#each gpg_keys as key}
|
||||
<div
|
||||
class="flex border-b border-surface-200 last:border-b-0 items-center justify-between gap-4 pb-2"
|
||||
>
|
||||
<Key24 fill="currentColor" />
|
||||
<span class="text-surface-500 flex-1 shrink-0">{key.title}</span>
|
||||
<code class="text-surface-500 font-medium shrink-0 w-[245px]">{key.fingerprint}</code>
|
||||
<button type="button" class="btn font-semibold variant-filled-warning"
|
||||
>{$_('settings.ssh_gpg_keys.gpg.remove')}</button
|
||||
>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
class="input bg-white placeholder:text-surface-300"
|
||||
placeholder={$_('settings.ssh_gpg_keys.gpg.title.placeholder')}
|
||||
/>
|
||||
<textarea
|
||||
placeholder={$_('settings.ssh_gpg_keys.gpg.key.placeholder')}
|
||||
rows="4"
|
||||
class="placeholder:text-surface-300"
|
||||
></textarea>
|
||||
<button type="button" class="btn font-semibold text-white variant-filled-success self-end"
|
||||
>{$_('settings.ssh_gpg_keys.gpg.add')}</button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -228,7 +228,30 @@
|
|||
}
|
||||
},
|
||||
"ssh_gpg_keys": {
|
||||
"label": ""
|
||||
"gpg": {
|
||||
"add": "",
|
||||
"headline": "",
|
||||
"key": {
|
||||
"placeholder": ""
|
||||
},
|
||||
"remove": "",
|
||||
"title": {
|
||||
"placeholder": ""
|
||||
}
|
||||
},
|
||||
"headline": "",
|
||||
"label": "",
|
||||
"ssh": {
|
||||
"add": "",
|
||||
"headline": "",
|
||||
"key": {
|
||||
"placeholder": ""
|
||||
},
|
||||
"remove": "",
|
||||
"title": {
|
||||
"placeholder": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -228,7 +228,30 @@
|
|||
}
|
||||
},
|
||||
"ssh_gpg_keys": {
|
||||
"label": ""
|
||||
"gpg": {
|
||||
"add": "",
|
||||
"headline": "",
|
||||
"key": {
|
||||
"placeholder": ""
|
||||
},
|
||||
"remove": "",
|
||||
"title": {
|
||||
"placeholder": ""
|
||||
}
|
||||
},
|
||||
"headline": "",
|
||||
"label": "",
|
||||
"ssh": {
|
||||
"add": "",
|
||||
"headline": "",
|
||||
"key": {
|
||||
"placeholder": ""
|
||||
},
|
||||
"remove": "",
|
||||
"title": {
|
||||
"placeholder": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -228,7 +228,30 @@
|
|||
}
|
||||
},
|
||||
"ssh_gpg_keys": {
|
||||
"label": "SSH/GPG Keys"
|
||||
"gpg": {
|
||||
"add": "Add GPG key",
|
||||
"headline": "GPG",
|
||||
"key": {
|
||||
"placeholder": "Don't paste the private part of the GPG key. Paste the public part which begins with '-----BEGIN PGP PUBLIC KEY BLOCK-----'."
|
||||
},
|
||||
"remove": "Remove",
|
||||
"title": {
|
||||
"placeholder": "Title"
|
||||
}
|
||||
},
|
||||
"headline": "SSH/GPG Keys",
|
||||
"label": "SSH/GPG Keys",
|
||||
"ssh": {
|
||||
"add": "Add SSH key",
|
||||
"headline": "SSH",
|
||||
"key": {
|
||||
"placeholder": "Begins with 'ssh-rsa', 'ecdsa-sha2-nistp256', 'ecdsa-sha2-nistp384', 'ecdsa-sha2-nistp521', 'ssh-ed25519', 'sk-ecdsa-sha2-nistp256@openssh.com', or 'sk-ssh-ed25519@openssh.com'"
|
||||
},
|
||||
"remove": "Remove",
|
||||
"title": {
|
||||
"placeholder": "Title"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -228,7 +228,30 @@
|
|||
}
|
||||
},
|
||||
"ssh_gpg_keys": {
|
||||
"label": ""
|
||||
"gpg": {
|
||||
"add": "",
|
||||
"headline": "",
|
||||
"key": {
|
||||
"placeholder": ""
|
||||
},
|
||||
"remove": "",
|
||||
"title": {
|
||||
"placeholder": ""
|
||||
}
|
||||
},
|
||||
"headline": "",
|
||||
"label": "",
|
||||
"ssh": {
|
||||
"add": "",
|
||||
"headline": "",
|
||||
"key": {
|
||||
"placeholder": ""
|
||||
},
|
||||
"remove": "",
|
||||
"title": {
|
||||
"placeholder": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -228,7 +228,30 @@
|
|||
}
|
||||
},
|
||||
"ssh_gpg_keys": {
|
||||
"label": ""
|
||||
"gpg": {
|
||||
"add": "",
|
||||
"headline": "",
|
||||
"key": {
|
||||
"placeholder": ""
|
||||
},
|
||||
"remove": "",
|
||||
"title": {
|
||||
"placeholder": ""
|
||||
}
|
||||
},
|
||||
"headline": "",
|
||||
"label": "",
|
||||
"ssh": {
|
||||
"add": "",
|
||||
"headline": "",
|
||||
"key": {
|
||||
"placeholder": ""
|
||||
},
|
||||
"remove": "",
|
||||
"title": {
|
||||
"placeholder": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
36
stories/icons/Key24.stories.ts
Normal file
36
stories/icons/Key24.stories.ts
Normal file
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Eric Liu
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*/
|
||||
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 - 2024 Eric Liu
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
import type { Meta, StoryObj } from '@storybook/svelte';
|
||||
import { Key24 } from 'svelte-octicons';
|
||||
|
||||
const meta = {
|
||||
title: 'Icons/Key24',
|
||||
component: Key24,
|
||||
tags: ['autodocs'],
|
||||
argTypes: {
|
||||
fill: {
|
||||
control: 'color'
|
||||
}
|
||||
}
|
||||
} satisfies Meta<Key24>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Plain: Story = {};
|
26
stories/molecules/SetttingsKeys.stories.ts
Normal file
26
stories/molecules/SetttingsKeys.stories.ts
Normal file
|
@ -0,0 +1,26 @@
|
|||
/* Stories for SettingsKeys molecule.
|
||||
* Copyright (C) 2024 André Jaenisch
|
||||
* SPDX-FileCopyrightText: 2024 André Jaenisch
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
|
||||
*
|
||||
* 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 type { Meta, StoryObj } from '@storybook/svelte';
|
||||
|
||||
import SettingsKeys from '$lib/components/molecules/SettingsKeys.svelte';
|
||||
|
||||
const meta = {
|
||||
title: 'Molecules/SettingsKeys',
|
||||
component: SettingsKeys,
|
||||
tags: ['autodocs']
|
||||
} satisfies Meta<SettingsKeys>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Plain: Story = {};
|
|
@ -24,7 +24,20 @@ export default {
|
|||
join(require.resolve('@skeletonlabs/skeleton'), '../**/*.{html,js,svelte,ts}')
|
||||
],
|
||||
theme: {
|
||||
extend: {}
|
||||
extend: {},
|
||||
fontFamily: {
|
||||
mono: [
|
||||
'ui-monospace',
|
||||
'Spline Sans Mono',
|
||||
'SFMono-Regular',
|
||||
'Menlo',
|
||||
'Monaco',
|
||||
'Consolas',
|
||||
'Liberation Mono',
|
||||
'Courier New',
|
||||
'monospace'
|
||||
]
|
||||
}
|
||||
},
|
||||
plugins: [require('@tailwindcss/typography'), forms, skeleton]
|
||||
};
|
||||
|
|
95
tests/components/molecules/SettingsKeys.test.ts
Normal file
95
tests/components/molecules/SettingsKeys.test.ts
Normal file
|
@ -0,0 +1,95 @@
|
|||
/* Component test for SettingsKeys molecule.
|
||||
* Copyright (C) 2024 André Jaenisch
|
||||
* SPDX-FileCopyrightText: 2024 André Jaenisch
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
|
||||
*
|
||||
* 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 '@testing-library/jest-dom';
|
||||
import { render, screen } from '@testing-library/svelte';
|
||||
import { init, locale, register } from 'svelte-i18n';
|
||||
|
||||
import SettingsKeys from '../../../src/lib/components/molecules/SettingsKeys.svelte';
|
||||
import enMessages from '../../../src/lib/i18n/locales/en.json';
|
||||
|
||||
describe('SettingsKeys.svelte', () => {
|
||||
beforeEach(() => {
|
||||
register('en', () => import('../../../src/lib/i18n/locales/en.json'));
|
||||
init({ fallbackLocale: 'en', initialLocale: 'en' });
|
||||
locale.set('en');
|
||||
});
|
||||
|
||||
it('should mount', () => {
|
||||
// Arrange
|
||||
// Nothing to prepare
|
||||
|
||||
// Act
|
||||
const { container } = render(SettingsKeys);
|
||||
|
||||
// Assert
|
||||
expect(container).toBeTruthy();
|
||||
});
|
||||
|
||||
describe('SSH', () => {
|
||||
it('should have a way to remove a SSH key', () => {
|
||||
// Arrange
|
||||
// Currently those keys are hardcoded
|
||||
const numberOfKeys = 2 /* SSH */ + 1; /* GPG */
|
||||
|
||||
// Act
|
||||
render(SettingsKeys);
|
||||
|
||||
// Assert
|
||||
expect(
|
||||
screen.getAllByRole('button', { name: enMessages.settings.ssh_gpg_keys.ssh.remove })
|
||||
).toHaveLength(numberOfKeys);
|
||||
});
|
||||
|
||||
it('should have a way to add a SSH key', () => {
|
||||
// Arrange
|
||||
// Nothing to prepare
|
||||
|
||||
// Act
|
||||
render(SettingsKeys);
|
||||
|
||||
// Assert
|
||||
expect(
|
||||
screen.getByRole('button', { name: enMessages.settings.ssh_gpg_keys.ssh.add })
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('GPG', () => {
|
||||
it('should have a way to remove a GPG key', () => {
|
||||
// Arrange
|
||||
// Currently those keys are hardcoded
|
||||
const numberOfKeys = 2 /* SSH */ + 1; /* GPG */
|
||||
|
||||
// Act
|
||||
render(SettingsKeys);
|
||||
|
||||
// Assert
|
||||
expect(
|
||||
screen.getAllByRole('button', { name: enMessages.settings.ssh_gpg_keys.gpg.remove })
|
||||
).toHaveLength(numberOfKeys);
|
||||
});
|
||||
|
||||
it('should have a way to add a GPG key', () => {
|
||||
// Arrange
|
||||
// Nothing to prepare
|
||||
|
||||
// Act
|
||||
render(SettingsKeys);
|
||||
|
||||
// Assert
|
||||
expect(
|
||||
screen.getByRole('button', { name: enMessages.settings.ssh_gpg_keys.gpg.add })
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in a new issue