127127 </div >
128128</template >
129129
130- <script >
130+ <script setup lang="ts" >
131131
132132import { t } from ' @nextcloud/l10n'
133+ import { computed } from ' vue'
133134
134135import { selectCustonOption } from ' ../../helpers/certification'
135136import NcSettingsSection from ' @nextcloud/vue/components/NcSettingsSection'
@@ -146,161 +147,170 @@ import {
146147 mdiShieldOff ,
147148} from ' @mdi/js'
148149
149- export default {
150+ defineOptions ( {
150151 name: ' CertificateContent' ,
151- components: {
152- NcSettingsSection,
153- NcNoteCard,
154- NcChip,
155- NcIconSvgWrapper,
156- },
157- props: {
158- certificate: {
159- type: Object ,
160- default : () => {},
161- required: false ,
162- },
163- index: {
164- type: String ,
165- default: ' 0' ,
166- },
167- },
168- data () {
169- return {
170- mdiCheckCircle,
171- mdiCancel,
172- mdiAlertCircleOutline,
173- mdiHelpCircle,
174- mdiShieldCheck,
175- mdiShieldAlert,
176- mdiShieldOff,
177- EXPIRATION_WARNING_DAYS : 30 ,
152+ })
153+
154+ type PurposeEntry = [boolean , boolean , string ]
155+ type CertificateMap = Record <string , unknown >
156+ type CertificateData = {
157+ subject? : Record <string , unknown >
158+ issuer? : Record <string , unknown >
159+ purposes? : Record <string , PurposeEntry >
160+ extracerts? : CertificateData []
161+ valid_from? : string
162+ valid_to? : string
163+ validTo_time_t? : number
164+ version? : number
165+ hash? : string
166+ signatureTypeLN? : string
167+ serialNumber? : string
168+ serialNumberHex? : string
169+ name? : string
170+ extensions? : Record <string , unknown >
171+ crl_validation? : string
172+ }
173+
174+ const props = withDefaults (defineProps <{
175+ certificate? : CertificateData
176+ index? : string
177+ }>(), {
178+ certificate : () => ({}),
179+ index: ' 0' ,
180+ })
181+
182+ const EXPIRATION_WARNING_DAYS = 30
183+
184+ const validityStatusMap = computed (() => ({
185+ unknown: { text: t (' libresign' , ' Unknown' ), variant: ' tertiary' , icon: mdiHelpCircle },
186+ expired: { text: t (' libresign' , ' Expired' ), variant: ' error' , icon: mdiCancel },
187+ expiring: { text: t (' libresign' , ' Expires Soon' ), variant: ' warning' , icon: mdiAlertCircleOutline },
188+ valid: { text: t (' libresign' , ' Valid' ), variant: ' success' , icon: mdiCheckCircle },
189+ }))
190+
191+ const crlStatusMap = computed (() => ({
192+ valid: { text: t (' libresign' , ' Valid (Not Revoked)' ), variant: ' success' , icon: mdiShieldCheck },
193+ revoked: { text: t (' libresign' , ' Revoked' ), variant: ' error' , icon: mdiShieldOff },
194+ missing: { text: t (' libresign' , ' No CRL Information' ), variant: ' warning' , icon: mdiShieldAlert },
195+ no_urls: { text: t (' libresign' , ' No CRL URLs Found' ), variant: ' warning' , icon: mdiShieldAlert },
196+ urls_inaccessible: { text: t (' libresign' , ' CRL URLs Inaccessible' ), variant: ' tertiary' , icon: mdiHelpCircle },
197+ validation_failed: { text: t (' libresign' , ' CRL Validation Failed' ), variant: ' tertiary' , icon: mdiHelpCircle },
198+ validation_error: { text: t (' libresign' , ' CRL Validation Error' ), variant: ' tertiary' , icon: mdiHelpCircle },
199+ }))
200+
201+ const shouldShowPurposes = computed (() => Boolean (
202+ props .certificate .purposes
203+ && Object .keys (props .certificate .purposes ).length
204+ && props .index === ' 0' ,
205+ ))
206+
207+ const certificateValidityStatus = computed (() => validityStatusMap .value [getValidityStatus ()])
208+ const crlValidationStatus = computed (() => crlStatusMap .value [props .certificate .crl_validation ?? ' ' ] || {
209+ text: t (' libresign' , ' Unknown Status' ),
210+ variant: ' tertiary' ,
211+ icon: mdiHelpCircle ,
212+ })
213+
214+ function orderList(data : CertificateMap = {}) {
215+ const sorted: CertificateMap = {}
216+ ;[' CN' , ' OU' , ' O' ].forEach (element => {
217+ if (data [element ]) {
218+ sorted [element ] = data [element ]
178219 }
179- },
180- computed: {
181- shouldShowPurposes () {
182- return this .certificate .purposes &&
183- Object .keys (this .certificate .purposes ).length &&
184- this .index === ' 0'
185- },
186- validityStatusMap () {
187- return {
188- unknown: { text: this .t (' libresign' , ' Unknown' ), variant: ' tertiary' , icon: this .mdiHelpCircle },
189- expired: { text: this .t (' libresign' , ' Expired' ), variant: ' error' , icon: this .mdiCancel },
190- expiring: { text: this .t (' libresign' , ' Expires Soon' ), variant: ' warning' , icon: this .mdiAlertCircleOutline },
191- valid: { text: this .t (' libresign' , ' Valid' ), variant: ' success' , icon: this .mdiCheckCircle }
192- }
193- },
194- crlStatusMap () {
195- return {
196- valid: { text: this .t (' libresign' , ' Valid (Not Revoked)' ), variant: ' success' , icon: this .mdiShieldCheck },
197- revoked: { text: this .t (' libresign' , ' Revoked' ), variant: ' error' , icon: this .mdiShieldOff },
198- missing: { text: this .t (' libresign' , ' No CRL Information' ), variant: ' warning' , icon: this .mdiShieldAlert },
199- no_urls: { text: this .t (' libresign' , ' No CRL URLs Found' ), variant: ' warning' , icon: this .mdiShieldAlert },
200- urls_inaccessible: { text: this .t (' libresign' , ' CRL URLs Inaccessible' ), variant: ' tertiary' , icon: this .mdiHelpCircle },
201- validation_failed: { text: this .t (' libresign' , ' CRL Validation Failed' ), variant: ' tertiary' , icon: this .mdiHelpCircle },
202- validation_error: { text: this .t (' libresign' , ' CRL Validation Error' ), variant: ' tertiary' , icon: this .mdiHelpCircle }
203- }
204- },
205- certificateValidityStatus () {
206- return this .validityStatusMap [this .getValidityStatus ()]
207- },
208- crlValidationStatus () {
209- return this .crlStatusMap [this .certificate .crl_validation ] || {
210- text: this .t (' libresign' , ' Unknown Status' ),
211- variant: ' tertiary' ,
212- icon: this .mdiHelpCircle
213- }
214- },
215- },
216- methods: {
217- t,
218- orderList (data ) {
219- const sorted = {};
220- [' CN' , ' OU' , ' O' ].forEach (element => {
221- if (data[element]) {
222- sorted[element] = data[element]
223- }
224- })
225- Object .keys (data).forEach ((key ) => {
226- if (! sorted[key]) {
227- sorted[key] = data[key]
228- }
229- })
230- return sorted
231- },
232- getLabelFromId (id ) {
233- const option = selectCustonOption (id)
234- if (option .isSome ()) {
235- return this .camelCaseToTitleCase (option .unwrap ().label )
236- }
237- return this .camelCaseToTitleCase (id)
238- },
239- camelCaseToTitleCase (text ) {
240- if (text .includes (' ' )) {
241- return text .replace (/ ^ . / , str => str .toUpperCase ())
242- }
243-
244- return text
245- // Handle acronyms (consecutive uppercase letters)
246- .replace (/ ([A-Z ] + )([A-Z ][a-z ] )/ g , ' $1 $2' )
247- // Add space before uppercase letters that follow lowercase
248- .replace (/ ([a-z ] )([A-Z ] )/ g , ' $1 $2' )
249- // Capitalize first letter
250- .replace (/ ^ . / , str => str .toUpperCase ())
251- .trim ()
252- },
253- formatPurposeName (purpose ) {
254- const purposeNames = {
255- ' sslclient' : this .t (' libresign' , ' SSL Client' ),
256- ' sslserver' : this .t (' libresign' , ' SSL Server' ),
257- ' nssslserver' : this .t (' libresign' , ' Netscape SSL Server' ),
258- ' smimesign' : this .t (' libresign' , ' S/MIME Signing' ),
259- ' smimeencrypt' : this .t (' libresign' , ' S/MIME Encryption' ),
260- ' crlsign' : this .t (' libresign' , ' CRL Signing' ),
261- ' any' : this .t (' libresign' , ' Any Purpose' ),
262- ' ocsphelper' : this .t (' libresign' , ' OCSP Helper' ),
263- ' timestampsign' : this .t (' libresign' , ' Timestamp Signing' ),
264- ' codesign' : this .t (' libresign' , ' Code Signing' ),
265- }
266- return purposeNames[purpose] || purpose
267- },
268- getChainCertificateLabel (index , certificate ) {
269- if (index === 0 ) {
270- return this .t (' libresign' , ' Intermediate Certificate' )
271- }
272- if (certificate .subject && certificate .issuer &&
273- JSON .stringify (certificate .subject ) === JSON .stringify (certificate .issuer )) {
274- return this .t (' libresign' , ' Root Certificate (CA)' )
275- }
276- return this .t (' libresign' , ' Certificate {number}' , { number: index + 1 })
277- },
278- getValidityStatus () {
279- if (! this .certificate .validTo_time_t ) {
280- return ' unknown'
281- }
282-
283- const now = new Date ()
284- const expirationDate = this .unixTimestampToDate (this .certificate .validTo_time_t )
285-
286- if (expirationDate <= now) {
287- return ' expired'
288- }
289-
290- const warningDate = new Date ()
291- warningDate .setDate (now .getDate () + this .EXPIRATION_WARNING_DAYS )
292-
293- if (expirationDate <= warningDate) {
294- return ' expiring'
295- }
296-
297- return ' valid'
298- },
299- unixTimestampToDate (unixTimestamp ) {
300- return new Date (unixTimestamp * 1000 )
301- },
302- },
220+ })
221+ Object .keys (data ).forEach ((key ) => {
222+ if (! sorted [key ]) {
223+ sorted [key ] = data [key ]
224+ }
225+ })
226+ return sorted
303227}
228+
229+ function getLabelFromId(id : string ) {
230+ const option = selectCustonOption (id )
231+ if (option .isSome ()) {
232+ return camelCaseToTitleCase (option .unwrap ().label )
233+ }
234+ return camelCaseToTitleCase (id )
235+ }
236+
237+ function camelCaseToTitleCase(text : string ) {
238+ if (text .includes (' ' )) {
239+ return text .replace (/ ^ . / , str => str .toUpperCase ())
240+ }
241+
242+ return text
243+ .replace (/ ([A-Z ] + )([A-Z ][a-z ] )/ g , ' $1 $2' )
244+ .replace (/ ([a-z ] )([A-Z ] )/ g , ' $1 $2' )
245+ .replace (/ ^ . / , str => str .toUpperCase ())
246+ .trim ()
247+ }
248+
249+ function formatPurposeName(purpose : string ) {
250+ const purposeNames: Record <string , string > = {
251+ sslclient: t (' libresign' , ' SSL Client' ),
252+ sslserver: t (' libresign' , ' SSL Server' ),
253+ nssslserver: t (' libresign' , ' Netscape SSL Server' ),
254+ smimesign: t (' libresign' , ' S/MIME Signing' ),
255+ smimeencrypt: t (' libresign' , ' S/MIME Encryption' ),
256+ crlsign: t (' libresign' , ' CRL Signing' ),
257+ any: t (' libresign' , ' Any Purpose' ),
258+ ocsphelper: t (' libresign' , ' OCSP Helper' ),
259+ timestampsign: t (' libresign' , ' Timestamp Signing' ),
260+ codesign: t (' libresign' , ' Code Signing' ),
261+ }
262+ return purposeNames [purpose ] || purpose
263+ }
264+
265+ function getChainCertificateLabel(index : number , certificate : CertificateData ) {
266+ if (index === 0 ) {
267+ return t (' libresign' , ' Intermediate Certificate' )
268+ }
269+ if (certificate .subject && certificate .issuer
270+ && JSON .stringify (certificate .subject ) === JSON .stringify (certificate .issuer )) {
271+ return t (' libresign' , ' Root Certificate (CA)' )
272+ }
273+ return t (' libresign' , ' Certificate {number}' , { number: index + 1 })
274+ }
275+
276+ function getValidityStatus() {
277+ if (! props .certificate .validTo_time_t ) {
278+ return ' unknown'
279+ }
280+
281+ const now = new Date ()
282+ const expirationDate = unixTimestampToDate (props .certificate .validTo_time_t )
283+ if (expirationDate <= now ) {
284+ return ' expired'
285+ }
286+
287+ const warningDate = new Date ()
288+ warningDate .setDate (now .getDate () + EXPIRATION_WARNING_DAYS )
289+ if (expirationDate <= warningDate ) {
290+ return ' expiring'
291+ }
292+
293+ return ' valid'
294+ }
295+
296+ function unixTimestampToDate(unixTimestamp : number ) {
297+ return new Date (unixTimestamp * 1000 )
298+ }
299+
300+ defineExpose ({
301+ shouldShowPurposes ,
302+ validityStatusMap ,
303+ crlStatusMap ,
304+ certificateValidityStatus ,
305+ crlValidationStatus ,
306+ orderList ,
307+ getLabelFromId ,
308+ camelCaseToTitleCase ,
309+ formatPurposeName ,
310+ getChainCertificateLabel ,
311+ getValidityStatus ,
312+ unixTimestampToDate ,
313+ })
304314 </script >
305315
306316<style lang="scss" scoped>
0 commit comments