Skip to content

Commit 00548f1

Browse files
committed
feat: add DocMDP admin settings UI component
Add Vue component for DocMDP configuration in admin settings: - Toggle switch to enable/disable DocMDP - Radio buttons for certification level selection: * No certification (P=0) * No changes allowed (P=1) * Form filling allowed (P=2) * Form filling and annotations (P=3) - Loading/saving/error indicators - Saves via POST /api/v1/admin/docmdp/config Uses Nextcloud Vue components (NcCheckboxRadioSwitch, NcLoadingIcon, NcSavingIndicatorIcon, NcNoteCard). Signed-off-by: Vitor Mattos <[email protected]>
1 parent 321080d commit 00548f1

1 file changed

Lines changed: 200 additions & 0 deletions

File tree

src/views/Settings/DocMDP.vue

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
<!--
2+
- SPDX-FileCopyrightText: 2025 LibreCode coop and LibreCode contributors
3+
- SPDX-License-Identifier: AGPL-3.0-or-later
4+
-->
5+
<template>
6+
<NcSettingsSection :name="name">
7+
<p class="docmdp-info">
8+
{{ t('libresign', 'DocMDP adds certification signatures to protect PDF documents from unauthorized modifications.') }}
9+
</p>
10+
<p>
11+
<NcCheckboxRadioSwitch type="switch"
12+
:checked="enabled"
13+
:disabled="loading"
14+
@update:checked="onEnabledChange">
15+
{{ t('libresign', 'Enable DocMDP') }}
16+
</NcCheckboxRadioSwitch>
17+
</p>
18+
<NcNoteCard v-if="errorMessage" type="error">
19+
{{ errorMessage }}
20+
</NcNoteCard>
21+
<div v-if="enabled">
22+
<label>
23+
{{ t('libresign', 'Default certification level for new signatures:') }}
24+
</label>
25+
<div class="docmdp-select-wrapper">
26+
<NcCheckboxRadioSwitch v-for="level in availableLevels"
27+
:key="level.value"
28+
type="radio"
29+
:checked="String(selectedLevel?.value)"
30+
:value="String(level.value)"
31+
:disabled="loading"
32+
name="docmdp_level"
33+
@update:checked="onLevelChange">
34+
<div class="docmdp-option">
35+
<div class="docmdp-option-content">
36+
<strong>{{ level.label }}</strong>
37+
<p class="docmdp-option-description">
38+
{{ level.description }}
39+
</p>
40+
</div>
41+
<div v-if="selectedLevel?.value === level.value" class="docmdp-option-status">
42+
<NcLoadingIcon v-if="loading" :size="20" />
43+
<NcSavingIndicatorIcon v-else-if="saved" :size="20" />
44+
<NcSavingIndicatorIcon v-else-if="showErrorIcon" :size="20" error />
45+
</div>
46+
</div>
47+
</NcCheckboxRadioSwitch>
48+
</div>
49+
</div>
50+
</NcSettingsSection>
51+
</template>
52+
53+
<script>
54+
import axios from '@nextcloud/axios'
55+
import { loadState } from '@nextcloud/initial-state'
56+
import { translate as t } from '@nextcloud/l10n'
57+
import { generateOcsUrl } from '@nextcloud/router'
58+
59+
import NcCheckboxRadioSwitch from '@nextcloud/vue/components/NcCheckboxRadioSwitch'
60+
import NcLoadingIcon from '@nextcloud/vue/components/NcLoadingIcon'
61+
import NcNoteCard from '@nextcloud/vue/components/NcNoteCard'
62+
import NcSavingIndicatorIcon from '@nextcloud/vue/components/NcSavingIndicatorIcon'
63+
import NcSettingsSection from '@nextcloud/vue/components/NcSettingsSection'
64+
65+
export default {
66+
name: 'DocMDP',
67+
components: {
68+
NcCheckboxRadioSwitch,
69+
NcLoadingIcon,
70+
NcNoteCard,
71+
NcSavingIndicatorIcon,
72+
NcSettingsSection,
73+
},
74+
data() {
75+
return {
76+
name: t('libresign', 'DocMDP Configuration'),
77+
enabled: false,
78+
selectedLevel: null,
79+
availableLevels: [],
80+
loading: false,
81+
errorMessage: '',
82+
saved: false,
83+
showErrorIcon: false,
84+
}
85+
},
86+
async mounted() {
87+
this.loadConfig()
88+
},
89+
methods: {
90+
loadConfig() {
91+
try {
92+
const config = loadState('libresign', 'docmdp_config')
93+
this.enabled = config.enabled
94+
this.availableLevels = config.availableLevels
95+
96+
this.selectedLevel = this.availableLevels.find(
97+
level => level.value === config.defaultLevel
98+
)
99+
100+
if (this.enabled && !this.selectedLevel && this.availableLevels.length > 0) {
101+
this.selectedLevel = this.availableLevels[0]
102+
}
103+
} catch (error) {
104+
console.error('Error loading DocMDP configuration:', error)
105+
this.errorMessage = t('libresign', 'Failed to load DocMDP configuration')
106+
}
107+
},
108+
onEnabledChange(value) {
109+
this.enabled = value
110+
this.saved = false
111+
this.errorMessage = ''
112+
this.showErrorIcon = false
113+
114+
if (value) {
115+
if (!this.selectedLevel && this.availableLevels.length > 0) {
116+
this.selectedLevel = this.availableLevels[0]
117+
}
118+
} else {
119+
this.selectedLevel = this.availableLevels[0] || null
120+
}
121+
122+
this.saveConfig()
123+
},
124+
onLevelChange(value) {
125+
this.selectedLevel = this.availableLevels.find(level => level.value === parseInt(value))
126+
this.errorMessage = ''
127+
this.showErrorIcon = false
128+
this.saveConfig()
129+
},
130+
async saveConfig() {
131+
this.loading = true
132+
this.errorMessage = ''
133+
this.saved = false
134+
this.showErrorIcon = false
135+
136+
try {
137+
const url = generateOcsUrl('apps/libresign/api/v1/admin/docmdp/config')
138+
await axios.post(url, {
139+
enabled: this.enabled,
140+
defaultLevel: this.enabled ? (this.selectedLevel?.value ?? 0) : 0,
141+
})
142+
143+
this.saved = true
144+
setTimeout(() => {
145+
this.saved = false
146+
}, 3000)
147+
} catch (error) {
148+
console.error('Error saving DocMDP configuration:', error)
149+
this.errorMessage = error.response?.data?.ocs?.data?.error
150+
|| t('libresign', 'Failed to save DocMDP configuration')
151+
this.showErrorIcon = true
152+
setTimeout(() => {
153+
this.showErrorIcon = false
154+
}, 3000)
155+
} finally {
156+
this.loading = false
157+
}
158+
},
159+
},
160+
}
161+
</script>
162+
163+
<style lang="scss" scoped>
164+
.docmdp {
165+
&-info {
166+
color: var(--color-text-maxcontrast);
167+
margin-top: 0.5rem;
168+
}
169+
170+
&-option {
171+
display: flex;
172+
justify-content: space-between;
173+
align-items: flex-start;
174+
gap: 1rem;
175+
width: 100%;
176+
177+
&-content {
178+
flex: 1;
179+
}
180+
181+
&-status {
182+
flex-shrink: 0;
183+
display: flex;
184+
align-items: center;
185+
}
186+
187+
&-description {
188+
margin: 0.25rem 0 0 0;
189+
color: var(--color-text-maxcontrast);
190+
font-size: 90%;
191+
}
192+
}
193+
194+
&-select-wrapper {
195+
display: flex;
196+
flex-direction: column;
197+
gap: 0.5rem;
198+
}
199+
}
200+
</style>

0 commit comments

Comments
 (0)