Skip to content
Open

GPU #295

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
d6e8899
feat: begin adding gpu views
annybs Feb 8, 2025
f4ccd1c
chore: copy EditableTitle, Slider from vpn branch
annybs Feb 8, 2025
11b5966
feat: add sliders to gpu deploy
annybs Feb 8, 2025
bfc76bf
feat: complete deploy form
annybs Feb 17, 2025
ebbf330
feat: begin to integrate gpu endpoints
annybs Feb 24, 2025
96a4545
feat: integrate gpu list view
annybs Feb 24, 2025
f38db8b
feat: misc development
annybs Feb 24, 2025
3000fa6
feat: add gpu overview
annybs Feb 24, 2025
3ef9b28
feat: working power toggle, misc fixes
annybs Feb 24, 2025
be39e28
feat: more polling fixes, add ip address copy
annybs Feb 24, 2025
70afe71
fix: gpu list design
annybs Feb 24, 2025
4d24187
fix: flickering while polling; name change validation
annybs Feb 24, 2025
1471e80
fix: clear validation error on name edit
annybs Feb 24, 2025
1a0e41e
feat: implement destroy gpu flow
annybs Feb 24, 2025
35f53fe
feat: begin to add api key integration
annybs Feb 25, 2025
a4b7c29
feat: improve error handling in deploy, destroy
annybs Mar 3, 2025
a745762
feat: create tabulated settings view, replaces account
annybs Mar 3, 2025
3644db3
feat: visual improvements to api keys panel
annybs Mar 3, 2025
b4bb84d
feat: improve error display, grid layout for api keys
annybs Mar 4, 2025
325e9e9
feat: prepopulate gpu server name and hostname
andiradulescu Apr 22, 2025
60bada6
feat: gpu root password hint
andiradulescu Apr 22, 2025
5c8441f
feat: update gpu server specs
andiradulescu Apr 22, 2025
eaa13ae
feat: select gpu with nvidia logo and text
andiradulescu Apr 22, 2025
a9bf1c8
feat: increased root password complexity for gpu
andiradulescu Apr 22, 2025
0187858
fix: gpu overview formatting
andiradulescu Apr 22, 2025
70f10af
feat: improve gpu details
andiradulescu Apr 23, 2025
23d1210
feat: improve gpu details loading
andiradulescu Apr 23, 2025
1d8a7e6
fix: gpu list formatting
andiradulescu Apr 23, 2025
7934b51
fix: rename server to gpu in loaders
andiradulescu Apr 23, 2025
2842dea
fix: gpu overview spacing
andiradulescu Apr 23, 2025
8691bbe
fix: gpu detail states
andiradulescu Apr 23, 2025
7cb1d36
refactor: call createServerHostname directly in onMounted
andiradulescu Apr 23, 2025
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
5 changes: 5 additions & 0 deletions src/components/MobileNavigation.vue
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ export default {
link: '/servers',
text: 'Servers'
},
{
_key: 'gpu',
link: '/gpus',
text: 'GPUs'
},
{
_key: 'dns',
link: '/domains',
Expand Down
5 changes: 5 additions & 0 deletions src/components/SideNavigation.vue
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ export default {
link: '/servers',
text: 'Servers'
},
{
_key: 'gpu',
link: '/gpus',
text: 'GPUs'
},
{
_key: 'dns',
link: '/domains',
Expand Down
2 changes: 1 addition & 1 deletion src/components/UserMenu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
</MenuItem>
<MenuItem v-slot="{ active }">
<button
@click.prevent="navigate('/account')"
@click.prevent="navigate({ name: 'Settings' })"
:class="[
'menu__item',
active ? 'active' : ''
Expand Down
4 changes: 2 additions & 2 deletions src/components/billing/Invoices.vue
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,11 @@ const isCryptoView = Boolean(store.state.account?.useCryptoView)
If you add an email address to your account, you will receive an email notification when a new invoice is generated and whenever your account status changes.
You can also use your email address to log in to your Edge Account, keeping your account number secure.
<span v-if="!accountEmail">
<RouterLink :to="{ name: 'Account' }" class="text-green">Add an email address</RouterLink>.
<RouterLink :to="{ name: 'Settings' }" class="text-green">Add an email address</RouterLink>.
</span>
<span v-else>
Your email address is <span class="text-green">{{ accountEmail }}</span>.
You can <RouterLink :to="{ name: 'Account' }" class="text-green">change your email address</RouterLink> if you need to.
You can <RouterLink :to="{ name: 'Settings' }" class="text-green">change your email address</RouterLink> if you need to.
</span>
</p>
<p>
Expand Down
2 changes: 1 addition & 1 deletion src/components/billing/PaymentCards.vue
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@
<RouterLink :to="{ name: 'Wallet' }" class="text-green">View your wallet</RouterLink> to start funding your account manually.
</p>
<p v-else>
<RouterLink :to="{ name: 'Account' }" class="text-green">Enable Crypto View</RouterLink> to start funding your account manually.
<RouterLink :to="{ name: 'Settings' }" class="text-green">Enable Crypto View</RouterLink> to start funding your account manually.
</p>
</section>
</article>
Expand Down
2 changes: 1 addition & 1 deletion src/components/billing/Wallet.vue
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ async function startPurchase() {
You're currently using the crypto view, which provides comprehensive visibility of your wallet and transactions on the XE blockchain.
This page enables you to manage your account balance directly using XE.
If you're new to cryptocurrency, you can <a href="https://wiki.edge.network/getting-and-storing-tokens/wallets" target="_blank" class="text-green">learn more about XE on the Edge Wiki</a>.
Alternatively, if you prefer a more streamlined interface, you can <RouterLink :to="{ name: 'Account' }" class="text-green">disable the crypto view</RouterLink>.
Alternatively, if you prefer a more streamlined interface, you can <RouterLink :to="{ name: 'Settings' }" class="text-green">disable the crypto view</RouterLink>.
</p>
</section>
</article>
Expand Down
17 changes: 17 additions & 0 deletions src/components/icons/NvidiaIcon.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<template>
<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' width='24' height='24' :class="className">
<path fill="rgb(118 185 0)" d='M8.948 8.798v-1.43a7 7 0 0 1 .424-.018c3.922-.124 6.493 3.374 6.493 3.374s-2.774 3.851-5.75 3.851a3.7 3.7 0 0 1-1.158-.185v-4.346c1.528.185 1.837.857 2.747 2.385l2.04-1.714s-1.492-1.952-4-1.952a6 6 0 0 0-.796.035m0-4.735v2.138l.424-.027c5.45-.185 9.01 4.47 9.01 4.47s-4.08 4.964-8.33 4.964a6.5 6.5 0 0 1-1.095-.097v1.325c.3.035.61.062.91.062c3.957 0 6.82-2.023 9.593-4.408c.459.371 2.34 1.263 2.73 1.652c-2.633 2.208-8.772 3.984-12.253 3.984c-.335 0-.653-.018-.971-.053v1.864H24V4.063zm0 10.326v1.131c-3.657-.654-4.673-4.46-4.673-4.46s1.758-1.944 4.673-2.262v1.237H8.94c-1.528-.186-2.73 1.245-2.73 1.245s.68 2.412 2.739 3.11M2.456 10.9s2.164-3.197 6.5-3.533V6.201C4.153 6.59 0 10.653 0 10.653s2.35 6.802 8.948 7.42v-1.237c-4.84-.6-6.492-5.936-6.492-5.936'/>
</svg>
</template>

<script>
export default {
name: 'NvidiaIcon',
props: {
className: {
type: String,
default: 'w-6 h-6'
}
}
}
</script>
29 changes: 21 additions & 8 deletions src/components/server/deploy/Password.vue
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,12 @@
<script>
import { EyeIcon, EyeOffIcon } from '@heroicons/vue/solid'

const passwordCharset = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789*#$!?-_'
const passwordCharset = {
lowercase: 'abcdefghijklmnopqrstuvwxyz',
uppercase: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
numbers: '0123456789',
symbols: '@#$%^&*'
}

export default {
name: 'Password',
Expand All @@ -82,13 +87,21 @@ export default {
}, 2000)
},
generate() {
this.password = (new Array(24))
.fill(null)
.map(() => {
const n = Math.floor(Math.random() * passwordCharset.length)
return passwordCharset[n]
})
.join('')
const password = [
passwordCharset.lowercase[Math.floor(Math.random() * passwordCharset.lowercase.length)],
passwordCharset.uppercase[Math.floor(Math.random() * passwordCharset.uppercase.length)],
passwordCharset.numbers[Math.floor(Math.random() * passwordCharset.numbers.length)],
passwordCharset.symbols[Math.floor(Math.random() * passwordCharset.symbols.length)]
]
const allChars = Object.values(passwordCharset).join('')
for (let i = password.length; i < 24; i++) {
password.push(allChars[Math.floor(Math.random() * allChars.length)])
}
for (let i = password.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1))
;[password[i], password[j]] = [password[j], password[i]]
}
this.password = password.join('')
},
toggleShowPassword() {
if (this.disableControls) return
Expand Down
98 changes: 98 additions & 0 deletions src/layout/EditableTitle.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
<script setup>
import LoadingSpinner from '@/components/icons/LoadingSpinner.vue'
import { CheckIcon, PencilIcon, XIcon } from '@heroicons/vue/outline'
import { defineEmits, defineModel, defineProps, ref, watchPostEffect } from 'vue'

const emit = defineEmits(['cancel', 'edit','save'])

const props = defineProps({
busy: Boolean,
disabled: Boolean,
invalid: Boolean,
placeholder: String
})

const editing = ref(false)
const input = ref(null)
const model = defineModel()
const previousValue = ref(model.value)

function cancel() {
editing.value = false
model.value = previousValue.value

emit('cancel')
}

function edit() {
editing.value = true

emit('edit')
}

function save() {
if (props.invalid) return

editing.value = false
previousValue.value = model.value

emit('save')
}

watchPostEffect(() => {
if (input.value) {
input.value.focus()
}
})
</script>

<template>
<div v-if="editing" class="editable-title__container flex">
<input
v-model="model"
@keypress.enter="save"
class="editable-title__value"
:placeholder="placeholder"
ref="input"
type="text"
/>

<div class="mt-3 flex">
<button class="ml-2" @click="save" :disabled="invalid || disabled">
<CheckIcon class="button__icon text-green hover:text-green-600" />
</button>
<button @click="cancel" class="ml-2">
<XIcon class="button__icon text-red hover:text-red-600" />
</button>
</div>
</div>

<div v-else class="editable-title__container flex">
<h1 class="w-max mb-0">{{ model }}</h1>

<div class="mt-3">
<button v-if="busy" class="ml-2" disabled>
<LoadingSpinner />
</button>
<button v-else class="ml-2" @click="edit" :disabled="disabled">
<PencilIcon class="button__icon text-gray-400 hover:text-green" />
</button>
</div>
</div>
</template>

<style>
.editable-title__value {
@apply bg-transparent text-3xl text-gray-600 border-b border-gray-400 w-full;
@apply focus:outline-none focus:border-green focus:text-green;
}

.editable-title__container .button__icon {
@apply w-5 ml-1;
}

.editable-title__container button:disabled,
.editable-title__container button:disabled .button__icon {
@apply text-gray-400 hover:no-underline;
}
</style>
35 changes: 35 additions & 0 deletions src/layout/ErrorV2.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<script setup>
import { ExclamationIcon } from '@heroicons/vue/outline'
import { computed, defineProps } from 'vue'

const props = defineProps({
error: Object
})

const message = computed(() => {
if (!props.error) return ''

if (props.error && props.error.response && props.error.response.body) {
const body = props.error.response.body
if (body.hint) return body.hint
if (body.reason) return body.reason
if (body.error) return body.error
if (body.message) return body.message
}

if (props.error.value && props.error.message) {
return props.error.message
}

return 'Unknown error'
})
</script>

<template>
<div v-if="error" class="error_wrapper block errorMessage">
<div class="float-left">
<ExclamationIcon class="w-3.5 text-red" />
</div>
<span class="errorMessage__text text-red">{{ message }}</span>
</div>
</template>
46 changes: 46 additions & 0 deletions src/layout/PowerToggle.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<script setup>
import LoadingSpinner from '../components/icons/LoadingSpinner.vue'
import { Switch } from '@headlessui/vue'
import { defineEmits, defineModel, defineProps } from 'vue'

const emit = defineEmits(['click'])

/** v-model must be a Boolean ref! */
const model = defineModel()

defineProps({
busy: Boolean,
disabled: Boolean,
offText: String,
on: Boolean,
onText: String
})
</script>

<template>
<Switch
v-model="model"
class="relative inline-flex flex-shrink-0 transition-colors duration-200 ease-in-out border-2 border-transparent rounded-full cursor-pointer w-16 md:w-20 h-7 md:h-8 focus:outline-none"
:class="model ? 'bg-green' : 'bg-gray-300'"
:disabled="busy || disabled"
@click="emit('click', !model)"
>
<span class="sr-only">Status</span>

<span v-if="model" class="absolute w-full h-full flex items-center text-xs leading-none justify-start text-white pl-3 md:pl-4">
<span v-if="busy">
<LoadingSpinner />
</span>
<span v-else>{{ onText || 'ON' }}</span>
</span>
<span v-else class="absolute w-full h-full flex items-center text-xs leading-none justify-end text-gray-500 pr-2.5 md:pr-3.5">
<span v-if="busy">
<LoadingSpinner />
</span>
<span v-else>{{ offText || 'OFF' }}</span>
</span>

<span v-if="model" aria-hidden class="translate-x-9 md:translate-x-12 transform inline-block w-6 h-6 md:w-7 md:h-7 transition duration-200 ease-in-out bg-white rounded-full shadow-lg pointer-events-none ring-0"/>
<span v-else aria-hidden class="translate-x-0 transform inline-block w-6 h-6 md:w-7 md:h-7 transition duration-200 ease-in-out bg-white rounded-full shadow-lg pointer-events-none ring-0"/>
</Switch>
</template>
60 changes: 60 additions & 0 deletions src/layout/Slider.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<script setup>
import 'vue-slider-component/theme/antd.css'
import VueSlider from 'vue-slider-component'
import { defineEmits, defineModel, defineProps } from 'vue'

defineProps({
disabled: Boolean,
formatter: Function,
marks: Object,
max: Number,
min: Number,
title: String,
tooltip: String
})

const emit = defineEmits(['change'])

const model = defineModel()
</script>

<template>
<div class="slider__container" :class="{ disabled }">
<span v-if="title" class="slider__title">{{ title }}</span>
<vue-slider
v-model="model"
adsorb
tooltipPlacement="top"
width="100%"
:contained="true"
:disabled="disabled"
:dot-style="{ background: '#4ecd5f', boxShadow: '0 0 2px 1px #eee', border: 'none' }"
:dotSize="20"
:label-style="{ color: '#999', fontSize: '12px' }"
:marks="marks"
:max="max"
:min="min"
:process-style="{ background: '#4ecd5f' }"
:step-active-style="{ background: '#fff', opacity: '1', border: 'none', boxShadow: 'rgba(0, 0, 0, 0.24) 0px 3px 8px' }"
:tooltip="tooltip || 'hover'"
:tooltip-formatter="formatter"
:tooltip-style="{ background: '#4ecd5f', borderColor: '#4ecd5f' }"
@change="val => emit('change', val)"
/>
</div>
</template>

<style>
.slider__container {
@apply relative flex space-x-3 items-start justify-center pr-5 pl-2 pt-14 pb-8 border border-gray-300 rounded-md;
}

.slider__container.disabled {
@apply cursor-not-allowed opacity-50;
}

.slider__title {
@apply absolute top-0 inline-block px-3 text-gray-500 transform -translate-y-1/2 bg-white;
}

</style>
Loading