mirror of
https://github.com/unraid/api.git
synced 2026-01-07 09:10:05 -06:00
refactor: uptime expire time formatting
This commit is contained in:
@@ -85,3 +85,58 @@ withDefaults(defineProps<Props>(), {
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<style lang="postcss">
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
|
||||
.unraid_mark_2,
|
||||
.unraid_mark_4 {
|
||||
animation: mark_2 1.5s ease infinite;
|
||||
}
|
||||
.unraid_mark_3 {
|
||||
animation: mark_3 1.5s ease infinite;
|
||||
}
|
||||
.unraid_mark_6,
|
||||
.unraid_mark_8 {
|
||||
animation: mark_6 1.5s ease infinite;
|
||||
}
|
||||
.unraid_mark_7 {
|
||||
animation: mark_7 1.5s ease infinite;
|
||||
}
|
||||
|
||||
@keyframes mark_2 {
|
||||
50% {
|
||||
transform: translateY(-40px);
|
||||
}
|
||||
100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
@keyframes mark_3 {
|
||||
50% {
|
||||
transform: translateY(-62px);
|
||||
}
|
||||
100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
@keyframes mark_6 {
|
||||
50% {
|
||||
transform: translateY(40px);
|
||||
}
|
||||
100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
@keyframes mark_7 {
|
||||
50% {
|
||||
transform: translateY(62px);
|
||||
}
|
||||
100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@tailwind utilities;
|
||||
</style>
|
||||
|
||||
@@ -79,7 +79,7 @@ onBeforeMount(() => {
|
||||
<div v-if="bannerGradient" class="absolute z-0 w-[125%] top-0 bottom-0 right-0" :style="bannerGradient" />
|
||||
|
||||
<div class="text-gamma text-10px xs:text-12px text-right font-semibold leading-normal relative z-10 flex flex-col items-end justify-end gap-x-4px xs:flex-row xs:items-baseline xs:gap-x-12px">
|
||||
<UpcUptimeExpire />
|
||||
<UpcUptimeExpire :t="t" />
|
||||
<span class="hidden xs:block">•</span>
|
||||
<UpcServerState :t="t" />
|
||||
</div>
|
||||
|
||||
@@ -193,7 +193,12 @@ const accountActionStatusCopy = computed((): { text: string; } => {
|
||||
:error="keyInstallStatus === 'failed'"
|
||||
:text="keyInstallStatusCopy.text"
|
||||
>
|
||||
<UpcUptimeExpire v-if="keyType === 'Trial'" :for-expire="true" class="opacity-75 italic mt-4px" />
|
||||
<UpcUptimeExpire
|
||||
v-if="keyType === 'Trial'"
|
||||
:for-expire="true"
|
||||
class="opacity-75 italic mt-4px"
|
||||
:t="t"
|
||||
/>
|
||||
|
||||
<template v-if="keyInstallStatus === 'failed'">
|
||||
<div v-if="isSupported" class="flex justify-center">
|
||||
@@ -281,5 +286,54 @@ const accountActionStatusCopy = computed((): { text: string; } => {
|
||||
<style lang="postcss">
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
|
||||
.unraid_mark_2,
|
||||
.unraid_mark_4 {
|
||||
animation: mark_2 1.5s ease infinite;
|
||||
}
|
||||
.unraid_mark_3 {
|
||||
animation: mark_3 1.5s ease infinite;
|
||||
}
|
||||
.unraid_mark_6,
|
||||
.unraid_mark_8 {
|
||||
animation: mark_6 1.5s ease infinite;
|
||||
}
|
||||
.unraid_mark_7 {
|
||||
animation: mark_7 1.5s ease infinite;
|
||||
}
|
||||
|
||||
@keyframes mark_2 {
|
||||
50% {
|
||||
transform: translateY(-40px);
|
||||
}
|
||||
100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
@keyframes mark_3 {
|
||||
50% {
|
||||
transform: translateY(-62px);
|
||||
}
|
||||
100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
@keyframes mark_6 {
|
||||
50% {
|
||||
transform: translateY(40px);
|
||||
}
|
||||
100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
@keyframes mark_7 {
|
||||
50% {
|
||||
transform: translateY(62px);
|
||||
}
|
||||
100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@tailwind utilities;
|
||||
</style>
|
||||
|
||||
@@ -30,7 +30,11 @@ const showExpireTime = computed(() => {
|
||||
<header :class="{ 'text-center': showConnectCopy }">
|
||||
<h2 class="text-24px text-center font-semibold" v-html="heading" />
|
||||
<div class="flex flex-col gap-y-8px" v-html="subheading" />
|
||||
<UpcUptimeExpire v-if="showExpireTime" class="text-center opacity-75 mt-12px" />
|
||||
<UpcUptimeExpire
|
||||
v-if="showExpireTime"
|
||||
class="text-center opacity-75 mt-12px"
|
||||
:t="t"
|
||||
/>
|
||||
</header>
|
||||
<ul v-if="stateData.actions" class="list-reset flex flex-col gap-y-8px px-16px">
|
||||
<li v-for="action in stateData.actions" :key="action.name">
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
<script setup lang="ts">
|
||||
import { storeToRefs } from 'pinia';
|
||||
import dateDiff from '~/helpers/time/dateDiff';
|
||||
import dateFormat from '~/helpers/time/dateFormat';
|
||||
import buildStringFromValues from '~/helpers/time/buildTimeString';
|
||||
|
||||
import useTimeHelper from '~/composables/time';
|
||||
import { useServerStore } from '~/store/server';
|
||||
|
||||
export interface Props {
|
||||
forExpire?: boolean;
|
||||
t: any;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
forExpire: false,
|
||||
});
|
||||
|
||||
const { buildStringFromValues, dateDiff, formatDate } = useTimeHelper(props.t);
|
||||
|
||||
const serverStore = useServerStore();
|
||||
const { uptime, expireTime, state } = storeToRefs(serverStore);
|
||||
|
||||
@@ -26,9 +28,7 @@ const time = computed(() => {
|
||||
});
|
||||
|
||||
const parsedTime = ref<string>('');
|
||||
const formattedTime = computed<string>(() => {
|
||||
return dateFormat(time.value);
|
||||
});
|
||||
const formattedTime = computed<string>(() => formatDate(time.value));
|
||||
|
||||
const countUp = computed<boolean>(() => {
|
||||
if (props.forExpire && expireTime.value) {
|
||||
@@ -41,16 +41,16 @@ const output = computed(() => {
|
||||
if (!countUp.value || state.value === 'EEXPIRED') {
|
||||
return {
|
||||
title: state.value === 'EEXPIRED'
|
||||
? `Trial Key Expired at ${formattedTime.value}`
|
||||
: `Trial Key Expires at ${formattedTime.value}`,
|
||||
? props.t('Trial Key Expired at {0}', [formattedTime.value])
|
||||
: props.t('Trial Key Expires at {0}', [formattedTime.value]),
|
||||
text: state.value === 'EEXPIRED'
|
||||
? `Trial Key Expired ${parsedTime.value}`
|
||||
: `Trial Key Expires in ${parsedTime.value}`,
|
||||
? props.t('Trial Key Expired {0}', [parsedTime.value])
|
||||
: props.t('Trial Key Expires in {0}', [parsedTime.value]),
|
||||
};
|
||||
}
|
||||
return {
|
||||
title: `Server Up Since ${formattedTime.value}`,
|
||||
text: `Uptime ${parsedTime.value}`,
|
||||
title: props.t('Server Up Since {0}', [formattedTime.value]),
|
||||
text: props.t('Uptime {0}', [parsedTime.value]),
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
140
composables/time.ts
Normal file
140
composables/time.ts
Normal file
@@ -0,0 +1,140 @@
|
||||
import dayjs, { extend } from 'dayjs';
|
||||
import localizedFormat from 'dayjs/plugin/localizedFormat';
|
||||
|
||||
/** @see https://day.js.org/docs/en/display/format#localized-formats */
|
||||
extend(localizedFormat);
|
||||
|
||||
export interface TimeStringsObject {
|
||||
years: number;
|
||||
months: number;
|
||||
days: number;
|
||||
hours: number;
|
||||
minutes: number;
|
||||
seconds: number;
|
||||
firstDateWasLater: boolean;
|
||||
displaySeconds?: boolean;
|
||||
}
|
||||
|
||||
const useTimeHelper = (t: any) => {
|
||||
const buildStringFromValues = (payload: TimeStringsObject) => {
|
||||
const {
|
||||
years,
|
||||
months,
|
||||
days,
|
||||
hours,
|
||||
minutes,
|
||||
seconds,
|
||||
firstDateWasLater,
|
||||
displaySeconds,
|
||||
} = payload;
|
||||
const result = [];
|
||||
|
||||
if (years) { result.push(t('year', years)); }
|
||||
if (months) { result.push(t('month', months)); }
|
||||
if (days) { result.push(t('day', days)); }
|
||||
if (hours) { result.push(t('hour', hours)); }
|
||||
if (minutes) { result.push(t('minute', minutes)); }
|
||||
if (seconds && ((!years && !months && !days && !hours && !minutes) || displaySeconds)) { result.push(t('second', seconds)); }
|
||||
if (firstDateWasLater) { result.push(t('ago')); }
|
||||
return result.join(' ');
|
||||
};
|
||||
|
||||
const formatDate = (date: number): string => dayjs(date).format('llll');
|
||||
|
||||
/**
|
||||
* Original meat and potatos from:
|
||||
* @version: 1.0.1
|
||||
* @author: huangjinlin
|
||||
* @repo: https://github.com/huangjinlin/dayjs-precise-range
|
||||
*/
|
||||
const buildValueObject = (
|
||||
yDiff: number,
|
||||
mDiff: number,
|
||||
dDiff: number,
|
||||
hourDiff: number,
|
||||
minDiff: number,
|
||||
secDiff: number,
|
||||
firstDateWasLater: boolean
|
||||
): TimeStringsObject => ({
|
||||
years: yDiff,
|
||||
months: mDiff,
|
||||
days: dDiff,
|
||||
hours: hourDiff,
|
||||
minutes: minDiff,
|
||||
seconds: secDiff,
|
||||
firstDateWasLater,
|
||||
});
|
||||
|
||||
const preciseDateDiff = (d1: dayjs.Dayjs, d2: dayjs.Dayjs): TimeStringsObject => {
|
||||
let m1 = dayjs(d1);
|
||||
let m2 = dayjs(d2);
|
||||
let firstDateWasLater;
|
||||
|
||||
if (m1.isSame(m2)) {
|
||||
return buildValueObject(0, 0, 0, 0, 0, 0, false);
|
||||
}
|
||||
if (m1.isAfter(m2)) {
|
||||
const tmp = m1;
|
||||
m1 = m2;
|
||||
m2 = tmp;
|
||||
firstDateWasLater = true;
|
||||
} else {
|
||||
firstDateWasLater = false;
|
||||
}
|
||||
|
||||
let yDiff = m2.year() - m1.year();
|
||||
let mDiff = m2.month() - m1.month();
|
||||
let dDiff = m2.date() - m1.date();
|
||||
let hourDiff = m2.hour() - m1.hour();
|
||||
let minDiff = m2.minute() - m1.minute();
|
||||
let secDiff = m2.second() - m1.second();
|
||||
|
||||
if (secDiff < 0) {
|
||||
secDiff = 60 + secDiff;
|
||||
minDiff -= 1;
|
||||
}
|
||||
if (minDiff < 0) {
|
||||
minDiff = 60 + minDiff;
|
||||
hourDiff -= 1;
|
||||
}
|
||||
if (hourDiff < 0) {
|
||||
hourDiff = 24 + hourDiff;
|
||||
dDiff -= 1;
|
||||
}
|
||||
if (dDiff < 0) {
|
||||
const daysInLastFullMonth = dayjs(`${m2.year()}-${m2.month() + 1}`).subtract(1, 'M').daysInMonth();
|
||||
if (daysInLastFullMonth < m1.date()) { // 31/01 -> 2/03
|
||||
dDiff = daysInLastFullMonth + dDiff + (m1.date() - daysInLastFullMonth);
|
||||
} else {
|
||||
dDiff = daysInLastFullMonth + dDiff;
|
||||
}
|
||||
mDiff -= 1;
|
||||
}
|
||||
if (mDiff < 0) {
|
||||
mDiff = 12 + mDiff;
|
||||
yDiff -= 1;
|
||||
}
|
||||
|
||||
return buildValueObject(yDiff, mDiff, dDiff, hourDiff, minDiff, secDiff, firstDateWasLater);
|
||||
};
|
||||
|
||||
const readableDifference = (a = '', b = '') => {
|
||||
try {
|
||||
const x = a ? dayjs(parseInt(a, 10)) : dayjs();
|
||||
const y = b ? dayjs(parseInt(b, 10)) : dayjs();
|
||||
return preciseDateDiff(x, y);
|
||||
} catch (error) {
|
||||
throw new Error('Couldn\'t calculate date difference');
|
||||
}
|
||||
};
|
||||
|
||||
const dateDiff = (time: string, countUp: boolean) => countUp ? readableDifference(time, '') : readableDifference('', time);
|
||||
|
||||
return {
|
||||
buildStringFromValues,
|
||||
dateDiff,
|
||||
formatDate,
|
||||
};
|
||||
};
|
||||
|
||||
export default useTimeHelper;
|
||||
@@ -1,42 +0,0 @@
|
||||
import { TimeStringsObject } from '~/types/time';
|
||||
|
||||
const buildStringFromValues = ({
|
||||
years, months, days, hours, minutes, seconds, firstDateWasLater, displaySeconds,
|
||||
}: TimeStringsObject) => {
|
||||
const result = [];
|
||||
|
||||
type DateStrings = {
|
||||
[key: string]: string;
|
||||
};
|
||||
const dateStrings: DateStrings = {
|
||||
year: 'year',
|
||||
years: 'years',
|
||||
month: 'month',
|
||||
months: 'months',
|
||||
day: 'day',
|
||||
days: 'days',
|
||||
hour: 'hour',
|
||||
hours: 'hours',
|
||||
minute: 'minute',
|
||||
minutes: 'minutes',
|
||||
second: 'second',
|
||||
seconds: 'seconds',
|
||||
firstDateWasLater: 'ago',
|
||||
delimiter: ' ',
|
||||
};
|
||||
const pluralize = (num: number, word: string) => {
|
||||
const index = word + (num === 1 ? '' : 's');
|
||||
return `${num} ${dateStrings[index]}`;
|
||||
};
|
||||
|
||||
if (years) { result.push(pluralize(years, 'year')); }
|
||||
if (months) { result.push(pluralize(months, 'month')); }
|
||||
if (days) { result.push(pluralize(days, 'day')); }
|
||||
if (hours) { result.push(pluralize(hours, 'hour')); }
|
||||
if (minutes) { result.push(pluralize(minutes, 'minute')); }
|
||||
if (seconds && ((!years && !months && !days && !hours && !minutes) || displaySeconds)) { result.push(pluralize(seconds, 'second')); }
|
||||
if (firstDateWasLater) { result.push(dateStrings.firstDateWasLater); }
|
||||
return result.join(dateStrings.delimiter);
|
||||
};
|
||||
|
||||
export default buildStringFromValues;
|
||||
@@ -1,11 +0,0 @@
|
||||
import dayjs from 'dayjs';
|
||||
import preciseDateDiff from './preciseDateDiff';
|
||||
|
||||
const readbleDifference = (a = '', b = '') => {
|
||||
const x = a ? dayjs(parseInt(a, 10)) : dayjs();
|
||||
const y = b ? dayjs(parseInt(b, 10)) : dayjs();
|
||||
return preciseDateDiff(x, y);
|
||||
};
|
||||
const dateDiff = (time: string, countUp: boolean) => countUp ? readbleDifference(time, '') : readbleDifference('', time);
|
||||
|
||||
export default dateDiff;
|
||||
@@ -1,9 +0,0 @@
|
||||
import dayjs, { extend } from 'dayjs';
|
||||
import localizedFormat from 'dayjs/plugin/localizedFormat';
|
||||
|
||||
/** @see https://day.js.org/docs/en/display/format#localized-formats */
|
||||
extend(localizedFormat);
|
||||
|
||||
const formatDate = (date: number): string => dayjs(date).format('llll');
|
||||
|
||||
export default formatDate;
|
||||
@@ -1,72 +0,0 @@
|
||||
import dayjs from 'dayjs';
|
||||
import { TimeStringsObject } from '~/types/time';
|
||||
|
||||
/**
|
||||
* Original meat and potatos from:
|
||||
* @version: 1.0.1
|
||||
* @author: huangjinlin
|
||||
* @repo: https://github.com/huangjinlin/dayjs-precise-range
|
||||
*/
|
||||
const buildValueObject = (yDiff: number, mDiff: number, dDiff: number, hourDiff: number, minDiff: number, secDiff: number, firstDateWasLater: boolean): TimeStringsObject => ({
|
||||
years: yDiff,
|
||||
months: mDiff,
|
||||
days: dDiff,
|
||||
hours: hourDiff,
|
||||
minutes: minDiff,
|
||||
seconds: secDiff,
|
||||
firstDateWasLater,
|
||||
});
|
||||
const preciseDateDiff = (d1: dayjs.Dayjs, d2: dayjs.Dayjs): TimeStringsObject => {
|
||||
let m1 = dayjs(d1);
|
||||
let m2 = dayjs(d2);
|
||||
let firstDateWasLater;
|
||||
|
||||
if (m1.isSame(m2)) {
|
||||
return buildValueObject(0, 0, 0, 0, 0, 0, false);
|
||||
}
|
||||
if (m1.isAfter(m2)) {
|
||||
const tmp = m1;
|
||||
m1 = m2;
|
||||
m2 = tmp;
|
||||
firstDateWasLater = true;
|
||||
} else {
|
||||
firstDateWasLater = false;
|
||||
}
|
||||
|
||||
let yDiff = m2.year() - m1.year();
|
||||
let mDiff = m2.month() - m1.month();
|
||||
let dDiff = m2.date() - m1.date();
|
||||
let hourDiff = m2.hour() - m1.hour();
|
||||
let minDiff = m2.minute() - m1.minute();
|
||||
let secDiff = m2.second() - m1.second();
|
||||
|
||||
if (secDiff < 0) {
|
||||
secDiff = 60 + secDiff;
|
||||
minDiff -= 1;
|
||||
}
|
||||
if (minDiff < 0) {
|
||||
minDiff = 60 + minDiff;
|
||||
hourDiff -= 1;
|
||||
}
|
||||
if (hourDiff < 0) {
|
||||
hourDiff = 24 + hourDiff;
|
||||
dDiff -= 1;
|
||||
}
|
||||
if (dDiff < 0) {
|
||||
const daysInLastFullMonth = dayjs(`${m2.year()}-${m2.month() + 1}`).subtract(1, 'M').daysInMonth();
|
||||
if (daysInLastFullMonth < m1.date()) { // 31/01 -> 2/03
|
||||
dDiff = daysInLastFullMonth + dDiff + (m1.date() - daysInLastFullMonth);
|
||||
} else {
|
||||
dDiff = daysInLastFullMonth + dDiff;
|
||||
}
|
||||
mDiff -= 1;
|
||||
}
|
||||
if (mDiff < 0) {
|
||||
mDiff = 12 + mDiff;
|
||||
yDiff -= 1;
|
||||
}
|
||||
|
||||
return buildValueObject(yDiff, mDiff, dDiff, hourDiff, minDiff, secDiff, firstDateWasLater);
|
||||
};
|
||||
|
||||
export default preciseDateDiff;
|
||||
@@ -1,10 +0,0 @@
|
||||
export interface TimeStringsObject {
|
||||
years: number;
|
||||
months: number;
|
||||
days: number;
|
||||
hours: number;
|
||||
minutes: number;
|
||||
seconds: number;
|
||||
firstDateWasLater: boolean;
|
||||
displaySeconds?: boolean;
|
||||
}
|
||||
Reference in New Issue
Block a user