Compare commits
2 commits
e91b64ca97
...
4152034e4a
Author | SHA1 | Date | |
---|---|---|---|
4152034e4a | |||
67375bd281 |
16 changed files with 500 additions and 50 deletions
|
@ -10,8 +10,5 @@ class ExtendedUserAdmin(UserAdmin):
|
||||||
ordering = ('username',)
|
ordering = ('username',)
|
||||||
filter_horizontal = ('groups', 'user_permissions', 'permissions')
|
filter_horizontal = ('groups', 'user_permissions', 'permissions')
|
||||||
|
|
||||||
def permissions(self, obj):
|
|
||||||
return ', '.join(obj.get_all_permissions())
|
|
||||||
|
|
||||||
|
|
||||||
admin.site.register(ExtendedUser, ExtendedUserAdmin)
|
admin.site.register(ExtendedUser, ExtendedUserAdmin)
|
||||||
|
|
|
@ -33,7 +33,7 @@ def media_urls(request, hash):
|
||||||
headers={
|
headers={
|
||||||
'X-Accel-Redirect': f'/redirect_media/{hash_path}',
|
'X-Accel-Redirect': f'/redirect_media/{hash_path}',
|
||||||
'Access-Control-Allow-Origin': '*',
|
'Access-Control-Allow-Origin': '*',
|
||||||
'Cache-Control': 'max-age=31536000, private',
|
'Cache-Control': 'max-age=31536000, private, immutable',
|
||||||
'Expires': datetime.utcnow() + timedelta(days=365),
|
'Expires': datetime.utcnow() + timedelta(days=365),
|
||||||
'Age': 0,
|
'Age': 0,
|
||||||
'ETag': file.hash,
|
'ETag': file.hash,
|
||||||
|
@ -74,7 +74,7 @@ def thumbnail_urls(request, size, hash):
|
||||||
headers={
|
headers={
|
||||||
'X-Accel-Redirect': f'/redirect_thumbnail/{size}/{hash_path}',
|
'X-Accel-Redirect': f'/redirect_thumbnail/{size}/{hash_path}',
|
||||||
'Access-Control-Allow-Origin': '*',
|
'Access-Control-Allow-Origin': '*',
|
||||||
'Cache-Control': 'max-age=31536000, private',
|
'Cache-Control': 'max-age=31536000, private, immutable',
|
||||||
'Expires': datetime.utcnow() + timedelta(days=365),
|
'Expires': datetime.utcnow() + timedelta(days=365),
|
||||||
'Age': 0,
|
'Age': 0,
|
||||||
'ETag': file.hash + "_" + str(size),
|
'ETag': file.hash + "_" + str(size),
|
||||||
|
|
|
@ -90,4 +90,6 @@ class AbstractFile(models.Model):
|
||||||
|
|
||||||
class File(AbstractFile):
|
class File(AbstractFile):
|
||||||
item = models.ForeignKey(Item, models.CASCADE, db_column='iid', null=True, blank=True, related_name='files')
|
item = models.ForeignKey(Item, models.CASCADE, db_column='iid', null=True, blank=True, related_name='files')
|
||||||
pass
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.hash
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
from datetime import datetime
|
from django.utils import timezone
|
||||||
|
|
||||||
from django.urls import re_path
|
from django.urls import re_path
|
||||||
from rest_framework import routers, viewsets, serializers
|
from rest_framework import routers, viewsets, serializers
|
||||||
from rest_framework.decorators import api_view, permission_classes, authentication_classes
|
from rest_framework.decorators import api_view, permission_classes, authentication_classes
|
||||||
|
@ -87,7 +86,7 @@ class ItemSerializer(serializers.ModelSerializer):
|
||||||
def update(self, instance, validated_data):
|
def update(self, instance, validated_data):
|
||||||
if 'returned' in validated_data:
|
if 'returned' in validated_data:
|
||||||
if validated_data['returned']:
|
if validated_data['returned']:
|
||||||
validated_data['returned_at'] = datetime.now()
|
validated_data['returned_at'] = timezone.now()
|
||||||
validated_data.pop('returned')
|
validated_data.pop('returned')
|
||||||
if 'dataImage' in validated_data:
|
if 'dataImage' in validated_data:
|
||||||
file = File.objects.create(data=validated_data['dataImage'])
|
file = File.objects.create(data=validated_data['dataImage'])
|
||||||
|
|
|
@ -35,6 +35,9 @@ class Item(SoftDeleteModel):
|
||||||
('match_item', 'Can match item')
|
('match_item', 'Can match item')
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return '[' + str(self.uid) + ']' + self.description
|
||||||
|
|
||||||
|
|
||||||
class Container(SoftDeleteModel):
|
class Container(SoftDeleteModel):
|
||||||
cid = models.AutoField(primary_key=True)
|
cid = models.AutoField(primary_key=True)
|
||||||
|
@ -42,6 +45,9 @@ class Container(SoftDeleteModel):
|
||||||
created_at = models.DateTimeField(blank=True, null=True)
|
created_at = models.DateTimeField(blank=True, null=True)
|
||||||
updated_at = models.DateTimeField(blank=True, null=True)
|
updated_at = models.DateTimeField(blank=True, null=True)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return '[' + str(self.cid) + ']' + self.name
|
||||||
|
|
||||||
|
|
||||||
class Event(models.Model):
|
class Event(models.Model):
|
||||||
eid = models.AutoField(primary_key=True)
|
eid = models.AutoField(primary_key=True)
|
||||||
|
@ -53,3 +59,6 @@ class Event(models.Model):
|
||||||
post_end = models.DateTimeField(blank=True, null=True)
|
post_end = models.DateTimeField(blank=True, null=True)
|
||||||
created_at = models.DateTimeField(null=True, auto_now_add=True)
|
created_at = models.DateTimeField(null=True, auto_now_add=True)
|
||||||
updated_at = models.DateTimeField(blank=True, null=True)
|
updated_at = models.DateTimeField(blank=True, null=True)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return '[' + str(self.slug) + ']' + self.name
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
from datetime import datetime
|
from django.utils import timezone
|
||||||
|
|
||||||
from django.test import TestCase, Client
|
from django.test import TestCase, Client
|
||||||
from django.contrib.auth.models import Permission
|
from django.contrib.auth.models import Permission
|
||||||
from knox.models import AuthToken
|
from knox.models import AuthToken
|
||||||
|
@ -164,7 +163,7 @@ class ItemTestCase(TestCase):
|
||||||
response = self.client.get(f'/api/2/{self.event.slug}/item/')
|
response = self.client.get(f'/api/2/{self.event.slug}/item/')
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertEqual(len(response.json()), 2)
|
self.assertEqual(len(response.json()), 2)
|
||||||
item2.returned_at = datetime.now()
|
item2.returned_at = timezone.now()
|
||||||
item2.save()
|
item2.save()
|
||||||
response = self.client.get(f'/api/2/{self.event.slug}/item/')
|
response = self.client.get(f'/api/2/{self.event.slug}/item/')
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
import aiosmtplib
|
import aiosmtplib
|
||||||
from asgiref.sync import sync_to_async
|
|
||||||
from channels.layers import get_channel_layer
|
from channels.layers import get_channel_layer
|
||||||
|
from channels.db import database_sync_to_async
|
||||||
from django.core.files.base import ContentFile
|
from django.core.files.base import ContentFile
|
||||||
|
|
||||||
from mail.models import Email, EventAddress, EmailAttachment
|
from mail.models import Email, EventAddress, EmailAttachment
|
||||||
|
@ -82,8 +82,7 @@ def make_reply(reply_email, references=None, event=None):
|
||||||
return reply
|
return reply
|
||||||
|
|
||||||
|
|
||||||
async def send_smtp(message, log):
|
async def send_smtp(message):
|
||||||
log.info('Sending message to %s' % message['To'])
|
|
||||||
await aiosmtplib.send(message, hostname="127.0.0.1", port=25, use_tls=False, start_tls=False)
|
await aiosmtplib.send(message, hostname="127.0.0.1", port=25, use_tls=False, start_tls=False)
|
||||||
|
|
||||||
|
|
||||||
|
@ -148,9 +147,9 @@ def parse_email_body(raw, log=None):
|
||||||
attachments.append(attachment)
|
attachments.append(attachment)
|
||||||
if 'inline' in cdispo:
|
if 'inline' in cdispo:
|
||||||
body = body + f'<img src="cid:{attachment.id}">'
|
body = body + f'<img src="cid:{attachment.id}">'
|
||||||
log.info("Image", ctype, attachment.id)
|
log.info("Image %s %s", ctype, attachment.id)
|
||||||
else:
|
else:
|
||||||
log.info("Attachment", ctype, cdispo)
|
log.info("Attachment %s %s", ctype, cdispo)
|
||||||
else:
|
else:
|
||||||
if parsed.get_content_type() == 'text/plain':
|
if parsed.get_content_type() == 'text/plain':
|
||||||
body = parsed.get_payload()
|
body = parsed.get_payload()
|
||||||
|
@ -161,7 +160,7 @@ def parse_email_body(raw, log=None):
|
||||||
soup = BeautifulSoup(body, 'html.parser')
|
soup = BeautifulSoup(body, 'html.parser')
|
||||||
body = re.sub(r'([\r\n]+.?)*[\r\n]', r'\n', soup.get_text()).strip('\n')
|
body = re.sub(r'([\r\n]+.?)*[\r\n]', r'\n', soup.get_text()).strip('\n')
|
||||||
else:
|
else:
|
||||||
log.warning("Unknown content type", parsed.get_content_type())
|
log.warning("Unknown content type %s", parsed.get_content_type())
|
||||||
body = "Unknown content type"
|
body = "Unknown content type"
|
||||||
body = unescape_and_decode_quoted_printable(body)
|
body = unescape_and_decode_quoted_printable(body)
|
||||||
body = unescape_and_decode_base64(body)
|
body = unescape_and_decode_base64(body)
|
||||||
|
@ -172,6 +171,7 @@ def parse_email_body(raw, log=None):
|
||||||
return parsed, body, attachments
|
return parsed, body, attachments
|
||||||
|
|
||||||
|
|
||||||
|
@database_sync_to_async
|
||||||
def receive_email(envelope, log=None):
|
def receive_email(envelope, log=None):
|
||||||
parsed, body, attachments = parse_email_body(envelope.content, log)
|
parsed, body, attachments = parse_email_body(envelope.content, log)
|
||||||
|
|
||||||
|
@ -255,9 +255,10 @@ class LMTPHandler:
|
||||||
content = None
|
content = None
|
||||||
try:
|
try:
|
||||||
content = envelope.content
|
content = envelope.content
|
||||||
email, new, reply = await sync_to_async(receive_email)(envelope, log)
|
email, new, reply = await receive_email(envelope, log)
|
||||||
log.info(f"Created email {email.id}")
|
log.info(f"Created email {email.id}")
|
||||||
systemevent = await sync_to_async(SystemEvent.objects.create)(type='email received', reference=email.id)
|
systemevent = await database_sync_to_async(SystemEvent.objects.create)(type='email received',
|
||||||
|
reference=email.id)
|
||||||
log.info(f"Created system event {systemevent.id}")
|
log.info(f"Created system event {systemevent.id}")
|
||||||
channel_layer = get_channel_layer()
|
channel_layer = get_channel_layer()
|
||||||
await channel_layer.group_send(
|
await channel_layer.group_send(
|
||||||
|
@ -266,14 +267,15 @@ class LMTPHandler:
|
||||||
)
|
)
|
||||||
log.info(f"Sent message to frontend")
|
log.info(f"Sent message to frontend")
|
||||||
if new and reply:
|
if new and reply:
|
||||||
await send_smtp(reply, log)
|
log.info('Sending message to %s' % reply['To'])
|
||||||
|
await send_smtp(reply)
|
||||||
log.info("Sent auto reply")
|
log.info("Sent auto reply")
|
||||||
|
|
||||||
return '250 Message accepted for delivery'
|
return '250 Message accepted for delivery'
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
import uuid
|
from hashlib import sha256
|
||||||
random_filename = 'mail-' + str(uuid.uuid4())
|
random_filename = 'mail-' + sha256(content).hexdigest()
|
||||||
with open(random_filename, 'wb') as f:
|
with open(random_filename, 'wb') as f:
|
||||||
f.write(content)
|
f.write(content)
|
||||||
log.error(type(e), e, f"Saved email to {random_filename}")
|
log.error(f"Saved email to {random_filename} because of error %s (%s)", e, type(e))
|
||||||
return '451 Internal server error'
|
return '451 Internal server error'
|
||||||
|
|
|
@ -47,8 +47,7 @@ def reply(request, pk):
|
||||||
body=request.data['message'],
|
body=request.data['message'],
|
||||||
in_reply_to=first_mail.reference,
|
in_reply_to=first_mail.reference,
|
||||||
)
|
)
|
||||||
log = logging.getLogger('mail.log')
|
async_to_sync(send_smtp)(make_reply(mail, references))
|
||||||
async_to_sync(send_smtp)(make_reply(mail, references), log)
|
|
||||||
|
|
||||||
return Response({'status': 'ok'}, status=status.HTTP_201_CREATED)
|
return Response({'status': 'ok'}, status=status.HTTP_201_CREATED)
|
||||||
|
|
||||||
|
|
|
@ -64,6 +64,9 @@ class IssueThread(SoftDeleteModel):
|
||||||
return
|
return
|
||||||
self.assignments.create(assigned_to=value)
|
self.assignments.create(assigned_to=value)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return '[' + str(self.id) + '][' + self.short_uuid() + '] ' + self.name
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
permissions = [
|
permissions = [
|
||||||
('send_mail', 'Can send mail'),
|
('send_mail', 'Can send mail'),
|
||||||
|
@ -91,6 +94,9 @@ class Comment(models.Model):
|
||||||
comment = models.TextField()
|
comment = models.TextField()
|
||||||
timestamp = models.DateTimeField(auto_now_add=True)
|
timestamp = models.DateTimeField(auto_now_add=True)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return str(self.issue_thread) + ' comment #' + str(self.id)
|
||||||
|
|
||||||
|
|
||||||
class StateChange(models.Model):
|
class StateChange(models.Model):
|
||||||
id = models.AutoField(primary_key=True)
|
id = models.AutoField(primary_key=True)
|
||||||
|
@ -98,9 +104,15 @@ class StateChange(models.Model):
|
||||||
state = models.CharField(max_length=255, choices=STATE_CHOICES, default='pending_new')
|
state = models.CharField(max_length=255, choices=STATE_CHOICES, default='pending_new')
|
||||||
timestamp = models.DateTimeField(auto_now_add=True)
|
timestamp = models.DateTimeField(auto_now_add=True)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return str(self.issue_thread) + ' state change to ' + self.state
|
||||||
|
|
||||||
|
|
||||||
class Assignment(models.Model):
|
class Assignment(models.Model):
|
||||||
id = models.AutoField(primary_key=True)
|
id = models.AutoField(primary_key=True)
|
||||||
issue_thread = models.ForeignKey(IssueThread, on_delete=models.CASCADE, related_name='assignments')
|
issue_thread = models.ForeignKey(IssueThread, on_delete=models.CASCADE, related_name='assignments')
|
||||||
assigned_to = models.ForeignKey(ExtendedUser, on_delete=models.CASCADE, related_name='assigned_tickets')
|
assigned_to = models.ForeignKey(ExtendedUser, on_delete=models.CASCADE, related_name='assigned_tickets')
|
||||||
timestamp = models.DateTimeField(auto_now_add=True)
|
timestamp = models.DateTimeField(auto_now_add=True)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return str(self.issue_thread) + ' assigned to ' + self.assigned_to.username
|
||||||
|
|
|
@ -28,7 +28,7 @@ export default {
|
||||||
}),
|
}),
|
||||||
methods: {
|
methods: {
|
||||||
...mapMutations(['removeToast', 'createToast', 'closeAddBoxModal', 'openAddBoxModal']),
|
...mapMutations(['removeToast', 'createToast', 'closeAddBoxModal', 'openAddBoxModal']),
|
||||||
...mapActions(['loadEvents']),
|
...mapActions(['loadEvents', 'scheduleAfterInit']),
|
||||||
openAddItemModal() {
|
openAddItemModal() {
|
||||||
this.addItemModalOpen = true;
|
this.addItemModalOpen = true;
|
||||||
},
|
},
|
||||||
|
@ -44,6 +44,7 @@ export default {
|
||||||
},
|
},
|
||||||
created: function () {
|
created: function () {
|
||||||
document.title = document.location.hostname;
|
document.title = document.location.hostname;
|
||||||
|
this.scheduleAfterInit(() => [this.loadEvents()]);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -27,16 +27,19 @@ export default {
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(['lastUsed'])
|
...mapState(['lastUsed'])
|
||||||
},
|
},
|
||||||
created() {
|
|
||||||
this.item = {box: this.lastUsed.box || '', cid: this.lastUsed.cid || ''};
|
|
||||||
},
|
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions(['postItem']),
|
...mapActions(['postItem', 'loadBoxes', 'scheduleAfterInit']),
|
||||||
saveNewItem() {
|
saveNewItem() {
|
||||||
this.postItem(this.item).then(() => {
|
this.postItem(this.item).then(() => {
|
||||||
this.$emit('close');
|
this.$emit('close');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.item = {box: this.lastUsed.box || '', cid: this.lastUsed.cid || ''};
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.scheduleAfterInit(() => [this.loadBoxes()]);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
345
web/src/shared-state-plugin/index.js
Normal file
345
web/src/shared-state-plugin/index.js
Normal file
|
@ -0,0 +1,345 @@
|
||||||
|
import {isProxy, toRaw} from 'vue';
|
||||||
|
|
||||||
|
export default (config) => {
|
||||||
|
if (!('isLoadedKey' in config)) {
|
||||||
|
throw new Error("isLoadedKey not defined in config");
|
||||||
|
}
|
||||||
|
if (('asyncFetch' in config) && !('lastfetched' in config)) {
|
||||||
|
throw new Error("asyncFetch defined but lastfetched not defined in config");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.debug) console.log('plugin created');
|
||||||
|
|
||||||
|
const clone = (obj) => {
|
||||||
|
if (isProxy(obj)) {
|
||||||
|
obj = toRaw(obj);
|
||||||
|
}
|
||||||
|
if (obj === null || typeof obj !== 'object') {
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
if (obj.__proto__ === ({}).__proto__) {
|
||||||
|
return Object.assign({}, obj);
|
||||||
|
}
|
||||||
|
if (obj.__proto__ === [].__proto__) {
|
||||||
|
return obj.slice();
|
||||||
|
}
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
const deepEqual = (a, b) => {
|
||||||
|
if (a === b) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (a === null || b === null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (a.__proto__ === ({}).__proto__ && b.__proto__ === ({}).__proto__) {
|
||||||
|
|
||||||
|
if (Object.keys(a).length !== Object.keys(b).length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (let key in b) {
|
||||||
|
if (!(key in a)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (let key in a) {
|
||||||
|
if (!(key in b)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!deepEqual(a[key], b[key])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (a.__proto__ === [].__proto__ && b.__proto__ === [].__proto__) {
|
||||||
|
if (a.length !== b.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (let i = 0; i < a.length; i++) {
|
||||||
|
if (!deepEqual(a[i], b[i])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const toRawRecursive = (obj) => {
|
||||||
|
if (isProxy(obj)) {
|
||||||
|
obj = toRaw(obj);
|
||||||
|
}
|
||||||
|
if (obj === null || typeof obj !== 'object') {
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
if (obj.__proto__ === ({}).__proto__) {
|
||||||
|
const new_obj = {};
|
||||||
|
for (let key in obj) {
|
||||||
|
new_obj[key] = toRawRecursive(obj[key]);
|
||||||
|
}
|
||||||
|
return new_obj;
|
||||||
|
}
|
||||||
|
if (obj.__proto__ === [].__proto__) {
|
||||||
|
return obj.map((item) => toRawRecursive(item));
|
||||||
|
}
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** may only be called from worker */
|
||||||
|
const worker_fun = function (self, ctx) {
|
||||||
|
/* globals WebSocket, SharedWorker, onconnect, onmessage, postMessage, close, location */
|
||||||
|
|
||||||
|
let intialized = false;
|
||||||
|
let state = {};
|
||||||
|
let ports = [];
|
||||||
|
let notify_socket;
|
||||||
|
|
||||||
|
const tryConnect = () => {
|
||||||
|
if (self.WebSocket === undefined) {
|
||||||
|
if (ctx.debug) console.log("no websocket support");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!notify_socket || notify_socket.readyState !== WebSocket.OPEN) {
|
||||||
|
// global location is not useful in worker loaded from data url
|
||||||
|
const scheme = ctx.location.protocol === "https:" ? "wss" : "ws";
|
||||||
|
if (ctx.debug) console.log("connecting to", scheme + '://' + ctx.location.host + '/ws/2/notify/');
|
||||||
|
notify_socket = new WebSocket(scheme + '://' + ctx.location.host + '/ws/2/notify/');
|
||||||
|
notify_socket.onopen = (e) => {
|
||||||
|
if (ctx.debug) console.log("open", JSON.stringify(e));
|
||||||
|
};
|
||||||
|
notify_socket.onclose = (e) => {
|
||||||
|
if (ctx.debug) console.log("close", JSON.stringify(e));
|
||||||
|
setTimeout(() => {
|
||||||
|
tryConnect();
|
||||||
|
}, 1000);
|
||||||
|
};
|
||||||
|
notify_socket.onerror = (e) => {
|
||||||
|
if (ctx.debug) console.log("error", JSON.stringify(e));
|
||||||
|
setTimeout(() => {
|
||||||
|
tryConnect();
|
||||||
|
}, 1000);
|
||||||
|
};
|
||||||
|
notify_socket.onmessage = (e) => {
|
||||||
|
let data = JSON.parse(e.data);
|
||||||
|
if (ctx.debug) console.log("message", data);
|
||||||
|
//this.loadEventItems()
|
||||||
|
//this.loadTickets()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const deepEqual = (a, b) => {
|
||||||
|
if (a === b) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (a === null || b === null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (a.__proto__ === ({}).__proto__ && b.__proto__ === ({}).__proto__) {
|
||||||
|
|
||||||
|
if (Object.keys(a).length !== Object.keys(b).length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (let key in b) {
|
||||||
|
if (!(key in a)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (let key in a) {
|
||||||
|
if (!(key in b)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!deepEqual(a[key], b[key])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (a.__proto__ === [].__proto__ && b.__proto__ === [].__proto__) {
|
||||||
|
if (a.length !== b.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (let i = 0; i < a.length; i++) {
|
||||||
|
if (!deepEqual(a[i], b[i])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const handle_message = (message_data, reply, others, all) => {
|
||||||
|
switch (message_data.type) {
|
||||||
|
case 'state_init':
|
||||||
|
if (!intialized) {
|
||||||
|
intialized = true;
|
||||||
|
state = message_data.state;
|
||||||
|
reply({type: 'state_init', first: true});
|
||||||
|
} else {
|
||||||
|
reply({type: 'state_init', first: false, state: state});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'state_diff':
|
||||||
|
if (message_data.key in state) {
|
||||||
|
if (!deepEqual(state[message_data.key], message_data.old_value)) {
|
||||||
|
if (ctx.debug) console.log("state diff old value mismatch | state:", state[message_data.key], " old:", message_data.old_value);
|
||||||
|
}
|
||||||
|
if (!deepEqual(state[message_data.key], message_data.new_value)) {
|
||||||
|
if (ctx.debug) console.log("state diff changed | state:", state[message_data.key], " new:", message_data.new_value);
|
||||||
|
state[message_data.key] = message_data.new_value;
|
||||||
|
others(message_data);
|
||||||
|
} else {
|
||||||
|
if (ctx.debug) console.log("state diff no change | state:", state[message_data.key], " new:", message_data.new_value);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (ctx.debug) console.log("state diff key not found", message_data.key);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (ctx.debug) console.log("unknown message", message_data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onconnect = (connect_event) => {
|
||||||
|
const port = connect_event.ports[0];
|
||||||
|
ports.push(port);
|
||||||
|
port.onmessage = (message_event) => {
|
||||||
|
const reply = (message_data) => {
|
||||||
|
port.postMessage(message_data);
|
||||||
|
}
|
||||||
|
const others = (message_data) => {
|
||||||
|
for (let i = 0; i < ports.length; i++) {
|
||||||
|
if (ports[i] !== port) {
|
||||||
|
ports[i].postMessage(message_data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const all = (message_data) => {
|
||||||
|
for (let i = 0; i < ports.length; i++) {
|
||||||
|
ports[i].postMessage(message_data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
handle_message(message_event.data, reply, others, all);
|
||||||
|
}
|
||||||
|
port.start();
|
||||||
|
if (ctx.debug) console.log("worker connected", JSON.stringify(connect_event));
|
||||||
|
tryConnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctx.debug) console.log("worker loaded");
|
||||||
|
}
|
||||||
|
|
||||||
|
const worker_context = {
|
||||||
|
location: {
|
||||||
|
protocol: location.protocol, host: location.host
|
||||||
|
}, bug: config.debug
|
||||||
|
}
|
||||||
|
const worker_code = '(' + worker_fun.toString() + ')(self,' + JSON.stringify(worker_context) + ')';
|
||||||
|
const worker_url = 'data:application/javascript;base64,' + btoa(worker_code);
|
||||||
|
|
||||||
|
const worker = new SharedWorker(worker_url, 'vuex-shared-state-plugin');
|
||||||
|
worker.port.start();
|
||||||
|
if (config.debug) console.log('worker started');
|
||||||
|
|
||||||
|
const updateWorkerState = (key, new_value, old_value = null) => {
|
||||||
|
if (new_value === old_value) {
|
||||||
|
if (config.debug) console.log('updateWorkerState: no change', key, new_value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (new_value === undefined) {
|
||||||
|
if (config.debug) console.log('updateWorkerState: undefined', key, new_value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
worker.port.postMessage({
|
||||||
|
type: 'state_diff',
|
||||||
|
key: key,
|
||||||
|
new_value: isProxy(new_value) ? toRawRecursive(new_value) : new_value,
|
||||||
|
old_value: isProxy(old_value) ? toRawRecursive(old_value) : old_value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const registerInitialState = (keys, local_state) => {
|
||||||
|
const value = keys.reduce((obj, key) => {
|
||||||
|
obj[key] = isProxy(local_state[key]) ? toRawRecursive(local_state[key]) : local_state[key];
|
||||||
|
return obj;
|
||||||
|
}, {});
|
||||||
|
if (config.debug) console.log('registerInitilState', value);
|
||||||
|
worker.port.postMessage({
|
||||||
|
type: 'state_init', state: value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return (store) => {
|
||||||
|
|
||||||
|
worker.port.onmessage = function (e) {
|
||||||
|
switch (e.data.type) {
|
||||||
|
case 'state_init':
|
||||||
|
if (config.debug) console.log('state_init', e.data);
|
||||||
|
if (e.data.first) {
|
||||||
|
if (config.debug) console.log('worker state initialized');
|
||||||
|
} else {
|
||||||
|
for (let key in e.data.state) {
|
||||||
|
if (key in store.state) {
|
||||||
|
if (config.debug) console.log('worker state init received', key, clone(e.data.state[key]));
|
||||||
|
if (!deepEqual(store.state[key], e.data.state[key])) {
|
||||||
|
store.state[key] = e.data.state[key];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (config.debug) console.log("state init key not found", key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
store.state[config.isLoadedKey] = true;
|
||||||
|
if ('afterInit' in config) {
|
||||||
|
setTimeout(() => {
|
||||||
|
store.dispatch(config.afterInit);
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'state_diff':
|
||||||
|
if (config.debug) console.log('state_diff', e.data);
|
||||||
|
if (e.data.key in store.state) {
|
||||||
|
if (config.debug) console.log('worker state update', e.data.key, clone(e.data.new_value));
|
||||||
|
//TODO this triggers the watcher again, but we don't want that
|
||||||
|
store.state[e.data.key] = e.data.new_value;
|
||||||
|
} else {
|
||||||
|
if (config.debug) console.log("state diff key not found", e.data.key);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (config.debug) console.log("unknown message", e.data);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
registerInitialState(config.state, store.state);
|
||||||
|
|
||||||
|
if ('mutations' in config) {
|
||||||
|
store.subscribe((mutation, state) => {
|
||||||
|
if (mutation.type in config.mutations) {
|
||||||
|
console.log(mutation.type, mutation.payload);
|
||||||
|
console.log(state);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/*if ('actions' in config) {
|
||||||
|
store.subscribeAction((action, state) => {
|
||||||
|
if (action.type in config.actions) {
|
||||||
|
console.log(action.type, action.payload);
|
||||||
|
console.log(state);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}*/
|
||||||
|
if ('state' in config) {
|
||||||
|
config.watch.forEach((member) => {
|
||||||
|
store.watch((state, getters) => state[member], (newValue, oldValue) => {
|
||||||
|
if (config.debug) console.log('watch', member, clone(newValue), clone(oldValue));
|
||||||
|
updateWorkerState(member, newValue, oldValue);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
|
@ -4,8 +4,9 @@ import router from './router';
|
||||||
import * as base64 from 'base-64';
|
import * as base64 from 'base-64';
|
||||||
import * as utf8 from 'utf8';
|
import * as utf8 from 'utf8';
|
||||||
import {ticketStateColorLookup, ticketStateIconLookup, http} from "@/utils";
|
import {ticketStateColorLookup, ticketStateIconLookup, http} from "@/utils";
|
||||||
|
import sharedStatePlugin from "@/shared-state-plugin";
|
||||||
import persistentStatePlugin from "@/persistent-state-plugin";
|
import persistentStatePlugin from "@/persistent-state-plugin";
|
||||||
|
import {triggerRef} from "vue";
|
||||||
|
|
||||||
const store = createStore({
|
const store = createStore({
|
||||||
state: {
|
state: {
|
||||||
|
@ -20,6 +21,7 @@ const store = createStore({
|
||||||
groups: [],
|
groups: [],
|
||||||
state_options: [],
|
state_options: [],
|
||||||
lastEvent: '37C3',
|
lastEvent: '37C3',
|
||||||
|
lastUsed: {},
|
||||||
remember: false,
|
remember: false,
|
||||||
user: {
|
user: {
|
||||||
username: null,
|
username: null,
|
||||||
|
@ -30,7 +32,19 @@ const store = createStore({
|
||||||
},
|
},
|
||||||
|
|
||||||
thumbnailCache: {},
|
thumbnailCache: {},
|
||||||
|
fetchedData: {
|
||||||
|
events: 0,
|
||||||
|
items: 0,
|
||||||
|
boxes: 0,
|
||||||
|
tickets: 0,
|
||||||
|
users: 0,
|
||||||
|
groups: 0,
|
||||||
|
states: 0,
|
||||||
|
},
|
||||||
persistent_loaded: false,
|
persistent_loaded: false,
|
||||||
|
shared_loaded: false,
|
||||||
|
afterInitHandlers: [],
|
||||||
|
|
||||||
showAddBoxModal: false,
|
showAddBoxModal: false,
|
||||||
},
|
},
|
||||||
getters: {
|
getters: {
|
||||||
|
@ -80,26 +94,33 @@ const store = createStore({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
mutations: {
|
mutations: {
|
||||||
|
updateLastUsed(state, diff) {
|
||||||
|
state.lastUsed = {...state.lastUsed, ...diff};
|
||||||
|
},
|
||||||
updateLastEvent(state, slug) {
|
updateLastEvent(state, slug) {
|
||||||
state.lastEvent = slug;
|
state.lastEvent = slug;
|
||||||
},
|
},
|
||||||
replaceEvents(state, events) {
|
replaceEvents(state, events) {
|
||||||
state.events = events;
|
state.events = events;
|
||||||
|
state.fetchedData = {...state.fetchedData, events: Date.now()};
|
||||||
},
|
},
|
||||||
replaceTicketStates(state, states) {
|
replaceTicketStates(state, states) {
|
||||||
state.state_options = states;
|
state.state_options = states;
|
||||||
|
state.fetchedData = {...state.fetchedData, states: Date.now()};
|
||||||
},
|
},
|
||||||
changeView(state, {view, slug}) {
|
changeView(state, {view, slug}) {
|
||||||
router.push({path: `/${slug}/${view}`});
|
router.push({path: `/${slug}/${view}`});
|
||||||
},
|
},
|
||||||
replaceLoadedItems(state, newItems) {
|
replaceLoadedItems(state, newItems) {
|
||||||
state.loadedItems = newItems;
|
state.loadedItems = newItems;
|
||||||
|
state.fetchedData = {...state.fetchedData, items: Date.now()}; // TODO: manage caching items for different events and search results correctly
|
||||||
},
|
},
|
||||||
setItemCache(state, {slug, items}) {
|
setItemCache(state, {slug, items}) {
|
||||||
state.itemCache[slug] = items;
|
state.itemCache[slug] = items;
|
||||||
},
|
},
|
||||||
replaceBoxes(state, loadedBoxes) {
|
replaceBoxes(state, loadedBoxes) {
|
||||||
state.loadedBoxes = loadedBoxes;
|
state.loadedBoxes = loadedBoxes;
|
||||||
|
state.fetchedData = {...state.fetchedData, boxes: Date.now()};
|
||||||
},
|
},
|
||||||
updateItem(state, updatedItem) {
|
updateItem(state, updatedItem) {
|
||||||
const item = state.loadedItems.filter(({uid}) => uid === updatedItem.uid)[0];
|
const item = state.loadedItems.filter(({uid}) => uid === updatedItem.uid)[0];
|
||||||
|
@ -113,16 +134,21 @@ const store = createStore({
|
||||||
},
|
},
|
||||||
replaceTickets(state, tickets) {
|
replaceTickets(state, tickets) {
|
||||||
state.tickets = tickets;
|
state.tickets = tickets;
|
||||||
},
|
state.fetchedData = {...state.fetchedData, tickets: Date.now()};
|
||||||
replaceUsers(state, users) {
|
|
||||||
state.users = users;
|
|
||||||
},
|
|
||||||
replaceGroups(state, groups) {
|
|
||||||
state.groups = groups;
|
|
||||||
},
|
},
|
||||||
updateTicket(state, updatedTicket) {
|
updateTicket(state, updatedTicket) {
|
||||||
const ticket = state.tickets.filter(({id}) => id === updatedTicket.id)[0];
|
const ticket = state.tickets.filter(({id}) => id === updatedTicket.id)[0];
|
||||||
Object.assign(ticket, updatedTicket);
|
Object.assign(ticket, updatedTicket);
|
||||||
|
//triggerRef(state.tickets);
|
||||||
|
state.tickets = [...state.tickets];
|
||||||
|
},
|
||||||
|
replaceUsers(state, users) {
|
||||||
|
state.users = users;
|
||||||
|
state.fetchedData = {...state.fetchedData, users: Date.now()};
|
||||||
|
},
|
||||||
|
replaceGroups(state, groups) {
|
||||||
|
state.groups = groups;
|
||||||
|
state.fetchedData = {...state.fetchedData, groups: Date.now()};
|
||||||
},
|
},
|
||||||
openAddBoxModal(state) {
|
openAddBoxModal(state) {
|
||||||
state.showAddBoxModal = true;
|
state.showAddBoxModal = true;
|
||||||
|
@ -227,6 +253,19 @@ const store = createStore({
|
||||||
}
|
}
|
||||||
await Promise.all(promises);
|
await Promise.all(promises);
|
||||||
},
|
},
|
||||||
|
async afterSharedInit({dispatch, state}) {
|
||||||
|
const handlers = state.afterInitHandlers;
|
||||||
|
state.afterInitHandlers = [];
|
||||||
|
await Promise.all(handlers.map(h => h()).flat());
|
||||||
|
},
|
||||||
|
scheduleAfterInit({dispatch, state}, handler) {
|
||||||
|
if (state.shared_loaded) {
|
||||||
|
Promise.all(handler()).then(() => {
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
state.afterInitHandlers.push(handler);
|
||||||
|
}
|
||||||
|
},
|
||||||
async fetchImage({state}, url) {
|
async fetchImage({state}, url) {
|
||||||
return await fetch(url, {headers: {'Authorization': `Token ${state.user.token}`}});
|
return await fetch(url, {headers: {'Authorization': `Token ${state.user.token}`}});
|
||||||
},
|
},
|
||||||
|
@ -235,11 +274,15 @@ const store = createStore({
|
||||||
commit('setPermissions', data.permissions);
|
commit('setPermissions', data.permissions);
|
||||||
},
|
},
|
||||||
async loadEvents({commit, state}) {
|
async loadEvents({commit, state}) {
|
||||||
|
if (!state.user.token) return;
|
||||||
|
if (state.fetchedData.events > Date.now() - 1000 * 60 * 60 * 24) return;
|
||||||
const {data, success} = await http.get('/2/events/', state.user.token);
|
const {data, success} = await http.get('/2/events/', state.user.token);
|
||||||
if (data && success)
|
if (data && success)
|
||||||
commit('replaceEvents', data);
|
commit('replaceEvents', data);
|
||||||
},
|
},
|
||||||
async fetchTicketStates({commit, state}) {
|
async fetchTicketStates({commit, state}) {
|
||||||
|
if (!state.user.token) return;
|
||||||
|
if (state.fetchedData.states > Date.now() - 1000 * 60 * 60 * 24) return;
|
||||||
const {data, success} = await http.get('/2/tickets/states/', state.user.token);
|
const {data, success} = await http.get('/2/tickets/states/', state.user.token);
|
||||||
if (data && success)
|
if (data && success)
|
||||||
commit('replaceTicketStates', data);
|
commit('replaceTicketStates', data);
|
||||||
|
@ -255,6 +298,8 @@ const store = createStore({
|
||||||
router.push({path: `/${getters.getEventSlug}/items/`, query: {box}});
|
router.push({path: `/${getters.getEventSlug}/items/`, query: {box}});
|
||||||
},
|
},
|
||||||
async loadEventItems({commit, getters, state}) {
|
async loadEventItems({commit, getters, state}) {
|
||||||
|
if (!state.user.token) return;
|
||||||
|
if (state.fetchedData.items > Date.now() - 1000 * 60 * 60 * 24) return;
|
||||||
try {
|
try {
|
||||||
commit('replaceLoadedItems', []);
|
commit('replaceLoadedItems', []);
|
||||||
const slug = getters.getEventSlug;
|
const slug = getters.getEventSlug;
|
||||||
|
@ -279,6 +324,8 @@ const store = createStore({
|
||||||
commit('replaceLoadedItems', data);
|
commit('replaceLoadedItems', data);
|
||||||
},
|
},
|
||||||
async loadBoxes({commit, state}) {
|
async loadBoxes({commit, state}) {
|
||||||
|
if (!state.user.token) return;
|
||||||
|
if (state.fetchedData.boxes > Date.now() - 1000 * 60 * 60 * 24) return;
|
||||||
const {data, success} = await http.get('/2/boxes/', state.user.token);
|
const {data, success} = await http.get('/2/boxes/', state.user.token);
|
||||||
if (data && success)
|
if (data && success)
|
||||||
commit('replaceBoxes', data);
|
commit('replaceBoxes', data);
|
||||||
|
@ -315,6 +362,8 @@ const store = createStore({
|
||||||
commit('appendItem', data);
|
commit('appendItem', data);
|
||||||
},
|
},
|
||||||
async loadTickets({commit, state}) {
|
async loadTickets({commit, state}) {
|
||||||
|
if (!state.user.token) return;
|
||||||
|
if (state.fetchedData.tickets > Date.now() - 1000 * 60 * 60 * 24) return;
|
||||||
const {data, success} = await http.get('/2/tickets/', state.user.token);
|
const {data, success} = await http.get('/2/tickets/', state.user.token);
|
||||||
if (data && success)
|
if (data && success)
|
||||||
commit('replaceTickets', data);
|
commit('replaceTickets', data);
|
||||||
|
@ -322,6 +371,7 @@ const store = createStore({
|
||||||
async sendMail({commit, dispatch, state}, {id, message}) {
|
async sendMail({commit, dispatch, state}, {id, message}) {
|
||||||
const {data, success} = await http.post(`/2/tickets/${id}/reply/`, {message}, state.user.token);
|
const {data, success} = await http.post(`/2/tickets/${id}/reply/`, {message}, state.user.token);
|
||||||
if (data && success) {
|
if (data && success) {
|
||||||
|
state.fetchedData.tickets = 0;
|
||||||
await dispatch('loadTickets');
|
await dispatch('loadTickets');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -337,15 +387,20 @@ const store = createStore({
|
||||||
async postComment({commit, dispatch, state}, {id, message}) {
|
async postComment({commit, dispatch, state}, {id, message}) {
|
||||||
const {data, success} = await http.post(`/2/tickets/${id}/comment/`, {comment: message}, state.user.token);
|
const {data, success} = await http.post(`/2/tickets/${id}/comment/`, {comment: message}, state.user.token);
|
||||||
if (data && success) {
|
if (data && success) {
|
||||||
|
state.fetchedData.tickets = 0;
|
||||||
await dispatch('loadTickets');
|
await dispatch('loadTickets');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async loadUsers({commit, state}) {
|
async loadUsers({commit, state}) {
|
||||||
|
if (!state.user.token) return;
|
||||||
|
if (state.fetchedData.users > Date.now() - 1000 * 60 * 60 * 24) return;
|
||||||
const {data, success} = await http.get('/2/users/', state.user.token);
|
const {data, success} = await http.get('/2/users/', state.user.token);
|
||||||
if (data && success)
|
if (data && success)
|
||||||
commit('replaceUsers', data);
|
commit('replaceUsers', data);
|
||||||
},
|
},
|
||||||
async loadGroups({commit, state}) {
|
async loadGroups({commit, state}) {
|
||||||
|
if (!state.user.token) return;
|
||||||
|
if (state.fetchedData.groups > Date.now() - 1000 * 60 * 60 * 24) return;
|
||||||
const {data, success} = await http.get('/2/groups/', state.user.token);
|
const {data, success} = await http.get('/2/groups/', state.user.token);
|
||||||
if (data && success)
|
if (data && success)
|
||||||
commit('replaceGroups', data);
|
commit('replaceGroups', data);
|
||||||
|
@ -368,8 +423,38 @@ const store = createStore({
|
||||||
"remember",
|
"remember",
|
||||||
"user",
|
"user",
|
||||||
"events",
|
"events",
|
||||||
|
"lastUsed",
|
||||||
]
|
]
|
||||||
}),
|
}),
|
||||||
|
sharedStatePlugin({
|
||||||
|
debug: true,
|
||||||
|
isLoadedKey: "shared_loaded",
|
||||||
|
clearingMutation: "logout",
|
||||||
|
afterInit: "afterSharedInit",
|
||||||
|
state: [
|
||||||
|
"test",
|
||||||
|
"state_options",
|
||||||
|
"fetchedData",
|
||||||
|
"tickets",
|
||||||
|
"users",
|
||||||
|
"groups",
|
||||||
|
"loadedBoxes",
|
||||||
|
"loadedItems",
|
||||||
|
],
|
||||||
|
watch: [
|
||||||
|
"test",
|
||||||
|
"state_options",
|
||||||
|
"fetchedData",
|
||||||
|
"tickets",
|
||||||
|
"users",
|
||||||
|
"groups",
|
||||||
|
"loadedBoxes",
|
||||||
|
"loadedItems",
|
||||||
|
],
|
||||||
|
mutations: [
|
||||||
|
//"replaceTickets",
|
||||||
|
],
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -93,7 +93,7 @@ export default {
|
||||||
...mapGetters(['layout']),
|
...mapGetters(['layout']),
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions(['deleteItem', 'markItemReturned', 'loadEventItems', 'updateItem']),
|
...mapActions(['deleteItem', 'markItemReturned', 'loadEventItems', 'updateItem', 'scheduleAfterInit']),
|
||||||
openLightboxModalWith(item) {
|
openLightboxModalWith(item) {
|
||||||
this.lightboxHash = item.file;
|
this.lightboxHash = item.file;
|
||||||
},
|
},
|
||||||
|
@ -115,7 +115,7 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.loadEventItems();
|
this.scheduleAfterInit(() => [this.loadEventItems()]);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -62,7 +62,7 @@ export default {
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions(['deleteItem', 'markItemReturned', 'sendMail', 'updateTicketPartial', 'postComment']),
|
...mapActions(['deleteItem', 'markItemReturned', 'sendMail', 'updateTicketPartial', 'postComment']),
|
||||||
...mapActions(['loadTickets', 'loadUsers', 'fetchTicketStates']),
|
...mapActions(['loadTickets', 'fetchTicketStates', 'loadUsers', 'scheduleAfterInit']),
|
||||||
handleMail(mail) {
|
handleMail(mail) {
|
||||||
this.sendMail({
|
this.sendMail({
|
||||||
id: this.ticket.id,
|
id: this.ticket.id,
|
||||||
|
@ -86,12 +86,10 @@ export default {
|
||||||
id: ticket.id,
|
id: ticket.id,
|
||||||
assigned_to: ticket.assigned_to
|
assigned_to: ticket.assigned_to
|
||||||
})
|
})
|
||||||
}
|
|
||||||
},
|
},
|
||||||
created() {
|
},
|
||||||
this.fetchTicketStates()
|
mounted() {
|
||||||
this.loadTickets()
|
this.scheduleAfterInit(() => [this.fetchTicketStates(), this.loadTickets(), this.loadUsers()]);
|
||||||
this.loadUsers()
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -65,7 +65,7 @@ export default {
|
||||||
...mapGetters(['stateInfo', 'getEventSlug', 'layout']),
|
...mapGetters(['stateInfo', 'getEventSlug', 'layout']),
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions(['loadTickets', 'fetchTicketStates']),
|
...mapActions(['loadTickets', 'fetchTicketStates', 'scheduleAfterInit']),
|
||||||
gotoDetail(ticket) {
|
gotoDetail(ticket) {
|
||||||
this.$router.push({name: 'ticket', params: {id: ticket.id}});
|
this.$router.push({name: 'ticket', params: {id: ticket.id}});
|
||||||
},
|
},
|
||||||
|
@ -80,9 +80,8 @@ export default {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {
|
mounted() {
|
||||||
this.fetchTicketStates();
|
this.scheduleAfterInit(() => [this.fetchTicketStates(), this.loadTickets()]);
|
||||||
this.loadTickets();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
Loading…
Reference in a new issue