-
Notifications
You must be signed in to change notification settings - Fork 1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2 from davelopez/ui-add-secrets-to-tools
Add UI for Tool secrets
- Loading branch information
Showing
6 changed files
with
421 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,183 @@ | ||
<script setup lang="ts"> | ||
import { BAlert, BButton, BModal } from "bootstrap-vue"; | ||
import { computed, ref } from "vue"; | ||
import type { ToolCredentialsDefinition, UserCredentials } from "@/api/users"; | ||
import { useUserCredentialsStore } from "@/stores/userCredentials"; | ||
import { useUserStore } from "@/stores/userStore"; | ||
import LoadingSpan from "@/components/LoadingSpan.vue"; | ||
import ManageToolCredentials from "@/components/User/Credentials/ManageToolCredentials.vue"; | ||
interface Props { | ||
toolId: string; | ||
toolVersion: string; | ||
toolCredentialsDefinition: ToolCredentialsDefinition[]; | ||
} | ||
const props = defineProps<Props>(); | ||
const userStore = useUserStore(); | ||
const userCredentialsStore = useUserCredentialsStore(); | ||
const isBusy = ref(true); | ||
const busyMessage = ref<string>(""); | ||
const userCredentials = ref<UserCredentials[] | undefined>(undefined); | ||
const hasUserProvidedRequiredCredentials = computed<boolean>(() => { | ||
if (!userCredentials.value) { | ||
return false; | ||
} | ||
return userCredentials.value.every((credentials) => credentials.optional || areSetByUser(credentials)); | ||
}); | ||
const hasUserProvidedAllCredentials = computed<boolean>(() => { | ||
if (!userCredentials.value) { | ||
return false; | ||
} | ||
return userCredentials.value.every(areSetByUser); | ||
}); | ||
const hasSomeOptionalCredentials = computed<boolean>(() => { | ||
return props.toolCredentialsDefinition.some((credentials) => credentials.optional); | ||
}); | ||
const hasSomeRequiredCredentials = computed<boolean>(() => { | ||
return props.toolCredentialsDefinition.some((credentials) => !credentials.optional); | ||
}); | ||
const provideCredentialsButtonTitle = computed(() => { | ||
return hasUserProvidedRequiredCredentials.value ? "Manage credentials" : "Provide credentials"; | ||
}); | ||
const bannerVariant = computed(() => { | ||
if (isBusy.value) { | ||
return "info"; | ||
} | ||
return hasUserProvidedRequiredCredentials.value ? "success" : "warning"; | ||
}); | ||
const showModal = ref(false); | ||
/** | ||
* Check if the user has credentials for the tool. | ||
* @param providedCredentials - The provided credentials to check. If not provided, the function will fetch the | ||
* credentials from the store if they exist. | ||
*/ | ||
async function checkUserCredentials(providedCredentials?: UserCredentials[]) { | ||
busyMessage.value = "Checking your credentials..."; | ||
isBusy.value = true; | ||
try { | ||
if (userStore.isAnonymous) { | ||
return; | ||
} | ||
if (!providedCredentials) { | ||
providedCredentials = | ||
userCredentialsStore.getAllUserCredentialsForTool(props.toolId) ?? | ||
(await userCredentialsStore.fetchAllUserCredentialsForTool( | ||
props.toolId, | ||
props.toolCredentialsDefinition | ||
)); | ||
} | ||
userCredentials.value = providedCredentials; | ||
} catch (error) { | ||
// TODO: Implement error handling. | ||
console.error("Error checking user credentials", error); | ||
} finally { | ||
isBusy.value = false; | ||
} | ||
} | ||
function areSetByUser(credentials: UserCredentials): boolean { | ||
return ( | ||
credentials.variables.every((variable) => variable.value) && | ||
credentials.secrets.every((secret) => secret.alreadySet) | ||
); | ||
} | ||
function provideCredentials() { | ||
showModal.value = true; | ||
} | ||
async function onSavedCredentials(providedCredentials: UserCredentials[]) { | ||
showModal.value = false; | ||
busyMessage.value = "Saving your credentials..."; | ||
try { | ||
isBusy.value = true; | ||
userCredentials.value = await userCredentialsStore.saveUserCredentialsForTool( | ||
props.toolId, | ||
providedCredentials | ||
); | ||
} catch (error) { | ||
// TODO: Implement error handling. | ||
console.error("Error saving user credentials", error); | ||
} finally { | ||
isBusy.value = false; | ||
} | ||
} | ||
checkUserCredentials(); | ||
</script> | ||
|
||
<template> | ||
<div> | ||
<BAlert show :variant="bannerVariant" class="tool-credentials-banner"> | ||
<LoadingSpan v-if="isBusy" :message="busyMessage" /> | ||
<div v-else-if="userStore.isAnonymous"> | ||
<span v-if="hasSomeRequiredCredentials"> | ||
<strong> | ||
This tool requires credentials to access its services and you need to be logged in to provide | ||
them. | ||
</strong> | ||
</span> | ||
<span v-else> | ||
This tool <strong>can use additional credentials</strong> to access its services | ||
<strong>or you can use it anonymously</strong>. | ||
</span> | ||
<br /> | ||
Please <a href="/login/start">log in or register here</a>. | ||
</div> | ||
<div v-else class="d-flex justify-content-between align-items-center"> | ||
<div class="credentials-info"> | ||
<span v-if="hasUserProvidedRequiredCredentials"> | ||
<strong>You have already provided credentials for this tool.</strong> You can update or delete | ||
your credentials, using the <i>{{ provideCredentialsButtonTitle }}</i> button. | ||
<span v-if="hasSomeOptionalCredentials && !hasUserProvidedAllCredentials"> | ||
<br /> | ||
You can still provide some optional credentials for this tool. | ||
</span> | ||
</span> | ||
<span v-else-if="hasSomeRequiredCredentials"> | ||
This tool <strong>requires you to enter credentials</strong> to access its services. Please | ||
provide your credentials before using the tool using the | ||
<i>{{ provideCredentialsButtonTitle }}</i> button. | ||
</span> | ||
<span v-else> | ||
This tool <strong>can use credentials</strong> to access its services. If you don't provide | ||
credentials, you can still use the tool, but you will access its services | ||
<strong>anonymously</strong> and in some cases, with limited functionality. | ||
</span> | ||
</div> | ||
|
||
<BButton variant="primary" size="sm" class="provide-credentials-btn" @click="provideCredentials"> | ||
{{ provideCredentialsButtonTitle }} | ||
</BButton> | ||
</div> | ||
</BAlert> | ||
<BModal v-model="showModal" title="Manage Tool Credentials" hide-footer> | ||
<ManageToolCredentials | ||
:tool-id="props.toolId" | ||
:tool-version="props.toolVersion" | ||
:credentials="userCredentials" | ||
@save-credentials="onSavedCredentials" /> | ||
</BModal> | ||
</div> | ||
</template> | ||
|
||
<style scoped> | ||
.tool-credentials-banner { | ||
margin-bottom: 1rem; | ||
} | ||
</style> |
55 changes: 55 additions & 0 deletions
55
client/src/components/User/Credentials/CredentialsInput.vue
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
<script setup lang="ts"> | ||
import { BBadge, BCard } from "bootstrap-vue"; | ||
import type { UserCredentials } from "@/api/users"; | ||
interface Props { | ||
credential: UserCredentials; | ||
} | ||
defineProps<Props>(); | ||
</script> | ||
|
||
<template> | ||
<BCard> | ||
<h3> | ||
{{ credential.label || credential.name }} | ||
<BBadge | ||
v-if="credential.optional" | ||
variant="secondary" | ||
class="optional-credentials" | ||
title="These credentials are optional. If you do not provide them, the tool will use default values or | ||
anonymous access."> | ||
Optional | ||
</BBadge> | ||
<BBadge | ||
v-else | ||
variant="danger" | ||
class="required-credentials" | ||
title="These credentials are required. You must provide them to use the tool."> | ||
Required | ||
</BBadge> | ||
</h3> | ||
<p>{{ credential.description }}</p> | ||
<div v-for="variable in credential.variables" :key="variable.name"> | ||
<label :for="variable.name">{{ variable.label || variable.name }}</label> | ||
<input :id="variable.name" v-model="variable.value" type="text" autocomplete="off" /> | ||
</div> | ||
<div v-for="secret in credential.secrets" :key="secret.name" class="secret-input"> | ||
<label :for="secret.name">{{ secret.label || secret.name }}</label> | ||
<input :id="secret.name" v-model="secret.value" type="password" autocomplete="off" /> | ||
<span v-if="secret.alreadySet" class="tick-icon">✔️</span> | ||
</div> | ||
</BCard> | ||
</template> | ||
|
||
<style scoped> | ||
.secret-input { | ||
display: flex; | ||
align-items: center; | ||
} | ||
.tick-icon { | ||
color: green; | ||
margin-left: 0.5em; | ||
} | ||
</style> |
61 changes: 61 additions & 0 deletions
61
client/src/components/User/Credentials/ManageToolCredentials.vue
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
<script setup lang="ts"> | ||
import { ref } from "vue"; | ||
import type { UserCredentials } from "@/api/users"; | ||
import CredentialsInput from "@/components/User/Credentials/CredentialsInput.vue"; | ||
interface ManageToolCredentialsProps { | ||
toolId: string; | ||
toolVersion: string; | ||
credentials?: UserCredentials[]; | ||
} | ||
const props = defineProps<ManageToolCredentialsProps>(); | ||
const providedCredentials = ref<UserCredentials[]>(initializeCredentials()); | ||
const emit = defineEmits<{ | ||
(e: "save-credentials", credentials: UserCredentials[]): void; | ||
}>(); | ||
function saveCredentials() { | ||
emit("save-credentials", providedCredentials.value); | ||
} | ||
function initializeCredentials(): UserCredentials[] { | ||
// If credentials are provided, clone them to avoid modifying the original data | ||
return props.credentials ? JSON.parse(JSON.stringify(props.credentials)) : []; | ||
} | ||
</script> | ||
|
||
<template> | ||
<div> | ||
<p> | ||
Here you can manage your credentials for the tool <strong>{{ toolId }}</strong> version | ||
<strong> {{ toolVersion }} </strong>. | ||
</p> | ||
<CredentialsInput | ||
v-for="credential in providedCredentials" | ||
:key="credential.reference" | ||
:credential="credential" /> | ||
<button @click="saveCredentials">Save Credentials</button> | ||
</div> | ||
</template> | ||
|
||
<style scoped> | ||
.credential-card { | ||
border: 1px solid #ccc; | ||
padding: 1em; | ||
margin-bottom: 1em; | ||
border-radius: 5px; | ||
} | ||
.secret-input { | ||
display: flex; | ||
align-items: center; | ||
} | ||
.tick-icon { | ||
color: green; | ||
margin-left: 0.5em; | ||
} | ||
</style> |
Oops, something went wrong.