Compare commits

...

2 commits

8 changed files with 213 additions and 49 deletions

View file

@ -113,10 +113,10 @@ export default defineComponent({
},
methods: {
...mapActions(useNotificationStore, ["revoke"]),
close(id: number) {
close(id: string) {
this.revoke(id);
},
hovering(id: number, value: boolean, timeout?: number) {
hovering(id: string, value: boolean, timeout?: number) {
if (value) {
clearTimeout(this.timeouts[id]);
} else {

View file

@ -0,0 +1,44 @@
<template>
<div class="w-full md:max-w-md">
<div class="flex flex-col items-center">
<p class="text-xl font-medium">Newsletter Mail-Versand Logs</p>
</div>
<br />
<div class="h-96 overflow-y-scroll">
<p v-for="entry in mailSourceMessages">
{{ entry }}
</p>
</div>
<div class="flex flex-row justify-end">
<div class="flex flex-row gap-4 py-2">
<button primary-outline @click="closeModal">
abbrechen
</button>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { defineComponent } from "vue";
import { mapState, mapActions } from "pinia";
import { useModalStore } from "@/stores/modal";
import { useNewsletterPrintoutStore } from "@/stores/admin/club/newsletter/newsletterPrintout";
</script>
<script lang="ts">
export default defineComponent({
data() {
return {
};
},
computed:{
...mapState(useNewsletterPrintoutStore, ["mailSourceMessages"])
},
methods: {
...mapActions(useModalStore, ["closeModal"]),
},
});
</script>

View file

@ -0,0 +1,50 @@
<template>
<div class="w-full md:max-w-md">
<div class="flex flex-col items-center">
<p class="text-xl font-medium">Newsletter Druck-Prozess Logs</p>
</div>
<br />
<div class="flex flex-col gap-2 h-96 overflow-y-scroll">
<div
v-for="entry in pdfSourceMessages"
class="flex flex-row gap-2 border border-gray-200 rounded-md p-1 items-center"
>
<SuccessCheckmark v-if="entry.factor == 'success'" class="w-5 h-5" />
<InformationCircleIcon v-else-if="entry.factor == 'info'" class="w-5 h-5 min-h-5 min-w-5 text-gray-500" />
<FailureXMark v-else-if="entry.factor == 'failed'" class="w-5 h-5" />
<p>{{ entry.iteration }}/{{ entry.total }}: {{ entry.msg }}</p>
</div>
</div>
<div class="flex flex-row justify-end">
<div class="flex flex-row gap-4 py-2">
<button primary-outline @click="closeModal">abbrechen</button>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { defineComponent } from "vue";
import { mapState, mapActions } from "pinia";
import { useModalStore } from "@/stores/modal";
import { useNewsletterPrintoutStore } from "@/stores/admin/club/newsletter/newsletterPrintout";
import SuccessCheckmark from "@/components/SuccessCheckmark.vue";
import { InformationCircleIcon } from "@heroicons/vue/24/solid";
import FailureXMark from "@/components/FailureXMark.vue";
</script>
<script lang="ts">
export default defineComponent({
data() {
return {};
},
computed: {
...mapState(useNewsletterPrintoutStore, ["pdfSourceMessages"]),
},
methods: {
...mapActions(useModalStore, ["closeModal"]),
},
});
</script>

View file

@ -9,8 +9,6 @@ export async function setNewsletterId(to: any, from: any, next: any) {
useNewsletterDatesStore().$reset();
useNewsletterRecipientsStore().$reset();
useNewsletterPrintoutStore().unsubscribePdfPrintingProgress();
useNewsletterPrintoutStore().unsubscribeMailSendingProgress();
useNewsletterPrintoutStore().$reset();
next();
@ -23,8 +21,6 @@ export async function resetNewsletterStores(to: any, from: any, next: any) {
useNewsletterDatesStore().$reset();
useNewsletterRecipientsStore().$reset();
useNewsletterPrintoutStore().unsubscribePdfPrintingProgress();
useNewsletterPrintoutStore().unsubscribeMailSendingProgress();
useNewsletterPrintoutStore().$reset();
next();

View file

@ -62,7 +62,7 @@ http.interceptors.response.use(
.then(() => {
return http(originalRequest);
})
.catch();
.catch(() => {});
}
const notificationStore = useNotificationStore();
@ -108,4 +108,25 @@ function newEventSource(path: string) {
});
}
export { http, newEventSource, host };
async function* streamingFetch(path: string, abort?: AbortController) {
await refreshToken()
.then(() => {})
.catch(() => {});
const token = localStorage.getItem("accessToken");
const response = await fetch(url + "/api" + path, {
signal: abort?.signal,
headers: {
Authorization: `Bearer ${token}`,
},
});
const reader = response.body?.getReader();
while (true && reader) {
const { done, value } = await reader.read();
if (done) break;
yield new TextDecoder().decode(value);
}
}
export { http, newEventSource, streamingFetch, host };

View file

@ -1,8 +1,9 @@
import { defineStore } from "pinia";
import { http, newEventSource } from "@/serverCom";
import { http, newEventSource, streamingFetch } from "@/serverCom";
import { useNewsletterStore } from "./newsletter";
import type { AxiosResponse } from "axios";
import type { EventSourcePolyfill } from "event-source-polyfill";
import { useNotificationStore, type NotificationType } from "../../../notification";
export const useNewsletterPrintoutStore = defineStore("newsletterPrintout", {
state: () => {
@ -12,10 +13,10 @@ export const useNewsletterPrintoutStore = defineStore("newsletterPrintout", {
printing: undefined as undefined | "loading" | "success" | "failed",
sending: undefined as undefined | "loading" | "success" | "failed",
sendingPreview: undefined as undefined | "loading" | "success" | "failed",
pdfProgessSource: undefined as undefined | EventSourcePolyfill,
mailProgessSource: undefined as undefined | EventSourcePolyfill,
pdfSourceMessages: [] as Array<Object>,
mailSourceMessages: [] as Array<Object>,
pdfSourceMessages: [] as Array<{ kind: string; factor: string; [key: string]: string }>,
mailSourceMessages: [] as Array<{ kind: string; factor: string; [key: string]: string }>,
pdfPrintingAbort: undefined as undefined | AbortController,
mailSendingAbort: undefined as undefined | AbortController,
};
},
actions: {
@ -63,6 +64,7 @@ export const useNewsletterPrintoutStore = defineStore("newsletterPrintout", {
});
},
createNewsletterPrintout() {
this.subscribePdfPrintingProgress();
this.printing = "loading";
const newsletterId = useNewsletterStore().activeNewsletter;
if (newsletterId == null) return;
@ -78,10 +80,12 @@ export const useNewsletterPrintoutStore = defineStore("newsletterPrintout", {
.finally(() => {
setTimeout(() => {
this.printing = undefined;
this.pdfPrintingAbort?.abort();
}, 1500);
});
},
createNewsletterSend() {
this.subscribeMailSendingProgress();
this.sending = "loading";
const newsletterId = useNewsletterStore().activeNewsletter;
if (newsletterId == null) return;
@ -96,32 +100,55 @@ export const useNewsletterPrintoutStore = defineStore("newsletterPrintout", {
.finally(() => {
setTimeout(() => {
this.sending = undefined;
this.mailSendingAbort?.abort();
}, 1500);
});
},
subscribePdfPrintingProgress() {
// const newsletterId = useNewsletterStore().activeNewsletter;
// if (this.pdfProgessSource != undefined) return;
// this.pdfProgessSource = newEventSource(`/admin/newsletter/${newsletterId}/printoutprogress`);
// this.pdfProgessSource.onmessage = (event) => {
// console.log("pdf", event);
// };
async subscribePdfPrintingProgress() {
this.pdfSourceMessages = [];
const newsletterId = useNewsletterStore().activeNewsletter;
const notificationStore = useNotificationStore();
this.pdfPrintingAbort = new AbortController();
for await (let chunk of streamingFetch(
`/admin/newsletter/${newsletterId}/printoutprogress`,
this.pdfPrintingAbort
)) {
chunk.split("//").forEach((r) => {
if (r.trim() != "") {
let data = JSON.parse(r);
this.pdfSourceMessages.push(data);
let type: NotificationType = "info";
let timeout = undefined;
if (data.factor == "failed") {
type = "error";
timeout = 0;
}
notificationStore.push(`Druck: ${data.iteration}/${data.total}`, `${data.msg}`, type, timeout);
}
});
this.fetchNewsletterPrintout();
}
},
subscribeMailSendingProgress() {
// const newsletterId = useNewsletterStore().activeNewsletter;
// if (this.mailProgessSource != undefined) return;
// this.mailProgessSource = newEventSource(`/admin/newsletter/${newsletterId}/sendprogress`);
// this.mailProgessSource.onmessage = (event) => {
// console.log("mail", event);
// };
},
unsubscribePdfPrintingProgress() {
this.pdfProgessSource?.close();
this.pdfProgessSource = undefined;
},
unsubscribeMailSendingProgress() {
this.mailProgessSource?.close();
this.mailProgessSource = undefined;
async subscribeMailSendingProgress() {
this.mailSourceMessages = [];
const newsletterId = useNewsletterStore().activeNewsletter;
const notificationStore = useNotificationStore();
this.mailSendingAbort = new AbortController();
for await (let chunk of streamingFetch(`/admin/newsletter/${newsletterId}/sendprogress`, this.mailSendingAbort)) {
chunk.split("//").forEach((r) => {
if (r.trim() != "") {
let data = JSON.parse(r);
this.mailSourceMessages.push(data);
let type: NotificationType = "info";
let timeout = undefined;
if (data.factor == "failed") {
type = "error";
timeout = 0;
}
notificationStore.push(`Mailversand: ${data.iteration}/${data.total}`, `${data.msg}`, type, timeout);
}
});
}
},
},
});

View file

@ -1,7 +1,7 @@
import { defineStore } from "pinia";
export interface Notification {
id: number;
id: string;
title: string;
text: string;
type: NotificationType;
@ -19,7 +19,7 @@ export const useNotificationStore = defineStore("notification", {
},
actions: {
push(title: string, text: string, type: NotificationType, timeout: number = 5000) {
let id = Date.now();
let id = `${Date.now()}_${Math.random()}`;
this.notifications.push({
id,
title,
@ -27,14 +27,16 @@ export const useNotificationStore = defineStore("notification", {
type,
indicator: false,
});
setTimeout(() => {
this.notifications[this.notifications.findIndex((n) => n.id === id)].indicator = true;
}, 100);
this.timeouts[id] = setTimeout(() => {
this.revoke(id);
}, timeout);
if (timeout != 0) {
setTimeout(() => {
this.notifications[this.notifications.findIndex((n) => n.id === id)].indicator = true;
}, 100);
this.timeouts[id] = setTimeout(() => {
this.revoke(id);
}, timeout);
}
},
revoke(id: number) {
revoke(id: string) {
this.notifications.splice(
this.notifications.findIndex((n) => n.id === id),
1

View file

@ -61,6 +61,12 @@
<SuccessCheckmark v-else-if="sendingPreview == 'success'" />
<FailureXMark v-else-if="sendingPreview == 'failed'" />
</button>
<button v-if="pdfSourceMessages.length != 0" primary-outline class="!w-fit" @click="openPdfLogs">
Druck Logs
</button>
<button v-if="mailSourceMessages.length != 0" primary-outline class="!w-fit" @click="openMailLogs">
Versand Logs
</button>
</div>
</div>
</template>
@ -84,13 +90,19 @@ export default defineComponent({
newsletterId: String,
},
computed: {
...mapState(useNewsletterPrintoutStore, ["printout", "loading", "printing", "sending", "sendingPreview"]),
...mapState(useNewsletterPrintoutStore, [
"printout",
"loading",
"printing",
"sending",
"sendingPreview",
"mailSourceMessages",
"pdfSourceMessages",
]),
...mapState(useAbilityStore, ["can"]),
},
mounted() {
this.fetchNewsletterPrintout();
this.subscribeMailSendingProgress();
this.subscribePdfPrintingProgress();
},
methods: {
...mapActions(useModalStore, ["openModal"]),
@ -100,8 +112,6 @@ export default defineComponent({
"fetchNewsletterPrintoutById",
"createNewsletterMailPreview",
"createNewsletterSend",
"subscribeMailSendingProgress",
"subscribePdfPrintingProgress",
]),
openPdfShow(filename?: string) {
this.openModal(
@ -122,6 +132,20 @@ export default defineComponent({
})
.catch(() => {});
},
openPdfLogs() {
this.openModal(
markRaw(
defineAsyncComponent(() => import("@/components/admin/club/newsletter/NewsletterPrintingProgressModal.vue"))
)
);
},
openMailLogs() {
this.openModal(
markRaw(
defineAsyncComponent(() => import("@/components/admin/club/newsletter/NewsletterMailProgressModal.vue"))
)
);
},
},
});
</script>