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

Adding expose application #3215

Merged
merged 20 commits into from
Aug 28, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
bd2afab
Adding expose application
maayarosama Aug 4, 2024
9fd10ee
Removing selection details output
maayarosama Aug 4, 2024
8c9f89e
Adding validations to ip field and expose icon
maayarosama Aug 6, 2024
46b4c53
adjused expose descriptions
Mik-TF Aug 7, 2024
b2af973
Merge branch 'development' into development_expose_application
maayarosama Aug 12, 2024
d52d90a
Removing vm deployment
maayarosama Aug 12, 2024
b8d037d
Makint network optional in gateway
maayarosama Aug 14, 2024
44e4b97
Changing expose to domains and fixing open dialouge feature
maayarosama Aug 18, 2024
845e7ea
Merge branch 'development' into development_expose_application
maayarosama Aug 18, 2024
cf93d08
feat: Add support for listing domains(gateways)
MohamedElmdary Aug 18, 2024
c7f2acd
Fixing deployment dialouge
maayarosama Aug 18, 2024
54eec66
Fixing delete gateways and rollbackgateway
maayarosama Aug 19, 2024
32a5530
fix: deployment data dialog
MohamedElmdary Aug 19, 2024
699eac4
Fix build error, adding validate subdomain and renaming deleteGateway…
maayarosama Aug 19, 2024
81e3152
- style: Capitalize state/health of gateway
MohamedElmdary Aug 20, 2024
fb920ad
chore: applying typos suggestions
MohamedElmdary Aug 21, 2024
21e47bc
fix: Data wasn't correctly picked when using custom domain
MohamedElmdary Aug 22, 2024
2b402b7
- feat: Add support for tls passthrough
MohamedElmdary Aug 26, 2024
ac90473
chore: Remove unused env variables
MohamedElmdary Aug 27, 2024
f7a6fe6
fix: build failed due to removing env which being used somewhere in code
MohamedElmdary Aug 27, 2024
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
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions packages/playground/public/info/expose.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
title: Expose
---

Expose allows users to securely expose servers hosted on local machines or VMs to the public internet. Users are required to specify the machine's IP, which can be a Mycelium IP, Yggdrasil IP, or a public IP (IPv4 or IPv6). For more details, check the [Expose documentation](https://www.manual.grid.tf/documentation/dashboard/solutions/expose.html).
5 changes: 5 additions & 0 deletions packages/playground/src/constants/deployment_list.ts
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need the changes in this file?

Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,11 @@ export const deploymentListEnvironments = {
CHAIN_ENDPOINT: "Chain Endpoint",
},

expose: {
SSH_KEY: _ssh,
EXPOSE_WEBSERVER_HOSTNAME: "Expose Webserver Hostname",
},

static_website: {
SSH_KEY: _ssh,
GITHUB_URL: "HTTPS URL for git repository",
Expand Down
22 changes: 22 additions & 0 deletions packages/playground/src/router/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,28 @@ function createApplicationsRoutes(): RouteRecordRaw[] {
},
},
},
{
path: DashboardRoutes.Applications.Expose,
component: () => import("../views/expose_view.vue"),
meta: {
title: "Expose",
info: { page: "info/expose.md" },
navbarConfig: {
back: true,
path: [
{ title: "Deploy" },
{
title: "Applications",
disabled: false,
to: DashboardRoutes.Deploy.Applications,
},
{
title: "Expose",
},
],
},
},
},
// Commented for now and will be user later.
// {
// path: DashboardRoutes.Applications.Freeflow,
Expand Down
1 change: 1 addition & 0 deletions packages/playground/src/router/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ enum ApplicationRoutes {
TFRobot = "/deploy/applications/tfrobot/",
Gitea = "/deploy/applications/gitea/",
Nostr = "/deploy/applications/nostr/",
Expose = "/deploy/applications/expose/",
}

const DashboardRoutes = {
Expand Down
3 changes: 3 additions & 0 deletions packages/playground/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ export enum ProjectName {
TFRobot = "TFRobot",
Gitea = "Gitea",
Nostr = "Nostr",
Expose = "Expose",
}

export enum SolutionCode {
Expand Down Expand Up @@ -155,6 +156,7 @@ export enum SolutionCode {
tfrobot = "tfr",
gitea = "gt",
nostr = "nt",
expose = "ex",
}

export const solutionType: { [key: string]: string } = {
Expand All @@ -181,6 +183,7 @@ export const solutionType: { [key: string]: string } = {
tfrobot: "TFRobot",
Gitea: "Gitea",
nostr: "Nostr",
expose: "Expose",
};

export interface solutionFlavor {
Expand Down
26 changes: 26 additions & 0 deletions packages/playground/src/views/expose_view.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<template>
<view-layout>
<TfExpose />

<template #list>
<TfDeploymentList title="Expose" :project-name="name" />
</template>
</view-layout>
</template>

<script lang="ts">
import { ProjectName } from "../types";
import TfDeploymentList from "../weblets/tf_deployment_list.vue";
import TfExpose from "../weblets/tf_expose.vue";

export default {
name: "ExposeView",
components: {
TfExpose,
TfDeploymentList,
},
setup() {
return { name: ProjectName.Expose };
},
};
</script>
7 changes: 7 additions & 0 deletions packages/playground/src/views/solutions_view.vue
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,13 @@ export default {
icon: "wordpress.png",
route: DashboardRoutes.Applications.Wordpress,
},
{
title: "Expose",
excerpt:
"Expose allows users to securely expose servers hosted on local machines or VMs to the public internet.",
icon: "expose.png",
route: DashboardRoutes.Applications.Expose,
},
];
cards = cards.sort((a, b) => a.title.localeCompare(b.title));

Expand Down
15 changes: 15 additions & 0 deletions packages/playground/src/weblets/tf_deployment_list.vue
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,20 @@
/>
</template>

<template #Expose-actions="{ item }">
<IconActionBtn
tooltip="Show Details"
icon="mdi-eye-outline"
@click="openDialog(tabs[activeTab].value, item)"
/>
<IconActionBtn
tooltip="Visit"
icon="mdi-web"
color="anchor"
:href="'https://' + item.env.EXPOSE_WEBSERVER_HOSTNAME"
/>
</template>

<template #StaticWebsite-actions="{ item }">
<IconActionBtn
tooltip="Show Details"
Expand Down Expand Up @@ -419,6 +433,7 @@ const tabs: Tab[] = [
{ title: "TFRobot", value: "TFRobot", imgPath: "images/icons/tfrobot.png" },
{ title: "Gitea", value: "Gitea", imgPath: "images/icons/gitea.png" },
{ title: "Nostr", value: "Nostr", imgPath: "images/icons/nostr.png" },
{ title: "Expose", value: "Expose", imgPath: "images/icons/expose.png" },
];

const layout = useLayout();
Expand Down
242 changes: 242 additions & 0 deletions packages/playground/src/weblets/tf_expose.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
<template>
<weblet-layout
ref="layout"
:cpu="solution?.cpu"
:memory="solution?.memory"
:disk="solution?.disk"
:dedicated="dedicated"
:SelectedNode="selectionDetails?.node"
:valid-filters="selectionDetails?.validFilters"
title-image="images/icons/expose.png"
>
<template #title>Deploy an Expose Instance </template>

<d-tabs :tabs="[{ title: 'Config', value: 'config' }]">
<input-validator
:value="name"
:rules="[
validators.required('Name is required.'),
validators.IsAlphanumericExpectUnderscore('Name should consist of letters ,numbers and underscores only.'),
name => validators.isAlpha('Name must start with alphabet char.')(name[0]),
validators.minLength('Name must be at least 2 characters.', 2),
validators.maxLength('Name cannot exceed 50 characters.', 50),
]"
#="{ props }"
>
<input-tooltip tooltip="Instance name.">
<v-text-field label="Name" v-model="name" v-bind="props" />
</input-tooltip>
</input-validator>

<SelectSolutionFlavor
v-model="solution"
:small="{ cpu: 1, memory: 2, disk: 50 }"
:medium="{ cpu: 2, memory: 4, disk: 100 }"
/>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this solution shouldn't contain a vm, it should be only a gateway workload


<input-tooltip inline tooltip="Click to know more about dedicated machines." :href="manual.dedicated_machines">
<v-switch color="primary" inset label="Dedicated" v-model="dedicated" hide-details />
</input-tooltip>

<input-tooltip inline tooltip="Renting capacity on certified nodes is charged 25% extra.">
<v-switch color="primary" inset label="Certified" v-model="certified" hide-details />
</input-tooltip>

<TfSelectionDetails
require-domain
:filters="{
certified,
dedicated,
cpu: solution?.cpu,
solutionDisk: solution?.disk,
memory: solution?.memory,
rootFilesystemSize,
}"
v-model="selectionDetails"
/>
<input-tooltip tooltip="Selecting custom domain sets subdomain as gateway name.">
<input-validator
:value="subdomain"
:rules="[
validators.required('Subdomain is required.'),
validators.isLowercase('Subdomain should consist of lowercase letters only.'),
validators.isAlphanumeric('Subdomain should consist of letters and numbers only.'),
subdomain => validators.isAlpha('Subdomain must start with alphabet char.')(subdomain[0]),
validators.minLength('Subdomain must be at least 4 characters.', 4),
subdomain => validators.maxLength('Subdomain cannot exceed 50 characters.', 50)(subdomain),
]"
#="{ props }"
>
<v-text-field label="Subdomain" v-model.trim="subdomain" v-bind="props" />
</input-validator>
</input-tooltip>

<input-tooltip tooltip="Port used to access the machine.">
<input-validator
:value="port"
:rules="[validators.required('Port is required.'), validators.isPort('Please provide a valid port.')]"
#="{ props }"
>
<v-text-field label="Port" v-model.number="port" type="number" v-bind="props" />
</input-validator>
</input-tooltip>

<input-tooltip
tooltip="User's machine's public IP , It could be Mycelium IP, Yggdrasil IP, or a public IP (IPv4 or IPv6)."
>
<input-validator
:value="ip"
:rules="[validators.required('Public IP is required.'), validators.isIP('Public IP is not valid.')]"
#="{ props }"
>
<v-text-field label="Public IP" v-model="ip" v-bind="props" />
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it doesn't have to be a public ip. Mycelium and Yggdarsil are not public. let's rename it to IP

</input-validator>
</input-tooltip>

<manage-ssh-deployemnt @selected-keys="updateSSHkeyEnv($event)" />
</d-tabs>

<template #footer-actions="{ validateBeforeDeploy }">
<v-btn color="secondary" @click="validateBeforeDeploy(deploy)" text="Deploy" />
</template>
</weblet-layout>
</template>

<script lang="ts" setup>
import { calculateRootFileSystem, type GridClient } from "@threefold/grid_client";
import { computed, type Ref, ref } from "vue";

import { manual } from "@/utils/manual";

import { useLayout } from "../components/weblet_layout.vue";
import { useGrid } from "../stores";
import type { Flist, solutionFlavor as SolutionFlavor } from "../types";
import { ProjectName } from "../types";
import { deployVM } from "../utils/deploy_vm";
import { deployGatewayName, rollbackDeployment } from "../utils/gateway";
import { normalizeError } from "../utils/helpers";
import { generateName } from "../utils/strings";

const layout = useLayout();
const name = ref(generateName({ prefix: "ex" }));
const ip = ref();
const mycelium = ref(true);
const solution = ref() as Ref<SolutionFlavor>;
const subdomain = ref("");
const port = ref(80);
const flist: Flist = {
value: "https://hub.grid.tf/tf-official-vms/ubuntu-22.04.flist",
entryPoint: "/init.sh",
};
const dedicated = ref(false);
const certified = ref(false);
const rootFilesystemSize = computed(() =>
calculateRootFileSystem({ CPUCores: solution.value?.cpu ?? 0, RAMInMegaBytes: solution.value?.memory ?? 0 }),
);
const selectionDetails = ref<SelectionDetails>();
const selectedSSHKeys = ref("");
const gridStore = useGrid();
const grid = gridStore.client as GridClient;

function finalize(deployment: any) {
layout.value.reloadDeploymentsList();
layout.value.setStatus("success", "Successfully deployed an Expose instance.");
layout.value.openDialog(deployment, deploymentListEnvironments.expose);
}

async function deploy() {
layout.value.setStatus("deploy");

const projectName = ProjectName.Expose.toLowerCase() + "/" + name.value;
console.log(projectName);
const domain = selectionDetails.value?.domain?.enabledCustomDomain
? selectionDetails.value.domain.customDomain
: subdomain.value + "." + selectionDetails.value?.domain?.selectedDomain?.publicConfig.domain;

let vm: any;

try {
layout.value?.validateSSH();
updateGrid(grid, { projectName });
console.log(name.value);

await layout.value.validateBalance(grid!);

vm = await deployVM(grid!, {
name: name.value,
network: {
addAccess: selectionDetails.value!.domain!.enableSelectedDomain,
accessNodeId: selectionDetails.value?.domain?.selectedDomain?.nodeId,
},
machines: [
{
name: name.value,
cpu: solution.value.cpu,
memory: solution.value.memory,
disks: [
{
size: solution.value.disk,
mountPoint: "/var/lib/docker",
},
],
flist: flist.value,
entryPoint: flist.entryPoint,
publicIpv4: false,
publicIpv6: false,
mycelium: mycelium.value,
planetary: false,
envs: [
{ key: "SSH_KEY", value: selectedSSHKeys.value },
{ key: "EXPOSE_WEBSERVER_HOSTNAME", value: domain },
],
nodeId: selectionDetails.value!.node!.nodeId,
rentedBy: dedicated.value ? grid!.twinId : undefined,
certified: certified.value,
rootFilesystemSize: rootFilesystemSize.value,
},
],
});
} catch (e) {
return layout.value.setStatus("failed", normalizeError(e, "Failed to deploy a Subsquid instance."));
}

if (!selectionDetails.value?.domain?.enableSelectedDomain) {
vm[0].customDomain = selectionDetails.value?.domain?.customDomain;
finalize(vm);
return;
}

try {
layout.value.setStatus("deploy", "Preparing to deploy gateway...");

await deployGatewayName(grid, selectionDetails.value.domain, {
subdomain: subdomain.value,
ip: ip.value,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the ipv4 here will work as it will be in the format http://{ip}:{port}
but in case of the ipv6, it won't work as it should be http://[{ip}]:{port}

port: port.value,
network: vm[0].interfaces[0].network,
});
finalize(vm);
} catch (e) {
layout.value.setStatus("deploy", "Rollbacking back due to fail to deploy gateway...");

await rollbackDeployment(grid!, name.value);
layout.value.setStatus("failed", normalizeError(e, "Failed to deploy a Subsquid instance."));
}
}

function updateSSHkeyEnv(selectedKeys: string) {
selectedSSHKeys.value = selectedKeys;
}
</script>

<script lang="ts">
import SelectSolutionFlavor from "../components/select_solution_flavor.vue";
import ManageSshDeployemnt from "../components/ssh_keys/ManageSshDeployemnt.vue";
import { deploymentListEnvironments } from "../constants";
import type { SelectionDetails } from "../types/nodeSelector";
import { updateGrid } from "../utils/grid";
export default {
name: "TfExpose",
components: { SelectSolutionFlavor, ManageSshDeployemnt },
};
</script>
Loading