diff --git a/backend/storage/preferences.go b/backend/storage/preferences.go index 8ca03a0..261bb79 100644 --- a/backend/storage/preferences.go +++ b/backend/storage/preferences.go @@ -27,8 +27,8 @@ func (p *PreferencesStorage) DefaultPreferences() types.Preferences { } func (p *PreferencesStorage) getPreferences() (ret types.Preferences) { - b, err := p.storage.Load() ret = p.DefaultPreferences() + b, err := p.storage.Load() if err != nil { return } diff --git a/backend/types/preferences.go b/backend/types/preferences.go index 846f15c..40d5430 100644 --- a/backend/types/preferences.go +++ b/backend/types/preferences.go @@ -24,6 +24,7 @@ func NewPreferences() Preferences { ScanSize: consts.DEFAULT_SCAN_SIZE, KeyIconStyle: 0, CheckUpdate: true, + AllowTrack: true, }, Editor: PreferencesEditor{ FontSize: consts.DEFAULT_FONT_SIZE, @@ -41,6 +42,7 @@ func NewPreferences() Preferences { } type PreferencesBehavior struct { + Welcomed bool `json:"welcomed" yaml:"welcomed"` AsideWidth int `json:"asideWidth" yaml:"aside_width"` WindowWidth int `json:"windowWidth" yaml:"window_width"` WindowHeight int `json:"windowHeight" yaml:"window_height"` @@ -61,6 +63,7 @@ type PreferencesGeneral struct { UseSysProxyHttp bool `json:"useSysProxyHttp" yaml:"use_sys_proxy_http,omitempty"` CheckUpdate bool `json:"checkUpdate" yaml:"check_update"` SkipVersion string `json:"skipVersion" yaml:"skip_version,omitempty"` + AllowTrack bool `json:"allowTrack" yaml:"allow_track"` } type PreferencesEditor struct { diff --git a/frontend/index.html b/frontend/index.html index b53c808..9d89d63 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -8,8 +8,6 @@
- diff --git a/frontend/src/App.vue b/frontend/src/App.vue index ce4dada..4d0f90c 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -8,11 +8,11 @@ import AddFieldsDialog from './components/dialogs/AddFieldsDialog.vue' import AppContent from './AppContent.vue' import GroupDialog from './components/dialogs/GroupDialog.vue' import DeleteKeyDialog from './components/dialogs/DeleteKeyDialog.vue' -import { onMounted, ref, watch } from 'vue' +import { h, onMounted, ref, watch } from 'vue' import usePreferencesStore from './stores/preferences.js' import useConnectionStore from './stores/connections.js' import { useI18n } from 'vue-i18n' -import { darkTheme } from 'naive-ui' +import { darkTheme, NButton, NSpace } from 'naive-ui' import KeyFilterDialog from './components/dialogs/KeyFilterDialog.vue' import { WindowSetDarkTheme, WindowSetLightTheme } from 'wailsjs/runtime/runtime.js' import { darkThemeOverrides, themeOverrides } from '@/utils/theme.js' @@ -22,6 +22,8 @@ import ExportKeyDialog from '@/components/dialogs/ExportKeyDialog.vue' import ImportKeyDialog from '@/components/dialogs/ImportKeyDialog.vue' import { Info } from 'wailsjs/go/services/systemService.js' import DecoderDialog from '@/components/dialogs/DecoderDialog.vue' +import { enableTrack, loadModule, trackEvent } from '@/utils/analytics.js' +import { endsWith } from 'lodash' const prefStore = usePreferencesStore() const connectionStore = useConnectionStore() @@ -36,10 +38,64 @@ onMounted(async () => { if (prefStore.autoCheckUpdate) { prefStore.checkForUpdate() } - Info().then(({ data }) => { - // const {os, arch, version} = data - umami && umami.track('startup', data) + loadModule(prefStore.general.allowTrack !== false).then(() => { + Info().then(({ data }) => { + if (endsWith(data.version, 'dev')) { + enableTrack(false) + } else { + trackEvent('startup', data, true) + } + }) }) + + // show greetings and user behavior tracking statements + if (!!!prefStore.behavior.welcomed) { + const n = $notification.show({ + title: i18n.t('dialogue.welcome.title'), + content: i18n.t('preferences.general.track_tip') + '\n\n' + i18n.t('dialogue.welcome.content'), + // duration: 5000, + keepAliveOnHover: true, + closable: false, + meta: ' ', + action: () => + h( + NSpace, + {}, + { + default: () => [ + h( + NButton, + { + secondary: true, + type: 'tertiary', + onClick: () => { + prefStore.setAsWelcomed(false) + n.destroy() + }, + }, + { + default: () => i18n.t('dialogue.welcome.reject'), + }, + ), + h( + NButton, + { + secondary: true, + type: 'primary', + onClick: () => { + prefStore.setAsWelcomed(true) + n.destroy() + }, + }, + { + default: () => i18n.t('dialogue.welcome.accept'), + }, + ), + ], + }, + ), + }) + } } finally { initializing.value = false } diff --git a/frontend/src/components/dialogs/PreferencesDialog.vue b/frontend/src/components/dialogs/PreferencesDialog.vue index d936d9e..f2c239d 100644 --- a/frontend/src/components/dialogs/PreferencesDialog.vue +++ b/frontend/src/components/dialogs/PreferencesDialog.vue @@ -258,6 +258,19 @@ const onClose = () => { {{ $t('preferences.general.auto_check_update') }} + + + {{ $t('preferences.general.allow_track') }} + + +
+ {{ $t('preferences.general.track_tip') }} +
+
+
+
diff --git a/frontend/src/components/sidebar/Ribbon.vue b/frontend/src/components/sidebar/Ribbon.vue index 1de4a5c..bbbd64e 100644 --- a/frontend/src/components/sidebar/Ribbon.vue +++ b/frontend/src/components/sidebar/Ribbon.vue @@ -17,6 +17,7 @@ import { useRender } from '@/utils/render.js' import wechatUrl from '@/assets/images/wechat_official.png' import QRCode from '@/components/icons/QRCode.vue' import Twitter from '@/components/icons/Twitter.vue' +import { trackEvent } from '@/utils/analytics.js' const themeVars = useThemeVars() const render = useRender() @@ -122,17 +123,17 @@ const onSelectPreferenceMenu = (key) => { } const openWechatOfficial = () => { - umami && umami.track('open', { target: 'wechat_official' }) + trackEvent('open', { target: 'wechat_official' }) showWechat.value = true } const openX = () => { - umami && umami.track('open', { target: 'x' }) + trackEvent('open', { target: 'x' }) BrowserOpenURL('https://twitter.com/LykinHuang') } const openGithub = () => { - umami && umami.track('open', { target: 'github' }) + trackEvent('open', { target: 'github' }) BrowserOpenURL('https://github.com/tiny-craft/tiny-rdm') } diff --git a/frontend/src/langs/en-us.json b/frontend/src/langs/en-us.json index 68f9039..acc1357 100644 --- a/frontend/src/langs/en-us.json +++ b/frontend/src/langs/en-us.json @@ -46,7 +46,10 @@ "key_icon_style2": "Dot", "key_icon_style3": "Common", "update": "Update", - "auto_check_update": "Auto check for updates" + "auto_check_update": "Auto check for updates", + "privacy": "Privacy", + "allow_track": "Allows anonymous data to be collected", + "track_tip": "In order to provide a better user experience, Tiny RDM collects some anonymous data to help optimize the software and improve the user experience, please rest assured that this does not involve your personal privacy information." }, "editor": { "name": "Editor", @@ -382,6 +385,12 @@ "later": "Later", "skip": "Skip This Version" }, + "welcome": { + "title": "Welcome to Tiny RDM!", + "content": "If you have any concerns, you can turn off this data collection feature at any time by going to Preferences. If you have any questions, feel free to contact the developer. I hope Tiny RDM can become your helpful assistant!", + "accept": "Help Improve", + "reject": "Reject" + }, "about": { "source": "Source Code", "website": "Official Website" diff --git a/frontend/src/langs/es-es.json b/frontend/src/langs/es-es.json index de442bf..91ad7f8 100644 --- a/frontend/src/langs/es-es.json +++ b/frontend/src/langs/es-es.json @@ -46,7 +46,10 @@ "key_icon_style2": "Punto", "key_icon_style3": "Común", "update": "Actualizar", - "auto_check_update": "Buscar actualizaciones automáticamente" + "auto_check_update": "Buscar actualizaciones automáticamente", + "privacy": "Política de Privacidad", + "allow_track":"Permitir recopilar datos anónimos", + "track_tip": "Con el fin de brindar una mejor experiencia de usuario, Tiny RDM recopila algunos datos anónimos para ayudar a optimizar el software y mejorar la experiencia del usuario. Tenga la seguridad de que esto no involucrará su información de privacidad personal." }, "editor": { "name": "Editor", @@ -382,6 +385,12 @@ "later": "Más tarde", "skip": "Omitir esta versión" }, + "welcome": { + "title": "¡Bienvenido a Tiny RDM!", + "content": "Si tiene alguna inquietud, puede desactivar esta función de recopilación de datos en cualquier momento yendo a Preferencias. Si tiene alguna pregunta, no dude en ponerse en contacto con el desarrollador. ¡Espero que Tiny RDM pueda ser un buen asistente para usted!", + "accept": "Ayudar a Mejorar", + "reject": "Rechazar" + }, "about": { "source": "Código fuente", "website": "Sitio web oficial" diff --git a/frontend/src/langs/fr-fr.json b/frontend/src/langs/fr-fr.json index af166dc..c411379 100644 --- a/frontend/src/langs/fr-fr.json +++ b/frontend/src/langs/fr-fr.json @@ -46,7 +46,10 @@ "key_icon_style2": "Point", "key_icon_style3": "Commun", "update": "Mise à jour", - "auto_check_update": "Vérifier automatiquement les mises à jour" + "auto_check_update": "Vérifier automatiquement les mises à jour", + "privacy": "Politique de confidentialité", + "allow_track":"Autoriser la collecte de données anonymes", + "track_tip":"Afin d'offrir une meilleure expérience utilisateur, Tiny RDM collecte certaines données anonymes pour aider à optimiser le logiciel et améliorer l'expérience utilisateur. Soyez assuré que cela n'implique pas vos informations personnelles et privées." }, "editor": { "name": "Éditeur", @@ -382,6 +385,12 @@ "later": "Plus tard", "skip": "Ignorer cette version" }, + "welcome": { + "title": "Bienvenue dans Tiny RDM!", + "content": "Si vous avez des inquiétudes, vous pouvez désactiver cette fonction de collecte de données à tout moment en allant dans les Préférences. Si vous avez des questions, n'hésitez pas à contacter le développeur. J'espère que Tiny RDM pourra être un bon assistant pour vous!", + "accept":"Aider à Améliorer", + "reject":"Rejeter" + }, "about": { "source": "Code source", "website": "Site officiel" diff --git a/frontend/src/langs/ja-jp.json b/frontend/src/langs/ja-jp.json index a675351..b7d185b 100644 --- a/frontend/src/langs/ja-jp.json +++ b/frontend/src/langs/ja-jp.json @@ -46,7 +46,10 @@ "key_icon_style2": "ドットタイプ", "key_icon_style3": "共通アイコン", "update": "更新", - "auto_check_update": "自動でアップデートを確認" + "auto_check_update": "自動でアップデートを確認", + "privacy": "プライバシーポリシー", + "allow_track":"匿名データの収集を許可する", + "track_tip":"ユーザーエクスペリエンスを改善するために、Tiny RDMは一部の匿名データを収集し、ソフトウェアの最適化とユーザーエクスペリエンスの向上に役立てています。個人プライバシー情報は含まれませんのでご安心ください。" }, "editor": { "name": "エディター", @@ -382,6 +385,12 @@ "later": "後で", "skip": "このバージョンをスキップ" }, + "welcome": { + "title": "Tiny RDMをご利用いただきありがとうございます!", + "content": "ご不安な点がありましたら、いつでも「設定」から当該機能をオフにすることができます。ご不明な点がございましたら、開発者までお問い合わせください。Tiny RDMがお役に立てることを願っております。", + "accept":"改善の支援", + "reject":"拒否する" + }, "about": { "source": "ソースコード", "website": "公式ウェブサイト" diff --git a/frontend/src/langs/ko-kr.json b/frontend/src/langs/ko-kr.json index 9d645b1..03f2ddc 100644 --- a/frontend/src/langs/ko-kr.json +++ b/frontend/src/langs/ko-kr.json @@ -46,7 +46,10 @@ "key_icon_style2": "점", "key_icon_style3": "일반", "update": "업데이트", - "auto_check_update": "자동 업데이트 확인" + "auto_check_update": "자동 업데이트 확인", + "privacy": "개인정보 보호정책", + "allow_track":"익명 데이터 수집을 허용합니다", + "track_tip":"Tiny RDM은 사용자 경험을 개선하기 위해 일부 익명 데이터를 수집하여 소프트웨어를 최적화하고 사용자 경험을 향상시키는 데 도움을 줍니다. 이는 귀하의 개인 정보와는 무관함을 안심하셔도 됩니다." }, "editor": { "name": "편집기", @@ -381,6 +384,12 @@ "later": "나중에", "skip": "이 버전 건너뛰기" }, + "welcome": { + "title": "Tiny RDM을 사용해 주셔서 감사합니다!", + "content": "걱정되는 부분이 있다면 언제든지 '설정'에서 이 기능을 끌 수 있습니다. 궁금한 점이 있으면 개발자에게 문의하세요. Tiny RDM이 여러분께 좋은 도우미가 되기를 바랍니다!", + "accept":"개선 지원", + "reject":"거절" + }, "about": { "source": "소스 코드", "website": "공식 웹사이트" diff --git a/frontend/src/langs/pt-br.json b/frontend/src/langs/pt-br.json index 4cb8587..7ca9fa2 100644 --- a/frontend/src/langs/pt-br.json +++ b/frontend/src/langs/pt-br.json @@ -46,7 +46,10 @@ "key_icon_style2": "Ponto", "key_icon_style3": "Comum", "update": "Atualizar", - "auto_check_update": "Verificar atualizações automaticamente" + "auto_check_update": "Verificar atualizações automaticamente", + "privacy": "Política de Privacidade", + "allow_track":"Permitir a coleta de dados anônimos", + "track_tip":"Para fornecer uma melhor experiência ao usuário, o Tiny RDM coleta alguns dados anônimos para ajudar a otimizar o software e melhorar a experiência do usuário. Fique tranquilo, isso não envolve suas informações de privacidade pessoal." }, "editor": { "name": "Editor", @@ -382,6 +385,12 @@ "later": "Depois", "skip": "Ignorar Esta Versão" }, + "welcome": { + "title": "Bem-vindo ao Tiny RDM!", + "content": "Se você tiver alguma preocupação, pode desativar esse recurso de coleta de dados a qualquer momento indo em Preferências. Se tiver alguma dúvida, sinta-se à vontade para entrar em contato com o desenvolvedor. Espero que o Tiny RDM possa se tornar um assistente útil para você!", + "accept":"Help Improve", + "reject":"Reject" + }, "about": { "source": "Código Fonte", "website": "Site Oficial" diff --git a/frontend/src/langs/zh-cn.json b/frontend/src/langs/zh-cn.json index 5f08b37..98df3b5 100644 --- a/frontend/src/langs/zh-cn.json +++ b/frontend/src/langs/zh-cn.json @@ -46,7 +46,10 @@ "key_icon_style2": "圆点类型", "key_icon_style3": "通用图标", "update": "更新", - "auto_check_update": "自动检查更新" + "auto_check_update": "自动检查更新", + "privacy": "隐私策略", + "allow_track": "允许收集匿名数据", + "track_tip": "为了提供更好的用户体验,Tiny RDM会收集一些匿名的数据,以帮助优化软件和改进用户体验,请放心这不会涉及到您的个人隐私信息。" }, "editor": { "name": "编辑器", @@ -382,6 +385,12 @@ "later": "稍后提醒我", "skip": "忽略本次更新" }, + "welcome": { + "title": "欢迎使用Tiny RDM!", + "content": "如果您对此有任何顾虑,可以随时前往\"偏好设置\"中关闭此项数据收集功能。有任何问题可联系开发者,希望Tiny RDM可以成为您的好帮手!", + "accept": "帮助改进", + "reject": "不接受" + }, "about": { "source": "源码地址", "website": "官方网站" diff --git a/frontend/src/langs/zh-tw.json b/frontend/src/langs/zh-tw.json index 4109bde..c6a656c 100644 --- a/frontend/src/langs/zh-tw.json +++ b/frontend/src/langs/zh-tw.json @@ -46,7 +46,10 @@ "key_icon_style2": "點類型", "key_icon_style3": "通用圖示", "update": "更新", - "auto_check_update": "自動檢查更新" + "auto_check_update": "自動檢查更新", + "privacy": "隱私權政策", + "allow_track":"允許收集匿名數據", + "track_tip":"為了提供更好的使用者體驗,Tiny RDM 會收集一些匿名的數據,以幫助最佳化軟體和改進使用者體驗,請放心這不會涉及到您的個人隱私資訊。" }, "editor": { "name": "編輯器", @@ -382,6 +385,12 @@ "later": "稍後提醒我", "skip": "忽略本次更新" }, + "welcome": { + "title": "歡迎使用Tiny RDM!", + "content": "如果您對此有任何顧慮,可以隨時前往\"偏好設定\"中關閉此項數據收集功能。有任何問題可聯繫開發者,希望Tiny RDM可以成為您的好幫手!", + "accept": "幫助改進", + "reject": "不接受" + }, "about": { "source": "源碼地址", "website": "官方網站" diff --git a/frontend/src/stores/preferences.js b/frontend/src/stores/preferences.js index cc724cc..2afc2ef 100644 --- a/frontend/src/stores/preferences.js +++ b/frontend/src/stores/preferences.js @@ -35,6 +35,7 @@ const usePreferencesStore = defineStore('preferences', { */ state: () => ({ behavior: { + welcomed: false, asideWidth: 300, windowWidth: 0, windowHeight: 0, @@ -52,6 +53,7 @@ const usePreferencesStore = defineStore('preferences', { useSysProxyHttp: false, checkUpdate: true, skipVersion: '', + allowTrack: true, }, editor: { font: '', @@ -445,6 +447,12 @@ const usePreferencesStore = defineStore('preferences', { return true }, + setAsWelcomed(acceptTrack) { + this.behavior.welcomed = true + this.general.allowTrack = acceptTrack + this.savePreferences() + }, + async checkForUpdate(manual = false) { let msgRef = null if (manual) { diff --git a/frontend/src/utils/analytics.js b/frontend/src/utils/analytics.js new file mode 100644 index 0000000..7348f27 --- /dev/null +++ b/frontend/src/utils/analytics.js @@ -0,0 +1,46 @@ +let inited = false +let allow = false + +/** + * load umami analytics module + * @param {boolean} allowTrack + * @return {Promise} + */ +export const loadModule = async (allowTrack = true) => { + allow = allowTrack !== false + await new Promise((resolve, reject) => { + const script = document.createElement('script') + script.setAttribute('src', 'https://analytics.tinycraft.cc/script.js') + script.setAttribute('data-website-id', 'ad6de51d-1e27-44a5-958d-319679c56aec') + script.setAttribute('data-cache', 'true') + script.setAttribute('data-auto-track', allowTrack !== false ? 'true' : 'false') + script.onload = () => { + inited = true + resolve() + } + script.onerror = () => { + inited = false + reject() + } + document.body.appendChild(script) + }) +} + +const enable = () => { + return inited && allow && umami +} + +export const enableTrack = (enable) => { + allow = enable +} + +export const trackEvent = async (event, data) => { + if (enable() || event === 'startup') { + umami.track(({ website, language }) => ({ + language, + website, + name: event, + data, + })) + } +}