From 04fb25e9939850dc122a92523a8d9f708149b120 Mon Sep 17 00:00:00 2001 From: Yc Chen Date: Fri, 10 Nov 2023 17:58:09 +0800 Subject: [PATCH] i18n 50% --- .gitignore | 1 + common/static/dist/css/style.css | 1 + common/static/dist/css/style.css.map | 1 + .../static/dist/css/{login.css => style.less} | 29 ++- common/static/i18n/i18n-archery.js | 24 ++ common/static/i18n/i18n.min.js | 200 +++++++++++++++++ common/static/i18n/lang/i18n_cn.json | 49 ++++ common/static/i18n/lang/i18n_en.json | 49 ++++ common/static/img/archery.png | Bin 0 -> 3880 bytes .../watermark/{shuiyin.js => watermark.js} | 0 common/templates/2fa.html | 2 +- common/templates/base.html | 131 ++++++----- common/templates/config.html | 6 +- common/templates/dashboard.html | 8 +- common/templates/login.html | 37 +-- common/utils/global_info.py | 11 + package-lock.json | 210 ++++++++++++++++++ package.json | 5 + sql/templates/legacy_login_form.html | 8 +- sql/templates/sqlquery.html | 2 +- sql/templates/sqlworkflow.html | 13 +- src/docker/nginx.conf | 2 +- 22 files changed, 685 insertions(+), 104 deletions(-) create mode 100644 common/static/dist/css/style.css create mode 100644 common/static/dist/css/style.css.map rename common/static/dist/css/{login.css => style.less} (55%) create mode 100644 common/static/i18n/i18n-archery.js create mode 100644 common/static/i18n/i18n.min.js create mode 100644 common/static/i18n/lang/i18n_cn.json create mode 100644 common/static/i18n/lang/i18n_en.json create mode 100644 common/static/img/archery.png rename common/static/watermark/{shuiyin.js => watermark.js} (100%) create mode 100644 package-lock.json create mode 100644 package.json diff --git a/.gitignore b/.gitignore index e713d73c6d..eee5908d9a 100644 --- a/.gitignore +++ b/.gitignore @@ -15,5 +15,6 @@ sonar-project.properties .env local_settings.py src/docker-compose-dev +node_modules .coverage archery_custom \ No newline at end of file diff --git a/common/static/dist/css/style.css b/common/static/dist/css/style.css new file mode 100644 index 0000000000..78a0d6cc1d --- /dev/null +++ b/common/static/dist/css/style.css @@ -0,0 +1 @@ +a{color:#000;font-family:'Inter Var',-apple-system,BlinkMacSystemFont,San Francisco,Segoe UI,Roboto,Helvetica Neue,sans-serif}a:hover{color:#555555}.navbar-brand{padding:0}.navbar-brand>img{height:50px}.user-bottom-div{display:block;text-align:center;background-color:#ddd;position:fixed;bottom:0;width:100%;height:50px;line-height:50px}.lsb-login{display:flex;justify-content:center;align-items:center;height:90vh;margin:0}.login-form-wrapper{background-color:#FFF;border-radius:4px;box-shadow:rgba(0,0,0,0.1) 0 3px 5px;padding:30px 40px;border-top:5px solid #337ab7}/*# sourceMappingURL=style.css.map */ \ No newline at end of file diff --git a/common/static/dist/css/style.css.map b/common/static/dist/css/style.css.map new file mode 100644 index 0000000000..2b5bb54490 --- /dev/null +++ b/common/static/dist/css/style.css.map @@ -0,0 +1 @@ +{"version":3,"sources":["style.less"],"names":[],"mappings":"AACA,EACI,UAAA,CACA,YAAa,qGAEb,CAAC,OACG,cAIR,cACI,UAGJ,aAAc,KACV,YAGJ,iBACI,aAAA,CACA,iBAAA,CACA,qBAAA,CACA,cAAA,CACA,QAAA,CACA,UAAA,CACA,WAAA,CACA,iBAGJ,WACI,YAAA,CACA,sBAAA,CACA,kBAAA,CACA,WAAA,CACA,SAGJ,oBACI,qBAAA,CACA,iBAAA,CACA,oCAAA,CACA,iBAAA,CACA","file":"style.css"} \ No newline at end of file diff --git a/common/static/dist/css/login.css b/common/static/dist/css/style.less similarity index 55% rename from common/static/dist/css/login.css rename to common/static/dist/css/style.less index b3b3d69dd8..a8d13fa554 100644 --- a/common/static/dist/css/login.css +++ b/common/static/dist/css/style.less @@ -1,3 +1,21 @@ + +a { + color: #000; + font-family: 'Inter Var', -apple-system, BlinkMacSystemFont, San Francisco, Segoe UI, Roboto, Helvetica Neue, sans-serif; + + &:hover { + color: #555555; + } +} + +.navbar-brand { + padding: 0; +} + +.navbar-brand > img { + height: 50px; +} + .user-bottom-div { display: block; text-align: center; @@ -20,12 +38,7 @@ .login-form-wrapper { background-color: #FFF; border-radius: 4px; - box-shadow: rgba(0,0,0,0.1) 0 3px 5px; - padding: 30px 10px; + box-shadow: rgba(0, 0, 0, 0.1) 0 3px 5px; + padding: 30px 40px; border-top: 5px solid #337ab7; -} - -.login-form { - margin: 20px; -} - +} \ No newline at end of file diff --git a/common/static/i18n/i18n-archery.js b/common/static/i18n/i18n-archery.js new file mode 100644 index 0000000000..81dd9cfb0c --- /dev/null +++ b/common/static/i18n/i18n-archery.js @@ -0,0 +1,24 @@ +let i18n_default_lang = 'en' +let i18n_default_path = '/static/i18n/lang/' +/*默认语言*/ +i18n("[i18n]", { + defaultLang: i18n_default_lang, // 设置默认语言, + filePath: i18n_default_path, + filePrefix: "i18n_", + fileSuffix: "", + forever:true,// 默认为 true 保存当前语言,设置为 false 后,每次刷新都为cn + get: true, + only: ['value', 'html', 'placeholder', 'title'], // 全局设置i18n-only,默认值:['value', 'html', 'placeholder', 'title'] + callback: function() { + // console.log("i18n is ready."); + } +}); +$(".lang_btn").click(function (){ + console.log(this.getAttribute('lang')) + i18n("[i18n]", { + lang: this.getAttribute('lang'),// 变更语言 + filePath: i18n_default_path, + forever:true, + get: true + }); +}) \ No newline at end of file diff --git a/common/static/i18n/i18n.min.js b/common/static/i18n/i18n.min.js new file mode 100644 index 0000000000..827572d46d --- /dev/null +++ b/common/static/i18n/i18n.min.js @@ -0,0 +1,200 @@ +(function() { + window['i18n'] = i18n; + window['i18n']['get'] = get; + + window['i18n']['extra'] = extra; // + + var defaults = { + lang: "", + defaultLang: "", + filePath: "/i18n/", + filePrefix: "i18n_", + fileSuffix: "", + forever: true, + get: false, // 是否在js中使用 i18n.get("key") + only: ['value', 'html', 'placeholder', 'title'], + callback: function() {}, + } + + function i18n(ele, options) { + + defaults.i18nLang = null; + defaults.$ele = document.querySelectorAll(ele); + + options = _extend(defaults, options); + if(_getCookie('i18n_lang') != "" && _getCookie('i18n_lang') != "undefined" && _getCookie('i18n_lang') != null) { + defaults.defaultLang = _getCookie('i18n_lang'); + } else if(options.lang == "" && defaults.defaultLang == "") { + throw "defaultLang must not be null !"; + }; + + if(options.lang != null && options.lang != "") { + if(options.forever) { + _setCookie('i18n_lang', options.lang); + } else { + _clearCookie("i18n_lang"); + } + } else { + options.lang = defaults.defaultLang; + }; + + var tempLang = null; + var obj = { + 'async': true, + 'success': _success, + 'err': _err, + '$ele': options.$ele, + 'options': options + } + if(options.get) { + // 如果需要使用 i18n.get("key") + // 改为同步 + obj.async = false; + } + obj.url = options.filePath + options.filePrefix + options.lang + options.fileSuffix + ".json"; + i18nLang = _getJSON(obj); // end get json + + if(options.get) { + // 如果需要使用 i18n.get("key") + // 改回异步 + obj.async = true; + } + return window['i18n']; + } + + function get(key) { + if(this.i18nLang == null || JSON.stringify(i18nLang) == '{}') { + return {} + }; + return this.i18nLang[key]; + } + + function extra(obj) { + if(!('ele' in obj) || !('attr' in obj)){ + throw '参数错误,正确的JSON格式为 {"ele":"","attr":""}' + } + + // document.querySelectorAll("button[title]") + var $ele = document.querySelectorAll(obj.ele); + $ele.forEach(function($this) { + let key = $this.getAttribute(obj.attr); + if(i18n.get(key) == undefined){ + return true; + } + $this.setAttribute(obj.attr, i18n.get(key)) + }) + + } + + /* ============== 内部方法 =============== */ + function _extend(destination, source) { + for(var prop in source) { + destination[prop] = source[prop]; + } + return destination; + }; + + function _getCookie(name) { + var arr = document.cookie.split('; '); + for(var i = 0; i < arr.length; i++) { + var arr1 = arr[i].split('='); + if(arr1[0] == name) { + return arr1[1]; + } + } + return ''; + }; + + function _setCookie(name, value, myDay) { + var oDate = new Date(); + oDate.setDate(oDate.getDate() + myDay); + document.cookie = name + '=' + value + '; expires=' + oDate + "; path=/"; + }; + + function _clearCookie(name) { + document.cookie = name + '=' + '' + '; expires=' + -1; + } + + function _getJSON(obj) { + // obj = {'async':'','url':'','success':'','err':''} + //1.创建Ajax对象。js中,使用一个没有定义的变量会报错,使用一个没有定义的属性,是undefined。IE6下使用没有定义的XMLHttpRequest会报错,所以当做window的一个属性使用 + var xhr = null; + if(window.XMLHttpRequest) { + xhr = new XMLHttpRequest(); //非IE6 + } else { + xhr = new ActiveXObject("Microsoft.XMLHTTP"); //IE6 + } + //2.连接到服务器 + if('async' in obj && obj.async == false) { + // 如果明确设置异步为false,则,同步操作 + xhr.open("GET", obj.url, false); + } else { + xhr.open("GET", obj.url, true); + } + + //3.发送get请求 + xhr.send(null); + + if('async' in obj && obj.async == false) { + // 如果是同步请求 + i18n.i18nLang = obj.success(xhr.responseText, obj.$ele, obj.options) + } else { + //4.接收返回值 + xhr.onreadystatechange = function() { + //xhr.readyState--浏览器和服务器之间进行到哪一步了 + if(xhr.readyState == 4) { //读取完成 //读取的结果是成功 + if(xhr.status == 200) { + i18n.i18nLang = obj.success(xhr.responseText, obj.$ele, obj.options) + } else { + obj.err(xhr.responseText); //对失败的原因做出处理 + } + } + } + } + + }; + + function _success(data, $ele, options) { + var i18nLang = {}; + if(data != null) { + i18nLang = data; + } + if(typeof(i18nLang) == 'string') { + i18nLang = JSON.parse(i18nLang); + } + $ele.forEach(function($this) { + var i18nOnly = $this.getAttribute("i18n-only"); + let i18Onlys = i18nOnly && i18nOnly.split(',') || [] + + if(i18Onlys.length == 0){ + // 全局默认 + i18Onlys = options.only + } + + // type: html、value、等属性 + i18Onlys.forEach(function(type){ + if(type.indexOf('i18n') == 0){ + return + } + if(type == 'html'){ + // 设置html + if($this.innerHTML) { + $this.innerHTML = i18nLang[$this.getAttribute("i18n")] + } + }else{ + if($this.getAttribute(type)) { + $this.setAttribute(type, i18nLang[$this.getAttribute("i18n")]) + } + } + }) + + })// end forEach + options.callback(); + + return i18nLang; + }; + + function _err(data) { + throw data; + } +})(); \ No newline at end of file diff --git a/common/static/i18n/lang/i18n_cn.json b/common/static/i18n/lang/i18n_cn.json new file mode 100644 index 0000000000..5ba580d5eb --- /dev/null +++ b/common/static/i18n/lang/i18n_cn.json @@ -0,0 +1,49 @@ +{ + "main.title": "Archery - SQL审核查询平台", + "btn.show_tran_login": "显示传统登录", + "btn.confirm": "确定", + "btn.cancel": "取消", + "btn.submit_sql": "SQL上线", + "label.audition": "系统审计", + "label.more": "更多", + "label.hello": "你好", + "label.exit": "退出", + "label.close": "关闭", + "label.logout": "退出", + "label.sysinfo": "系统信息", + "label.system": "系统管理", + "label.admin_dashboard": "管理后台", + "label.2fa": "2FA", + "label.2fa_config": "2FA 配置", + "label.change_pass": "修改密码", + "label.dashboard": "控制台", + "label.sql_audition": "SQL审核", + "label.sql_query": "SQL查询", + "label.sql_online": "SQL上线", + "label.sql_analysis": "SQL分析", + "label.data_dictionary": "数据字典", + "label.privilege_manage": "权限管理", + "label.optimization": "优化", + "label.sql_optimization": "SQL优化", + "label.slow_log": "慢日志", + "label.instance_manage": "实例管理", + "label.instance": "实例列表", + "label.general": "常规配置项", + "label.query": "查询", + "label.session": "会话管理", + "label.user": "用户管理", + "label.dba_principles": "DBA Principles", + "label.variables": "参数配置", + "label.database": "数据库管理", + "label.plugins": "工具插件", + "label.resource_group": "资源组管理", + "label.privilege": "权限组管理", + "label.username": "用户名", + "label.password": "密码", + "label.status": "状态", + "placeholder.search": "搜索", + "tooltip.status": "状态", + "footer.about": "关于", + "footer.contact": "联系我们", + "footer.title": "联系我们" +} diff --git a/common/static/i18n/lang/i18n_en.json b/common/static/i18n/lang/i18n_en.json new file mode 100644 index 0000000000..b00f97b2c1 --- /dev/null +++ b/common/static/i18n/lang/i18n_en.json @@ -0,0 +1,49 @@ +{ + "main.title": "Archery - SQL Audition Platform", + "btn.show_tran_login": "Show Traditional Login", + "btn.confirm": "Confirm", + "btn.cancel": "Cancel", + "btn.submit_sql": "Submit SQL", + "label.audition": "Audition", + "label.more": "More", + "label.hello": "Hello", + "label.exit": "Exit", + "label.close": "Close", + "label.logout": "Logout", + "label.sysinfo": "System Info", + "label.system": "System", + "label.admin_dashboard": "Admin Dashboard", + "label.2fa": "2FA", + "label.2fa_config": "2FA Configuration", + "label.change_pass": "Change Password", + "label.dashboard": "DashBoard", + "label.sql_audition": "SQL Audition", + "label.sql_query": "SQL Query", + "label.sql_online": "SQL Online", + "label.sql_analysis": "SQL Analysis", + "label.data_dictionary": "Data Dictionary", + "label.privilege_manage": "Privilege", + "label.optimization": "Optimization", + "label.sql_optimization": "SQL Optimization", + "label.slow_log": "Slow Logs", + "label.instance_manage": "Instance Manage", + "label.instance": "Instance", + "label.general": "General", + "label.query": "Query", + "label.session": "Session", + "label.user": "User", + "label.dba_principles": "DBA Principles", + "label.variables": "Variables", + "label.database": "Database", + "label.plugins": "Plugins", + "label.resource_group": "Resource Group", + "label.privilege": "Privilege", + "label.username": "Username", + "label.password": "Password", + "label.status": "Status", + "placeholder.search": "Search", + "tooltip.status": "Status", + "footer.about": "About", + "footer.contact": "Contact us", + "footer.title": "Contact us" +} diff --git a/common/static/img/archery.png b/common/static/img/archery.png new file mode 100644 index 0000000000000000000000000000000000000000..55ee2d9919dfdf2da128f0a89cc0ceb1f2aa9c53 GIT binary patch literal 3880 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 literal 0 HcmV?d00001 diff --git a/common/static/watermark/shuiyin.js b/common/static/watermark/watermark.js similarity index 100% rename from common/static/watermark/shuiyin.js rename to common/static/watermark/watermark.js diff --git a/common/templates/2fa.html b/common/templates/2fa.html index 7339b6a49b..26e6ecd76a 100644 --- a/common/templates/2fa.html +++ b/common/templates/2fa.html @@ -1,7 +1,7 @@ - Archery - 两步验证 + Archery - 2FA Setting {% load static %} diff --git a/common/templates/base.html b/common/templates/base.html index 34acc86486..8f9caabf17 100644 --- a/common/templates/base.html +++ b/common/templates/base.html @@ -7,7 +7,7 @@ - Archery-SQL审核查询平台 + @@ -18,13 +18,14 @@ + - +{# #} @@ -39,13 +40,16 @@ - Archery +{# Archery#} + + Archery Logo +