Skip to content

Commit

Permalink
impl: InstancesView
Browse files Browse the repository at this point in the history
  • Loading branch information
cp-20 committed Jan 18, 2025
1 parent e1bf37b commit c38b359
Show file tree
Hide file tree
Showing 7 changed files with 443 additions and 5 deletions.
3 changes: 3 additions & 0 deletions client/src/assets/base.css
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@
--ct-red-950-rgb: 69, 10, 10;

--color-primary: #005bac;
--color-primary-rgb: 0, 91, 172;
--color-primary-hover: #004a9a;
--color-primary-hover-rgb: 0, 74, 154;
--color-primary-background: var(--ct-slate-200);
}

Expand Down
1 change: 0 additions & 1 deletion client/src/components/BenchmarkStatusChip.vue
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ const labels: Record<Status, string> = {
line-height: 2;
border-radius: 2px;
width: 72px;
color: white;
font-weight: 600;
font-size: 0.7rem;
text-align: center;
Expand Down
95 changes: 95 additions & 0 deletions client/src/components/CopyToClipboardButton.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
<script setup lang="ts">
import { Icon } from '@iconify/vue'
import { ref } from 'vue'
const { text } = defineProps<{ text: string }>()
const clicked = ref(false)
const copyToClipboard = () => {
navigator.clipboard.writeText(text)
clicked.value = true
setTimeout(() => {
clicked.value = false
}, 3000)
}
</script>

<template>
<button @click="copyToClipboard" class="copy-to-clipboard-button" :class="{ clicked }">
<Icon class="copy-icon" icon="mdi:content-copy" width="16" height="16" />
<Icon class="copied-icon" icon="mdi:check-bold" width="16" height="16" />
</button>
</template>

<style scoped>
.copy-to-clipboard-button {
background-color: transparent;
border: none;
cursor: pointer;
padding: 0;
margin: 0;
position: relative;
width: 16px;
height: 16px;
}
.copy-icon,
.copied-icon {
position: absolute;
inset: 0;
}
.copy-icon {
opacity: 1;
}
.copied-icon {
opacity: 0;
}
.copy-to-clipboard-button.clicked .copy-icon {
animation: copy-icon 3s;
}
@keyframes copy-icon {
0% {
opacity: 1;
transform: scale(1);
}
10% {
opacity: 0;
transform: scale(0);
}
90% {
opacity: 0;
transform: scale(0);
}
100% {
opacity: 1;
transform: scale(1);
}
}
.copy-to-clipboard-button.clicked .copied-icon {
animation: copied-icon 3s;
}
@keyframes copied-icon {
0% {
opacity: 0;
transform: scale(0);
}
10% {
opacity: 1;
transform: scale(1);
}
90% {
opacity: 1;
transform: scale(1);
}
100% {
opacity: 0;
transform: scale(0);
}
}
</style>
210 changes: 210 additions & 0 deletions client/src/components/InstanceCardList.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
<script setup lang="ts">
import CopyToClipboardButton from '@/components/CopyToClipboardButton.vue'
import InstanceStatusChip from '@/components/InstanceStatusChip.vue'
import MainButton from '@/components/MainButton.vue'
import MainSwitch from '@/components/MainSwitch.vue'
import {
useCreateTeamInstance,
useDeleteTeamInstance,
useStartTeamInstance,
useStopTeamInstance,
useTeamInstances,
} from '@/lib/useServerData'
import { Icon } from '@iconify/vue'
import { useQueryClient } from '@tanstack/vue-query'
import { ref, computed } from 'vue'
const { teamId } = defineProps<{ teamId: string }>()
const showDeleted = ref(false)
const client = useQueryClient()
const { data: instances, refetch } = useTeamInstances(teamId)
const { mutate: createInstance } = useCreateTeamInstance(client)
const { mutate: startInstance } = useStartTeamInstance(client)
const { mutate: stopInstance } = useStopTeamInstance(client)
const { mutate: deleteInstance } = useDeleteTeamInstance(client)
const visibleInstances = computed(() =>
instances.value?.filter((i) => showDeleted.value || i.status !== 'deleted'),
)
setInterval(() => {
if (
instances.value?.some(
(i) =>
i.status === 'building' ||
i.status === 'starting' ||
i.status === 'stopping' ||
i.status === 'deleting',
)
) {
refetch()
}
}, 500)
</script>

<template>
<div class="instance-card-list-container">
<div class="instance-card-list-header">
<MainSwitch v-model="showDeleted">削除済みのインスタンスも表示する</MainSwitch>
</div>
<div v-if="visibleInstances?.length === 0" class="no-instances">
まだインスタンスがありません
</div>
<div class="instance-card-list">
<div v-for="instance in visibleInstances" :key="instance.id" class="instance-card">
<div class="instance-info">
<div class="card-title">
<Icon icon="mdi:server-network" width="24" height="24" />
<div>サーバー{{ instance.serverId }}</div>
<InstanceStatusChip :status="instance.status" />
</div>
<div class="info-elements">
<div class="info-element">
<div class="info-label">プライベートIPアドレス</div>
<div class="info-value">
<span>
{{ instance.privateIPAddress }}
</span>
<CopyToClipboardButton :text="instance.privateIPAddress" />
</div>
</div>
<div class="info-element">
<div class="info-label">パブリックIPアドレス</div>
<div class="info-value">
<span>
{{ instance.publicIPAddress }}
</span>
<CopyToClipboardButton :text="instance.publicIPAddress" />
</div>
</div>
</div>
</div>
<div class="action-buttons">
<MainButton
class="action-button"
:disabled="instance.status !== 'stopped'"
@click="startInstance({ teamId, instanceId: instance.id })"
>
<Icon icon="mdi:play-circle" width="20" height="20" />
<span>起動</span>
</MainButton>
<MainButton
class="action-button"
:disabled="instance.status !== 'running'"
@click="stopInstance({ teamId, instanceId: instance.id })"
>
<Icon icon="mdi:stop-pause" width="20" height="20" />
<span>停止</span>
</MainButton>
<MainButton
class="action-button"
variant="destructive"
:disabled="instance.status !== 'stopped'"
@click="deleteInstance({ teamId, instanceId: instance.id })"
>
<Icon icon="mdi:trash-can" width="20" height="20" />
<span>削除</span>
</MainButton>
</div>
</div>
</div>
<MainButton @click="createInstance({ teamId })" class="create-instance-button">
<Icon icon="mdi:tools" width="20" height="20" />
<span>新しいサーバーを作成</span>
</MainButton>
</div>
</template>

<style scoped>
.instance-card-list-container {
display: flex;
flex-direction: column;
gap: 1rem;
}
.instance-card-list-header {
display: flex;
justify-content: flex-end;
font-size: 0.9rem;
font-weight: 600;
color: var(--ct-slate-600);
}
.no-instances {
padding: 1rem;
border: 1px dashed var(--ct-slate-300);
border-radius: 4px;
text-align: center;
color: var(--ct-slate-400);
}
.instance-card-list {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(600px, 1fr));
gap: 1rem;
}
.instance-card {
padding: 1rem;
border: 1px solid var(--ct-slate-300);
border-radius: 4px;
display: flex;
gap: 2rem;
}
.instance-info {
display: flex;
flex-direction: column;
gap: 0.5rem;
flex: 1;
}
.card-title {
font-weight: 600;
display: flex;
align-items: center;
gap: 0.5rem;
}
.info-elements {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 0.5rem;
}
.info-label {
font-size: 0.8rem;
font-weight: 600;
}
.info-value {
display: flex;
align-items: center;
gap: 0.25rem;
}
.action-buttons {
display: flex;
flex-direction: column;
gap: 0.5rem;
width: 160px;
}
.action-button {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
gap: 0.25rem;
}
.create-instance-button {
width: 100%;
display: flex;
align-items: center;
justify-content: center;
gap: 0.25rem;
}
</style>
60 changes: 60 additions & 0 deletions client/src/components/InstanceStatusChip.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<script setup lang="ts">
import type { components } from '@/api/openapi'
type Status = components['schemas']['InstanceStatus']
const { status } = defineProps<{ status: Status }>()
const labels: Record<Status, string> = {
building: 'BUILDING',
starting: 'STARTING',
running: 'RUNNING',
stopping: 'STOPPING',
stopped: 'STOPPED',
deleting: 'DELETING',
deleted: 'DELETED',
}
</script>

<template>
<div class="status-chip" :class="status">
{{ labels[status] }}
</div>
</template>

<style scoped>
.status-chip {
line-height: 2;
border-radius: 2px;
width: 72px;
font-weight: 600;
font-size: 0.7rem;
text-align: center;
}
.status-chip.building {
background-color: #f0ad4e33;
color: #f0ad4e;
}
.status-chip.starting {
background-color: #5bc0de33;
color: #5bc0de;
}
.status-chip.running {
background-color: #5cb85c33;
color: #5cb85c;
}
.status-chip.stopping {
background-color: #f0ad4e33;
color: #f0ad4e;
}
.status-chip.stopped {
background-color: #de5b5b33;
color: #de5b5b;
}
.status-chip.deleting {
background-color: #f0ad4e33;
color: #f0ad4e;
}
.status-chip.deleted {
background-color: #77777733;
color: #777777;
}
</style>
Loading

0 comments on commit c38b359

Please sign in to comment.