mirror of
https://github.com/unraid/api.git
synced 2026-01-04 07:29:48 -06:00
feat: changelog modal
This commit is contained in:
139
web/components/UpdateOs/ChangelogModal.vue
Normal file
139
web/components/UpdateOs/ChangelogModal.vue
Normal file
@@ -0,0 +1,139 @@
|
||||
<script setup lang="ts">
|
||||
import { ArrowTopRightOnSquareIcon, ArrowSmallRightIcon, KeyIcon, ServerStackIcon, XMarkIcon } from '@heroicons/vue/24/solid';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { usePurchaseStore } from '~/store/purchase';
|
||||
import { useUpdateOsStore } from '~/store/updateOs';
|
||||
// import { useUpdateOsActionsStore } from '~/store/updateOsActions';
|
||||
import { useUpdateOsChangelogStore } from '~/store/updateOsChangelog';
|
||||
|
||||
export interface Props {
|
||||
open?: boolean;
|
||||
t: any;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
open: false,
|
||||
});
|
||||
|
||||
const purchaseStore = usePurchaseStore();
|
||||
const updateOsStore = useUpdateOsStore();
|
||||
// const updateOsActionsStore = useUpdateOsActionsStore();
|
||||
const updateOsChangelogStore = useUpdateOsChangelogStore();
|
||||
|
||||
const { available, availableWithRenewal } = storeToRefs(updateOsStore);
|
||||
const {
|
||||
isReleaseForUpdateStable,
|
||||
releaseForUpdate,
|
||||
mutatedParsedChangelog,
|
||||
parseChangelogFailed,
|
||||
parsedChangelogTitle,
|
||||
} = storeToRefs(updateOsChangelogStore);
|
||||
|
||||
const showExternalChangelogLink = computed(() => {
|
||||
return (
|
||||
releaseForUpdate.value &&
|
||||
isReleaseForUpdateStable.value &&
|
||||
(releaseForUpdate.value?.changelog)
|
||||
);
|
||||
});
|
||||
|
||||
const showExtendKeyButton = computed(() => {
|
||||
return availableWithRenewal.value;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal
|
||||
:error="!!parseChangelogFailed"
|
||||
:open="!!releaseForUpdate"
|
||||
:title="parsedChangelogTitle ?? undefined"
|
||||
max-width="max-w-800px"
|
||||
:t="t"
|
||||
@close="updateOsChangelogStore.setReleaseForUpdate(null)"
|
||||
>
|
||||
<template #main>
|
||||
<div
|
||||
v-if="mutatedParsedChangelog"
|
||||
class="prose dark:prose-invert prose-a:text-unraid-red hover:prose-a:no-underline hover:prose-a:text-unraid-red/60 dark:prose-a:text-orange hover:dark:prose-a:text-orange/60"
|
||||
v-html="mutatedParsedChangelog"
|
||||
/>
|
||||
|
||||
<div
|
||||
v-else-if="parseChangelogFailed"
|
||||
class="text-center flex flex-col gap-4 prose dark:prose-invert"
|
||||
>
|
||||
<h2 class="text-lg text-unraid-red italic font-semibold">
|
||||
{{ props.t(`Error Parsing Changelog • {0}`, [parseChangelogFailed]) }}
|
||||
</h2>
|
||||
<p>
|
||||
{{ props.t(`It's highly recommended to review the changelog before continuing your update`) }}
|
||||
</p>
|
||||
<div class="flex self-center">
|
||||
<BrandButton
|
||||
v-if="releaseForUpdate?.changelog"
|
||||
:href="releaseForUpdate?.changelog"
|
||||
btn-style="underline"
|
||||
:external="true"
|
||||
:icon-right="ArrowTopRightOnSquareIcon"
|
||||
>
|
||||
{{ props.t("View Changelog on Docs") }}
|
||||
</BrandButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-else
|
||||
class="text-center flex flex-col justify-center w-full min-h-[250px] min-w-[280px] sm:min-w-[400px]"
|
||||
>
|
||||
<BrandLoading class="w-[150px] mx-auto mt-24px" />
|
||||
<p>{{ props.t("Fetching & parsing changelog…") }}</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #footer>
|
||||
<div class="flex flex-col gap-3 sm:gap-4 sm:flex-row sm:justify-between">
|
||||
<div>
|
||||
<BrandButton
|
||||
v-if="showExternalChangelogLink"
|
||||
:href="releaseForUpdate?.changelog"
|
||||
btn-style="underline"
|
||||
:external="true"
|
||||
:icon-right="ArrowTopRightOnSquareIcon"
|
||||
>
|
||||
{{ props.t("View Docs") }}
|
||||
</BrandButton>
|
||||
</div>
|
||||
<div class="flex flex-col sm:flex-row gap-3 sm:gap-4 sm:justify-end">
|
||||
<BrandButton
|
||||
btn-style="underline-hover-red"
|
||||
:icon="XMarkIcon"
|
||||
@click="updateOsChangelogStore.setReleaseForUpdate(null)"
|
||||
>
|
||||
{{ props.t("Close") }}
|
||||
</BrandButton>
|
||||
<template v-if="releaseForUpdate">
|
||||
<BrandButton
|
||||
v-if="showExtendKeyButton"
|
||||
btn-style="fill"
|
||||
:icon="KeyIcon"
|
||||
:icon-right="ArrowTopRightOnSquareIcon"
|
||||
@click="purchaseStore.renew()"
|
||||
>
|
||||
{{ props.t("Extend Key to Update") }}
|
||||
</BrandButton>
|
||||
<BrandButton
|
||||
v-else-if="releaseForUpdate.sha256"
|
||||
:icon="ServerStackIcon"
|
||||
:icon-right="ArrowSmallRightIcon"
|
||||
@click="updateOsChangelogStore.fetchAndConfirmInstall(releaseForUpdate.sha256)"
|
||||
>
|
||||
{{ props.t('Confirm Install Unraid OS {0}', [available]) }}
|
||||
</BrandButton>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</Modal>
|
||||
</template>
|
||||
103
web/store/updateOsChangelog.ts
Normal file
103
web/store/updateOsChangelog.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
import { marked } from 'marked';
|
||||
import { defineStore } from 'pinia';
|
||||
import prerelease from 'semver/functions/prerelease';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
|
||||
import { request } from '~/composables/services/request';
|
||||
import { useCallbackStore } from '~/store/callbackActions';
|
||||
// import { useServerStore } from '~/store/server';
|
||||
import type { ServerUpdateOsResponse } from '~/types/server';
|
||||
|
||||
export const useUpdateOsChangelogStore = defineStore('updateOsChangelog', () => {
|
||||
const callbackStore = useCallbackStore();
|
||||
// const serverStore = useServerStore();
|
||||
// const osVersionBranch = computed(() => serverStore.osVersionBranch);
|
||||
|
||||
const releaseForUpdate = ref<ServerUpdateOsResponse | null>(null);
|
||||
watch(releaseForUpdate, async (newVal, oldVal) => {
|
||||
console.debug('[releaseForUpdate] watch', newVal, oldVal);
|
||||
resetChangelogDetails(); // reset values when setting and unsetting a selected release
|
||||
// Fetch and parse the changelog when the user selects a release
|
||||
if (newVal) {
|
||||
await fetchAndParseChangelog();
|
||||
}
|
||||
});
|
||||
|
||||
const changelogUrl = computed((): string => {
|
||||
if (!releaseForUpdate.value) { return ''; }
|
||||
/** @todo have Eli provide the correct URL with changelog and changelog_pretty */
|
||||
return `https://raw.githubusercontent.com/unraid/docs/main/docs/unraid-os/release-notes/${releaseForUpdate.value.version}.md`;
|
||||
});
|
||||
|
||||
const isReleaseForUpdateStable = computed(() => releaseForUpdate.value ? prerelease(releaseForUpdate.value.version) === null : false);
|
||||
const parsedChangelog = ref<string>('');
|
||||
const parseChangelogFailed = ref<string>('');
|
||||
// used to remove the first <h1></h1> and it's contents from the parsedChangelog
|
||||
const mutatedParsedChangelog = computed(() => {
|
||||
if (parsedChangelog.value) {
|
||||
return parsedChangelog.value.replace(/<h1>(.*?)<\/h1>/, '');
|
||||
}
|
||||
return parsedChangelog.value;
|
||||
});
|
||||
// used to extract the first <h1></h1> and it's contents from the parsedChangelog for the modal header title
|
||||
const parsedChangelogTitle = computed(() => {
|
||||
if (parseChangelogFailed.value) {
|
||||
return parseChangelogFailed.value;
|
||||
}
|
||||
if (parsedChangelog.value) {
|
||||
return parsedChangelog.value.match(/<h1>(.*?)<\/h1>/)?.[1] ?? `Version ${releaseForUpdate.value?.version} ${releaseForUpdate.value?.date}`;
|
||||
}
|
||||
return '';
|
||||
});
|
||||
|
||||
const setReleaseForUpdate = (release: ServerUpdateOsResponse | null) => {
|
||||
console.debug('[setReleaseForUpdate]', release);
|
||||
releaseForUpdate.value = release;
|
||||
};
|
||||
const resetChangelogDetails = () => {
|
||||
console.debug('[resetChangelogDetails]');
|
||||
parsedChangelog.value = '';
|
||||
parseChangelogFailed.value = '';
|
||||
};
|
||||
const fetchAndParseChangelog = async () => {
|
||||
console.debug('[fetchAndParseChangelog]');
|
||||
try {
|
||||
const changelogMarkdownRaw = await request
|
||||
.url(changelogUrl.value ?? releaseForUpdate.value?.changelog ?? '')
|
||||
.get()
|
||||
.text();
|
||||
parsedChangelog.value = await marked.parse(changelogMarkdownRaw);
|
||||
} catch (error: unknown) {
|
||||
const caughtError = error as Error;
|
||||
parseChangelogFailed.value =
|
||||
caughtError && caughtError?.message
|
||||
? caughtError.message
|
||||
: `Failed to parse ${releaseForUpdate.value?.version} changelog`;
|
||||
}
|
||||
};
|
||||
|
||||
const fetchAndConfirmInstall = (sha256: string) => {
|
||||
callbackStore.send(
|
||||
window.location.href,
|
||||
[{
|
||||
sha256,
|
||||
type: 'updateOs',
|
||||
}],
|
||||
undefined,
|
||||
'forUpc',
|
||||
);
|
||||
};
|
||||
|
||||
return {
|
||||
// state
|
||||
parseChangelogFailed,
|
||||
releaseForUpdate,
|
||||
// getters
|
||||
isReleaseForUpdateStable,
|
||||
mutatedParsedChangelog,
|
||||
parsedChangelogTitle,
|
||||
// actions
|
||||
setReleaseForUpdate,
|
||||
fetchAndConfirmInstall,
|
||||
};
|
||||
});
|
||||
Reference in New Issue
Block a user