Skip to content

Commit 69b9191

Browse files
committed
feat(ui): add 'Add Container' button and dialog
This feature is still not functional, as the script (for agent installation in containers) is not implemented yet. Signed-off-by: Vinicius Aquino <[email protected]>
1 parent 9424b8f commit 69b9191

File tree

4 files changed

+309
-0
lines changed

4 files changed

+309
-0
lines changed
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
<template>
2+
<v-btn
3+
@click="dialog = !dialog"
4+
color="primary"
5+
tabindex="0"
6+
variant="elevated"
7+
aria-label="Dialog Add Container"
8+
@keypress.enter="dialog = !dialog"
9+
data-test="device-add-btn"
10+
:size="props.size"
11+
>
12+
Add Container
13+
</v-btn>
14+
15+
<v-dialog v-model="dialog" width="800" transition="dialog-bottom-transition" data-test="dialog">
16+
<v-card class="bg-v-theme-surface">
17+
<v-card-title class="text-h5 pa-4 bg-primary" data-test="dialog-title">
18+
Registering a container
19+
</v-card-title>
20+
21+
<v-card-text class="mt-4 mb-0 pb-1" data-test="dialog-text">
22+
<p class="text-body-2 mb-2">
23+
In order to register a container on ShellHub, you need to install
24+
ShellHub agent onto it.
25+
</p>
26+
27+
<p class="text-body-2 mb-2">
28+
The easiest way to install ShellHub agent is with our automatic
29+
one-line installation script, which works in any Docker container properly set up.
30+
</p>
31+
32+
<p class="text-body-2 font-weight-bold mt-4">
33+
Run the following command on your container:
34+
</p>
35+
36+
<v-text-field
37+
:model-value="command()"
38+
@click:append="copyCommand"
39+
class="code mt-1"
40+
variant="outlined"
41+
append-icon="mdi-content-copy"
42+
readonly
43+
active
44+
data-test="command-field"
45+
density="compact"
46+
/>
47+
48+
<v-divider />
49+
50+
<p class="text-caption mt-2 mb-0">
51+
Check the
52+
<a
53+
:href="'https://docs.shellhub.io/overview/supported-platforms/docker'"
54+
target="_blank"
55+
rel="noopener noreferrer"
56+
data-test="documentation-link"
57+
>documentation</a
58+
>
59+
for more information about integration with Docker containers.
60+
</p>
61+
</v-card-text>
62+
63+
<v-card-actions>
64+
<v-spacer />
65+
<v-btn variant="text" data-test="close-btn" @click="dialog = !dialog">
66+
Close
67+
</v-btn>
68+
</v-card-actions>
69+
</v-card>
70+
</v-dialog>
71+
</template>
72+
73+
<script setup lang="ts">
74+
import { computed, ref } from "vue";
75+
import { useStore } from "../../store";
76+
import { INotificationsCopy } from "@/interfaces/INotifications";
77+
78+
const props = defineProps({
79+
size: {
80+
type: String,
81+
default: "default",
82+
required: false,
83+
},
84+
});
85+
const store = useStore();
86+
87+
const dialog = ref(false);
88+
89+
const command = () => "TODO";
90+
91+
const copyCommand = () => {
92+
navigator.clipboard.writeText(command());
93+
store.dispatch("snackbar/showSnackbarCopy", INotificationsCopy.command);
94+
};
95+
</script>
96+
97+
<style lang="scss" scoped>
98+
.code {
99+
font-family: monospace;
100+
font-size: 85%;
101+
font-weight: normal;
102+
}
103+
</style>

ui/src/views/Containers.vue

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@
2222

2323
<div class="d-flex" data-test="device-header-component-group">
2424
<TagSelector variant="container" v-if="isContainerList" />
25+
<ContainerAdd />
2526
</div>
27+
2628
</div>
2729
<div class="mt-2" v-if="show" data-test="device-table-component">
2830
<Containers />
@@ -51,6 +53,7 @@ import Containers from "../components/Containers/Container.vue";
5153
import TagSelector from "../components/Tags/TagSelector.vue";
5254
import BoxMessage from "../components/Box/BoxMessage.vue";
5355
import handleError from "@/utils/handleError";
56+
import ContainerAdd from "../components/Containers/ContainerAdd.vue";
5457
5558
const store = useStore();
5659
const router = useRouter();
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
import { DOMWrapper, mount, VueWrapper } from "@vue/test-utils";
2+
import { createVuetify } from "vuetify";
3+
import MockAdapter from "axios-mock-adapter";
4+
import { expect, describe, it, beforeEach, vi } from "vitest";
5+
import { store, key } from "@/store";
6+
import ContainerAdd from "@/components/Containers/ContainerAdd.vue";
7+
import { router } from "@/router";
8+
import { namespacesApi, billingApi, devicesApi } from "@/api/http";
9+
import { SnackbarPlugin } from "@/plugins/snackbar";
10+
11+
const node = document.createElement("div");
12+
node.setAttribute("id", "app");
13+
document.body.appendChild(node);
14+
15+
const devices = [
16+
{
17+
uid: "a582b47a42d",
18+
name: "39-5e-2a",
19+
identity: {
20+
mac: "00:00:00:00:00:00",
21+
},
22+
info: {
23+
id: "linuxmint",
24+
pretty_name: "Linux Mint 19.3",
25+
version: "",
26+
},
27+
public_key: "----- PUBLIC KEY -----",
28+
tenant_id: "fake-tenant-data",
29+
last_seen: "2020-05-20T18:58:53.276Z",
30+
online: false,
31+
namespace: "user",
32+
status: "accepted",
33+
},
34+
{
35+
uid: "a582b47a42e",
36+
name: "39-5e-2b",
37+
identity: {
38+
mac: "00:00:00:00:00:00",
39+
},
40+
info: {
41+
id: "linuxmint",
42+
pretty_name: "Linux Mint 19.3",
43+
version: "",
44+
},
45+
public_key: "----- PUBLIC KEY -----",
46+
tenant_id: "fake-tenant-data",
47+
last_seen: "2020-05-20T19:58:53.276Z",
48+
online: true,
49+
namespace: "user",
50+
status: "accepted",
51+
},
52+
];
53+
54+
const members = [
55+
{
56+
id: "xxxxxxxx",
57+
username: "test",
58+
role: "owner",
59+
},
60+
];
61+
62+
const billingData = {
63+
active: false,
64+
status: "canceled",
65+
customer_id: "cus_test",
66+
subscription_id: "sub_test",
67+
current_period_end: 2068385820,
68+
created_at: "",
69+
updated_at: "",
70+
invoices: [],
71+
};
72+
73+
const namespaceData = {
74+
name: "test",
75+
owner: "xxxxxxxx",
76+
tenant_id: "fake-tenant-data",
77+
members,
78+
max_devices: 3,
79+
devices_count: 3,
80+
devices: 2,
81+
created_at: "",
82+
billing: billingData,
83+
};
84+
85+
const authData = {
86+
status: "",
87+
token: "",
88+
user: "test",
89+
name: "test",
90+
tenant: "fake-tenant-data",
91+
92+
id: "xxxxxxxx",
93+
role: "owner",
94+
};
95+
96+
const customerData = {
97+
id: "cus_test",
98+
name: "test",
99+
100+
payment_methods: [
101+
{
102+
id: "test_id",
103+
number: "xxxxxxxxxxxx4242",
104+
brand: "visa",
105+
exp_month: 3,
106+
exp_year: 2029,
107+
cvc: "",
108+
default: true,
109+
},
110+
],
111+
};
112+
113+
const stats = {
114+
registered_devices: 3,
115+
online_devices: 1,
116+
active_sessions: 0,
117+
pending_devices: 0,
118+
rejected_devices: 0,
119+
};
120+
121+
describe("ContainerAdd", () => {
122+
let wrapper: VueWrapper<InstanceType<typeof ContainerAdd>>;
123+
124+
const vuetify = createVuetify();
125+
126+
let mockNamespace: MockAdapter;
127+
let mockBilling: MockAdapter;
128+
let mockDevices: MockAdapter;
129+
130+
beforeEach(async () => {
131+
const el = document.createElement("div");
132+
document.body.appendChild(el);
133+
134+
vi.useFakeTimers();
135+
localStorage.setItem("tenant", "fake-tenant-data");
136+
137+
mockBilling = new MockAdapter(billingApi.getAxios());
138+
mockNamespace = new MockAdapter(namespacesApi.getAxios());
139+
mockDevices = new MockAdapter(devicesApi.getAxios());
140+
141+
mockNamespace.onGet("http://localhost:3000/api/namespaces/fake-tenant-data").reply(200, namespaceData);
142+
mockBilling.onGet("http://localhost:3000/api/billing/customer").reply(200, customerData);
143+
mockBilling.onGet("http://localhost:3000/api/billing/subscription").reply(200, billingData);
144+
mockBilling.onGet("http://localhost:3000/api/billing/devices-most-used").reply(200, devices);
145+
mockDevices.onGet("http://localhost:3000/api/devices?filter=&page=1&per_page=10&status=accepted").reply(200, devices);
146+
mockDevices.onGet("http://localhost:3000/api/stats").reply(200, stats);
147+
148+
store.commit("auth/authSuccess", authData);
149+
store.commit("namespaces/setNamespace", namespaceData);
150+
store.commit("billing/setSubscription", billingData);
151+
store.commit("customer/setCustomer", customerData);
152+
store.commit("devices/setDeviceChooserStatus", true);
153+
154+
wrapper = mount(ContainerAdd, {
155+
global: {
156+
plugins: [[store, key], vuetify, router, SnackbarPlugin],
157+
},
158+
attachTo: el,
159+
});
160+
});
161+
162+
it("Is a Vue instance", () => {
163+
expect(wrapper.vm).toBeTruthy();
164+
});
165+
166+
it("Renders the component", () => {
167+
expect(wrapper.html()).toMatchSnapshot();
168+
});
169+
170+
it("Data is defined", () => {
171+
expect(wrapper.vm.$data).toBeDefined();
172+
});
173+
174+
it("Renders the component data table", async () => {
175+
expect(wrapper.find('[data-test="device-add-btn"]').exists()).toBe(true);
176+
await wrapper.findComponent('[data-test="device-add-btn"]').trigger("click");
177+
const dialog = new DOMWrapper(document.body);
178+
expect(dialog.find('[data-test="dialog"]').exists()).toBe(true);
179+
expect(dialog.find('[data-test="dialog-title"]').exists()).toBe(true);
180+
expect(dialog.find('[data-test="dialog-text"]').exists()).toBe(true);
181+
expect(dialog.find('[data-test="command-field"]').exists()).toBe(true);
182+
expect(dialog.find('[data-test="documentation-link"]').exists()).toBe(true);
183+
expect(dialog.find('[data-test="close-btn"]').exists()).toBe(true);
184+
});
185+
186+
it("Opens the dialog when add device button is clicked", async () => {
187+
const dialog = new DOMWrapper(document.body);
188+
expect(dialog.find('[data-test="dialog"]').exists()).toBe(true);
189+
190+
await wrapper.find('[data-test="device-add-btn"]').trigger("click");
191+
192+
expect(dialog.find('[data-test="dialog"]').exists()).toBe(true);
193+
});
194+
});
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2+
3+
exports[`ContainerAdd > Renders the component 1`] = `
4+
"<button data-v-b6b1870d="" type="button" class="v-btn v-btn--elevated v-theme--light bg-primary v-btn--density-default v-btn--size-default v-btn--variant-elevated" tabindex="0" aria-label="Dialog Add Container" data-test="device-add-btn"><span class="v-btn__overlay"></span><span class="v-btn__underlay"></span>
5+
<!----><span class="v-btn__content" data-no-activator=""> Add Container </span>
6+
<!---->
7+
<!---->
8+
</button>"
9+
`;

0 commit comments

Comments
 (0)