| 1 | <template> |
| 2 | <n-spin :show="loading" content-class="flex grow flex-col" class="flex flex-col overflow-hidden"> |
| 3 | <div v-if="license" class="flex flex-col gap-4"> |
| 4 | <CardKV v-if="!hideKey"> |
| 5 | <template #key> |
| 6 | <span class="flex items-center gap-3"> |
| 7 | <Icon :name="KeyIcon" :size="14" /> |
| 8 | <span>Key</span> |
| 9 | </span> |
| 10 | </template> |
| 11 | <template #value> |
| 12 | {{ license.key }} |
| 13 | </template> |
| 14 | </CardKV> |
| 15 | <CardKV v-if="!hideFeatures"> |
| 16 | <template #key> |
| 17 | <span class="flex items-center gap-3"> |
| 18 | <Icon :name="FeaturesIcon" :size="14" /> |
| 19 | <span>Features</span> |
| 20 | </span> |
| 21 | </template> |
| 22 | <template #value> |
| 23 | <div v-if="features.length" class="grid-auto-fit-200 grid gap-2"> |
| 24 | <CardKV v-for="feature of features" :key="feature"> |
| 25 | <template #value> |
| 26 | <span class="flex items-center gap-3"> |
| 27 | <Icon :name="CheckIcon" :size="14" class="text-primary" /> |
| 28 | <span>{{ feature }}</span> |
| 29 | </span> |
| 30 | </template> |
| 31 | </CardKV> |
| 32 | </div> |
| 33 | <template v-else>No feature enabled</template> |
| 34 | </template> |
| 35 | </CardKV> |
| 36 | <CardKV> |
| 37 | <template #key> |
| 38 | <span class="flex items-center gap-3"> |
| 39 | <Icon :name="ExpiresIcon" :size="14" /> |
| 40 | <span>Expires</span> |
| 41 | </span> |
| 42 | </template> |
| 43 | <template #value> |
| 44 | {{ expiresText }} |
| 45 | <span class="text-secondary">({{ periodText }})</span> |
| 46 | </template> |
| 47 | </CardKV> |
| 48 | <CardKV> |
| 49 | <template #key> |
| 50 | <span class="flex items-center gap-3"> |
| 51 | <Icon :name="CustomerIcon" :size="14" /> |
| 52 | <span>Customer</span> |
| 53 | </span> |
| 54 | </template> |
| 55 | <template #value> |
| 56 | <div class="flex flex-wrap gap-2"> |
| 57 | <Badge v-for="(value, key) of license.customer" :key type="splitted" color="primary" fluid> |
| 58 | <template #label> |
| 59 | {{ sanitizeKey(key) }} |
| 60 | </template> |
| 61 | <template #value> |
| 62 | <template v-if="key === 'created'"> |
| 63 | {{ formatDate(value, dFormats.datetime) }} |
| 64 | </template> |
| 65 | <template v-else> |
| 66 | {{ value ?? "-" }} |
| 67 | </template> |
| 68 | </template> |
| 69 | </Badge> |
| 70 | </div> |
| 71 | </template> |
| 72 | </CardKV> |
| 73 | <CardKV v-if="dockerCompose"> |
| 74 | <template #key> |
| 75 | <span class="flex items-center gap-3"> |
| 76 | <Icon :name="ConfigIcon" :size="14" /> |
| 77 | <span>Docker Configuration</span> |
| 78 | </span> |
| 79 | </template> |
| 80 | <template #value> |
| 81 | <Markdown :source="dockerCompose" code-bg-transparent /> |
| 82 | </template> |
| 83 | </CardKV> |
| 84 | </div> |
| 85 | </n-spin> |
| 86 | </template> |
| 87 | |
| 88 | <script setup lang="ts"> |
| 89 | import type { License, LicenseFeatures } from "@/types/license.d" |
| 90 | import _startCase from "lodash/startCase" |
| 91 | import { NSpin, useMessage } from "naive-ui" |
| 92 | import { computed, defineAsyncComponent, onBeforeMount, onMounted, ref, toRefs } from "vue" |
| 93 | import Api from "@/api" |
| 94 | import Badge from "@/components/common/Badge.vue" |
| 95 | import CardKV from "@/components/common/cards/CardKV.vue" |
| 96 | import Icon from "@/components/common/Icon.vue" |
| 97 | import { useSettingsStore } from "@/stores/settings" |
| 98 | import { formatDate } from "@/utils/format" |
| 99 | |
| 100 | const props = defineProps<{ |
| 101 | licenseData?: License |
| 102 | featuresData?: LicenseFeatures[] |
| 103 | hideKey?: boolean |
| 104 | hideFeatures?: boolean |
| 105 | }>() |
| 106 | |
| 107 | const emit = defineEmits<{ |
| 108 | (e: "licenseLoaded", value: License): void |
| 109 | ( |
| 110 | e: "mounted", |
| 111 | value: { |
| 112 | reload: () => void |
| 113 | } |
| 114 | ): void |
| 115 | }>() |
| 116 | |
| 117 | const Markdown = defineAsyncComponent(() => import("@/components/common/Markdown.vue")) |
| 118 | |
| 119 | const { licenseData, featuresData, hideKey, hideFeatures } = toRefs(props) |
| 120 | |
| 121 | const KeyIcon = "ph:key" |
| 122 | const ExpiresIcon = "ph:calendar-blank" |
| 123 | const CustomerIcon = "carbon:user" |
| 124 | const CheckIcon = "carbon:checkmark-outline" |
| 125 | const FeaturesIcon = "material-symbols:checklist" |
| 126 | const ConfigIcon = "carbon:settings" |
| 127 | |
| 128 | const message = useMessage() |
| 129 | const loadingLicense = ref(false) |
| 130 | const loadingFeatures = ref(false) |
| 131 | const loadingDockerCompose = ref(false) |
| 132 | const dFormats = useSettingsStore().dateFormat |
| 133 | |
| 134 | const licenseLoaded = ref<License | null>(null) |
| 135 | const featuresLoaded = ref<LicenseFeatures[]>([]) |
| 136 | const dockerCompose = ref<string | null>(null) |
| 137 | const license = computed(() => licenseLoaded.value || licenseData?.value || null) |
| 138 | const features = computed(() => featuresLoaded.value || featuresData?.value || []) |
| 139 | const expiresText = computed(() => (license.value ? formatDate(license.value.expires, dFormats.datetime) : "")) |
| 140 | const periodText = computed(() => |
| 141 | license.value ? `${license.value.period} Day${license.value.period === 1 ? "" : "s"}` : "" |
| 142 | ) |
| 143 | |
| 144 | const loading = computed(() => loadingLicense.value || loadingFeatures.value || loadingDockerCompose.value) |
| 145 | |
| 146 | function getLicense() { |
| 147 | loadingLicense.value = true |
| 148 | |
| 149 | Api.license |
| 150 | .verifyLicense() |
| 151 | .then(res => { |
| 152 | if (res.data.success) { |
| 153 | licenseLoaded.value = res.data?.license |
| 154 | emit("licenseLoaded", licenseLoaded.value) |
| 155 | } else { |
| 156 | message.warning(res.data?.message || "An error occurred. Please try again later.") |
| 157 | } |
| 158 | }) |
| 159 | .catch(err => { |
| 160 | if (err.response.status !== 404) { |
| 161 | message.error(err.response?.data?.message || "An error occurred. Please try again later.") |
| 162 | } |
| 163 | }) |
| 164 | .finally(() => { |
| 165 | loadingLicense.value = false |
| 166 | }) |
| 167 | } |
| 168 | |
| 169 | function getLicenseFeatures() { |
| 170 | loadingFeatures.value = true |
| 171 | |
| 172 | Api.license |
| 173 | .getLicenseFeatures() |
| 174 | .then(res => { |
| 175 | if (res.data.success) { |
| 176 | featuresLoaded.value = res.data?.features |
| 177 | } else { |
| 178 | message.warning(res.data?.message || "An error occurred. Please try again later.") |
| 179 | } |
| 180 | }) |
| 181 | .catch(err => { |
| 182 | if (err.response.status !== 404) { |
| 183 | message.error(err.response?.data?.message || "An error occurred. Please try again later.") |
| 184 | } |
| 185 | }) |
| 186 | .finally(() => { |
| 187 | loadingFeatures.value = false |
| 188 | }) |
| 189 | } |
| 190 | |
| 191 | function retrieveDockerCompose() { |
| 192 | loadingDockerCompose.value = true |
| 193 | |
| 194 | Api.license |
| 195 | .retrieveDockerCompose() |
| 196 | .then(res => { |
| 197 | if (res.data.success) { |
| 198 | dockerCompose.value = res.data?.docker_compose |
| 199 | } else { |
| 200 | message.warning(res.data?.message || "An error occurred. Please try again later.") |
| 201 | } |
| 202 | }) |
| 203 | .catch(err => { |
| 204 | if (err.response.status !== 404) { |
| 205 | message.error(err.response?.data?.message || "An error occurred. Please try again later.") |
| 206 | } |
| 207 | }) |
| 208 | .finally(() => { |
| 209 | loadingDockerCompose.value = false |
| 210 | }) |
| 211 | } |
| 212 | |
| 213 | function load() { |
| 214 | if (!license.value) { |
| 215 | getLicense() |
| 216 | } |
| 217 | if (!hideFeatures.value && !features.value.length) { |
| 218 | getLicenseFeatures() |
| 219 | } |
| 220 | |
| 221 | retrieveDockerCompose() |
| 222 | } |
| 223 | |
| 224 | function sanitizeKey(text: string) { |
| 225 | return _startCase(text).toLowerCase() |
| 226 | } |
| 227 | |
| 228 | onBeforeMount(() => { |
| 229 | load() |
| 230 | }) |
| 231 | |
| 232 | onMounted(() => { |
| 233 | emit("mounted", { |
| 234 | reload: load |
| 235 | }) |
| 236 | }) |
| 237 | </script> |