Skip to content

Commit 45082dd

Browse files
committed
4.3 Release
1 parent ed64fac commit 45082dd

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+2610
-624
lines changed

.eslintrc.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ module.exports = {
2222
"allowDestructuring": true, // Allow `const { props, state } = this`; false by default
2323
"allowedNames": ["vm"] // Allow `const vm= this`; `[]` by default
2424
}
25-
]
25+
],
26+
"vue/valid-v-slot": ["error", {"allowModifiers": true}]
2627
},
2728
overrides: [
2829
{
@@ -34,5 +35,5 @@ module.exports = {
3435
jest: true
3536
}
3637
}
37-
]
38+
],
3839
}

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
"@ckeditor/ckeditor5-theme-lark": "^35.0.0",
4242
"@ckeditor/ckeditor5-upload": "^35.0.0",
4343
"@ckeditor/ckeditor5-vue2": "^3.0.0",
44+
"@types/leaflet": "^1.9.14",
4445
"@types/marked": "^4.0.7",
4546
"@types/qs": "^6.9.7",
4647
"@vue/babel-helper-vue-jsx-merge-props": "^1.2.1",
@@ -50,7 +51,9 @@
5051
"colors": "1.4.0",
5152
"core-js": "^3.21.1",
5253
"dompurify": "^2.4.1",
54+
"html-to-image": "^1.11.11",
5355
"idb": "^7.0.1",
56+
"leaflet": "^1.9.4",
5457
"localforage": "^1.10.0",
5558
"marked": "^4.2.5",
5659
"postcss": "^8.4.31",
@@ -66,7 +69,6 @@
6669
"vue-cookies": "^1.7.4",
6770
"vue-final-modal": "^2.4.3",
6871
"vue-js-modal": "^2.0.1",
69-
"vue-matomo": "^4.1.0",
7072
"vue-meta": "^2.4.0",
7173
"vue-observe-visibility": "^1.0.0",
7274
"vue-pdf-embed": "^1.1.4",

public/layers-2x.png

1.23 KB
Loading

public/layers.png

696 Bytes
Loading

public/marker-icon-2x.png

2.41 KB
Loading

public/marker-icon.png

1.43 KB
Loading

public/marker-shadow.png

618 Bytes
Loading

src/App.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<!-- App.vue -->
22
<template>
33
<v-app>
4-
<ScotNavBar />
4+
<ScotNavBar style="z-index: 99;"/>
55
<v-main style="max-height: 100vh">
66
<QuickSettingsDrawer />
77
<v-dialog v-if="searchResults != undefined" :value="showSearchOverlay">

src/api/auth.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,13 @@ export default (axios: AxiosStatic) => ({
9090
method: 'GET',
9191
withCredentials: true
9292
})
93+
},
94+
async resetPasswordAttempts(username: number): Promise<any> {
95+
return axios({
96+
url: '/users/' + username + '/reset-failed-attempts',
97+
method: 'POST',
98+
withCredentials: true
99+
})
93100
}
94101
}
95102
);

src/api/elements.ts

Lines changed: 89 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import {AxiosStatic} from "axios"
22
import { IRElementType, IRElementStatus, PermissionEnum } from "@/store/modules/IRElements/types"
33
import { convertToSnakeCase } from '@/utils/elementUtils'
44

5-
const IRElementAPIPaths: {[key in IRElementType]?: string}= {
5+
const IRElementAPIPaths: {[key in IRElementType]?: string} = {
66
[IRElementType.Alertgroup]: "/alertgroup",
77
[IRElementType.Event]: "/event",
88
[IRElementType.Alert]: "/alert",
@@ -142,14 +142,15 @@ export default (axios: AxiosStatic) => ({
142142
})
143143
},
144144

145-
async submitFile(formData:FormData, targetType:string, targetId:string): Promise<any> {
145+
async submitFile(formData: FormData, targetType: string, targetId: string, description: string | null = null): Promise<any> {
146146
return axios({
147147
url: '/file/',
148148
method: 'POST',
149149
headers: {
150150
'Content-Type': 'multipart/form-data',
151151
'target_type': targetType,
152152
'target_id': targetId,
153+
'description': description
153154
},
154155
withCredentials: true,
155156
data: formData
@@ -195,6 +196,26 @@ export default (axios: AxiosStatic) => ({
195196
})
196197
},
197198

199+
async updateFileById(fileId: number, filename: string | null, description: string | null) {
200+
const path = 'file' + '/' + fileId
201+
const data: any = {}
202+
if (filename) {
203+
data["filename"] = filename
204+
}
205+
if (description) {
206+
data["description"] = description
207+
}
208+
return axios({
209+
url: path,
210+
method: 'PUT',
211+
headers: {
212+
'Content-Type': 'application/json'
213+
},
214+
withCredentials: true,
215+
data: data
216+
})
217+
},
218+
198219
async undeleteElementById(elementId: number, elementType: IRElementType, keep_id: boolean = true) {
199220
const path = IRElementAPIPaths[elementType] + '/undelete'
200221
return axios({
@@ -615,6 +636,18 @@ export default (axios: AxiosStatic) => ({
615636
})
616637
},
617638

639+
async addEntityTag(entityId:number, tagToAdd:any): Promise<any> {
640+
return axios({
641+
url: `/entity/${entityId}/tag`,
642+
method: 'POST',
643+
headers: {
644+
'Content-Type': 'application/json'
645+
},
646+
withCredentials:true,
647+
data: {'id': tagToAdd}
648+
})
649+
},
650+
618651
async removeEntityClass(entityId:number, entityClassesToRemove:any): Promise<any> {
619652
return axios({
620653
url: `/entity/${entityId}/entity_class/remove`,
@@ -742,4 +775,57 @@ export default (axios: AxiosStatic) => ({
742775
withCredentials: true
743776
})
744777
},
745-
});
778+
779+
async upvoteElement(elementID: number, elementType: IRElementType, abortController: AbortController): Promise<any> {
780+
return axios({
781+
url: `${IRElementAPIPaths[elementType]}/${elementID}/upvote`,
782+
method: 'POST',
783+
withCredentials: true,
784+
signal: abortController?.signal
785+
})
786+
},
787+
788+
async downvoteElement(elementID: number, elementType: IRElementType, abortController: AbortController): Promise<any> {
789+
return axios({
790+
url: `${IRElementAPIPaths[elementType]}/${elementID}/downvote`,
791+
method: 'POST',
792+
withCredentials: true,
793+
signal: abortController?.signal
794+
})
795+
},
796+
797+
async favoriteElement(elementID: number, elementType: IRElementType, abortController: AbortController): Promise<any> {
798+
return axios({
799+
url: `${IRElementAPIPaths[elementType]}/${elementID}/favorite`,
800+
method: 'POST',
801+
withCredentials: true,
802+
signal: abortController?.signal
803+
})
804+
},
805+
806+
async subscribeElement(elementID: number, elementType: IRElementType, abortController: AbortController): Promise<any> {
807+
return axios({
808+
url: `notification/subscribe`,
809+
method: 'POST',
810+
withCredentials: true,
811+
data: {
812+
target_type: convertToSnakeCase(elementType),
813+
target_id: elementID
814+
},
815+
signal: abortController?.signal
816+
})
817+
},
818+
819+
async unsubscribeElement(elementID: number, elementType: IRElementType, abortController: AbortController): Promise<any> {
820+
return axios({
821+
url: `notification/unsubscribe`,
822+
method: 'POST',
823+
withCredentials: true,
824+
data: {
825+
target_type: convertToSnakeCase(elementType),
826+
target_id: elementID
827+
},
828+
signal: abortController?.signal
829+
})
830+
},
831+
});

src/api/user.ts

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {AxiosStatic} from "axios"
22
import { PermissionEnum } from "../store/modules/IRElements/types";
3+
import { UserLinksEnum } from "../store/modules/user/types";
34
export default (axios: AxiosStatic) => ({
45
async updateUserMe(updateData: any): Promise<any> {
56
return axios({
@@ -156,18 +157,19 @@ export default (axios: AxiosStatic) => ({
156157
})
157158
},
158159

159-
async retrieveNotifications(): Promise<any> {
160+
async retrieveNotifications(includeAcked: boolean | undefined = undefined, skip: number | undefined = undefined, limit: number | undefined = undefined): Promise<any> {
160161
return axios({
161162
url: '/notification/',
162163
method: 'GET',
163164
withCredentials: true,
165+
params: { include_acked: includeAcked, skip, limit }
164166
})
165167
},
166168

167169

168170
async ackNotifications(notificationIds: Array<number>): Promise<any> {
169171
return axios({
170-
url: '/notification/ack/',
172+
url: '/notification/ack',
171173
method: 'POST',
172174
data: {notification_ids:notificationIds},
173175
headers: {
@@ -177,6 +179,18 @@ export default (axios: AxiosStatic) => ({
177179
})
178180
},
179181

182+
async sendBroadcastNotification(message: string, priority: string, expires: Date): Promise<any> {
183+
return axios({
184+
url: '/notification/broadcast',
185+
method: 'POST',
186+
data: { message, priority, expires },
187+
headers: {
188+
'Content-Type': 'application/json'
189+
},
190+
withCredentials: true,
191+
})
192+
},
193+
180194
async updateApiKey(key: string, updateData: any): Promise<any> {
181195
return axios({
182196
url: '/apikey/' + key,
@@ -216,5 +230,31 @@ export default (axios: AxiosStatic) => ({
216230
},
217231

218232
})
233+
},
234+
235+
async getFavorites(abortController?: AbortController): Promise<any> {
236+
return axios({
237+
url: 'user_links/',
238+
method: "GET",
239+
withCredentials: true,
240+
params: { link_type: UserLinksEnum.favorite, limit: -1 },
241+
headers: {
242+
'Content-Type': 'application/json'
243+
},
244+
signal: abortController?.signal
245+
})
246+
},
247+
248+
async getSubscriptions(abortController?: AbortController): Promise<any> {
249+
return axios({
250+
url: 'user_links/',
251+
method: "GET",
252+
withCredentials: true,
253+
params: { link_type: UserLinksEnum.subscription, limit: -1 },
254+
headers: {
255+
'Content-Type': 'application/json'
256+
},
257+
signal: abortController?.signal
258+
})
219259
}
220260
});
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
<template>
2+
<v-card>
3+
<v-card-title>Send Global Announcement</v-card-title>
4+
<v-card-subtitle>Announcements will be sent to all users as a notification</v-card-subtitle>
5+
<v-card-text>
6+
<v-text-field v-model="announcementText"
7+
label="Announcement Text">
8+
</v-text-field>
9+
<v-row>
10+
<v-col>
11+
<v-select :items="priorityChoices" label="Priority" v-model="priority"></v-select>
12+
</v-col>
13+
<v-col cols="7">
14+
<v-row no-gutters>
15+
<v-col class="pr-3">
16+
<v-text-field v-model="expireAmount" label="Expiration"
17+
@input="onExpiresInput" :error-messages="errorText"
18+
:error="errorText != null"
19+
hint="The amount of time that the announcement will be shown to any user who logs in that hasn't seen it yet (default 12 hours)">
20+
</v-text-field>
21+
</v-col>
22+
<v-col>
23+
<v-select :items="timespanChoices" v-model="expireUnit"></v-select>
24+
</v-col>
25+
</v-row>
26+
</v-col>
27+
</v-row>
28+
</v-card-text>
29+
<v-card-actions>
30+
<v-btn color="green" @click="submitAnnouncement" :loading="submitLoading">
31+
Submit Announcement
32+
<template v-slot:loader v-if="submitComplete">
33+
<span class="custom-loader">
34+
<v-icon>mdi-check</v-icon>
35+
</span>
36+
</template>
37+
</v-btn>
38+
</v-card-actions>
39+
</v-card>
40+
</template>
41+
42+
<script lang="ts">
43+
import { Component, Vue } from 'vue-property-decorator'
44+
import { Action, Getter, Mutation, } from 'vuex-class';
45+
import { Settings } from '@/store/modules/team/types';
46+
import { PriorityEnum } from '@/store/modules/user/types';
47+
48+
@Component({
49+
components: {
50+
},
51+
})
52+
53+
export default class GlobalAnnouncements extends Vue{
54+
@Action('sendBroadcastNotification', { namespace: 'user' }) sendBroadcastNotification: CallableFunction
55+
56+
announcementText: string = ''
57+
priority: string = 'Medium'
58+
priorityChoices = Object.keys(PriorityEnum)
59+
expireAmount: string = ''
60+
expireUnit: string = 'Minutes'
61+
timespanChoices: Array<string> = ['Seconds', 'Minutes', 'Hours']
62+
submitLoading: boolean = false
63+
submitComplete: boolean = false
64+
errorText: string | null = null
65+
66+
async submitAnnouncement(){
67+
const now = new Date()
68+
const rawInput = Number.parseInt(this.expireAmount)
69+
if (Number.isNaN(rawInput)){
70+
this.errorText = 'You must enter a number'
71+
return
72+
}
73+
var expireDate = undefined;
74+
if (this.expireUnit == 'Seconds'){
75+
expireDate = new Date(now.getTime() + rawInput*1000)
76+
}
77+
if (this.expireUnit == 'Minutes'){
78+
expireDate = new Date(now.getTime() + rawInput*60000)
79+
}
80+
else {
81+
expireDate = new Date(now.getTime() + rawInput*3600000)
82+
}
83+
this.submitLoading = true
84+
const result = await this.sendBroadcastNotification({
85+
expires: expireDate.toISOString(),
86+
priority: this.priority.toLowerCase(),
87+
notificationText: this.announcementText
88+
})
89+
if (result) {
90+
this.submitComplete = true
91+
await new Promise(r => setTimeout(r, 2000)) // Wait 2 seconds
92+
this.submitComplete = false
93+
}
94+
this.submitLoading = false
95+
}
96+
97+
onExpiresInput(){
98+
this.errorText = null
99+
}
100+
}
101+
</script>

0 commit comments

Comments
 (0)