Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 48 additions & 64 deletions ui/src/components/Namespace/NamespaceAdd.vue
Original file line number Diff line number Diff line change
@@ -1,87 +1,68 @@
<template>
<BaseDialog v-model="showDialog" @close="close">
<v-card data-test="namespace-add-card" class="bg-v-theme-surface rounded" rounded>
<FormDialog
v-model="showDialog"
@close="close"
@confirm="addNamespace"
@cancel="close"
:title="isCommunityVersion ? 'Add a namespace using the CLI' : 'New Namespace'"
icon="mdi-folder-plus"
:confirm-text="isCommunityVersion ? '' : 'Submit'"
:confirm-disabled="isCommunityVersion || !fieldMeta.valid"
:confirm-loading="isLoading"
cancel-text="Close"
confirm-data-test="add-btn"
cancel-data-test="close-btn"
:footer-helper-text="isCommunityVersion ? 'Learn more on' : ''"
:footer-helper-link-text="isCommunityVersion ? 'ShellHub Administration Guide' : ''"
:footer-helper-link="isCommunityVersion ? 'https://docs.shellhub.io/self-hosted/administration' : ''"
data-test="namespace-add-card"
>
<v-card-text class="pa-6">
<template v-if="!isCommunityVersion">
<v-card-title class="bg-primary d-flex justify-space-between align-center text-h5 pa-4">
New Namespace
<v-btn
icon="mdi-close"
variant="text"
@click="close"
/>
</v-card-title>
<v-container>
<v-card-text class="pb-0">
<v-text-field
v-model="namespaceName"
label="Namespace"
:error-messages="namespaceNameError"
variant="outlined"
data-test="username-text"
:persistent-hint="true"
/>
</v-card-text>
<v-card-text>
<v-row class="pl-3 pt-0">
<v-col>
<ul>
<li>The namespace you choose here will be used for in the SSHID of your devices.</li>
<li>The namespace can contain only lowercase alphanumeric characters and hyphens.</li>
<li>It cannot begin or end with a hyphen ("-").</li>
<li>The namespace must be a minimum of 3 characters and a maximum of 63 characters.</li>
<li>The namespace cannot be changed after creation.</li>
</ul>
</v-col>
</v-row>
</v-card-text>
<v-divider />
<v-card-actions>
<v-spacer />
<v-btn data-test="close-btn" @click="close">Close</v-btn>
<v-btn color="primary" variant="outlined" data-test="add-btn" @click="addNamespace" :disabled="!fieldMeta.valid">Submit</v-btn>
</v-card-actions>
</v-container>
<v-text-field
v-model="namespaceName"
label="Namespace"
:error-messages="namespaceNameError"
hide-details="auto"
class="mt-1 mb-4"
/>
<div class="text-body-2 text-justify">
<ul class="pl-4">
<li>The namespace you choose here will be used for in the SSHID of your devices.</li>
<li>The namespace can contain only lowercase alphanumeric characters and hyphens.</li>
<li>It cannot begin or end with a hyphen ("-").</li>
<li>The namespace must be a minimum of 3 characters and a maximum of 63 characters.</li>
<li>The namespace cannot be changed after creation.</li>
</ul>
</div>
</template>
<template v-else>
<v-card-title class="bg-primary">Add a namespace using the CLI</v-card-title>
<v-card-text class="mt-4 mb-0 pb-1 mb-4">
<p class="text-body-2">
In the Community Edition of ShellHub, namespaces must be added using the administration CLI.
For detailed instructions on how to add namespaces, please refer to the documentation at the ShellHub
Administration Guide.
</p>
<div id="cli-instructions" class="mt-3 text-body-2">
<p class="text-caption mb-0 mt-3" data-test="openContentSecond-text">
Check the
<a
:href="'https://docs.shellhub.io/self-hosted/administration'"
target="_blank"
rel="noopener noreferrer"
>ShellHub Administration Guide</a>
for more information.
</p>
</div>
</v-card-text>
<p class="text-body-2">
In the Community Edition of ShellHub, namespaces must be added using the administration CLI.
For detailed instructions on how to add namespaces, please refer to the documentation at the ShellHub
Administration Guide.
</p>
</template>
</v-card>
</BaseDialog>
</v-card-text>
</FormDialog>
</template>

<script setup lang="ts">
import { computed } from "vue";
import { computed, ref } from "vue";
import { useField } from "vee-validate";
import * as yup from "yup";
import axios, { AxiosError } from "axios";
import handleError from "@/utils/handleError";
import useSnackbar from "@/helpers/snackbar";
import { envVariables } from "@/envVariables";
import BaseDialog from "../BaseDialog.vue";
import FormDialog from "../FormDialog.vue";
import useNamespacesStore from "@/store/modules/namespaces";

const namespacesStore = useNamespacesStore();
const snackbar = useSnackbar();
const showDialog = defineModel({ default: false });
const isCommunityVersion = computed(() => envVariables.isCommunity);
const isLoading = ref(false);

// Validation schema for namespace name
const namespaceSchema = yup
Expand Down Expand Up @@ -125,6 +106,7 @@ const handleErrorAndNotify = (error: unknown) => {

// Add a new namespace
const addNamespace = async () => {
isLoading.value = true;
try {
const newNamespaceId = await namespacesStore.createNamespace(namespaceName.value);
await changeNamespace(newNamespaceId);
Expand All @@ -145,6 +127,8 @@ const addNamespace = async () => {
} else {
handleErrorAndNotify(error);
}
} finally {
isLoading.value = false;
}
};
</script>
86 changes: 39 additions & 47 deletions ui/src/components/Namespace/NamespaceDelete.vue
Original file line number Diff line number Diff line change
@@ -1,61 +1,49 @@
<template>
<BaseDialog v-model="showDialog">
<v-card data-test="namespace-delete-card" class="bg-v-theme-surface">
<v-card-title class="text-h5 pa-4 bg-primary">
Namespace Deletion
</v-card-title>
<MessageDialog
v-model="showDialog"
@close="showDialog = false"
@confirm="remove"
@cancel="showDialog = false"
title="Namespace Deletion"
icon="mdi-delete-alert"
icon-color="error"
:confirm-text="isBillingActive || !canDeleteNamespace ? '' : 'Remove'"
confirm-color="error"
:confirm-disabled="isBillingActive || !canDeleteNamespace"
:confirm-loading="isLoading"
cancel-text="Close"
confirm-data-test="remove-btn"
cancel-data-test="close-btn"
>
<div
v-if="isBillingActive"
data-test="content-subscription-text"
>
<p class="mb-2">
To ensure the integrity of your namespace,
we have implemented a restriction that prevents its deletion while you have an active subscription or an unpaid invoice.
</p>
<p class="mb-2">
Kindly note that in order to proceed with the deletion of your namespace,
please ensure that there are no active subscriptions associated with it, and all outstanding invoices are settled.
</p>
</div>

<v-card-text class="mt-4 mb-3 pb-1">
<div
v-if="isBillingActive"
data-test="content-subscription-text"
>
<p class="mb-2">
To ensure the integrity of your namespace,
we have implemented a restriction that prevents its deletion while you have an active subscription or an unpaid invoice.
</p>
<p class="mb-2">
Kindly note that in order to proceed with the deletion of your namespace,
please ensure that there are no active subscriptions associated with it, and all outstanding invoices are settled.
</p>
</div>

<p data-test="content-text" v-else>
This action cannot be undone. This will permanently delete the
<b> {{ displayOnlyTenCharacters(name) }} </b> and its related data.
</p>
</v-card-text>

<v-card-actions>
<v-spacer />

<v-btn variant="text" data-test="close-btn" @click="showDialog = false">
Close
</v-btn>

<v-btn
color="red darken-1"
variant="text"
data-test="remove-btn"
@click="remove()"
:disabled="isBillingActive || !canDeleteNamespace"
>
Remove
</v-btn>
</v-card-actions>
</v-card>
</BaseDialog>
<p data-test="content-text" v-else>
This action cannot be undone. This will permanently delete <strong>{{ displayOnlyTenCharacters(name) }}</strong> and its related data.
</p>
</MessageDialog>
</template>

<script setup lang="ts">
import { computed, onMounted } from "vue";
import { computed, onMounted, ref } from "vue";
import { useRouter } from "vue-router";
import axios, { AxiosError } from "axios";
import hasPermission from "@/utils/permission";
import { displayOnlyTenCharacters } from "@/utils/string";
import handleError from "@/utils/handleError";
import useSnackbar from "@/helpers/snackbar";
import BaseDialog from "../BaseDialog.vue";
import MessageDialog from "../MessageDialog.vue";
import useAuthStore from "@/store/modules/auth";
import useBillingStore from "@/store/modules/billing";
import useNamespacesStore from "@/store/modules/namespaces";
Expand All @@ -70,12 +58,14 @@ const namespacesStore = useNamespacesStore();
const snackbar = useSnackbar();
const router = useRouter();
const showDialog = defineModel({ default: false });
const isLoading = ref(false);
const { name } = namespacesStore.currentNamespace;
const tenant = computed(() => props.tenant);
const isBillingActive = computed(() => billingStore.isActive);
const canDeleteNamespace = hasPermission("namespace:delete");

const remove = async () => {
isLoading.value = true;
try {
await namespacesStore.deleteNamespace(tenant.value);
snackbar.showSuccess("Namespace deleted successfully.");
Expand All @@ -95,6 +85,8 @@ const remove = async () => {
}
snackbar.showError("An error occurred while deleting the namespace.");
handleError(error);
} finally {
isLoading.value = false;
}
};

Expand Down
86 changes: 37 additions & 49 deletions ui/src/components/Namespace/NamespaceEdit.vue
Original file line number Diff line number Diff line change
@@ -1,59 +1,43 @@
<template>
<BaseDialog v-model="showDialog" @close="close">
<v-card data-test="password-change-card" class="bg-v-theme-surface">
<v-card-title class="text-h5 pa-4 bg-primary" data-test="title">
Change Connection Announcement
</v-card-title>

<v-card-text class="mt-4 mb-3 pb-1">
<div class="mt-4 pl-4 pr-4">
<v-textarea
v-model="connectionAnnouncement"
label="Connection Announcement"
:error-messages="connectionAnnouncementError"
data-test="connection-announcement-text"
variant="underlined"
hint="A connection announcement is a custom message written
during a session when a connection is established on a device
within the namespace."
persistent-hint
required
auto-grow
max-rows="25"
/>
</div>
</v-card-text>

<v-card-actions>
<v-spacer />

<v-btn variant="text" data-test="close-btn" @click="close">
Cancel
</v-btn>

<v-btn
color="primary"
variant="text"
data-test="change-connection-btn"
:disabled="!!connectionAnnouncementError"
@click="updateAnnouncement()"
>
Save Announcement
</v-btn>

</v-card-actions>
</v-card>
</BaseDialog>
<FormDialog
v-model="showDialog"
@close="close"
@confirm="updateAnnouncement"
@cancel="close"
title="Change Connection Announcement"
icon="mdi-bullhorn"
confirm-text="Save Announcement"
:confirm-disabled="!!connectionAnnouncementError"
:confirm-loading="isLoading"
cancel-text="Cancel"
confirm-data-test="change-connection-btn"
cancel-data-test="close-btn"
>
<v-card-text class="pa-6">
<v-textarea
v-model="connectionAnnouncement"
label="Connection Announcement"
:error-messages="connectionAnnouncementError"
data-test="connection-announcement-text"
hint="A connection announcement is a custom message written during a session
when a connection is established on a device within the namespace."
persistent-hint
required
auto-grow
max-rows="25"
/>
</v-card-text>
</FormDialog>
</template>

<script setup lang="ts">
import { computed, onMounted } from "vue";
import { computed, onMounted, ref } from "vue";
import { useField } from "vee-validate";
import axios from "axios";
import * as yup from "yup";
import handleError from "@/utils/handleError";
import useSnackbar from "@/helpers/snackbar";
import BaseDialog from "../BaseDialog.vue";
import FormDialog from "../FormDialog.vue";
import useAuthStore from "@/store/modules/auth";
import useNamespacesStore from "@/store/modules/namespaces";

Expand All @@ -64,6 +48,7 @@ const namespace = computed(() => namespacesStore.currentNamespace);
const { tenantId } = authStore;
const showDialog = defineModel({ default: false });
const emit = defineEmits(["update"]);
const isLoading = ref(false);

const {
value: connectionAnnouncement,
Expand Down Expand Up @@ -94,7 +79,7 @@ onMounted(async () => {
}
});

const handleUpdateNameError = (error: unknown): void => {
const handleUpdateAnnouncementError = (error: unknown): void => {
if (axios.isAxiosError(error)) {
switch (error.response?.status) {
case 400:
Expand All @@ -111,6 +96,7 @@ const handleUpdateNameError = (error: unknown): void => {
};

const updateAnnouncement = async () => {
isLoading.value = true;
try {
await namespacesStore.editNamespace({
tenant_id: tenantId,
Expand All @@ -126,7 +112,9 @@ const updateAnnouncement = async () => {

showDialog.value = false;
} catch (error) {
handleUpdateNameError(error);
handleUpdateAnnouncementError(error);
} finally {
isLoading.value = false;
}
};

Expand Down
Loading