Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: secrets managment #158

Merged
merged 14 commits into from
Nov 4, 2024
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ Administration application for leto-modelizer.

### Requirements

- node - v18.14
- npm - v8.19.3
* node - [v20.16.0](https://nodejs.org/en/blog/release/v20.16.0)
* npm - [v10.8.1](https://www.npmjs.com/package/npm/v/10.8.1)

This server is based on [Quasar](https://quasar.dev/).

Expand Down
10 changes: 10 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html)

## [Unreleased]

### Added

* Setup AI settings page with:
* Table to display all secrets.
* Add action to create a secret.
* Add action to edit a secret.
* Add action to remove a secret.

## [1.0.0] - 2024/10/15

### Added
Expand Down
19,480 changes: 3,890 additions & 15,590 deletions package-lock.json

Large diffs are not rendered by default.

37 changes: 37 additions & 0 deletions src/components/card/SecretFiltersCard.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<template>
<q-card>
<q-card-section class="row justify-center items-center">
<span class="text-weight-medium q-mr-md">
{{ $t('SecretFiltersCard.text.title') }}
</span>
<q-input
outlined
dense
clearable
:model-value="secretKey"
:label="$t('SecretFiltersCard.text.byKey')"
:debounce="inputDebounceTime"
class="q-mr-sm"
data-cy="secret_filter_key"
@update:model-value="(value) => $emit('update:secretKey', value)"
>
<template #prepend>
<q-icon :name="$t('SecretFiltersCard.icon.byKey')" />
</template>
</q-input>
</q-card-section>
</q-card>
</template>

<script setup>
import { ref } from 'vue';

defineEmits(['update:secretKey']);
defineProps({
secretKey: {
type: String,
default: '',
},
});
const inputDebounceTime = ref(process.env.INPUT_DEBOUNCE_TIME);
</script>
106 changes: 106 additions & 0 deletions src/components/dialog/AddSecretDialog.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
<template>
<q-dialog
v-model="show"
data-cy="add-secret-dialog"
>
<q-card>
<q-card-section class="flex row justify-center">
<span class="text-h6">
{{ $t('AddSecretDialog.text.title') }}
</span>
</q-card-section>
<q-form @submit="onSubmit">
<q-card-section class="column flex-center">
<q-input
v-model="secretKey"
outlined
class="input-secret-key"
data-cy="secret_field_key"
:label="$t('AddSecretDialog.text.key')"
/>
<q-input
v-model="secretValue"
outlined
type="textarea"
class="q-mt-md input-secret-value"
data-cy="secret_field_value"
:label="$t('AddSecretDialog.text.value')"
/>
</q-card-section>
<q-card-actions align="center">
<q-btn
v-close-popup
:label="$t('AddSecretDialog.text.cancel')"
color="negative"
/>
<q-btn
:label="$t('AddSecretDialog.text.confirm')"
:loading="submitting"
type="submit"
color="positive"
data-cy="button_confirm"
>
<template #loading>
<q-spinner-hourglass />
</template>
</q-btn>
</q-card-actions>
</q-form>
</q-card>
</q-dialog>
</template>

<script setup>
import { useDialog } from 'src/composables/Dialog';
import { ref } from 'vue';
import ReloadSecretsEvent from 'src/composables/events/ReloadSecretsEvent';
import * as SecretService from 'src/services/SecretService';
import { Notify } from 'quasar';
import { useI18n } from 'vue-i18n';

const { t } = useI18n();
const submitting = ref(false);
const secretKey = ref('');
const secretValue = ref('');
const { show } = useDialog('add-secret', () => {
submitting.value = false;
secretKey.value = '';
secretValue.value = '';
});

/**
* Add secret, send event to reload all secrets and close dialog.
* @returns {Promise<void>} Promise with nothing on success.
*/
async function onSubmit() {
submitting.value = true;

return SecretService.add(secretKey.value, secretValue.value)
.then(() => {
Notify.create({
type: 'positive',
message: t('AddSecretDialog.text.notifySuccess'),
html: true,
});

ReloadSecretsEvent.next();

show.value = false;
})
.catch(() => {
Notify.create({
type: 'negative',
message: t('AddSecretDialog.text.notifyError'),
html: true,
});
}).finally(() => {
submitting.value = false;
});
}
</script>

<style lang="scss" scoped>
.input-secret-value, .input-secret-key {
min-width: 400px;
}
</style>
80 changes: 80 additions & 0 deletions src/components/dialog/RemoveSecretDialog.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<template>
<q-dialog
v-model="show"
data-cy="remove-secret-dialog"
>
<q-card>
<q-card-section class="flex row justify-center">
<span class="text-h6">
{{ $t('RemoveSecretDialog.text.title', { key: secret.key }) }}
</span>
</q-card-section>
<q-form @submit="onSubmit">
<q-card-section class="column flex-center">
{{ $t('RemoveSecretDialog.text.content') }}
</q-card-section>
<q-card-actions align="center">
<q-btn
v-close-popup
:label="$t('RemoveSecretDialog.text.cancel')"
color="negative"
/>
<q-btn
:label="$t('RemoveSecretDialog.text.confirm')"
:loading="submitting"
type="submit"
color="positive"
data-cy="button_confirm"
>
<template #loading>
<q-spinner-hourglass />
</template>
</q-btn>
</q-card-actions>
</q-form>
</q-card>
</q-dialog>
</template>

<script setup>
import { useDialog } from 'src/composables/Dialog';
import { ref } from 'vue';
import ReloadSecretsEvent from 'src/composables/events/ReloadSecretsEvent';
import * as SecretService from 'src/services/SecretService';
import { Notify } from 'quasar';
import { useI18n } from 'vue-i18n';

const { t } = useI18n();
const submitting = ref(false);
const secret = ref(null);
const { show } = useDialog('remove-secret', (event) => {
submitting.value = false;
secret.value = event.secret;
});

/**
* Remove secret, send event to reload all secrets and close dialog.
* @returns {Promise<void>} Promise with nothing on success.
*/
async function onSubmit() {
submitting.value = true;

return SecretService.remove(secret.value.id)
.then(() => Notify.create({
type: 'positive',
message: t('RemoveSecretDialog.text.notifySuccess'),
html: true,
}))
.catch(() => Notify.create({
type: 'negative',
message: t('RemoveSecretDialog.text.notifyError'),
html: true,
}))
.finally(() => {
ReloadSecretsEvent.next();

submitting.value = false;
show.value = false;
});
}
</script>
94 changes: 94 additions & 0 deletions src/components/dialog/UpdateSecretDialog.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
<template>
<q-dialog
v-model="show"
data-cy="edit-secret-dialog"
>
<q-card>
<q-card-section class="flex row justify-center">
<span class="text-h6">
{{ $t('UpdateSecretDialog.text.title', { key: secret.key }) }}
</span>
</q-card-section>
<q-form @submit="onSubmit">
<q-card-section class="column flex-center">
<q-input
v-model="secretValue"
outlined
type="textarea"
class="q-mt-md input-secret-value"
data-cy="secret_field_key"
:label="$t('UpdateSecretDialog.text.value')"
/>
</q-card-section>
<q-card-actions align="center">
<q-btn
v-close-popup
:label="$t('UpdateSecretDialog.text.cancel')"
color="negative"
/>
<q-btn
:label="$t('UpdateSecretDialog.text.confirm')"
:loading="submitting"
type="submit"
color="positive"
data-cy="button_confirm"
>
<template #loading>
<q-spinner-hourglass />
</template>
</q-btn>
</q-card-actions>
</q-form>
</q-card>
</q-dialog>
</template>

<script setup>
import { useDialog } from 'src/composables/Dialog';
import { ref } from 'vue';
import ReloadSecretsEvent from 'src/composables/events/ReloadSecretsEvent';
import * as SecretService from 'src/services/SecretService';
import { Notify } from 'quasar';
import { useI18n } from 'vue-i18n';

const { t } = useI18n();
const submitting = ref(false);
const secret = ref(null);
const secretValue = ref('');
const { show } = useDialog('update-secret', (event) => {
submitting.value = false;
secretValue.value = '';
secret.value = event.secret;
});

/**
* Update secret, send event to reload all secrets and close dialog.
* @returns {Promise<void>} Promise with nothing on success.
*/
async function onSubmit() {
submitting.value = true;

return SecretService.update(secret.value.id, secret.value.key, secretValue.value)
.then(() => Notify.create({
type: 'positive',
message: t('UpdateSecretDialog.text.notifySuccess'),
html: true,
}))
.catch(() => Notify.create({
type: 'negative',
message: t('UpdateSecretDialog.text.notifyError'),
html: true,
})).finally(() => {
ReloadSecretsEvent.next();

show.value = false;
submitting.value = false;
});
}
</script>

<style lang="scss" scoped>
.input-secret-value, .input-secret-key {
min-width: 400px;
}
</style>
6 changes: 6 additions & 0 deletions src/components/drawer/ApplicationDrawer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,12 @@ const menuList = ref([
url: '/libraries',
name: 'libraries',
},
{
icon: t('ApplicationDrawer.icon.ai'),
label: t('ApplicationDrawer.text.ai'),
url: '/ai',
name: 'ai',
},
]);
</script>

Expand Down
13 changes: 13 additions & 0 deletions src/components/tab-panel/ConfigurationsTabPanel.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<template>
<q-tab-panel
name="configurations"
data-cy="configurations_tab_panel"
>
<h6
class="q-ma-none q-mb-sm"
data-cy="configurations_title"
>
{{ $t('ConfigurationsTabPanel.text.title') }}
</h6>
</q-tab-panel>
</template>
Loading
Loading