diff --git a/package-lock.json b/package-lock.json index 46a52e23..a8c3f218 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10705,6 +10705,11 @@ "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", "dev": true }, + "ua-parser-js": { + "version": "0.7.22", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.22.tgz", + "integrity": "sha512-YUxzMjJ5T71w6a8WWVcMGM6YWOTX27rCoIQgLXiWaxqXSx9D7DNjiGWn1aJIRSQ5qr0xuhra77bSIh6voR/46Q==" + }, "uglify-js": { "version": "3.4.10", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.10.tgz", diff --git a/package.json b/package.json index 4a3c49c3..6c541d18 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "framework7-vue": "^5.7.14", "js-cookie": "^2.2.1", "moment": "^2.29.1", + "ua-parser-js": "^0.7.22", "vue": "^2.6.12", "vue-clipboard2": "^0.3.1", "vue-i18n": "^8.22.1", diff --git a/src/Mobile.vue b/src/Mobile.vue index 3d4653d6..1e6f2d67 100644 --- a/src/Mobile.vue +++ b/src/Mobile.vue @@ -107,6 +107,10 @@ body { display: none; } +.list-item-media-valign-middle .item-media { + align-self: normal !important; +} + .lab-list-item-error-info div.item-footer { color: var(--f7-input-error-text-color) } diff --git a/src/consts/icon.js b/src/consts/icon.js index fbb86a23..0b95becd 100644 --- a/src/consts/icon.js +++ b/src/consts/icon.js @@ -65,10 +65,28 @@ const allAccountIcons = { f7Icon: 'bitcoin' } }; +const deviceIcons = { + mobile: { + f7Icon: 'device_phone_portrait' + }, + tablet: { + f7Icon: 'device_tablet_portrait' + }, + wearable: { + f7Icon: 'device_phone_portrait' + }, + desktop: { + f7Icon: 'device_desktop' + }, + tv: { + f7Icon: 'tv' + } +}; export default { allAccountIcons: allAccountIcons, defaultAccountIconId: defaultAccountIconId, defaultAccountIcon: allAccountIcons[defaultAccountIconId], totalAccountIconCount: totalAccountIconCount, + deviceIcons: deviceIcons, }; diff --git a/src/consts/licenses.js b/src/consts/licenses.js index 3399637c..7549c3bc 100644 --- a/src/consts/licenses.js +++ b/src/consts/licenses.js @@ -152,6 +152,12 @@ const licenses = [ copyright: 'Copyright (c) 2018 Copyright 2018 Klaus Hartl, Fagner Brack, GitHub Contributors', url: 'https://github.com/js-cookie/js-cookie', licenseUrl: 'https://github.com/js-cookie/js-cookie/blob/master/LICENSE' + }, + { + name: 'UAParser.js', + copyright: 'Copyright (c) 2012-2019 Faisal Salman ', + url: 'https://github.com/faisalman/ua-parser-js', + licenseUrl: 'https://github.com/faisalman/ua-parser-js/blob/master/license.md' } ]; diff --git a/src/filters/tokenDevice.js b/src/filters/tokenDevice.js new file mode 100644 index 00000000..b093f71c --- /dev/null +++ b/src/filters/tokenDevice.js @@ -0,0 +1,36 @@ +import utils from "../lib/utils.js"; + +export default function (token) { + const ua = utils.parseUserAgent(token.userAgent); + let result = ''; + + if (ua.device.model) { + result = ua.device.model; + } else if (ua.os.name) { + result = ua.os.name; + + if (ua.os.version) { + result += ' ' + ua.os.version; + } + } + + if (ua.browser.name) { + let browserInfo = ua.browser.name; + + if (ua.browser.version) { + browserInfo += ' ' + ua.browser.version; + } + + if (result) { + result += ' (' + browserInfo + ')'; + } else { + result = browserInfo; + } + } + + if (!result) { + return 'Unknown Device'; + } + + return result; +} diff --git a/src/filters/tokenIcon.js b/src/filters/tokenIcon.js new file mode 100644 index 00000000..ea16460b --- /dev/null +++ b/src/filters/tokenIcon.js @@ -0,0 +1,22 @@ +import icons from "../consts/icon.js"; +import utils from "../lib/utils.js"; + +export default function (token) { + const ua = utils.parseUserAgent(token.userAgent); + + if (!ua || !ua.device) { + return icons.deviceIcons.desktop.f7Icon; + } + + if (ua.device.type === 'mobile') { + return icons.deviceIcons.mobile.f7Icon; + } else if (ua.device.type === 'wearable') { + return icons.deviceIcons.wearable.f7Icon; + } else if (ua.device.type === 'tablet') { + return icons.deviceIcons.tablet.f7Icon; + } else if (ua.device.type === 'smarttv') { + return icons.deviceIcons.tv.f7Icon; + } else { + return icons.deviceIcons.desktop.f7Icon; + } +} diff --git a/src/lib/utils.js b/src/lib/utils.js index 3054164d..9be7aa41 100644 --- a/src/lib/utils.js +++ b/src/lib/utils.js @@ -1,4 +1,5 @@ -import accountConstants from '../consts/account.js' +import uaParser from 'ua-parser-js'; +import accountConstants from '../consts/account.js'; function isFunction(val) { return typeof(val) === 'function'; @@ -28,6 +29,26 @@ function isBoolean(val) { return typeof(val) === 'boolean'; } +function parseUserAgent(ua) { + const uaParseRet = uaParser(ua); + + return { + device: { + vendor: uaParseRet.device.vendor, + model: uaParseRet.device.model, + type: uaParseRet.device.type + }, + os: { + name: uaParseRet.os.name, + version: uaParseRet.os.version + }, + browser: { + name: uaParseRet.browser.name, + version: uaParseRet.browser.version + } + }; +} + function getCategorizedAccounts(allAccounts) { const ret = {}; @@ -104,6 +125,7 @@ export default { isString, isNumber, isBoolean, + parseUserAgent, getCategorizedAccounts, getAccountByAccountId, getAllFilteredAccountsBalance, diff --git a/src/locales/en.js b/src/locales/en.js index a8699fc8..f7f5b8dd 100644 --- a/src/locales/en.js +++ b/src/locales/en.js @@ -380,6 +380,7 @@ export default { 'Unable to get session list': 'Unable to get session list', 'Current': 'Current', 'Other Device': 'Other Device', + 'Unknown Device': 'Unknown Device', 'Are you sure you want to logout from this session?': 'Are you sure you want to logout from this session?', 'Unable to logout from this session': 'Unable to logout from this session', 'Are you sure you want to logout all other sessions?': 'Are you sure you want to logout all other sessions?', diff --git a/src/locales/zh_Hans.js b/src/locales/zh_Hans.js index 74e998f9..bb754c16 100644 --- a/src/locales/zh_Hans.js +++ b/src/locales/zh_Hans.js @@ -380,6 +380,7 @@ export default { 'Unable to get session list': '无法获取会话列表', 'Current': '当前', 'Other Device': '其他设备', + 'Unknown Device': '未知设备', 'Are you sure you want to logout from this session?': '您确定是否要退出该会话?', 'Unable to logout from this session': '无法退出该会话', 'Are you sure you want to logout all other sessions?': '您确定是否要退出其他所有会话?', diff --git a/src/mobile-main.js b/src/mobile-main.js index e7b42426..bb7a19c5 100644 --- a/src/mobile-main.js +++ b/src/mobile-main.js @@ -25,6 +25,8 @@ import userstate from './lib/userstate.js'; import utils from './lib/utils.js'; import currencyFilter from './filters/currency.js'; import accountIconFilter from './filters/accountIcon.js'; +import tokenDeviceFilter from './filters/tokenDevice.js'; +import tokenIconFilter from './filters/tokenIcon.js'; import App from './Mobile.vue'; Vue.use(VueI18n); @@ -151,6 +153,8 @@ Vue.prototype.$user = userstate; Vue.filter('currency', (value, currencyCode) => currencyFilter({ i18n }, value, currencyCode)); Vue.filter('accountIcon', (value) => accountIconFilter(value)); +Vue.filter('tokenDevice', (value) => tokenDeviceFilter(value)); +Vue.filter('tokenIcon', (value) => tokenIconFilter(value)); Vue.prototype.$setLanguage(settings.getLanguage() || getDefaultLanguage()); diff --git a/src/views/mobile/users/SessionList.vue b/src/views/mobile/users/SessionList.vue index 9cfbf592..d3c0b5a9 100644 --- a/src/views/mobile/users/SessionList.vue +++ b/src/views/mobile/users/SessionList.vue @@ -11,8 +11,12 @@ - + + + MM/DD/YYYY HH:mm:ss + @@ -20,7 +24,14 @@ - + + + {{ token.createdAt | moment($t('format.datetime.long')) }}