diff --git a/locales/en-US.json b/locales/en-US.json index 93b0e59927..c60a2d3f35 100644 --- a/locales/en-US.json +++ b/locales/en-US.json @@ -244,7 +244,10 @@ }, "label": "Profile" }, - "select_a_settings": "Select a settings" + "select_a_settings": "Select a settings", + "users": { + "label": "Logged in users" + } }, "state": { "edited": "(Edited)", diff --git a/package.json b/package.json index bfb4bcf82d..fc7d4aa756 100644 --- a/package.json +++ b/package.json @@ -70,6 +70,7 @@ "@iconify-json/twemoji": "^1.1.7", "@nuxtjs/i18n": "^8.0.0-beta.7", "@pinia/nuxt": "^0.4.6", + "@types/file-saver": "^2.0.5", "@types/fnando__sparkline": "^0.3.4", "@types/fs-extra": "^9.0.13", "@types/js-yaml": "^4.0.5", @@ -83,6 +84,7 @@ "emoji-mart": "^5.4.0", "eslint": "^8.30.0", "esno": "^0.16.3", + "file-saver": "^2.0.5", "fs-extra": "^11.1.0", "jsdom": "^20.0.3", "lint-staged": "^13.1.0", diff --git a/pages/settings.vue b/pages/settings.vue index aae8b41984..ae3a5401d7 100644 --- a/pages/settings.vue +++ b/pages/settings.vue @@ -43,6 +43,12 @@ const isRootPath = computedEager(() => route.name === 'settings') :text="$t('settings.preferences.label')" to="/settings/preferences" /> + +import type { UserLogin } from '~/types' + +const { lg } = breakpoints + +const loggedInUsers = useUsers() + +async function exportTokens() { + if (!confirm('Please aware that the tokens represent the **full access** to your accounts, and should be treated as sensitive information. Are you sure you want to export the tokens?')) + return + + const { saveAs } = await import('file-saver') + const data = { + '/': 'Generated by Elk, you can import it to Elk to restore the tokens.', + '//': 'This file represents the **full access** to your accounts, and should be treated as sensitive information.', + 'version': 1, + 'origin': location.origin, + 'users': loggedInUsers.value, + } + const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json;charset=utf-8' }) + saveAs(blob, `elk-users-tokens-${new Date().toISOString().slice(0, 10)}.json`) +} + +async function importTokens() { + const input = document.createElement('input') as HTMLInputElement + input.type = 'file' + input.accept = 'application/json' + input.multiple = false + + input.addEventListener('change', async (e) => { + const file = (e.target as any)?.files?.[0] as File + if (!file) + return + + try { + const content = await file.text() + const data = JSON.parse(content) + if (data.version !== 1) + throw new Error('Invalid version') + const users = data.users as UserLogin[] + const newUsers: UserLogin[] = [] + for (const user of users) { + if (loggedInUsers.value.some(u => u.server === user.server && u.account.id === user.account.id)) + continue + newUsers.push(user) + } + if (newUsers.length === 0) { + alert('No new users found') + return + } + if (!confirm(`Found ${newUsers.length} new users, are you sure you want to import them?`)) + return + loggedInUsers.value = [...loggedInUsers.value, ...newUsers] + } + catch (e) { + console.error(e) + alert('Invalid Elk tokens file') + } + }) + + input.click() +} + + + diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4e470cb74b..542b7caf45 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -22,6 +22,7 @@ specifiers: '@tiptap/starter-kit': 2.0.0-beta.204 '@tiptap/suggestion': 2.0.0-beta.204 '@tiptap/vue-3': 2.0.0-beta.204 + '@types/file-saver': ^2.0.5 '@types/fnando__sparkline': ^0.3.4 '@types/fs-extra': ^9.0.13 '@types/js-yaml': ^4.0.5 @@ -39,6 +40,7 @@ specifiers: emoji-mart: ^5.4.0 eslint: ^8.30.0 esno: ^0.16.3 + file-saver: ^2.0.5 floating-vue: 2.0.0-beta.20 focus-trap: ^7.2.0 form-data: ^4.0.0 @@ -122,6 +124,7 @@ devDependencies: '@iconify-json/twemoji': 1.1.7 '@nuxtjs/i18n': 8.0.0-beta.7 '@pinia/nuxt': 0.4.6_typescript@4.9.4 + '@types/file-saver': 2.0.5 '@types/fnando__sparkline': 0.3.4 '@types/fs-extra': 9.0.13 '@types/js-yaml': 4.0.5 @@ -135,6 +138,7 @@ devDependencies: emoji-mart: 5.4.0 eslint: 8.30.0 esno: 0.16.3 + file-saver: 2.0.5 fs-extra: 11.1.0 jsdom: 20.0.3 lint-staged: 13.1.0 @@ -2652,6 +2656,10 @@ packages: resolution: {integrity: sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==} dev: true + /@types/file-saver/2.0.5: + resolution: {integrity: sha512-zv9kNf3keYegP5oThGLaPk8E081DFDuwfqjtiTzm6PoxChdJ1raSuADf2YGCVIyrSynLrgc8JWv296s7Q7pQSQ==} + dev: true + /@types/fnando__sparkline/0.3.4: resolution: {integrity: sha512-FWU1zw7CVJYVeDk77FGphTUabfPims4F/Yq+WFB0Gh647lLtiXHWn8vpfT95Fl65IsNBDOhEbxJdhmERMGubNQ==} dev: true @@ -5692,6 +5700,10 @@ packages: flat-cache: 3.0.4 dev: true + /file-saver/2.0.5: + resolution: {integrity: sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==} + dev: true + /file-uri-to-path/1.0.0: resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} dev: true