Merge branch 'main' into card-hover
This commit is contained in:
		
						commit
						e9e0884789
					
				
							
								
								
									
										1
									
								
								components.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								components.d.ts
									
									
									
									
										vendored
									
									
								
							| @ -188,6 +188,7 @@ declare module '@vue/runtime-core' { | |||||||
|     UserAgentParser: typeof import('./src/tools/user-agent-parser/user-agent-parser.vue')['default'] |     UserAgentParser: typeof import('./src/tools/user-agent-parser/user-agent-parser.vue')['default'] | ||||||
|     UserAgentResultCards: typeof import('./src/tools/user-agent-parser/user-agent-result-cards.vue')['default'] |     UserAgentResultCards: typeof import('./src/tools/user-agent-parser/user-agent-result-cards.vue')['default'] | ||||||
|     UuidGenerator: typeof import('./src/tools/uuid-generator/uuid-generator.vue')['default'] |     UuidGenerator: typeof import('./src/tools/uuid-generator/uuid-generator.vue')['default'] | ||||||
|  |     WifiQrCodeGenerator: typeof import('./src/tools/wifi-qr-code-generator/wifi-qr-code-generator.vue')['default'] | ||||||
|     XmlFormatter: typeof import('./src/tools/xml-formatter/xml-formatter.vue')['default'] |     XmlFormatter: typeof import('./src/tools/xml-formatter/xml-formatter.vue')['default'] | ||||||
|     YamlToJson: typeof import('./src/tools/yaml-to-json-converter/yaml-to-json.vue')['default'] |     YamlToJson: typeof import('./src/tools/yaml-to-json-converter/yaml-to-json.vue')['default'] | ||||||
|     YamlToToml: typeof import('./src/tools/yaml-to-toml/yaml-to-toml.vue')['default'] |     YamlToToml: typeof import('./src/tools/yaml-to-toml/yaml-to-toml.vue')['default'] | ||||||
|  | |||||||
| @ -146,7 +146,7 @@ function formatDateUsingFormatter(formatter: (date: Date) => string, date?: Date | |||||||
|       <c-input-text |       <c-input-text | ||||||
|         v-model:value="inputDate" |         v-model:value="inputDate" | ||||||
|         autofocus |         autofocus | ||||||
|         placeholder="Put you date string here..." |         placeholder="Put your date string here..." | ||||||
|         clearable |         clearable | ||||||
|         test-id="date-time-converter-input" |         test-id="date-time-converter-input" | ||||||
|         :validation="validation" |         :validation="validation" | ||||||
|  | |||||||
| @ -56,6 +56,7 @@ import { tool as metaTagGenerator } from './meta-tag-generator'; | |||||||
| import { tool as mimeTypes } from './mime-types'; | import { tool as mimeTypes } from './mime-types'; | ||||||
| import { tool as otpCodeGeneratorAndValidator } from './otp-code-generator-and-validator'; | import { tool as otpCodeGeneratorAndValidator } from './otp-code-generator-and-validator'; | ||||||
| import { tool as qrCodeGenerator } from './qr-code-generator'; | import { tool as qrCodeGenerator } from './qr-code-generator'; | ||||||
|  | import { tool as wifiQrCodeGenerator } from './wifi-qr-code-generator'; | ||||||
| import { tool as randomPortGenerator } from './random-port-generator'; | import { tool as randomPortGenerator } from './random-port-generator'; | ||||||
| import { tool as romanNumeralConverter } from './roman-numeral-converter'; | import { tool as romanNumeralConverter } from './roman-numeral-converter'; | ||||||
| import { tool as sqlPrettify } from './sql-prettify'; | import { tool as sqlPrettify } from './sql-prettify'; | ||||||
| @ -117,7 +118,7 @@ export const toolsByCategory: ToolCategory[] = [ | |||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     name: 'Images and videos', |     name: 'Images and videos', | ||||||
|     components: [qrCodeGenerator, svgPlaceholderGenerator, cameraRecorder], |     components: [qrCodeGenerator, wifiQrCodeGenerator, svgPlaceholderGenerator, cameraRecorder], | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     name: 'Development', |     name: 'Development', | ||||||
|  | |||||||
| @ -14,6 +14,6 @@ test.describe('Tool - Password strength analyser', () => { | |||||||
| 
 | 
 | ||||||
|     const crackDuration = await page.getByTestId('crack-duration').textContent(); |     const crackDuration = await page.getByTestId('crack-duration').textContent(); | ||||||
| 
 | 
 | ||||||
|     expect(crackDuration).toEqual('15,091 milleniums, 3 centurys'); |     expect(crackDuration).toEqual('15,091 millennia, 3 centuries'); | ||||||
|   }); |   }); | ||||||
| }); | }); | ||||||
|  | |||||||
| @ -19,20 +19,20 @@ function getHumanFriendlyDuration({ seconds }: { seconds: number }) { | |||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   const timeUnits = [ |   const timeUnits = [ | ||||||
|     { unit: 'millenium', secondsInUnit: 31536000000, format: prettifyExponentialNotation }, |     { unit: 'millenium', secondsInUnit: 31536000000, format: prettifyExponentialNotation, plural: 'millennia' }, | ||||||
|     { unit: 'century', secondsInUnit: 3153600000 }, |     { unit: 'century', secondsInUnit: 3153600000, plural: 'centuries' }, | ||||||
|     { unit: 'decade', secondsInUnit: 315360000 }, |     { unit: 'decade', secondsInUnit: 315360000, plural: 'decades' }, | ||||||
|     { unit: 'year', secondsInUnit: 31536000 }, |     { unit: 'year', secondsInUnit: 31536000, plural: 'years' }, | ||||||
|     { unit: 'month', secondsInUnit: 2592000 }, |     { unit: 'month', secondsInUnit: 2592000, plural: 'months' }, | ||||||
|     { unit: 'week', secondsInUnit: 604800 }, |     { unit: 'week', secondsInUnit: 604800, plural: 'weeks' }, | ||||||
|     { unit: 'day', secondsInUnit: 86400 }, |     { unit: 'day', secondsInUnit: 86400, plural: 'days' }, | ||||||
|     { unit: 'hour', secondsInUnit: 3600 }, |     { unit: 'hour', secondsInUnit: 3600, plural: 'hours' }, | ||||||
|     { unit: 'minute', secondsInUnit: 60 }, |     { unit: 'minute', secondsInUnit: 60, plural: 'minutes' }, | ||||||
|     { unit: 'second', secondsInUnit: 1 }, |     { unit: 'second', secondsInUnit: 1, plural: 'seconds' }, | ||||||
|   ]; |   ]; | ||||||
| 
 | 
 | ||||||
|   return _.chain(timeUnits) |   return _.chain(timeUnits) | ||||||
|     .map(({ unit, secondsInUnit, format = _.identity }) => { |     .map(({ unit, secondsInUnit, plural, format = _.identity }) => { | ||||||
|       const quantity = Math.floor(seconds / secondsInUnit); |       const quantity = Math.floor(seconds / secondsInUnit); | ||||||
|       seconds %= secondsInUnit; |       seconds %= secondsInUnit; | ||||||
| 
 | 
 | ||||||
| @ -41,7 +41,7 @@ function getHumanFriendlyDuration({ seconds }: { seconds: number }) { | |||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|       const formattedQuantity = format(quantity); |       const formattedQuantity = format(quantity); | ||||||
|       return `${formattedQuantity} ${unit}${quantity > 1 ? 's' : ''}`; |       return `${formattedQuantity} ${quantity > 1 ? plural : unit}`; | ||||||
|     }) |     }) | ||||||
|     .compact() |     .compact() | ||||||
|     .take(2) |     .take(2) | ||||||
|  | |||||||
| @ -5,7 +5,7 @@ export const tool = defineTool({ | |||||||
|   name: 'UUIDs v4 generator', |   name: 'UUIDs v4 generator', | ||||||
|   path: '/uuid-generator', |   path: '/uuid-generator', | ||||||
|   description: |   description: | ||||||
|     'A universally unique identifier (UUID) is a 128-bit number used to identify information in computer systems. The number of possible UUIDs is 16^32, which is 2^128 or about 3.4x10^38 (which is a lot !).', |     'A Universally Unique Identifier (UUID) is a 128-bit number used to identify information in computer systems. The number of possible UUIDs is 16^32, which is 2^128 or about 3.4x10^38 (which is a lot!).', | ||||||
|   keywords: ['uuid', 'v4', 'random', 'id', 'alphanumeric', 'identity', 'token', 'string', 'identifier', 'unique'], |   keywords: ['uuid', 'v4', 'random', 'id', 'alphanumeric', 'identity', 'token', 'string', 'identifier', 'unique'], | ||||||
|   component: () => import('./uuid-generator.vue'), |   component: () => import('./uuid-generator.vue'), | ||||||
|   icon: Fingerprint, |   icon: Fingerprint, | ||||||
|  | |||||||
							
								
								
									
										13
									
								
								src/tools/wifi-qr-code-generator/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/tools/wifi-qr-code-generator/index.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,13 @@ | |||||||
|  | import { Qrcode } from '@vicons/tabler'; | ||||||
|  | import { defineTool } from '../tool'; | ||||||
|  | 
 | ||||||
|  | export const tool = defineTool({ | ||||||
|  |   name: 'WiFi QR Code generator', | ||||||
|  |   path: '/wifi-qrcode-generator', | ||||||
|  |   description: | ||||||
|  |     'Generate and download QR-codes for quick connections to WiFi networks.', | ||||||
|  |   keywords: ['qr', 'code', 'generator', 'square', 'color', 'link', 'low', 'medium', 'quartile', 'high', 'transparent', 'wifi'], | ||||||
|  |   component: () => import('./wifi-qr-code-generator.vue'), | ||||||
|  |   icon: Qrcode, | ||||||
|  |   createdAt: new Date('2023-09-06'), | ||||||
|  | }); | ||||||
							
								
								
									
										146
									
								
								src/tools/wifi-qr-code-generator/useQRCode.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										146
									
								
								src/tools/wifi-qr-code-generator/useQRCode.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,146 @@ | |||||||
|  | import { type MaybeRef, get } from '@vueuse/core'; | ||||||
|  | import QRCode, { type QRCodeToDataURLOptions } from 'qrcode'; | ||||||
|  | import { isRef, ref, watch } from 'vue'; | ||||||
|  | 
 | ||||||
|  | export const wifiEncryptions = ['WEP', 'WPA', 'nopass', 'WPA2-EAP'] as const; | ||||||
|  | export type WifiEncryption = typeof wifiEncryptions[number]; | ||||||
|  | 
 | ||||||
|  | // @see https://en.wikipedia.org/wiki/Extensible_Authentication_Protocol
 | ||||||
|  | // for a list of available EAP methods. There are a lot (40!) of them.
 | ||||||
|  | export const EAPMethods = [ | ||||||
|  |   'MD5', | ||||||
|  |   'POTP', | ||||||
|  |   'GTC', | ||||||
|  |   'TLS', | ||||||
|  |   'IKEv2', | ||||||
|  |   'SIM', | ||||||
|  |   'AKA', | ||||||
|  |   'AKA\'', | ||||||
|  |   'TTLS', | ||||||
|  |   'PWD', | ||||||
|  |   'LEAP', | ||||||
|  |   'PSK', | ||||||
|  |   'FAST', | ||||||
|  |   'TEAP', | ||||||
|  |   'EKE', | ||||||
|  |   'NOOB', | ||||||
|  |   'PEAP', | ||||||
|  | ] as const; | ||||||
|  | export type EAPMethod = typeof EAPMethods[number]; | ||||||
|  | 
 | ||||||
|  | export const EAPPhase2Methods = [ | ||||||
|  |   'None', | ||||||
|  |   'MSCHAPV2', | ||||||
|  | ] as const; | ||||||
|  | export type EAPPhase2Method = typeof EAPPhase2Methods[number]; | ||||||
|  | 
 | ||||||
|  | interface IWifiQRCodeOptions { | ||||||
|  |   ssid: MaybeRef<string> | ||||||
|  |   password: MaybeRef<string> | ||||||
|  |   eapMethod: MaybeRef<EAPMethod> | ||||||
|  |   isHiddenSSID: MaybeRef<boolean> | ||||||
|  |   eapAnonymous: MaybeRef<boolean> | ||||||
|  |   eapIdentity: MaybeRef<string> | ||||||
|  |   eapPhase2Method: MaybeRef<EAPPhase2Method> | ||||||
|  |   color: { foreground: MaybeRef<string>; background: MaybeRef<string> } | ||||||
|  |   options?: QRCodeToDataURLOptions | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | interface GetQrCodeTextOptions { | ||||||
|  |   ssid: string | ||||||
|  |   password: string | ||||||
|  |   encryption: WifiEncryption | ||||||
|  |   eapMethod: EAPMethod | ||||||
|  |   isHiddenSSID: boolean | ||||||
|  |   eapAnonymous: boolean | ||||||
|  |   eapIdentity: string | ||||||
|  |   eapPhase2Method: EAPPhase2Method | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function escapeString(str: string) { | ||||||
|  |   // replaces \, ;, ,, " and : with the same character preceded by a backslash
 | ||||||
|  |   return str.replace(/([\\;,:"])/g, '\\$1'); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function getQrCodeText(options: GetQrCodeTextOptions): string | null { | ||||||
|  |   const { ssid, password, encryption, eapMethod, isHiddenSSID, eapAnonymous, eapIdentity, eapPhase2Method } = options; | ||||||
|  |   if (!ssid) { | ||||||
|  |     return null; | ||||||
|  |   } | ||||||
|  |   if (encryption === 'nopass') { | ||||||
|  |     return `WIFI:S:${escapeString(ssid)};;`; // type can be omitted in that case, and password is not needed, makes the QR Code smaller
 | ||||||
|  |   } | ||||||
|  |   if (encryption !== 'WPA2-EAP' && password) { | ||||||
|  |     // EAP has a lot of options, so we'll handle it separately
 | ||||||
|  |     // WPA and WEP are pretty simple though.
 | ||||||
|  |     return `WIFI:S:${escapeString(ssid)};T:${encryption};P:${escapeString(password)};${isHiddenSSID ? 'H:true' : ''};`; | ||||||
|  |   } | ||||||
|  |   if (encryption === 'WPA2-EAP' && password && eapMethod) { | ||||||
|  |     // WPA2-EAP string is a lot more complex, first off, we drop the text if there is no identity, and it's not anonymous.
 | ||||||
|  |     if (!eapIdentity && !eapAnonymous) { | ||||||
|  |       return null; | ||||||
|  |     } | ||||||
|  |     // From reading, I could only find that a phase 2 is required for the PEAP method, I may be wrong though, I didn't read the whole spec.
 | ||||||
|  |     if (eapMethod === 'PEAP' && !eapPhase2Method) { | ||||||
|  |       return null; | ||||||
|  |     } | ||||||
|  |     // The string is built in the following order:
 | ||||||
|  |     // 1. SSID
 | ||||||
|  |     // 2. Authentication type
 | ||||||
|  |     // 3. Password
 | ||||||
|  |     // 4. EAP method
 | ||||||
|  |     // 5. EAP phase 2 method
 | ||||||
|  |     // 6. Identity or anonymous if checked
 | ||||||
|  |     // 7. Hidden SSID if checked
 | ||||||
|  |     const identity = eapAnonymous ? 'A:anon' : `I:${escapeString(eapIdentity)}`; | ||||||
|  |     const phase2 = eapPhase2Method !== 'None' ? `PH2:${eapPhase2Method};` : ''; | ||||||
|  |     return `WIFI:S:${escapeString(ssid)};T:WPA2-EAP;P:${escapeString(password)};E:${eapMethod};${phase2}${identity};${isHiddenSSID ? 'H:true' : ''};`; | ||||||
|  |   } | ||||||
|  |   return null; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export function useWifiQRCode({ | ||||||
|  |   ssid, | ||||||
|  |   password, | ||||||
|  |   eapMethod, | ||||||
|  |   isHiddenSSID, | ||||||
|  |   eapAnonymous, | ||||||
|  |   eapIdentity, | ||||||
|  |   eapPhase2Method, | ||||||
|  |   color: { background, foreground }, | ||||||
|  |   options, | ||||||
|  | }: IWifiQRCodeOptions) { | ||||||
|  |   const qrcode = ref(''); | ||||||
|  |   const encryption = ref<WifiEncryption>('WPA'); | ||||||
|  | 
 | ||||||
|  |   watch( | ||||||
|  |     [ssid, password, encryption, eapMethod, isHiddenSSID, eapAnonymous, eapIdentity, eapPhase2Method, background, foreground].filter(isRef), | ||||||
|  |     async () => { | ||||||
|  |       // @see https://github.com/zxing/zxing/wiki/Barcode-Contents#wi-fi-network-config-android-ios-11
 | ||||||
|  |       // This is the full spec, there's quite a bit of logic to generate the string embeddedin the QR code.
 | ||||||
|  |       const text = getQrCodeText({ | ||||||
|  |         ssid: get(ssid), | ||||||
|  |         password: get(password), | ||||||
|  |         encryption: get(encryption), | ||||||
|  |         eapMethod: get(eapMethod), | ||||||
|  |         isHiddenSSID: get(isHiddenSSID), | ||||||
|  |         eapAnonymous: get(eapAnonymous), | ||||||
|  |         eapIdentity: get(eapIdentity), | ||||||
|  |         eapPhase2Method: get(eapPhase2Method), | ||||||
|  |       }); | ||||||
|  |       if (text) { | ||||||
|  |         qrcode.value = await QRCode.toDataURL(get(text).trim(), { | ||||||
|  |           color: { | ||||||
|  |             dark: get(foreground), | ||||||
|  |             light: get(background), | ||||||
|  |             ...options?.color, | ||||||
|  |           }, | ||||||
|  |           errorCorrectionLevel: 'M', | ||||||
|  |           ...options, | ||||||
|  |         }); | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     { immediate: true }, | ||||||
|  |   ); | ||||||
|  |   return { qrcode, encryption }; | ||||||
|  | } | ||||||
							
								
								
									
										153
									
								
								src/tools/wifi-qr-code-generator/wifi-qr-code-generator.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										153
									
								
								src/tools/wifi-qr-code-generator/wifi-qr-code-generator.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,153 @@ | |||||||
|  | <script setup lang="ts"> | ||||||
|  | import { | ||||||
|  |   EAPMethods, | ||||||
|  |   EAPPhase2Methods, | ||||||
|  |   useWifiQRCode, | ||||||
|  | } from './useQRCode'; | ||||||
|  | import { useDownloadFileFromBase64 } from '@/composable/downloadBase64'; | ||||||
|  | 
 | ||||||
|  | const foreground = ref('#000000ff'); | ||||||
|  | const background = ref('#ffffffff'); | ||||||
|  | 
 | ||||||
|  | const ssid = ref(); | ||||||
|  | const password = ref(); | ||||||
|  | const eapMethod = ref(); | ||||||
|  | const isHiddenSSID = ref(false); | ||||||
|  | const eapAnonymous = ref(false); | ||||||
|  | const eapIdentity = ref(); | ||||||
|  | const eapPhase2Method = ref(); | ||||||
|  | 
 | ||||||
|  | const { qrcode, encryption } = useWifiQRCode({ | ||||||
|  |   ssid, | ||||||
|  |   password, | ||||||
|  |   eapMethod, | ||||||
|  |   isHiddenSSID, | ||||||
|  |   eapAnonymous, | ||||||
|  |   eapIdentity, | ||||||
|  |   eapPhase2Method, | ||||||
|  |   color: { | ||||||
|  |     background, | ||||||
|  |     foreground, | ||||||
|  |   }, | ||||||
|  |   options: { width: 1024 }, | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | const { download } = useDownloadFileFromBase64({ source: qrcode, filename: 'qr-code.png' }); | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <template> | ||||||
|  |   <c-card> | ||||||
|  |     <div grid grid-cols-1 gap-12> | ||||||
|  |       <div> | ||||||
|  |         <c-select | ||||||
|  |           v-model:value="encryption" | ||||||
|  |           mb-4 | ||||||
|  |           label="Encryption method" | ||||||
|  |           default-value="WPA" | ||||||
|  |           label-position="left" | ||||||
|  |           label-width="130px" | ||||||
|  |           label-align="right" | ||||||
|  |           :options="[ | ||||||
|  |             { | ||||||
|  |               label: 'No password', | ||||||
|  |               value: 'nopass', | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |               label: 'WPA/WPA2', | ||||||
|  |               value: 'WPA', | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |               label: 'WEP', | ||||||
|  |               value: 'WEP', | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |               label: 'WPA2-EAP', | ||||||
|  |               value: 'WPA2-EAP', | ||||||
|  |             }, | ||||||
|  |           ]" | ||||||
|  |         /> | ||||||
|  |         <div class="mb-6 flex flex-row items-center gap-2"> | ||||||
|  |           <c-input-text | ||||||
|  |             v-model:value="ssid" | ||||||
|  |             label-position="left" | ||||||
|  |             label-width="130px" | ||||||
|  |             label-align="right" | ||||||
|  |             label="SSID:" | ||||||
|  |             rows="1" | ||||||
|  |             autosize | ||||||
|  |             placeholder="Your WiFi SSID..." | ||||||
|  |             mb-6 | ||||||
|  |           /> | ||||||
|  |           <n-checkbox v-model:checked="isHiddenSSID"> | ||||||
|  |             Hidden SSID | ||||||
|  |           </n-checkbox> | ||||||
|  |         </div> | ||||||
|  |         <c-input-text | ||||||
|  |           v-if="encryption !== 'nopass'" | ||||||
|  |           v-model:value="password" | ||||||
|  |           label-position="left" | ||||||
|  |           label-width="130px" | ||||||
|  |           label-align="right" | ||||||
|  |           label="Password:" | ||||||
|  |           rows="1" | ||||||
|  |           autosize | ||||||
|  |           type="password" | ||||||
|  |           placeholder="Your WiFi Password..." | ||||||
|  |           mb-6 | ||||||
|  |         /> | ||||||
|  |         <c-select | ||||||
|  |           v-if="encryption === 'WPA2-EAP'" | ||||||
|  |           v-model:value="eapMethod" | ||||||
|  |           label="EAP method" | ||||||
|  |           label-position="left" | ||||||
|  |           label-width="130px" | ||||||
|  |           label-align="right" | ||||||
|  |           :options="EAPMethods.map((method) => ({ label: method, value: method }))" | ||||||
|  |           searchable mb-4 | ||||||
|  |         /> | ||||||
|  |         <div v-if="encryption === 'WPA2-EAP'" class="mb-6 flex flex-row items-center gap-2"> | ||||||
|  |           <c-input-text | ||||||
|  |             v-model:value="eapIdentity" | ||||||
|  |             label-position="left" | ||||||
|  |             label-width="130px" | ||||||
|  |             label-align="right" | ||||||
|  |             label="Identity:" | ||||||
|  |             rows="1" | ||||||
|  |             autosize | ||||||
|  |             placeholder="Your EAP Identity..." | ||||||
|  |             mb-6 | ||||||
|  |           /> | ||||||
|  |           <n-checkbox v-model:checked="eapAnonymous"> | ||||||
|  |             Anonymous? | ||||||
|  |           </n-checkbox> | ||||||
|  |         </div> | ||||||
|  |         <c-select | ||||||
|  |           v-if="encryption === 'WPA2-EAP'" | ||||||
|  |           v-model:value="eapPhase2Method" | ||||||
|  |           label="EAP Phase 2 method" | ||||||
|  |           label-position="left" | ||||||
|  |           label-width="130px" | ||||||
|  |           label-align="right" | ||||||
|  |           :options="EAPPhase2Methods.map((method) => ({ label: method, value: method }))" | ||||||
|  |           searchable mb-4 | ||||||
|  |         /> | ||||||
|  |         <n-form label-width="130" label-placement="left"> | ||||||
|  |           <n-form-item label="Foreground color:"> | ||||||
|  |             <n-color-picker v-model:value="foreground" :modes="['hex']" /> | ||||||
|  |           </n-form-item> | ||||||
|  |           <n-form-item label="Background color:"> | ||||||
|  |             <n-color-picker v-model:value="background" :modes="['hex']" /> | ||||||
|  |           </n-form-item> | ||||||
|  |         </n-form> | ||||||
|  |       </div> | ||||||
|  |       <div v-if="qrcode"> | ||||||
|  |         <div flex flex-col items-center gap-3> | ||||||
|  |           <img alt="wifi-qrcode" :src="qrcode" width="200"> | ||||||
|  |           <c-button @click="download"> | ||||||
|  |             Download qr-code | ||||||
|  |           </c-button> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|  |   </c-card> | ||||||
|  | </template> | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user