diff --git a/web/assets/Vazirmatn-UI-NL-Regular.woff2 b/web/assets/Vazirmatn-UI-NL-Regular.woff2 deleted file mode 100644 index 3ce577ae..00000000 Binary files a/web/assets/Vazirmatn-UI-NL-Regular.woff2 and /dev/null differ diff --git a/web/assets/ant-design-vue/antd-with-locales.min.js b/web/assets/ant-design-vue/antd-with-locales.min.js deleted file mode 100644 index faf69ce0..00000000 --- a/web/assets/ant-design-vue/antd-with-locales.min.js +++ /dev/null @@ -1,3 +0,0 @@ -/*! For license information please see antd-with-locales.min.js.LICENSE.txt */ -!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("moment"),require("vue")):"function"==typeof define&&define.amd?define(["moment","vue"],t):"object"==typeof exports?exports.antd=t(require("moment"),require("vue")):e.antd=t(e.moment,e.Vue)}(window,(function(e,t){return function(e){var t={};function n(i){if(t[i])return t[i].exports;var r=t[i]={i:i,l:!1,exports:{}};return e[i].call(r.exports,r,r.exports,n),r.l=!0,r.exports}return n.m=e,n.c=t,n.d=function(e,t,i){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:i})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var i=Object.create(null);if(n.r(i),Object.defineProperty(i,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var r in e)n.d(i,r,function(t){return e[t]}.bind(null,r));return i},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=545)}([function(e,t,n){"use strict";var i=n(14),r=n.n(i),a=n(35),o=n.n(a),s=Object.prototype,c=s.toString,l=s.hasOwnProperty,u=/^\s*function (\w+)/,h=function(e){var t=null!=e?e.type?e.type:e:null,n=t&&t.toString().match(u);return n&&n[1]},d=function(e){if(null==e)return null;var t=e.constructor.toString().match(u);return t&&t[1]},f=Number.isInteger||function(e){return"number"==typeof e&&isFinite(e)&&Math.floor(e)===e},p=Array.isArray||function(e){return"[object Array]"===c.call(e)},v=function(e){return"[object Function]"===c.call(e)},m=function(e,t){var n;return Object.defineProperty(t,"_vueTypes_name",{enumerable:!1,writable:!1,value:e}),n=t,Object.defineProperty(n,"isRequired",{get:function(){return this.required=!0,this},enumerable:!1}),function(e){Object.defineProperty(e,"def",{value:function(e){return void 0===e&&void 0===this.default?(this.default=void 0,this):v(e)||g(this,e)?(this.default=p(e)||o()(e)?function(){return e}:e,this):(b(this._vueTypes_name+' - invalid default value: "'+e+'"',e),this)},enumerable:!1,writable:!1})}(t),v(t.validator)&&(t.validator=t.validator.bind(t)),t},g=function e(t,n){var i=arguments.length>2&&void 0!==arguments[2]&&arguments[2],r=t,a=!0,s=void 0;o()(t)||(r={type:t});var c=r._vueTypes_name?r._vueTypes_name+" - ":"";return l.call(r,"type")&&null!==r.type&&(p(r.type)?(a=r.type.some((function(t){return e(t,n,!0)})),s=r.type.map((function(e){return h(e)})).join(" or ")):a="Array"===(s=h(r))?p(n):"Object"===s?o()(n):"String"===s||"Number"===s||"Boolean"===s||"Function"===s?d(n)===s:n instanceof r.type),a?l.call(r,"validator")&&v(r.validator)?((a=r.validator(n))||!1!==i||b(c+"custom validation failed"),a):a:(!1===i&&b(c+'value "'+n+'" should be of type "'+s+'"'),!1)},b=function(){},y={get any(){return m("any",{type:null})},get func(){return m("function",{type:Function}).def(C.func)},get bool(){return m("boolean",{type:Boolean}).def(C.bool)},get string(){return m("string",{type:String}).def(C.string)},get number(){return m("number",{type:Number}).def(C.number)},get array(){return m("array",{type:Array}).def(C.array)},get object(){return m("object",{type:Object}).def(C.object)},get integer(){return m("integer",{type:Number,validator:function(e){return f(e)}}).def(C.integer)},get symbol(){return m("symbol",{type:null,validator:function(e){return"symbol"===(void 0===e?"undefined":r()(e))}})},custom:function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"custom validation failed";if("function"!=typeof e)throw new TypeError("[VueTypes error]: You must provide a function as argument");return m(e.name||"<>",{validator:function(){var n=e.apply(void 0,arguments);return n||b(this._vueTypes_name+" - "+t),n}})},oneOf:function(e){if(!p(e))throw new TypeError("[VueTypes error]: You must provide an array as argument");var t='oneOf - value should be one of "'+e.join('", "')+'"',n=e.reduce((function(e,t){return null!=t&&-1===e.indexOf(t.constructor)&&e.push(t.constructor),e}),[]);return m("oneOf",{type:n.length>0?n:null,validator:function(n){var i=-1!==e.indexOf(n);return i||b(t),i}})},instanceOf:function(e){return m("instanceOf",{type:e})},oneOfType:function(e){if(!p(e))throw new TypeError("[VueTypes error]: You must provide an array as argument");var t=!1,n=e.reduce((function(e,n){if(o()(n)){if("oneOf"===n._vueTypes_name)return e.concat(n.type||[]);if(n.type&&!v(n.validator)){if(p(n.type))return e.concat(n.type);e.push(n.type)}else v(n.validator)&&(t=!0);return e}return e.push(n),e}),[]);if(!t)return m("oneOfType",{type:n}).def(void 0);var i=e.map((function(e){return e&&p(e.type)?e.type.map(h):h(e)})).reduce((function(e,t){return e.concat(p(t)?t:[t])}),[]).join('", "');return this.custom((function(t){var n=e.some((function(e){return"oneOf"===e._vueTypes_name?!e.type||g(e.type,t,!0):g(e,t,!0)}));return n||b('oneOfType - value type should be one of "'+i+'"'),n})).def(void 0)},arrayOf:function(e){return m("arrayOf",{type:Array,validator:function(t){var n=t.every((function(t){return g(e,t)}));return n||b('arrayOf - value must be an array of "'+h(e)+'"'),n}})},objectOf:function(e){return m("objectOf",{type:Object,validator:function(t){var n=Object.keys(t).every((function(n){return g(e,t[n])}));return n||b('objectOf - value must be an object of "'+h(e)+'"'),n}})},shape:function(e){var t=Object.keys(e),n=t.filter((function(t){return e[t]&&!0===e[t].required})),i=m("shape",{type:Object,validator:function(i){var r=this;if(!o()(i))return!1;var a=Object.keys(i);return n.length>0&&n.some((function(e){return-1===a.indexOf(e)}))?(b('shape - at least one of required properties "'+n.join('", "')+'" is not present'),!1):a.every((function(n){if(-1===t.indexOf(n))return!0===r._vueTypes_isLoose||(b('shape - object is missing "'+n+'" property'),!1);var a=e[n];return g(a,i[n])}))}});return Object.defineProperty(i,"_vueTypes_isLoose",{enumerable:!1,writable:!0,value:!1}),Object.defineProperty(i,"loose",{get:function(){return this._vueTypes_isLoose=!0,this},enumerable:!1}),i}},C={func:void 0,bool:void 0,string:void 0,number:void 0,array:void 0,object:void 0,integer:void 0};Object.defineProperty(y,"sensibleDefaults",{enumerable:!1,set:function(e){!1===e?C={}:!0===e?C={func:void 0,bool:void 0,string:void 0,number:void 0,array:void 0,object:void 0,integer:void 0}:o()(e)&&(C=e)},get:function(){return C}});t.a=y},function(e,t,n){"use strict";n.d(t,"i",(function(){return T})),n.d(t,"h",(function(){return V})),n.d(t,"k",(function(){return P})),n.d(t,"f",(function(){return H})),n.d(t,"q",(function(){return j})),n.d(t,"u",(function(){return _})),n.d(t,"v",(function(){return L})),n.d(t,"c",(function(){return F})),n.d(t,"x",(function(){return A})),n.d(t,"s",(function(){return m})),n.d(t,"l",(function(){return k})),n.d(t,"g",(function(){return w})),n.d(t,"o",(function(){return x})),n.d(t,"m",(function(){return z})),n.d(t,"j",(function(){return M})),n.d(t,"e",(function(){return O})),n.d(t,"r",(function(){return S})),n.d(t,"y",(function(){return v})),n.d(t,"t",(function(){return E})),n.d(t,"w",(function(){return $})),n.d(t,"a",(function(){return p})),n.d(t,"p",(function(){return b})),n.d(t,"n",(function(){return y})),n.d(t,"d",(function(){return C}));var i=n(14),r=n.n(i),a=n(22),o=n.n(a),s=n(2),c=n.n(s),l=n(35),u=n.n(l),h=n(5),d=n.n(h);var f=/-(\w)/g,p=function(e){return e.replace(f,(function(e,t){return t?t.toUpperCase():""}))},v=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"",t=arguments[1],n={},i=/;(?![^(]*\))/g,r=/:(.+)/;return e.split(i).forEach((function(e){if(e){var i=e.split(r);if(i.length>1){var a=t?p(i[0].trim()):i[0].trim();n[a]=i[1].trim()}}})),n},m=function(e,t){return t in((e.$options||{}).propsData||{})},g=function(e){return e.data&&e.data.scopedSlots||{}},b=function(e){var t=e.componentOptions||{};e.$vnode&&(t=e.$vnode.componentOptions||{});var n=e.children||t.children||[],i={};return n.forEach((function(e){if(!_(e)){var t=e.data&&e.data.slot||"default";i[t]=i[t]||[],i[t].push(e)}})),c()({},i,g(e))},y=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"default",n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};return e.$scopedSlots&&e.$scopedSlots[t]&&e.$scopedSlots[t](n)||e.$slots[t]||[]},C=function(e){var t=e.componentOptions||{};return e.$vnode&&(t=e.$vnode.componentOptions||{}),e.children||t.children||[]},x=function(e){if(e.fnOptions)return e.fnOptions;var t=e.componentOptions;return e.$vnode&&(t=e.$vnode.componentOptions),t&&t.Ctor.options||{}},k=function(e){if(e.componentOptions){var t=e.componentOptions,n=t.propsData,i=void 0===n?{}:n,r=t.Ctor,a=((void 0===r?{}:r).options||{}).props||{},s={},l=!0,u=!1,h=void 0;try{for(var d,f=Object.entries(a)[Symbol.iterator]();!(l=(d=f.next()).done);l=!0){var p=d.value,v=o()(p,2),m=v[0],g=v[1],b=g.default;void 0!==b&&(s[m]="function"==typeof b&&"Function"!==(y=g.type,C=void 0,(C=y&&y.toString().match(/^\s*function (\w+)/))?C[1]:"")?b.call(e):b)}}catch(e){u=!0,h=e}finally{try{!l&&f.return&&f.return()}finally{if(u)throw h}}return c()({},s,i)}var y,C,x=e.$options,k=void 0===x?{}:x,w=e.$props;return function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n={};return Object.keys(e).forEach((function(i){(i in t||void 0!==e[i])&&(n[i]=e[i])})),n}(void 0===w?{}:w,k.propsData)},w=function(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:e,i=!(arguments.length>3&&void 0!==arguments[3])||arguments[3];if(e.$createElement){var r=e.$createElement,a=e[t];return void 0!==a?"function"==typeof a&&i?a(r,n):a:e.$scopedSlots[t]&&i&&e.$scopedSlots[t](n)||e.$scopedSlots[t]||e.$slots[t]||void 0}var o=e.context.$createElement,s=z(e)[t];if(void 0!==s)return"function"==typeof s&&i?s(o,n):s;var c=g(e)[t];if(void 0!==c)return"function"==typeof c&&i?c(o,n):c;var l=[],u=e.componentOptions||{};return(u.children||[]).forEach((function(e){e.data&&e.data.slot===t&&(e.data.attrs&&delete e.data.attrs.slot,"template"===e.tag?l.push(e.children):l.push(e))})),l.length?l:void 0},z=function(e){var t=e.componentOptions;return e.$vnode&&(t=e.$vnode.componentOptions),t&&t.propsData||{}},S=function(e,t){return z(e)[t]},O=function(e){var t=e.data;return e.$vnode&&(t=e.$vnode.data),t&&t.attrs||{}},M=function(e){var t=e.key;return e.$vnode&&(t=e.$vnode.key),t};function T(e){var t={};return e.componentOptions&&e.componentOptions.listeners?t=e.componentOptions.listeners:e.data&&e.data.on&&(t=e.data.on),c()({},t)}function V(e){var t={};return e.data&&e.data.on&&(t=e.data.on),c()({},t)}function P(e){return(e.$vnode?e.$vnode.componentOptions.listeners:e.$listeners)||{}}function H(e){var t={};e.data?t=e.data:e.$vnode&&e.$vnode.data&&(t=e.$vnode.data);var n=t.class||{},i=t.staticClass,r={};return i&&i.split(" ").forEach((function(e){r[e.trim()]=!0})),"string"==typeof n?n.split(" ").forEach((function(e){r[e.trim()]=!0})):Array.isArray(n)?d()(n).split(" ").forEach((function(e){r[e.trim()]=!0})):r=c()({},r,n),r}function j(e,t){var n={};e.data?n=e.data:e.$vnode&&e.$vnode.data&&(n=e.$vnode.data);var i=n.style||n.staticStyle;if("string"==typeof i)i=v(i,t);else if(t&&i){var r={};return Object.keys(i).forEach((function(e){return r[p(e)]=i[e]})),r}return i}function _(e){return!(e.tag||e.text&&""!==e.text.trim())}function L(e){return!e.tag}function F(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[];return e.filter((function(e){return!_(e)}))}var E=function(e,t){return Object.keys(t).forEach((function(n){if(!e[n])throw new Error("not have "+n+" prop");e[n].def&&(e[n]=e[n].def(t[n]))})),e};function A(){var e=[].slice.call(arguments,0),t={};return e.forEach((function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},n=!0,i=!1,r=void 0;try{for(var a,s=Object.entries(e)[Symbol.iterator]();!(n=(a=s.next()).done);n=!0){var l=a.value,h=o()(l,2),d=h[0],f=h[1];t[d]=t[d]||{},u()(f)?c()(t[d],f):t[d]=f}}catch(e){i=!0,r=e}finally{try{!n&&s.return&&s.return()}finally{if(i)throw r}}})),t}function $(e){return e&&"object"===(void 0===e?"undefined":r()(e))&&"componentOptions"in e&&"context"in e&&void 0!==e.tag}t.b=m},function(e,t,n){"use strict";t.__esModule=!0;var i,r=n(245),a=(i=r)&&i.__esModule?i:{default:i};t.default=a.default||function(e){for(var t=1;t0&&void 0!==arguments[0]?arguments[0]:{},t=arguments[1],n="function"==typeof e?e(this.$data,this.$props):e;if(this.getDerivedStateFromProps){var i=this.getDerivedStateFromProps(Object(s.l)(this),o()({},this.$data,n));if(null===i)return;n=o()({},n,i||{})}o()(this.$data,n),this.$forceUpdate(),this.$nextTick((function(){t&&t()}))},__emit:function(){var e=[].slice.call(arguments,0),t=e[0],n=this.$listeners[t];if(e.length&&n)if(Array.isArray(n))for(var i=0,a=n.length;i1&&void 0!==arguments[1]?arguments[1]:v;if(e){var n=m.definitions.get(e);return n&&"function"==typeof n.icon&&(n=o()({},n,{icon:n.icon(t.primaryColor,t.secondaryColor)})),n}},setTwoToneColors:function(e){var t=e.primaryColor,n=e.secondaryColor;v.primaryColor=t,v.secondaryColor=n||Object(p.c)(t)},getTwoToneColors:function(){return o()({},v)},render:function(e){var t=this.$props,n=t.type,i=t.primaryColor,r=t.secondaryColor,a=void 0,s=v;if(i&&(s={primaryColor:i,secondaryColor:r||Object(p.c)(i)}),Object(p.d)(n))a=n;else if("string"==typeof n&&!(a=m.get(n,s)))return null;return a?(a&&"function"==typeof a.icon&&(a=o()({},a,{icon:a.icon(s.primaryColor,s.secondaryColor)})),Object(p.b)(e,a.icon,"svg-"+a.name,{attrs:{"data-icon":a.name,width:"1em",height:"1em",fill:"currentColor","aria-hidden":"true"},on:this.$listeners})):(Object(p.e)("type should be string or icon definiton, but got "+n),null)},install:function(e){e.component(m.name,m)}},g=m,b=n(0),y=n(12),C=n.n(y),x=n(1),k=new Set;var w=n(13),z={width:"1em",height:"1em",fill:"currentColor","aria-hidden":"true",focusable:"false"},S=/-fill$/,O=/-o$/,M=/-twotone$/;var T=n(26);function V(e){return g.setTwoToneColors({primaryColor:e})}var P=n(10);g.add.apply(g,u()(Object.keys(f).filter((function(e){return"default"!==e})).map((function(e){return f[e]})))),V("#1890ff");function H(e,t,n){var i,a=n.$props,s=n.$slots,l=Object(x.k)(n),u=a.type,h=a.component,f=a.viewBox,p=a.spin,v=a.theme,m=a.twoToneColor,b=a.rotate,y=a.tabIndex,C=Object(x.c)(s.default);C=0===C.length?void 0:C,Object(w.a)(Boolean(u||h||C),"Icon","Icon should have `type` prop or `component` prop or `children`.");var k=d()((i={},c()(i,"anticon",!0),c()(i,"anticon-"+u,!!u),i)),T=d()(c()({},"anticon-spin",!!p||"loading"===u)),V=b?{msTransform:"rotate("+b+"deg)",transform:"rotate("+b+"deg)"}:void 0,P={attrs:o()({},z,{viewBox:f}),class:T,style:V};f||delete P.attrs.viewBox;var H=y;void 0===H&&"click"in l&&(H=-1);var j={attrs:{"aria-label":u&&t.icon+": "+u,tabIndex:H},on:l,class:k,staticClass:""};return e("i",j,[function(){if(h)return e(h,P,[C]);if(C){Object(w.a)(Boolean(f)||1===C.length&&"use"===C[0].tag,"Icon","Make sure that you provide correct `viewBox` prop (default `0 0 1024 1024`) to the icon.");var t={attrs:o()({},z),class:T,style:V};return e("svg",r()([t,{attrs:{viewBox:f}}]),[C])}if("string"==typeof u){var n=u;if(v){var i=function(e){var t=null;return S.test(e)?t="filled":O.test(e)?t="outlined":M.test(e)&&(t="twoTone"),t}(u);Object(w.a)(!i||v===i,"Icon","The icon name '"+u+"' already specify a theme '"+i+"', the 'theme' prop '"+v+"' will be ignored.")}return n=function(e,t){var n=e;return"filled"===t?n+="-fill":"outlined"===t?n+="-o":"twoTone"===t?n+="-twotone":Object(w.a)(!1,"Icon","This icon '"+e+"' has unknown theme '"+t+"'"),n}(function(e){return e.replace(S,"").replace(O,"").replace(M,"")}(function(e){var t=e;switch(e){case"cross":t="close";break;case"interation":t="interaction";break;case"canlendar":t="calendar";break;case"colum-height":t="column-height"}return Object(w.a)(t===e,"Icon","Icon '"+e+"' was a typo and is now deprecated, please use '"+t+"' instead."),t}(n)),v||"outlined"),e(g,{attrs:{focusable:"false",type:n,primaryColor:m},class:T,style:V})}}()])}var j={name:"AIcon",props:{tabIndex:b.a.number,type:b.a.string,component:b.a.any,viewBox:b.a.any,spin:b.a.bool.def(!1),rotate:b.a.number,theme:b.a.oneOf(["filled","outlined","twoTone"]),twoToneColor:b.a.string,role:b.a.string},render:function(e){var t=this;return e(T.a,{attrs:{componentName:"Icon"},scopedSlots:{default:function(n){return H(e,n,t)}}})},createFromIconfontCN:function(e){var t=e.scriptUrl,n=e.extraCommonProps,i=void 0===n?{}:n;if("undefined"!=typeof document&&"undefined"!=typeof window&&"function"==typeof document.createElement&&"string"==typeof t&&t.length&&!k.has(t)){var r=document.createElement("script");r.setAttribute("src",t),r.setAttribute("data-namespace",t),k.add(t),document.body.appendChild(r)}return{functional:!0,name:"AIconfont",props:_.props,render:function(e,t){var n=t.props,r=t.slots,a=t.listeners,o=t.data,s=n.type,c=C()(n,["type"]),l=r().default,u=null;s&&(u=e("use",{attrs:{"xlink:href":"#"+s}})),l&&(u=l);var h=Object(x.x)(i,o,{props:c,on:a});return e(_,h,[u])}}},getTwoToneColor:function(){return g.getTwoToneColors().primaryColor}};j.setTwoToneColor=V,j.install=function(e){e.use(P.a),e.component(j.name,j)};var _=t.a=j},function(e,t,n){"use strict";n.d(t,"b",(function(){return h})),n.d(t,"a",(function(){return d}));var i=n(11),r=n.n(i),a=n(2),o=n.n(a),s=n(1),c=n(5),l=n.n(c);function u(e,t){var n=e.componentOptions,i=e.data,r={};n&&n.listeners&&(r=o()({},n.listeners));var a={};i&&i.on&&(a=o()({},i.on));var s=new e.constructor(e.tag,i?o()({},i,{on:a}):i,e.children,e.text,e.elm,e.context,n?o()({},n,{listeners:r}):n,e.asyncFactory);return s.ns=e.ns,s.isStatic=e.isStatic,s.key=e.key,s.isComment=e.isComment,s.fnContext=e.fnContext,s.fnOptions=e.fnOptions,s.fnScopeId=e.fnScopeId,s.isCloned=!0,t&&(e.children&&(s.children=h(e.children,!0)),n&&n.children&&(n.children=h(n.children,!0))),s}function h(e,t){for(var n=e.length,i=new Array(n),r=0;r1&&void 0!==arguments[1]?arguments[1]:{},n=arguments[2],i=e;if(Array.isArray(e)&&(i=Object(s.c)(e)[0]),!i)return null;var a=u(i,n),c=t.props,h=void 0===c?{}:c,d=t.key,f=t.on,p=void 0===f?{}:f,v=t.nativeOn,m=void 0===v?{}:v,g=t.children,b=t.directives,y=void 0===b?[]:b,C=a.data||{},x={},k={},w=t.attrs,z=void 0===w?{}:w,S=t.ref,O=t.domProps,M=void 0===O?{}:O,T=t.style,V=void 0===T?{}:T,P=t.class,H=void 0===P?{}:P,j=t.scopedSlots,_=void 0===j?{}:j;return k="string"==typeof C.style?Object(s.y)(C.style):o()({},C.style,k),k="string"==typeof V?o()({},k,Object(s.y)(k)):o()({},k,V),"string"==typeof C.class&&""!==C.class.trim()?C.class.split(" ").forEach((function(e){x[e.trim()]=!0})):Array.isArray(C.class)?l()(C.class).split(" ").forEach((function(e){x[e.trim()]=!0})):x=o()({},C.class,x),"string"==typeof H&&""!==H.trim()?H.split(" ").forEach((function(e){x[e.trim()]=!0})):x=o()({},x,H),a.data=o()({},C,{style:k,attrs:o()({},C.attrs,z),class:x,domProps:o()({},C.domProps,M),scopedSlots:o()({},C.scopedSlots,_),directives:[].concat(r()(C.directives||[]),r()(y))}),a.componentOptions?(a.componentOptions.propsData=a.componentOptions.propsData||{},a.componentOptions.listeners=a.componentOptions.listeners||{},a.componentOptions.propsData=o()({},a.componentOptions.propsData,h),a.componentOptions.listeners=o()({},a.componentOptions.listeners,p),g&&(a.componentOptions.children=g)):(g&&(a.children=g),a.data.on=o()({},a.data.on||{},p)),a.data.on=o()({},a.data.on||{},m),void 0!==d&&(a.key=d,a.data.key=d),"string"==typeof S&&(a.data.ref=S),a}},function(e,t,n){"use strict";var i=n(25),r=n.n(i),a=n(112),o=n(77);function s(e){return e.directive("ant-portal",{inserted:function(e,t){var n=t.value,i="function"==typeof n?n(e):n;i!==e.parentNode&&i.appendChild(e)},componentUpdated:function(e,t){var n=t.value,i="function"==typeof n?n(e):n;i!==e.parentNode&&i.appendChild(e)}})}var c={install:function(e){e.use(r.a,{name:"ant-ref"}),Object(a.a)(e),Object(o.a)(e),s(e)}},l={};l.install=function(e){l.Vue=e,e.use(c)};t.a=l},function(e,t,n){"use strict";t.__esModule=!0;var i,r=n(274),a=(i=r)&&i.__esModule?i:{default:i};t.default=function(e){if(Array.isArray(e)){for(var t=0,n=Array(e.length);t=0||Object.prototype.hasOwnProperty.call(e,i)&&(n[i]=e[i]);return n}},function(e,t,n){"use strict";var i={};function r(e,t){0}function a(e,t,n){t||i[n]||(e(!1,n),i[n]=!0)}var o=function(e,t){a(r,e,t)};t.a=function(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:"";o(e,"[antdv: "+t+"] "+n)}},function(e,t,n){"use strict";t.__esModule=!0;var i=o(n(251)),r=o(n(261)),a="function"==typeof r.default&&"symbol"==typeof i.default?function(e){return typeof e}:function(e){return e&&"function"==typeof r.default&&e.constructor===r.default&&e!==r.default.prototype?"symbol":typeof e};function o(e){return e&&e.__esModule?e:{default:e}}t.default="function"==typeof r.default&&"symbol"===a(i.default)?function(e){return void 0===e?"undefined":a(e)}:function(e){return e&&"function"==typeof r.default&&e.constructor===r.default&&e!==r.default.prototype?"symbol":void 0===e?"undefined":a(e)}},function(t,n){t.exports=e},function(e,t,n){"use strict";var i=n(2),r=n.n(i);t.a=function(e,t){for(var n=r()({},e),i=0;i=0&&n.splice(i,1),n}function y(e,t){var n=e.slice();return-1===n.indexOf(t)&&n.push(t),n}function C(e){return e.split("-")}function x(e,t){return e+"-"+t}function k(e){return Object(v.o)(e).isTreeNode}function w(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[];return e.filter(k)}function z(e){var t=Object(v.l)(e)||{},n=t.disabled,i=t.disableCheckbox,r=t.checkable;return!(!n&&!i)||!1===r}function S(e,t){!function n(i,r,a){var o=i?i.componentOptions.children:e,s=i?x(a.pos,r):0,c=w(o);if(i){var l=i.key;l||null!=l||(l=s);var u={node:i,index:r,pos:s,key:l,parentPos:a.node?a.pos:null};t(u)}c.forEach((function(e,t){n(e,t,{node:i,pos:s})}))}(null)}function O(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[],t=arguments[1],n=e.map(t);return 1===n.length?n[0]:n}function M(e,t){var n=Object(v.l)(t),i=n.eventKey,r=n.pos,a=[];return S(e,(function(e){var t=e.key;a.push(t)})),a.push(i||r),a}function T(e,t){var n=e.clientY,i=t.$refs.selectHandle.getBoundingClientRect(),r=i.top,a=i.bottom,o=i.height,s=Math.max(.25*o,2);return n<=r+s?-1:n>=a-s?1:0}function V(e,t){if(e)return t.multiple?e.slice():e.length?[e[0]]:e}var P=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return{props:Object(f.a)(e,["on","key","class","className","style"]),on:e.on||{},class:e.class||e.className,style:e.style,key:e.key}};function H(e,t,n){if(!t)return[];var i=(n||{}).processProps,r=void 0===i?P:i;return(Array.isArray(t)?t:[t]).map((function(t){var i=t.children,a=u()(t,["children"]),o=H(e,i,n);return e(p.a,r(a),[o])}))}function j(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=t.initWrapper,i=t.processEntity,r=t.onProcessFinished,a=new Map,o=new Map,s={posEntities:a,keyEntities:o};return n&&(s=n(s)||s),S(e,(function(e){var t=e.node,n=e.index,r=e.pos,c=e.key,l=e.parentPos,u={node:t,index:n,key:c,pos:r};a.set(r,u),o.set(c,u),u.parent=a.get(l),u.parent&&(u.parent.children=u.parent.children||[],u.parent.children.push(u)),i&&i(u,s)})),r&&r(s),s}function _(e){if(!e)return null;var t=void 0;if(Array.isArray(e))t={checkedKeys:e,halfCheckedKeys:void 0};else{if("object"!==(void 0===e?"undefined":c()(e)))return d()(!1,"`checkedKeys` is not an array or an object"),null;t={checkedKeys:e.checked||void 0,halfCheckedKeys:e.halfChecked||void 0}}return t}function L(e,t,n){var i=arguments.length>3&&void 0!==arguments[3]?arguments[3]:{},r=new Map,a=new Map;function s(e){if(r.get(e)!==t){var i=n.get(e);if(i){var o=i.children,c=i.parent;if(!z(i.node)){var l=!0,u=!1;(o||[]).filter((function(e){return!z(e.node)})).forEach((function(e){var t=e.key,n=r.get(t),i=a.get(t);(n||i)&&(u=!0),n||(l=!1)})),t?r.set(e,l):r.set(e,!1),a.set(e,u),c&&s(c.key)}}}}function c(e){if(r.get(e)!==t){var i=n.get(e);if(i){var a=i.children;z(i.node)||(r.set(e,t),(a||[]).forEach((function(e){c(e.key)})))}}}function l(e){var i=n.get(e);if(i){var a=i.children,o=i.parent,l=i.node;r.set(e,t),z(l)||((a||[]).filter((function(e){return!z(e.node)})).forEach((function(e){c(e.key)})),o&&s(o.key))}else d()(!1,"'"+e+"' does not exist in the tree.")}(i.checkedKeys||[]).forEach((function(e){r.set(e,!0)})),(i.halfCheckedKeys||[]).forEach((function(e){a.set(e,!0)})),(e||[]).forEach((function(e){l(e)}));var u=[],h=[],f=!0,p=!1,v=void 0;try{for(var m,g=r[Symbol.iterator]();!(f=(m=g.next()).done);f=!0){var b=m.value,y=o()(b,2),C=y[0],x=y[1];x&&u.push(C)}}catch(e){p=!0,v=e}finally{try{!f&&g.return&&g.return()}finally{if(p)throw v}}var k=!0,w=!1,S=void 0;try{for(var O,M=a[Symbol.iterator]();!(k=(O=M.next()).done);k=!0){var T=O.value,V=o()(T,2),P=V[0],H=V[1];!r.get(P)&&H&&h.push(P)}}catch(e){w=!0,S=e}finally{try{!k&&M.return&&M.return()}finally{if(w)throw S}}return{checkedKeys:u,halfCheckedKeys:h}}function F(e,t){var n=new Map;return(e||[]).forEach((function(e){!function e(i){if(!n.get(i)){var r=t.get(i);if(r){n.set(i,!0);var a=r.parent,o=r.node,s=Object(v.l)(o);s&&s.disabled||a&&e(a.key)}}}(e)})),[].concat(r()(n.keys()))}},function(e,t,n){(function(t){for(var i=n(287),r="undefined"==typeof window?t:window,a=["moz","webkit"],o="AnimationFrame",s=r["request"+o],c=r["cancel"+o]||r["cancelRequest"+o],l=0;!s&&l1&&void 0!==arguments[1]?arguments[1]:{},n=t.beforeEnter,a=t.enter,o=t.afterEnter,s=t.leave,c=t.afterLeave,l=t.appear,u=void 0===l||l,h=t.tag,d=t.nativeOn,f={props:{appear:u,css:!1},on:{beforeEnter:n||r,enter:a||function(t,n){Object(i.a)(t,e+"-enter",n)},afterEnter:o||r,leave:s||function(t,n){Object(i.a)(t,e+"-leave",n)},afterLeave:c||r},nativeOn:d};return h&&(f.tag=h),f}},function(e,t,n){"use strict";var i=function(){};e.exports=i},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default={install:function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=t.name||"ref";e.directive(n,{bind:function(t,n,i){e.nextTick((function(){n.value(i.componentInstance||t,i.key)})),n.value(i.componentInstance||t,i.key)},update:function(e,t,i,r){if(r.data&&r.data.directives){var a=r.data.directives.find((function(e){return e.name===n}));if(a&&a.value!==t.value)return a&&a.value(null,r.key),void t.value(i.componentInstance||e,i.key)}i.componentInstance===r.componentInstance&&i.elm===r.elm||t.value(i.componentInstance||e,i.key)},unbind:function(e,t,n){t.value(null,n.key)}})}}},function(e,t,n){"use strict";var i=n(2),r=n.n(i),a=n(0),o=n(36);t.a={name:"LocaleReceiver",props:{componentName:a.a.string.def("global"),defaultLocale:a.a.oneOfType([a.a.object,a.a.func]),children:a.a.func},inject:{localeData:{default:function(){return{}}}},methods:{getLocale:function(){var e=this.componentName,t=this.defaultLocale||o.a[e||"global"],n=this.localeData.antLocale,i=e&&n?n[e]:{};return r()({},"function"==typeof t?t():t,i||{})},getLocaleCode:function(){var e=this.localeData.antLocale,t=e&&e.locale;return e&&e.exist&&!t?o.a.locale:t}},render:function(){var e=this.$scopedSlots,t=this.children||e.default,n=this.localeData.antLocale;return t(this.getLocale(),this.getLocaleCode(),n)}}},function(e,t){e.exports=function(e,t,n,i){var r=n?n.call(i,e,t):void 0;if(void 0!==r)return!!r;if(e===t)return!0;if("object"!=typeof e||!e||"object"!=typeof t||!t)return!1;var a=Object.keys(e),o=Object.keys(t);if(a.length!==o.length)return!1;for(var s=Object.prototype.hasOwnProperty.bind(t),c=0;c=0&&n.left>=0&&n.bottom>n.top&&n.right>n.left?n:null}function se(e){var t,n,i;if(ne.isWindow(e)||9===e.nodeType){var r=ne.getWindow(e);t={left:ne.getWindowScrollLeft(r),top:ne.getWindowScrollTop(r)},n=ne.viewportWidth(r),i=ne.viewportHeight(r)}else t=ne.offset(e),n=ne.outerWidth(e),i=ne.outerHeight(e);return t.width=n,t.height=i,t}function ce(e,t){var n=t.charAt(0),i=t.charAt(1),r=e.width,a=e.height,o=e.left,s=e.top;return"c"===n?s+=a/2:"b"===n&&(s+=a),"c"===i?o+=r/2:"r"===i&&(o+=r),{left:o,top:s}}function le(e,t,n,i,r){var a=ce(t,n[1]),o=ce(e,n[0]),s=[o.left-a.left,o.top-a.top];return{left:Math.round(e.left-s[0]+i[0]-r[0]),top:Math.round(e.top-s[1]+i[1]-r[1])}}function ue(e,t,n){return e.leftn.right}function he(e,t,n){return e.topn.bottom}function de(e,t,n){var i=[];return ne.each(e,(function(e){i.push(e.replace(t,(function(e){return n[e]})))})),i}function fe(e,t){return e[t]=-e[t],e}function pe(e,t){return(/%$/.test(e)?parseInt(e.substring(0,e.length-1),10)/100*t:parseInt(e,10))||0}function ve(e,t){e[0]=pe(e[0],t.width),e[1]=pe(e[1],t.height)}function me(e,t,n,i){var r=n.points,a=n.offset||[0,0],o=n.targetOffset||[0,0],s=n.overflow,c=n.source||e;a=[].concat(a),o=[].concat(o);var l={},u=0,h=oe(c,!(!(s=s||{})||!s.alwaysByViewport)),d=se(c);ve(a,d),ve(o,t);var f=le(d,t,r,a,o),p=ne.merge(d,f);if(h&&(s.adjustX||s.adjustY)&&i){if(s.adjustX&&ue(f,d,h)){var v=de(r,/[lr]/gi,{l:"r",r:"l"}),m=fe(a,0),g=fe(o,0);(function(e,t,n){return e.left>n.right||e.left+t.widthn.bottom||e.top+t.height=n.left&&r.left+a.width>n.right&&(a.width-=r.left+a.width-n.right),i.adjustX&&r.left+a.width>n.right&&(r.left=Math.max(n.right-a.width,n.left)),i.adjustY&&r.top=n.top&&r.top+a.height>n.bottom&&(a.height-=r.top+a.height-n.bottom),i.adjustY&&r.top+a.height>n.bottom&&(r.top=Math.max(n.bottom-a.height,n.top)),ne.mix(r,a)}(f,d,h,l))}return p.width!==d.width&&ne.css(c,"width",ne.width(c)+p.width-d.width),p.height!==d.height&&ne.css(c,"height",ne.height(c)+p.height-d.height),ne.offset(c,{left:p.left,top:p.top},{useCssRight:n.useCssRight,useCssBottom:n.useCssBottom,useCssTransform:n.useCssTransform,ignoreShake:n.ignoreShake}),{points:r,offset:a,targetOffset:o,overflow:l}}function ge(e,t,n){var i=n.target||t;return me(e,se(i),n,!function(e,t){var n=oe(e,t),i=se(e);return!n||i.left+i.width<=n.left||i.top+i.height<=n.top||i.left>=n.right||i.top>=n.bottom}(i,n.overflow&&n.overflow.alwaysByViewport))}ge.__getOffsetParent=re,ge.__getVisibleRectForElement=oe;function be(e){return e&&"object"===(void 0===e?"undefined":g()(e))&&e.window===e}function ye(e,t){var n=Math.floor(e),i=Math.floor(t);return Math.abs(n-i)<=1}var Ce=n(9),xe=n(114),ke=n.n(xe);function we(e){return"function"==typeof e&&e?e():null}function ze(e){return"object"===(void 0===e?"undefined":g()(e))&&e?e:null}var Se={props:{childrenProps:u.a.object,align:u.a.object.isRequired,target:u.a.oneOfType([u.a.func,u.a.object]).def((function(){return window})),monitorBufferTime:u.a.number.def(50),monitorWindowResize:u.a.bool.def(!1),disabled:u.a.bool.def(!1)},data:function(){return this.aligned=!1,{}},mounted:function(){var e=this;this.$nextTick((function(){e.prevProps=a()({},e.$props);var t=e.$props;!e.aligned&&e.forceAlign(),!t.disabled&&t.monitorWindowResize&&e.startMonitorWindowResize()}))},updated:function(){var e=this;this.$nextTick((function(){var t,n,i=e.prevProps,r=e.$props,o=!1;if(!r.disabled){var s=e.$el,c=s?s.getBoundingClientRect():null;if(i.disabled)o=!0;else{var l=we(i.target),u=we(r.target),h=ze(i.target),d=ze(r.target);be(l)&&be(u)?o=!1:(l!==u||l&&!u&&d||h&&d&&u||d&&!((t=h)===(n=d)||t&&n&&("pageX"in n&&"pageY"in n?t.pageX===n.pageX&&t.pageY===n.pageY:"clientX"in n&&"clientY"in n&&t.clientX===n.clientX&&t.clientY===n.clientY)))&&(o=!0);var f=e.sourceRect||{};o||!s||ye(f.width,c.width)&&ye(f.height,c.height)||(o=!0)}e.sourceRect=c}o&&e.forceAlign(),r.monitorWindowResize&&!r.disabled?e.startMonitorWindowResize():e.stopMonitorWindowResize(),e.prevProps=a()({},e.$props,{align:ke()(e.$props.align)})}))},beforeDestroy:function(){this.stopMonitorWindowResize()},methods:{startMonitorWindowResize:function(){this.resizeHandler||(this.bufferMonitor=function(e,t){var n=void 0;function i(){n&&(clearTimeout(n),n=null)}function r(){i(),n=setTimeout(e,t)}return r.clear=i,r}(this.forceAlign,this.$props.monitorBufferTime),this.resizeHandler=Object(p.a)(window,"resize",this.bufferMonitor))},stopMonitorWindowResize:function(){this.resizeHandler&&(this.bufferMonitor.clear(),this.resizeHandler.remove(),this.resizeHandler=null)},forceAlign:function(){var e=this.$props,t=e.disabled,n=e.target,i=e.align;if(!t&&n){var r=this.$el,a=Object(d.k)(this),o=void 0,s=we(n),c=ze(n),l=document.activeElement;s?o=ge(r,s,i):c&&(o=function(e,t,n){var i,r,a=ne.getDocument(e),o=a.defaultView||a.parentWindow,s=ne.getWindowScrollLeft(o),c=ne.getWindowScrollTop(o),l=ne.viewportWidth(o),u=ne.viewportHeight(o),h={left:i="pageX"in t?t.pageX:s+t.clientX,top:r="pageY"in t?t.pageY:c+t.clientY,width:0,height:0},d=i>=0&&i<=s+l&&r>=0&&r<=c+u,f=[n.points[0],"cc"];return me(e,h,y(y({},n),{},{points:f}),d)}(r,c,i)),function(e,t){e!==document.activeElement&&Object(h.a)(t,e)&&e.focus()}(l,r),this.aligned=!0,a.align&&a.align(r,o)}}},render:function(){var e=this.$props.childrenProps,t=Object(d.n)(this)[0];return t&&e?Object(Ce.a)(t,{props:e}):t}},Oe=n(6),Me=n.n(Oe),Te={props:{visible:u.a.bool,hiddenClassName:u.a.string},render:function(){var e=arguments[0],t=this.$props,n=t.hiddenClassName,i=(t.visible,null);if(n||!this.$slots.default||this.$slots.default.length>1){var r="";i=e("div",{class:r},[this.$slots.default])}else i=this.$slots.default[0];return i}},Ve={props:{hiddenClassName:u.a.string.def(""),prefixCls:u.a.string,visible:u.a.bool},render:function(){var e=arguments[0],t=this.$props,n=t.prefixCls,i=t.visible,r=t.hiddenClassName,a={on:Object(d.k)(this)};return e("div",Me()([a,{class:i?"":r}]),[e(Te,{class:n+"-content",attrs:{visible:i}},[this.$slots.default])])}},Pe=n(46),He=n(4),je={name:"VCTriggerPopup",mixins:[He.a],props:{visible:u.a.bool,getClassNameFromAlign:u.a.func,getRootDomNode:u.a.func,align:u.a.any,destroyPopupOnHide:u.a.bool,prefixCls:u.a.string,getContainer:u.a.func,transitionName:u.a.string,animation:u.a.any,maskAnimation:u.a.string,maskTransitionName:u.a.string,mask:u.a.bool,zIndex:u.a.number,popupClassName:u.a.any,popupStyle:u.a.object.def((function(){return{}})),stretch:u.a.string,point:u.a.shape({pageX:u.a.number,pageY:u.a.number})},data:function(){return this.domEl=null,{stretchChecked:!1,targetWidth:void 0,targetHeight:void 0}},mounted:function(){var e=this;this.$nextTick((function(){e.rootNode=e.getPopupDomNode(),e.setStretchSize()}))},updated:function(){var e=this;this.$nextTick((function(){e.setStretchSize()}))},beforeDestroy:function(){this.$el.parentNode?this.$el.parentNode.removeChild(this.$el):this.$el.remove&&this.$el.remove()},methods:{onAlign:function(e,t){var n=this.$props.getClassNameFromAlign(t);this.currentAlignClassName!==n&&(this.currentAlignClassName=n,e.className=this.getClassName(n));var i=Object(d.k)(this);i.align&&i.align(e,t)},setStretchSize:function(){var e=this.$props,t=e.stretch,n=e.getRootDomNode,i=e.visible,r=this.$data,a=r.stretchChecked,o=r.targetHeight,s=r.targetWidth;if(t&&i){var c=n();if(c){var l=c.offsetHeight,u=c.offsetWidth;o===l&&s===u&&a||this.setState({stretchChecked:!0,targetHeight:l,targetWidth:u})}}else a&&this.setState({stretchChecked:!1})},getPopupDomNode:function(){return this.$refs.popupInstance?this.$refs.popupInstance.$el:null},getTargetElement:function(){return this.$props.getRootDomNode()},getAlignTarget:function(){var e=this.$props.point;return e||this.getTargetElement},getMaskTransitionName:function(){var e=this.$props,t=e.maskTransitionName,n=e.maskAnimation;return!t&&n&&(t=e.prefixCls+"-"+n),t},getTransitionName:function(){var e=this.$props,t=e.transitionName,n=e.animation;return t||("string"==typeof n?t=""+n:n&&n.props&&n.props.name&&(t=n.props.name)),t},getClassName:function(e){return this.$props.prefixCls+" "+this.$props.popupClassName+" "+e},getPopupElement:function(){var e=this,t=this.$createElement,n=this.$props,i=this.$slots,r=this.getTransitionName,o=this.$data,s=o.stretchChecked,c=o.targetHeight,l=o.targetWidth,u=n.align,h=n.visible,f=n.prefixCls,p=n.animation,v=n.popupStyle,m=n.getClassNameFromAlign,b=n.destroyPopupOnHide,y=n.stretch,C=this.getClassName(this.currentAlignClassName||m(u));h||(this.currentAlignClassName=null);var x={};y&&(-1!==y.indexOf("height")?x.height="number"==typeof c?c+"px":c:-1!==y.indexOf("minHeight")&&(x.minHeight="number"==typeof c?c+"px":c),-1!==y.indexOf("width")?x.width="number"==typeof l?l+"px":l:-1!==y.indexOf("minWidth")&&(x.minWidth="number"==typeof l?l+"px":l),s||setTimeout((function(){e.$refs.alignInstance&&e.$refs.alignInstance.forceAlign()}),0));var k={props:{prefixCls:f,visible:h},class:C,on:Object(d.k)(this),ref:"popupInstance",style:a()({},x,v,this.getZIndexStyle())},w={props:{appear:!0,css:!1}},z=r(),S=!!z,O={beforeEnter:function(){},enter:function(t,n){e.$nextTick((function(){e.$refs.alignInstance?e.$refs.alignInstance.$nextTick((function(){e.domEl=t,Object(Pe.a)(t,z+"-enter",n)})):n()}))},beforeLeave:function(){e.domEl=null},leave:function(e,t){Object(Pe.a)(e,z+"-leave",t)}};if("object"===(void 0===p?"undefined":g()(p))){S=!0;var M=p.on,T=void 0===M?{}:M,V=p.props,P=void 0===V?{}:V;w.props=a()({},w.props,P),w.on=a()({},O,T)}else w.on=O;return S||(w={}),t("transition",w,b?[h?t(Se,{attrs:{target:this.getAlignTarget(),monitorWindowResize:!0,align:u},key:"popup",ref:"alignInstance",on:{align:this.onAlign}},[t(Ve,k,[i.default])]):null]:[t(Se,{directives:[{name:"show",value:h}],attrs:{target:this.getAlignTarget(),monitorWindowResize:!0,disabled:!h,align:u},key:"popup",ref:"alignInstance",on:{align:this.onAlign}},[t(Ve,k,[i.default])])])},getZIndexStyle:function(){var e={},t=this.$props;return void 0!==t.zIndex&&(e.zIndex=t.zIndex),e},getMaskElement:function(){var e=this.$createElement,t=this.$props,n=null;if(t.mask){var i=this.getMaskTransitionName();n=e(Te,{directives:[{name:"show",value:t.visible}],style:this.getZIndexStyle(),key:"mask",class:t.prefixCls+"-mask",attrs:{visible:t.visible}}),i&&(n=e("transition",{attrs:{appear:!0,name:i}},[n]))}return n}},render:function(){var e=arguments[0],t=this.getMaskElement,n=this.getPopupElement;return e("div",[t(),n()])}};function _e(e,t,n){return n?e[0]===t[0]:e[0]===t[0]&&e[1]===t[1]}function Le(){}var Fe={props:{autoMount:u.a.bool.def(!0),autoDestroy:u.a.bool.def(!0),visible:u.a.bool,forceRender:u.a.bool.def(!1),parent:u.a.any,getComponent:u.a.func.isRequired,getContainer:u.a.func.isRequired,children:u.a.func.isRequired},mounted:function(){this.autoMount&&this.renderComponent()},updated:function(){this.autoMount&&this.renderComponent()},beforeDestroy:function(){this.autoDestroy&&this.removeContainer()},methods:{removeContainer:function(){this.container&&(this._component&&this._component.$destroy(),this.container.parentNode.removeChild(this.container),this.container=null,this._component=null)},renderComponent:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=arguments[1],n=this.visible,i=this.forceRender,r=this.getContainer,a=this.parent,o=this;if(n||a._component||a.$refs._component||i){var s=this.componentEl;this.container||(this.container=r(),s=document.createElement("div"),this.componentEl=s,this.container.appendChild(s));var c={component:o.getComponent(e)};this._component?this._component.setComponent(c):this._component=new this.$root.constructor({el:s,parent:o,data:{_com:c},mounted:function(){this.$nextTick((function(){t&&t.call(o)}))},updated:function(){this.$nextTick((function(){t&&t.call(o)}))},methods:{setComponent:function(e){this.$data._com=e}},render:function(){return this.$data._com.component}})}}},render:function(){return this.children({renderComponent:this.renderComponent,removeContainer:this.removeContainer})}};s.a.use(l.a,{name:"ant-ref"});var Ee=["click","mousedown","touchstart","mouseenter","mouseleave","focus","blur","contextmenu"],Ae={name:"Trigger",mixins:[He.a],props:{action:u.a.oneOfType([u.a.string,u.a.arrayOf(u.a.string)]).def([]),showAction:u.a.any.def([]),hideAction:u.a.any.def([]),getPopupClassNameFromAlign:u.a.any.def((function(){return""})),afterPopupVisibleChange:u.a.func.def(Le),popup:u.a.any,popupStyle:u.a.object.def((function(){return{}})),prefixCls:u.a.string.def("rc-trigger-popup"),popupClassName:u.a.string.def(""),popupPlacement:u.a.string,builtinPlacements:u.a.object,popupTransitionName:u.a.oneOfType([u.a.string,u.a.object]),popupAnimation:u.a.any,mouseEnterDelay:u.a.number.def(0),mouseLeaveDelay:u.a.number.def(.1),zIndex:u.a.number,focusDelay:u.a.number.def(0),blurDelay:u.a.number.def(.15),getPopupContainer:u.a.func,getDocument:u.a.func.def((function(){return window.document})),forceRender:u.a.bool,destroyPopupOnHide:u.a.bool.def(!1),mask:u.a.bool.def(!1),maskClosable:u.a.bool.def(!0),popupAlign:u.a.object.def((function(){return{}})),popupVisible:u.a.bool,defaultPopupVisible:u.a.bool.def(!1),maskTransitionName:u.a.oneOfType([u.a.string,u.a.object]),maskAnimation:u.a.string,stretch:u.a.string,alignPoint:u.a.bool},provide:function(){return{vcTriggerContext:this}},inject:{vcTriggerContext:{default:function(){return{}}},savePopupRef:{default:function(){return Le}},dialogContext:{default:function(){return null}}},data:function(){var e=this,t=this.$props,n=void 0;return n=Object(d.s)(this,"popupVisible")?!!t.popupVisible:!!t.defaultPopupVisible,Ee.forEach((function(t){e["fire"+t]=function(n){e.fireEvents(t,n)}})),{prevPopupVisible:n,sPopupVisible:n,point:null}},watch:{popupVisible:function(e){void 0!==e&&(this.prevPopupVisible=this.sPopupVisible,this.sPopupVisible=e)}},deactivated:function(){this.setPopupVisible(!1)},mounted:function(){var e=this;this.$nextTick((function(){e.renderComponent(null),e.updatedCal()}))},updated:function(){var e=this;this.renderComponent(null,(function(){e.sPopupVisible!==e.prevPopupVisible&&e.afterPopupVisibleChange(e.sPopupVisible),e.prevPopupVisible=e.sPopupVisible})),this.$nextTick((function(){e.updatedCal()}))},beforeDestroy:function(){this.clearDelayTimer(),this.clearOutsideHandler(),clearTimeout(this.mouseDownTimeout)},methods:{updatedCal:function(){var e=this.$props;if(this.$data.sPopupVisible){var t=void 0;this.clickOutsideHandler||!this.isClickToHide()&&!this.isContextmenuToShow()||(t=e.getDocument(),this.clickOutsideHandler=Object(p.a)(t,"mousedown",this.onDocumentClick)),this.touchOutsideHandler||(t=t||e.getDocument(),this.touchOutsideHandler=Object(p.a)(t,"touchstart",this.onDocumentClick)),!this.contextmenuOutsideHandler1&&this.isContextmenuToShow()&&(t=t||e.getDocument(),this.contextmenuOutsideHandler1=Object(p.a)(t,"scroll",this.onContextmenuClose)),!this.contextmenuOutsideHandler2&&this.isContextmenuToShow()&&(this.contextmenuOutsideHandler2=Object(p.a)(window,"blur",this.onContextmenuClose))}else this.clearOutsideHandler()},onMouseenter:function(e){var t=this.$props.mouseEnterDelay;this.fireEvents("mouseenter",e),this.delaySetPopupVisible(!0,t,t?null:e)},onMouseMove:function(e){this.fireEvents("mousemove",e),this.setPoint(e)},onMouseleave:function(e){this.fireEvents("mouseleave",e),this.delaySetPopupVisible(!1,this.$props.mouseLeaveDelay)},onPopupMouseenter:function(){this.clearDelayTimer()},onPopupMouseleave:function(e){e&&e.relatedTarget&&!e.relatedTarget.setTimeout&&this._component&&this._component.getPopupDomNode&&Object(h.a)(this._component.getPopupDomNode(),e.relatedTarget)||this.delaySetPopupVisible(!1,this.$props.mouseLeaveDelay)},onFocus:function(e){this.fireEvents("focus",e),this.clearDelayTimer(),this.isFocusToShow()&&(this.focusTime=Date.now(),this.delaySetPopupVisible(!0,this.$props.focusDelay))},onMousedown:function(e){this.fireEvents("mousedown",e),this.preClickTime=Date.now()},onTouchstart:function(e){this.fireEvents("touchstart",e),this.preTouchTime=Date.now()},onBlur:function(e){Object(h.a)(e.target,e.relatedTarget||document.activeElement)||(this.fireEvents("blur",e),this.clearDelayTimer(),this.isBlurToHide()&&this.delaySetPopupVisible(!1,this.$props.blurDelay))},onContextmenu:function(e){e.preventDefault(),this.fireEvents("contextmenu",e),this.setPopupVisible(!0,e)},onContextmenuClose:function(){this.isContextmenuToShow()&&this.close()},onClick:function(e){if(this.fireEvents("click",e),this.focusTime){var t=void 0;if(this.preClickTime&&this.preTouchTime?t=Math.min(this.preClickTime,this.preTouchTime):this.preClickTime?t=this.preClickTime:this.preTouchTime&&(t=this.preTouchTime),Math.abs(t-this.focusTime)<20)return;this.focusTime=0}this.preClickTime=0,this.preTouchTime=0,this.isClickToShow()&&(this.isClickToHide()||this.isBlurToHide())&&e&&e.preventDefault&&e.preventDefault(),e&&e.domEvent&&e.domEvent.preventDefault();var n=!this.$data.sPopupVisible;(this.isClickToHide()&&!n||n&&this.isClickToShow())&&this.setPopupVisible(!this.$data.sPopupVisible,e)},onPopupMouseDown:function(){var e=this,t=this.vcTriggerContext,n=void 0===t?{}:t;this.hasPopupMouseDown=!0,clearTimeout(this.mouseDownTimeout),this.mouseDownTimeout=setTimeout((function(){e.hasPopupMouseDown=!1}),0),n.onPopupMouseDown&&n.onPopupMouseDown.apply(n,arguments)},onDocumentClick:function(e){if(!this.$props.mask||this.$props.maskClosable){var t=e.target,n=this.$el;Object(h.a)(n,t)||this.hasPopupMouseDown||this.close()}},getPopupDomNode:function(){return this._component&&this._component.getPopupDomNode?this._component.getPopupDomNode():null},getRootDomNode:function(){return this.$el},handleGetPopupClassFromAlign:function(e){var t=[],n=this.$props,i=n.popupPlacement,r=n.builtinPlacements,a=n.prefixCls,o=n.alignPoint,s=n.getPopupClassNameFromAlign;return i&&r&&t.push(function(e,t,n,i){var r=n.points;for(var a in e)if(e.hasOwnProperty(a)&&_e(e[a].points,r,i))return t+"-placement-"+a;return""}(r,a,e,o)),s&&t.push(s(e)),t.join(" ")},getPopupAlign:function(){var e=this.$props,t=e.popupPlacement,n=e.popupAlign,i=e.builtinPlacements;return t&&i?function(e,t,n){var i=e[t]||{};return a()({},i,n)}(i,t,n):n},savePopup:function(e){this._component=e,this.savePopupRef(e)},getComponent:function(){var e=this.$createElement,t={};this.isMouseEnterToShow()&&(t.mouseenter=this.onPopupMouseenter),this.isMouseLeaveToHide()&&(t.mouseleave=this.onPopupMouseleave),t.mousedown=this.onPopupMouseDown,t.touchstart=this.onPopupMouseDown;var n=this.handleGetPopupClassFromAlign,i=this.getRootDomNode,r=this.getContainer,o=this.$props,s=o.prefixCls,c=o.destroyPopupOnHide,l=o.popupClassName,u=o.action,h=o.popupAnimation,f=o.popupTransitionName,p=o.popupStyle,v=o.mask,m=o.maskAnimation,g=o.maskTransitionName,b=o.zIndex,y=o.stretch,C=o.alignPoint,x=this.$data,k=x.sPopupVisible,w=x.point,z={props:{prefixCls:s,destroyPopupOnHide:c,visible:k,point:C&&w,action:u,align:this.getPopupAlign(),animation:h,getClassNameFromAlign:n,stretch:y,getRootDomNode:i,mask:v,zIndex:b,transitionName:f,maskAnimation:m,maskTransitionName:g,getContainer:r,popupClassName:l,popupStyle:p},on:a()({align:Object(d.k)(this).popupAlign||Le},t),directives:[{name:"ant-ref",value:this.savePopup}]};return e(je,z,[Object(d.g)(this,"popup")])},getContainer:function(){var e=this.$props,t=this.dialogContext,n=document.createElement("div");return n.style.position="absolute",n.style.top="0",n.style.left="0",n.style.width="100%",(e.getPopupContainer?e.getPopupContainer(this.$el,t):e.getDocument().body).appendChild(n),this.popupContainer=n,n},setPopupVisible:function(e,t){var n=this.alignPoint,i=this.sPopupVisible;if(this.clearDelayTimer(),i!==e){Object(d.s)(this,"popupVisible")||this.setState({sPopupVisible:e,prevPopupVisible:i});var r=Object(d.k)(this);r.popupVisibleChange&&r.popupVisibleChange(e)}n&&t&&this.setPoint(t)},setPoint:function(e){this.$props.alignPoint&&e&&this.setState({point:{pageX:e.pageX,pageY:e.pageY}})},delaySetPopupVisible:function(e,t,n){var i=this,r=1e3*t;if(this.clearDelayTimer(),r){var a=n?{pageX:n.pageX,pageY:n.pageY}:null;this.delayTimer=Object(f.b)((function(){i.setPopupVisible(e,a),i.clearDelayTimer()}),r)}else this.setPopupVisible(e,n)},clearDelayTimer:function(){this.delayTimer&&(Object(f.a)(this.delayTimer),this.delayTimer=null)},clearOutsideHandler:function(){this.clickOutsideHandler&&(this.clickOutsideHandler.remove(),this.clickOutsideHandler=null),this.contextmenuOutsideHandler1&&(this.contextmenuOutsideHandler1.remove(),this.contextmenuOutsideHandler1=null),this.contextmenuOutsideHandler2&&(this.contextmenuOutsideHandler2.remove(),this.contextmenuOutsideHandler2=null),this.touchOutsideHandler&&(this.touchOutsideHandler.remove(),this.touchOutsideHandler=null)},createTwoChains:function(e){var t=function(){},n=Object(d.k)(this);return this.childOriginEvents[e]&&n[e]?this["fire"+e]:t=this.childOriginEvents[e]||n[e]||t},isClickToShow:function(){var e=this.$props,t=e.action,n=e.showAction;return-1!==t.indexOf("click")||-1!==n.indexOf("click")},isContextmenuToShow:function(){var e=this.$props,t=e.action,n=e.showAction;return-1!==t.indexOf("contextmenu")||-1!==n.indexOf("contextmenu")},isClickToHide:function(){var e=this.$props,t=e.action,n=e.hideAction;return-1!==t.indexOf("click")||-1!==n.indexOf("click")},isMouseEnterToShow:function(){var e=this.$props,t=e.action,n=e.showAction;return-1!==t.indexOf("hover")||-1!==n.indexOf("mouseenter")},isMouseLeaveToHide:function(){var e=this.$props,t=e.action,n=e.hideAction;return-1!==t.indexOf("hover")||-1!==n.indexOf("mouseleave")},isFocusToShow:function(){var e=this.$props,t=e.action,n=e.showAction;return-1!==t.indexOf("focus")||-1!==n.indexOf("focus")},isBlurToHide:function(){var e=this.$props,t=e.action,n=e.hideAction;return-1!==t.indexOf("focus")||-1!==n.indexOf("blur")},forcePopupAlign:function(){this.$data.sPopupVisible&&this._component&&this._component.$refs.alignInstance&&this._component.$refs.alignInstance.forceAlign()},fireEvents:function(e,t){this.childOriginEvents[e]&&this.childOriginEvents[e](t),this.__emit(e,t)},close:function(){this.setPopupVisible(!1)}},render:function(){var e=this,t=arguments[0],n=this.sPopupVisible,i=Object(d.c)(this.$slots.default),r=this.$props,a=r.forceRender,o=r.alignPoint;i.length>1&&Object(v.a)(!1,"Trigger $slots.default.length > 1, just support only one default",!0);var s=i[0];this.childOriginEvents=Object(d.h)(s);var c={props:{},nativeOn:{},key:"trigger"};return this.isContextmenuToShow()?c.nativeOn.contextmenu=this.onContextmenu:c.nativeOn.contextmenu=this.createTwoChains("contextmenu"),this.isClickToHide()||this.isClickToShow()?(c.nativeOn.click=this.onClick,c.nativeOn.mousedown=this.onMousedown,c.nativeOn.touchstart=this.onTouchstart):(c.nativeOn.click=this.createTwoChains("click"),c.nativeOn.mousedown=this.createTwoChains("mousedown"),c.nativeOn.touchstart=this.createTwoChains("onTouchstart")),this.isMouseEnterToShow()?(c.nativeOn.mouseenter=this.onMouseenter,o&&(c.nativeOn.mousemove=this.onMouseMove)):c.nativeOn.mouseenter=this.createTwoChains("mouseenter"),this.isMouseLeaveToHide()?c.nativeOn.mouseleave=this.onMouseleave:c.nativeOn.mouseleave=this.createTwoChains("mouseleave"),this.isFocusToShow()||this.isBlurToHide()?(c.nativeOn.focus=this.onFocus,c.nativeOn.blur=this.onBlur):(c.nativeOn.focus=this.createTwoChains("focus"),c.nativeOn.blur=function(t){!t||t.relatedTarget&&Object(h.a)(t.target,t.relatedTarget)||e.createTwoChains("blur")(t)}),this.trigger=Object(Ce.a)(s,c),t(Fe,{attrs:{parent:this,visible:n,autoMount:!1,forceRender:a,getComponent:this.getComponent,getContainer:this.getContainer,children:function(t){var n=t.renderComponent;return e.renderComponent=n,e.trigger}}})}};t.a=Ae},function(e,t,n){var i=n(33),r=n(347),a=n(196),o=Math.max,s=Math.min;e.exports=function(e,t,n){var c,l,u,h,d,f,p=0,v=!1,m=!1,g=!0;if("function"!=typeof e)throw new TypeError("Expected a function");function b(t){var n=c,i=l;return c=l=void 0,p=t,h=e.apply(i,n)}function y(e){return p=e,d=setTimeout(x,t),v?b(e):h}function C(e){var n=e-f;return void 0===f||n>=t||n<0||m&&e-p>=u}function x(){var e=r();if(C(e))return k(e);d=setTimeout(x,function(e){var n=t-(e-f);return m?s(n,u-(e-p)):n}(e))}function k(e){return d=void 0,g&&c?b(e):(c=l=void 0,h)}function w(){var e=r(),n=C(e);if(c=arguments,l=this,f=e,n){if(void 0===d)return y(f);if(m)return clearTimeout(d),d=setTimeout(x,t),b(f)}return void 0===d&&(d=setTimeout(x,t)),h}return t=a(t)||0,i(n)&&(v=!!n.leading,u=(m="maxWait"in n)?o(a(n.maxWait)||0,t):u,g="trailing"in n?!!n.trailing:g),w.cancel=function(){void 0!==d&&clearTimeout(d),p=0,c=f=l=d=void 0},w.flush=function(){return void 0===d?h:k(r())},w}},function(e,t,n){"use strict";var i=n(3),r=n.n(i),a=n(2),o=n.n(a),s=n(9),c=n(12),l=n.n(c),u=n(0),h=n(28),d={adjustX:1,adjustY:1},f=[0,0],p={left:{points:["cr","cl"],overflow:d,offset:[-4,0],targetOffset:f},right:{points:["cl","cr"],overflow:d,offset:[4,0],targetOffset:f},top:{points:["bc","tc"],overflow:d,offset:[0,-4],targetOffset:f},bottom:{points:["tc","bc"],overflow:d,offset:[0,4],targetOffset:f},topLeft:{points:["bl","tl"],overflow:d,offset:[0,-4],targetOffset:f},leftTop:{points:["tr","tl"],overflow:d,offset:[-4,0],targetOffset:f},topRight:{points:["br","tr"],overflow:d,offset:[0,-4],targetOffset:f},rightTop:{points:["tl","tr"],overflow:d,offset:[4,0],targetOffset:f},bottomRight:{points:["tr","br"],overflow:d,offset:[0,4],targetOffset:f},rightBottom:{points:["bl","br"],overflow:d,offset:[4,0],targetOffset:f},bottomLeft:{points:["tl","bl"],overflow:d,offset:[0,4],targetOffset:f},leftBottom:{points:["br","bl"],overflow:d,offset:[-4,0],targetOffset:f}},v={props:{prefixCls:u.a.string,overlay:u.a.any,trigger:u.a.any},updated:function(){var e=this.trigger;e&&e.forcePopupAlign()},render:function(){var e=arguments[0],t=this.overlay,n=this.prefixCls;return e("div",{class:n+"-inner",attrs:{role:"tooltip"}},["function"==typeof t?t():t])}},m=n(1);function g(){}var b={props:{trigger:u.a.any.def(["hover"]),defaultVisible:u.a.bool,visible:u.a.bool,placement:u.a.string.def("right"),transitionName:u.a.oneOfType([u.a.string,u.a.object]),animation:u.a.any,afterVisibleChange:u.a.func.def((function(){})),overlay:u.a.any,overlayStyle:u.a.object,overlayClassName:u.a.string,prefixCls:u.a.string.def("rc-tooltip"),mouseEnterDelay:u.a.number.def(0),mouseLeaveDelay:u.a.number.def(.1),getTooltipContainer:u.a.func,destroyTooltipOnHide:u.a.bool.def(!1),align:u.a.object.def((function(){return{}})),arrowContent:u.a.any.def(null),tipId:u.a.string,builtinPlacements:u.a.object},methods:{getPopupElement:function(){var e=this.$createElement,t=this.$props,n=t.prefixCls,i=t.tipId;return[e("div",{class:n+"-arrow",key:"arrow"},[Object(m.g)(this,"arrowContent")]),e(v,{key:"content",attrs:{trigger:this.$refs.trigger,prefixCls:n,id:i,overlay:Object(m.g)(this,"overlay")}})]},getPopupDomNode:function(){return this.$refs.trigger.getPopupDomNode()}},render:function(e){var t=Object(m.l)(this),n=t.overlayClassName,i=t.trigger,r=t.mouseEnterDelay,a=t.mouseLeaveDelay,s=t.overlayStyle,c=t.prefixCls,u=t.afterVisibleChange,d=t.transitionName,f=t.animation,v=t.placement,b=t.align,y=t.destroyTooltipOnHide,C=t.defaultVisible,x=t.getTooltipContainer,k=l()(t,["overlayClassName","trigger","mouseEnterDelay","mouseLeaveDelay","overlayStyle","prefixCls","afterVisibleChange","transitionName","animation","placement","align","destroyTooltipOnHide","defaultVisible","getTooltipContainer"]),w=o()({},k);Object(m.s)(this,"visible")&&(w.popupVisible=this.$props.visible);var z=Object(m.k)(this),S={props:o()({popupClassName:n,prefixCls:c,action:i,builtinPlacements:p,popupPlacement:v,popupAlign:b,getPopupContainer:x,afterPopupVisibleChange:u,popupTransitionName:d,popupAnimation:f,defaultPopupVisible:C,destroyPopupOnHide:y,mouseLeaveDelay:a,popupStyle:s,mouseEnterDelay:r},w),on:o()({},z,{popupVisibleChange:z.visibleChange||g,popupAlign:z.popupAlign||g}),ref:"trigger"};return e(h.a,S,[e("template",{slot:"popup"},[this.getPopupElement(e)]),this.$slots.default])}},y={adjustX:1,adjustY:1},C={adjustX:0,adjustY:0},x=[0,0];function k(e){return"boolean"==typeof e?e?y:C:o()({},C,e)}var w=n(7),z=n(53),S=Object(z.a)(),O={name:"ATooltip",model:{prop:"visible",event:"visibleChange"},props:o()({},S,{title:u.a.any}),inject:{configProvider:{default:function(){return w.a}}},data:function(){return{sVisible:!!this.$props.visible||!!this.$props.defaultVisible}},watch:{visible:function(e){this.sVisible=e}},methods:{onVisibleChange:function(e){Object(m.s)(this,"visible")||(this.sVisible=!this.isNoTitle()&&e),this.isNoTitle()||this.$emit("visibleChange",e)},getPopupDomNode:function(){return this.$refs.tooltip.getPopupDomNode()},getPlacements:function(){var e=this.$props,t=e.builtinPlacements,n=e.arrowPointAtCenter,i=e.autoAdjustOverflow;return t||function(e){var t=e.arrowWidth,n=void 0===t?5:t,i=e.horizontalArrowShift,r=void 0===i?16:i,a=e.verticalArrowShift,s=void 0===a?12:a,c=e.autoAdjustOverflow,l=void 0===c||c,u={left:{points:["cr","cl"],offset:[-4,0]},right:{points:["cl","cr"],offset:[4,0]},top:{points:["bc","tc"],offset:[0,-4]},bottom:{points:["tc","bc"],offset:[0,4]},topLeft:{points:["bl","tc"],offset:[-(r+n),-4]},leftTop:{points:["tr","cl"],offset:[-4,-(s+n)]},topRight:{points:["br","tc"],offset:[r+n,-4]},rightTop:{points:["tl","cr"],offset:[4,-(s+n)]},bottomRight:{points:["tr","bc"],offset:[r+n,4]},rightBottom:{points:["bl","cr"],offset:[4,s+n]},bottomLeft:{points:["tl","bc"],offset:[-(r+n),4]},leftBottom:{points:["br","cl"],offset:[-4,s+n]}};return Object.keys(u).forEach((function(t){u[t]=e.arrowPointAtCenter?o()({},u[t],{overflow:k(l),targetOffset:x}):o()({},p[t],{overflow:k(l)}),u[t].ignoreShake=!0})),u}({arrowPointAtCenter:n,verticalArrowShift:8,autoAdjustOverflow:i})},getDisabledCompatibleChildren:function(e){var t=this.$createElement,n=e.componentOptions&&e.componentOptions.Ctor.options||{};if((!0===n.__ANT_BUTTON||!0===n.__ANT_SWITCH||!0===n.__ANT_CHECKBOX)&&(e.componentOptions.propsData.disabled||""===e.componentOptions.propsData.disabled)||"button"===e.tag&&e.data&&e.data.attrs&&void 0!==e.data.attrs.disabled){var i=function(e,t){var n={},i=o()({},e);return t.forEach((function(t){e&&t in e&&(n[t]=e[t],delete i[t])})),{picked:n,omitted:i}}(Object(m.q)(e),["position","left","right","top","bottom","float","display","zIndex"]),r=i.picked,a=i.omitted,c=o()({display:"inline-block"},r,{cursor:"not-allowed",width:e.componentOptions.propsData.block?"100%":null}),l=o()({},a,{pointerEvents:"none"});return t("span",{style:c,class:Object(m.f)(e)},[Object(s.a)(e,{style:l,class:null})])}return e},isNoTitle:function(){var e=Object(m.g)(this,"title");return!e&&0!==e},getOverlay:function(){var e=Object(m.g)(this,"title");return 0===e?e:e||""},onPopupAlign:function(e,t){var n=this.getPlacements(),i=Object.keys(n).filter((function(e){return n[e].points[0]===t.points[0]&&n[e].points[1]===t.points[1]}))[0];if(i){var r=e.getBoundingClientRect(),a={top:"50%",left:"50%"};i.indexOf("top")>=0||i.indexOf("Bottom")>=0?a.top=r.height-t.offset[1]+"px":(i.indexOf("Top")>=0||i.indexOf("bottom")>=0)&&(a.top=-t.offset[1]+"px"),i.indexOf("left")>=0||i.indexOf("Right")>=0?a.left=r.width-t.offset[0]+"px":(i.indexOf("right")>=0||i.indexOf("Left")>=0)&&(a.left=-t.offset[0]+"px"),e.style.transformOrigin=a.left+" "+a.top}}},render:function(){var e=arguments[0],t=this.$props,n=this.$data,i=this.$slots,a=t.prefixCls,c=t.openClassName,l=t.getPopupContainer,u=this.configProvider.getPopupContainer,h=this.configProvider.getPrefixCls,d=h("tooltip",a),f=(i.default||[]).filter((function(e){return e.tag||""!==e.text.trim()}));f=1===f.length?f[0]:f;var p=n.sVisible;if(!Object(m.s)(this,"visible")&&this.isNoTitle()&&(p=!1),!f)return null;var v=this.getDisabledCompatibleChildren(Object(m.w)(f)?f:e("span",[f])),g=r()({},c||d+"-open",!0),y={props:o()({},t,{prefixCls:d,getTooltipContainer:l||u,builtinPlacements:this.getPlacements(),overlay:this.getOverlay(),visible:p}),ref:"tooltip",on:o()({},Object(m.k)(this),{visibleChange:this.onVisibleChange,popupAlign:this.onPopupAlign})};return e(b,y,[p?Object(s.a)(v,{class:g}):v])}},M=n(10);O.install=function(e){e.use(M.a),e.component(O.name,O)};t.a=O},function(e,t,n){"use strict";n.d(t,"a",(function(){return a})),n.d(t,"b",(function(){return o}));var i=["moz","ms","webkit"];var r=function(){if("undefined"==typeof window)return function(){};if(window.requestAnimationFrame)return window.requestAnimationFrame.bind(window);var e,t=i.filter((function(e){return e+"RequestAnimationFrame"in window}))[0];return t?window[t+"RequestAnimationFrame"]:(e=0,function(t){var n=(new Date).getTime(),i=Math.max(0,16-(n-e)),r=window.setTimeout((function(){t(n+i)}),i);return e=n+i,r})}(),a=function(e){return function(e){if("undefined"==typeof window)return null;if(window.cancelAnimationFrame)return window.cancelAnimationFrame(e);var t=i.filter((function(e){return e+"CancelAnimationFrame"in window||e+"CancelRequestAnimationFrame"in window}))[0];return t?(window[t+"CancelAnimationFrame"]||window[t+"CancelRequestAnimationFrame"]).call(this,e):clearTimeout(e)}(e.id)},o=function(e,t){var n=Date.now();var i={id:r((function a(){Date.now()-n>=t?e.call():i.id=r(a)}))};return i}},function(e,t,n){var i=n(215);e.exports=function(e,t,n){return null==e?e:i(e,t,n)}},function(e,t){e.exports=function(e){var t=typeof e;return null!=e&&("object"==t||"function"==t)}},function(e,t){var n=Array.isArray;e.exports=n},function(e,t,n){var i=n(61),r=n(135),a=n(44),o=Function.prototype,s=Object.prototype,c=o.toString,l=s.hasOwnProperty,u=c.call(Object);e.exports=function(e){if(!a(e)||"[object Object]"!=i(e))return!1;var t=r(e);if(null===t)return!0;var n=l.call(t,"constructor")&&t.constructor;return"function"==typeof n&&n instanceof n&&c.call(n)==u}},function(e,t,n){"use strict";var i=n(81);t.a=i.a},function(e,t,n){"use strict";n.d(t,"a",(function(){return a})),n.d(t,"b",(function(){return o})),n.d(t,"c",(function(){return s})),n.d(t,"d",(function(){return c})),n.d(t,"g",(function(){return l})),n.d(t,"e",(function(){return h})),n.d(t,"f",(function(){return d}));var i=n(2),r=n.n(i);function a(){return!0}function o(e){return r()({},e,{lastModified:e.lastModified,lastModifiedDate:e.lastModifiedDate,name:e.name,size:e.size,type:e.type,uid:e.uid,percent:0,originFileObj:e})}function s(){var e=.1;return function(t){var n=t;return n>=.98||(n+=e,(e-=.01)<.001&&(e=.001)),n}}function c(e,t){var n=void 0!==e.uid?"uid":"name";return t.filter((function(t){return t[n]===e[n]}))[0]}function l(e,t){var n=void 0!==e.uid?"uid":"name",i=t.filter((function(t){return t[n]!==e[n]}));return i.length===t.length?null:i}var u=function(e){return!!e&&0===e.indexOf("image/")},h=function(e){if(u(e.type))return!0;var t=e.thumbUrl||e.url,n=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"",t=e.split("/"),n=t[t.length-1],i=n.split(/#|\?/)[0];return(/\.[^./\\]*$/.exec(i)||[""])[0]}(t);return!(!/^data:image\//.test(t)&&!/(webp|svg|png|gif|jpg|jpeg|jfif|bmp|dpg|ico)$/i.test(n))||!/^data:/.test(t)&&!n};function d(e){return new Promise((function(t){if(u(e.type)){var n=document.createElement("canvas");n.width=200,n.height=200,n.style.cssText="position: fixed; left: 0; top: 0; width: 200px; height: 200px; z-index: 9999; display: none;",document.body.appendChild(n);var i=n.getContext("2d"),r=new Image;r.onload=function(){var e=r.width,a=r.height,o=200,s=200,c=0,l=0;e0},e.prototype.connect_=function(){i&&!this.connected_&&(document.addEventListener("transitionend",this.onTransitionEnd_),window.addEventListener("resize",this.refresh),s?(this.mutationsObserver_=new MutationObserver(this.refresh),this.mutationsObserver_.observe(document,{attributes:!0,childList:!0,characterData:!0,subtree:!0})):(document.addEventListener("DOMSubtreeModified",this.refresh),this.mutationEventsAdded_=!0),this.connected_=!0)},e.prototype.disconnect_=function(){i&&this.connected_&&(document.removeEventListener("transitionend",this.onTransitionEnd_),window.removeEventListener("resize",this.refresh),this.mutationsObserver_&&this.mutationsObserver_.disconnect(),this.mutationEventsAdded_&&document.removeEventListener("DOMSubtreeModified",this.refresh),this.mutationsObserver_=null,this.mutationEventsAdded_=!1,this.connected_=!1)},e.prototype.onTransitionEnd_=function(e){var t=e.propertyName,n=void 0===t?"":t;o.some((function(e){return!!~n.indexOf(e)}))&&this.refresh()},e.getInstance=function(){return this.instance_||(this.instance_=new e),this.instance_},e.instance_=null,e}(),l=function(e,t){for(var n=0,i=Object.keys(t);n0},e}(),x="undefined"!=typeof WeakMap?new WeakMap:new n,k=function e(t){if(!(this instanceof e))throw new TypeError("Cannot call a class as a function.");if(!arguments.length)throw new TypeError("1 argument required, but only 0 present.");var n=c.getInstance(),i=new C(t,n,this);x.set(this,i)};["observe","unobserve","disconnect"].forEach((function(e){k.prototype[e]=function(){var t;return(t=x.get(this))[e].apply(t,arguments)}}));var w=void 0!==r.ResizeObserver?r.ResizeObserver:k;t.a=w}).call(this,n(134))},function(e,t,n){"use strict";t.__esModule=!0,t.default=function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}},function(e,t,n){"use strict";var i=n(0),r=i.a.oneOf(["hover","focus","click","contextmenu"]);t.a=function(){return{trigger:i.a.oneOfType([r,i.a.arrayOf(r)]).def("hover"),visible:i.a.bool,defaultVisible:i.a.bool,placement:i.a.oneOf(["top","left","right","bottom","topLeft","topRight","bottomLeft","bottomRight","leftTop","leftBottom","rightTop","rightBottom"]).def("top"),transitionName:i.a.string.def("zoom-big-fast"),overlayStyle:i.a.object.def((function(){return{}})),overlayClassName:i.a.string,prefixCls:i.a.string,mouseEnterDelay:i.a.number.def(.1),mouseLeaveDelay:i.a.number.def(.1),getPopupContainer:i.a.func,arrowPointAtCenter:i.a.bool.def(!1),autoAdjustOverflow:i.a.oneOfType([i.a.bool,i.a.object]).def(!0),destroyTooltipOnHide:i.a.bool.def(!1),align:i.a.object.def((function(){return{}})),builtinPlacements:i.a.object}}},function(e,t,n){try{var i=n(180)}catch(e){i=n(180)}var r=/\s+/,a=Object.prototype.toString;function o(e){if(!e||!e.nodeType)throw new Error("A DOM element reference is required");this.el=e,this.list=e.classList}e.exports=function(e){return new o(e)},o.prototype.add=function(e){if(this.list)return this.list.add(e),this;var t=this.array();return~i(t,e)||t.push(e),this.el.className=t.join(" "),this},o.prototype.remove=function(e){if("[object RegExp]"==a.call(e))return this.removeMatching(e);if(this.list)return this.list.remove(e),this;var t=this.array(),n=i(t,e);return~n&&t.splice(n,1),this.el.className=t.join(" "),this},o.prototype.removeMatching=function(e){for(var t=this.array(),n=0;n0&&void 0!==arguments[0]?arguments[0]:{};return Object.keys(e).reduce((function(t,n){var i=e[n];switch(n){case"class":t.className=i,delete t.class;break;default:t[n]=i}return t}),{})}var f=function(){function e(){o()(this,e),this.collection={}}return c()(e,[{key:"clear",value:function(){this.collection={}}},{key:"delete",value:function(e){return delete this.collection[e]}},{key:"get",value:function(e){return this.collection[e]}},{key:"has",value:function(e){return Boolean(this.collection[e])}},{key:"set",value:function(e,t){return this.collection[e]=t,this}},{key:"size",get:function(){return Object.keys(this.collection).length}}]),e}();function p(e,t,n,i){return e(t.tag,i?r()({key:n},i,{attrs:r()({},d(t.attrs),i.attrs)}):{key:n,attrs:r()({},d(t.attrs))},(t.children||[]).map((function(i,r){return p(e,i,n+"-"+t.tag+"-"+r)})))}function v(e){return Object(l.generate)(e)[0]}function m(e,t){switch(t){case"fill":return e+"-fill";case"outline":return e+"-o";case"twotone":return e+"-twotone";default:throw new TypeError("Unknown theme type: "+t+", name: "+e)}}}).call(this,n(97))},function(e,t){var n={}.hasOwnProperty;e.exports=function(e,t){return n.call(e,t)}},function(e,t,n){var i=n(71),r=n(272),a=n(273),o=i?i.toStringTag:void 0;e.exports=function(e){return null==e?void 0===e?"[object Undefined]":"[object Null]":o&&o in Object(e)?r(e):a(e)}},function(e,t,n){var i=n(304),r=n(307);e.exports=function(e,t){var n=r(e,t);return i(n)?n:void 0}},function(e,t){e.exports=function(e,t){return e===t||e!=e&&t!=t}},function(e,t,n){"use strict";var i=n(2),r=n.n(i),a=n(45),o=n(65),s={lang:r()({placeholder:"Select date",rangePlaceholder:["Start date","End date"]},a.a),timePickerLocale:r()({},o.a)};t.a=s},function(e,t,n){"use strict";t.a={placeholder:"Select time"}},function(e,t,n){e.exports=function(){"use strict";return function(e,t,n){(n=n||{}).childrenKeyName=n.childrenKeyName||"children";var i=e||[],r=[],a=0;do{var o=i.filter((function(e){return t(e,a)}))[0];if(!o)break;r.push(o),i=o[n.childrenKeyName]||[],a+=1}while(i.length>0);return r}}()},function(e,t,n){var i=n(49),r=n(86);e.exports=n(50)?function(e,t,n){return i.f(e,t,r(1,n))}:function(e,t,n){return e[t]=n,e}},function(e,t,n){var i=n(84);e.exports=function(e){if(!i(e))throw TypeError(e+" is not an object!");return e}},function(e,t,n){var i=n(170),r=n(124);e.exports=function(e){return i(r(e))}},function(e,t){e.exports={}},function(e,t,n){var i=n(39).Symbol;e.exports=i},function(e,t,n){var i=n(139),r=n(140);e.exports=function(e,t,n,a){var o=!n;n||(n={});for(var s=-1,c=t.length;++s100?100:e}var b=function(e){var t=e.from,n=void 0===t?"#1890ff":t,i=e.to,r=void 0===i?"#1890ff":i,a=e.direction,o=void 0===a?"to right":a,s=p()(e,["from","to","direction"]);return 0!==Object.keys(s).length?{backgroundImage:"linear-gradient("+o+", "+function(e){var t=[],n=!0,i=!1,r=void 0;try{for(var a,o=Object.entries(e)[Symbol.iterator]();!(n=(a=o.next()).done);n=!0){var s=a.value,c=m()(s,2),l=c[0],u=c[1],h=parseFloat(l.replace(/%/g,""));if(isNaN(h))return{};t.push({key:h,value:u})}}catch(e){i=!0,r=e}finally{try{!n&&o.return&&o.return()}finally{if(i)throw r}}return(t=t.sort((function(e,t){return e.key-t.key}))).map((function(e){var t=e.key;return e.value+" "+t+"%"})).join(", ")}(s)+")"}:{backgroundImage:"linear-gradient("+o+", "+n+", "+r+")"}},y={functional:!0,render:function(e,t){var n=t.props,i=t.children,r=n.prefixCls,a=n.percent,s=n.successPercent,c=n.strokeWidth,l=n.size,u=n.strokeColor,h=n.strokeLinecap,d=void 0;d=u&&"string"!=typeof u?b(u):{background:u};var f=o()({width:g(a)+"%",height:(c||("small"===l?6:8))+"px",background:u,borderRadius:"square"===h?0:"100px"},d),p={width:g(s)+"%",height:(c||("small"===l?6:8))+"px",borderRadius:"square"===h?0:""},v=void 0!==s?e("div",{class:r+"-success-bg",style:p}):null;return e("div",[e("div",{class:r+"-outer"},[e("div",{class:r+"-inner"},[e("div",{class:r+"-bg",style:f}),v])]),i])}},C=n(6),x=n.n(C),k=n(17),w=n.n(k),z=n(25),S=n.n(z);var O=function(e){return{mixins:[e],updated:function(){var e=this,t=Date.now(),n=!1;Object.keys(this.paths).forEach((function(i){var r=e.paths[i];if(r){n=!0;var a=r.style;a.transitionDuration=".3s, .3s, .3s, .06s",e.prevTimeStamp&&t-e.prevTimeStamp<100&&(a.transitionDuration="0s, 0s")}})),n&&(this.prevTimeStamp=Date.now())}}},M=l.a.oneOfType([l.a.number,l.a.string]),T={percent:l.a.oneOfType([M,l.a.arrayOf(M)]),prefixCls:l.a.string,strokeColor:l.a.oneOfType([l.a.string,l.a.arrayOf(l.a.oneOfType([l.a.string,l.a.object])),l.a.object]),strokeLinecap:l.a.oneOf(["butt","round","square"]),strokeWidth:M,trailColor:l.a.string,trailWidth:M},V=o()({},T,{gapPosition:l.a.oneOf(["top","bottom","left","right"]),gapDegree:l.a.oneOfType([l.a.number,l.a.string,l.a.bool])}),P=o()({},{percent:0,prefixCls:"rc-progress",strokeColor:"#2db7f5",strokeLinecap:"round",strokeWidth:1,trailColor:"#D9D9D9",trailWidth:1},{gapPosition:"top"});w.a.use(S.a,{name:"ant-ref"});var H=0;function j(e){return+e.replace("%","")}function _(e){return Array.isArray(e)?e:[e]}function L(e,t,n,i){var r=arguments.length>4&&void 0!==arguments[4]?arguments[4]:0,a=arguments[5],o=50-i/2,s=0,c=-o,l=0,u=-2*o;switch(a){case"left":s=-o,c=0,l=2*o,u=0;break;case"right":s=o,c=0,l=-2*o,u=0;break;case"bottom":c=o,u=2*o}var h="M 50,50 m "+s+","+c+"\n a "+o+","+o+" 0 1 1 "+l+","+-u+"\n a "+o+","+o+" 0 1 1 "+-l+","+u,d=2*Math.PI*o,f={stroke:n,strokeDasharray:t/100*(d-r)+"px "+d+"px",strokeDashoffset:"-"+(r/2+e/100*(d-r))+"px",transition:"stroke-dashoffset .3s ease 0s, stroke-dasharray .3s ease 0s, stroke .3s, stroke-width .06s ease .3s, opacity .3s ease 0s"};return{pathString:h,pathStyle:f}}var F=O({props:Object(u.t)(V,P),created:function(){this.paths={},this.gradientId=H,H+=1},methods:{getStokeList:function(){var e=this,t=this.$createElement,n=this.$props,i=n.prefixCls,r=n.percent,a=n.strokeColor,o=n.strokeWidth,s=n.strokeLinecap,c=n.gapDegree,l=n.gapPosition,u=_(r),h=_(a),d=0;return u.map((function(n,r){var a=h[r]||h[h.length-1],u="[object Object]"===Object.prototype.toString.call(a)?"url(#"+i+"-gradient-"+e.gradientId+")":"",f=L(d,n,a,o,c,l),p=f.pathString,v=f.pathStyle;return d+=n,t("path",{key:r,attrs:{d:p,stroke:u,"stroke-linecap":s,"stroke-width":o,opacity:0===n?0:1,"fill-opacity":"0"},class:i+"-circle-path",style:v,directives:[{name:"ant-ref",value:function(t){e.paths[r]=t}}]})}))}},render:function(){var e=arguments[0],t=this.$props,n=t.prefixCls,i=t.strokeWidth,r=t.trailWidth,a=t.gapDegree,o=t.gapPosition,s=t.trailColor,c=t.strokeLinecap,l=t.strokeColor,u=p()(t,["prefixCls","strokeWidth","trailWidth","gapDegree","gapPosition","trailColor","strokeLinecap","strokeColor"]),h=L(0,100,s,i,a,o),d=h.pathString,f=h.pathStyle;delete u.percent;var v=_(l),m=v.find((function(e){return"[object Object]"===Object.prototype.toString.call(e)})),g={attrs:{d:d,stroke:s,"stroke-linecap":c,"stroke-width":r||i,"fill-opacity":"0"},class:n+"-circle-trail",style:f};return e("svg",x()([{class:n+"-circle",attrs:{viewBox:"0 0 100 100"}},u]),[m&&e("defs",[e("linearGradient",{attrs:{id:n+"-gradient-"+this.gradientId,x1:"100%",y1:"0%",x2:"0%",y2:"0%"}},[Object.keys(m).sort((function(e,t){return j(e)-j(t)})).map((function(t,n){return e("stop",{key:n,attrs:{offset:t,"stop-color":m[t]}})}))])]),e("path",g),this.getStokeList().reverse()])}}),E={normal:"#108ee9",exception:"#ff5500",success:"#87d068"};function A(e){var t=e.percent,n=e.successPercent,i=g(t);if(!n)return i;var r=g(n);return[n,g(i-r)]}var $={functional:!0,render:function(e,t){var n,i,a,o,s,c=t.props,l=t.children,u=c.prefixCls,h=c.width,d=c.strokeWidth,f=c.trailColor,p=c.strokeLinecap,v=c.gapPosition,m=c.gapDegree,g=c.type,b=h||120,y={width:"number"==typeof b?b+"px":b,height:"number"==typeof b?b+"px":b,fontSize:.15*b+6},C=d||6,x=v||"dashboard"===g&&"bottom"||"top",k=m||"dashboard"===g&&75,w=(a=(i=c).progressStatus,o=i.successPercent,s=i.strokeColor||E[a],o?[E.success,s]:s),z="[object Object]"===Object.prototype.toString.call(w);return e("div",{class:(n={},r()(n,u+"-inner",!0),r()(n,u+"-circle-gradient",z),n),style:y},[e(F,{attrs:{percent:A(c),strokeWidth:C,trailWidth:C,strokeColor:w,strokeLinecap:p,trailColor:f,prefixCls:u,gapDegree:k,gapPosition:x}}),l])}},D=["normal","exception","active","success"],I=l.a.oneOf(["line","circle","dashboard"]),R=l.a.oneOf(["default","small"]),N={prefixCls:l.a.string,type:I,percent:l.a.number,successPercent:l.a.number,format:l.a.func,status:l.a.oneOf(D),showInfo:l.a.bool,strokeWidth:l.a.number,strokeLinecap:l.a.oneOf(["butt","round","square"]),strokeColor:l.a.oneOfType([l.a.string,l.a.object]),trailColor:l.a.string,width:l.a.number,gapDegree:l.a.number,gapPosition:l.a.oneOf(["top","bottom","left","right"]),size:R},K={name:"AProgress",props:Object(u.t)(N,{type:"line",percent:0,showInfo:!0,trailColor:"#f3f3f3",size:"default",gapDegree:0,strokeLinecap:"round"}),inject:{configProvider:{default:function(){return h.a}}},methods:{getPercentNumber:function(){var e=this.$props,t=e.successPercent,n=e.percent,i=void 0===n?0:n;return parseInt(void 0!==t?t.toString():i.toString(),10)},getProgressStatus:function(){var e=this.$props.status;return D.indexOf(e)<0&&this.getPercentNumber()>=100?"success":e||"normal"},renderProcessInfo:function(e,t){var n=this.$createElement,i=this.$props,r=i.showInfo,a=i.format,o=i.type,s=i.percent,c=i.successPercent;if(!r)return null;var l=void 0,u=a||this.$scopedSlots.format||function(e){return e+"%"},h="circle"===o||"dashboard"===o?"":"-circle";return a||this.$scopedSlots.format||"exception"!==t&&"success"!==t?l=u(g(s),g(c)):"exception"===t?l=n(d.a,{attrs:{type:"close"+h,theme:"line"===o?"filled":"outlined"}}):"success"===t&&(l=n(d.a,{attrs:{type:"check"+h,theme:"line"===o?"filled":"outlined"}})),n("span",{class:e+"-text",attrs:{title:"string"==typeof l?l:void 0}},[l])}},render:function(){var e,t=arguments[0],n=Object(u.l)(this),i=n.prefixCls,a=n.size,s=n.type,l=n.showInfo,h=this.configProvider.getPrefixCls,d=h("progress",i),f=this.getProgressStatus(),p=this.renderProcessInfo(d,f),v=void 0;if("line"===s){var m={props:o()({},n,{prefixCls:d})};v=t(y,m,[p])}else if("circle"===s||"dashboard"===s){var g={props:o()({},n,{prefixCls:d,progressStatus:f})};v=t($,g,[p])}var b=c()(d,(e={},r()(e,d+"-"+("dashboard"===s?"circle":s),!0),r()(e,d+"-status-"+f,!0),r()(e,d+"-show-info",l),r()(e,d+"-"+a,a),e)),C={on:Object(u.k)(this),class:b};return t("div",C,[v])}},Y=n(10);K.install=function(e){e.use(Y.a),e.component(K.name,K)};t.a=K},function(e,t,n){"use strict";t.a={items_per_page:"/ page",jump_to:"Go to",jump_to_confirm:"confirm",page:"",prev_page:"Previous Page",next_page:"Next Page",prev_5:"Previous 5 Pages",next_5:"Next 5 Pages",prev_3:"Previous 3 Pages",next_3:"Next 3 Pages"}},function(e,t,n){"use strict";var i=n(64);t.a=i.a},function(e,t,n){var i=n(169),r=n(128);e.exports=Object.keys||function(e){return i(e,r)}},function(e,t){e.exports=!0},function(e,t){var n=0,i=Math.random();e.exports=function(e){return"Symbol(".concat(void 0===e?"":e,")_",(++n+i).toString(36))}},function(e,t){t.f={}.propertyIsEnumerable},function(e,t,n){var i=n(124);e.exports=function(e){return Object(i(e))}},function(e,t,n){"use strict";var i=n(253)(!0);n(172)(String,"String",(function(e){this._t=String(e),this._i=0}),(function(){var e,t=this._t,n=this._i;return n>=t.length?{value:void 0,done:!0}:(e=i(t,n),this._i+=e.length,{value:e,done:!1})}))},function(e,t){var n,i,r=e.exports={};function a(){throw new Error("setTimeout has not been defined")}function o(){throw new Error("clearTimeout has not been defined")}function s(e){if(n===setTimeout)return setTimeout(e,0);if((n===a||!n)&&setTimeout)return n=setTimeout,setTimeout(e,0);try{return n(e,0)}catch(t){try{return n.call(null,e,0)}catch(t){return n.call(this,e,0)}}}!function(){try{n="function"==typeof setTimeout?setTimeout:a}catch(e){n=a}try{i="function"==typeof clearTimeout?clearTimeout:o}catch(e){i=o}}();var c,l=[],u=!1,h=-1;function d(){u&&c&&(u=!1,c.length?l=c.concat(l):h=-1,l.length&&f())}function f(){if(!u){var e=s(d);u=!0;for(var t=l.length;t;){for(c=l,l=[];++h1)for(var n=1;n-1&&e%1==0&&e0;var a=function(e,t){for(var n=Object.create(null),i=e.split(","),r=0;r1),t})),s(e,u(e),n),l&&(n=r(n,7,c));for(var h=t.length;h--;)a(n,t[h]);return n}));e.exports=h},function(e,t,n){var i=n(368),r=n(106),a=n(107),o=a&&a.isRegExp,s=o?r(o):i;e.exports=s},function(e,t,n){"use strict";(function(e){function n(){return(n=Object.assign||function(e){for(var t=1;t=a)return e;switch(e){case"%s":return String(t[i++]);case"%d":return Number(t[i++]);case"%j":try{return JSON.stringify(t[i++])}catch(e){return"[Circular]"}break;default:return e}}));return o}return r}function d(e,t){return null==e||(!("array"!==t||!Array.isArray(e)||e.length)||!(!function(e){return"string"===e||"url"===e||"hex"===e||"email"===e||"date"===e||"pattern"===e}(t)||"string"!=typeof e||e))}function f(e,t,n){var i=0,r=e.length;!function a(o){if(o&&o.length)n(o);else{var s=i;i+=1,s()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/,url:new RegExp("^(?!mailto:)(?:(?:http|https|ftp)://|//)(?:\\S+(?::\\S*)?@)?(?:(?:(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}(?:\\.(?:[0-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))|(?:(?:[a-z\\u00a1-\\uffff0-9]+-*)*[a-z\\u00a1-\\uffff0-9]+)(?:\\.(?:[a-z\\u00a1-\\uffff0-9]+-*)*[a-z\\u00a1-\\uffff0-9]+)*(?:\\.(?:[a-z\\u00a1-\\uffff]{2,})))|localhost)(?::\\d{2,5})?(?:(/|\\?|#)[^\\s]*)?$","i"),hex:/^#?([a-f0-9]{6}|[a-f0-9]{3})$/i},C={integer:function(e){return C.number(e)&&parseInt(e,10)===e},float:function(e){return C.number(e)&&!C.integer(e)},array:function(e){return Array.isArray(e)},regexp:function(e){if(e instanceof RegExp)return!0;try{return!!new RegExp(e)}catch(e){return!1}},date:function(e){return"function"==typeof e.getTime&&"function"==typeof e.getMonth&&"function"==typeof e.getYear&&!isNaN(e.getTime())},number:function(e){return!isNaN(e)&&"number"==typeof e},object:function(e){return"object"==typeof e&&!C.array(e)},method:function(e){return"function"==typeof e},email:function(e){return"string"==typeof e&&!!e.match(y.email)&&e.length<255},url:function(e){return"string"==typeof e&&!!e.match(y.url)},hex:function(e){return"string"==typeof e&&!!e.match(y.hex)}};var x={required:b,whitespace:function(e,t,n,i,r){(/^\s+$/.test(t)||""===t)&&i.push(h(r.messages.whitespace,e.fullField))},type:function(e,t,n,i,r){if(e.required&&void 0===t)b(e,t,n,i,r);else{var a=e.type;["integer","float","array","regexp","object","method","email","number","date","url","hex"].indexOf(a)>-1?C[a](t)||i.push(h(r.messages.types[a],e.fullField,e.type)):a&&typeof t!==e.type&&i.push(h(r.messages.types[a],e.fullField,e.type))}},range:function(e,t,n,i,r){var a="number"==typeof e.len,o="number"==typeof e.min,s="number"==typeof e.max,c=t,l=null,u="number"==typeof t,d="string"==typeof t,f=Array.isArray(t);if(u?l="number":d?l="string":f&&(l="array"),!l)return!1;f&&(c=t.length),d&&(c=t.replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g,"_").length),a?c!==e.len&&i.push(h(r.messages[l].len,e.fullField,e.len)):o&&!s&&ce.max?i.push(h(r.messages[l].max,e.fullField,e.max)):o&&s&&(ce.max)&&i.push(h(r.messages[l].range,e.fullField,e.min,e.max))},enum:function(e,t,n,i,r){e.enum=Array.isArray(e.enum)?e.enum:[],-1===e.enum.indexOf(t)&&i.push(h(r.messages.enum,e.fullField,e.enum.join(", ")))},pattern:function(e,t,n,i,r){if(e.pattern)if(e.pattern instanceof RegExp)e.pattern.lastIndex=0,e.pattern.test(t)||i.push(h(r.messages.pattern.mismatch,e.fullField,t,e.pattern));else if("string"==typeof e.pattern){new RegExp(e.pattern).test(t)||i.push(h(r.messages.pattern.mismatch,e.fullField,t,e.pattern))}}};function k(e,t,n,i,r){var a=e.type,o=[];if(e.required||!e.required&&i.hasOwnProperty(e.field)){if(d(t,a)&&!e.required)return n();x.required(e,t,i,o,r,a),d(t,a)||x.type(e,t,i,o,r)}n(o)}var w={string:function(e,t,n,i,r){var a=[];if(e.required||!e.required&&i.hasOwnProperty(e.field)){if(d(t,"string")&&!e.required)return n();x.required(e,t,i,a,r,"string"),d(t,"string")||(x.type(e,t,i,a,r),x.range(e,t,i,a,r),x.pattern(e,t,i,a,r),!0===e.whitespace&&x.whitespace(e,t,i,a,r))}n(a)},method:function(e,t,n,i,r){var a=[];if(e.required||!e.required&&i.hasOwnProperty(e.field)){if(d(t)&&!e.required)return n();x.required(e,t,i,a,r),void 0!==t&&x.type(e,t,i,a,r)}n(a)},number:function(e,t,n,i,r){var a=[];if(e.required||!e.required&&i.hasOwnProperty(e.field)){if(""===t&&(t=void 0),d(t)&&!e.required)return n();x.required(e,t,i,a,r),void 0!==t&&(x.type(e,t,i,a,r),x.range(e,t,i,a,r))}n(a)},boolean:function(e,t,n,i,r){var a=[];if(e.required||!e.required&&i.hasOwnProperty(e.field)){if(d(t)&&!e.required)return n();x.required(e,t,i,a,r),void 0!==t&&x.type(e,t,i,a,r)}n(a)},regexp:function(e,t,n,i,r){var a=[];if(e.required||!e.required&&i.hasOwnProperty(e.field)){if(d(t)&&!e.required)return n();x.required(e,t,i,a,r),d(t)||x.type(e,t,i,a,r)}n(a)},integer:function(e,t,n,i,r){var a=[];if(e.required||!e.required&&i.hasOwnProperty(e.field)){if(d(t)&&!e.required)return n();x.required(e,t,i,a,r),void 0!==t&&(x.type(e,t,i,a,r),x.range(e,t,i,a,r))}n(a)},float:function(e,t,n,i,r){var a=[];if(e.required||!e.required&&i.hasOwnProperty(e.field)){if(d(t)&&!e.required)return n();x.required(e,t,i,a,r),void 0!==t&&(x.type(e,t,i,a,r),x.range(e,t,i,a,r))}n(a)},array:function(e,t,n,i,r){var a=[];if(e.required||!e.required&&i.hasOwnProperty(e.field)){if(null==t&&!e.required)return n();x.required(e,t,i,a,r,"array"),null!=t&&(x.type(e,t,i,a,r),x.range(e,t,i,a,r))}n(a)},object:function(e,t,n,i,r){var a=[];if(e.required||!e.required&&i.hasOwnProperty(e.field)){if(d(t)&&!e.required)return n();x.required(e,t,i,a,r),void 0!==t&&x.type(e,t,i,a,r)}n(a)},enum:function(e,t,n,i,r){var a=[];if(e.required||!e.required&&i.hasOwnProperty(e.field)){if(d(t)&&!e.required)return n();x.required(e,t,i,a,r),void 0!==t&&x.enum(e,t,i,a,r)}n(a)},pattern:function(e,t,n,i,r){var a=[];if(e.required||!e.required&&i.hasOwnProperty(e.field)){if(d(t,"string")&&!e.required)return n();x.required(e,t,i,a,r),d(t,"string")||x.pattern(e,t,i,a,r)}n(a)},date:function(e,t,n,i,r){var a=[];if(e.required||!e.required&&i.hasOwnProperty(e.field)){if(d(t,"date")&&!e.required)return n();var o;if(x.required(e,t,i,a,r),!d(t,"date"))o=t instanceof Date?t:new Date(t),x.type(e,o,i,a,r),o&&x.range(e,o.getTime(),i,a,r)}n(a)},url:k,hex:k,email:k,required:function(e,t,n,i,r){var a=[],o=Array.isArray(t)?"array":typeof t;x.required(e,t,i,a,r,o),n(a)},any:function(e,t,n,i,r){var a=[];if(e.required||!e.required&&i.hasOwnProperty(e.field)){if(d(t)&&!e.required)return n();x.required(e,t,i,a,r)}n(a)}};function z(){return{default:"Validation error on field %s",required:"%s is required",enum:"%s must be one of %s",whitespace:"%s cannot be empty",date:{format:"%s date %s is invalid for format %s",parse:"%s date could not be parsed, %s is invalid ",invalid:"%s date %s is invalid"},types:{string:"%s is not a %s",method:"%s is not a %s (function)",array:"%s is not an %s",object:"%s is not an %s",number:"%s is not a %s",date:"%s is not a %s",boolean:"%s is not a %s",integer:"%s is not an %s",float:"%s is not a %s",regexp:"%s is not a valid %s",email:"%s is not a valid %s",url:"%s is not a valid %s",hex:"%s is not a valid %s"},string:{len:"%s must be exactly %s characters",min:"%s must be at least %s characters",max:"%s cannot be longer than %s characters",range:"%s must be between %s and %s characters"},number:{len:"%s must equal %s",min:"%s cannot be less than %s",max:"%s cannot be greater than %s",range:"%s must be between %s and %s"},array:{len:"%s must be exactly %s in length",min:"%s cannot be less than %s in length",max:"%s cannot be greater than %s in length",range:"%s must be between %s and %s in length"},pattern:{mismatch:"%s value %s does not match pattern %s"},clone:function(){var e=JSON.parse(JSON.stringify(this));return e.clone=this.clone,e}}}var S=z();function O(e){this.rules=null,this._messages=S,this.define(e)}O.prototype={messages:function(e){return e&&(this._messages=g(z(),e)),this._messages},define:function(e){if(!e)throw new Error("Cannot configure a schema with no rules");if("object"!=typeof e||Array.isArray(e))throw new Error("Rules must be an object");var t,n;for(t in this.rules={},e)e.hasOwnProperty(t)&&(n=e[t],this.rules[t]=Array.isArray(n)?n:[n])},validate:function(e,t,i){var r=this;void 0===t&&(t={}),void 0===i&&(i=function(){});var a,o,s=e,c=t,l=i;if("function"==typeof c&&(l=c,c={}),!this.rules||0===Object.keys(this.rules).length)return l&&l(),Promise.resolve();if(c.messages){var d=this.messages();d===S&&(d=z()),g(d,c.messages),c.messages=d}else c.messages=this.messages();var f={};(c.keys||Object.keys(this.rules)).forEach((function(t){a=r.rules[t],o=s[t],a.forEach((function(i){var a=i;"function"==typeof a.transform&&(s===e&&(s=n({},s)),o=s[t]=a.transform(o)),(a="function"==typeof a?{validator:a}:n({},a)).validator=r.getValidationMethod(a),a.field=t,a.fullField=a.fullField||t,a.type=r.getType(a),a.validator&&(f[t]=f[t]||[],f[t].push({rule:a,value:o,source:s,field:t}))}))}));var p={};return v(f,c,(function(e,t){var i,r=e.rule,a=!("object"!==r.type&&"array"!==r.type||"object"!=typeof r.fields&&"object"!=typeof r.defaultField);function o(e,t){return n({},t,{fullField:r.fullField+"."+e})}function s(i){void 0===i&&(i=[]);var s=i;if(Array.isArray(s)||(s=[s]),!c.suppressWarning&&s.length&&O.warning("async-validator:",s),s.length&&void 0!==r.message&&(s=[].concat(r.message)),s=s.map(m(r)),c.first&&s.length)return p[r.field]=1,t(s);if(a){if(r.required&&!e.value)return void 0!==r.message?s=[].concat(r.message).map(m(r)):c.error&&(s=[c.error(r,h(c.messages.required,r.field))]),t(s);var l={};if(r.defaultField)for(var u in e.value)e.value.hasOwnProperty(u)&&(l[u]=r.defaultField);for(var d in l=n({},l,e.rule.fields))if(l.hasOwnProperty(d)){var f=Array.isArray(l[d])?l[d]:[l[d]];l[d]=f.map(o.bind(null,d))}var v=new O(l);v.messages(c.messages),e.rule.options&&(e.rule.options.messages=c.messages,e.rule.options.error=c.error),v.validate(e.value,e.rule.options||c,(function(e){var n=[];s&&s.length&&n.push.apply(n,s),e&&e.length&&n.push.apply(n,e),t(n.length?n:null)}))}else t(s)}a=a&&(r.required||!r.required&&e.value),r.field=e.field,r.asyncValidator?i=r.asyncValidator(r,e.value,s,e.source,c):r.validator&&(!0===(i=r.validator(r,e.value,s,e.source,c))?s():!1===i?s(r.message||r.field+" fails"):i instanceof Array?s(i):i instanceof Error&&s(i.message)),i&&i.then&&i.then((function(){return s()}),(function(e){return s(e)}))}),(function(e){!function(e){var t,n,i,r=[],a={};for(t=0;t0?i:n)(e)}},function(e,t,n){var i=n(127)("keys"),r=n(93);e.exports=function(e){return i[e]||(i[e]=r(e))}},function(e,t,n){var i=n(43),r=n(48),a=r["__core-js_shared__"]||(r["__core-js_shared__"]={});(e.exports=function(e,t){return a[e]||(a[e]=void 0!==t?t:{})})("versions",[]).push({version:i.version,mode:n(92)?"pure":"global",copyright:"© 2020 Denis Pushkarev (zloirock.ru)"})},function(e,t){e.exports="constructor,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,toLocaleString,toString,valueOf".split(",")},function(e,t){t.f=Object.getOwnPropertySymbols},function(e,t,n){var i=n(49).f,r=n(60),a=n(38)("toStringTag");e.exports=function(e,t,n){e&&!r(e=n?e:e.prototype,a)&&i(e,a,{configurable:!0,value:t})}},function(e,t,n){n(258);for(var i=n(48),r=n(67),a=n(70),o=n(38)("toStringTag"),s="CSSRuleList,CSSStyleDeclaration,CSSValueList,ClientRectList,DOMRectList,DOMStringList,DOMTokenList,DataTransferItemList,FileList,HTMLAllCollection,HTMLCollection,HTMLFormElement,HTMLSelectElement,MediaList,MimeTypeArray,NamedNodeMap,NodeList,PaintRequestList,Plugin,PluginArray,SVGLengthList,SVGNumberList,SVGPathSegList,SVGPointList,SVGStringList,SVGTransformList,SourceBufferList,StyleSheetList,TextTrackCueList,TextTrackList,TouchList".split(","),c=0;c-1&&e%1==0&&e<=9007199254740991}},function(e,t){var n=Object.prototype;e.exports=function(e){var t=e&&e.constructor;return e===("function"==typeof t&&t.prototype||n)}},function(e,t,n){var i=n(332),r=n(187),a=Object.prototype.propertyIsEnumerable,o=Object.getOwnPropertySymbols,s=o?function(e){return null==e?[]:(e=Object(e),i(o(e),(function(t){return a.call(e,t)})))}:r;e.exports=s},function(e,t){e.exports=function(e,t){for(var n=-1,i=t.length,r=e.length;++n0&&(t.percent=t.loaded/t.total*100),e.onProgress(t)});var n=new window.FormData;e.data&&Object.keys(e.data).forEach((function(t){var i=e.data[t];Array.isArray(i)?i.forEach((function(e){n.append(t+"[]",e)})):n.append(t,e.data[t])})),n.append(e.filename,e.file),t.onerror=function(t){e.onError(t)},t.onload=function(){if(t.status<200||t.status>=300)return e.onError(function(e,t){var n="cannot "+e.method+" "+e.action+" "+t.status+"'",i=new Error(n);return i.status=t.status,i.method=e.method,i.url=e.action,i}(e,t),p(t));e.onSuccess(p(t),t)},t.open(e.method,e.action,!0),e.withCredentials&&"withCredentials"in t&&(t.withCredentials=!0);var i=e.headers||{};for(var r in null!==i["X-Requested-With"]&&t.setRequestHeader("X-Requested-With","XMLHttpRequest"),i)i.hasOwnProperty(r)&&null!==i[r]&&t.setRequestHeader(r,i[r]);return t.send(n),{abort:function(){t.abort()}}}var m=+new Date,g=0;function b(){return"vc-upload-"+m+"-"+ ++g}var y=function(e,t){if(e&&t){var n=Array.isArray(t)?t:t.split(","),i=e.name||"",r=e.type||"",a=r.replace(/\/.*$/,"");return n.some((function(e){var t,n,o=e.trim();return"."===o.charAt(0)?(t=i.toLowerCase(),n=o.toLowerCase(),-1!==t.indexOf(n,t.length-n.length)):/\/\*$/.test(o)?a===o.replace(/\/.*$/,""):r===o}))}return!0};var C=function(e,t,n){var i=function e(i,r){r=r||"",i.isFile?i.file((function(e){n(e)&&(i.fullPath&&!e.webkitRelativePath&&(Object.defineProperties(e,{webkitRelativePath:{writable:!0}}),e.webkitRelativePath=i.fullPath.replace(/^\//,""),Object.defineProperties(e,{webkitRelativePath:{writable:!1}})),t([e]))})):i.isDirectory&&function(e,t){var n=e.createReader(),i=[];!function e(){n.readEntries((function(n){var r=Array.prototype.slice.apply(n);i=i.concat(r),!r.length?t(i):e()}))}()}(i,(function(t){t.forEach((function(t){e(t,""+r+i.name+"/")}))}))},r=!0,a=!1,o=void 0;try{for(var s,c=e[Symbol.iterator]();!(r=(s=c.next()).done);r=!0){i(s.value.webkitGetAsEntry())}}catch(e){a=!0,o=e}finally{try{!r&&c.return&&c.return()}finally{if(a)throw o}}},x={componentTag:a.a.string,prefixCls:a.a.string,name:a.a.string,multiple:a.a.bool,directory:a.a.bool,disabled:a.a.bool,accept:a.a.string,data:a.a.oneOfType([a.a.object,a.a.func]),action:a.a.oneOfType([a.a.string,a.a.func]),headers:a.a.object,beforeUpload:a.a.func,customRequest:a.a.func,withCredentials:a.a.bool,openFileDialogOnClick:a.a.bool,transformFile:a.a.func,method:a.a.string},k={inheritAttrs:!1,name:"ajaxUploader",mixins:[s.a],props:x,data:function(){return this.reqs={},{uid:b()}},mounted:function(){this._isMounted=!0},beforeDestroy:function(){this._isMounted=!1,this.abort()},methods:{onChange:function(e){var t=e.target.files;this.uploadFiles(t),this.reset()},onClick:function(){var e=this.$refs.fileInputRef;e&&e.click()},onKeyDown:function(e){"Enter"===e.key&&this.onClick()},onFileDrop:function(e){var t=this,n=this.$props.multiple;if(e.preventDefault(),"dragover"!==e.type)if(this.directory)C(e.dataTransfer.items,this.uploadFiles,(function(e){return y(e,t.accept)}));else{var i=h()(Array.prototype.slice.call(e.dataTransfer.files),(function(e){return y(e,t.accept)})),r=i[0],a=i[1];!1===n&&(r=r.slice(0,1)),this.uploadFiles(r),a.length&&this.$emit("reject",a)}},uploadFiles:function(e){var t=this,n=Array.prototype.slice.call(e);n.map((function(e){return e.uid=b(),e})).forEach((function(e){t.upload(e,n)}))},upload:function(e,t){var n=this;if(!this.beforeUpload)return setTimeout((function(){return n.post(e)}),0);var i=this.beforeUpload(e,t);i&&i.then?i.then((function(t){var i=Object.prototype.toString.call(t);return"[object File]"===i||"[object Blob]"===i?n.post(t):n.post(e)})).catch((function(e){console&&console.log(e)})):!1!==i&&setTimeout((function(){return n.post(e)}),0)},post:function(e){var t=this;if(this._isMounted){var n=this.$props,i=n.data,r=n.transformFile,a=void 0===r?function(e){return e}:r;new Promise((function(n){var i=t.action;if("function"==typeof i)return n(i(e));n(i)})).then((function(r){var o=e.uid,s=t.customRequest||v;Promise.resolve(a(e)).catch((function(e){console.error(e)})).then((function(a){"function"==typeof i&&(i=i(e));var c={action:r,filename:t.name,data:i,file:a,headers:t.headers,withCredentials:t.withCredentials,method:n.method||"post",onProgress:function(n){t.$emit("progress",n,e)},onSuccess:function(n,i){delete t.reqs[o],t.$emit("success",n,e,i)},onError:function(n,i){delete t.reqs[o],t.$emit("error",n,i,e)}};t.reqs[o]=s(c),t.$emit("start",e)}))}))}},reset:function(){this.setState({uid:b()})},abort:function(e){var t=this.reqs;if(e){var n=e;e&&e.uid&&(n=e.uid),t[n]&&t[n].abort&&t[n].abort(),delete t[n]}else Object.keys(t).forEach((function(e){t[e]&&t[e].abort&&t[e].abort(),delete t[e]}))}},render:function(){var e,t=arguments[0],n=this.$props,i=this.$attrs,a=n.componentTag,s=n.prefixCls,c=n.disabled,u=n.multiple,h=n.accept,d=n.directory,p=n.openFileDialogOnClick,v=f()((e={},l()(e,s,!0),l()(e,s+"-disabled",c),e)),m=c?{}:{click:p?this.onClick:function(){},keydown:p?this.onKeyDown:function(){},drop:this.onFileDrop,dragover:this.onFileDrop},g={on:r()({},Object(o.k)(this),m),attrs:{role:"button",tabIndex:c?null:"0"},class:v};return t(a,g,[t("input",{attrs:{id:i.id,type:"file",accept:h,directory:d?"directory":null,webkitdirectory:d?"webkitdirectory":null,multiple:u},ref:"fileInputRef",on:{click:function(e){return e.stopPropagation()},change:this.onChange},key:this.uid,style:{display:"none"}}),this.$slots.default])}},w=n(13),z={position:"absolute",top:0,opacity:0,filter:"alpha(opacity=0)",left:0,zIndex:9999},S={mixins:[s.a],props:{componentTag:a.a.string,disabled:a.a.bool,prefixCls:a.a.string,accept:a.a.string,multiple:a.a.bool,data:a.a.oneOfType([a.a.object,a.a.func]),action:a.a.oneOfType([a.a.string,a.a.func]),name:a.a.string},data:function(){return this.file={},{uploading:!1}},methods:{onLoad:function(){if(this.uploading){var e=this.file,t=void 0;try{var n=this.getIframeDocument(),i=n.getElementsByTagName("script")[0];i&&i.parentNode===n.body&&n.body.removeChild(i),t=n.body.innerHTML,this.$emit("success",t,e)}catch(n){Object(w.a)(!1,"cross domain error for Upload. Maybe server should return document.domain script. see Note from https://github.com/react-component/upload"),t="cross-domain",this.$emit("error",n,null,e)}this.endUpload()}},onChange:function(){var e=this,t=this.getFormInputNode(),n=this.file={uid:b(),name:t.value&&t.value.substring(t.value.lastIndexOf("\\")+1,t.value.length)};this.startUpload();var i=this.$props;if(!i.beforeUpload)return this.post(n);var r=i.beforeUpload(n);r&&r.then?r.then((function(){e.post(n)}),(function(){e.endUpload()})):!1!==r?this.post(n):this.endUpload()},getIframeNode:function(){return this.$refs.iframeRef},getIframeDocument:function(){return this.getIframeNode().contentDocument},getFormNode:function(){return this.getIframeDocument().getElementById("form")},getFormInputNode:function(){return this.getIframeDocument().getElementById("input")},getFormDataNode:function(){return this.getIframeDocument().getElementById("data")},getFileForMultiple:function(e){return this.multiple?[e]:e},getIframeHTML:function(e){var t="",n="";if(e){t=' - - - - - - - - - - -{{end}} \ No newline at end of file diff --git a/web/html/common/prompt_modal.html b/web/html/common/prompt_modal.html deleted file mode 100644 index b91ede03..00000000 --- a/web/html/common/prompt_modal.html +++ /dev/null @@ -1,71 +0,0 @@ -{{define "promptModal"}} - - - - - -{{end}} \ No newline at end of file diff --git a/web/html/common/qrcode_modal.html b/web/html/common/qrcode_modal.html deleted file mode 100644 index 94e750c7..00000000 --- a/web/html/common/qrcode_modal.html +++ /dev/null @@ -1,165 +0,0 @@ -{{define "qrcodeModal"}} - - - - - - - - -{{end}} diff --git a/web/html/common/text_modal.html b/web/html/common/text_modal.html deleted file mode 100644 index d668c792..00000000 --- a/web/html/common/text_modal.html +++ /dev/null @@ -1,56 +0,0 @@ -{{define "textModal"}} - - - - - - -{{end}} diff --git a/web/html/login.html b/web/html/login.html deleted file mode 100644 index 717adc56..00000000 --- a/web/html/login.html +++ /dev/null @@ -1,557 +0,0 @@ - - -{{template "head" .}} - - - - - -
-
- - - - - - - - - - - -
- - - - -

- - {{ i18n "pages.login.hello" }} - {{ i18n "pages.login.title" }} - -

-
-
- - - - - - - - - - - - - - - - - - -
- -
-
-
- - - - - - -    - - - - - - - - - - -
-
-
-
-
-
-
-
-{{template "js" .}} -{{template "component/themeSwitcher" .}} -{{template "component/password" .}} - - - diff --git a/web/html/xui/client_bulk_modal.html b/web/html/xui/client_bulk_modal.html deleted file mode 100644 index 74e49225..00000000 --- a/web/html/xui/client_bulk_modal.html +++ /dev/null @@ -1,250 +0,0 @@ -{{define "clientsBulkModal"}} - - - - - Random - Random+Prefix - Random+Prefix+Num - Random+Prefix+Num+Postfix - Prefix+Num+Postfix - - - - - - - - - - - - - - - - - - - - [[ key ]] - - - - - {{ i18n "none" }} - [[ key ]] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -{{end}} \ No newline at end of file diff --git a/web/html/xui/client_modal.html b/web/html/xui/client_modal.html deleted file mode 100644 index aa62e02a..00000000 --- a/web/html/xui/client_modal.html +++ /dev/null @@ -1,172 +0,0 @@ -{{define "clientsModal"}} - - - {{template "form/client"}} - - -{{end}} diff --git a/web/html/xui/common_sider.html b/web/html/xui/common_sider.html deleted file mode 100644 index df659489..00000000 --- a/web/html/xui/common_sider.html +++ /dev/null @@ -1,65 +0,0 @@ -{{define "menuItems"}} - - - - {{ i18n "menu.dashboard"}} - - - - - - {{ i18n "menu.inbounds"}} - - - - - - {{ i18n "menu.settings"}} - - - - - - {{ i18n "menu.xray"}} - - - - - - {{ i18n "menu.logout"}} - - -{{end}} - - -{{define "commonSider"}} - - - - {{template "menuItems" .}} - - - -
- -
- - - {{template "menuItems" .}} - -
- -{{end}} diff --git a/web/html/xui/component/password.html b/web/html/xui/component/password.html deleted file mode 100644 index 37e4c793..00000000 --- a/web/html/xui/component/password.html +++ /dev/null @@ -1,37 +0,0 @@ -{{define "component/passwordInput"}} - -{{end}} - -{{define "component/password"}} - -{{end}} \ No newline at end of file diff --git a/web/html/xui/component/persianDatepicker.html b/web/html/xui/component/persianDatepicker.html deleted file mode 100644 index df47c4f3..00000000 --- a/web/html/xui/component/persianDatepicker.html +++ /dev/null @@ -1,60 +0,0 @@ -{{define "component/persianDatepickerTemplate"}} - -{{end}} - -{{define "component/persianDatepicker"}} - - - - -{{end}} diff --git a/web/html/xui/component/setting.html b/web/html/xui/component/setting.html deleted file mode 100644 index 8adc000c..00000000 --- a/web/html/xui/component/setting.html +++ /dev/null @@ -1,36 +0,0 @@ -{{define "component/settingListItem"}} - - - - - - - - - - - - - - - - - - - -{{end}} - -{{define "component/setting"}} - -{{end}} diff --git a/web/html/xui/component/sortableTable.html b/web/html/xui/component/sortableTable.html deleted file mode 100644 index f62eba44..00000000 --- a/web/html/xui/component/sortableTable.html +++ /dev/null @@ -1,216 +0,0 @@ -{{define "component/sortableTableTrigger"}} - -{{end}} - -{{define "component/sortableTable"}} - - -{{end}} diff --git a/web/html/xui/component/themeSwitch.html b/web/html/xui/component/themeSwitch.html deleted file mode 100644 index 0de64a84..00000000 --- a/web/html/xui/component/themeSwitch.html +++ /dev/null @@ -1,113 +0,0 @@ -{{define "component/themeSwitchTemplate"}} - -{{end}} - -{{define "component/themeSwitchTemplateLogin"}} - -{{end}} - -{{define "component/themeSwitcher"}} - -{{end}} diff --git a/web/html/xui/dns_modal.html b/web/html/xui/dns_modal.html deleted file mode 100644 index f61cd8b2..00000000 --- a/web/html/xui/dns_modal.html +++ /dev/null @@ -1,114 +0,0 @@ -{{define "dnsModal"}} - - - - - - - - - - - - [[ l ]] - - - - - - - - - - - - -{{end}} \ No newline at end of file diff --git a/web/html/xui/fakedns_modal.html b/web/html/xui/fakedns_modal.html deleted file mode 100644 index 1b4dbe77..00000000 --- a/web/html/xui/fakedns_modal.html +++ /dev/null @@ -1,57 +0,0 @@ -{{define "fakednsModal"}} - - - - - - - - - - - -{{end}} diff --git a/web/html/xui/form/allocate.html b/web/html/xui/form/allocate.html deleted file mode 100644 index aba8d5c9..00000000 --- a/web/html/xui/form/allocate.html +++ /dev/null @@ -1,15 +0,0 @@ -{{define "form/allocate"}} - - - - [[ s ]] - - - - - - - - - -{{end}} diff --git a/web/html/xui/form/client.html b/web/html/xui/form/client.html deleted file mode 100644 index 0b894f01..00000000 --- a/web/html/xui/form/client.html +++ /dev/null @@ -1,172 +0,0 @@ -{{define "form/client"}} - - - - - - - - - - - - - - - - - - - [[ key ]] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {{ i18n "none" }} - [[ key ]] - - - - - - - - - [[ sizeFormat(clientStats.up) ]] / - [[ sizeFormat(clientStats.down) ]] - ([[ sizeFormat(clientStats.up + clientStats.down) ]]) - - - - - - - - - - - - - - - - - Expired - - - - - - -{{end}} \ No newline at end of file diff --git a/web/html/xui/form/inbound.html b/web/html/xui/form/inbound.html deleted file mode 100644 index 091393ff..00000000 --- a/web/html/xui/form/inbound.html +++ /dev/null @@ -1,133 +0,0 @@ -{{define "form/inbound"}} - - - - - - - - - - - - [[ p ]] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {{template "form/sniffing"}} - - - - - - -{{end}} diff --git a/web/html/xui/form/outbound.html b/web/html/xui/form/outbound.html deleted file mode 100644 index 8abef4aa..00000000 --- a/web/html/xui/form/outbound.html +++ /dev/null @@ -1,512 +0,0 @@ -{{define "form/outbound"}} - - - - - - - [[ y ]] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Link: - - - - - - - -{{end}} diff --git a/web/html/xui/form/protocol/dokodemo.html b/web/html/xui/form/protocol/dokodemo.html deleted file mode 100644 index 70ffe7e0..00000000 --- a/web/html/xui/form/protocol/dokodemo.html +++ /dev/null @@ -1,20 +0,0 @@ -{{define "form/dokodemo"}} - - - - - - - - - - TCP,UDP - TCP - UDP - - - - - - -{{end}} diff --git a/web/html/xui/form/protocol/http.html b/web/html/xui/form/protocol/http.html deleted file mode 100644 index b4c55313..00000000 --- a/web/html/xui/form/protocol/http.html +++ /dev/null @@ -1,26 +0,0 @@ -{{define "form/http"}} - - - - - - - -
{{ i18n "username" }}{{ i18n "password" }} - -
- - - - - - - - - - - -
-{{end}} diff --git a/web/html/xui/form/protocol/shadowsocks.html b/web/html/xui/form/protocol/shadowsocks.html deleted file mode 100644 index 1190b672..00000000 --- a/web/html/xui/form/protocol/shadowsocks.html +++ /dev/null @@ -1,50 +0,0 @@ -{{define "form/shadowsocks"}} - - - - - [[ method_name ]] - - - - - - - - - TCP,UDP - TCP - UDP - - - - - - -{{end}} diff --git a/web/html/xui/form/protocol/socks.html b/web/html/xui/form/protocol/socks.html deleted file mode 100644 index 849ec210..00000000 --- a/web/html/xui/form/protocol/socks.html +++ /dev/null @@ -1,34 +0,0 @@ -{{define "form/socks"}} - - - - - - - - - - - - -{{end}} diff --git a/web/html/xui/form/protocol/trojan.html b/web/html/xui/form/protocol/trojan.html deleted file mode 100644 index 09497bcd..00000000 --- a/web/html/xui/form/protocol/trojan.html +++ /dev/null @@ -1,50 +0,0 @@ -{{define "form/trojan"}} - - - {{template "form/client"}} - - - - - - - - - - - - - -
{{ i18n "pages.inbounds.email" }}Password
[[ client.email ]][[ client.password ]]
-
-
- -{{end}} diff --git a/web/html/xui/form/protocol/vless.html b/web/html/xui/form/protocol/vless.html deleted file mode 100644 index f0c9f2e2..00000000 --- a/web/html/xui/form/protocol/vless.html +++ /dev/null @@ -1,50 +0,0 @@ -{{define "form/vless"}} - - - {{template "form/client"}} - - - - - - - - - - - - - -
{{ i18n "pages.inbounds.email" }}ID
[[ client.email ]][[ client.id ]]
-
-
- -{{end}} diff --git a/web/html/xui/form/protocol/vmess.html b/web/html/xui/form/protocol/vmess.html deleted file mode 100644 index 3c5200ac..00000000 --- a/web/html/xui/form/protocol/vmess.html +++ /dev/null @@ -1,23 +0,0 @@ -{{define "form/vmess"}} - - - {{template "form/client"}} - - - - - - - - - - - - - - - -
{{ i18n "pages.inbounds.email" }}ID{{ i18n "security" }}
[[ client.email ]][[ client.id ]][[ client.security ]]
-
-
-{{end}} diff --git a/web/html/xui/form/protocol/wireguard.html b/web/html/xui/form/protocol/wireguard.html deleted file mode 100644 index 31e2e653..00000000 --- a/web/html/xui/form/protocol/wireguard.html +++ /dev/null @@ -1,76 +0,0 @@ -{{define "form/wireguard"}} - - - - - - - - - - - - - - - - - - - Peer [[ index + 1 ]] - - - - - - - - - - - - - - - - - - - - - - -{{end}} \ No newline at end of file diff --git a/web/html/xui/form/reality_settings.html b/web/html/xui/form/reality_settings.html deleted file mode 100644 index 16179458..00000000 --- a/web/html/xui/form/reality_settings.html +++ /dev/null @@ -1,56 +0,0 @@ -{{define "form/realitySettings"}} - -{{end}} \ No newline at end of file diff --git a/web/html/xui/form/sniffing.html b/web/html/xui/form/sniffing.html deleted file mode 100644 index d8a2e85a..00000000 --- a/web/html/xui/form/sniffing.html +++ /dev/null @@ -1,29 +0,0 @@ -{{define "form/sniffing"}} - - - - {{ i18n "enabled" }} - - - - - - - - - -{{end}} diff --git a/web/html/xui/form/stream/external_proxy.html b/web/html/xui/form/stream/external_proxy.html deleted file mode 100644 index defed984..00000000 --- a/web/html/xui/form/stream/external_proxy.html +++ /dev/null @@ -1,29 +0,0 @@ -{{define "form/externalProxy"}} - - - - - - - - - - - - - - - - - -{{end}} diff --git a/web/html/xui/form/stream/stream_grpc.html b/web/html/xui/form/stream/stream_grpc.html deleted file mode 100644 index e74a3c3f..00000000 --- a/web/html/xui/form/stream/stream_grpc.html +++ /dev/null @@ -1,13 +0,0 @@ -{{define "form/streamGRPC"}} - - - - - - - - - - - -{{end}} diff --git a/web/html/xui/form/stream/stream_httpupgrade.html b/web/html/xui/form/stream/stream_httpupgrade.html deleted file mode 100644 index 35dd2db8..00000000 --- a/web/html/xui/form/stream/stream_httpupgrade.html +++ /dev/null @@ -1,26 +0,0 @@ -{{define "form/streamHTTPUpgrade"}} - - - - - - - - - - - - - - - - - - - - - - - - -{{end}} diff --git a/web/html/xui/form/stream/stream_kcp.html b/web/html/xui/form/stream/stream_kcp.html deleted file mode 100644 index 97ca4071..00000000 --- a/web/html/xui/form/stream/stream_kcp.html +++ /dev/null @@ -1,47 +0,0 @@ -{{define "form/streamKCP"}} - - - - None - SRTP - uTP - WeChat - DTLS 1.2 - WireGuard - - - - - - - - - - - - - - - - - - - - - - - - - - - - -{{end}} diff --git a/web/html/xui/form/stream/stream_settings.html b/web/html/xui/form/stream/stream_settings.html deleted file mode 100644 index a3119d9c..00000000 --- a/web/html/xui/form/stream/stream_settings.html +++ /dev/null @@ -1,51 +0,0 @@ -{{define "form/streamSettings"}} - - - - - TCP (RAW) - mKCP - WebSocket - gRPC - HTTPUpgrade - XHTTP - - - - - - - - - - - - - - - - - - - - - - - - -{{end}} diff --git a/web/html/xui/form/stream/stream_sockopt.html b/web/html/xui/form/stream/stream_sockopt.html deleted file mode 100644 index 771d80aa..00000000 --- a/web/html/xui/form/stream/stream_sockopt.html +++ /dev/null @@ -1,66 +0,0 @@ -{{define "form/streamSockopt"}} - - - - - - - -{{end}} diff --git a/web/html/xui/form/stream/stream_tcp.html b/web/html/xui/form/stream/stream_tcp.html deleted file mode 100644 index 31d2f9e8..00000000 --- a/web/html/xui/form/stream/stream_tcp.html +++ /dev/null @@ -1,72 +0,0 @@ -{{define "form/streamTCP"}} - - - - - - - - - - - - - {{ i18n "pages.inbounds.stream.general.request" }} - - - - - - - - - - - - - - - - - - - - - - - - - - {{ i18n "pages.inbounds.stream.general.response" }} - - - - - - - - - - - - - - - - - - - - - - - -{{end}} diff --git a/web/html/xui/form/stream/stream_ws.html b/web/html/xui/form/stream/stream_ws.html deleted file mode 100644 index 65c87ae1..00000000 --- a/web/html/xui/form/stream/stream_ws.html +++ /dev/null @@ -1,29 +0,0 @@ -{{define "form/streamWS"}} - - - - - - - - - - - - - - - - - - - - - - - - - - - -{{end}} diff --git a/web/html/xui/form/stream/stream_xhttp.html b/web/html/xui/form/stream/stream_xhttp.html deleted file mode 100644 index 601d0cb4..00000000 --- a/web/html/xui/form/stream/stream_xhttp.html +++ /dev/null @@ -1,46 +0,0 @@ -{{define "form/streamXHTTP"}} - - - - - - - - - - - - - - - - - - - - - - - [[ key ]] - - - - - - - - - - - - - - - - - - -{{end}} \ No newline at end of file diff --git a/web/html/xui/form/tls_settings.html b/web/html/xui/form/tls_settings.html deleted file mode 100644 index 70ad7d05..00000000 --- a/web/html/xui/form/tls_settings.html +++ /dev/null @@ -1,116 +0,0 @@ -{{define "form/tlsSettings"}} - - - - - - {{ i18n "none" }} - Reality - TLS - - - - - - - - - -{{end}} \ No newline at end of file diff --git a/web/html/xui/inbound_client_table.html b/web/html/xui/inbound_client_table.html deleted file mode 100644 index 13593cea..00000000 --- a/web/html/xui/inbound_client_table.html +++ /dev/null @@ -1,267 +0,0 @@ -{{define "client_table"}} - - - - - - - - -{{end}} diff --git a/web/html/xui/inbound_info_modal.html b/web/html/xui/inbound_info_modal.html deleted file mode 100644 index d9913f32..00000000 --- a/web/html/xui/inbound_info_modal.html +++ /dev/null @@ -1,575 +0,0 @@ -{{define "inboundInfoModal"}} - - - - - - - - - - - - - - - - -
{{ i18n "protocol" }} - [[ dbInbound.protocol ]] -
{{ i18n "pages.inbounds.address" }} - - [[ dbInbound.address ]] - -
{{ i18n "pages.inbounds.port" }} - [[ dbInbound.port ]] -
-
- - - - - - - - - - - - - - - - - -
{{ i18n "encryption" }} - [[ inbound.settings.method ]] -
{{ i18n "password" }} - - [[ inbound.settings.password ]] - -
{{ i18n "pages.inbounds.network" }} - [[ inbound.settings.network ]] -
- - -
- -{{end}} diff --git a/web/html/xui/inbound_modal.html b/web/html/xui/inbound_modal.html deleted file mode 100644 index 4de3518c..00000000 --- a/web/html/xui/inbound_modal.html +++ /dev/null @@ -1,144 +0,0 @@ -{{define "inboundModal"}} - - {{template "form/inbound"}} - - -{{end}} diff --git a/web/html/xui/inbounds.html b/web/html/xui/inbounds.html deleted file mode 100644 index 89a37a29..00000000 --- a/web/html/xui/inbounds.html +++ /dev/null @@ -1,1498 +0,0 @@ - - -{{template "head" .}} - - - - - {{ template "commonSider" . }} - - - - - - - - - - - - {{ i18n "pages.inbounds.totalDownUp" }}: - [[ sizeFormat(total.up) ]] / [[ sizeFormat(total.down) ]] - - - {{ i18n "pages.inbounds.totalUsage" }}: - [[ sizeFormat(total.up + total.down) ]] - - - {{ i18n "pages.inbounds.inboundCount" }}: - [[ dbInbounds.length ]] - - - - - - - - - -
- - - - - - - - - - - - - {{ i18n "pages.inbounds.importInbound" }} - - - - {{ i18n "pages.inbounds.export" }} - - - - {{ i18n "pages.inbounds.export" }} - {{ i18n "pages.settings.subSettings" }} - - - - {{ i18n "pages.inbounds.resetAllTraffic" }} - - - - {{ i18n "pages.inbounds.resetAllClientTraffics" }} - - - - {{ i18n "pages.inbounds.delDepletedClients" }} - - - - - - - [[ key ]]s - - - - - -
-
- - - - - - - {{ i18n "none" }} - {{ i18n "disabled" }} - {{ i18n "depleted" }} - {{ i18n "depletingSoon" }} - {{ i18n "online" }} - -
- - - - - - - - - - - -
-
-
-
-
-
-{{template "js" .}} - - - - - - -{{template "component/themeSwitcher" .}} -{{template "component/persianDatepicker" .}} - - -{{template "inboundModal"}} -{{template "promptModal"}} -{{template "qrcodeModal"}} -{{template "textModal"}} -{{template "inboundInfoModal"}} -{{template "clientsModal"}} -{{template "clientsBulkModal"}} - - diff --git a/web/html/xui/index.html b/web/html/xui/index.html deleted file mode 100644 index 2e57277a..00000000 --- a/web/html/xui/index.html +++ /dev/null @@ -1,667 +0,0 @@ - - -{{template "head" .}} - - - - - {{ template "commonSider" . }} - - - - - - - - - - - - - - - -
CPU: [[ cpuCoreFormat(status.cpuCores) ]] - - -
-
- - -
- {{ i18n "pages.index.memory"}}: [[ sizeFormat(status.mem.current) ]] / [[ sizeFormat(status.mem.total) ]] -
-
-
-
- - - - -
- Swap: [[ sizeFormat(status.swap.current) ]] / [[ sizeFormat(status.swap.total) ]] -
-
- - -
- {{ i18n "pages.index.hard"}}: [[ sizeFormat(status.disk.current) ]] / [[ sizeFormat(status.disk.total) ]] -
-
-
-
-
-
-
-
- - - - - 3X-UI: - v{{ .cur_ver }} - @XrayUI - - - - - {{ i18n "pages.index.operationHours" }}: - Xray: [[ formatSecond(status.appStats.uptime) ]] - OS: [[ formatSecond(status.uptime) ]] - - - - - {{ i18n "pages.index.xrayStatus" }}: - [[ status.xray.state ]] - - An error occurred while running Xray - {{ i18n "pages.index.logs" }} - - - - - {{ i18n "pages.index.stopXray" }} - {{ i18n "pages.index.restartXray" }} - v[[ status.xray.version ]] - - - - - {{ i18n "menu.link" }}: - {{ i18n "pages.index.logs" }} - {{ i18n "pages.index.config" }} - {{ i18n "pages.index.backup" }} - - - - - {{ i18n "pages.index.systemLoad" }}: - - - [[ status.loads[0] ]] | [[ status.loads[1] ]] | [[ status.loads[2] ]] - - - - - - - - {{ i18n "usage"}}: - RAM: [[ sizeFormat(status.appStats.mem) ]] - Threads: [[ status.appStats.threads ]] - - - - - - - - - IPv4 - - - - - - - - IPv6 - - - - - - - - - - - - - - TCP: [[ status.tcpCount ]] - - - - - - - - UDP: [[ status.udpCount ]] - - - - - - - - - - - - - - Up: [[ sizeFormat(status.netIO.up) ]]/s - - - - - - - - Down: [[ sizeFormat(status.netIO.down) ]]/s - - - - - - - - - - - - - - - Out: [[ sizeFormat(status.netTraffic.sent) ]] - - - - - - - - In: [[ sizeFormat(status.netTraffic.recv) ]] - - - - - - - - -
-
-
- - - - - - - - - - - 10 - 20 - 50 - 100 - 500 - - - Debug - Info - Notice - Warning - Error - - - - - SysLog - - - - - - -
-
- - - - - - [[ backupModal.exportText ]] - - - [[ backupModal.importText ]] - - - -
-{{template "js" .}} - -{{template "component/themeSwitcher" .}} -{{template "textModal"}} - - - diff --git a/web/html/xui/settings.html b/web/html/xui/settings.html deleted file mode 100644 index 9eff8bec..00000000 --- a/web/html/xui/settings.html +++ /dev/null @@ -1,852 +0,0 @@ - - -{{template "head" .}} - - - - {{ template "commonSider" . }} - - - - - - - - - - - - - - {{ i18n "pages.settings.save" }} - {{ i18n "pages.settings.restartPanel" }} - - - - - - - - - - - - - - - - - - - - - [[ value ]] - - - [[ key ]] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {{ i18n "pages.settings.security.admin"}} - - - - - - - - - - - - - - - {{ i18n "confirm" }} - - - {{ i18n "pages.settings.security.secret"}} - - - - - - - - - - - - - - - - - - - - - - - - {{ i18n "confirm" }} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - [[ p ]] - - - - - - - Remove - - - Add Noise - - - - - - - - - - - - - - - - - - - - - - - - [[ p ]] - - - - - - - - - - - - - - - - - - - - - - - - - - - - [[ p.label ]] - - - - - - - - - - - - - [[ p.label ]] - - - - - - - - - - - - - - - - -{{template "js" .}} - -{{template "component/themeSwitcher" .}} -{{template "component/password" .}} -{{template "component/setting"}} - - - \ No newline at end of file diff --git a/web/html/xui/warp_modal.html b/web/html/xui/warp_modal.html deleted file mode 100644 index 3acd49e8..00000000 --- a/web/html/xui/warp_modal.html +++ /dev/null @@ -1,246 +0,0 @@ -{{define "warpModal"}} - - - - - -{{end}} \ No newline at end of file diff --git a/web/html/xui/xray.html b/web/html/xui/xray.html deleted file mode 100644 index a1247d9a..00000000 --- a/web/html/xui/xray.html +++ /dev/null @@ -1,2001 +0,0 @@ - - -{{template "head" .}} - - - - - - - - - - - - - - - - - - - - - {{ template "commonSider" . }} - - - - - - - - - - - - - {{ i18n "pages.xray.save" }} - {{ i18n "pages.xray.restart" }} - - Error in running xray-core - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - [[ s ]] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - WARP - - - - - {{ i18n "pages.settings.resetDefaultConfig" }} - - - - - - {{ i18n "pages.xray.rules.add" }} - - - - - - - - - - - - - {{ i18n "pages.xray.outbound.addOutbound" }} - - WARP - - - - - - - - - - - - - - - - - - - {{ i18n "pages.xray.outbound.addReverse" }} - - - - - - - - {{ i18n "pages.xray.balancer.addBalancer"}} - - - - - - - - Observatory - Burst Observatory - - - - - - - - - - - {{ i18n "pages.xray.completeTemplate"}} - {{ i18n "pages.xray.Inbounds" }} - {{ i18n "pages.xray.Outbounds" }} - {{ i18n "pages.xray.Routings" }} - - - - - - - - - -{{template "js" .}} -{{template "component/themeSwitcher" .}} -{{template "component/sortableTable" .}} -{{template "component/setting"}} -{{template "ruleModal"}} -{{template "outModal"}} -{{template "reverseModal"}} -{{template "balancerModal"}} -{{template "dnsModal"}} -{{template "fakednsModal"}} -{{template "warpModal"}} - - - diff --git a/web/html/xui/xray_balancer_modal.html b/web/html/xui/xray_balancer_modal.html deleted file mode 100644 index de2a0acb..00000000 --- a/web/html/xui/xray_balancer_modal.html +++ /dev/null @@ -1,123 +0,0 @@ -{{define "balancerModal"}} - - - - - - - - Random - Round Robin - Least Load - Least Ping - - - - - [[ tag ]] - - - - - [[ tag ]] - - - - - - -{{end}} \ No newline at end of file diff --git a/web/html/xui/xray_outbound_modal.html b/web/html/xui/xray_outbound_modal.html deleted file mode 100644 index fd9cf99c..00000000 --- a/web/html/xui/xray_outbound_modal.html +++ /dev/null @@ -1,127 +0,0 @@ -{{define "outModal"}} - - {{template "form/outbound"}} - - -{{end}} diff --git a/web/html/xui/xray_reverse_modal.html b/web/html/xui/xray_reverse_modal.html deleted file mode 100644 index bb1e4bdf..00000000 --- a/web/html/xui/xray_reverse_modal.html +++ /dev/null @@ -1,138 +0,0 @@ -{{define "reverseModal"}} - - - - - [[ x ]] - - - - - - - - - - - - - -{{end}} diff --git a/web/html/xui/xray_rule_modal.html b/web/html/xui/xray_rule_modal.html deleted file mode 100644 index c2d84dc5..00000000 --- a/web/html/xui/xray_rule_modal.html +++ /dev/null @@ -1,246 +0,0 @@ -{{define "ruleModal"}} - - - - - [[ dm ]] - - - - - - - - - - - - - [[ x ]] - - - - - [[ x ]] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - [[ tag ]] - - - - - [[ tag ]] - - - - - - [[ tag ]] - - - - - -{{end}} diff --git a/web/job/check_client_ip_job.go b/web/job/check_client_ip_job.go deleted file mode 100644 index c960268d..00000000 --- a/web/job/check_client_ip_job.go +++ /dev/null @@ -1,320 +0,0 @@ -package job - -import ( - "bufio" - "encoding/json" - "io" - "log" - "os" - "os/exec" - "regexp" - "sort" - "time" - - "x-ui/database" - "x-ui/database/model" - "x-ui/logger" - "x-ui/xray" -) - -type CheckClientIpJob struct { - lastClear int64 - disAllowedIps []string -} - -var job *CheckClientIpJob - -func NewCheckClientIpJob() *CheckClientIpJob { - job = new(CheckClientIpJob) - return job -} - -func (j *CheckClientIpJob) Run() { - if j.lastClear == 0 { - j.lastClear = time.Now().Unix() - } - - shouldClearAccessLog := false - iplimitActive := j.hasLimitIp() - f2bInstalled := j.checkFail2BanInstalled() - isAccessLogAvailable := j.checkAccessLogAvailable(iplimitActive) - - if iplimitActive { - if f2bInstalled && isAccessLogAvailable { - shouldClearAccessLog = j.processLogFile() - } else { - if !f2bInstalled { - logger.Warning("[LimitIP] Fail2Ban is not installed, Please install Fail2Ban from the x-ui bash menu.") - } - } - } - - if shouldClearAccessLog || (isAccessLogAvailable && time.Now().Unix()-j.lastClear > 3600) { - j.clearAccessLog() - } -} - -func (j *CheckClientIpJob) clearAccessLog() { - logAccessP, err := os.OpenFile(xray.GetAccessPersistentLogPath(), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o644) - j.checkError(err) - - accessLogPath, err := xray.GetAccessLogPath() - j.checkError(err) - - file, err := os.Open(accessLogPath) - j.checkError(err) - - _, err = io.Copy(logAccessP, file) - j.checkError(err) - - logAccessP.Close() - file.Close() - - err = os.Truncate(accessLogPath, 0) - j.checkError(err) - j.lastClear = time.Now().Unix() -} - -func (j *CheckClientIpJob) hasLimitIp() bool { - db := database.GetDB() - var inbounds []*model.Inbound - - err := db.Model(model.Inbound{}).Find(&inbounds).Error - if err != nil { - return false - } - - for _, inbound := range inbounds { - if inbound.Settings == "" { - continue - } - - settings := map[string][]model.Client{} - json.Unmarshal([]byte(inbound.Settings), &settings) - clients := settings["clients"] - - for _, client := range clients { - limitIp := client.LimitIP - if limitIp > 0 { - return true - } - } - } - - return false -} - -func (j *CheckClientIpJob) processLogFile() bool { - - ipRegex := regexp.MustCompile(`from (?:tcp:|udp:)?\[?([0-9a-fA-F\.:]+)\]?:\d+ accepted`) - emailRegex := regexp.MustCompile(`email: (.+)$`) - - accessLogPath, _ := xray.GetAccessLogPath() - file, _ := os.Open(accessLogPath) - defer file.Close() - - inboundClientIps := make(map[string]map[string]struct{}, 100) - - scanner := bufio.NewScanner(file) - for scanner.Scan() { - line := scanner.Text() - - ipMatches := ipRegex.FindStringSubmatch(line) - if len(ipMatches) < 2 { - continue - } - - ip := ipMatches[1] - - if ip == "127.0.0.1" || ip == "::1" { - continue - } - - emailMatches := emailRegex.FindStringSubmatch(line) - if len(emailMatches) < 2 { - continue - } - email := emailMatches[1] - - if _, exists := inboundClientIps[email]; !exists { - inboundClientIps[email] = make(map[string]struct{}) - } - inboundClientIps[email][ip] = struct{}{} - } - - shouldCleanLog := false - for email, uniqueIps := range inboundClientIps { - - ips := make([]string, 0, len(uniqueIps)) - for ip := range uniqueIps { - ips = append(ips, ip) - } - sort.Strings(ips) - - clientIpsRecord, err := j.getInboundClientIps(email) - if err != nil { - j.addInboundClientIps(email, ips) - continue - } - - shouldCleanLog = j.updateInboundClientIps(clientIpsRecord, email, ips) || shouldCleanLog - } - - return shouldCleanLog -} - -func (j *CheckClientIpJob) checkFail2BanInstalled() bool { - cmd := "fail2ban-client" - args := []string{"-h"} - err := exec.Command(cmd, args...).Run() - return err == nil -} - -func (j *CheckClientIpJob) checkAccessLogAvailable(iplimitActive bool) bool { - accessLogPath, err := xray.GetAccessLogPath() - if err != nil { - return false - } - - if accessLogPath == "none" || accessLogPath == "" { - if iplimitActive { - logger.Warning("[LimitIP] Access log path is not set, Please configure the access log path in Xray configs.") - } - return false - } - - return true -} - -func (j *CheckClientIpJob) checkError(e error) { - if e != nil { - logger.Warning("client ip job err:", e) - } -} - -func (j *CheckClientIpJob) contains(s []string, str string) bool { - for _, v := range s { - if v == str { - return true - } - } - - return false -} - -func (j *CheckClientIpJob) getInboundClientIps(clientEmail string) (*model.InboundClientIps, error) { - db := database.GetDB() - InboundClientIps := &model.InboundClientIps{} - err := db.Model(model.InboundClientIps{}).Where("client_email = ?", clientEmail).First(InboundClientIps).Error - if err != nil { - return nil, err - } - return InboundClientIps, nil -} - -func (j *CheckClientIpJob) addInboundClientIps(clientEmail string, ips []string) error { - inboundClientIps := &model.InboundClientIps{} - jsonIps, err := json.Marshal(ips) - j.checkError(err) - - inboundClientIps.ClientEmail = clientEmail - inboundClientIps.Ips = string(jsonIps) - - db := database.GetDB() - tx := db.Begin() - - defer func() { - if err == nil { - tx.Commit() - } else { - tx.Rollback() - } - }() - - err = tx.Save(inboundClientIps).Error - if err != nil { - return err - } - return nil -} - -func (j *CheckClientIpJob) updateInboundClientIps(inboundClientIps *model.InboundClientIps, clientEmail string, ips []string) bool { - jsonIps, err := json.Marshal(ips) - if err != nil { - logger.Error("failed to marshal IPs to JSON:", err) - return false - } - - inboundClientIps.ClientEmail = clientEmail - inboundClientIps.Ips = string(jsonIps) - - inbound, err := j.getInboundByEmail(clientEmail) - if err != nil { - logger.Errorf("failed to fetch inbound settings for email %s: %s", clientEmail, err) - return false - } - - if inbound.Settings == "" { - logger.Debug("wrong data:", inbound) - return false - } - - settings := map[string][]model.Client{} - json.Unmarshal([]byte(inbound.Settings), &settings) - clients := settings["clients"] - shouldCleanLog := false - j.disAllowedIps = []string{} - - logIpFile, err := os.OpenFile(xray.GetIPLimitLogPath(), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644) - if err != nil { - logger.Errorf("failed to open IP limit log file: %s", err) - return false - } - defer logIpFile.Close() - log.SetOutput(logIpFile) - log.SetFlags(log.LstdFlags) - - for _, client := range clients { - if client.Email == clientEmail { - limitIp := client.LimitIP - - if limitIp > 0 && inbound.Enable { - shouldCleanLog = true - - if limitIp < len(ips) { - j.disAllowedIps = append(j.disAllowedIps, ips[limitIp:]...) - for i := limitIp; i < len(ips); i++ { - log.Printf("[LIMIT_IP] Email = %s || SRC = %s", clientEmail, ips[i]) - } - } - } - } - } - - sort.Strings(j.disAllowedIps) - - if len(j.disAllowedIps) > 0 { - logger.Debug("disAllowedIps:", j.disAllowedIps) - } - - db := database.GetDB() - err = db.Save(inboundClientIps).Error - if err != nil { - logger.Error("failed to save inboundClientIps:", err) - return false - } - - return shouldCleanLog -} - -func (j *CheckClientIpJob) getInboundByEmail(clientEmail string) (*model.Inbound, error) { - db := database.GetDB() - inbound := &model.Inbound{} - - err := db.Model(&model.Inbound{}).Where("settings LIKE ?", "%"+clientEmail+"%").First(inbound).Error - if err != nil { - return nil, err - } - - return inbound, nil -} diff --git a/web/job/check_cpu_usage.go b/web/job/check_cpu_usage.go deleted file mode 100644 index 30ce4db6..00000000 --- a/web/job/check_cpu_usage.go +++ /dev/null @@ -1,34 +0,0 @@ -package job - -import ( - "strconv" - "time" - - "x-ui/web/service" - - "github.com/shirou/gopsutil/v4/cpu" -) - -type CheckCpuJob struct { - tgbotService service.Tgbot - settingService service.SettingService -} - -func NewCheckCpuJob() *CheckCpuJob { - return new(CheckCpuJob) -} - -// Here run is a interface method of Job interface -func (j *CheckCpuJob) Run() { - threshold, _ := j.settingService.GetTgCpu() - - // get latest status of server - percent, err := cpu.Percent(1*time.Second, false) - if err == nil && percent[0] > float64(threshold) { - msg := j.tgbotService.I18nBot("tgbot.messages.cpuThreshold", - "Percent=="+strconv.FormatFloat(percent[0], 'f', 2, 64), - "Threshold=="+strconv.Itoa(threshold)) - - j.tgbotService.SendMsgToTgbotAdmins(msg) - } -} diff --git a/web/job/check_hash_storage.go b/web/job/check_hash_storage.go deleted file mode 100644 index 468aa2e0..00000000 --- a/web/job/check_hash_storage.go +++ /dev/null @@ -1,19 +0,0 @@ -package job - -import ( - "x-ui/web/service" -) - -type CheckHashStorageJob struct { - tgbotService service.Tgbot -} - -func NewCheckHashStorageJob() *CheckHashStorageJob { - return new(CheckHashStorageJob) -} - -// Here Run is an interface method of the Job interface -func (j *CheckHashStorageJob) Run() { - // Remove expired hashes from storage - j.tgbotService.GetHashStorage().RemoveExpiredHashes() -} diff --git a/web/job/check_xray_running_job.go b/web/job/check_xray_running_job.go deleted file mode 100644 index bfef5ece..00000000 --- a/web/job/check_xray_running_job.go +++ /dev/null @@ -1,32 +0,0 @@ -package job - -import ( - "x-ui/logger" - "x-ui/web/service" -) - -type CheckXrayRunningJob struct { - xrayService service.XrayService - - checkTime int -} - -func NewCheckXrayRunningJob() *CheckXrayRunningJob { - return new(CheckXrayRunningJob) -} - -func (j *CheckXrayRunningJob) Run() { - if j.xrayService.IsXrayRunning() { - j.checkTime = 0 - } else { - j.checkTime++ - // only restart if it's down 2 times in a row - if j.checkTime > 1 { - err := j.xrayService.RestartXray(false) - j.checkTime = 0 - if err != nil { - logger.Error("Restart xray failed:", err) - } - } - } -} diff --git a/web/job/clear_logs_job.go b/web/job/clear_logs_job.go deleted file mode 100644 index c6f1d7cc..00000000 --- a/web/job/clear_logs_job.go +++ /dev/null @@ -1,76 +0,0 @@ -package job - -import ( - "io" - "os" - "path/filepath" - - "x-ui/logger" - "x-ui/xray" -) - -type ClearLogsJob struct{} - -func NewClearLogsJob() *ClearLogsJob { - return new(ClearLogsJob) -} - -// ensureFileExists creates the necessary directories and file if they don't exist -func ensureFileExists(path string) error { - dir := filepath.Dir(path) - if err := os.MkdirAll(dir, 0755); err != nil { - return err - } - - file, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR, 0644) - if err != nil { - return err - } - file.Close() - return nil -} - -// Here Run is an interface method of the Job interface -func (j *ClearLogsJob) Run() { - logFiles := []string{xray.GetIPLimitLogPath(), xray.GetIPLimitBannedLogPath(), xray.GetAccessPersistentLogPath()} - logFilesPrev := []string{xray.GetIPLimitBannedPrevLogPath(), xray.GetAccessPersistentPrevLogPath()} - - // Ensure all log files and their paths exist - for _, path := range append(logFiles, logFilesPrev...) { - if err := ensureFileExists(path); err != nil { - logger.Warning("Failed to ensure log file exists:", path, "-", err) - } - } - - // Clear log files and copy to previous logs - for i := 0; i < len(logFiles); i++ { - if i > 0 { - // Copy to previous logs - logFilePrev, err := os.OpenFile(logFilesPrev[i-1], os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644) - if err != nil { - logger.Warning("Failed to open previous log file for writing:", logFilesPrev[i-1], "-", err) - continue - } - - logFile, err := os.OpenFile(logFiles[i], os.O_RDONLY, 0644) - if err != nil { - logger.Warning("Failed to open current log file for reading:", logFiles[i], "-", err) - logFilePrev.Close() - continue - } - - _, err = io.Copy(logFilePrev, logFile) - if err != nil { - logger.Warning("Failed to copy log file:", logFiles[i], "to", logFilesPrev[i-1], "-", err) - } - - logFile.Close() - logFilePrev.Close() - } - - err := os.Truncate(logFiles[i], 0) - if err != nil { - logger.Warning("Failed to truncate log file:", logFiles[i], "-", err) - } - } -} diff --git a/web/job/stats_notify_job.go b/web/job/stats_notify_job.go deleted file mode 100644 index ae5eba70..00000000 --- a/web/job/stats_notify_job.go +++ /dev/null @@ -1,29 +0,0 @@ -package job - -import ( - "x-ui/web/service" -) - -type LoginStatus byte - -const ( - LoginSuccess LoginStatus = 1 - LoginFail LoginStatus = 0 -) - -type StatsNotifyJob struct { - xrayService service.XrayService - tgbotService service.Tgbot -} - -func NewStatsNotifyJob() *StatsNotifyJob { - return new(StatsNotifyJob) -} - -// Here run is a interface method of Job interface -func (j *StatsNotifyJob) Run() { - if !j.xrayService.IsXrayRunning() { - return - } - j.tgbotService.SendReport() -} diff --git a/web/job/xray_traffic_job.go b/web/job/xray_traffic_job.go deleted file mode 100644 index dbbbb059..00000000 --- a/web/job/xray_traffic_job.go +++ /dev/null @@ -1,37 +0,0 @@ -package job - -import ( - "x-ui/logger" - "x-ui/web/service" -) - -type XrayTrafficJob struct { - xrayService service.XrayService - inboundService service.InboundService - outboundService service.OutboundService -} - -func NewXrayTrafficJob() *XrayTrafficJob { - return new(XrayTrafficJob) -} - -func (j *XrayTrafficJob) Run() { - if !j.xrayService.IsXrayRunning() { - return - } - traffics, clientTraffics, err := j.xrayService.GetXrayTraffic() - if err != nil { - return - } - err, needRestart0 := j.inboundService.AddTraffic(traffics, clientTraffics) - if err != nil { - logger.Warning("add inbound traffic failed:", err) - } - err, needRestart1 := j.outboundService.AddTraffic(traffics, clientTraffics) - if err != nil { - logger.Warning("add outbound traffic failed:", err) - } - if needRestart0 || needRestart1 { - j.xrayService.SetToNeedRestart() - } -} diff --git a/web/locale/locale.go b/web/locale/locale.go deleted file mode 100644 index adc90ec5..00000000 --- a/web/locale/locale.go +++ /dev/null @@ -1,145 +0,0 @@ -package locale - -import ( - "embed" - "io/fs" - "strings" - - "x-ui/logger" - - "github.com/gin-gonic/gin" - "github.com/nicksnyder/go-i18n/v2/i18n" - "github.com/pelletier/go-toml/v2" - "golang.org/x/text/language" -) - -var ( - i18nBundle *i18n.Bundle - LocalizerWeb *i18n.Localizer - LocalizerBot *i18n.Localizer -) - -type I18nType string - -const ( - Bot I18nType = "bot" - Web I18nType = "web" -) - -type SettingService interface { - GetTgLang() (string, error) -} - -func InitLocalizer(i18nFS embed.FS, settingService SettingService) error { - // set default bundle to english - i18nBundle = i18n.NewBundle(language.MustParse("en-US")) - i18nBundle.RegisterUnmarshalFunc("toml", toml.Unmarshal) - - // parse files - if err := parseTranslationFiles(i18nFS, i18nBundle); err != nil { - return err - } - - // setup bot locale - if err := initTGBotLocalizer(settingService); err != nil { - return err - } - - return nil -} - -func createTemplateData(params []string, seperator ...string) map[string]interface{} { - var sep string = "==" - if len(seperator) > 0 { - sep = seperator[0] - } - - templateData := make(map[string]interface{}) - for _, param := range params { - parts := strings.SplitN(param, sep, 2) - templateData[parts[0]] = parts[1] - } - - return templateData -} - -func I18n(i18nType I18nType, key string, params ...string) string { - var localizer *i18n.Localizer - - switch i18nType { - case "bot": - localizer = LocalizerBot - case "web": - localizer = LocalizerWeb - default: - logger.Errorf("Invalid type for I18n: %s", i18nType) - return "" - } - - templateData := createTemplateData(params) - - msg, err := localizer.Localize(&i18n.LocalizeConfig{ - MessageID: key, - TemplateData: templateData, - }) - if err != nil { - logger.Errorf("Failed to localize message: %v", err) - return "" - } - - return msg -} - -func initTGBotLocalizer(settingService SettingService) error { - botLang, err := settingService.GetTgLang() - if err != nil { - return err - } - - LocalizerBot = i18n.NewLocalizer(i18nBundle, botLang) - return nil -} - -func LocalizerMiddleware() gin.HandlerFunc { - return func(c *gin.Context) { - var lang string - - if cookie, err := c.Request.Cookie("lang"); err == nil { - lang = cookie.Value - } else { - lang = c.GetHeader("Accept-Language") - } - - LocalizerWeb = i18n.NewLocalizer(i18nBundle, lang) - - c.Set("localizer", LocalizerWeb) - c.Set("I18n", I18n) - c.Next() - } -} - -func parseTranslationFiles(i18nFS embed.FS, i18nBundle *i18n.Bundle) error { - err := fs.WalkDir(i18nFS, "translation", - func(path string, d fs.DirEntry, err error) error { - if err != nil { - return err - } - - if d.IsDir() { - return nil - } - - data, err := i18nFS.ReadFile(path) - if err != nil { - return err - } - - _, err = i18nBundle.ParseMessageFileBytes(data, path) - return err - }) - if err != nil { - return err - } - - return nil -} diff --git a/web/middleware/domainValidator.go b/web/middleware/domainValidator.go deleted file mode 100644 index c94130c0..00000000 --- a/web/middleware/domainValidator.go +++ /dev/null @@ -1,25 +0,0 @@ -package middleware - -import ( - "net" - "net/http" - "strings" - - "github.com/gin-gonic/gin" -) - -func DomainValidatorMiddleware(domain string) gin.HandlerFunc { - return func(c *gin.Context) { - host := c.Request.Host - if colonIndex := strings.LastIndex(host, ":"); colonIndex != -1 { - host, _, _ = net.SplitHostPort(c.Request.Host) - } - - if host != domain { - c.AbortWithStatus(http.StatusForbidden) - return - } - - c.Next() - } -} diff --git a/web/middleware/redirect.go b/web/middleware/redirect.go deleted file mode 100644 index e3dc8ada..00000000 --- a/web/middleware/redirect.go +++ /dev/null @@ -1,34 +0,0 @@ -package middleware - -import ( - "net/http" - "strings" - - "github.com/gin-gonic/gin" -) - -func RedirectMiddleware(basePath string) gin.HandlerFunc { - return func(c *gin.Context) { - // Redirect from old '/xui' path to '/panel' - redirects := map[string]string{ - "panel/API": "panel/api", - "xui/API": "panel/api", - "xui": "panel", - } - - path := c.Request.URL.Path - for from, to := range redirects { - from, to = basePath+from, basePath+to - - if strings.HasPrefix(path, from) { - newPath := to + path[len(from):] - - c.Redirect(http.StatusMovedPermanently, newPath) - c.Abort() - return - } - } - - c.Next() - } -} diff --git a/web/network/auto_https_conn.go b/web/network/auto_https_conn.go deleted file mode 100644 index d1a9d521..00000000 --- a/web/network/auto_https_conn.go +++ /dev/null @@ -1,67 +0,0 @@ -package network - -import ( - "bufio" - "bytes" - "fmt" - "net" - "net/http" - "sync" -) - -type AutoHttpsConn struct { - net.Conn - - firstBuf []byte - bufStart int - - readRequestOnce sync.Once -} - -func NewAutoHttpsConn(conn net.Conn) net.Conn { - return &AutoHttpsConn{ - Conn: conn, - } -} - -func (c *AutoHttpsConn) readRequest() bool { - c.firstBuf = make([]byte, 2048) - n, err := c.Conn.Read(c.firstBuf) - c.firstBuf = c.firstBuf[:n] - if err != nil { - return false - } - reader := bytes.NewReader(c.firstBuf) - bufReader := bufio.NewReader(reader) - request, err := http.ReadRequest(bufReader) - if err != nil { - return false - } - resp := http.Response{ - Header: http.Header{}, - } - resp.StatusCode = http.StatusTemporaryRedirect - location := fmt.Sprintf("https://%v%v", request.Host, request.RequestURI) - resp.Header.Set("Location", location) - resp.Write(c.Conn) - c.Close() - c.firstBuf = nil - return true -} - -func (c *AutoHttpsConn) Read(buf []byte) (int, error) { - c.readRequestOnce.Do(func() { - c.readRequest() - }) - - if c.firstBuf != nil { - n := copy(buf, c.firstBuf[c.bufStart:]) - c.bufStart += n - if c.bufStart >= len(c.firstBuf) { - c.firstBuf = nil - } - return n, nil - } - - return c.Conn.Read(buf) -} diff --git a/web/network/auto_https_listener.go b/web/network/auto_https_listener.go deleted file mode 100644 index 26614696..00000000 --- a/web/network/auto_https_listener.go +++ /dev/null @@ -1,21 +0,0 @@ -package network - -import "net" - -type AutoHttpsListener struct { - net.Listener -} - -func NewAutoHttpsListener(listener net.Listener) net.Listener { - return &AutoHttpsListener{ - Listener: listener, - } -} - -func (l *AutoHttpsListener) Accept() (net.Conn, error) { - conn, err := l.Listener.Accept() - if err != nil { - return nil, err - } - return NewAutoHttpsConn(conn), nil -} diff --git a/web/service/config.json b/web/service/config.json deleted file mode 100644 index e7425ac0..00000000 --- a/web/service/config.json +++ /dev/null @@ -1,85 +0,0 @@ -{ - "log": { - "access": "none", - "dnsLog": false, - "error": "", - "loglevel": "warning", - "maskAddress": "" - }, - "api": { - "tag": "api", - "services": [ - "HandlerService", - "LoggerService", - "StatsService" - ] - }, - "inbounds": [ - { - "tag": "api", - "listen": "127.0.0.1", - "port": 62789, - "protocol": "dokodemo-door", - "settings": { - "address": "127.0.0.1" - } - } - ], - "outbounds": [ - { - "tag": "direct", - "protocol": "freedom", - "settings": { - "domainStrategy": "AsIs", - "redirect": "", - "noises": [] - } - }, - { - "tag": "blocked", - "protocol": "blackhole", - "settings": {} - } - ], - "policy": { - "levels": { - "0": { - "statsUserDownlink": true, - "statsUserUplink": true - } - }, - "system": { - "statsInboundDownlink": true, - "statsInboundUplink": true, - "statsOutboundDownlink": false, - "statsOutboundUplink": false - } - }, - "routing": { - "domainStrategy": "AsIs", - "rules": [ - { - "type": "field", - "inboundTag": [ - "api" - ], - "outboundTag": "api" - }, - { - "type": "field", - "outboundTag": "blocked", - "ip": [ - "geoip:private" - ] - }, - { - "type": "field", - "outboundTag": "blocked", - "protocol": [ - "bittorrent" - ] - } - ] - }, - "stats": {} -} \ No newline at end of file diff --git a/web/service/inbound.go b/web/service/inbound.go deleted file mode 100644 index 4f28af21..00000000 --- a/web/service/inbound.go +++ /dev/null @@ -1,2027 +0,0 @@ -package service - -import ( - "encoding/json" - "fmt" - "strconv" - "strings" - "time" - - "x-ui/database" - "x-ui/database/model" - "x-ui/logger" - "x-ui/util/common" - "x-ui/xray" - - "gorm.io/gorm" -) - -type InboundService struct { - xrayApi xray.XrayAPI -} - -func (s *InboundService) GetInbounds(userId int) ([]*model.Inbound, error) { - db := database.GetDB() - var inbounds []*model.Inbound - err := db.Model(model.Inbound{}).Preload("ClientStats").Where("user_id = ?", userId).Find(&inbounds).Error - if err != nil && err != gorm.ErrRecordNotFound { - return nil, err - } - return inbounds, nil -} - -func (s *InboundService) GetAllInbounds() ([]*model.Inbound, error) { - db := database.GetDB() - var inbounds []*model.Inbound - err := db.Model(model.Inbound{}).Preload("ClientStats").Find(&inbounds).Error - if err != nil && err != gorm.ErrRecordNotFound { - return nil, err - } - return inbounds, nil -} - -func (s *InboundService) checkPortExist(listen string, port int, ignoreId int) (bool, error) { - db := database.GetDB() - if listen == "" || listen == "0.0.0.0" || listen == "::" || listen == "::0" { - db = db.Model(model.Inbound{}).Where("port = ?", port) - } else { - db = db.Model(model.Inbound{}). - Where("port = ?", port). - Where( - db.Model(model.Inbound{}).Where( - "listen = ?", listen, - ).Or( - "listen = \"\"", - ).Or( - "listen = \"0.0.0.0\"", - ).Or( - "listen = \"::\"", - ).Or( - "listen = \"::0\"")) - } - if ignoreId > 0 { - db = db.Where("id != ?", ignoreId) - } - var count int64 - err := db.Count(&count).Error - if err != nil { - return false, err - } - return count > 0, nil -} - -func (s *InboundService) GetClients(inbound *model.Inbound) ([]model.Client, error) { - settings := map[string][]model.Client{} - json.Unmarshal([]byte(inbound.Settings), &settings) - if settings == nil { - return nil, fmt.Errorf("setting is null") - } - - clients := settings["clients"] - if clients == nil { - return nil, nil - } - return clients, nil -} - -func (s *InboundService) getAllEmails() ([]string, error) { - db := database.GetDB() - var emails []string - err := db.Raw(` - SELECT JSON_EXTRACT(client.value, '$.email') - FROM inbounds, - JSON_EACH(JSON_EXTRACT(inbounds.settings, '$.clients')) AS client - `).Scan(&emails).Error - if err != nil { - return nil, err - } - return emails, nil -} - -func (s *InboundService) contains(slice []string, str string) bool { - lowerStr := strings.ToLower(str) - for _, s := range slice { - if strings.ToLower(s) == lowerStr { - return true - } - } - return false -} - -func (s *InboundService) checkEmailsExistForClients(clients []model.Client) (string, error) { - allEmails, err := s.getAllEmails() - if err != nil { - return "", err - } - var emails []string - for _, client := range clients { - if client.Email != "" { - if s.contains(emails, client.Email) { - return client.Email, nil - } - if s.contains(allEmails, client.Email) { - return client.Email, nil - } - emails = append(emails, client.Email) - } - } - return "", nil -} - -func (s *InboundService) checkEmailExistForInbound(inbound *model.Inbound) (string, error) { - clients, err := s.GetClients(inbound) - if err != nil { - return "", err - } - allEmails, err := s.getAllEmails() - if err != nil { - return "", err - } - var emails []string - for _, client := range clients { - if client.Email != "" { - if s.contains(emails, client.Email) { - return client.Email, nil - } - if s.contains(allEmails, client.Email) { - return client.Email, nil - } - emails = append(emails, client.Email) - } - } - return "", nil -} - -func (s *InboundService) AddInbound(inbound *model.Inbound) (*model.Inbound, bool, error) { - exist, err := s.checkPortExist(inbound.Listen, inbound.Port, 0) - if err != nil { - return inbound, false, err - } - if exist { - return inbound, false, common.NewError("Port already exists:", inbound.Port) - } - - existEmail, err := s.checkEmailExistForInbound(inbound) - if err != nil { - return inbound, false, err - } - if existEmail != "" { - return inbound, false, common.NewError("Duplicate email:", existEmail) - } - - clients, err := s.GetClients(inbound) - if err != nil { - return inbound, false, err - } - - // Secure client ID - for _, client := range clients { - if inbound.Protocol == "trojan" { - if client.Password == "" { - return inbound, false, common.NewError("empty client ID") - } - } else if inbound.Protocol == "shadowsocks" { - if client.Email == "" { - return inbound, false, common.NewError("empty client ID") - } - } else { - if client.ID == "" { - return inbound, false, common.NewError("empty client ID") - } - } - } - - db := database.GetDB() - tx := db.Begin() - defer func() { - if err == nil { - tx.Commit() - } else { - tx.Rollback() - } - }() - - err = tx.Save(inbound).Error - if err == nil { - if len(inbound.ClientStats) == 0 { - for _, client := range clients { - s.AddClientStat(tx, inbound.Id, &client) - } - } - } else { - return inbound, false, err - } - - needRestart := false - if inbound.Enable { - s.xrayApi.Init(p.GetAPIPort()) - inboundJson, err1 := json.MarshalIndent(inbound.GenXrayInboundConfig(), "", " ") - if err1 != nil { - logger.Debug("Unable to marshal inbound config:", err1) - } - - err1 = s.xrayApi.AddInbound(inboundJson) - if err1 == nil { - logger.Debug("New inbound added by api:", inbound.Tag) - } else { - logger.Debug("Unable to add inbound by api:", err1) - needRestart = true - } - s.xrayApi.Close() - } - - return inbound, needRestart, err -} - -func (s *InboundService) DelInbound(id int) (bool, error) { - db := database.GetDB() - - var tag string - needRestart := false - result := db.Model(model.Inbound{}).Select("tag").Where("id = ? and enable = ?", id, true).First(&tag) - if result.Error == nil { - s.xrayApi.Init(p.GetAPIPort()) - err1 := s.xrayApi.DelInbound(tag) - if err1 == nil { - logger.Debug("Inbound deleted by api:", tag) - } else { - logger.Debug("Unable to delete inbound by api:", err1) - needRestart = true - } - s.xrayApi.Close() - } else { - logger.Debug("No enabled inbound founded to removing by api", tag) - } - - // Delete client traffics of inbounds - err := db.Where("inbound_id = ?", id).Delete(xray.ClientTraffic{}).Error - if err != nil { - return false, err - } - inbound, err := s.GetInbound(id) - if err != nil { - return false, err - } - clients, err := s.GetClients(inbound) - if err != nil { - return false, err - } - for _, client := range clients { - err := s.DelClientIPs(db, client.Email) - if err != nil { - return false, err - } - } - - return needRestart, db.Delete(model.Inbound{}, id).Error -} - -func (s *InboundService) GetInbound(id int) (*model.Inbound, error) { - db := database.GetDB() - inbound := &model.Inbound{} - err := db.Model(model.Inbound{}).First(inbound, id).Error - if err != nil { - return nil, err - } - return inbound, nil -} - -func (s *InboundService) UpdateInbound(inbound *model.Inbound) (*model.Inbound, bool, error) { - exist, err := s.checkPortExist(inbound.Listen, inbound.Port, inbound.Id) - if err != nil { - return inbound, false, err - } - if exist { - return inbound, false, common.NewError("Port already exists:", inbound.Port) - } - - oldInbound, err := s.GetInbound(inbound.Id) - if err != nil { - return inbound, false, err - } - - tag := oldInbound.Tag - - db := database.GetDB() - tx := db.Begin() - - defer func() { - if err != nil { - tx.Rollback() - } else { - tx.Commit() - } - }() - - err = s.updateClientTraffics(tx, oldInbound, inbound) - if err != nil { - return inbound, false, err - } - - oldInbound.Up = inbound.Up - oldInbound.Down = inbound.Down - oldInbound.Total = inbound.Total - oldInbound.Remark = inbound.Remark - oldInbound.Enable = inbound.Enable - oldInbound.ExpiryTime = inbound.ExpiryTime - oldInbound.Listen = inbound.Listen - oldInbound.Port = inbound.Port - oldInbound.Protocol = inbound.Protocol - oldInbound.Settings = inbound.Settings - oldInbound.StreamSettings = inbound.StreamSettings - oldInbound.Sniffing = inbound.Sniffing - oldInbound.Allocate = inbound.Allocate - if inbound.Listen == "" || inbound.Listen == "0.0.0.0" || inbound.Listen == "::" || inbound.Listen == "::0" { - oldInbound.Tag = fmt.Sprintf("inbound-%v", inbound.Port) - } else { - oldInbound.Tag = fmt.Sprintf("inbound-%v:%v", inbound.Listen, inbound.Port) - } - - needRestart := false - s.xrayApi.Init(p.GetAPIPort()) - if s.xrayApi.DelInbound(tag) == nil { - logger.Debug("Old inbound deleted by api:", tag) - } - if inbound.Enable { - inboundJson, err2 := json.MarshalIndent(oldInbound.GenXrayInboundConfig(), "", " ") - if err2 != nil { - logger.Debug("Unable to marshal updated inbound config:", err2) - needRestart = true - } else { - err2 = s.xrayApi.AddInbound(inboundJson) - if err2 == nil { - logger.Debug("Updated inbound added by api:", oldInbound.Tag) - } else { - logger.Debug("Unable to update inbound by api:", err2) - needRestart = true - } - } - } - s.xrayApi.Close() - - return inbound, needRestart, tx.Save(oldInbound).Error -} - -func (s *InboundService) updateClientTraffics(tx *gorm.DB, oldInbound *model.Inbound, newInbound *model.Inbound) error { - oldClients, err := s.GetClients(oldInbound) - if err != nil { - return err - } - newClients, err := s.GetClients(newInbound) - if err != nil { - return err - } - - var emailExists bool - - for _, oldClient := range oldClients { - emailExists = false - for _, newClient := range newClients { - if oldClient.Email == newClient.Email { - emailExists = true - break - } - } - if !emailExists { - err = s.DelClientStat(tx, oldClient.Email) - if err != nil { - return err - } - } - } - for _, newClient := range newClients { - emailExists = false - for _, oldClient := range oldClients { - if newClient.Email == oldClient.Email { - emailExists = true - break - } - } - if !emailExists { - err = s.AddClientStat(tx, oldInbound.Id, &newClient) - if err != nil { - return err - } - } - } - return nil -} - -func (s *InboundService) AddInboundClient(data *model.Inbound) (bool, error) { - clients, err := s.GetClients(data) - if err != nil { - return false, err - } - - var settings map[string]interface{} - err = json.Unmarshal([]byte(data.Settings), &settings) - if err != nil { - return false, err - } - - interfaceClients := settings["clients"].([]interface{}) - existEmail, err := s.checkEmailsExistForClients(clients) - if err != nil { - return false, err - } - if existEmail != "" { - return false, common.NewError("Duplicate email:", existEmail) - } - - oldInbound, err := s.GetInbound(data.Id) - if err != nil { - return false, err - } - - // Secure client ID - for _, client := range clients { - if oldInbound.Protocol == "trojan" { - if client.Password == "" { - return false, common.NewError("empty client ID") - } - } else if oldInbound.Protocol == "shadowsocks" { - if client.Email == "" { - return false, common.NewError("empty client ID") - } - } else { - if client.ID == "" { - return false, common.NewError("empty client ID") - } - } - } - - var oldSettings map[string]interface{} - err = json.Unmarshal([]byte(oldInbound.Settings), &oldSettings) - if err != nil { - return false, err - } - - oldClients := oldSettings["clients"].([]interface{}) - oldClients = append(oldClients, interfaceClients...) - - oldSettings["clients"] = oldClients - - newSettings, err := json.MarshalIndent(oldSettings, "", " ") - if err != nil { - return false, err - } - - oldInbound.Settings = string(newSettings) - - db := database.GetDB() - tx := db.Begin() - - defer func() { - if err != nil { - tx.Rollback() - } else { - tx.Commit() - } - }() - - needRestart := false - s.xrayApi.Init(p.GetAPIPort()) - for _, client := range clients { - if len(client.Email) > 0 { - s.AddClientStat(tx, data.Id, &client) - if client.Enable { - cipher := "" - if oldInbound.Protocol == "shadowsocks" { - cipher = oldSettings["method"].(string) - } - err1 := s.xrayApi.AddUser(string(oldInbound.Protocol), oldInbound.Tag, map[string]interface{}{ - "email": client.Email, - "id": client.ID, - "security": client.Security, - "flow": client.Flow, - "password": client.Password, - "cipher": cipher, - }) - if err1 == nil { - logger.Debug("Client added by api:", client.Email) - } else { - logger.Debug("Error in adding client by api:", err1) - needRestart = true - } - } - } else { - needRestart = true - } - } - s.xrayApi.Close() - - return needRestart, tx.Save(oldInbound).Error -} - -func (s *InboundService) DelInboundClient(inboundId int, clientId string) (bool, error) { - oldInbound, err := s.GetInbound(inboundId) - if err != nil { - logger.Error("Load Old Data Error") - return false, err - } - var settings map[string]interface{} - err = json.Unmarshal([]byte(oldInbound.Settings), &settings) - if err != nil { - return false, err - } - - email := "" - client_key := "id" - if oldInbound.Protocol == "trojan" { - client_key = "password" - } - if oldInbound.Protocol == "shadowsocks" { - client_key = "email" - } - - interfaceClients := settings["clients"].([]interface{}) - var newClients []interface{} - needApiDel := false - for _, client := range interfaceClients { - c := client.(map[string]interface{}) - c_id := c[client_key].(string) - if c_id == clientId { - email, _ = c["email"].(string) - needApiDel, _ = c["enable"].(bool) - } else { - newClients = append(newClients, client) - } - } - - if len(newClients) == 0 { - return false, common.NewError("no client remained in Inbound") - } - - settings["clients"] = newClients - newSettings, err := json.MarshalIndent(settings, "", " ") - if err != nil { - return false, err - } - - oldInbound.Settings = string(newSettings) - - db := database.GetDB() - - err = s.DelClientIPs(db, email) - if err != nil { - logger.Error("Error in delete client IPs") - return false, err - } - needRestart := false - - if len(email) > 0 { - notDepleted := true - err = db.Model(xray.ClientTraffic{}).Select("enable").Where("email = ?", email).First(¬Depleted).Error - if err != nil { - logger.Error("Get stats error") - return false, err - } - err = s.DelClientStat(db, email) - if err != nil { - logger.Error("Delete stats Data Error") - return false, err - } - if needApiDel && notDepleted { - s.xrayApi.Init(p.GetAPIPort()) - err1 := s.xrayApi.RemoveUser(oldInbound.Tag, email) - if err1 == nil { - logger.Debug("Client deleted by api:", email) - needRestart = false - } else { - if strings.Contains(err1.Error(), fmt.Sprintf("User %s not found.", email)) { - logger.Debug("User is already deleted. Nothing to do more...") - } else { - logger.Debug("Error in deleting client by api:", err1) - needRestart = true - } - } - s.xrayApi.Close() - } - } - return needRestart, db.Save(oldInbound).Error -} - -func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId string) (bool, error) { - clients, err := s.GetClients(data) - if err != nil { - return false, err - } - - var settings map[string]interface{} - err = json.Unmarshal([]byte(data.Settings), &settings) - if err != nil { - return false, err - } - - interfaceClients := settings["clients"].([]interface{}) - - oldInbound, err := s.GetInbound(data.Id) - if err != nil { - return false, err - } - - oldClients, err := s.GetClients(oldInbound) - if err != nil { - return false, err - } - - oldEmail := "" - newClientId := "" - clientIndex := -1 - for index, oldClient := range oldClients { - oldClientId := "" - if oldInbound.Protocol == "trojan" { - oldClientId = oldClient.Password - newClientId = clients[0].Password - } else if oldInbound.Protocol == "shadowsocks" { - oldClientId = oldClient.Email - newClientId = clients[0].Email - } else { - oldClientId = oldClient.ID - newClientId = clients[0].ID - } - if clientId == oldClientId { - oldEmail = oldClient.Email - clientIndex = index - break - } - } - - // Validate new client ID - if newClientId == "" || clientIndex == -1 { - return false, common.NewError("empty client ID") - } - - if len(clients[0].Email) > 0 && clients[0].Email != oldEmail { - existEmail, err := s.checkEmailsExistForClients(clients) - if err != nil { - return false, err - } - if existEmail != "" { - return false, common.NewError("Duplicate email:", existEmail) - } - } - - var oldSettings map[string]interface{} - err = json.Unmarshal([]byte(oldInbound.Settings), &oldSettings) - if err != nil { - return false, err - } - settingsClients := oldSettings["clients"].([]interface{}) - settingsClients[clientIndex] = interfaceClients[0] - oldSettings["clients"] = settingsClients - - newSettings, err := json.MarshalIndent(oldSettings, "", " ") - if err != nil { - return false, err - } - - oldInbound.Settings = string(newSettings) - db := database.GetDB() - tx := db.Begin() - - defer func() { - if err != nil { - tx.Rollback() - } else { - tx.Commit() - } - }() - - if len(clients[0].Email) > 0 { - if len(oldEmail) > 0 { - err = s.UpdateClientStat(tx, oldEmail, &clients[0]) - if err != nil { - return false, err - } - err = s.UpdateClientIPs(tx, oldEmail, clients[0].Email) - if err != nil { - return false, err - } - } else { - s.AddClientStat(tx, data.Id, &clients[0]) - } - } else { - err = s.DelClientStat(tx, oldEmail) - if err != nil { - return false, err - } - err = s.DelClientIPs(tx, oldEmail) - if err != nil { - return false, err - } - } - needRestart := false - if len(oldEmail) > 0 { - s.xrayApi.Init(p.GetAPIPort()) - if oldClients[clientIndex].Enable { - err1 := s.xrayApi.RemoveUser(oldInbound.Tag, oldEmail) - if err1 == nil { - logger.Debug("Old client deleted by api:", oldEmail) - } else { - if strings.Contains(err1.Error(), fmt.Sprintf("User %s not found.", oldEmail)) { - logger.Debug("User is already deleted. Nothing to do more...") - } else { - logger.Debug("Error in deleting client by api:", err1) - needRestart = true - } - } - } - if clients[0].Enable { - cipher := "" - if oldInbound.Protocol == "shadowsocks" { - cipher = oldSettings["method"].(string) - } - err1 := s.xrayApi.AddUser(string(oldInbound.Protocol), oldInbound.Tag, map[string]interface{}{ - "email": clients[0].Email, - "id": clients[0].ID, - "security": clients[0].Security, - "flow": clients[0].Flow, - "password": clients[0].Password, - "cipher": cipher, - }) - if err1 == nil { - logger.Debug("Client edited by api:", clients[0].Email) - } else { - logger.Debug("Error in adding client by api:", err1) - needRestart = true - } - } - s.xrayApi.Close() - } else { - logger.Debug("Client old email not found") - needRestart = true - } - return needRestart, tx.Save(oldInbound).Error -} - -func (s *InboundService) AddTraffic(inboundTraffics []*xray.Traffic, clientTraffics []*xray.ClientTraffic) (error, bool) { - var err error - db := database.GetDB() - tx := db.Begin() - - defer func() { - if err != nil { - tx.Rollback() - } else { - tx.Commit() - } - }() - err = s.addInboundTraffic(tx, inboundTraffics) - if err != nil { - return err, false - } - err = s.addClientTraffic(tx, clientTraffics) - if err != nil { - return err, false - } - - needRestart0, count, err := s.autoRenewClients(tx) - if err != nil { - logger.Warning("Error in renew clients:", err) - } else if count > 0 { - logger.Debugf("%v clients renewed", count) - } - - needRestart1, count, err := s.disableInvalidClients(tx) - if err != nil { - logger.Warning("Error in disabling invalid clients:", err) - } else if count > 0 { - logger.Debugf("%v clients disabled", count) - } - - needRestart2, count, err := s.disableInvalidInbounds(tx) - if err != nil { - logger.Warning("Error in disabling invalid inbounds:", err) - } else if count > 0 { - logger.Debugf("%v inbounds disabled", count) - } - return nil, (needRestart0 || needRestart1 || needRestart2) -} - -func (s *InboundService) addInboundTraffic(tx *gorm.DB, traffics []*xray.Traffic) error { - if len(traffics) == 0 { - return nil - } - - var err error - - for _, traffic := range traffics { - if traffic.IsInbound { - err = tx.Model(&model.Inbound{}).Where("tag = ?", traffic.Tag). - Updates(map[string]interface{}{ - "up": gorm.Expr("up + ?", traffic.Up), - "down": gorm.Expr("down + ?", traffic.Down), - }).Error - if err != nil { - return err - } - } - } - return nil -} - -func (s *InboundService) addClientTraffic(tx *gorm.DB, traffics []*xray.ClientTraffic) (err error) { - if len(traffics) == 0 { - // Empty onlineUsers - if p != nil { - p.SetOnlineClients(nil) - } - return nil - } - - var onlineClients []string - - emails := make([]string, 0, len(traffics)) - for _, traffic := range traffics { - emails = append(emails, traffic.Email) - } - dbClientTraffics := make([]*xray.ClientTraffic, 0, len(traffics)) - err = tx.Model(xray.ClientTraffic{}).Where("email IN (?)", emails).Find(&dbClientTraffics).Error - if err != nil { - return err - } - - // Avoid empty slice error - if len(dbClientTraffics) == 0 { - return nil - } - - dbClientTraffics, err = s.adjustTraffics(tx, dbClientTraffics) - if err != nil { - return err - } - - for dbTraffic_index := range dbClientTraffics { - for traffic_index := range traffics { - if dbClientTraffics[dbTraffic_index].Email == traffics[traffic_index].Email { - dbClientTraffics[dbTraffic_index].Up += traffics[traffic_index].Up - dbClientTraffics[dbTraffic_index].Down += traffics[traffic_index].Down - - // Add user in onlineUsers array on traffic - if traffics[traffic_index].Up+traffics[traffic_index].Down > 0 { - onlineClients = append(onlineClients, traffics[traffic_index].Email) - } - break - } - } - } - - // Set onlineUsers - p.SetOnlineClients(onlineClients) - - err = tx.Save(dbClientTraffics).Error - if err != nil { - logger.Warning("AddClientTraffic update data ", err) - } - - return nil -} - -func (s *InboundService) adjustTraffics(tx *gorm.DB, dbClientTraffics []*xray.ClientTraffic) ([]*xray.ClientTraffic, error) { - inboundIds := make([]int, 0, len(dbClientTraffics)) - for _, dbClientTraffic := range dbClientTraffics { - if dbClientTraffic.ExpiryTime < 0 { - inboundIds = append(inboundIds, dbClientTraffic.InboundId) - } - } - - if len(inboundIds) > 0 { - var inbounds []*model.Inbound - err := tx.Model(model.Inbound{}).Where("id IN (?)", inboundIds).Find(&inbounds).Error - if err != nil { - return nil, err - } - for inbound_index := range inbounds { - settings := map[string]interface{}{} - json.Unmarshal([]byte(inbounds[inbound_index].Settings), &settings) - clients, ok := settings["clients"].([]interface{}) - if ok { - var newClients []interface{} - for client_index := range clients { - c := clients[client_index].(map[string]interface{}) - for traffic_index := range dbClientTraffics { - if dbClientTraffics[traffic_index].ExpiryTime < 0 && c["email"] == dbClientTraffics[traffic_index].Email { - oldExpiryTime := c["expiryTime"].(float64) - newExpiryTime := (time.Now().Unix() * 1000) - int64(oldExpiryTime) - c["expiryTime"] = newExpiryTime - dbClientTraffics[traffic_index].ExpiryTime = newExpiryTime - break - } - } - newClients = append(newClients, interface{}(c)) - } - settings["clients"] = newClients - modifiedSettings, err := json.MarshalIndent(settings, "", " ") - if err != nil { - return nil, err - } - - inbounds[inbound_index].Settings = string(modifiedSettings) - } - } - err = tx.Save(inbounds).Error - if err != nil { - logger.Warning("AddClientTraffic update inbounds ", err) - logger.Error(inbounds) - } - } - - return dbClientTraffics, nil -} - -func (s *InboundService) autoRenewClients(tx *gorm.DB) (bool, int64, error) { - // check for time expired - var traffics []*xray.ClientTraffic - now := time.Now().Unix() * 1000 - var err, err1 error - - err = tx.Model(xray.ClientTraffic{}).Where("reset > 0 and expiry_time > 0 and expiry_time <= ?", now).Find(&traffics).Error - if err != nil { - return false, 0, err - } - // return if there is no client to renew - if len(traffics) == 0 { - return false, 0, nil - } - - var inbound_ids []int - var inbounds []*model.Inbound - needRestart := false - var clientsToAdd []struct { - protocol string - tag string - client map[string]interface{} - } - - for _, traffic := range traffics { - inbound_ids = append(inbound_ids, traffic.InboundId) - } - err = tx.Model(model.Inbound{}).Where("id IN ?", inbound_ids).Find(&inbounds).Error - if err != nil { - return false, 0, err - } - for inbound_index := range inbounds { - settings := map[string]interface{}{} - json.Unmarshal([]byte(inbounds[inbound_index].Settings), &settings) - clients := settings["clients"].([]interface{}) - for client_index := range clients { - c := clients[client_index].(map[string]interface{}) - for traffic_index, traffic := range traffics { - if traffic.Email == c["email"].(string) { - newExpiryTime := traffic.ExpiryTime - for newExpiryTime < now { - newExpiryTime += (int64(traffic.Reset) * 86400000) - } - c["expiryTime"] = newExpiryTime - traffics[traffic_index].ExpiryTime = newExpiryTime - traffics[traffic_index].Down = 0 - traffics[traffic_index].Up = 0 - if !traffic.Enable { - traffics[traffic_index].Enable = true - clientsToAdd = append(clientsToAdd, - struct { - protocol string - tag string - client map[string]interface{} - }{ - protocol: string(inbounds[inbound_index].Protocol), - tag: inbounds[inbound_index].Tag, - client: c, - }) - } - clients[client_index] = interface{}(c) - break - } - } - } - settings["clients"] = clients - newSettings, err := json.MarshalIndent(settings, "", " ") - if err != nil { - return false, 0, err - } - inbounds[inbound_index].Settings = string(newSettings) - } - err = tx.Save(inbounds).Error - if err != nil { - return false, 0, err - } - err = tx.Save(traffics).Error - if err != nil { - return false, 0, err - } - if p != nil { - err1 = s.xrayApi.Init(p.GetAPIPort()) - if err1 != nil { - return true, int64(len(traffics)), nil - } - for _, clientToAdd := range clientsToAdd { - err1 = s.xrayApi.AddUser(clientToAdd.protocol, clientToAdd.tag, clientToAdd.client) - if err1 != nil { - needRestart = true - } - } - s.xrayApi.Close() - } - return needRestart, int64(len(traffics)), nil -} - -func (s *InboundService) disableInvalidInbounds(tx *gorm.DB) (bool, int64, error) { - now := time.Now().Unix() * 1000 - needRestart := false - - if p != nil { - var tags []string - err := tx.Table("inbounds"). - Select("inbounds.tag"). - Where("((total > 0 and up + down >= total) or (expiry_time > 0 and expiry_time <= ?)) and enable = ?", now, true). - Scan(&tags).Error - if err != nil { - return false, 0, err - } - s.xrayApi.Init(p.GetAPIPort()) - for _, tag := range tags { - err1 := s.xrayApi.DelInbound(tag) - if err1 == nil { - logger.Debug("Inbound disabled by api:", tag) - } else { - logger.Debug("Error in disabling inbound by api:", err1) - needRestart = true - } - } - s.xrayApi.Close() - } - - result := tx.Model(model.Inbound{}). - Where("((total > 0 and up + down >= total) or (expiry_time > 0 and expiry_time <= ?)) and enable = ?", now, true). - Update("enable", false) - err := result.Error - count := result.RowsAffected - return needRestart, count, err -} - -func (s *InboundService) disableInvalidClients(tx *gorm.DB) (bool, int64, error) { - now := time.Now().Unix() * 1000 - needRestart := false - - if p != nil { - var results []struct { - Tag string - Email string - } - - err := tx.Table("inbounds"). - Select("inbounds.tag, client_traffics.email"). - Joins("JOIN client_traffics ON inbounds.id = client_traffics.inbound_id"). - Where("((client_traffics.total > 0 AND client_traffics.up + client_traffics.down >= client_traffics.total) OR (client_traffics.expiry_time > 0 AND client_traffics.expiry_time <= ?)) AND client_traffics.enable = ?", now, true). - Scan(&results).Error - if err != nil { - return false, 0, err - } - s.xrayApi.Init(p.GetAPIPort()) - for _, result := range results { - err1 := s.xrayApi.RemoveUser(result.Tag, result.Email) - if err1 == nil { - logger.Debug("Client disabled by api:", result.Email) - } else { - if strings.Contains(err1.Error(), fmt.Sprintf("User %s not found.", result.Email)) { - logger.Debug("User is already disabled. Nothing to do more...") - } else { - if strings.Contains(err1.Error(), fmt.Sprintf("User %s not found.", result.Email)) { - logger.Debug("User is already disabled. Nothing to do more...") - } else { - logger.Debug("Error in disabling client by api:", err1) - needRestart = true - } - } - } - } - s.xrayApi.Close() - } - result := tx.Model(xray.ClientTraffic{}). - Where("((total > 0 and up + down >= total) or (expiry_time > 0 and expiry_time <= ?)) and enable = ?", now, true). - Update("enable", false) - err := result.Error - count := result.RowsAffected - return needRestart, count, err -} - -func (s *InboundService) GetInboundTags() (string, error) { - db := database.GetDB() - var inboundTags []string - err := db.Model(model.Inbound{}).Select("tag").Find(&inboundTags).Error - if err != nil && err != gorm.ErrRecordNotFound { - return "", err - } - tags, _ := json.Marshal(inboundTags) - return string(tags), nil -} - -func (s *InboundService) MigrationRemoveOrphanedTraffics() { - db := database.GetDB() - db.Exec(` - DELETE FROM client_traffics - WHERE email NOT IN ( - SELECT JSON_EXTRACT(client.value, '$.email') - FROM inbounds, - JSON_EACH(JSON_EXTRACT(inbounds.settings, '$.clients')) AS client - ) - `) -} - -func (s *InboundService) AddClientStat(tx *gorm.DB, inboundId int, client *model.Client) error { - clientTraffic := xray.ClientTraffic{} - clientTraffic.InboundId = inboundId - clientTraffic.Email = client.Email - clientTraffic.Total = client.TotalGB - clientTraffic.ExpiryTime = client.ExpiryTime - clientTraffic.Enable = true - clientTraffic.Up = 0 - clientTraffic.Down = 0 - clientTraffic.Reset = client.Reset - result := tx.Create(&clientTraffic) - err := result.Error - return err -} - -func (s *InboundService) UpdateClientStat(tx *gorm.DB, email string, client *model.Client) error { - result := tx.Model(xray.ClientTraffic{}). - Where("email = ?", email). - Updates(map[string]interface{}{ - "enable": true, - "email": client.Email, - "total": client.TotalGB, - "expiry_time": client.ExpiryTime, - "reset": client.Reset, - }) - err := result.Error - return err -} - -func (s *InboundService) UpdateClientIPs(tx *gorm.DB, oldEmail string, newEmail string) error { - return tx.Model(model.InboundClientIps{}).Where("client_email = ?", oldEmail).Update("client_email", newEmail).Error -} - -func (s *InboundService) DelClientStat(tx *gorm.DB, email string) error { - return tx.Where("email = ?", email).Delete(xray.ClientTraffic{}).Error -} - -func (s *InboundService) DelClientIPs(tx *gorm.DB, email string) error { - return tx.Where("client_email = ?", email).Delete(model.InboundClientIps{}).Error -} - -func (s *InboundService) GetClientInboundByTrafficID(trafficId int) (traffic *xray.ClientTraffic, inbound *model.Inbound, err error) { - db := database.GetDB() - var traffics []*xray.ClientTraffic - err = db.Model(xray.ClientTraffic{}).Where("id = ?", trafficId).Find(&traffics).Error - if err != nil { - logger.Warningf("Error retrieving ClientTraffic with trafficId %d: %v", trafficId, err) - return nil, nil, err - } - if len(traffics) > 0 { - inbound, err = s.GetInbound(traffics[0].InboundId) - return traffics[0], inbound, err - } - return nil, nil, nil -} - -func (s *InboundService) GetClientInboundByEmail(email string) (traffic *xray.ClientTraffic, inbound *model.Inbound, err error) { - db := database.GetDB() - var traffics []*xray.ClientTraffic - err = db.Model(xray.ClientTraffic{}).Where("email = ?", email).Find(&traffics).Error - if err != nil { - logger.Warningf("Error retrieving ClientTraffic with email %s: %v", email, err) - return nil, nil, err - } - if len(traffics) > 0 { - inbound, err = s.GetInbound(traffics[0].InboundId) - return traffics[0], inbound, err - } - return nil, nil, nil -} - -func (s *InboundService) GetClientByEmail(clientEmail string) (*xray.ClientTraffic, *model.Client, error) { - traffic, inbound, err := s.GetClientInboundByEmail(clientEmail) - if err != nil { - return nil, nil, err - } - if inbound == nil { - return nil, nil, common.NewError("Inbound Not Found For Email:", clientEmail) - } - - clients, err := s.GetClients(inbound) - if err != nil { - return nil, nil, err - } - - for _, client := range clients { - if client.Email == clientEmail { - return traffic, &client, nil - } - } - - return nil, nil, common.NewError("Client Not Found In Inbound For Email:", clientEmail) -} - -func (s *InboundService) SetClientTelegramUserID(trafficId int, tgId int64) (bool, error) { - traffic, inbound, err := s.GetClientInboundByTrafficID(trafficId) - if err != nil { - return false, err - } - if inbound == nil { - return false, common.NewError("Inbound Not Found For Traffic ID:", trafficId) - } - - clientEmail := traffic.Email - - oldClients, err := s.GetClients(inbound) - if err != nil { - return false, err - } - - clientId := "" - - for _, oldClient := range oldClients { - if oldClient.Email == clientEmail { - if inbound.Protocol == "trojan" { - clientId = oldClient.Password - } else if inbound.Protocol == "shadowsocks" { - clientId = oldClient.Email - } else { - clientId = oldClient.ID - } - break - } - } - - if len(clientId) == 0 { - return false, common.NewError("Client Not Found For Email:", clientEmail) - } - - var settings map[string]interface{} - err = json.Unmarshal([]byte(inbound.Settings), &settings) - if err != nil { - return false, err - } - clients := settings["clients"].([]interface{}) - var newClients []interface{} - for client_index := range clients { - c := clients[client_index].(map[string]interface{}) - if c["email"] == clientEmail { - c["tgId"] = tgId - newClients = append(newClients, interface{}(c)) - } - } - settings["clients"] = newClients - modifiedSettings, err := json.MarshalIndent(settings, "", " ") - if err != nil { - return false, err - } - inbound.Settings = string(modifiedSettings) - needRestart, err := s.UpdateInboundClient(inbound, clientId) - return needRestart, err -} - -func (s *InboundService) checkIsEnabledByEmail(clientEmail string) (bool, error) { - _, inbound, err := s.GetClientInboundByEmail(clientEmail) - if err != nil { - return false, err - } - if inbound == nil { - return false, common.NewError("Inbound Not Found For Email:", clientEmail) - } - - clients, err := s.GetClients(inbound) - if err != nil { - return false, err - } - - isEnable := false - - for _, client := range clients { - if client.Email == clientEmail { - isEnable = client.Enable - break - } - } - - return isEnable, err -} - -func (s *InboundService) ToggleClientEnableByEmail(clientEmail string) (bool, bool, error) { - _, inbound, err := s.GetClientInboundByEmail(clientEmail) - if err != nil { - return false, false, err - } - if inbound == nil { - return false, false, common.NewError("Inbound Not Found For Email:", clientEmail) - } - - oldClients, err := s.GetClients(inbound) - if err != nil { - return false, false, err - } - - clientId := "" - clientOldEnabled := false - - for _, oldClient := range oldClients { - if oldClient.Email == clientEmail { - if inbound.Protocol == "trojan" { - clientId = oldClient.Password - } else if inbound.Protocol == "shadowsocks" { - clientId = oldClient.Email - } else { - clientId = oldClient.ID - } - clientOldEnabled = oldClient.Enable - break - } - } - - if len(clientId) == 0 { - return false, false, common.NewError("Client Not Found For Email:", clientEmail) - } - - var settings map[string]interface{} - err = json.Unmarshal([]byte(inbound.Settings), &settings) - if err != nil { - return false, false, err - } - clients := settings["clients"].([]interface{}) - var newClients []interface{} - for client_index := range clients { - c := clients[client_index].(map[string]interface{}) - if c["email"] == clientEmail { - c["enable"] = !clientOldEnabled - newClients = append(newClients, interface{}(c)) - } - } - settings["clients"] = newClients - modifiedSettings, err := json.MarshalIndent(settings, "", " ") - if err != nil { - return false, false, err - } - inbound.Settings = string(modifiedSettings) - - needRestart, err := s.UpdateInboundClient(inbound, clientId) - if err != nil { - return false, needRestart, err - } - - return !clientOldEnabled, needRestart, nil -} - -func (s *InboundService) ResetClientIpLimitByEmail(clientEmail string, count int) (bool, error) { - _, inbound, err := s.GetClientInboundByEmail(clientEmail) - if err != nil { - return false, err - } - if inbound == nil { - return false, common.NewError("Inbound Not Found For Email:", clientEmail) - } - - oldClients, err := s.GetClients(inbound) - if err != nil { - return false, err - } - - clientId := "" - - for _, oldClient := range oldClients { - if oldClient.Email == clientEmail { - if inbound.Protocol == "trojan" { - clientId = oldClient.Password - } else if inbound.Protocol == "shadowsocks" { - clientId = oldClient.Email - } else { - clientId = oldClient.ID - } - break - } - } - - if len(clientId) == 0 { - return false, common.NewError("Client Not Found For Email:", clientEmail) - } - - var settings map[string]interface{} - err = json.Unmarshal([]byte(inbound.Settings), &settings) - if err != nil { - return false, err - } - clients := settings["clients"].([]interface{}) - var newClients []interface{} - for client_index := range clients { - c := clients[client_index].(map[string]interface{}) - if c["email"] == clientEmail { - c["limitIp"] = count - newClients = append(newClients, interface{}(c)) - } - } - settings["clients"] = newClients - modifiedSettings, err := json.MarshalIndent(settings, "", " ") - if err != nil { - return false, err - } - inbound.Settings = string(modifiedSettings) - needRestart, err := s.UpdateInboundClient(inbound, clientId) - return needRestart, err -} - -func (s *InboundService) ResetClientExpiryTimeByEmail(clientEmail string, expiry_time int64) (bool, error) { - _, inbound, err := s.GetClientInboundByEmail(clientEmail) - if err != nil { - return false, err - } - if inbound == nil { - return false, common.NewError("Inbound Not Found For Email:", clientEmail) - } - - oldClients, err := s.GetClients(inbound) - if err != nil { - return false, err - } - - clientId := "" - - for _, oldClient := range oldClients { - if oldClient.Email == clientEmail { - if inbound.Protocol == "trojan" { - clientId = oldClient.Password - } else if inbound.Protocol == "shadowsocks" { - clientId = oldClient.Email - } else { - clientId = oldClient.ID - } - break - } - } - - if len(clientId) == 0 { - return false, common.NewError("Client Not Found For Email:", clientEmail) - } - - var settings map[string]interface{} - err = json.Unmarshal([]byte(inbound.Settings), &settings) - if err != nil { - return false, err - } - clients := settings["clients"].([]interface{}) - var newClients []interface{} - for client_index := range clients { - c := clients[client_index].(map[string]interface{}) - if c["email"] == clientEmail { - c["expiryTime"] = expiry_time - newClients = append(newClients, interface{}(c)) - } - } - settings["clients"] = newClients - modifiedSettings, err := json.MarshalIndent(settings, "", " ") - if err != nil { - return false, err - } - inbound.Settings = string(modifiedSettings) - needRestart, err := s.UpdateInboundClient(inbound, clientId) - return needRestart, err -} - -func (s *InboundService) ResetClientTrafficLimitByEmail(clientEmail string, totalGB int) (bool, error) { - if totalGB < 0 { - return false, common.NewError("totalGB must be >= 0") - } - _, inbound, err := s.GetClientInboundByEmail(clientEmail) - if err != nil { - return false, err - } - if inbound == nil { - return false, common.NewError("Inbound Not Found For Email:", clientEmail) - } - - oldClients, err := s.GetClients(inbound) - if err != nil { - return false, err - } - - clientId := "" - - for _, oldClient := range oldClients { - if oldClient.Email == clientEmail { - if inbound.Protocol == "trojan" { - clientId = oldClient.Password - } else if inbound.Protocol == "shadowsocks" { - clientId = oldClient.Email - } else { - clientId = oldClient.ID - } - break - } - } - - if len(clientId) == 0 { - return false, common.NewError("Client Not Found For Email:", clientEmail) - } - - var settings map[string]interface{} - err = json.Unmarshal([]byte(inbound.Settings), &settings) - if err != nil { - return false, err - } - clients := settings["clients"].([]interface{}) - var newClients []interface{} - for client_index := range clients { - c := clients[client_index].(map[string]interface{}) - if c["email"] == clientEmail { - c["totalGB"] = totalGB * 1024 * 1024 * 1024 - newClients = append(newClients, interface{}(c)) - } - } - settings["clients"] = newClients - modifiedSettings, err := json.MarshalIndent(settings, "", " ") - if err != nil { - return false, err - } - inbound.Settings = string(modifiedSettings) - needRestart, err := s.UpdateInboundClient(inbound, clientId) - return needRestart, err -} - -func (s *InboundService) ResetClientTrafficByEmail(clientEmail string) error { - db := database.GetDB() - - result := db.Model(xray.ClientTraffic{}). - Where("email = ?", clientEmail). - Updates(map[string]interface{}{"enable": true, "up": 0, "down": 0}) - - err := result.Error - if err != nil { - return err - } - return nil -} - -func (s *InboundService) ResetClientTraffic(id int, clientEmail string) (bool, error) { - needRestart := false - - traffic, err := s.GetClientTrafficByEmail(clientEmail) - if err != nil { - return false, err - } - - if !traffic.Enable { - inbound, err := s.GetInbound(id) - if err != nil { - return false, err - } - clients, err := s.GetClients(inbound) - if err != nil { - return false, err - } - for _, client := range clients { - if client.Email == clientEmail && client.Enable { - s.xrayApi.Init(p.GetAPIPort()) - cipher := "" - if string(inbound.Protocol) == "shadowsocks" { - var oldSettings map[string]interface{} - err = json.Unmarshal([]byte(inbound.Settings), &oldSettings) - if err != nil { - return false, err - } - cipher = oldSettings["method"].(string) - } - err1 := s.xrayApi.AddUser(string(inbound.Protocol), inbound.Tag, map[string]interface{}{ - "email": client.Email, - "id": client.ID, - "security": client.Security, - "flow": client.Flow, - "password": client.Password, - "cipher": cipher, - }) - if err1 == nil { - logger.Debug("Client enabled due to reset traffic:", clientEmail) - } else { - logger.Debug("Error in enabling client by api:", err1) - needRestart = true - } - s.xrayApi.Close() - break - } - } - } - - traffic.Up = 0 - traffic.Down = 0 - traffic.Enable = true - - db := database.GetDB() - err = db.Save(traffic).Error - if err != nil { - return false, err - } - - return needRestart, nil -} - -func (s *InboundService) ResetAllClientTraffics(id int) error { - db := database.GetDB() - - whereText := "inbound_id " - if id == -1 { - whereText += " > ?" - } else { - whereText += " = ?" - } - - result := db.Model(xray.ClientTraffic{}). - Where(whereText, id). - Updates(map[string]interface{}{"enable": true, "up": 0, "down": 0}) - - err := result.Error - return err -} - -func (s *InboundService) ResetAllTraffics() error { - db := database.GetDB() - - result := db.Model(model.Inbound{}). - Where("user_id > ?", 0). - Updates(map[string]interface{}{"up": 0, "down": 0}) - - err := result.Error - return err -} - -func (s *InboundService) DelDepletedClients(id int) (err error) { - db := database.GetDB() - tx := db.Begin() - defer func() { - if err == nil { - tx.Commit() - } else { - tx.Rollback() - } - }() - - whereText := "reset = 0 and inbound_id " - if id < 0 { - whereText += "> ?" - } else { - whereText += "= ?" - } - - depletedClients := []xray.ClientTraffic{} - err = db.Model(xray.ClientTraffic{}).Where(whereText+" and enable = ?", id, false).Select("inbound_id, GROUP_CONCAT(email) as email").Group("inbound_id").Find(&depletedClients).Error - if err != nil { - return err - } - - for _, depletedClient := range depletedClients { - emails := strings.Split(depletedClient.Email, ",") - oldInbound, err := s.GetInbound(depletedClient.InboundId) - if err != nil { - return err - } - var oldSettings map[string]interface{} - err = json.Unmarshal([]byte(oldInbound.Settings), &oldSettings) - if err != nil { - return err - } - - oldClients := oldSettings["clients"].([]interface{}) - var newClients []interface{} - for _, client := range oldClients { - deplete := false - c := client.(map[string]interface{}) - for _, email := range emails { - if email == c["email"].(string) { - deplete = true - break - } - } - if !deplete { - newClients = append(newClients, client) - } - } - if len(newClients) > 0 { - oldSettings["clients"] = newClients - - newSettings, err := json.MarshalIndent(oldSettings, "", " ") - if err != nil { - return err - } - - oldInbound.Settings = string(newSettings) - err = tx.Save(oldInbound).Error - if err != nil { - return err - } - } else { - // Delete inbound if no client remains - s.DelInbound(depletedClient.InboundId) - } - } - - err = tx.Where(whereText+" and enable = ?", id, false).Delete(xray.ClientTraffic{}).Error - if err != nil { - return err - } - - return nil -} - -func (s *InboundService) GetClientTrafficTgBot(tgId int64) ([]*xray.ClientTraffic, error) { - db := database.GetDB() - var inbounds []*model.Inbound - - // Retrieve inbounds where settings contain the given tgId - err := db.Model(model.Inbound{}).Where("settings LIKE ?", fmt.Sprintf(`%%"tgId": %d%%`, tgId)).Find(&inbounds).Error - if err != nil && err != gorm.ErrRecordNotFound { - logger.Errorf("Error retrieving inbounds with tgId %d: %v", tgId, err) - return nil, err - } - - var emails []string - for _, inbound := range inbounds { - clients, err := s.GetClients(inbound) - if err != nil { - logger.Errorf("Error retrieving clients for inbound %d: %v", inbound.Id, err) - continue - } - for _, client := range clients { - if client.TgID == tgId { - emails = append(emails, client.Email) - } - } - } - - var traffics []*xray.ClientTraffic - err = db.Model(xray.ClientTraffic{}).Where("email IN ?", emails).Find(&traffics).Error - if err != nil { - if err == gorm.ErrRecordNotFound { - logger.Warning("No ClientTraffic records found for emails:", emails) - return nil, nil - } - logger.Errorf("Error retrieving ClientTraffic for emails %v: %v", emails, err) - return nil, err - } - - return traffics, nil -} - -func (s *InboundService) GetClientTrafficByEmail(email string) (traffic *xray.ClientTraffic, err error) { - db := database.GetDB() - var traffics []*xray.ClientTraffic - - err = db.Model(xray.ClientTraffic{}).Where("email = ?", email).Find(&traffics).Error - if err != nil { - logger.Warningf("Error retrieving ClientTraffic with email %s: %v", email, err) - return nil, err - } - if len(traffics) > 0 { - return traffics[0], nil - } - - return nil, nil -} - -func (s *InboundService) GetClientTrafficByID(id string) ([]xray.ClientTraffic, error) { - db := database.GetDB() - var traffics []xray.ClientTraffic - - err := db.Model(xray.ClientTraffic{}).Where(`email IN( - SELECT JSON_EXTRACT(client.value, '$.email') as email - FROM inbounds, - JSON_EACH(JSON_EXTRACT(inbounds.settings, '$.clients')) AS client - WHERE - JSON_EXTRACT(client.value, '$.id') in (?) - )`, id).Find(&traffics).Error - - if err != nil { - logger.Debug(err) - return nil, err - } - return traffics, err -} - -func (s *InboundService) SearchClientTraffic(query string) (traffic *xray.ClientTraffic, err error) { - db := database.GetDB() - inbound := &model.Inbound{} - traffic = &xray.ClientTraffic{} - - // Search for inbound settings that contain the query - err = db.Model(model.Inbound{}).Where("settings LIKE ?", "%\""+query+"\"%").First(inbound).Error - if err != nil { - if err == gorm.ErrRecordNotFound { - logger.Warningf("Inbound settings containing query %s not found: %v", query, err) - return nil, err - } - logger.Errorf("Error searching for inbound settings with query %s: %v", query, err) - return nil, err - } - - traffic.InboundId = inbound.Id - - // Unmarshal settings to get clients - settings := map[string][]model.Client{} - if err := json.Unmarshal([]byte(inbound.Settings), &settings); err != nil { - logger.Errorf("Error unmarshalling inbound settings for inbound ID %d: %v", inbound.Id, err) - return nil, err - } - - clients := settings["clients"] - for _, client := range clients { - if (client.ID == query || client.Password == query) && client.Email != "" { - traffic.Email = client.Email - break - } - } - - if traffic.Email == "" { - logger.Warningf("No client found with query %s in inbound ID %d", query, inbound.Id) - return nil, gorm.ErrRecordNotFound - } - - // Retrieve ClientTraffic based on the found email - err = db.Model(xray.ClientTraffic{}).Where("email = ?", traffic.Email).First(traffic).Error - if err != nil { - if err == gorm.ErrRecordNotFound { - logger.Warningf("ClientTraffic for email %s not found: %v", traffic.Email, err) - return nil, err - } - logger.Errorf("Error retrieving ClientTraffic for email %s: %v", traffic.Email, err) - return nil, err - } - - return traffic, nil -} - -func (s *InboundService) GetInboundClientIps(clientEmail string) (string, error) { - db := database.GetDB() - InboundClientIps := &model.InboundClientIps{} - err := db.Model(model.InboundClientIps{}).Where("client_email = ?", clientEmail).First(InboundClientIps).Error - if err != nil { - return "", err - } - return InboundClientIps.Ips, nil -} - -func (s *InboundService) ClearClientIps(clientEmail string) error { - db := database.GetDB() - - result := db.Model(model.InboundClientIps{}). - Where("client_email = ?", clientEmail). - Update("ips", "") - err := result.Error - if err != nil { - return err - } - return nil -} - -func (s *InboundService) SearchInbounds(query string) ([]*model.Inbound, error) { - db := database.GetDB() - var inbounds []*model.Inbound - err := db.Model(model.Inbound{}).Preload("ClientStats").Where("remark like ?", "%"+query+"%").Find(&inbounds).Error - if err != nil && err != gorm.ErrRecordNotFound { - return nil, err - } - return inbounds, nil -} - -func (s *InboundService) MigrationRequirements() { - db := database.GetDB() - tx := db.Begin() - var err error - defer func() { - if err == nil { - tx.Commit() - } else { - tx.Rollback() - } - }() - - // Fix inbounds based problems - var inbounds []*model.Inbound - err = tx.Model(model.Inbound{}).Where("protocol IN (?)", []string{"vmess", "vless", "trojan"}).Find(&inbounds).Error - if err != nil && err != gorm.ErrRecordNotFound { - return - } - for inbound_index := range inbounds { - settings := map[string]interface{}{} - json.Unmarshal([]byte(inbounds[inbound_index].Settings), &settings) - clients, ok := settings["clients"].([]interface{}) - if ok { - // Fix Client configuration problems - var newClients []interface{} - for client_index := range clients { - c := clients[client_index].(map[string]interface{}) - - // Add email='' if it is not exists - if _, ok := c["email"]; !ok { - c["email"] = "" - } - - // Convert string tgId to int64 - if _, ok := c["tgId"]; ok { - var tgId interface{} = c["tgId"] - if tgIdStr, ok2 := tgId.(string); ok2 { - tgIdInt64, err := strconv.ParseInt(strings.ReplaceAll(tgIdStr, " ", ""), 10, 64) - if err == nil { - c["tgId"] = tgIdInt64 - } - } - } - - // Remove "flow": "xtls-rprx-direct" - if _, ok := c["flow"]; ok { - if c["flow"] == "xtls-rprx-direct" { - c["flow"] = "" - } - } - newClients = append(newClients, interface{}(c)) - } - settings["clients"] = newClients - modifiedSettings, err := json.MarshalIndent(settings, "", " ") - if err != nil { - return - } - - inbounds[inbound_index].Settings = string(modifiedSettings) - } - - // Add client traffic row for all clients which has email - modelClients, err := s.GetClients(inbounds[inbound_index]) - if err != nil { - return - } - for _, modelClient := range modelClients { - if len(modelClient.Email) > 0 { - var count int64 - tx.Model(xray.ClientTraffic{}).Where("email = ?", modelClient.Email).Count(&count) - if count == 0 { - s.AddClientStat(tx, inbounds[inbound_index].Id, &modelClient) - } - } - } - } - tx.Save(inbounds) - - // Remove orphaned traffics - tx.Where("inbound_id = 0").Delete(xray.ClientTraffic{}) - - // Migrate old MultiDomain to External Proxy - var externalProxy []struct { - Id int - Port int - StreamSettings []byte - } - err = tx.Raw(`select id, port, stream_settings - from inbounds - WHERE protocol in ('vmess','vless','trojan') - AND json_extract(stream_settings, '$.security') = 'tls' - AND json_extract(stream_settings, '$.tlsSettings.settings.domains') IS NOT NULL`).Scan(&externalProxy).Error - if err != nil || len(externalProxy) == 0 { - return - } - - for _, ep := range externalProxy { - var reverses interface{} - var stream map[string]interface{} - json.Unmarshal(ep.StreamSettings, &stream) - if tlsSettings, ok := stream["tlsSettings"].(map[string]interface{}); ok { - if settings, ok := tlsSettings["settings"].(map[string]interface{}); ok { - if domains, ok := settings["domains"].([]interface{}); ok { - for _, domain := range domains { - if domainMap, ok := domain.(map[string]interface{}); ok { - domainMap["forceTls"] = "same" - domainMap["port"] = ep.Port - domainMap["dest"] = domainMap["domain"].(string) - delete(domainMap, "domain") - } - } - } - reverses = settings["domains"] - delete(settings, "domains") - } - } - stream["externalProxy"] = reverses - newStream, _ := json.MarshalIndent(stream, " ", " ") - tx.Model(model.Inbound{}).Where("id = ?", ep.Id).Update("stream_settings", newStream) - } - - err = tx.Raw(`UPDATE inbounds - SET tag = REPLACE(tag, '0.0.0.0:', '') - WHERE INSTR(tag, '0.0.0.0:') > 0;`).Error - if err != nil { - return - } -} - -func (s *InboundService) MigrateDB() { - s.MigrationRequirements() - s.MigrationRemoveOrphanedTraffics() -} - -func (s *InboundService) GetOnlineClients() []string { - return p.GetOnlineClients() -} diff --git a/web/service/outbound.go b/web/service/outbound.go deleted file mode 100644 index 3b432228..00000000 --- a/web/service/outbound.go +++ /dev/null @@ -1,100 +0,0 @@ -package service - -import ( - "x-ui/database" - "x-ui/database/model" - "x-ui/logger" - "x-ui/xray" - - "gorm.io/gorm" -) - -type OutboundService struct{} - -func (s *OutboundService) AddTraffic(traffics []*xray.Traffic, clientTraffics []*xray.ClientTraffic) (error, bool) { - var err error - db := database.GetDB() - tx := db.Begin() - - defer func() { - if err != nil { - tx.Rollback() - } else { - tx.Commit() - } - }() - - err = s.addOutboundTraffic(tx, traffics) - if err != nil { - return err, false - } - - return nil, false -} - -func (s *OutboundService) addOutboundTraffic(tx *gorm.DB, traffics []*xray.Traffic) error { - if len(traffics) == 0 { - return nil - } - - var err error - - for _, traffic := range traffics { - if traffic.IsOutbound { - - var outbound model.OutboundTraffics - - err = tx.Model(&model.OutboundTraffics{}).Where("tag = ?", traffic.Tag). - FirstOrCreate(&outbound).Error - if err != nil { - return err - } - - outbound.Tag = traffic.Tag - outbound.Up = outbound.Up + traffic.Up - outbound.Down = outbound.Down + traffic.Down - outbound.Total = outbound.Up + outbound.Down - - err = tx.Save(&outbound).Error - if err != nil { - return err - } - } - } - return nil -} - -func (s *OutboundService) GetOutboundsTraffic() ([]*model.OutboundTraffics, error) { - db := database.GetDB() - var traffics []*model.OutboundTraffics - - err := db.Model(model.OutboundTraffics{}).Find(&traffics).Error - if err != nil { - logger.Warning("Error retrieving OutboundTraffics: ", err) - return nil, err - } - - return traffics, nil -} - -func (s *OutboundService) ResetOutboundTraffic(tag string) error { - db := database.GetDB() - - whereText := "tag " - if tag == "-alltags-" { - whereText += " <> ?" - } else { - whereText += " = ?" - } - - result := db.Model(model.OutboundTraffics{}). - Where(whereText, tag). - Updates(map[string]interface{}{"up": 0, "down": 0, "total": 0}) - - err := result.Error - if err != nil { - return err - } - - return nil -} diff --git a/web/service/panel.go b/web/service/panel.go deleted file mode 100644 index 3b0c75a5..00000000 --- a/web/service/panel.go +++ /dev/null @@ -1,26 +0,0 @@ -package service - -import ( - "os" - "syscall" - "time" - - "x-ui/logger" -) - -type PanelService struct{} - -func (s *PanelService) RestartPanel(delay time.Duration) error { - p, err := os.FindProcess(syscall.Getpid()) - if err != nil { - return err - } - go func() { - time.Sleep(delay) - err := p.Signal(syscall.SIGHUP) - if err != nil { - logger.Error("failed to send SIGHUP signal:", err) - } - }() - return nil -} diff --git a/web/service/server.go b/web/service/server.go deleted file mode 100644 index ec4f7c7f..00000000 --- a/web/service/server.go +++ /dev/null @@ -1,609 +0,0 @@ -package service - -import ( - "archive/zip" - "bytes" - "encoding/json" - "fmt" - "io" - "io/fs" - "mime/multipart" - "net/http" - "os" - "os/exec" - "runtime" - "strconv" - "strings" - "time" - - "x-ui/config" - "x-ui/database" - "x-ui/logger" - "x-ui/util/common" - "x-ui/util/sys" - "x-ui/xray" - - "github.com/shirou/gopsutil/v4/cpu" - "github.com/shirou/gopsutil/v4/disk" - "github.com/shirou/gopsutil/v4/host" - "github.com/shirou/gopsutil/v4/load" - "github.com/shirou/gopsutil/v4/mem" - "github.com/shirou/gopsutil/v4/net" -) - -type ProcessState string - -const ( - Running ProcessState = "running" - Stop ProcessState = "stop" - Error ProcessState = "error" -) - -type Status struct { - T time.Time `json:"-"` - Cpu float64 `json:"cpu"` - CpuCores int `json:"cpuCores"` - LogicalPro int `json:"logicalPro"` - CpuSpeedMhz float64 `json:"cpuSpeedMhz"` - Mem struct { - Current uint64 `json:"current"` - Total uint64 `json:"total"` - } `json:"mem"` - Swap struct { - Current uint64 `json:"current"` - Total uint64 `json:"total"` - } `json:"swap"` - Disk struct { - Current uint64 `json:"current"` - Total uint64 `json:"total"` - } `json:"disk"` - Xray struct { - State ProcessState `json:"state"` - ErrorMsg string `json:"errorMsg"` - Version string `json:"version"` - } `json:"xray"` - Uptime uint64 `json:"uptime"` - Loads []float64 `json:"loads"` - TcpCount int `json:"tcpCount"` - UdpCount int `json:"udpCount"` - NetIO struct { - Up uint64 `json:"up"` - Down uint64 `json:"down"` - } `json:"netIO"` - NetTraffic struct { - Sent uint64 `json:"sent"` - Recv uint64 `json:"recv"` - } `json:"netTraffic"` - PublicIP struct { - IPv4 string `json:"ipv4"` - IPv6 string `json:"ipv6"` - } `json:"publicIP"` - AppStats struct { - Threads uint32 `json:"threads"` - Mem uint64 `json:"mem"` - Uptime uint64 `json:"uptime"` - } `json:"appStats"` -} - -type Release struct { - TagName string `json:"tag_name"` -} - -type ServerService struct { - xrayService XrayService - inboundService InboundService -} - -func getPublicIP(url string) string { - resp, err := http.Get(url) - if err != nil { - return "N/A" - } - defer resp.Body.Close() - - ip, err := io.ReadAll(resp.Body) - if err != nil { - return "N/A" - } - - ipString := string(ip) - if ipString == "" { - return "N/A" - } - - return ipString -} - -func (s *ServerService) GetStatus(lastStatus *Status) *Status { - now := time.Now() - status := &Status{ - T: now, - } - - percents, err := cpu.Percent(0, false) - if err != nil { - logger.Warning("get cpu percent failed:", err) - } else { - status.Cpu = percents[0] - } - - status.CpuCores, err = cpu.Counts(false) - if err != nil { - logger.Warning("get cpu cores count failed:", err) - } - - status.LogicalPro = runtime.NumCPU() - if p != nil && p.IsRunning() { - status.AppStats.Uptime = p.GetUptime() - } else { - status.AppStats.Uptime = 0 - } - - cpuInfos, err := cpu.Info() - if err != nil { - logger.Warning("get cpu info failed:", err) - } else if len(cpuInfos) > 0 { - cpuInfo := cpuInfos[0] - status.CpuSpeedMhz = cpuInfo.Mhz // setting CPU speed in MHz - } else { - logger.Warning("could not find cpu info") - } - - upTime, err := host.Uptime() - if err != nil { - logger.Warning("get uptime failed:", err) - } else { - status.Uptime = upTime - } - - memInfo, err := mem.VirtualMemory() - if err != nil { - logger.Warning("get virtual memory failed:", err) - } else { - status.Mem.Current = memInfo.Used - status.Mem.Total = memInfo.Total - } - - swapInfo, err := mem.SwapMemory() - if err != nil { - logger.Warning("get swap memory failed:", err) - } else { - status.Swap.Current = swapInfo.Used - status.Swap.Total = swapInfo.Total - } - - distInfo, err := disk.Usage("/") - if err != nil { - logger.Warning("get dist usage failed:", err) - } else { - status.Disk.Current = distInfo.Used - status.Disk.Total = distInfo.Total - } - - avgState, err := load.Avg() - if err != nil { - logger.Warning("get load avg failed:", err) - } else { - status.Loads = []float64{avgState.Load1, avgState.Load5, avgState.Load15} - } - - ioStats, err := net.IOCounters(false) - if err != nil { - logger.Warning("get io counters failed:", err) - } else if len(ioStats) > 0 { - ioStat := ioStats[0] - status.NetTraffic.Sent = ioStat.BytesSent - status.NetTraffic.Recv = ioStat.BytesRecv - - if lastStatus != nil { - duration := now.Sub(lastStatus.T) - seconds := float64(duration) / float64(time.Second) - up := uint64(float64(status.NetTraffic.Sent-lastStatus.NetTraffic.Sent) / seconds) - down := uint64(float64(status.NetTraffic.Recv-lastStatus.NetTraffic.Recv) / seconds) - status.NetIO.Up = up - status.NetIO.Down = down - } - } else { - logger.Warning("can not find io counters") - } - - status.TcpCount, err = sys.GetTCPCount() - if err != nil { - logger.Warning("get tcp connections failed:", err) - } - - status.UdpCount, err = sys.GetUDPCount() - if err != nil { - logger.Warning("get udp connections failed:", err) - } - - status.PublicIP.IPv4 = getPublicIP("https://api.ipify.org") - status.PublicIP.IPv6 = getPublicIP("https://api6.ipify.org") - - if s.xrayService.IsXrayRunning() { - status.Xray.State = Running - status.Xray.ErrorMsg = "" - } else { - err := s.xrayService.GetXrayErr() - if err != nil { - status.Xray.State = Error - } else { - status.Xray.State = Stop - } - status.Xray.ErrorMsg = s.xrayService.GetXrayResult() - } - status.Xray.Version = s.xrayService.GetXrayVersion() - var rtm runtime.MemStats - runtime.ReadMemStats(&rtm) - - status.AppStats.Mem = rtm.Sys - status.AppStats.Threads = uint32(runtime.NumGoroutine()) - if p != nil && p.IsRunning() { - status.AppStats.Uptime = p.GetUptime() - } else { - status.AppStats.Uptime = 0 - } - - return status -} - -func (s *ServerService) GetXrayVersions() ([]string, error) { - const ( - XrayURL = "https://api.github.com/repos/XTLS/Xray-core/releases" - bufferSize = 8192 - ) - - resp, err := http.Get(XrayURL) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - buffer := bytes.NewBuffer(make([]byte, bufferSize)) - buffer.Reset() - if _, err := buffer.ReadFrom(resp.Body); err != nil { - return nil, err - } - - var releases []Release - if err := json.Unmarshal(buffer.Bytes(), &releases); err != nil { - return nil, err - } - - var versions []string - for _, release := range releases { - tagVersion := strings.TrimPrefix(release.TagName, "v") - tagParts := strings.Split(tagVersion, ".") - if len(tagParts) != 3 { - continue - } - - major, err1 := strconv.Atoi(tagParts[0]) - minor, err2 := strconv.Atoi(tagParts[1]) - patch, err3 := strconv.Atoi(tagParts[2]) - if err1 != nil || err2 != nil || err3 != nil { - continue - } - - if (major == 1 && minor == 8 && patch == 24) || - (major >= 25) { - versions = append(versions, release.TagName) - } - } - return versions, nil -} - -func (s *ServerService) StopXrayService() (string error) { - err := s.xrayService.StopXray() - if err != nil { - logger.Error("stop xray failed:", err) - return err - } - - return nil -} - -func (s *ServerService) RestartXrayService() (string error) { - s.xrayService.StopXray() - defer func() { - err := s.xrayService.RestartXray(true) - if err != nil { - logger.Error("start xray failed:", err) - } - }() - - return nil -} - -func (s *ServerService) downloadXRay(version string) (string, error) { - osName := runtime.GOOS - arch := runtime.GOARCH - - switch osName { - case "darwin": - osName = "macos" - } - - switch arch { - case "amd64": - arch = "64" - case "arm64": - arch = "arm64-v8a" - case "armv7": - arch = "arm32-v7a" - case "armv6": - arch = "arm32-v6" - case "armv5": - arch = "arm32-v5" - case "386": - arch = "32" - case "s390x": - arch = "s390x" - } - - fileName := fmt.Sprintf("Xray-%s-%s.zip", osName, arch) - url := fmt.Sprintf("https://github.com/XTLS/Xray-core/releases/download/%s/%s", version, fileName) - resp, err := http.Get(url) - if err != nil { - return "", err - } - defer resp.Body.Close() - - os.Remove(fileName) - file, err := os.Create(fileName) - if err != nil { - return "", err - } - defer file.Close() - - _, err = io.Copy(file, resp.Body) - if err != nil { - return "", err - } - - return fileName, nil -} - -func (s *ServerService) UpdateXray(version string) error { - zipFileName, err := s.downloadXRay(version) - if err != nil { - return err - } - - zipFile, err := os.Open(zipFileName) - if err != nil { - return err - } - defer func() { - zipFile.Close() - os.Remove(zipFileName) - }() - - stat, err := zipFile.Stat() - if err != nil { - return err - } - reader, err := zip.NewReader(zipFile, stat.Size()) - if err != nil { - return err - } - - s.xrayService.StopXray() - defer func() { - err := s.xrayService.RestartXray(true) - if err != nil { - logger.Error("start xray failed:", err) - } - }() - - copyZipFile := func(zipName string, fileName string) error { - zipFile, err := reader.Open(zipName) - if err != nil { - return err - } - os.Remove(fileName) - file, err := os.OpenFile(fileName, os.O_CREATE|os.O_RDWR|os.O_TRUNC, fs.ModePerm) - if err != nil { - return err - } - defer file.Close() - _, err = io.Copy(file, zipFile) - return err - } - - err = copyZipFile("xray", xray.GetBinaryPath()) - if err != nil { - return err - } - - return nil -} - -func (s *ServerService) GetLogs(count string, level string, syslog string) []string { - c, _ := strconv.Atoi(count) - var lines []string - - if syslog == "true" { - cmdArgs := []string{"journalctl", "-u", "x-ui", "--no-pager", "-n", count, "-p", level} - // Run the command - cmd := exec.Command(cmdArgs[0], cmdArgs[1:]...) - var out bytes.Buffer - cmd.Stdout = &out - err := cmd.Run() - if err != nil { - return []string{"Failed to run journalctl command!"} - } - lines = strings.Split(out.String(), "\n") - } else { - lines = logger.GetLogs(c, level) - } - - return lines -} - -func (s *ServerService) GetConfigJson() (interface{}, error) { - config, err := s.xrayService.GetXrayConfig() - if err != nil { - return nil, err - } - contents, err := json.MarshalIndent(config, "", " ") - if err != nil { - return nil, err - } - - var jsonData interface{} - err = json.Unmarshal(contents, &jsonData) - if err != nil { - return nil, err - } - - return jsonData, nil -} - -func (s *ServerService) GetDb() ([]byte, error) { - // Update by manually trigger a checkpoint operation - err := database.Checkpoint() - if err != nil { - return nil, err - } - // Open the file for reading - file, err := os.Open(config.GetDBPath()) - if err != nil { - return nil, err - } - defer file.Close() - - // Read the file contents - fileContents, err := io.ReadAll(file) - if err != nil { - return nil, err - } - - return fileContents, nil -} - -func (s *ServerService) ImportDB(file multipart.File) error { - // Check if the file is a SQLite database - isValidDb, err := database.IsSQLiteDB(file) - if err != nil { - return common.NewErrorf("Error checking db file format: %v", err) - } - if !isValidDb { - return common.NewError("Invalid db file format") - } - - // Reset the file reader to the beginning - _, err = file.Seek(0, 0) - if err != nil { - return common.NewErrorf("Error resetting file reader: %v", err) - } - - // Save the file as temporary file - tempPath := fmt.Sprintf("%s.temp", config.GetDBPath()) - // Remove the existing fallback file (if any) before creating one - _, err = os.Stat(tempPath) - if err == nil { - errRemove := os.Remove(tempPath) - if errRemove != nil { - return common.NewErrorf("Error removing existing temporary db file: %v", errRemove) - } - } - // Create the temporary file - tempFile, err := os.Create(tempPath) - if err != nil { - return common.NewErrorf("Error creating temporary db file: %v", err) - } - defer tempFile.Close() - - // Remove temp file before returning - defer os.Remove(tempPath) - - // Save uploaded file to temporary file - _, err = io.Copy(tempFile, file) - if err != nil { - return common.NewErrorf("Error saving db: %v", err) - } - - // Check if we can init db or not - err = database.InitDB(tempPath) - if err != nil { - return common.NewErrorf("Error checking db: %v", err) - } - - // Stop Xray - s.StopXrayService() - - // Backup the current database for fallback - fallbackPath := fmt.Sprintf("%s.backup", config.GetDBPath()) - // Remove the existing fallback file (if any) - _, err = os.Stat(fallbackPath) - if err == nil { - errRemove := os.Remove(fallbackPath) - if errRemove != nil { - return common.NewErrorf("Error removing existing fallback db file: %v", errRemove) - } - } - // Move the current database to the fallback location - err = os.Rename(config.GetDBPath(), fallbackPath) - if err != nil { - return common.NewErrorf("Error backing up temporary db file: %v", err) - } - - // Remove the temporary file before returning - defer os.Remove(fallbackPath) - - // Move temp to DB path - err = os.Rename(tempPath, config.GetDBPath()) - if err != nil { - errRename := os.Rename(fallbackPath, config.GetDBPath()) - if errRename != nil { - return common.NewErrorf("Error moving db file and restoring fallback: %v", errRename) - } - return common.NewErrorf("Error moving db file: %v", err) - } - - // Migrate DB - err = database.InitDB(config.GetDBPath()) - if err != nil { - errRename := os.Rename(fallbackPath, config.GetDBPath()) - if errRename != nil { - return common.NewErrorf("Error migrating db and restoring fallback: %v", errRename) - } - return common.NewErrorf("Error migrating db: %v", err) - } - s.inboundService.MigrateDB() - - // Start Xray - err = s.RestartXrayService() - if err != nil { - return common.NewErrorf("Imported DB but Failed to start Xray: %v", err) - } - - return nil -} - -func (s *ServerService) GetNewX25519Cert() (interface{}, error) { - // Run the command - cmd := exec.Command(xray.GetBinaryPath(), "x25519") - var out bytes.Buffer - cmd.Stdout = &out - err := cmd.Run() - if err != nil { - return nil, err - } - - lines := strings.Split(out.String(), "\n") - - privateKeyLine := strings.Split(lines[0], ":") - publicKeyLine := strings.Split(lines[1], ":") - - privateKey := strings.TrimSpace(privateKeyLine[1]) - publicKey := strings.TrimSpace(publicKeyLine[1]) - - keyPair := map[string]interface{}{ - "privateKey": privateKey, - "publicKey": publicKey, - } - - return keyPair, nil -} diff --git a/web/service/setting.go b/web/service/setting.go deleted file mode 100644 index d238c33d..00000000 --- a/web/service/setting.go +++ /dev/null @@ -1,598 +0,0 @@ -package service - -import ( - _ "embed" - "encoding/json" - "errors" - "fmt" - "reflect" - "strconv" - "strings" - "time" - - "x-ui/database" - "x-ui/database/model" - "x-ui/logger" - "x-ui/util/common" - "x-ui/util/random" - "x-ui/util/reflect_util" - "x-ui/web/entity" - "x-ui/xray" -) - -//go:embed config.json -var xrayTemplateConfig string - -var defaultValueMap = map[string]string{ - "xrayTemplateConfig": xrayTemplateConfig, - "webListen": "", - "webDomain": "", - "webPort": "2053", - "webCertFile": "", - "webKeyFile": "", - "secret": random.Seq(32), - "webBasePath": "/", - "sessionMaxAge": "60", - "pageSize": "50", - "expireDiff": "0", - "trafficDiff": "0", - "remarkModel": "-ieo", - "timeLocation": "Local", - "tgBotEnable": "false", - "tgBotToken": "", - "tgBotProxy": "", - "tgBotAPIServer": "", - "tgBotChatId": "", - "tgRunTime": "@daily", - "tgBotBackup": "false", - "tgBotLoginNotify": "true", - "tgCpu": "80", - "tgLang": "en-US", - "secretEnable": "false", - "subEnable": "false", - "subListen": "", - "subPort": "2096", - "subPath": "/sub/", - "subDomain": "", - "subCertFile": "", - "subKeyFile": "", - "subUpdates": "12", - "subEncrypt": "true", - "subShowInfo": "true", - "subURI": "", - "subJsonPath": "/json/", - "subJsonURI": "", - "subJsonFragment": "", - "subJsonNoises": "", - "subJsonMux": "", - "subJsonRules": "", - "datepicker": "gregorian", - "warp": "", -} - -type SettingService struct{} - -func (s *SettingService) GetDefaultJsonConfig() (interface{}, error) { - var jsonData interface{} - err := json.Unmarshal([]byte(xrayTemplateConfig), &jsonData) - if err != nil { - return nil, err - } - return jsonData, nil -} - -func (s *SettingService) GetAllSetting() (*entity.AllSetting, error) { - db := database.GetDB() - settings := make([]*model.Setting, 0) - err := db.Model(model.Setting{}).Not("key = ?", "xrayTemplateConfig").Find(&settings).Error - if err != nil { - return nil, err - } - allSetting := &entity.AllSetting{} - t := reflect.TypeOf(allSetting).Elem() - v := reflect.ValueOf(allSetting).Elem() - fields := reflect_util.GetFields(t) - - setSetting := func(key, value string) (err error) { - defer func() { - panicErr := recover() - if panicErr != nil { - err = errors.New(fmt.Sprint(panicErr)) - } - }() - - var found bool - var field reflect.StructField - for _, f := range fields { - if f.Tag.Get("json") == key { - field = f - found = true - break - } - } - - if !found { - // Some settings are automatically generated, no need to return to the front end to modify the user - return nil - } - - fieldV := v.FieldByName(field.Name) - switch t := fieldV.Interface().(type) { - case int: - n, err := strconv.ParseInt(value, 10, 64) - if err != nil { - return err - } - fieldV.SetInt(n) - case string: - fieldV.SetString(value) - case bool: - fieldV.SetBool(value == "true") - default: - return common.NewErrorf("unknown field %v type %v", key, t) - } - return - } - - keyMap := map[string]bool{} - for _, setting := range settings { - err := setSetting(setting.Key, setting.Value) - if err != nil { - return nil, err - } - keyMap[setting.Key] = true - } - - for key, value := range defaultValueMap { - if keyMap[key] { - continue - } - err := setSetting(key, value) - if err != nil { - return nil, err - } - } - - return allSetting, nil -} - -func (s *SettingService) ResetSettings() error { - db := database.GetDB() - err := db.Where("1 = 1").Delete(model.Setting{}).Error - if err != nil { - return err - } - return db.Model(model.User{}). - Where("1 = 1"). - Update("login_secret", "").Error -} - -func (s *SettingService) getSetting(key string) (*model.Setting, error) { - db := database.GetDB() - setting := &model.Setting{} - err := db.Model(model.Setting{}).Where("key = ?", key).First(setting).Error - if err != nil { - return nil, err - } - return setting, nil -} - -func (s *SettingService) saveSetting(key string, value string) error { - setting, err := s.getSetting(key) - db := database.GetDB() - if database.IsNotFound(err) { - return db.Create(&model.Setting{ - Key: key, - Value: value, - }).Error - } else if err != nil { - return err - } - setting.Key = key - setting.Value = value - return db.Save(setting).Error -} - -func (s *SettingService) getString(key string) (string, error) { - setting, err := s.getSetting(key) - if database.IsNotFound(err) { - value, ok := defaultValueMap[key] - if !ok { - return "", common.NewErrorf("key <%v> not in defaultValueMap", key) - } - return value, nil - } else if err != nil { - return "", err - } - return setting.Value, nil -} - -func (s *SettingService) setString(key string, value string) error { - return s.saveSetting(key, value) -} - -func (s *SettingService) getBool(key string) (bool, error) { - str, err := s.getString(key) - if err != nil { - return false, err - } - return strconv.ParseBool(str) -} - -func (s *SettingService) setBool(key string, value bool) error { - return s.setString(key, strconv.FormatBool(value)) -} - -func (s *SettingService) getInt(key string) (int, error) { - str, err := s.getString(key) - if err != nil { - return 0, err - } - return strconv.Atoi(str) -} - -func (s *SettingService) setInt(key string, value int) error { - return s.setString(key, strconv.Itoa(value)) -} - -func (s *SettingService) GetXrayConfigTemplate() (string, error) { - return s.getString("xrayTemplateConfig") -} - -func (s *SettingService) GetListen() (string, error) { - return s.getString("webListen") -} - -func (s *SettingService) SetListen(ip string) error { - return s.setString("webListen", ip) -} - -func (s *SettingService) GetWebDomain() (string, error) { - return s.getString("webDomain") -} - -func (s *SettingService) GetTgBotToken() (string, error) { - return s.getString("tgBotToken") -} - -func (s *SettingService) SetTgBotToken(token string) error { - return s.setString("tgBotToken", token) -} - -func (s *SettingService) GetTgBotProxy() (string, error) { - return s.getString("tgBotProxy") -} - -func (s *SettingService) SetTgBotProxy(token string) error { - return s.setString("tgBotProxy", token) -} - -func (s *SettingService) GetTgBotAPIServer() (string, error) { - return s.getString("tgBotAPIServer") -} - -func (s *SettingService) SetTgBotAPIServer(token string) error { - return s.setString("tgBotAPIServer", token) -} - -func (s *SettingService) GetTgBotChatId() (string, error) { - return s.getString("tgBotChatId") -} - -func (s *SettingService) SetTgBotChatId(chatIds string) error { - return s.setString("tgBotChatId", chatIds) -} - -func (s *SettingService) GetTgbotEnabled() (bool, error) { - return s.getBool("tgBotEnable") -} - -func (s *SettingService) SetTgbotEnabled(value bool) error { - return s.setBool("tgBotEnable", value) -} - -func (s *SettingService) GetTgbotRuntime() (string, error) { - return s.getString("tgRunTime") -} - -func (s *SettingService) SetTgbotRuntime(time string) error { - return s.setString("tgRunTime", time) -} - -func (s *SettingService) GetTgBotBackup() (bool, error) { - return s.getBool("tgBotBackup") -} - -func (s *SettingService) GetTgBotLoginNotify() (bool, error) { - return s.getBool("tgBotLoginNotify") -} - -func (s *SettingService) GetTgCpu() (int, error) { - return s.getInt("tgCpu") -} - -func (s *SettingService) GetTgLang() (string, error) { - return s.getString("tgLang") -} - -func (s *SettingService) GetPort() (int, error) { - return s.getInt("webPort") -} - -func (s *SettingService) SetPort(port int) error { - return s.setInt("webPort", port) -} - -func (s *SettingService) SetCertFile(webCertFile string) error { - return s.setString("webCertFile", webCertFile) -} - -func (s *SettingService) GetCertFile() (string, error) { - return s.getString("webCertFile") -} - -func (s *SettingService) SetKeyFile(webKeyFile string) error { - return s.setString("webKeyFile", webKeyFile) -} - -func (s *SettingService) GetKeyFile() (string, error) { - return s.getString("webKeyFile") -} - -func (s *SettingService) GetExpireDiff() (int, error) { - return s.getInt("expireDiff") -} - -func (s *SettingService) GetTrafficDiff() (int, error) { - return s.getInt("trafficDiff") -} - -func (s *SettingService) GetSessionMaxAge() (int, error) { - return s.getInt("sessionMaxAge") -} - -func (s *SettingService) GetRemarkModel() (string, error) { - return s.getString("remarkModel") -} - -func (s *SettingService) GetSecretStatus() (bool, error) { - return s.getBool("secretEnable") -} - -func (s *SettingService) SetSecretStatus(value bool) error { - return s.setBool("secretEnable", value) -} - -func (s *SettingService) GetSecret() ([]byte, error) { - secret, err := s.getString("secret") - if secret == defaultValueMap["secret"] { - err := s.saveSetting("secret", secret) - if err != nil { - logger.Warning("save secret failed:", err) - } - } - return []byte(secret), err -} - -func (s *SettingService) SetBasePath(basePath string) error { - if !strings.HasPrefix(basePath, "/") { - basePath = "/" + basePath - } - if !strings.HasSuffix(basePath, "/") { - basePath += "/" - } - return s.setString("webBasePath", basePath) -} - -func (s *SettingService) GetBasePath() (string, error) { - basePath, err := s.getString("webBasePath") - if err != nil { - return "", err - } - if !strings.HasPrefix(basePath, "/") { - basePath = "/" + basePath - } - if !strings.HasSuffix(basePath, "/") { - basePath += "/" - } - return basePath, nil -} - -func (s *SettingService) GetTimeLocation() (*time.Location, error) { - l, err := s.getString("timeLocation") - if err != nil { - return nil, err - } - location, err := time.LoadLocation(l) - if err != nil { - defaultLocation := defaultValueMap["timeLocation"] - logger.Errorf("location <%v> not exist, using default location: %v", l, defaultLocation) - return time.LoadLocation(defaultLocation) - } - return location, nil -} - -func (s *SettingService) GetSubEnable() (bool, error) { - return s.getBool("subEnable") -} - -func (s *SettingService) GetSubListen() (string, error) { - return s.getString("subListen") -} - -func (s *SettingService) GetSubPort() (int, error) { - return s.getInt("subPort") -} - -func (s *SettingService) GetSubPath() (string, error) { - return s.getString("subPath") -} - -func (s *SettingService) GetSubJsonPath() (string, error) { - return s.getString("subJsonPath") -} - -func (s *SettingService) GetSubDomain() (string, error) { - return s.getString("subDomain") -} - -func (s *SettingService) GetSubCertFile() (string, error) { - return s.getString("subCertFile") -} - -func (s *SettingService) GetSubKeyFile() (string, error) { - return s.getString("subKeyFile") -} - -func (s *SettingService) GetSubUpdates() (string, error) { - return s.getString("subUpdates") -} - -func (s *SettingService) GetSubEncrypt() (bool, error) { - return s.getBool("subEncrypt") -} - -func (s *SettingService) GetSubShowInfo() (bool, error) { - return s.getBool("subShowInfo") -} - -func (s *SettingService) GetPageSize() (int, error) { - return s.getInt("pageSize") -} - -func (s *SettingService) GetSubURI() (string, error) { - return s.getString("subURI") -} - -func (s *SettingService) GetSubJsonURI() (string, error) { - return s.getString("subJsonURI") -} - -func (s *SettingService) GetSubJsonFragment() (string, error) { - return s.getString("subJsonFragment") -} - -func (s *SettingService) GetSubJsonNoises() (string, error) { - return s.getString("subJsonNoises") -} - -func (s *SettingService) GetSubJsonMux() (string, error) { - return s.getString("subJsonMux") -} - -func (s *SettingService) GetSubJsonRules() (string, error) { - return s.getString("subJsonRules") -} - -func (s *SettingService) GetDatepicker() (string, error) { - return s.getString("datepicker") -} - -func (s *SettingService) GetWarp() (string, error) { - return s.getString("warp") -} - -func (s *SettingService) SetWarp(data string) error { - return s.setString("warp", data) -} - -func (s *SettingService) GetIpLimitEnable() (bool, error) { - accessLogPath, err := xray.GetAccessLogPath() - if err != nil { - return false, err - } - return (accessLogPath != "none" && accessLogPath != ""), nil -} - -func (s *SettingService) UpdateAllSetting(allSetting *entity.AllSetting) error { - if err := allSetting.CheckValid(); err != nil { - return err - } - - v := reflect.ValueOf(allSetting).Elem() - t := reflect.TypeOf(allSetting).Elem() - fields := reflect_util.GetFields(t) - errs := make([]error, 0) - for _, field := range fields { - key := field.Tag.Get("json") - fieldV := v.FieldByName(field.Name) - value := fmt.Sprint(fieldV.Interface()) - err := s.saveSetting(key, value) - if err != nil { - errs = append(errs, err) - } - } - return common.Combine(errs...) -} - -func (s *SettingService) GetDefaultXrayConfig() (interface{}, error) { - var jsonData interface{} - err := json.Unmarshal([]byte(xrayTemplateConfig), &jsonData) - if err != nil { - return nil, err - } - return jsonData, nil -} - -func (s *SettingService) GetDefaultSettings(host string) (interface{}, error) { - type settingFunc func() (interface{}, error) - settings := map[string]settingFunc{ - "expireDiff": func() (interface{}, error) { return s.GetExpireDiff() }, - "trafficDiff": func() (interface{}, error) { return s.GetTrafficDiff() }, - "pageSize": func() (interface{}, error) { return s.GetPageSize() }, - "defaultCert": func() (interface{}, error) { return s.GetCertFile() }, - "defaultKey": func() (interface{}, error) { return s.GetKeyFile() }, - "tgBotEnable": func() (interface{}, error) { return s.GetTgbotEnabled() }, - "subEnable": func() (interface{}, error) { return s.GetSubEnable() }, - "subURI": func() (interface{}, error) { return s.GetSubURI() }, - "subJsonURI": func() (interface{}, error) { return s.GetSubJsonURI() }, - "remarkModel": func() (interface{}, error) { return s.GetRemarkModel() }, - "datepicker": func() (interface{}, error) { return s.GetDatepicker() }, - "ipLimitEnable": func() (interface{}, error) { return s.GetIpLimitEnable() }, - } - - result := make(map[string]interface{}) - - for key, fn := range settings { - value, err := fn() - if err != nil { - return "", err - } - result[key] = value - } - - if result["subEnable"].(bool) && (result["subURI"].(string) == "" || result["subJsonURI"].(string) == "") { - subURI := "" - subPort, _ := s.GetSubPort() - subPath, _ := s.GetSubPath() - subJsonPath, _ := s.GetSubJsonPath() - subDomain, _ := s.GetSubDomain() - subKeyFile, _ := s.GetSubKeyFile() - subCertFile, _ := s.GetSubCertFile() - subTLS := false - if subKeyFile != "" && subCertFile != "" { - subTLS = true - } - if subDomain == "" { - subDomain = strings.Split(host, ":")[0] - } - if subTLS { - subURI = "https://" - } else { - subURI = "http://" - } - if (subPort == 443 && subTLS) || (subPort == 80 && !subTLS) { - subURI += subDomain - } else { - subURI += fmt.Sprintf("%s:%d", subDomain, subPort) - } - if result["subURI"].(string) == "" { - result["subURI"] = subURI + subPath - } - if result["subJsonURI"].(string) == "" { - result["subJsonURI"] = subURI + subJsonPath - } - } - - return result, nil -} diff --git a/web/service/tgbot.go b/web/service/tgbot.go deleted file mode 100644 index 7c6780d8..00000000 --- a/web/service/tgbot.go +++ /dev/null @@ -1,1861 +0,0 @@ -package service - -import ( - "embed" - "errors" - "fmt" - "net" - "net/url" - "os" - "strconv" - "strings" - "time" - - "x-ui/config" - "x-ui/database" - "x-ui/database/model" - "x-ui/logger" - "x-ui/util/common" - "x-ui/web/global" - "x-ui/web/locale" - "x-ui/xray" - - "github.com/mymmrac/telego" - th "github.com/mymmrac/telego/telegohandler" - tu "github.com/mymmrac/telego/telegoutil" - "github.com/valyala/fasthttp" - "github.com/valyala/fasthttp/fasthttpproxy" -) - -var ( - bot *telego.Bot - botHandler *th.BotHandler - adminIds []int64 - isRunning bool - hostname string - hashStorage *global.HashStorage -) - -type LoginStatus byte - -const ( - LoginSuccess LoginStatus = 1 - LoginFail LoginStatus = 0 - EmptyTelegramUserID = int64(0) -) - -type Tgbot struct { - inboundService InboundService - settingService SettingService - serverService ServerService - xrayService XrayService - lastStatus *Status -} - -func (t *Tgbot) NewTgbot() *Tgbot { - return new(Tgbot) -} - -func (t *Tgbot) I18nBot(name string, params ...string) string { - return locale.I18n(locale.Bot, name, params...) -} - -func (t *Tgbot) GetHashStorage() *global.HashStorage { - return hashStorage -} - -func (t *Tgbot) Start(i18nFS embed.FS) error { - // Initialize localizer - err := locale.InitLocalizer(i18nFS, &t.settingService) - if err != nil { - return err - } - - // Initialize hash storage to store callback queries - hashStorage = global.NewHashStorage(20 * time.Minute) - - t.SetHostname() - - // Get Telegram bot token - tgBotToken, err := t.settingService.GetTgBotToken() - if err != nil || tgBotToken == "" { - logger.Warning("Failed to get Telegram bot token:", err) - return err - } - - // Get Telegram bot chat ID(s) - tgBotID, err := t.settingService.GetTgBotChatId() - if err != nil { - logger.Warning("Failed to get Telegram bot chat ID:", err) - return err - } - - // Parse admin IDs from comma-separated string - if tgBotID != "" { - for _, adminID := range strings.Split(tgBotID, ",") { - id, err := strconv.Atoi(adminID) - if err != nil { - logger.Warning("Failed to parse admin ID from Telegram bot chat ID:", err) - return err - } - adminIds = append(adminIds, int64(id)) - } - } - - // Get Telegram bot proxy URL - tgBotProxy, err := t.settingService.GetTgBotProxy() - if err != nil { - logger.Warning("Failed to get Telegram bot proxy URL:", err) - } - - // Get Telegram bot API server URL - tgBotAPIServer, err := t.settingService.GetTgBotAPIServer() - if err != nil { - logger.Warning("Failed to get Telegram bot API server URL:", err) - } - - // Create new Telegram bot instance - bot, err = t.NewBot(tgBotToken, tgBotProxy, tgBotAPIServer) - if err != nil { - logger.Error("Failed to initialize Telegram bot API:", err) - return err - } - - // Start receiving Telegram bot messages - if !isRunning { - logger.Info("Telegram bot receiver started") - go t.OnReceive() - isRunning = true - } - - return nil -} - -func (t *Tgbot) NewBot(token string, proxyUrl string, apiServerUrl string) (*telego.Bot, error) { - if proxyUrl == "" && apiServerUrl == "" { - return telego.NewBot(token) - } - - if proxyUrl != "" { - if !strings.HasPrefix(proxyUrl, "socks5://") { - logger.Warning("Invalid socks5 URL, using default") - return telego.NewBot(token) - } - - _, err := url.Parse(proxyUrl) - if err != nil { - logger.Warningf("Can't parse proxy URL, using default instance for tgbot: %v", err) - return telego.NewBot(token) - } - - return telego.NewBot(token, telego.WithFastHTTPClient(&fasthttp.Client{ - Dial: fasthttpproxy.FasthttpSocksDialer(proxyUrl), - })) - } - - if !strings.HasPrefix(apiServerUrl, "http") { - logger.Warning("Invalid http(s) URL, using default") - return telego.NewBot(token) - } - - _, err := url.Parse(apiServerUrl) - if err != nil { - logger.Warningf("Can't parse API server URL, using default instance for tgbot: %v", err) - return telego.NewBot(token) - } - - return telego.NewBot(token, telego.WithAPIServer(apiServerUrl)) -} - -func (t *Tgbot) IsRunning() bool { - return isRunning -} - -func (t *Tgbot) SetHostname() { - host, err := os.Hostname() - if err != nil { - logger.Error("get hostname error:", err) - hostname = "" - return - } - hostname = host -} - -func (t *Tgbot) Stop() { - botHandler.Stop() - bot.StopLongPolling() - logger.Info("Stop Telegram receiver ...") - isRunning = false - adminIds = nil -} - -func (t *Tgbot) encodeQuery(query string) string { - // NOTE: we only need to hash for more than 64 chars - if len(query) <= 64 { - return query - } - - return hashStorage.SaveHash(query) -} - -func (t *Tgbot) decodeQuery(query string) (string, error) { - if !hashStorage.IsMD5(query) { - return query, nil - } - - decoded, exists := hashStorage.GetValue(query) - if !exists { - return "", common.NewError("hash not found in storage!") - } - - return decoded, nil -} - -func (t *Tgbot) OnReceive() { - params := telego.GetUpdatesParams{ - Timeout: 10, - } - - updates, _ := bot.UpdatesViaLongPolling(¶ms) - - botHandler, _ = th.NewBotHandler(bot, updates) - - botHandler.HandleMessage(func(_ *telego.Bot, message telego.Message) { - t.SendMsgToTgbot(message.Chat.ID, t.I18nBot("tgbot.keyboardClosed"), tu.ReplyKeyboardRemove()) - }, th.TextEqual(t.I18nBot("tgbot.buttons.closeKeyboard"))) - - botHandler.HandleMessage(func(_ *telego.Bot, message telego.Message) { - t.answerCommand(&message, message.Chat.ID, checkAdmin(message.From.ID)) - }, th.AnyCommand()) - - botHandler.HandleCallbackQuery(func(_ *telego.Bot, query telego.CallbackQuery) { - t.answerCallback(&query, checkAdmin(query.From.ID)) - }, th.AnyCallbackQueryWithMessage()) - - botHandler.HandleMessage(func(_ *telego.Bot, message telego.Message) { - if message.UsersShared != nil { - if checkAdmin(message.From.ID) { - for _, sharedUser := range message.UsersShared.Users { - userID := sharedUser.UserID - needRestart, err := t.inboundService.SetClientTelegramUserID(message.UsersShared.RequestID, userID) - if needRestart { - t.xrayService.SetToNeedRestart() - } - output := "" - if err != nil { - output += t.I18nBot("tgbot.messages.selectUserFailed") - } else { - output += t.I18nBot("tgbot.messages.userSaved") - } - t.SendMsgToTgbot(message.Chat.ID, output, tu.ReplyKeyboardRemove()) - } - } else { - t.SendMsgToTgbot(message.Chat.ID, t.I18nBot("tgbot.noResult"), tu.ReplyKeyboardRemove()) - } - } - }, th.AnyMessage()) - - botHandler.Start() -} - -func (t *Tgbot) answerCommand(message *telego.Message, chatId int64, isAdmin bool) { - msg, onlyMessage := "", false - - command, _, commandArgs := tu.ParseCommand(message.Text) - - // Helper function to handle unknown commands. - handleUnknownCommand := func() { - msg += t.I18nBot("tgbot.commands.unknown") - } - - // Handle the command. - switch command { - case "help": - msg += t.I18nBot("tgbot.commands.help") - msg += t.I18nBot("tgbot.commands.pleaseChoose") - case "start": - msg += t.I18nBot("tgbot.commands.start", "Firstname=="+message.From.FirstName) - if isAdmin { - msg += t.I18nBot("tgbot.commands.welcome", "Hostname=="+hostname) - } - msg += "\n\n" + t.I18nBot("tgbot.commands.pleaseChoose") - case "status": - onlyMessage = true - msg += t.I18nBot("tgbot.commands.status") - case "id": - onlyMessage = true - msg += t.I18nBot("tgbot.commands.getID", "ID=="+strconv.FormatInt(message.From.ID, 10)) - case "usage": - onlyMessage = true - if len(commandArgs) > 0 { - if isAdmin { - t.searchClient(chatId, commandArgs[0]) - } else { - t.getClientUsage(chatId, int64(message.From.ID), commandArgs[0]) - } - } else { - msg += t.I18nBot("tgbot.commands.usage") - } - case "inbound": - onlyMessage = true - if isAdmin && len(commandArgs) > 0 { - t.searchInbound(chatId, commandArgs[0]) - } else { - handleUnknownCommand() - } - case "restart": - onlyMessage = true - if isAdmin { - if len(commandArgs) == 0 { - msg += t.I18nBot("tgbot.commands.restartUsage") - } else if strings.ToLower(commandArgs[0]) == "force" { - if t.xrayService.IsXrayRunning() { - err := t.xrayService.RestartXray(true) - if err != nil { - msg += t.I18nBot("tgbot.commands.restartFailed", "Error=="+err.Error()) - } else { - msg += t.I18nBot("tgbot.commands.restartSuccess") - } - } else { - msg += t.I18nBot("tgbot.commands.xrayNotRunning") - } - } else { - handleUnknownCommand() - msg += t.I18nBot("tgbot.commands.restartUsage") - } - } else { - handleUnknownCommand() - } - default: - handleUnknownCommand() - } - - if msg != "" { - t.sendResponse(chatId, msg, onlyMessage, isAdmin) - } -} - -// Helper function to send the message based on onlyMessage flag. -func (t *Tgbot) sendResponse(chatId int64, msg string, onlyMessage, isAdmin bool) { - if onlyMessage { - t.SendMsgToTgbot(chatId, msg) - } else { - t.SendAnswer(chatId, msg, isAdmin) - } -} - -func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool) { - chatId := callbackQuery.Message.GetChat().ID - - if isAdmin { - // get query from hash storage - decodedQuery, err := t.decodeQuery(callbackQuery.Data) - if err != nil { - t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.noQuery")) - return - } - dataArray := strings.Split(decodedQuery, " ") - - if len(dataArray) >= 2 && len(dataArray[1]) > 0 { - email := dataArray[1] - switch dataArray[0] { - case "client_get_usage": - t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.messages.email", "Email=="+email)) - t.searchClient(chatId, email) - case "client_refresh": - t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.clientRefreshSuccess", "Email=="+email)) - t.searchClient(chatId, email, callbackQuery.Message.GetMessageID()) - case "client_cancel": - t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.canceled", "Email=="+email)) - t.searchClient(chatId, email, callbackQuery.Message.GetMessageID()) - case "ips_refresh": - t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.IpRefreshSuccess", "Email=="+email)) - t.searchClientIps(chatId, email, callbackQuery.Message.GetMessageID()) - case "ips_cancel": - t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.canceled", "Email=="+email)) - t.searchClientIps(chatId, email, callbackQuery.Message.GetMessageID()) - case "tgid_refresh": - t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.TGIdRefreshSuccess", "Email=="+email)) - t.clientTelegramUserInfo(chatId, email, callbackQuery.Message.GetMessageID()) - case "tgid_cancel": - t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.canceled", "Email=="+email)) - t.clientTelegramUserInfo(chatId, email, callbackQuery.Message.GetMessageID()) - case "reset_traffic": - inlineKeyboard := tu.InlineKeyboard( - tu.InlineKeyboardRow( - tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.cancelReset")).WithCallbackData(t.encodeQuery("client_cancel "+email)), - ), - tu.InlineKeyboardRow( - tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.confirmResetTraffic")).WithCallbackData(t.encodeQuery("reset_traffic_c "+email)), - ), - ) - t.editMessageCallbackTgBot(chatId, callbackQuery.Message.GetMessageID(), inlineKeyboard) - case "reset_traffic_c": - err := t.inboundService.ResetClientTrafficByEmail(email) - if err == nil { - t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.resetTrafficSuccess", "Email=="+email)) - t.searchClient(chatId, email, callbackQuery.Message.GetMessageID()) - } else { - t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation")) - } - case "limit_traffic": - inlineKeyboard := tu.InlineKeyboard( - tu.InlineKeyboardRow( - tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.cancel")).WithCallbackData(t.encodeQuery("client_cancel "+email)), - ), - tu.InlineKeyboardRow( - tu.InlineKeyboardButton(t.I18nBot("tgbot.unlimited")).WithCallbackData(t.encodeQuery("limit_traffic_c "+email+" 0")), - tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.custom")).WithCallbackData(t.encodeQuery("limit_traffic_in "+email+" 0")), - ), - tu.InlineKeyboardRow( - tu.InlineKeyboardButton("1 GB").WithCallbackData(t.encodeQuery("limit_traffic_c "+email+" 1")), - tu.InlineKeyboardButton("5 GB").WithCallbackData(t.encodeQuery("limit_traffic_c "+email+" 5")), - tu.InlineKeyboardButton("10 GB").WithCallbackData(t.encodeQuery("limit_traffic_c "+email+" 10")), - ), - tu.InlineKeyboardRow( - tu.InlineKeyboardButton("20 GB").WithCallbackData(t.encodeQuery("limit_traffic_c "+email+" 20")), - tu.InlineKeyboardButton("30 GB").WithCallbackData(t.encodeQuery("limit_traffic_c "+email+" 30")), - tu.InlineKeyboardButton("40 GB").WithCallbackData(t.encodeQuery("limit_traffic_c "+email+" 40")), - ), - tu.InlineKeyboardRow( - tu.InlineKeyboardButton("50 GB").WithCallbackData(t.encodeQuery("limit_traffic_c "+email+" 50")), - tu.InlineKeyboardButton("60 GB").WithCallbackData(t.encodeQuery("limit_traffic_c "+email+" 60")), - tu.InlineKeyboardButton("80 GB").WithCallbackData(t.encodeQuery("limit_traffic_c "+email+" 80")), - ), - tu.InlineKeyboardRow( - tu.InlineKeyboardButton("100 GB").WithCallbackData(t.encodeQuery("limit_traffic_c "+email+" 100")), - tu.InlineKeyboardButton("150 GB").WithCallbackData(t.encodeQuery("limit_traffic_c "+email+" 150")), - tu.InlineKeyboardButton("200 GB").WithCallbackData(t.encodeQuery("limit_traffic_c "+email+" 200")), - ), - ) - t.editMessageCallbackTgBot(chatId, callbackQuery.Message.GetMessageID(), inlineKeyboard) - case "limit_traffic_c": - if len(dataArray) == 3 { - limitTraffic, err := strconv.Atoi(dataArray[2]) - if err == nil { - needRestart, err := t.inboundService.ResetClientTrafficLimitByEmail(email, limitTraffic) - if needRestart { - t.xrayService.SetToNeedRestart() - } - if err == nil { - t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.setTrafficLimitSuccess", "Email=="+email)) - t.searchClient(chatId, email, callbackQuery.Message.GetMessageID()) - return - } - } - } - t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation")) - t.searchClient(chatId, email, callbackQuery.Message.GetMessageID()) - case "limit_traffic_in": - if len(dataArray) >= 3 { - oldInputNumber, err := strconv.Atoi(dataArray[2]) - inputNumber := oldInputNumber - if err == nil { - if len(dataArray) == 4 { - num, err := strconv.Atoi(dataArray[3]) - if err == nil { - if num == -2 { - inputNumber = 0 - } else if num == -1 { - if inputNumber > 0 { - inputNumber = (inputNumber / 10) - } - } else { - inputNumber = (inputNumber * 10) + num - } - } - if inputNumber == oldInputNumber { - t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.successfulOperation")) - return - } - if inputNumber >= 999999 { - t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation")) - return - } - } - inlineKeyboard := tu.InlineKeyboard( - tu.InlineKeyboardRow( - tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.cancel")).WithCallbackData(t.encodeQuery("client_cancel "+email)), - ), - tu.InlineKeyboardRow( - tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.confirmNumberAdd", "Num=="+strconv.Itoa(inputNumber))).WithCallbackData(t.encodeQuery("limit_traffic_c "+email+" "+strconv.Itoa(inputNumber))), - ), - tu.InlineKeyboardRow( - tu.InlineKeyboardButton("1").WithCallbackData(t.encodeQuery("limit_traffic_in "+email+" "+strconv.Itoa(inputNumber)+" 1")), - tu.InlineKeyboardButton("2").WithCallbackData(t.encodeQuery("limit_traffic_in "+email+" "+strconv.Itoa(inputNumber)+" 2")), - tu.InlineKeyboardButton("3").WithCallbackData(t.encodeQuery("limit_traffic_in "+email+" "+strconv.Itoa(inputNumber)+" 3")), - ), - tu.InlineKeyboardRow( - tu.InlineKeyboardButton("4").WithCallbackData(t.encodeQuery("limit_traffic_in "+email+" "+strconv.Itoa(inputNumber)+" 4")), - tu.InlineKeyboardButton("5").WithCallbackData(t.encodeQuery("limit_traffic_in "+email+" "+strconv.Itoa(inputNumber)+" 5")), - tu.InlineKeyboardButton("6").WithCallbackData(t.encodeQuery("limit_traffic_in "+email+" "+strconv.Itoa(inputNumber)+" 6")), - ), - tu.InlineKeyboardRow( - tu.InlineKeyboardButton("7").WithCallbackData(t.encodeQuery("limit_traffic_in "+email+" "+strconv.Itoa(inputNumber)+" 7")), - tu.InlineKeyboardButton("8").WithCallbackData(t.encodeQuery("limit_traffic_in "+email+" "+strconv.Itoa(inputNumber)+" 8")), - tu.InlineKeyboardButton("9").WithCallbackData(t.encodeQuery("limit_traffic_in "+email+" "+strconv.Itoa(inputNumber)+" 9")), - ), - tu.InlineKeyboardRow( - tu.InlineKeyboardButton("🔄").WithCallbackData(t.encodeQuery("limit_traffic_in "+email+" "+strconv.Itoa(inputNumber)+" -2")), - tu.InlineKeyboardButton("0").WithCallbackData(t.encodeQuery("limit_traffic_in "+email+" "+strconv.Itoa(inputNumber)+" 0")), - tu.InlineKeyboardButton("⬅️").WithCallbackData(t.encodeQuery("limit_traffic_in "+email+" "+strconv.Itoa(inputNumber)+" -1")), - ), - ) - t.editMessageCallbackTgBot(chatId, callbackQuery.Message.GetMessageID(), inlineKeyboard) - return - } - } - t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation")) - t.searchClient(chatId, email, callbackQuery.Message.GetMessageID()) - case "reset_exp": - inlineKeyboard := tu.InlineKeyboard( - tu.InlineKeyboardRow( - tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.cancelReset")).WithCallbackData(t.encodeQuery("client_cancel "+email)), - ), - tu.InlineKeyboardRow( - tu.InlineKeyboardButton(t.I18nBot("tgbot.unlimited")).WithCallbackData(t.encodeQuery("reset_exp_c "+email+" 0")), - tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.custom")).WithCallbackData(t.encodeQuery("reset_exp_in "+email+" 0")), - ), - tu.InlineKeyboardRow( - tu.InlineKeyboardButton(t.I18nBot("tgbot.add")+" 7 "+t.I18nBot("tgbot.days")).WithCallbackData(t.encodeQuery("reset_exp_c "+email+" 7")), - tu.InlineKeyboardButton(t.I18nBot("tgbot.add")+" 10 "+t.I18nBot("tgbot.days")).WithCallbackData(t.encodeQuery("reset_exp_c "+email+" 10")), - ), - tu.InlineKeyboardRow( - tu.InlineKeyboardButton(t.I18nBot("tgbot.add")+" 14 "+t.I18nBot("tgbot.days")).WithCallbackData(t.encodeQuery("reset_exp_c "+email+" 14")), - tu.InlineKeyboardButton(t.I18nBot("tgbot.add")+" 20 "+t.I18nBot("tgbot.days")).WithCallbackData(t.encodeQuery("reset_exp_c "+email+" 20")), - ), - tu.InlineKeyboardRow( - tu.InlineKeyboardButton(t.I18nBot("tgbot.add")+" 1 "+t.I18nBot("tgbot.month")).WithCallbackData(t.encodeQuery("reset_exp_c "+email+" 30")), - tu.InlineKeyboardButton(t.I18nBot("tgbot.add")+" 3 "+t.I18nBot("tgbot.months")).WithCallbackData(t.encodeQuery("reset_exp_c "+email+" 90")), - ), - tu.InlineKeyboardRow( - tu.InlineKeyboardButton(t.I18nBot("tgbot.add")+" 6 "+t.I18nBot("tgbot.months")).WithCallbackData(t.encodeQuery("reset_exp_c "+email+" 180")), - tu.InlineKeyboardButton(t.I18nBot("tgbot.add")+" 12 "+t.I18nBot("tgbot.months")).WithCallbackData(t.encodeQuery("reset_exp_c "+email+" 365")), - ), - ) - t.editMessageCallbackTgBot(chatId, callbackQuery.Message.GetMessageID(), inlineKeyboard) - case "reset_exp_c": - if len(dataArray) == 3 { - days, err := strconv.Atoi(dataArray[2]) - if err == nil { - var date int64 = 0 - if days > 0 { - traffic, err := t.inboundService.GetClientTrafficByEmail(email) - if err != nil { - logger.Warning(err) - msg := t.I18nBot("tgbot.wentWrong") - t.SendMsgToTgbot(chatId, msg) - return - } - if traffic == nil { - msg := t.I18nBot("tgbot.noResult") - t.SendMsgToTgbot(chatId, msg) - return - } - - if traffic.ExpiryTime > 0 { - if traffic.ExpiryTime-time.Now().Unix()*1000 < 0 { - date = -int64(days * 24 * 60 * 60000) - } else { - date = traffic.ExpiryTime + int64(days*24*60*60000) - } - } else { - date = traffic.ExpiryTime - int64(days*24*60*60000) - } - - } - needRestart, err := t.inboundService.ResetClientExpiryTimeByEmail(email, date) - if needRestart { - t.xrayService.SetToNeedRestart() - } - if err == nil { - t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.expireResetSuccess", "Email=="+email)) - t.searchClient(chatId, email, callbackQuery.Message.GetMessageID()) - return - } - } - } - t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation")) - t.searchClient(chatId, email, callbackQuery.Message.GetMessageID()) - case "reset_exp_in": - if len(dataArray) >= 3 { - oldInputNumber, err := strconv.Atoi(dataArray[2]) - inputNumber := oldInputNumber - if err == nil { - if len(dataArray) == 4 { - num, err := strconv.Atoi(dataArray[3]) - if err == nil { - if num == -2 { - inputNumber = 0 - } else if num == -1 { - if inputNumber > 0 { - inputNumber = (inputNumber / 10) - } - } else { - inputNumber = (inputNumber * 10) + num - } - } - if inputNumber == oldInputNumber { - t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.successfulOperation")) - return - } - if inputNumber >= 999999 { - t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation")) - return - } - } - inlineKeyboard := tu.InlineKeyboard( - tu.InlineKeyboardRow( - tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.cancel")).WithCallbackData(t.encodeQuery("client_cancel "+email)), - ), - tu.InlineKeyboardRow( - tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.confirmNumber", "Num=="+strconv.Itoa(inputNumber))).WithCallbackData(t.encodeQuery("reset_exp_c "+email+" "+strconv.Itoa(inputNumber))), - ), - tu.InlineKeyboardRow( - tu.InlineKeyboardButton("1").WithCallbackData(t.encodeQuery("reset_exp_in "+email+" "+strconv.Itoa(inputNumber)+" 1")), - tu.InlineKeyboardButton("2").WithCallbackData(t.encodeQuery("reset_exp_in "+email+" "+strconv.Itoa(inputNumber)+" 2")), - tu.InlineKeyboardButton("3").WithCallbackData(t.encodeQuery("reset_exp_in "+email+" "+strconv.Itoa(inputNumber)+" 3")), - ), - tu.InlineKeyboardRow( - tu.InlineKeyboardButton("4").WithCallbackData(t.encodeQuery("reset_exp_in "+email+" "+strconv.Itoa(inputNumber)+" 4")), - tu.InlineKeyboardButton("5").WithCallbackData(t.encodeQuery("reset_exp_in "+email+" "+strconv.Itoa(inputNumber)+" 5")), - tu.InlineKeyboardButton("6").WithCallbackData(t.encodeQuery("reset_exp_in "+email+" "+strconv.Itoa(inputNumber)+" 6")), - ), - tu.InlineKeyboardRow( - tu.InlineKeyboardButton("7").WithCallbackData(t.encodeQuery("reset_exp_in "+email+" "+strconv.Itoa(inputNumber)+" 7")), - tu.InlineKeyboardButton("8").WithCallbackData(t.encodeQuery("reset_exp_in "+email+" "+strconv.Itoa(inputNumber)+" 8")), - tu.InlineKeyboardButton("9").WithCallbackData(t.encodeQuery("reset_exp_in "+email+" "+strconv.Itoa(inputNumber)+" 9")), - ), - tu.InlineKeyboardRow( - tu.InlineKeyboardButton("🔄").WithCallbackData(t.encodeQuery("reset_exp_in "+email+" "+strconv.Itoa(inputNumber)+" -2")), - tu.InlineKeyboardButton("0").WithCallbackData(t.encodeQuery("reset_exp_in "+email+" "+strconv.Itoa(inputNumber)+" 0")), - tu.InlineKeyboardButton("⬅️").WithCallbackData(t.encodeQuery("reset_exp_in "+email+" "+strconv.Itoa(inputNumber)+" -1")), - ), - ) - t.editMessageCallbackTgBot(chatId, callbackQuery.Message.GetMessageID(), inlineKeyboard) - return - } - } - t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation")) - t.searchClient(chatId, email, callbackQuery.Message.GetMessageID()) - case "ip_limit": - inlineKeyboard := tu.InlineKeyboard( - tu.InlineKeyboardRow( - tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.cancelIpLimit")).WithCallbackData(t.encodeQuery("client_cancel "+email)), - ), - tu.InlineKeyboardRow( - tu.InlineKeyboardButton(t.I18nBot("tgbot.unlimited")).WithCallbackData(t.encodeQuery("ip_limit_c "+email+" 0")), - tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.custom")).WithCallbackData(t.encodeQuery("ip_limit_in "+email+" 0")), - ), - tu.InlineKeyboardRow( - tu.InlineKeyboardButton("1").WithCallbackData(t.encodeQuery("ip_limit_c "+email+" 1")), - tu.InlineKeyboardButton("2").WithCallbackData(t.encodeQuery("ip_limit_c "+email+" 2")), - ), - tu.InlineKeyboardRow( - tu.InlineKeyboardButton("3").WithCallbackData(t.encodeQuery("ip_limit_c "+email+" 3")), - tu.InlineKeyboardButton("4").WithCallbackData(t.encodeQuery("ip_limit_c "+email+" 4")), - ), - tu.InlineKeyboardRow( - tu.InlineKeyboardButton("5").WithCallbackData(t.encodeQuery("ip_limit_c "+email+" 5")), - tu.InlineKeyboardButton("6").WithCallbackData(t.encodeQuery("ip_limit_c "+email+" 6")), - tu.InlineKeyboardButton("7").WithCallbackData(t.encodeQuery("ip_limit_c "+email+" 7")), - ), - tu.InlineKeyboardRow( - tu.InlineKeyboardButton("8").WithCallbackData(t.encodeQuery("ip_limit_c "+email+" 8")), - tu.InlineKeyboardButton("9").WithCallbackData(t.encodeQuery("ip_limit_c "+email+" 9")), - tu.InlineKeyboardButton("10").WithCallbackData(t.encodeQuery("ip_limit_c "+email+" 10")), - ), - ) - t.editMessageCallbackTgBot(chatId, callbackQuery.Message.GetMessageID(), inlineKeyboard) - case "ip_limit_c": - if len(dataArray) == 3 { - count, err := strconv.Atoi(dataArray[2]) - if err == nil { - needRestart, err := t.inboundService.ResetClientIpLimitByEmail(email, count) - if needRestart { - t.xrayService.SetToNeedRestart() - } - if err == nil { - t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.resetIpSuccess", "Email=="+email, "Count=="+strconv.Itoa(count))) - t.searchClient(chatId, email, callbackQuery.Message.GetMessageID()) - return - } - } - } - t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation")) - t.searchClient(chatId, email, callbackQuery.Message.GetMessageID()) - case "ip_limit_in": - if len(dataArray) >= 3 { - oldInputNumber, err := strconv.Atoi(dataArray[2]) - inputNumber := oldInputNumber - if err == nil { - if len(dataArray) == 4 { - num, err := strconv.Atoi(dataArray[3]) - if err == nil { - if num == -2 { - inputNumber = 0 - } else if num == -1 { - if inputNumber > 0 { - inputNumber = (inputNumber / 10) - } - } else { - inputNumber = (inputNumber * 10) + num - } - } - if inputNumber == oldInputNumber { - t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.successfulOperation")) - return - } - if inputNumber >= 999999 { - t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation")) - return - } - } - inlineKeyboard := tu.InlineKeyboard( - tu.InlineKeyboardRow( - tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.cancel")).WithCallbackData(t.encodeQuery("client_cancel "+email)), - ), - tu.InlineKeyboardRow( - tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.confirmNumber", "Num=="+strconv.Itoa(inputNumber))).WithCallbackData(t.encodeQuery("ip_limit_c "+email+" "+strconv.Itoa(inputNumber))), - ), - tu.InlineKeyboardRow( - tu.InlineKeyboardButton("1").WithCallbackData(t.encodeQuery("ip_limit_in "+email+" "+strconv.Itoa(inputNumber)+" 1")), - tu.InlineKeyboardButton("2").WithCallbackData(t.encodeQuery("ip_limit_in "+email+" "+strconv.Itoa(inputNumber)+" 2")), - tu.InlineKeyboardButton("3").WithCallbackData(t.encodeQuery("ip_limit_in "+email+" "+strconv.Itoa(inputNumber)+" 3")), - ), - tu.InlineKeyboardRow( - tu.InlineKeyboardButton("4").WithCallbackData(t.encodeQuery("ip_limit_in "+email+" "+strconv.Itoa(inputNumber)+" 4")), - tu.InlineKeyboardButton("5").WithCallbackData(t.encodeQuery("ip_limit_in "+email+" "+strconv.Itoa(inputNumber)+" 5")), - tu.InlineKeyboardButton("6").WithCallbackData(t.encodeQuery("ip_limit_in "+email+" "+strconv.Itoa(inputNumber)+" 6")), - ), - tu.InlineKeyboardRow( - tu.InlineKeyboardButton("7").WithCallbackData(t.encodeQuery("ip_limit_in "+email+" "+strconv.Itoa(inputNumber)+" 7")), - tu.InlineKeyboardButton("8").WithCallbackData(t.encodeQuery("ip_limit_in "+email+" "+strconv.Itoa(inputNumber)+" 8")), - tu.InlineKeyboardButton("9").WithCallbackData(t.encodeQuery("ip_limit_in "+email+" "+strconv.Itoa(inputNumber)+" 9")), - ), - tu.InlineKeyboardRow( - tu.InlineKeyboardButton("🔄").WithCallbackData(t.encodeQuery("ip_limit_in "+email+" "+strconv.Itoa(inputNumber)+" -2")), - tu.InlineKeyboardButton("0").WithCallbackData(t.encodeQuery("ip_limit_in "+email+" "+strconv.Itoa(inputNumber)+" 0")), - tu.InlineKeyboardButton("⬅️").WithCallbackData(t.encodeQuery("ip_limit_in "+email+" "+strconv.Itoa(inputNumber)+" -1")), - ), - ) - t.editMessageCallbackTgBot(chatId, callbackQuery.Message.GetMessageID(), inlineKeyboard) - return - } - } - t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation")) - t.searchClient(chatId, email, callbackQuery.Message.GetMessageID()) - case "clear_ips": - inlineKeyboard := tu.InlineKeyboard( - tu.InlineKeyboardRow( - tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.cancel")).WithCallbackData(t.encodeQuery("ips_cancel "+email)), - ), - tu.InlineKeyboardRow( - tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.confirmClearIps")).WithCallbackData(t.encodeQuery("clear_ips_c "+email)), - ), - ) - t.editMessageCallbackTgBot(chatId, callbackQuery.Message.GetMessageID(), inlineKeyboard) - case "clear_ips_c": - err := t.inboundService.ClearClientIps(email) - if err == nil { - t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.clearIpSuccess", "Email=="+email)) - t.searchClientIps(chatId, email, callbackQuery.Message.GetMessageID()) - } else { - t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation")) - } - case "ip_log": - t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.getIpLog", "Email=="+email)) - t.searchClientIps(chatId, email) - case "tg_user": - t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.getUserInfo", "Email=="+email)) - t.clientTelegramUserInfo(chatId, email) - case "tgid_remove": - inlineKeyboard := tu.InlineKeyboard( - tu.InlineKeyboardRow( - tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.cancel")).WithCallbackData(t.encodeQuery("tgid_cancel "+email)), - ), - tu.InlineKeyboardRow( - tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.confirmRemoveTGUser")).WithCallbackData(t.encodeQuery("tgid_remove_c "+email)), - ), - ) - t.editMessageCallbackTgBot(chatId, callbackQuery.Message.GetMessageID(), inlineKeyboard) - case "tgid_remove_c": - traffic, err := t.inboundService.GetClientTrafficByEmail(email) - if err != nil || traffic == nil { - t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation")) - return - } - needRestart, err := t.inboundService.SetClientTelegramUserID(traffic.Id, EmptyTelegramUserID) - if needRestart { - t.xrayService.SetToNeedRestart() - } - if err == nil { - t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.removedTGUserSuccess", "Email=="+email)) - t.clientTelegramUserInfo(chatId, email, callbackQuery.Message.GetMessageID()) - } else { - t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation")) - } - case "toggle_enable": - inlineKeyboard := tu.InlineKeyboard( - tu.InlineKeyboardRow( - tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.cancel")).WithCallbackData(t.encodeQuery("client_cancel "+email)), - ), - tu.InlineKeyboardRow( - tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.confirmToggle")).WithCallbackData(t.encodeQuery("toggle_enable_c "+email)), - ), - ) - t.editMessageCallbackTgBot(chatId, callbackQuery.Message.GetMessageID(), inlineKeyboard) - case "toggle_enable_c": - enabled, needRestart, err := t.inboundService.ToggleClientEnableByEmail(email) - if needRestart { - t.xrayService.SetToNeedRestart() - } - if err == nil { - if enabled { - t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.enableSuccess", "Email=="+email)) - } else { - t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.disableSuccess", "Email=="+email)) - } - t.searchClient(chatId, email, callbackQuery.Message.GetMessageID()) - } else { - t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation")) - } - case "get_clients": - inboundId := dataArray[1] - inboundIdInt, err := strconv.Atoi(inboundId) - if err != nil { - t.sendCallbackAnswerTgBot(callbackQuery.ID, err.Error()) - return - } - inbound, err := t.inboundService.GetInbound(inboundIdInt) - if err != nil { - t.sendCallbackAnswerTgBot(callbackQuery.ID, err.Error()) - return - } - clients, err := t.getInboundClients(inboundIdInt) - if err != nil { - t.sendCallbackAnswerTgBot(callbackQuery.ID, err.Error()) - return - } - t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.chooseClient", "Inbound=="+inbound.Remark), clients) - - } - return - } else { - switch callbackQuery.Data { - case "get_inbounds": - inbounds, err := t.getInbounds() - if err != nil { - t.sendCallbackAnswerTgBot(callbackQuery.ID, err.Error()) - return - - } - t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.buttons.allClients")) - t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.chooseInbound"), inbounds) - } - - } - } - - switch callbackQuery.Data { - case "get_usage": - t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.buttons.serverUsage")) - t.getServerUsage(chatId) - case "usage_refresh": - t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.successfulOperation")) - t.getServerUsage(chatId, callbackQuery.Message.GetMessageID()) - case "inbounds": - t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.buttons.getInbounds")) - t.SendMsgToTgbot(chatId, t.getInboundUsages()) - case "deplete_soon": - t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.buttons.depleteSoon")) - t.getExhausted(chatId) - case "get_backup": - t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.buttons.dbBackup")) - t.sendBackup(chatId) - case "get_banlogs": - t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.buttons.getBanLogs")) - t.sendBanLogs(chatId, true) - case "client_traffic": - tgUserID := callbackQuery.From.ID - t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.buttons.clientUsage")) - t.getClientUsage(chatId, tgUserID) - case "client_commands": - t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.buttons.commands")) - t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.commands.helpClientCommands")) - case "onlines": - t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.buttons.onlines")) - t.onlineClients(chatId) - case "onlines_refresh": - t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.successfulOperation")) - t.onlineClients(chatId, callbackQuery.Message.GetMessageID()) - case "commands": - t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.buttons.commands")) - t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.commands.helpAdminCommands")) - } -} - -func checkAdmin(tgId int64) bool { - for _, adminId := range adminIds { - if adminId == tgId { - return true - } - } - return false -} - -func (t *Tgbot) SendAnswer(chatId int64, msg string, isAdmin bool) { - numericKeyboard := tu.InlineKeyboard( - tu.InlineKeyboardRow( - tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.serverUsage")).WithCallbackData(t.encodeQuery("get_usage")), - ), - tu.InlineKeyboardRow( - tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.dbBackup")).WithCallbackData(t.encodeQuery("get_backup")), - tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.getBanLogs")).WithCallbackData(t.encodeQuery("get_banlogs")), - ), - tu.InlineKeyboardRow( - tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.getInbounds")).WithCallbackData(t.encodeQuery("inbounds")), - tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.depleteSoon")).WithCallbackData(t.encodeQuery("deplete_soon")), - ), - tu.InlineKeyboardRow( - tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.commands")).WithCallbackData(t.encodeQuery("commands")), - tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.onlines")).WithCallbackData(t.encodeQuery("onlines")), - tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.allClients")).WithCallbackData(t.encodeQuery("get_inbounds")), - ), - // TODOOOOOOOOOOOOOO: Add restart button here. - ) - numericKeyboardClient := tu.InlineKeyboard( - tu.InlineKeyboardRow( - tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.clientUsage")).WithCallbackData(t.encodeQuery("client_traffic")), - tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.commands")).WithCallbackData(t.encodeQuery("client_commands")), - ), - ) - - var ReplyMarkup telego.ReplyMarkup - if isAdmin { - ReplyMarkup = numericKeyboard - } else { - ReplyMarkup = numericKeyboardClient - } - t.SendMsgToTgbot(chatId, msg, ReplyMarkup) -} - -func (t *Tgbot) SendMsgToTgbot(chatId int64, msg string, replyMarkup ...telego.ReplyMarkup) { - if !isRunning { - return - } - - if msg == "" { - logger.Info("[tgbot] message is empty!") - return - } - - var allMessages []string - limit := 2000 - - // paging message if it is big - if len(msg) > limit { - messages := strings.Split(msg, "\r\n\r\n") - lastIndex := -1 - - for _, message := range messages { - if (len(allMessages) == 0) || (len(allMessages[lastIndex])+len(message) > limit) { - allMessages = append(allMessages, message) - lastIndex++ - } else { - allMessages[lastIndex] += "\r\n\r\n" + message - } - } - if strings.TrimSpace(allMessages[len(allMessages)-1]) == "" { - allMessages = allMessages[:len(allMessages)-1] - } - } else { - allMessages = append(allMessages, msg) - } - for n, message := range allMessages { - params := telego.SendMessageParams{ - ChatID: tu.ID(chatId), - Text: message, - ParseMode: "HTML", - } - // only add replyMarkup to last message - if len(replyMarkup) > 0 && n == (len(allMessages)-1) { - params.ReplyMarkup = replyMarkup[0] - } - _, err := bot.SendMessage(¶ms) - if err != nil { - logger.Warning("Error sending telegram message :", err) - } - time.Sleep(500 * time.Millisecond) - } -} - -func (t *Tgbot) SendMsgToTgbotAdmins(msg string, replyMarkup ...telego.ReplyMarkup) { - if len(replyMarkup) > 0 { - for _, adminId := range adminIds { - t.SendMsgToTgbot(adminId, msg, replyMarkup[0]) - } - } else { - for _, adminId := range adminIds { - t.SendMsgToTgbot(adminId, msg) - } - } -} - -func (t *Tgbot) SendReport() { - runTime, err := t.settingService.GetTgbotRuntime() - if err == nil && len(runTime) > 0 { - msg := "" - msg += t.I18nBot("tgbot.messages.report", "RunTime=="+runTime) - msg += t.I18nBot("tgbot.messages.datetime", "DateTime=="+time.Now().Format("2006-01-02 15:04:05")) - t.SendMsgToTgbotAdmins(msg) - } - - info := t.sendServerUsage() - t.SendMsgToTgbotAdmins(info) - - t.sendExhaustedToAdmins() - t.notifyExhausted() - - backupEnable, err := t.settingService.GetTgBotBackup() - if err == nil && backupEnable { - t.SendBackupToAdmins() - } -} - -func (t *Tgbot) SendBackupToAdmins() { - if !t.IsRunning() { - return - } - for _, adminId := range adminIds { - t.sendBackup(int64(adminId)) - } -} - -func (t *Tgbot) sendExhaustedToAdmins() { - if !t.IsRunning() { - return - } - for _, adminId := range adminIds { - t.getExhausted(int64(adminId)) - } -} - -func (t *Tgbot) getServerUsage(chatId int64, messageID ...int) string { - info := t.prepareServerUsageInfo() - - keyboard := tu.InlineKeyboard(tu.InlineKeyboardRow( - tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.refresh")).WithCallbackData(t.encodeQuery("usage_refresh")))) - - if len(messageID) > 0 { - t.editMessageTgBot(chatId, messageID[0], info, keyboard) - } else { - t.SendMsgToTgbot(chatId, info, keyboard) - } - - return info -} - -// Send server usage without an inline keyboard -func (t *Tgbot) sendServerUsage() string { - info := t.prepareServerUsageInfo() - return info -} - -func (t *Tgbot) prepareServerUsageInfo() string { - info, ipv4, ipv6 := "", "", "" - - // get latest status of server - t.lastStatus = t.serverService.GetStatus(t.lastStatus) - onlines := p.GetOnlineClients() - - info += t.I18nBot("tgbot.messages.hostname", "Hostname=="+hostname) - info += t.I18nBot("tgbot.messages.version", "Version=="+config.GetVersion()) - info += t.I18nBot("tgbot.messages.xrayVersion", "XrayVersion=="+fmt.Sprint(t.lastStatus.Xray.Version)) - - // get ip address - netInterfaces, err := net.Interfaces() - if err != nil { - logger.Error("net.Interfaces failed, err: ", err.Error()) - info += t.I18nBot("tgbot.messages.ip", "IP=="+t.I18nBot("tgbot.unknown")) - info += "\r\n" - } else { - for i := 0; i < len(netInterfaces); i++ { - if (netInterfaces[i].Flags & net.FlagUp) != 0 { - addrs, _ := netInterfaces[i].Addrs() - - for _, address := range addrs { - if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() { - if ipnet.IP.To4() != nil { - ipv4 += ipnet.IP.String() + " " - } else if ipnet.IP.To16() != nil && !ipnet.IP.IsLinkLocalUnicast() { - ipv6 += ipnet.IP.String() + " " - } - } - } - } - } - - info += t.I18nBot("tgbot.messages.ipv4", "IPv4=="+ipv4) - info += t.I18nBot("tgbot.messages.ipv6", "IPv6=="+ipv6) - } - - info += t.I18nBot("tgbot.messages.serverUpTime", "UpTime=="+strconv.FormatUint(t.lastStatus.Uptime/86400, 10), "Unit=="+t.I18nBot("tgbot.days")) - info += t.I18nBot("tgbot.messages.serverLoad", "Load1=="+strconv.FormatFloat(t.lastStatus.Loads[0], 'f', 2, 64), "Load2=="+strconv.FormatFloat(t.lastStatus.Loads[1], 'f', 2, 64), "Load3=="+strconv.FormatFloat(t.lastStatus.Loads[2], 'f', 2, 64)) - info += t.I18nBot("tgbot.messages.serverMemory", "Current=="+common.FormatTraffic(int64(t.lastStatus.Mem.Current)), "Total=="+common.FormatTraffic(int64(t.lastStatus.Mem.Total))) - info += t.I18nBot("tgbot.messages.onlinesCount", "Count=="+fmt.Sprint(len(onlines))) - info += t.I18nBot("tgbot.messages.tcpCount", "Count=="+strconv.Itoa(t.lastStatus.TcpCount)) - info += t.I18nBot("tgbot.messages.udpCount", "Count=="+strconv.Itoa(t.lastStatus.UdpCount)) - info += t.I18nBot("tgbot.messages.traffic", "Total=="+common.FormatTraffic(int64(t.lastStatus.NetTraffic.Sent+t.lastStatus.NetTraffic.Recv)), "Upload=="+common.FormatTraffic(int64(t.lastStatus.NetTraffic.Sent)), "Download=="+common.FormatTraffic(int64(t.lastStatus.NetTraffic.Recv))) - info += t.I18nBot("tgbot.messages.xrayStatus", "State=="+fmt.Sprint(t.lastStatus.Xray.State)) - return info -} - -func (t *Tgbot) UserLoginNotify(username string, password string, ip string, time string, status LoginStatus) { - if !t.IsRunning() { - return - } - - if username == "" || ip == "" || time == "" { - logger.Warning("UserLoginNotify failed, invalid info!") - return - } - - loginNotifyEnabled, err := t.settingService.GetTgBotLoginNotify() - if err != nil || !loginNotifyEnabled { - return - } - - msg := "" - if status == LoginSuccess { - msg += t.I18nBot("tgbot.messages.loginSuccess") - msg += t.I18nBot("tgbot.messages.hostname", "Hostname=="+hostname) - } else if status == LoginFail { - msg += t.I18nBot("tgbot.messages.loginFailed") - msg += t.I18nBot("tgbot.messages.hostname", "Hostname=="+hostname) - msg += t.I18nBot("tgbot.messages.password", "Password=="+password) - } - msg += t.I18nBot("tgbot.messages.username", "Username=="+username) - msg += t.I18nBot("tgbot.messages.ip", "IP=="+ip) - msg += t.I18nBot("tgbot.messages.time", "Time=="+time) - t.SendMsgToTgbotAdmins(msg) -} - -func (t *Tgbot) getInboundUsages() string { - info := "" - // get traffic - inbounds, err := t.inboundService.GetAllInbounds() - if err != nil { - logger.Warning("GetAllInbounds run failed:", err) - info += t.I18nBot("tgbot.answers.getInboundsFailed") - } else { - // NOTE:If there no any sessions here,need to notify here - // TODO:Sub-node push, automatic conversion format - for _, inbound := range inbounds { - info += t.I18nBot("tgbot.messages.inbound", "Remark=="+inbound.Remark) - info += t.I18nBot("tgbot.messages.port", "Port=="+strconv.Itoa(inbound.Port)) - info += t.I18nBot("tgbot.messages.traffic", "Total=="+common.FormatTraffic((inbound.Up+inbound.Down)), "Upload=="+common.FormatTraffic(inbound.Up), "Download=="+common.FormatTraffic(inbound.Down)) - - if inbound.ExpiryTime == 0 { - info += t.I18nBot("tgbot.messages.expire", "Time=="+t.I18nBot("tgbot.unlimited")) - } else { - info += t.I18nBot("tgbot.messages.expire", "Time=="+time.Unix((inbound.ExpiryTime/1000), 0).Format("2006-01-02 15:04:05")) - } - info += "\r\n" - } - } - return info -} - -func (t *Tgbot) getInbounds() (*telego.InlineKeyboardMarkup, error) { - inbounds, err := t.inboundService.GetAllInbounds() - var buttons []telego.InlineKeyboardButton - - if err != nil { - logger.Warning("GetAllInbounds run failed:", err) - return nil, errors.New(t.I18nBot("tgbot.answers.getInboundsFailed")) - } else { - if len(inbounds) > 0 { - for _, inbound := range inbounds { - status := "❌" - if inbound.Enable { - status = "✅" - } - buttons = append(buttons, tu.InlineKeyboardButton(fmt.Sprintf("%v - %v", inbound.Remark, status)).WithCallbackData(t.encodeQuery("get_clients "+strconv.Itoa(inbound.Id)))) - } - } else { - logger.Warning("GetAllInbounds run failed:", err) - return nil, errors.New(t.I18nBot("tgbot.answers.getInboundsFailed")) - } - - } - cols := 0 - if len(buttons) < 6 { - cols = 3 - } else { - cols = 2 - } - keyboard := tu.InlineKeyboardGrid(tu.InlineKeyboardCols(cols, buttons...)) - return keyboard, nil -} - -func (t *Tgbot) getInboundClients(id int) (*telego.InlineKeyboardMarkup, error) { - inbound, err := t.inboundService.GetInbound(id) - if err != nil { - logger.Warning("getIboundClients run failed:", err) - return nil, errors.New(t.I18nBot("tgbot.answers.getInboundsFailed")) - } - clients, err := t.inboundService.GetClients(inbound) - var buttons []telego.InlineKeyboardButton - - if err != nil { - logger.Warning("GetInboundClients run failed:", err) - return nil, errors.New(t.I18nBot("tgbot.answers.getInboundsFailed")) - } else { - if len(clients) > 0 { - for _, client := range clients { - buttons = append(buttons, tu.InlineKeyboardButton(client.Email).WithCallbackData(t.encodeQuery("client_get_usage "+client.Email))) - } - - } else { - return nil, errors.New(t.I18nBot("tgbot.answers.getClientsFailed")) - } - - } - cols := 0 - if len(buttons) < 6 { - cols = 3 - } else { - cols = 2 - } - keyboard := tu.InlineKeyboardGrid(tu.InlineKeyboardCols(cols, buttons...)) - - return keyboard, nil -} - -func (t *Tgbot) clientInfoMsg( - traffic *xray.ClientTraffic, - printEnabled bool, - printOnline bool, - printActive bool, - printDate bool, - printTraffic bool, - printRefreshed bool, -) string { - now := time.Now().Unix() - expiryTime := "" - flag := false - diff := traffic.ExpiryTime/1000 - now - if traffic.ExpiryTime == 0 { - expiryTime = t.I18nBot("tgbot.unlimited") - } else if diff > 172800 || !traffic.Enable { - expiryTime = time.Unix((traffic.ExpiryTime / 1000), 0).Format("2006-01-02 15:04:05") - } else if traffic.ExpiryTime < 0 { - expiryTime = fmt.Sprintf("%d %s", traffic.ExpiryTime/-86400000, t.I18nBot("tgbot.days")) - flag = true - } else { - expiryTime = fmt.Sprintf("%d %s", diff/3600, t.I18nBot("tgbot.hours")) - flag = true - } - - total := "" - if traffic.Total == 0 { - total = t.I18nBot("tgbot.unlimited") - } else { - total = common.FormatTraffic((traffic.Total)) - } - - enabled := "" - isEnabled, err := t.inboundService.checkIsEnabledByEmail(traffic.Email) - if err != nil { - logger.Warning(err) - enabled = t.I18nBot("tgbot.wentWrong") - } else if isEnabled { - enabled = t.I18nBot("tgbot.messages.yes") - } else { - enabled = t.I18nBot("tgbot.messages.no") - } - - active := "" - if traffic.Enable { - active = t.I18nBot("tgbot.messages.yes") - } else { - active = t.I18nBot("tgbot.messages.no") - } - - status := t.I18nBot("tgbot.offline") - if p.IsRunning() { - for _, online := range p.GetOnlineClients() { - if online == traffic.Email { - status = t.I18nBot("tgbot.online") - break - } - } - } - - output := "" - output += t.I18nBot("tgbot.messages.email", "Email=="+traffic.Email) - if printEnabled { - output += t.I18nBot("tgbot.messages.enabled", "Enable=="+enabled) - } - if printOnline { - output += t.I18nBot("tgbot.messages.online", "Status=="+status) - } - if printActive { - output += t.I18nBot("tgbot.messages.active", "Enable=="+active) - } - if printDate { - if flag { - output += t.I18nBot("tgbot.messages.expireIn", "Time=="+expiryTime) - } else { - output += t.I18nBot("tgbot.messages.expire", "Time=="+expiryTime) - } - } - if printTraffic { - output += t.I18nBot("tgbot.messages.upload", "Upload=="+common.FormatTraffic(traffic.Up)) - output += t.I18nBot("tgbot.messages.download", "Download=="+common.FormatTraffic(traffic.Down)) - output += t.I18nBot("tgbot.messages.total", "UpDown=="+common.FormatTraffic((traffic.Up+traffic.Down)), "Total=="+total) - } - if printRefreshed { - output += t.I18nBot("tgbot.messages.refreshedOn", "Time=="+time.Now().Format("2006-01-02 15:04:05")) - } - - return output -} - -func (t *Tgbot) getClientUsage(chatId int64, tgUserID int64, email ...string) { - traffics, err := t.inboundService.GetClientTrafficTgBot(tgUserID) - if err != nil { - logger.Warning(err) - msg := t.I18nBot("tgbot.wentWrong") - t.SendMsgToTgbot(chatId, msg) - return - } - - if len(traffics) == 0 { - t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.askToAddUserId", "TgUserID=="+strconv.FormatInt(tgUserID, 10))) - return - } - - output := "" - - if len(traffics) > 0 { - if len(email) > 0 { - for _, traffic := range traffics { - if traffic.Email == email[0] { - output := t.clientInfoMsg(traffic, true, true, true, true, true, true) - t.SendMsgToTgbot(chatId, output) - return - } - } - msg := t.I18nBot("tgbot.noResult") - t.SendMsgToTgbot(chatId, msg) - return - } else { - for _, traffic := range traffics { - output += t.clientInfoMsg(traffic, true, true, true, true, true, false) - output += "\r\n" - } - } - } - - output += t.I18nBot("tgbot.messages.refreshedOn", "Time=="+time.Now().Format("2006-01-02 15:04:05")) - t.SendMsgToTgbot(chatId, output) - output = t.I18nBot("tgbot.commands.pleaseChoose") - t.SendAnswer(chatId, output, false) -} - -func (t *Tgbot) searchClientIps(chatId int64, email string, messageID ...int) { - ips, err := t.inboundService.GetInboundClientIps(email) - if err != nil || len(ips) == 0 { - ips = t.I18nBot("tgbot.noIpRecord") - } - - output := "" - output += t.I18nBot("tgbot.messages.email", "Email=="+email) - output += t.I18nBot("tgbot.messages.ips", "IPs=="+ips) - output += t.I18nBot("tgbot.messages.refreshedOn", "Time=="+time.Now().Format("2006-01-02 15:04:05")) - - inlineKeyboard := tu.InlineKeyboard( - tu.InlineKeyboardRow( - tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.refresh")).WithCallbackData(t.encodeQuery("ips_refresh "+email)), - ), - tu.InlineKeyboardRow( - tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.clearIPs")).WithCallbackData(t.encodeQuery("clear_ips "+email)), - ), - ) - - if len(messageID) > 0 { - t.editMessageTgBot(chatId, messageID[0], output, inlineKeyboard) - } else { - t.SendMsgToTgbot(chatId, output, inlineKeyboard) - } -} - -func (t *Tgbot) clientTelegramUserInfo(chatId int64, email string, messageID ...int) { - traffic, client, err := t.inboundService.GetClientByEmail(email) - if err != nil { - logger.Warning(err) - msg := t.I18nBot("tgbot.wentWrong") - t.SendMsgToTgbot(chatId, msg) - return - } - if client == nil { - msg := t.I18nBot("tgbot.noResult") - t.SendMsgToTgbot(chatId, msg) - return - } - tgId := "None" - if client.TgID != 0 { - tgId = strconv.FormatInt(client.TgID, 10) - } - - output := "" - output += t.I18nBot("tgbot.messages.email", "Email=="+email) - output += t.I18nBot("tgbot.messages.TGUser", "TelegramID=="+tgId) - output += t.I18nBot("tgbot.messages.refreshedOn", "Time=="+time.Now().Format("2006-01-02 15:04:05")) - - inlineKeyboard := tu.InlineKeyboard( - tu.InlineKeyboardRow( - tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.refresh")).WithCallbackData(t.encodeQuery("tgid_refresh "+email)), - ), - tu.InlineKeyboardRow( - tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.removeTGUser")).WithCallbackData(t.encodeQuery("tgid_remove "+email)), - ), - ) - - if len(messageID) > 0 { - t.editMessageTgBot(chatId, messageID[0], output, inlineKeyboard) - } else { - t.SendMsgToTgbot(chatId, output, inlineKeyboard) - requestUser := telego.KeyboardButtonRequestUsers{ - RequestID: int32(traffic.Id), - UserIsBot: new(bool), - } - keyboard := tu.Keyboard( - tu.KeyboardRow( - tu.KeyboardButton(t.I18nBot("tgbot.buttons.selectTGUser")).WithRequestUsers(&requestUser), - ), - tu.KeyboardRow( - tu.KeyboardButton(t.I18nBot("tgbot.buttons.closeKeyboard")), - ), - ).WithIsPersistent().WithResizeKeyboard() - t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.buttons.selectOneTGUser"), keyboard) - } -} - -func (t *Tgbot) searchClient(chatId int64, email string, messageID ...int) { - traffic, err := t.inboundService.GetClientTrafficByEmail(email) - if err != nil { - logger.Warning(err) - msg := t.I18nBot("tgbot.wentWrong") - t.SendMsgToTgbot(chatId, msg) - return - } - if traffic == nil { - msg := t.I18nBot("tgbot.noResult") - t.SendMsgToTgbot(chatId, msg) - return - } - - output := t.clientInfoMsg(traffic, true, true, true, true, true, true) - - inlineKeyboard := tu.InlineKeyboard( - tu.InlineKeyboardRow( - tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.refresh")).WithCallbackData(t.encodeQuery("client_refresh "+email)), - ), - tu.InlineKeyboardRow( - tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.resetTraffic")).WithCallbackData(t.encodeQuery("reset_traffic "+email)), - tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.limitTraffic")).WithCallbackData(t.encodeQuery("limit_traffic "+email)), - ), - tu.InlineKeyboardRow( - tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.resetExpire")).WithCallbackData(t.encodeQuery("reset_exp "+email)), - ), - tu.InlineKeyboardRow( - tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.ipLog")).WithCallbackData(t.encodeQuery("ip_log "+email)), - tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.ipLimit")).WithCallbackData(t.encodeQuery("ip_limit "+email)), - ), - tu.InlineKeyboardRow( - tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.setTGUser")).WithCallbackData(t.encodeQuery("tg_user "+email)), - ), - tu.InlineKeyboardRow( - tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.toggle")).WithCallbackData(t.encodeQuery("toggle_enable "+email)), - ), - ) - if len(messageID) > 0 { - t.editMessageTgBot(chatId, messageID[0], output, inlineKeyboard) - } else { - t.SendMsgToTgbot(chatId, output, inlineKeyboard) - } -} - -func (t *Tgbot) searchInbound(chatId int64, remark string) { - inbounds, err := t.inboundService.SearchInbounds(remark) - if err != nil { - logger.Warning(err) - msg := t.I18nBot("tgbot.wentWrong") - t.SendMsgToTgbot(chatId, msg) - return - } - if len(inbounds) == 0 { - msg := t.I18nBot("tgbot.noInbounds") - t.SendMsgToTgbot(chatId, msg) - return - } - - for _, inbound := range inbounds { - info := "" - info += t.I18nBot("tgbot.messages.inbound", "Remark=="+inbound.Remark) - info += t.I18nBot("tgbot.messages.port", "Port=="+strconv.Itoa(inbound.Port)) - info += t.I18nBot("tgbot.messages.traffic", "Total=="+common.FormatTraffic((inbound.Up+inbound.Down)), "Upload=="+common.FormatTraffic(inbound.Up), "Download=="+common.FormatTraffic(inbound.Down)) - - if inbound.ExpiryTime == 0 { - info += t.I18nBot("tgbot.messages.expire", "Time=="+t.I18nBot("tgbot.unlimited")) - } else { - info += t.I18nBot("tgbot.messages.expire", "Time=="+time.Unix((inbound.ExpiryTime/1000), 0).Format("2006-01-02 15:04:05")) - } - t.SendMsgToTgbot(chatId, info) - - if len(inbound.ClientStats) > 0 { - output := "" - for _, traffic := range inbound.ClientStats { - output += t.clientInfoMsg(&traffic, true, true, true, true, true, true) - } - t.SendMsgToTgbot(chatId, output) - } - } -} - -func (t *Tgbot) getExhausted(chatId int64) { - trDiff := int64(0) - exDiff := int64(0) - now := time.Now().Unix() * 1000 - var exhaustedInbounds []model.Inbound - var exhaustedClients []xray.ClientTraffic - var disabledInbounds []model.Inbound - var disabledClients []xray.ClientTraffic - - TrafficThreshold, err := t.settingService.GetTrafficDiff() - if err == nil && TrafficThreshold > 0 { - trDiff = int64(TrafficThreshold) * 1073741824 - } - ExpireThreshold, err := t.settingService.GetExpireDiff() - if err == nil && ExpireThreshold > 0 { - exDiff = int64(ExpireThreshold) * 86400000 - } - inbounds, err := t.inboundService.GetAllInbounds() - if err != nil { - logger.Warning("Unable to load Inbounds", err) - } - - for _, inbound := range inbounds { - if inbound.Enable { - if (inbound.ExpiryTime > 0 && (inbound.ExpiryTime-now < exDiff)) || - (inbound.Total > 0 && (inbound.Total-(inbound.Up+inbound.Down) < trDiff)) { - exhaustedInbounds = append(exhaustedInbounds, *inbound) - } - if len(inbound.ClientStats) > 0 { - for _, client := range inbound.ClientStats { - if client.Enable { - if (client.ExpiryTime > 0 && (client.ExpiryTime-now < exDiff)) || - (client.Total > 0 && (client.Total-(client.Up+client.Down) < trDiff)) { - exhaustedClients = append(exhaustedClients, client) - } - } else { - disabledClients = append(disabledClients, client) - } - } - } - } else { - disabledInbounds = append(disabledInbounds, *inbound) - } - } - - // Inbounds - output := "" - output += t.I18nBot("tgbot.messages.exhaustedCount", "Type=="+t.I18nBot("tgbot.inbounds")) - output += t.I18nBot("tgbot.messages.disabled", "Disabled=="+strconv.Itoa(len(disabledInbounds))) - output += t.I18nBot("tgbot.messages.depleteSoon", "Deplete=="+strconv.Itoa(len(exhaustedInbounds))) - - if len(exhaustedInbounds) > 0 { - output += t.I18nBot("tgbot.messages.depleteSoon", "Deplete=="+t.I18nBot("tgbot.inbounds")) - - for _, inbound := range exhaustedInbounds { - output += t.I18nBot("tgbot.messages.inbound", "Remark=="+inbound.Remark) - output += t.I18nBot("tgbot.messages.port", "Port=="+strconv.Itoa(inbound.Port)) - output += t.I18nBot("tgbot.messages.traffic", "Total=="+common.FormatTraffic((inbound.Up+inbound.Down)), "Upload=="+common.FormatTraffic(inbound.Up), "Download=="+common.FormatTraffic(inbound.Down)) - if inbound.ExpiryTime == 0 { - output += t.I18nBot("tgbot.messages.expire", "Time=="+t.I18nBot("tgbot.unlimited")) - } else { - output += t.I18nBot("tgbot.messages.expire", "Time=="+time.Unix((inbound.ExpiryTime/1000), 0).Format("2006-01-02 15:04:05")) - } - output += "\r\n" - } - } - - // Clients - exhaustedCC := len(exhaustedClients) - output += t.I18nBot("tgbot.messages.exhaustedCount", "Type=="+t.I18nBot("tgbot.clients")) - output += t.I18nBot("tgbot.messages.disabled", "Disabled=="+strconv.Itoa(len(disabledClients))) - output += t.I18nBot("tgbot.messages.depleteSoon", "Deplete=="+strconv.Itoa(exhaustedCC)) - - if exhaustedCC > 0 { - output += t.I18nBot("tgbot.messages.depleteSoon", "Deplete=="+t.I18nBot("tgbot.clients")) - var buttons []telego.InlineKeyboardButton - for _, traffic := range exhaustedClients { - output += t.clientInfoMsg(&traffic, true, false, false, true, true, false) - output += "\r\n" - buttons = append(buttons, tu.InlineKeyboardButton(traffic.Email).WithCallbackData(t.encodeQuery("client_get_usage "+traffic.Email))) - } - cols := 0 - if exhaustedCC < 11 { - cols = 1 - } else { - cols = 2 - } - output += t.I18nBot("tgbot.messages.refreshedOn", "Time=="+time.Now().Format("2006-01-02 15:04:05")) - keyboard := tu.InlineKeyboardGrid(tu.InlineKeyboardCols(cols, buttons...)) - t.SendMsgToTgbot(chatId, output, keyboard) - } else { - output += t.I18nBot("tgbot.messages.refreshedOn", "Time=="+time.Now().Format("2006-01-02 15:04:05")) - t.SendMsgToTgbot(chatId, output) - } -} - -func (t *Tgbot) notifyExhausted() { - trDiff := int64(0) - exDiff := int64(0) - now := time.Now().Unix() * 1000 - - TrafficThreshold, err := t.settingService.GetTrafficDiff() - if err == nil && TrafficThreshold > 0 { - trDiff = int64(TrafficThreshold) * 1073741824 - } - ExpireThreshold, err := t.settingService.GetExpireDiff() - if err == nil && ExpireThreshold > 0 { - exDiff = int64(ExpireThreshold) * 86400000 - } - inbounds, err := t.inboundService.GetAllInbounds() - if err != nil { - logger.Warning("Unable to load Inbounds", err) - } - - var chatIDsDone []int64 - for _, inbound := range inbounds { - if inbound.Enable { - if len(inbound.ClientStats) > 0 { - clients, err := t.inboundService.GetClients(inbound) - if err == nil { - for _, client := range clients { - if client.TgID != 0 { - chatID := client.TgID - if !int64Contains(chatIDsDone, chatID) && !checkAdmin(chatID) { - var disabledClients []xray.ClientTraffic - var exhaustedClients []xray.ClientTraffic - traffics, err := t.inboundService.GetClientTrafficTgBot(client.TgID) - if err == nil && len(traffics) > 0 { - output := t.I18nBot("tgbot.messages.exhaustedCount", "Type=="+t.I18nBot("tgbot.clients")) - for _, traffic := range traffics { - if traffic.Enable { - if (traffic.ExpiryTime > 0 && (traffic.ExpiryTime-now < exDiff)) || - (traffic.Total > 0 && (traffic.Total-(traffic.Up+traffic.Down) < trDiff)) { - exhaustedClients = append(exhaustedClients, *traffic) - } - } else { - disabledClients = append(disabledClients, *traffic) - } - } - if len(exhaustedClients) > 0 { - output += t.I18nBot("tgbot.messages.disabled", "Disabled=="+strconv.Itoa(len(disabledClients))) - if len(disabledClients) > 0 { - output += t.I18nBot("tgbot.clients") + ":\r\n" - for _, traffic := range disabledClients { - output += " " + traffic.Email - } - output += "\r\n" - } - output += "\r\n" - output += t.I18nBot("tgbot.messages.depleteSoon", "Deplete=="+strconv.Itoa(len(exhaustedClients))) - for _, traffic := range exhaustedClients { - output += t.clientInfoMsg(&traffic, true, false, false, true, true, false) - output += "\r\n" - } - t.SendMsgToTgbot(chatID, output) - } - chatIDsDone = append(chatIDsDone, chatID) - } - } - } - } - } - } - } - } -} - -func int64Contains(slice []int64, item int64) bool { - for _, s := range slice { - if s == item { - return true - } - } - return false -} - -func (t *Tgbot) onlineClients(chatId int64, messageID ...int) { - if !p.IsRunning() { - return - } - - onlines := p.GetOnlineClients() - onlinesCount := len(onlines) - output := t.I18nBot("tgbot.messages.onlinesCount", "Count=="+fmt.Sprint(onlinesCount)) - keyboard := tu.InlineKeyboard(tu.InlineKeyboardRow( - tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.refresh")).WithCallbackData(t.encodeQuery("onlines_refresh")))) - - if onlinesCount > 0 { - var buttons []telego.InlineKeyboardButton - for _, online := range onlines { - buttons = append(buttons, tu.InlineKeyboardButton(online).WithCallbackData(t.encodeQuery("client_get_usage "+online))) - } - cols := 0 - if onlinesCount < 21 { - cols = 2 - } else if onlinesCount < 61 { - cols = 3 - } else { - cols = 4 - } - keyboard.InlineKeyboard = append(keyboard.InlineKeyboard, tu.InlineKeyboardCols(cols, buttons...)...) - } - - if len(messageID) > 0 { - t.editMessageTgBot(chatId, messageID[0], output, keyboard) - } else { - t.SendMsgToTgbot(chatId, output, keyboard) - } -} - -func (t *Tgbot) sendBackup(chatId int64) { - output := t.I18nBot("tgbot.messages.backupTime", "Time=="+time.Now().Format("2006-01-02 15:04:05")) - t.SendMsgToTgbot(chatId, output) - - // Update by manually trigger a checkpoint operation - err := database.Checkpoint() - if err != nil { - logger.Error("Error in trigger a checkpoint operation: ", err) - } - - file, err := os.Open(config.GetDBPath()) - if err == nil { - document := tu.Document( - tu.ID(chatId), - tu.File(file), - ) - _, err = bot.SendDocument(document) - if err != nil { - logger.Error("Error in uploading backup: ", err) - } - } else { - logger.Error("Error in opening db file for backup: ", err) - } - - file, err = os.Open(xray.GetConfigPath()) - if err == nil { - document := tu.Document( - tu.ID(chatId), - tu.File(file), - ) - _, err = bot.SendDocument(document) - if err != nil { - logger.Error("Error in uploading config.json: ", err) - } - } else { - logger.Error("Error in opening config.json file for backup: ", err) - } -} - -func (t *Tgbot) sendBanLogs(chatId int64, dt bool) { - if dt { - output := t.I18nBot("tgbot.messages.datetime", "DateTime=="+time.Now().Format("2006-01-02 15:04:05")) - t.SendMsgToTgbot(chatId, output) - } - - file, err := os.Open(xray.GetIPLimitBannedPrevLogPath()) - if err == nil { - // Check if the file is non-empty before attempting to upload - fileInfo, _ := file.Stat() - if fileInfo.Size() > 0 { - document := tu.Document( - tu.ID(chatId), - tu.File(file), - ) - _, err = bot.SendDocument(document) - if err != nil { - logger.Error("Error in uploading IPLimitBannedPrevLog: ", err) - } - } else { - logger.Warning("IPLimitBannedPrevLog file is empty, not uploading.") - } - file.Close() - } else { - logger.Error("Error in opening IPLimitBannedPrevLog file for backup: ", err) - } - - file, err = os.Open(xray.GetIPLimitBannedLogPath()) - if err == nil { - // Check if the file is non-empty before attempting to upload - fileInfo, _ := file.Stat() - if fileInfo.Size() > 0 { - document := tu.Document( - tu.ID(chatId), - tu.File(file), - ) - _, err = bot.SendDocument(document) - if err != nil { - logger.Error("Error in uploading IPLimitBannedLog: ", err) - } - } else { - logger.Warning("IPLimitBannedLog file is empty, not uploading.") - } - file.Close() - } else { - logger.Error("Error in opening IPLimitBannedLog file for backup: ", err) - } -} - -func (t *Tgbot) sendCallbackAnswerTgBot(id string, message string) { - params := telego.AnswerCallbackQueryParams{ - CallbackQueryID: id, - Text: message, - } - if err := bot.AnswerCallbackQuery(¶ms); err != nil { - logger.Warning(err) - } -} - -func (t *Tgbot) editMessageCallbackTgBot(chatId int64, messageID int, inlineKeyboard *telego.InlineKeyboardMarkup) { - params := telego.EditMessageReplyMarkupParams{ - ChatID: tu.ID(chatId), - MessageID: messageID, - ReplyMarkup: inlineKeyboard, - } - if _, err := bot.EditMessageReplyMarkup(¶ms); err != nil { - logger.Warning(err) - } -} - -func (t *Tgbot) editMessageTgBot(chatId int64, messageID int, text string, inlineKeyboard ...*telego.InlineKeyboardMarkup) { - params := telego.EditMessageTextParams{ - ChatID: tu.ID(chatId), - MessageID: messageID, - Text: text, - ParseMode: "HTML", - } - if len(inlineKeyboard) > 0 { - params.ReplyMarkup = inlineKeyboard[0] - } - if _, err := bot.EditMessageText(¶ms); err != nil { - logger.Warning(err) - } -} diff --git a/web/service/user.go b/web/service/user.go deleted file mode 100644 index fdfffa3c..00000000 --- a/web/service/user.go +++ /dev/null @@ -1,116 +0,0 @@ -package service - -import ( - "errors" - - "x-ui/database" - "x-ui/database/model" - "x-ui/logger" - - "gorm.io/gorm" -) - -type UserService struct{} - -func (s *UserService) GetFirstUser() (*model.User, error) { - db := database.GetDB() - - user := &model.User{} - err := db.Model(model.User{}). - First(user). - Error - if err != nil { - return nil, err - } - return user, nil -} - -func (s *UserService) CheckUser(username string, password string, secret string) *model.User { - db := database.GetDB() - - user := &model.User{} - err := db.Model(model.User{}). - Where("username = ? and password = ? and login_secret = ?", username, password, secret). - First(user). - Error - if err == gorm.ErrRecordNotFound { - return nil - } else if err != nil { - logger.Warning("check user err:", err) - return nil - } - return user -} - -func (s *UserService) UpdateUser(id int, username string, password string) error { - db := database.GetDB() - return db.Model(model.User{}). - Where("id = ?", id). - Updates(map[string]interface{}{"username": username, "password": password}). - Error -} - -func (s *UserService) UpdateUserSecret(id int, secret string) error { - db := database.GetDB() - return db.Model(model.User{}). - Where("id = ?", id). - Update("login_secret", secret). - Error -} - -func (s *UserService) RemoveUserSecret() error { - db := database.GetDB() - return db.Model(model.User{}). - Where("1 = 1"). - Update("login_secret", ""). - Error -} - -func (s *UserService) GetUserSecret(id int) *model.User { - db := database.GetDB() - user := &model.User{} - err := db.Model(model.User{}). - Where("id = ?", id). - First(user). - Error - if err == gorm.ErrRecordNotFound { - return nil - } - return user -} - -func (s *UserService) CheckSecretExistence() (bool, error) { - db := database.GetDB() - - var count int64 - err := db.Model(model.User{}). - Where("login_secret IS NOT NULL"). - Count(&count). - Error - if err != nil { - return false, err - } - - return count > 0, nil -} - -func (s *UserService) UpdateFirstUser(username string, password string) error { - if username == "" { - return errors.New("username can not be empty") - } else if password == "" { - return errors.New("password can not be empty") - } - db := database.GetDB() - user := &model.User{} - err := db.Model(model.User{}).First(user).Error - if database.IsNotFound(err) { - user.Username = username - user.Password = password - return db.Model(model.User{}).Create(user).Error - } else if err != nil { - return err - } - user.Username = username - user.Password = password - return db.Save(user).Error -} diff --git a/web/service/warp.go b/web/service/warp.go deleted file mode 100644 index 5b710f4a..00000000 --- a/web/service/warp.go +++ /dev/null @@ -1,173 +0,0 @@ -package service - -import ( - "bytes" - "encoding/json" - "fmt" - "net/http" - "os" - "time" - "x-ui/logger" - "x-ui/util/common" -) - -type WarpService struct { - SettingService -} - -func (s *WarpService) GetWarpData() (string, error) { - warp, err := s.SettingService.GetWarp() - if err != nil { - return "", err - } - return warp, nil -} - -func (s *WarpService) DelWarpData() error { - err := s.SettingService.SetWarp("") - if err != nil { - return err - } - return nil -} - -func (s *WarpService) GetWarpConfig() (string, error) { - var warpData map[string]string - warp, err := s.SettingService.GetWarp() - if err != nil { - return "", err - } - err = json.Unmarshal([]byte(warp), &warpData) - if err != nil { - return "", err - } - - url := fmt.Sprintf("https://api.cloudflareclient.com/v0a2158/reg/%s", warpData["device_id"]) - - req, err := http.NewRequest("GET", url, nil) - if err != nil { - return "", err - } - req.Header.Set("Authorization", "Bearer "+warpData["access_token"]) - - client := &http.Client{} - resp, err := client.Do(req) - if err != nil { - return "", err - } - defer resp.Body.Close() - buffer := bytes.NewBuffer(make([]byte, 8192)) - buffer.Reset() - _, err = buffer.ReadFrom(resp.Body) - if err != nil { - return "", err - } - - return buffer.String(), nil -} - -func (s *WarpService) RegWarp(secretKey string, publicKey string) (string, error) { - tos := time.Now().UTC().Format("2006-01-02T15:04:05.000Z") - hostName, _ := os.Hostname() - data := fmt.Sprintf(`{"key":"%s","tos":"%s","type": "PC","model": "x-ui", "name": "%s"}`, publicKey, tos, hostName) - - url := "https://api.cloudflareclient.com/v0a2158/reg" - - req, err := http.NewRequest("POST", url, bytes.NewBuffer([]byte(data))) - if err != nil { - return "", err - } - - req.Header.Add("CF-Client-Version", "a-7.21-0721") - req.Header.Add("Content-Type", "application/json") - - client := &http.Client{} - resp, err := client.Do(req) - if err != nil { - return "", err - } - defer resp.Body.Close() - buffer := bytes.NewBuffer(make([]byte, 8192)) - buffer.Reset() - _, err = buffer.ReadFrom(resp.Body) - if err != nil { - return "", err - } - - var rspData map[string]interface{} - err = json.Unmarshal(buffer.Bytes(), &rspData) - if err != nil { - return "", err - } - - deviceId := rspData["id"].(string) - token := rspData["token"].(string) - license, ok := rspData["account"].(map[string]interface{})["license"].(string) - if !ok { - logger.Debug("Error accessing license value.") - return "", err - } - - warpData := fmt.Sprintf("{\n \"access_token\": \"%s\",\n \"device_id\": \"%s\",", token, deviceId) - warpData += fmt.Sprintf("\n \"license_key\": \"%s\",\n \"private_key\": \"%s\"\n}", license, secretKey) - - s.SettingService.SetWarp(warpData) - - result := fmt.Sprintf("{\n \"data\": %s,\n \"config\": %s\n}", warpData, buffer.String()) - - return result, nil -} - -func (s *WarpService) SetWarpLicense(license string) (string, error) { - var warpData map[string]string - warp, err := s.SettingService.GetWarp() - if err != nil { - return "", err - } - err = json.Unmarshal([]byte(warp), &warpData) - if err != nil { - return "", err - } - - url := fmt.Sprintf("https://api.cloudflareclient.com/v0a2158/reg/%s/account", warpData["device_id"]) - data := fmt.Sprintf(`{"license": "%s"}`, license) - - req, err := http.NewRequest("PUT", url, bytes.NewBuffer([]byte(data))) - if err != nil { - return "", err - } - req.Header.Set("Authorization", "Bearer "+warpData["access_token"]) - - client := &http.Client{} - resp, err := client.Do(req) - if err != nil { - return "", err - } - defer resp.Body.Close() - buffer := bytes.NewBuffer(make([]byte, 8192)) - buffer.Reset() - _, err = buffer.ReadFrom(resp.Body) - if err != nil { - return "", err - } - - var response map[string]interface{} - err = json.Unmarshal(buffer.Bytes(), &response) - if err != nil { - return "", err - } - if response["success"] == false { - errorArr, _ := response["errors"].([]interface{}) - errorObj := errorArr[0].(map[string]interface{}) - return "", common.NewError(errorObj["code"], errorObj["message"]) - } - - warpData["license_key"] = license - newWarpData, err := json.MarshalIndent(warpData, "", " ") - if err != nil { - return "", err - } - s.SettingService.SetWarp(string(newWarpData)) - - return string(newWarpData), nil -} diff --git a/web/service/xray.go b/web/service/xray.go deleted file mode 100644 index d37c963a..00000000 --- a/web/service/xray.go +++ /dev/null @@ -1,227 +0,0 @@ -package service - -import ( - "encoding/json" - "errors" - "sync" - - "x-ui/logger" - "x-ui/xray" - - "go.uber.org/atomic" -) - -var ( - p *xray.Process - lock sync.Mutex - isNeedXrayRestart atomic.Bool - result string -) - -type XrayService struct { - inboundService InboundService - settingService SettingService - xrayAPI xray.XrayAPI -} - -func (s *XrayService) IsXrayRunning() bool { - return p != nil && p.IsRunning() -} - -func (s *XrayService) GetXrayErr() error { - if p == nil { - return nil - } - return p.GetErr() -} - -func (s *XrayService) GetXrayResult() string { - if result != "" { - return result - } - if s.IsXrayRunning() { - return "" - } - if p == nil { - return "" - } - result = p.GetResult() - return result -} - -func (s *XrayService) GetXrayVersion() string { - if p == nil { - return "Unknown" - } - return p.GetVersion() -} - -func RemoveIndex(s []interface{}, index int) []interface{} { - return append(s[:index], s[index+1:]...) -} - -func (s *XrayService) GetXrayConfig() (*xray.Config, error) { - templateConfig, err := s.settingService.GetXrayConfigTemplate() - if err != nil { - return nil, err - } - - xrayConfig := &xray.Config{} - err = json.Unmarshal([]byte(templateConfig), xrayConfig) - if err != nil { - return nil, err - } - - s.inboundService.AddTraffic(nil, nil) - - inbounds, err := s.inboundService.GetAllInbounds() - if err != nil { - return nil, err - } - for _, inbound := range inbounds { - if !inbound.Enable { - continue - } - // get settings clients - settings := map[string]interface{}{} - json.Unmarshal([]byte(inbound.Settings), &settings) - clients, ok := settings["clients"].([]interface{}) - if ok { - // check users active or not - clientStats := inbound.ClientStats - for _, clientTraffic := range clientStats { - indexDecrease := 0 - for index, client := range clients { - c := client.(map[string]interface{}) - if c["email"] == clientTraffic.Email { - if !clientTraffic.Enable { - clients = RemoveIndex(clients, index-indexDecrease) - indexDecrease++ - logger.Infof("Remove Inbound User %s due to expiration or traffic limit", c["email"]) - } - } - } - } - - // clear client config for additional parameters - var final_clients []interface{} - for _, client := range clients { - c := client.(map[string]interface{}) - if c["enable"] != nil { - if enable, ok := c["enable"].(bool); ok && !enable { - continue - } - } - for key := range c { - if key != "email" && key != "id" && key != "password" && key != "flow" && key != "method" { - delete(c, key) - } - if c["flow"] == "xtls-rprx-vision-udp443" { - c["flow"] = "xtls-rprx-vision" - } - } - final_clients = append(final_clients, interface{}(c)) - } - - settings["clients"] = final_clients - modifiedSettings, err := json.MarshalIndent(settings, "", " ") - if err != nil { - return nil, err - } - - inbound.Settings = string(modifiedSettings) - } - - if len(inbound.StreamSettings) > 0 { - // Unmarshal stream JSON - var stream map[string]interface{} - json.Unmarshal([]byte(inbound.StreamSettings), &stream) - - // Remove the "settings" field under "tlsSettings" and "realitySettings" - tlsSettings, ok1 := stream["tlsSettings"].(map[string]interface{}) - realitySettings, ok2 := stream["realitySettings"].(map[string]interface{}) - if ok1 || ok2 { - if ok1 { - delete(tlsSettings, "settings") - } else if ok2 { - delete(realitySettings, "settings") - } - } - - delete(stream, "externalProxy") - - newStream, err := json.MarshalIndent(stream, "", " ") - if err != nil { - return nil, err - } - inbound.StreamSettings = string(newStream) - } - - inboundConfig := inbound.GenXrayInboundConfig() - xrayConfig.InboundConfigs = append(xrayConfig.InboundConfigs, *inboundConfig) - } - return xrayConfig, nil -} - -func (s *XrayService) GetXrayTraffic() ([]*xray.Traffic, []*xray.ClientTraffic, error) { - if !s.IsXrayRunning() { - err := errors.New("xray is not running") - logger.Debug("Attempted to fetch Xray traffic, but Xray is not running:", err) - return nil, nil, err - } - apiPort := p.GetAPIPort() - s.xrayAPI.Init(apiPort) - defer s.xrayAPI.Close() - - traffic, clientTraffic, err := s.xrayAPI.GetTraffic(true) - if err != nil { - logger.Debug("Failed to fetch Xray traffic:", err) - return nil, nil, err - } - return traffic, clientTraffic, nil -} - -func (s *XrayService) RestartXray(isForce bool) error { - lock.Lock() - defer lock.Unlock() - logger.Debug("restart xray, force:", isForce) - - xrayConfig, err := s.GetXrayConfig() - if err != nil { - return err - } - - if s.IsXrayRunning() { - if !isForce && p.GetConfig().Equals(xrayConfig) { - logger.Debug("It does not need to restart xray") - return nil - } - p.Stop() - } - - p = xray.NewProcess(xrayConfig) - result = "" - err = p.Start() - if err != nil { - return err - } - return nil -} - -func (s *XrayService) StopXray() error { - lock.Lock() - defer lock.Unlock() - logger.Debug("Attempting to stop Xray...") - if s.IsXrayRunning() { - return p.Stop() - } - return errors.New("xray is not running") -} - -func (s *XrayService) SetToNeedRestart() { - isNeedXrayRestart.Store(true) -} - -func (s *XrayService) IsNeedRestartAndSetFalse() bool { - return isNeedXrayRestart.CompareAndSwap(true, false) -} diff --git a/web/service/xray_setting.go b/web/service/xray_setting.go deleted file mode 100644 index f497bf84..00000000 --- a/web/service/xray_setting.go +++ /dev/null @@ -1,29 +0,0 @@ -package service - -import ( - _ "embed" - "encoding/json" - - "x-ui/util/common" - "x-ui/xray" -) - -type XraySettingService struct { - SettingService -} - -func (s *XraySettingService) SaveXraySetting(newXraySettings string) error { - if err := s.CheckXrayConfig(newXraySettings); err != nil { - return err - } - return s.SettingService.saveSetting("xrayTemplateConfig", newXraySettings) -} - -func (s *XraySettingService) CheckXrayConfig(XrayTemplateConfig string) error { - xrayConfig := &xray.Config{} - err := json.Unmarshal([]byte(XrayTemplateConfig), xrayConfig) - if err != nil { - return common.NewError("xray template config invalid:", err) - } - return nil -} diff --git a/web/session/session.go b/web/session/session.go deleted file mode 100644 index 13aedad8..00000000 --- a/web/session/session.go +++ /dev/null @@ -1,65 +0,0 @@ -package session - -import ( - "encoding/gob" - - "x-ui/database/model" - - "github.com/gin-contrib/sessions" - "github.com/gin-gonic/gin" -) - -const ( - loginUserKey = "LOGIN_USER" - defaultPath = "/" -) - -func init() { - gob.Register(model.User{}) -} - -func SetLoginUser(c *gin.Context, user *model.User) { - if user == nil { - return - } - s := sessions.Default(c) - s.Set(loginUserKey, *user) -} - -func SetMaxAge(c *gin.Context, maxAge int) { - s := sessions.Default(c) - s.Options(sessions.Options{ - Path: defaultPath, - MaxAge: maxAge, - HttpOnly: true, - }) -} - -func GetLoginUser(c *gin.Context) *model.User { - s := sessions.Default(c) - obj := s.Get(loginUserKey) - if obj == nil { - return nil - } - user, ok := obj.(model.User) - if !ok { - - s.Delete(loginUserKey) - return nil - } - return &user -} - -func IsLogin(c *gin.Context) bool { - return GetLoginUser(c) != nil -} - -func ClearSession(c *gin.Context) { - s := sessions.Default(c) - s.Clear() - s.Options(sessions.Options{ - Path: defaultPath, - MaxAge: -1, - HttpOnly: true, - }) -} diff --git a/web/translation/translate.en_US.toml b/web/translation/translate.en_US.toml deleted file mode 100644 index 638d619e..00000000 --- a/web/translation/translate.en_US.toml +++ /dev/null @@ -1,598 +0,0 @@ -"username" = "Username" -"password" = "Password" -"login" = "Log In" -"confirm" = "Confirm" -"cancel" = "Cancel" -"close" = "Close" -"copy" = "Copy" -"copied" = "Copied" -"download" = "Download" -"remark" = "Remark" -"enable" = "Enabled" -"protocol" = "Protocol" -"search" = "Search" -"filter" = "Filter" -"loading" = "Loading..." -"second" = "Second" -"minute" = "Minute" -"hour" = "Hour" -"day" = "Day" -"check" = "Check" -"indefinite" = "Indefinite" -"unlimited" = "Unlimited" -"none" = "None" -"qrCode" = "QR Code" -"info" = "More Information" -"edit" = "Edit" -"delete" = "Delete" -"reset" = "Reset" -"copySuccess" = "Copied Successful" -"sure" = "Sure" -"encryption" = "Encryption" -"transmission" = "Transmission" -"host" = "Host" -"path" = "Path" -"camouflage" = "Obfuscation" -"status" = "Status" -"enabled" = "Enabled" -"disabled" = "Disabled" -"depleted" = "Ended" -"depletingSoon" = "Depleting" -"offline" = "Offline" -"online" = "Online" -"domainName" = "Domain Name" -"monitor" = "Listen IP" -"certificate" = "Digital Certificate" -"fail" = "Failed" -"comment" = "Comment" -"success" = "Successfully" -"getVersion" = "Get Version" -"install" = "Install" -"clients" = "Clients" -"usage" = "Usage" -"secretToken" = "Secret Token" -"remained" = "Remained" -"security" = "Security" -"secAlertTitle" = "Security Alert" -"secAlertSsl" = "This connection is not secure. Please avoid entering sensitive information until TLS is activated for data protection." -"secAlertConf" = "Certain settings are vulnerable to attacks. It is recommended to reinforce security protocols to prevent potential breaches." -"secAlertSSL" = "Panel lacks secure connection. Please install TLS certificate for data protection." -"secAlertPanelPort" = "Panel default port is vulnerable. Please configure a random or specific port." -"secAlertPanelURI" = "Panel default URI path is insecure. Please configure a complex URI path." -"secAlertSubURI" = "Subscription default URI path is insecure. Please configure a complex URI path." -"secAlertSubJsonURI" = "Subscription JSON default URI path is insecure. Please configure a complex URI path." - -[menu] -"dashboard" = "Overview" -"inbounds" = "Inbounds" -"settings" = "Panel Settings" -"xray" = "Xray Configs" -"logout" = "Log Out" -"link" = "Manage" - -[pages.login] -"hello" = "Hello" -"title" = "Welcome" -"loginAgain" = "Your session has expired, please log in again" - -[pages.login.toasts] -"invalidFormData" = "The Input data format is invalid." -"emptyUsername" = "Username is required" -"emptyPassword" = "Password is required" -"wrongUsernameOrPassword" = "Invalid username or password or secret." -"successLogin" = "Login" - -[pages.index] -"title" = "Overview" -"memory" = "RAM" -"hard" = "Disk" -"xrayStatus" = "Xray" -"stopXray" = "Stop" -"restartXray" = "Restart" -"xraySwitch" = "Version" -"xraySwitchClick" = "Choose the version you want to switch to." -"xraySwitchClickDesk" = "Choose carefully, as older versions may not be compatible with current configurations." -"operationHours" = "Uptime" -"systemLoad" = "System Load" -"systemLoadDesc" = "System load average for the past 1, 5, and 15 minutes" -"connectionTcpCountDesc" = "Total TCP connections across the system" -"connectionUdpCountDesc" = "Total UDP connections across the system" -"connectionCount" = "Connection Stats" -"upSpeed" = "Overall upload speed across the system" -"downSpeed" = "Overall download speed across the system" -"totalSent" = "Total data sent across the system since OS startup" -"totalReceive" = "Total data received across the system since OS startup" -"xraySwitchVersionDialog" = "Change Xray Version" -"xraySwitchVersionDialogDesc" = "Are you sure you want to change the Xray version to" -"dontRefresh" = "Installation is in progress, please do not refresh this page" -"logs" = "Logs" -"config" = "Config" -"backup" = "Backup & Restore" -"backupTitle" = "Database Backup & Restore" -"backupDescription" = "It is recommended to make a backup before restoring a database." -"exportDatabase" = "Back Up" -"importDatabase" = "Restore" - -[pages.inbounds] -"title" = "Inbounds" -"totalDownUp" = "Total Sent/Received" -"totalUsage" = "Total Usage" -"inboundCount" = "Total Inbounds" -"operate" = "Menu" -"enable" = "Enabled" -"remark" = "Remark" -"protocol" = "Protocol" -"port" = "Port" -"traffic" = "Traffic" -"details" = "Details" -"transportConfig" = "Transport" -"expireDate" = "Duration" -"resetTraffic" = "Reset Traffic" -"addInbound" = "Add Inbound" -"generalActions" = "General Actions" -"create" = "Create" -"update" = "Update" -"modifyInbound" = "Modify Inbound" -"deleteInbound" = "Delete Inbound" -"deleteInboundContent" = "Are you sure you want to delete inbound?" -"deleteClient" = "Delete Client" -"deleteClientContent" = "Are you sure you want to delete client?" -"resetTrafficContent" = "Are you sure you want to reset traffic?" -"copyLink" = "Copy URL" -"address" = "Address" -"network" = "Network" -"destinationPort" = "Destination Port" -"targetAddress" = "Target Address" -"monitorDesc" = "Leave blank to listen on all IPs" -"meansNoLimit" = "= Unlimited. (unit: GB)" -"totalFlow" = "Total Flow" -"leaveBlankToNeverExpire" = "Leave blank to never expire" -"noRecommendKeepDefault" = "It is recommended to keep the default" -"certificatePath" = "File Path" -"certificateContent" = "File Content" -"publicKey" = "Public Key" -"privatekey" = "Private Key" -"clickOnQRcode" = "Click on QR Code to Copy" -"client" = "Client" -"export" = "Export All URLs" -"clone" = "Clone" -"cloneInbound" = "Clone" -"cloneInboundContent" = "All settings of this inbound, except Port, Listening IP, and Clients, will be applied to the clone." -"cloneInboundOk" = "Clone" -"resetAllTraffic" = "Reset All Inbounds Traffic" -"resetAllTrafficTitle" = "Reset All Inbounds Traffic" -"resetAllTrafficContent" = "Are you sure you want to reset the traffic of all inbounds?" -"resetInboundClientTraffics" = "Reset Clients Traffic" -"resetInboundClientTrafficTitle" = "Reset Clients Traffic" -"resetInboundClientTrafficContent" = "Are you sure you want to reset the traffic of this inbound's clients?" -"resetAllClientTraffics" = "Reset All Clients Traffic" -"resetAllClientTrafficTitle" = "Reset All Clients Traffic" -"resetAllClientTrafficContent" = "Are you sure you want to reset the traffic of all clients?" -"delDepletedClients" = "Delete Depleted Clients" -"delDepletedClientsTitle" = "Delete Depleted Clients" -"delDepletedClientsContent" = "Are you sure you want to delete all the depleted clients?" -"email" = "Email" -"emailDesc" = "Please provide a unique email address." -"IPLimit" = "IP Limit" -"IPLimitDesc" = "Disables inbound if the count exceeds the set value. (0 = disable)" -"IPLimitlog" = "IP Log" -"IPLimitlogDesc" = "The IPs history log. (to enable inbound after disabling, clear the log)" -"IPLimitlogclear" = "Clear The Log" -"setDefaultCert" = "Set Cert from Panel" -"telegramDesc" = "Please provide Telegram Chat ID. (use '/id' command in the bot) or (@userinfobot)" -"subscriptionDesc" = "To find your subscription URL, navigate to the 'Details'. Additionally, you can use the same name for several clients." -"info" = "Info" -"same" = "Same" -"inboundData" = "Inbound's Data" -"exportInbound" = "Export Inbound" -"import" = "Import" -"importInbound" = "Import an Inbound" - -[pages.client] -"add" = "Add Client" -"edit" = "Edit Client" -"submitAdd" = "Add Client" -"submitEdit" = "Save Changes" -"clientCount" = "Number of Clients" -"bulk" = "Add Bulk" -"method" = "Method" -"first" = "First" -"last" = "Last" -"prefix" = "Prefix" -"postfix" = "Postfix" -"delayedStart" = "Start After First Use" -"expireDays" = "Duration" -"days" = "Day(s)" -"renew" = "Auto Renew" -"renewDesc" = "Auto-renewal after expiration. (0 = disable)(unit: day)" - -[pages.inbounds.toasts] -"obtain" = "Obtain" - -[pages.inbounds.stream.general] -"request" = "Request" -"response" = "Response" -"name" = "Name" -"value" = "Value" - -[pages.inbounds.stream.tcp] -"version" = "Version" -"method" = "Method" -"path" = "Path" -"status" = "Status" -"statusDescription" = "Status Desc" -"requestHeader" = "Request Header" -"responseHeader" = "Response Header" - -[pages.settings] -"title" = "Panel Settings" -"save" = "Save" -"infoDesc" = "Every change made here needs to be saved. Please restart the panel to apply changes." -"restartPanel" = "Restart Panel" -"restartPanelDesc" = "Are you sure you want to restart the panel? If you cannot access the panel after restarting, please view the panel log info on the server." -"actions" = "Actions" -"resetDefaultConfig" = "Reset to Default" -"panelSettings" = "General" -"securitySettings" = "Authentication" -"TGBotSettings" = "Telegram Bot" -"panelListeningIP" = "Listen IP" -"panelListeningIPDesc" = "The IP address for the web panel. (leave blank to listen on all IPs)" -"panelListeningDomain" = "Listen Domain" -"panelListeningDomainDesc" = "The domain name for the web panel. (leave blank to listen on all domains and IPs)" -"panelPort" = "Listen Port" -"panelPortDesc" = "The port number for the web panel. (must be an unused port)" -"publicKeyPath" = "Public Key Path" -"publicKeyPathDesc" = "The public key file path for the web panel. (begins with ‘/‘)" -"privateKeyPath" = "Private Key Path" -"privateKeyPathDesc" = "The private key file path for the web panel. (begins with ‘/‘)" -"panelUrlPath" = "URI Path" -"panelUrlPathDesc" = "The URI path for the web panel. (begins with ‘/‘ and concludes with ‘/‘)" -"pageSize" = "Pagination Size" -"pageSizeDesc" = "Define page size for inbounds table. (0 = disable)" -"remarkModel" = "Remark Model & Separation Character" -"datepicker" = "Calendar Type" -"datepickerPlaceholder" = "Select date" -"datepickerDescription" = "Scheduled tasks will run based on this calendar." -"sampleRemark" = "Sample Remark" -"oldUsername" = "Current Username" -"currentPassword" = "Current Password" -"newUsername" = "New Username" -"newPassword" = "New Password" -"telegramBotEnable" = "Enable Telegram Bot" -"telegramBotEnableDesc" = "Enables the Telegram bot." -"telegramToken" = "Telegram Token" -"telegramTokenDesc" = "The Telegram bot token obtained from '@BotFather'." -"telegramProxy" = "SOCKS Proxy" -"telegramProxyDesc" = "Enables SOCKS5 proxy for connecting to Telegram. (adjust settings as per guide)" -"telegramAPIServer" = "Telegram API Server" -"telegramAPIServerDesc" = "The Telegram API server to use. Leave blank to use the default server." -"telegramChatId" = "Admin Chat ID" -"telegramChatIdDesc" = "The Telegram Admin Chat ID(s). (comma-separated)(get it here @userinfobot) or (use '/id' command in the bot)" -"telegramNotifyTime" = "Notification Time" -"telegramNotifyTimeDesc" = "The Telegram bot notification time set for periodic reports. (use the crontab time format)" -"tgNotifyBackup" = "Database Backup" -"tgNotifyBackupDesc" = "Send a database backup file with a report." -"tgNotifyLogin" = "Login Notification" -"tgNotifyLoginDesc" = "Get notified about the username, IP address, and time whenever someone attempts to log into your web panel." -"sessionMaxAge" = "Session Duration" -"sessionMaxAgeDesc" = "The duration for which you can stay logged in. (unit: minute)" -"expireTimeDiff" = "Expiration Date Notification" -"expireTimeDiffDesc" = "Get notified about expiration date when reaching this threshold. (unit: day)" -"trafficDiff" = "Traffic Cap Notification" -"trafficDiffDesc" = "Get notified about traffic cap when reaching this threshold. (unit: GB)" -"tgNotifyCpu" = "CPU Load Notification" -"tgNotifyCpuDesc" = "Get notified if CPU load exceeds this threshold. (unit: %)" -"timeZone" = "Time Zone" -"timeZoneDesc" = "Scheduled tasks will run based on this time zone." -"subSettings" = "Subscription" -"subEnable" = "Enable Subscription Service" -"subEnableDesc" = "Enables the subscription service." -"subListen" = "Listen IP" -"subListenDesc" = "The IP address for the subscription service. (leave blank to listen on all IPs)" -"subPort" = "Listen Port" -"subPortDesc" = "The port number for the subscription service. (must be an unused port)" -"subCertPath" = "Public Key Path" -"subCertPathDesc" = "The public key file path for the subscription service. (begins with ‘/‘)" -"subKeyPath" = "Private Key Path" -"subKeyPathDesc" = "The private key file path for the subscription service. (begins with ‘/‘)" -"subPath" = "URI Path" -"subPathDesc" = "The URI path for the subscription service. (begins with ‘/‘ and concludes with ‘/‘)" -"subDomain" = "Listen Domain" -"subDomainDesc" = "The domain name for the subscription service. (leave blank to listen on all domains and IPs)" -"subUpdates" = "Update Intervals" -"subUpdatesDesc" = "The update intervals of the subscription URL in the client apps. (unit: hour)" -"subEncrypt" = "Encode" -"subEncryptDesc" = "The returned content of subscription service will be Base64 encoded." -"subShowInfo" = "Show Usage Info" -"subShowInfoDesc" = "The remaining traffic and date will be displayed in the client apps." -"subURI" = "Reverse Proxy URI" -"subURIDesc" = "The URI path of the subscription URL for use behind proxies." -"fragment" = "Fragmentation" -"fragmentDesc" = "Enable fragmentation for TLS hello packet." -"fragmentSett" = "Fragmentation Settings" -"noisesDesc" = "Enable Noises." -"noisesSett" = "Noises Settings" -"mux" = "Mux" -"muxDesc" = "Transmit multiple independent data streams within an established data stream." -"muxSett" = "Mux Settings" -"direct" = "Direct Connection" -"directDesc" = "Directly establishes connections with domains or IP ranges of a specific country." - - -[pages.xray] -"title" = "Xray Configs" -"save" = "Save" -"restart" = "Restart Xray" -"basicTemplate" = "Basics" -"advancedTemplate" = "Advanced" -"generalConfigs" = "General" -"generalConfigsDesc" = "These options will determine general adjustments." -"logConfigs" = "Log" -"logConfigsDesc" = "Logs may affect your server's efficiency. It is recommended to enable it wisely only in case of your needs" -"blockConfigs" = "Protection Shield" -"blockConfigsDesc" = "These options will block traffic based on specific requested protocols and websites." -"basicRouting" = "Basic Routing" -"blockConnectionsConfigsDesc" = "These options will block traffic based on the specific requested country." -"directConnectionsConfigsDesc" = "A direct connection ensures that specific traffic is not routed through another server." -"blockips" = "Block IPs" -"blockdomains" = "Block Domains" -"directips" = "Direct IPs" -"directdomains" = "Direct Domains" -"ipv4Routing" = "IPv4 Routing" -"ipv4RoutingDesc" = "These options will route traffic based on a specific destination via IPv4." -"warpRouting" = "WARP Routing" -"warpRoutingDesc" = "These options will route traffic based on a specific destination via WARP." -"Template" = "Advanced Xray Configuration Template" -"TemplateDesc" = "The final Xray config file will be generated based on this template." -"FreedomStrategy" = "Freedom Protocol Strategy" -"FreedomStrategyDesc" = "Set the output strategy for the network in the Freedom Protocol." -"RoutingStrategy" = "Overall Routing Strategy" -"RoutingStrategyDesc" = "Set the overall traffic routing strategy for resolving all requests." -"Torrent" = "Block BitTorrent Protocol" -"TorrentDesc" = "Blocks BitTorrent protocol." -"Family" = "Family Protection" -"FamilyDesc" = "Blocks adult content, and malware websites." -"Inbounds" = "Inbounds" -"InboundsDesc" = "Accepting the specific clients." -"Outbounds" = "Outbounds" -"Balancers" = "Balancers" -"OutboundsDesc" = "Set the outgoing traffic pathway." -"Routings" = "Routing Rules" -"RoutingsDesc" = "The priority of each rule is important!" -"completeTemplate" = "All" -"logLevel" = "Log Level" -"logLevelDesc" = "The log level for error logs, indicating the information that needs to be recorded." -"accessLog" = "Access Log" -"accessLogDesc" = "The file path for the access log. The special value 'none' disabled access logs" -"errorLog" = "Error Log" -"errorLogDesc" = "The file path for the error log. The special value 'none' disabled error logs" -"dnsLog" = "DNS Log" -"dnsLogDesc" = "Whether to enable DNS query logs" -"outboundTraffic" = "Outbounds Traffic" -"outboundTrafficDesc" = "Whether to enable outbound traffic" -"maskAddress" = "Mask Address" -"maskAddressDesc" = "IP address mask, when enabled, will automatically replace the IP address that appears in the log." - -[pages.xray.rules] -"first" = "First" -"last" = "Last" -"up" = "Up" -"down" = "Down" -"source" = "Source" -"dest" = "Destination" -"inbound" = "Inbound" -"outbound" = "Outbound" -"balancer" = "Balancer" -"info" = "Info" -"add" = "Add Rule" -"edit" = "Edit Rule" -"useComma" = "Comma-separated items" - -[pages.xray.outbound] -"addOutbound" = "Add Outbound" -"addReverse" = "Add Reverse" -"editOutbound" = "Edit Outbound" -"editReverse" = "Edit Reverse" -"tag" = "Tag" -"tagDesc" = "Unique Tag" -"address" = "Address" -"reverse" = "Reverse" -"domain" = "Domain" -"type" = "Type" -"bridge" = "Bridge" -"portal" = "Portal" -"intercon" = "Interconnection" -"settings" = "Settings" -"accountInfo" = "Account Information" -"outboundStatus" = "Outbound Status" -"sendThrough" = "Send Through" - -[pages.xray.balancer] -"addBalancer" = "Add Balancer" -"editBalancer" = "Edit Balancer" -"balancerStrategy" = "Strategy" -"balancerSelectors" = "Selectors" -"tag" = "Tag" -"tagDesc" = "Unique Tag" -"balancerDesc" = "It is not possible to use balancerTag and outboundTag at the same time. If used at the same time, only outboundTag will work." - -[pages.xray.wireguard] -"secretKey" = "Secret Key" -"publicKey" = "Public Key" -"allowedIPs" = "Allowed IPs" -"endpoint" = "Endpoint" -"psk" = "PreShared Key" -"domainStrategy" = "Domain Strategy" - -[pages.xray.dns] -"enable" = "Enable DNS" -"enableDesc" = "Enable built-in DNS server" -"tag" = "DNS Inbound Tag" -"tagDesc" = "This tag will be available as an Inbound tag in routing rules." -"strategy" = "Query Strategy" -"strategyDesc" = "Overall strategy to resolve domain names" -"add" = "Add Server" -"edit" = "Edit Server" -"domains" = "Domains" -"expectIPs" = "Expect IPs" - -[pages.xray.fakedns] -"add" = "Add Fake DNS" -"edit" = "Edit Fake DNS" -"ipPool" = "IP Pool Subnet" -"poolSize" = "Pool Size" - -[pages.settings.security] -"admin" = "Admin" -"secret" = "Secret Token" -"loginSecurity" = "Secure Login" -"loginSecurityDesc" = "Adds an additional layer of authentication to provide more security." -"secretToken" = "Secret Token" -"secretTokenDesc" = "Please securely store this token in a safe place. This token is required for login and cannot be recovered." - -[pages.settings.toasts] -"modifySettings" = "Modify Settings" -"getSettings" = "Get Settings" -"modifyUser" = "Modify Admin" -"originalUserPassIncorrect" = "The Current username or password is invalid" -"userPassMustBeNotEmpty" = "The new username and password is empty" - -[tgbot] -"keyboardClosed" = "❌ Custom keyboard closed!" -"noResult" = "❗ No result!" -"noQuery" = "❌ Query not found! Please use the command again!" -"wentWrong" = "❌ Something went wrong!" -"noIpRecord" = "❗ No IP Record!" -"noInbounds" = "❗ No inbound found!" -"unlimited" = "♾ Unlimited(Reset)" -"add" = "Add" -"month" = "Month" -"months" = "Months" -"day" = "Day" -"days" = "Days" -"hours" = "Hours" -"unknown" = "Unknown" -"inbounds" = "Inbounds" -"clients" = "Clients" -"offline" = "🔴 Offline" -"online" = "🟢 Online" - -[tgbot.commands] -"unknown" = "❗ Unknown command." -"pleaseChoose" = "👇 Please choose:\r\n" -"help" = "🤖 Welcome to this bot! It's designed to offer specific data from the web panel and allows you to make modifications as needed.\r\n\r\n" -"start" = "👋 Hello {{ .Firstname }}.\r\n" -"welcome" = "🤖 Welcome to {{ .Hostname }} management bot.\r\n" -"status" = "✅ Bot is OK!" -"usage" = "❗ Please provide a text to search!" -"getID" = "🆔 Your ID: {{ .ID }}" -"helpAdminCommands" = "To restart Xray Core:\r\n/restart force\r\n\r\nTo search for a client email:\r\n/usage [Email]\r\n\r\nTo search for inbounds (with client stats):\r\n/inbound [Remark]\r\n\r\nTelegram Chat ID:\r\n/id" -"helpClientCommands" = "To search for statistics, use the following command:\r\n\r\n/usage [Email]\r\n\r\nTelegram Chat ID:\r\n/id" -"restartUsage" = "\r\n\r\n/restart force" -"restartSuccess" = "✅ Operation successful!" -"restartFailed" = "❗ Error in operation.\r\n\r\nError: {{ .Error }}." -"xrayNotRunning" = "❗ Xray Core is not running." - -[tgbot.messages] -"cpuThreshold" = "🔴 CPU Load {{ .Percent }}% exceeds the threshold of {{ .Threshold }}%" -"selectUserFailed" = "❌ Error in user selection!" -"userSaved" = "✅ Telegram User saved." -"loginSuccess" = "✅ Logged in to the panel successfully.\r\n" -"loginFailed" = "❗️Login attempt to the panel failed.\r\n" -"report" = "🕰 Scheduled Reports: {{ .RunTime }}\r\n" -"datetime" = "⏰ Date&Time: {{ .DateTime }}\r\n" -"hostname" = "💻 Host: {{ .Hostname }}\r\n" -"version" = "🚀 3X-UI Version: {{ .Version }}\r\n" -"xrayVersion" = "📡 Xray Version: {{ .XrayVersion }}\r\n" -"ipv6" = "🌐 IPv6: {{ .IPv6 }}\r\n" -"ipv4" = "🌐 IPv4: {{ .IPv4 }}\r\n" -"ip" = "🌐 IP: {{ .IP }}\r\n" -"ips" = "🔢 IPs:\r\n{{ .IPs }}\r\n" -"serverUpTime" = "⏳ Uptime: {{ .UpTime }} {{ .Unit }}\r\n" -"serverLoad" = "📈 System Load: {{ .Load1 }}, {{ .Load2 }}, {{ .Load3 }}\r\n" -"serverMemory" = "📋 RAM: {{ .Current }}/{{ .Total }}\r\n" -"tcpCount" = "🔹 TCP: {{ .Count }}\r\n" -"udpCount" = "🔸 UDP: {{ .Count }}\r\n" -"traffic" = "🚦 Traffic: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n" -"xrayStatus" = "ℹ️ Status: {{ .State }}\r\n" -"username" = "👤 Username: {{ .Username }}\r\n" -"password" = "👤 Password: {{ .Password }}\r\n" -"time" = "⏰ Time: {{ .Time }}\r\n" -"inbound" = "📍 Inbound: {{ .Remark }}\r\n" -"port" = "🔌 Port: {{ .Port }}\r\n" -"expire" = "📅 Expire Date: {{ .Time }}\r\n" -"expireIn" = "📅 Expire In: {{ .Time }}\r\n" -"active" = "💡 Active: {{ .Enable }}\r\n" -"enabled" = "🚨 Enabled: {{ .Enable }}\r\n" -"online" = "🌐 Connection status: {{ .Status }}\r\n" -"email" = "📧 Email: {{ .Email }}\r\n" -"upload" = "🔼 Upload: ↑{{ .Upload }}\r\n" -"download" = "🔽 Download: ↓{{ .Download }}\r\n" -"total" = "📊 Total: ↑↓{{ .UpDown }} / {{ .Total }}\r\n" -"TGUser" = "👤 Telegram User: {{ .TelegramID }}\r\n" -"exhaustedMsg" = "🚨 Exhausted {{ .Type }}:\r\n" -"exhaustedCount" = "🚨 Exhausted {{ .Type }} count:\r\n" -"onlinesCount" = "🌐 Online Clients: {{ .Count }}\r\n" -"disabled" = "🛑 Disabled: {{ .Disabled }}\r\n" -"depleteSoon" = "🔜 Deplete Soon: {{ .Deplete }}\r\n\r\n" -"backupTime" = "🗄 Backup Time: {{ .Time }}\r\n" -"refreshedOn" = "\r\n📋🔄 Refreshed On: {{ .Time }}\r\n\r\n" -"yes" = "✅ Yes" -"no" = "❌ No" - -[tgbot.buttons] -"closeKeyboard" = "❌ Close Keyboard" -"cancel" = "❌ Cancel" -"cancelReset" = "❌ Cancel Reset" -"cancelIpLimit" = "❌ Cancel IP Limit" -"confirmResetTraffic" = "✅ Confirm Reset Traffic?" -"confirmClearIps" = "✅ Confirm Clear IPs?" -"confirmRemoveTGUser" = "✅ Confirm Remove Telegram User?" -"confirmToggle" = "✅ Confirm Enable/Disable User?" -"dbBackup" = "Get DB Backup" -"serverUsage" = "Server Usage" -"getInbounds" = "Get Inbounds" -"depleteSoon" = "Deplete Soon" -"clientUsage" = "Get Usage" -"onlines" = "Online Clients" -"commands" = "Commands" -"refresh" = "🔄 Refresh" -"clearIPs" = "❌ Clear IPs" -"removeTGUser" = "❌ Remove Telegram User" -"selectTGUser" = "👤 Select Telegram User" -"selectOneTGUser" = "👤 Select a Telegram User:" -"resetTraffic" = "📈 Reset Traffic" -"resetExpire" = "📅 Change Expiry Date" -"ipLog" = "🔢 IP Log" -"ipLimit" = "🔢 IP Limit" -"setTGUser" = "👤 Set Telegram User" -"toggle" = "🔘 Enable / Disable" -"custom" = "🔢 Custom" -"confirmNumber" = "✅ Confirm: {{ .Num }}" -"confirmNumberAdd" = "✅ Confirm adding: {{ .Num }}" -"limitTraffic" = "🚧 Traffic Limit" -"getBanLogs" = "Get Ban Logs" -"allClients" = "All Clients" - -[tgbot.answers] -"successfulOperation" = "✅ Operation successful!" -"errorOperation" = "❗ Error in operation." -"getInboundsFailed" = "❌ Failed to get inbounds." -"getClientsFailed" = "❌ Failed to get clients." -"canceled" = "❌ {{ .Email }}: Operation canceled." -"clientRefreshSuccess" = "✅ {{ .Email }}: Client refreshed successfully." -"IpRefreshSuccess" = "✅ {{ .Email }}: IPs refreshed successfully." -"TGIdRefreshSuccess" = "✅ {{ .Email }}: Client's Telegram User refreshed successfully." -"resetTrafficSuccess" = "✅ {{ .Email }}: Traffic reset successfully." -"setTrafficLimitSuccess" = "✅ {{ .Email }}: Traffic limit saved successfully." -"expireResetSuccess" = "✅ {{ .Email }}: Expire days reset successfully." -"resetIpSuccess" = "✅ {{ .Email }}: IP limit {{ .Count }} saved successfully." -"clearIpSuccess" = "✅ {{ .Email }}: IPs cleared successfully." -"getIpLog" = "✅ {{ .Email }}: Get IP Log." -"getUserInfo" = "✅ {{ .Email }}: Get Telegram User Info." -"removedTGUserSuccess" = "✅ {{ .Email }}: Telegram User removed successfully." -"enableSuccess" = "✅ {{ .Email }}: Enabled successfully." -"disableSuccess" = "✅ {{ .Email }}: Disabled successfully." -"askToAddUserId" = "Your configuration is not found!\r\nPlease ask your admin to use your Telegram ChatID in your configuration(s).\r\n\r\nYour ChatID: {{ .TgUserID }}" -"chooseClient" = "Choose a Client for Inbound {{ .Inbound }}" -"chooseInbound" = "Choose an Inbound" diff --git a/web/translation/translate.es_ES.toml b/web/translation/translate.es_ES.toml deleted file mode 100644 index 62ae984e..00000000 --- a/web/translation/translate.es_ES.toml +++ /dev/null @@ -1,598 +0,0 @@ -"username" = "Nombre de Usuario" -"password" = "Contraseña" -"login" = "Acceder" -"confirm" = "Confirmar" -"cancel" = "Cancelar" -"close" = "Cerrar" -"copy" = "Copiar" -"copied" = "Copiado" -"download" = "Descargar" -"remark" = "Nota" -"enable" = "Habilitar" -"protocol" = "Protocolo" -"search" = "Buscar" -"filter" = "Filtrar" -"loading" = "Cargando..." -"second" = "Segundo" -"minute" = "Minuto" -"hour" = "Hora" -"day" = "Día" -"check" = "Verificar" -"indefinite" = "Indefinido" -"unlimited" = "Ilimitado" -"none" = "None" -"qrCode" = "Código QR" -"info" = "Más Información" -"edit" = "Editar" -"delete" = "Eliminar" -"reset" = "Restablecer" -"copySuccess" = "Copiado exitosamente" -"sure" = "Seguro" -"encryption" = "Encriptación" -"transmission" = "Transmisión" -"host" = "Anfitrión" -"path" = "Ruta" -"camouflage" = "Camuflaje" -"status" = "Estado" -"enabled" = "Habilitado" -"disabled" = "Deshabilitado" -"depleted" = "Agotado" -"depletingSoon" = "Agotándose" -"offline" = "fuera de línea" -"online" = "en línea" -"domainName" = "Nombre de dominio" -"monitor" = "Listening IP" -"certificate" = "Certificado Digital" -"fail" = "Falló" -"comment" = "Comentario" -"success" = "Éxito" -"getVersion" = "Obtener versión" -"install" = "Instalar" -"clients" = "Clientes" -"usage" = "Uso" -"secretToken" = "Token Secreto" -"remained" = "Restante" -"security" = "Seguridad" -"secAlertTitle" = "Alerta de Seguridad" -"secAlertSsl" = "Esta conexión no es segura. Por favor, evite ingresar información sensible hasta que se active TLS para la protección de datos." -"secAlertConf" = "Ciertas configuraciones son vulnerables a ataques. Se recomienda reforzar los protocolos de seguridad para prevenir posibles violaciones." -"secAlertSSL" = "El panel carece de una conexión segura. Por favor, instale un certificado TLS para la protección de datos." -"secAlertPanelPort" = "El puerto predeterminado del panel es vulnerable. Por favor, configure un puerto aleatorio o específico." -"secAlertPanelURI" = "La ruta URI predeterminada del panel no es segura. Por favor, configure una ruta URI compleja." -"secAlertSubURI" = "La ruta URI predeterminada de la suscripción no es segura. Por favor, configure una ruta URI compleja." -"secAlertSubJsonURI" = "La ruta URI JSON predeterminada de la suscripción no es segura. Por favor, configure una ruta URI compleja." - -[menu] -"dashboard" = "Estado del Sistema" -"inbounds" = "Entradas" -"settings" = "Configuraciones" -"xray" = "Ajustes Xray" -"logout" = "Cerrar Sesión" -"link" = "Gestionar" - -[pages.login] -"hello" = "Hola" -"title" = "Bienvenido" -"loginAgain" = "El límite de tiempo de inicio de sesión ha expirado. Por favor, inicia sesión nuevamente." - -[pages.login.toasts] -"invalidFormData" = "El formato de los datos de entrada es inválido." -"emptyUsername" = "Por favor ingresa el nombre de usuario." -"emptyPassword" = "Por favor ingresa la contraseña." -"wrongUsernameOrPassword" = "Nombre de usuario o contraseña inválidos." -"successLogin" = "Inicio de Sesión Exitoso" - -[pages.index] -"title" = "Estado del Sistema" -"memory" = "Memoria" -"hard" = "Disco Duro" -"xrayStatus" = "Xray" -"stopXray" = "Detener" -"restartXray" = "Reiniciar" -"xraySwitch" = "Versión" -"xraySwitchClick" = "Elige la versión a la que deseas cambiar." -"xraySwitchClickDesk" = "Elige sabiamente, ya que las versiones anteriores pueden no ser compatibles con las configuraciones actuales." -"operationHours" = "Tiempo de Funcionamiento" -"systemLoad" = "Carga del Sistema" -"systemLoadDesc" = "promedio de carga del sistema en los últimos 1, 5 y 15 minutos" -"connectionTcpCountDesc" = "Conexiones TCP totales en todas las tarjetas de red." -"connectionUdpCountDesc" = "Conexiones UDP totales en todas las tarjetas de red." -"connectionCount" = "Número de Conexiones" -"upSpeed" = "Velocidad de Subida Total para Todas las Tarjetas de Red." -"downSpeed" = "Velocidad de Bajada Total para Todas las Tarjetas de Red." -"totalSent" = "Tráfico Total de Subida de Todas las Tarjetas de Red desde el inicio del sistema." -"totalReceive" = "Datos Descargados Totales en Todas las Tarjetas de Red desde el inicio del sistema." -"xraySwitchVersionDialog" = "Cambiar Versión de Xray" -"xraySwitchVersionDialogDesc" = "¿Estás seguro de que deseas cambiar la versión de Xray a" -"dontRefresh" = "La instalación está en progreso, por favor no actualices esta página." -"logs" = "Registros" -"config" = "Configuración" -"backup" = "Copia de Seguridad y Restauración" -"backupTitle" = "Copia de Seguridad y Restauración de la Base de Datos" -"backupDescription" = "Recuerda hacer una copia de seguridad antes de importar una nueva base de datos." -"exportDatabase" = "Descargar Base de Datos" -"importDatabase" = "Cargar Base de Datos" - -[pages.inbounds] -"title" = "Entradas" -"totalDownUp" = "Subidas/Descargas Totales" -"totalUsage" = "Uso Total" -"inboundCount" = "Número de Entradas" -"operate" = "Menú" -"enable" = "Habilitar" -"remark" = "Notas" -"protocol" = "Protocolo" -"port" = "Puerto" -"traffic" = "Tráfico" -"details" = "Detalles" -"transportConfig" = "Transporte" -"expireDate" = "Fecha de Expiración" -"resetTraffic" = "Restablecer Tráfico" -"addInbound" = "Agregar Entrada" -"generalActions" = "Acciones Generales" -"create" = "Crear" -"update" = "Actualizar" -"modifyInbound" = "Modificar Entrada" -"deleteInbound" = "Eliminar Entrada" -"deleteInboundContent" = "¿Confirmar eliminación de entrada?" -"deleteClient" = "Eliminar cliente" -"deleteClientContent" = "¿Está seguro de que desea eliminar el cliente?" -"resetTrafficContent" = "¿Confirmar restablecimiento de tráfico?" -"copyLink" = "Copiar Enlace" -"address" = "Dirección" -"network" = "Red" -"destinationPort" = "Puerto de Destino" -"targetAddress" = "Dirección de Destino" -"monitorDesc" = "Dejar en blanco por defecto" -"meansNoLimit" = "= illimitata. (unidad: GB)" -"totalFlow" = "Flujo Total" -"leaveBlankToNeverExpire" = "Dejar en Blanco para Nunca Expirar" -"noRecommendKeepDefault" = "No hay requisitos especiales para mantener la configuración predeterminada" -"certificatePath" = "Ruta Cert" -"certificateContent" = "Datos Cert" -"publicKey" = "Clave Pública" -"privatekey" = "Clave Privada" -"clickOnQRcode" = "Haz clic en el Código QR para Copiar" -"client" = "Cliente" -"export" = "Exportar Enlaces" -"clone" = "Clonar" -"cloneInbound" = "Clonar Entradas" -"cloneInboundContent" = "Se aplicarán todas las configuraciones de esta entrada, excepto el Puerto, la IP de Escucha y los Clientes, al clon." -"cloneInboundOk" = "Clonar" -"resetAllTraffic" = "Restablecer Tráfico de Todas las Entradas" -"resetAllTrafficTitle" = "Restablecer tráfico de todas las entradas" -"resetAllTrafficContent" = "¿Estás seguro de que deseas restablecer el tráfico de todas las entradas?" -"resetInboundClientTraffics" = "Restablecer Tráfico de Clientes" -"resetInboundClientTrafficTitle" = "Restablecer todo el tráfico de clientes" -"resetInboundClientTrafficContent" = "¿Estás seguro de que deseas restablecer todo el tráfico para los clientes de esta entrada?" -"resetAllClientTraffics" = "Restablecer Tráfico de Todos los Clientes" -"resetAllClientTrafficTitle" = "Restablecer todo el tráfico de clientes" -"resetAllClientTrafficContent" = "¿Estás seguro de que deseas restablecer todo el tráfico para todos los clientes?" -"delDepletedClients" = "Eliminar Clientes Agotados" -"delDepletedClientsTitle" = "Eliminar clientes agotados" -"delDepletedClientsContent" = "¿Estás seguro de que deseas eliminar todos los clientes agotados?" -"email" = "Email" -"emailDesc" = "Por favor proporciona una dirección de correo electrónico única." -"IPLimit" = "Límite de IP" -"IPLimitDesc" = "Desactiva la entrada si la cantidad supera el valor ingresado (ingresa 0 para desactivar el límite de IP)." -"IPLimitlog" = "Registro de IP" -"IPLimitlogDesc" = "Registro de historial de IPs (antes de habilitar la entrada después de que haya sido desactivada por el límite de IP, debes borrar el registro)." -"IPLimitlogclear" = "Limpiar el Registro" -"setDefaultCert" = "Establecer certificado desde el panel" -"telegramDesc" = "Por favor, proporciona el ID de Chat de Telegram. (usa el comando '/id' en el bot) o (@userinfobot)" -"subscriptionDesc" = "Puedes encontrar tu enlace de suscripción en Detalles, también puedes usar el mismo nombre para varias configuraciones." -"info" = "Info" -"same" = "misma" -"inboundData" = "Datos de entrada" -"exportInbound" = "Exportación entrante" -"import" = "Importar" -"importInbound" = "Importar un entrante" - -[pages.client] -"add" = "Agregar Cliente" -"edit" = "Editar Cliente" -"submitAdd" = "Agregar Cliente" -"submitEdit" = "Guardar Cambios" -"clientCount" = "Número de Clientes" -"bulk" = "Agregar en Lote" -"method" = "Método" -"first" = "Primero" -"last" = "Último" -"prefix" = "Prefijo" -"postfix" = "Sufijo" -"delayedStart" = "Iniciar después del primer uso" -"expireDays" = "Duración" -"days" = "Día(s)" -"renew" = "Renovación automática" -"renewDesc" = "Renovación automática después de la expiración. (0 = desactivar) (unidad: día)" - -[pages.inbounds.toasts] -"obtain" = "Recibir" - -[pages.inbounds.stream.general] -"request" = "Pedido" -"response" = "Respuesta" -"name" = "Nombre" -"value" = "Valor" - -[pages.inbounds.stream.tcp] -"version" = "Versión" -"method" = "Método" -"path" = "Camino" -"status" = "Estado" -"statusDescription" = "Descripción de la Situación" -"requestHeader" = "Encabezado de solicitud" -"responseHeader" = "Encabezado de respuesta" - -[pages.settings] -"title" = "Configuraciones" -"save" = "Guardar" -"infoDesc" = "Cada cambio realizado aquí debe ser guardado. Por favor, reinicie el panel para aplicar los cambios." -"restartPanel" = "Reiniciar Panel" -"restartPanelDesc" = "¿Está seguro de que desea reiniciar el panel? Haga clic en Aceptar para reiniciar después de 3 segundos. Si no puede acceder al panel después de reiniciar, por favor, consulte la información de registro del panel en el servidor." -"actions" = "Acciones" -"resetDefaultConfig" = "Restablecer a Configuración Predeterminada" -"panelSettings" = "Configuraciones del Panel" -"securitySettings" = "Configuraciones de Seguridad" -"TGBotSettings" = "Configuraciones de Bot de Telegram" -"panelListeningIP" = "IP de Escucha del Panel" -"panelListeningIPDesc" = "Dejar en blanco por defecto para monitorear todas las IPs." -"panelListeningDomain" = "Dominio de Escucha del Panel" -"panelListeningDomainDesc" = "Dejar en blanco por defecto para monitorear todos los dominios e IPs." -"panelPort" = "Puerto del Panel" -"panelPortDesc" = "El puerto utilizado para mostrar este panel." -"publicKeyPath" = "Ruta del Archivo de Clave Pública del Certificado del Panel" -"publicKeyPathDesc" = "Complete con una ruta absoluta que comience con." -"privateKeyPath" = "Ruta del Archivo de Clave Privada del Certificado del Panel" -"privateKeyPathDesc" = "Complete con una ruta absoluta que comience con." -"panelUrlPath" = "Ruta Raíz de la URL del Panel" -"panelUrlPathDesc" = "Debe empezar con '/' y terminar con." -"pageSize" = "Tamaño de paginación" -"pageSizeDesc" = "Defina el tamaño de página para la tabla de entradas. Establezca 0 para desactivar" -"remarkModel" = "Modelo de observación y carácter de separación" -"datepicker" = "selector de fechas" -"datepickerPlaceholder" = "Seleccionar fecha" -"datepickerDescription" = "El tipo de calendario selector especifica la fecha de vencimiento" -"sampleRemark" = "Observación de muestra" -"oldUsername" = "Nombre de Usuario Actual" -"currentPassword" = "Contraseña Actual" -"newUsername" = "Nuevo Nombre de Usuario" -"newPassword" = "Nueva Contraseña" -"telegramBotEnable" = "Habilitar bot de Telegram" -"telegramBotEnableDesc" = "Conéctese a las funciones de este panel a través del bot de Telegram." -"telegramToken" = "Token de Telegram" -"telegramTokenDesc" = "Debe obtener el token del administrador de bots de Telegram @botfather." -"telegramProxy" = "Socks5 Proxy" -"telegramProxyDesc" = "Si necesita el proxy Socks5 para conectarse a Telegram. Ajuste su configuración según la guía." -"telegramAPIServer" = "API Server de Telegram" -"telegramAPIServerDesc" = "El servidor API de Telegram a utilizar. Déjelo en blanco para utilizar el servidor predeterminado." -"telegramChatId" = "IDs de Chat de Telegram para Administradores" -"telegramChatIdDesc" = "IDs de Chat múltiples separados por comas. Use @userinfobot o use el comando '/id' en el bot para obtener sus IDs de Chat." -"telegramNotifyTime" = "Hora de Notificación del Bot de Telegram" -"telegramNotifyTimeDesc" = "Usar el formato de tiempo de Crontab." -"tgNotifyBackup" = "Respaldo de Base de Datos" -"tgNotifyBackupDesc" = "Incluir archivo de respaldo de base de datos con notificación de informe." -"tgNotifyLogin" = "Notificación de Inicio de Sesión" -"tgNotifyLoginDesc" = "Muestra el nombre de usuario, dirección IP y hora cuando alguien intenta iniciar sesión en su panel." -"sessionMaxAge" = "Edad Máxima de Sesión" -"sessionMaxAgeDesc" = "La duración de una sesión de inicio de sesión (unidad: minutos)." -"expireTimeDiff" = "Umbral de Expiración para Notificación" -"expireTimeDiffDesc" = "Reciba notificaciones sobre la expiración de la cuenta antes del umbral (unidad: días)." -"trafficDiff" = "Umbral de Tráfico para Notificación" -"trafficDiffDesc" = "Reciba notificaciones sobre el agotamiento del tráfico antes de alcanzar el umbral (unidad: GB)." -"tgNotifyCpu" = "Umbral de Alerta de Porcentaje de CPU" -"tgNotifyCpuDesc" = "Reciba notificaciones si el uso de la CPU supera este umbral (unidad: %)." -"timeZone" = "Zona Horaria" -"timeZoneDesc" = "Las tareas programadas se ejecutan de acuerdo con la hora en esta zona horaria." -"subSettings" = "Suscripción" -"subEnable" = "Habilitar Servicio" -"subEnableDesc" = "Función de suscripción con configuración separada." -"subListen" = "Listening IP" -"subListenDesc" = "Dejar en blanco por defecto para monitorear todas las IPs." -"subPort" = "Puerto de Suscripción" -"subPortDesc" = "El número de puerto para el servicio de suscripción debe estar sin usar en el servidor." -"subCertPath" = "Ruta del Archivo de Clave Pública del Certificado de Suscripción" -"subCertPathDesc" = "Complete con una ruta absoluta que comience con '/'" -"subKeyPath" = "Ruta del Archivo de Clave Privada del Certificado de Suscripción" -"subKeyPathDesc" = "Complete con una ruta absoluta que comience con '/'" -"subPath" = "Ruta Raíz de la URL de Suscripción" -"subPathDesc" = "Debe empezar con '/' y terminar con '/'" -"subDomain" = "Dominio de Escucha" -"subDomainDesc" = "Dejar en blanco por defecto para monitorear todos los dominios e IPs." -"subUpdates" = "Intervalos de Actualización de Suscripción" -"subUpdatesDesc" = "Horas de intervalo entre actualizaciones en la aplicación del cliente." -"subEncrypt" = "Encriptar configuraciones" -"subEncryptDesc" = "Encriptar las configuraciones devueltas en la suscripción." -"subShowInfo" = "Mostrar información de uso" -"subShowInfoDesc" = "Mostrar tráfico restante y fecha después del nombre de configuración." -"subURI" = "URI de proxy inverso" -"subURIDesc" = "Cambiar el URI base de la URL de suscripción para usar detrás de los servidores proxy" -"fragment" = "Fragmentación" -"fragmentDesc" = "Habilitar la fragmentación para el paquete de saludo de TLS" -"fragmentSett" = "Configuración de Fragmentación" -"noisesDesc" = "Activar Noises." -"noisesSett" = "Configuración de Noises" -"mux" = "Mux" -"muxDesc" = "Transmite múltiples flujos de datos independientes dentro de un flujo de datos establecido." -"muxSett" = "Configuración Mux" -"direct" = "Conexión Directa" -"directDesc" = "Establece conexiones directas con dominios o rangos de IP de un país específico." - - -[pages.xray] -"title" = "Xray Configuración" -"save" = "Guardar configuración" -"restart" = "Reiniciar Xray" -"basicTemplate" = "Plantilla Básica" -"advancedTemplate" = "Plantilla Avanzada" -"generalConfigs" = "Configuraciones Generales" -"generalConfigsDesc" = "Estas opciones proporcionarán ajustes generales." -"logConfigs" = "Registro" -"logConfigsDesc" = "Los registros pueden afectar la eficiencia de su servidor. Se recomienda habilitarlos sabiamente solo en caso de sus necesidades." -"blockConfigs" = "Configuraciones de Bloqueo" -"blockConfigsDesc" = "Estas opciones evitarán que los usuarios se conecten a protocolos y sitios web específicos." -"basicRouting" = "Enrutamiento Básico" -"blockConnectionsConfigsDesc" = "Estas opciones bloquearán el tráfico según el país solicitado específico." -"directConnectionsConfigsDesc" = "Una conexión directa asegura que el tráfico específico no sea enrutado a través de otro servidor." -"blockips" = "Bloquear IPs" -"blockdomains" = "Bloquear Dominios" -"directips" = "IPs Directas" -"directdomains" = "Dominios Directos" -"ipv4Routing" = "Enrutamiento IPv4" -"ipv4RoutingDesc" = "Estas opciones solo enrutarán a los dominios objetivo a través de IPv4." -"warpRouting" = "Enrutamiento WARP" -"warpRoutingDesc" = "Precaución: Antes de usar estas opciones, instale WARP en modo de proxy socks5 en su servidor siguiendo los pasos en el GitHub del panel. WARP enrutará el tráfico a los sitios web a través de los servidores de Cloudflare." -"Template" = "Plantilla de Configuración de Xray" -"TemplateDesc" = "Genera el archivo de configuración final de Xray basado en esta plantilla." -"FreedomStrategy" = "Configurar Estrategia para el Protocolo Freedom" -"FreedomStrategyDesc" = "Establece la estrategia de salida de la red en el Protocolo Freedom." -"RoutingStrategy" = "Configurar Estrategia de Enrutamiento de Dominios" -"RoutingStrategyDesc" = "Establece la estrategia general de enrutamiento para la resolución de DNS." -"Torrent" = "Prohibir Uso de BitTorrent" -"TorrentDesc" = "Cambia la plantilla de configuración para evitar el uso de BitTorrent por parte de los usuarios." -"Family" = "Bloquee malware y contenido para adultos" -"FamilyDesc" = "Resolutores de DNS de Cloudflare para bloquear malware y contenido para adultos para protección familiar." -"Inbounds" = "Entrante" -"InboundsDesc" = "Cambia la plantilla de configuración para aceptar clientes específicos." -"Outbounds" = "Salidas" -"Balancers" = "Equilibradores" -"OutboundsDesc" = "Cambia la plantilla de configuración para definir formas de salida para este servidor." -"Routings" = "Reglas de enrutamiento" -"RoutingsDesc" = "¡La prioridad de cada regla es importante!" -"completeTemplate" = "Todos" -"logLevel" = "Nivel de registro" -"logLevelDesc" = "El nivel de registro para registros de errores, que indica la información que debe registrarse." -"accessLog" = "Registro de acceso" -"accessLogDesc" = "La ruta del archivo para el registro de acceso. El valor especial 'ninguno' deshabilita los registros de acceso" -"errorLog" = "Registro de Errores" -"errorLogDesc" = "La ruta del archivo para el registro de errores. El valor especial 'none' desactiva los registros de errores." -"dnsLog" = "Registro DNS" -"dnsLogDesc" = "Si habilitar los registros de consulta DNS" -"outboundTraffic" = "Tráfico saliente" -"outboundTrafficDesc" = "Si se debe habilitar el tráfico saliente" -"maskAddress" = "Enmascarar Dirección" -"maskAddressDesc" = "Máscara de dirección IP, cuando se habilita, reemplazará automáticamente la dirección IP que aparece en el registro." - -[pages.xray.rules] -"first" = "Primero" -"last" = "Último" -"up" = "Arriba" -"down" = "Abajo" -"source" = "Fuente" -"dest" = "Destino" -"inbound" = "Entrante" -"outbound" = "Saliente" -"balancer" = "Equilibrador" -"info" = "Información" -"add" = "Agregar Regla" -"edit" = "Editar Regla" -"useComma" = "Elementos separados por comas" - -[pages.xray.outbound] -"addOutbound" = "Agregar salida" -"addReverse" = "Agregar reverso" -"editOutbound" = "Editar salida" -"editReverse" = "Editar reverso" -"tag" = "Etiqueta" -"tagDesc" = "etiqueta única" -"address" = "Dirección" -"reverse" = "Reverso" -"domain" = "Dominio" -"type" = "Tipo" -"bridge" = "puente" -"portal" = "portal" -"intercon" = "Interconexión" -"settings" = "Configuración" -"accountInfo" = "Información de la Cuenta" -"outboundStatus" = "Estado de Salida" -"sendThrough" = "Enviar a través de" - -[pages.xray.balancer] -"addBalancer" = "Agregar equilibrador" -"editBalancer" = "Editar balanceador" -"balancerStrategy" = "Estrategia" -"balancerSelectors" = "Selectores" -"tag" = "Etiqueta" -"tagDesc" = "etiqueta única" -"balancerDesc" = "No es posible utilizar balancerTag y outboundTag al mismo tiempo. Si se utilizan al mismo tiempo, sólo funcionará outboundTag." - -[pages.xray.wireguard] -"secretKey" = "Llave secreta" -"publicKey" = "Llave pública" -"allowedIPs" = "IP permitidas" -"endpoint" = "Punto final" -"psk" = "Clave precompartida" -"domainStrategy" = "Estrategia de dominio" - -[pages.xray.dns] -"enable" = "Habilitar DNS" -"enableDesc" = "Habilitar servidor DNS incorporado" -"tag" = "Etiqueta de Entrada DNS" -"tagDesc" = "Esta etiqueta estará disponible como una etiqueta de entrada en las reglas de enrutamiento." -"strategy" = "Estrategia de Consulta" -"strategyDesc" = "Estrategia general para resolver nombres de dominio" -"add" = "Agregar Servidor" -"edit" = "Editar Servidor" -"domains" = "Dominios" -"expectIPs" = "IPs esperadas" - -[pages.xray.fakedns] -"add" = "Agregar DNS Falso" -"edit" = "Editar DNS Falso" -"ipPool" = "Subred del grupo de IP" -"poolSize" = "Tamaño del grupo" - -[pages.settings.security] -"admin" = "Administrador" -"secret" = "Token Secreto" -"loginSecurity" = "Seguridad de Inicio de Sesión" -"loginSecurityDesc" = "Habilitar un paso adicional de seguridad para el inicio de sesión de usuarios." -"secretToken" = "Token Secreto" -"secretTokenDesc" = "Por favor, copia y guarda este token de forma segura en un lugar seguro. Este token es necesario para iniciar sesión y no se puede recuperar con la herramienta de comando x-ui." - -[pages.settings.toasts] -"modifySettings" = "Modificar Configuraciones " -"getSettings" = "Obtener Configuraciones " -"modifyUser" = "Modificar Usuario " -"originalUserPassIncorrect" = "Nombre de usuario o contraseña original incorrectos" -"userPassMustBeNotEmpty" = "El nuevo nombre de usuario y la nueva contraseña no pueden estar vacíos" - -[tgbot] -"keyboardClosed" = "❌ ¡Teclado personalizado cerrado!" -"noResult" = "❗ ¡Sin resultados!" -"noQuery" = "❌ ¡Consulta no encontrada! ¡Por favor utiliza el comando nuevamente!" -"wentWrong" = "❌ ¡Algo salió mal!" -"noIpRecord" = "❗ ¡Sin Registro de IP!" -"noInbounds" = "❗ ¡No se encontraron entradas!" -"unlimited" = "♾ Ilimitado" -"add" = "Agregar" -"month" = "Mes" -"months" = "Meses" -"day" = "Día" -"days" = "Días" -"hours" = "Horas" -"unknown" = "Desconocido" -"inbounds" = "Entradas" -"clients" = "Clientes" -"offline" = "🔴 Sin conexión" -"online" = "🟢 En línea" - -[tgbot.commands] -"unknown" = "❗ Comando desconocido" -"pleaseChoose" = "👇 Por favor elige:\r\n" -"help" = "🤖 ¡Bienvenido a este bot! Está diseñado para ofrecerte datos específicos del servidor y te permite hacer modificaciones según sea necesario.\r\n\r\n" -"start" = "👋 Hola {{ .Firstname }}.\r\n" -"welcome" = "🤖 Bienvenido al bot de gestión de {{ .Hostname }}.\r\n" -"status" = "✅ ¡El bot está bien!" -"usage" = "❗ ¡Por favor proporciona un texto para buscar!" -"getID" = "🆔 Tu ID: {{ .ID }}" -"helpAdminCommands" = "Para reiniciar Xray Core:\r\n/restart force\r\n\r\nPara buscar un correo electrónico de cliente:\r\n/usage [Correo electrónico]\r\n\r\nPara buscar entradas (con estadísticas de cliente):\r\n/inbound [Observación]\r\n\r\nID de Chat de Telegram:\r\n/id" -"helpClientCommands" = "Para buscar estadísticas, utiliza el siguiente comando:\r\n/usage [Correo electrónico]\r\n\r\nID de Chat de Telegram:\r\n/id" -"restartUsage" = "\r\n\r\n/restart force" -"restartSuccess" = "✅ ¡Operación exitosa!" -"restartFailed" = "❗ Error en la operación.\r\n\r\nError: {{ .Error }}." -"xrayNotRunning" = "❗ Xray Core no está en ejecución." - -[tgbot.messages] -"cpuThreshold" = "🔴 El uso de CPU {{ .Percent }}% es mayor que el umbral {{ .Threshold }}%" -"selectUserFailed" = "❌ ¡Error al seleccionar usuario!" -"userSaved" = "✅ Usuario de Telegram guardado." -"loginSuccess" = "✅ Has iniciado sesión en el panel con éxito.\r\n" -"loginFailed" = "❗️ Falló el inicio de sesión en el panel.\r\n" -"report" = "🕰 Informes programados: {{ .RunTime }}\r\n" -"datetime" = "⏰ Fecha y Hora: {{ .DateTime }}\r\n" -"hostname" = "💻 Nombre del Host: {{ .Hostname }}\r\n" -"version" = "🚀 Versión de X-UI: {{ .Version }}\r\n" -"xrayVersion" = "📡 Versión de Xray: {{ .XrayVersion }}\r\n" -"ipv6" = "🌐 IPv6: {{ .IPv6 }}\r\n" -"ipv4" = "🌐 IPv4: {{ .IPv4 }}\r\n" -"ip" = "🌐 IP: {{ .IP }}\r\n" -"ips" = "🔢 IPs:\r\n{{ .IPs }}\r\n" -"serverUpTime" = "⏳ Tiempo de actividad del servidor: {{ .UpTime }} {{ .Unit }}\r\n" -"serverLoad" = "📈 Carga del servidor: {{ .Load1 }}, {{ .Load2 }}, {{ .Load3 }}\r\n" -"serverMemory" = "📋 Memoria del servidor: {{ .Current }}/{{ .Total }}\r\n" -"tcpCount" = "🔹 Conteo de TCP: {{ .Count }}\r\n" -"udpCount" = "🔸 Conteo de UDP: {{ .Count }}\r\n" -"traffic" = "🚦 Tráfico: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n" -"xrayStatus" = "ℹ️ Estado de Xray: {{ .State }}\r\n" -"username" = "👤 Nombre de usuario: {{ .Username }}\r\n" -"password" = "👤 Contraseña: {{ .Password }}\r\n" -"time" = "⏰ Hora: {{ .Time }}\r\n" -"inbound" = "📍 Inbound: {{ .Remark }}\r\n" -"port" = "🔌 Puerto: {{ .Port }}\r\n" -"expire" = "📅 Fecha de Vencimiento: {{ .Time }}\r\n" -"expireIn" = "📅 Vence en: {{ .Time }}\r\n" -"active" = "💡 Activo: {{ .Enable }}\r\n" -"enabled" = "🚨 Habilitado: {{ .Enable }}\r\n" -"online" = "🌐 Estado de conexión: {{ .Status }}\r\n" -"email" = "📧 Email: {{ .Email }}\r\n" -"upload" = "🔼 Subida: ↑{{ .Upload }}\r\n" -"download" = "🔽 Bajada: ↓{{ .Download }}\r\n" -"total" = "📊 Total: ↑↓{{ .UpDown }} / {{ .Total }}\r\n" -"TGUser" = "👤 Usuario de Telegram: {{ .TelegramID }}\r\n" -"exhaustedMsg" = "🚨 Agotado {{ .Type }}:\r\n" -"exhaustedCount" = "🚨 Cantidad de Agotados {{ .Type }}:\r\n" -"onlinesCount" = "🌐 Clientes en línea: {{ .Count }}\r\n" -"disabled" = "🛑 Desactivado: {{ .Disabled }}\r\n" -"depleteSoon" = "🔜 Se agotará pronto: {{ .Deplete }}\r\n\r\n" -"backupTime" = "🗄 Hora de la Copia de Seguridad: {{ .Time }}\r\n" -"refreshedOn" = "\r\n📋🔄 Actualizado en: {{ .Time }}\r\n\r\n" -"yes" = "✅ Sí" -"no" = "❌ No" - -[tgbot.buttons] -"closeKeyboard" = "❌ Cerrar Teclado" -"cancel" = "❌ Cancelar" -"cancelReset" = "❌ Cancelar Reinicio" -"cancelIpLimit" = "❌ Cancelar Límite de IP" -"confirmResetTraffic" = "✅ ¿Confirmar Reinicio de Tráfico?" -"confirmClearIps" = "✅ ¿Confirmar Limpiar IPs?" -"confirmRemoveTGUser" = "✅ ¿Confirmar Eliminar Usuario de Telegram?" -"confirmToggle" = "✅ ¿Confirmar habilitar/deshabilitar usuario?" -"dbBackup" = "Obtener Copia de Seguridad de BD" -"serverUsage" = "Uso del Servidor" -"getInbounds" = "Obtener Entradas" -"depleteSoon" = "Pronto se Agotará" -"clientUsage" = "Obtener Uso" -"onlines" = "Clientes en línea" -"commands" = "Comandos" -"refresh" = "🔄 Actualizar" -"clearIPs" = "❌ Limpiar IPs" -"removeTGUser" = "❌ Eliminar Usuario de Telegram" -"selectTGUser" = "👤 Seleccionar Usuario de Telegram" -"selectOneTGUser" = "👤 Selecciona un usuario de telegram:" -"resetTraffic" = "📈 Reiniciar Tráfico" -"resetExpire" = "📅 Cambiar fecha de Vencimiento" -"ipLog" = "🔢 Registro de IP" -"ipLimit" = "🔢 Límite de IP" -"setTGUser" = "👤 Establecer Usuario de Telegram" -"toggle" = "🔘 Habilitar / Deshabilitar" -"custom" = "🔢 Costumbre" -"confirmNumber" = "✅ Confirmar: {{ .Num }}" -"confirmNumberAdd" = "✅ Confirmar agregando: {{ .Num }}" -"limitTraffic" = "🚧 Límite de tráfico" -"getBanLogs" = "Registros de prohibición" -"allClients" = "Todos los Clientes" - -[tgbot.answers] -"successfulOperation" = "✅ ¡Exitosa!" -"errorOperation" = "❗ Error en la Operación." -"getInboundsFailed" = "❌ Error al obtener las entradas" -"getClientsFailed" = "❌ No se pudo obtener los clientes." -"canceled" = "❌ {{ .Email }} : Operación cancelada." -"clientRefreshSuccess" = "✅ {{ .Email }} : Cliente actualizado exitosamente." -"IpRefreshSuccess" = "✅ {{ .Email }} : IPs actualizadas exitosamente." -"TGIdRefreshSuccess" = "✅ {{ .Email }} : Usuario de Telegram del cliente actualizado exitosamente." -"resetTrafficSuccess" = "✅ {{ .Email }} : Tráfico reiniciado exitosamente." -"setTrafficLimitSuccess" = "✅ {{ .Email }} : Límite de Tráfico guardado exitosamente." -"expireResetSuccess" = "✅ {{ .Email }} : Días de vencimiento reiniciados exitosamente." -"resetIpSuccess" = "✅ {{ .Email }} : Límite de IP {{ .Count }} guardado exitosamente." -"clearIpSuccess" = "✅ {{ .Email }} : IPs limpiadas exitosamente." -"getIpLog" = "✅ {{ .Email }} : Obtener Registro de IP." -"getUserInfo" = "✅ {{ .Email }} : Obtener Información de Usuario de Telegram." -"removedTGUserSuccess" = "✅ {{ .Email }} : Usuario de Telegram eliminado exitosamente." -"enableSuccess" = "✅ {{ .Email }} : Habilitado exitosamente." -"disableSuccess" = "✅ {{ .Email }} : Deshabilitado exitosamente." -"askToAddUserId" = "¡No se encuentra su configuración!\r\nPor favor, pídale a su administrador que use su ChatID de usuario de Telegram en su(s) configuración(es).\r\n\r\nSu ChatID de usuario: {{ .TgUserID }}" -"chooseClient" = "Elige un Cliente para Inbound {{ .Inbound }}" -"chooseInbound" = "Elige un Inbound" diff --git a/web/translation/translate.fa_IR.toml b/web/translation/translate.fa_IR.toml deleted file mode 100644 index 6219996d..00000000 --- a/web/translation/translate.fa_IR.toml +++ /dev/null @@ -1,598 +0,0 @@ -"username" = "نام‌کاربری" -"password" = "رمزعبور" -"login" = "ورود" -"confirm" = "تایید" -"cancel" = "انصراف" -"close" = "بستن" -"copy" = "کپی" -"copied" = "کپی شد" -"download" = "دانلود" -"remark" = "نام" -"enable" = "فعال" -"protocol" = "پروتکل" -"search" = "جستجو" -"filter" = "فیلتر" -"loading" = "...در حال بارگذاری" -"second" = "ثانیه" -"minute" = "دقیقه" -"hour" = "ساعت" -"day" = "روز" -"check" = "چک کردن" -"indefinite" = "نامحدود" -"unlimited" = "نامحدود" -"none" = "هیچ" -"qrCode" = "QRکد" -"info" = "اطلاعات بیشتر" -"edit" = "ویرایش" -"delete" = "حذف" -"reset" = "ریست" -"copySuccess" = "باموفقیت کپی‌شد" -"sure" = "مطمئن" -"encryption" = "رمزگذاری" -"transmission" = "راه‌اتصال" -"host" = "آدرس" -"path" = "مسیر" -"camouflage" = "مبهم‌سازی" -"status" = "وضعیت" -"enabled" = "فعال" -"disabled" = "غیرفعال" -"depleted" = "منقضی" -"depletingSoon" = "در‌حال‌انقضا" -"offline" = "آفلاین" -"online" = "آنلاین" -"domainName" = "آدرس دامنه" -"monitor" = "آی‌پی اتصال" -"certificate" = "گواهی دیجیتال" -"fail" = "ناموفق" -"comment" = "توضیحات" -"success" = "موفق" -"getVersion" = "دریافت نسخه" -"install" = "نصب" -"clients" = "کاربران" -"usage" = "استفاده" -"secretToken" = "توکن امنیتی" -"remained" = "باقی‌مانده" -"security" = "امنیت" -"secAlertTitle" = "هشدار‌امنیتی" -"secAlertSsl" = "این‌اتصال‌امن نیست. لطفا‌ تازمانی‌که تی‌ال‌اس برای محافظت از‌ داده‌ها فعال نشده‌است، از وارد کردن اطلاعات حساس خودداری کنید" -"secAlertConf" = "تنظیمات خاصی در برابر حملات آسیب پذیر هستند. توصیه می‌شود پروتکل‌های امنیتی را برای جلوگیری از نفوذ احتمالی تقویت کنید" -"secAlertSSL" = "پنل فاقد ارتباط امن است. لطفاً یک گواهینامه تی‌ال‌اس برای محافظت از داده‌ها نصب کنید" -"secAlertPanelPort" = "استفاده از پورت پیش‌فرض پنل ناامن است. لطفاً یک پورت تصادفی یا خاص تنظیم کنید" -"secAlertPanelURI" = "مسیر پیش‌فرض لینک پنل ناامن است. لطفاً یک مسیر پیچیده تنظیم کنید" -"secAlertSubURI" = "مسیر پیش‌فرض لینک سابسکریپشن ناامن است. لطفاً یک مسیر پیچیده تنظیم کنید" -"secAlertSubJsonURI" = "مسیر پیش‌فرض لینک سابسکریپشن جیسون ناامن است. لطفاً یک مسیر پیچیده تنظیم کنید" - -[menu] -"dashboard" = "نمای کلی" -"inbounds" = "ورودی‌ها" -"settings" = "تنظیمات پنل" -"xray" = "پیکربندی ایکس‌ری" -"logout" = "خروج" -"link" = "مدیریت" - -[pages.login] -"hello" = "سلام" -"title" = "خوش‌آمدید" -"loginAgain" = "مدت زمان استفاده به‌اتمام‌رسیده، لطفا دوباره وارد شوید" - -[pages.login.toasts] -"invalidFormData" = "اطلاعات به‌درستی وارد نشده‌است" -"emptyUsername" = "لطفا یک نام‌کاربری وارد کنید‌" -"emptyPassword" = "لطفا یک رمزعبور وارد کنید" -"wrongUsernameOrPassword" = "نام‌کاربری یا رمزعبور‌اشتباه‌است" -"successLogin" = "ورود" - -[pages.index] -"title" = "نمای کلی" -"memory" = "RAM" -"hard" = "Disk" -"xrayStatus" = "ایکس‌ری" -"stopXray" = "توقف" -"restartXray" = "شروع‌مجدد" -"xraySwitch" = "‌نسخه" -"xraySwitchClick" = "نسخه مورد نظر را انتخاب کنید" -"xraySwitchClickDesk" = "لطفا بادقت انتخاب کنید. درصورت انتخاب نسخه قدیمی‌تر، امکان ناهماهنگی با پیکربندی فعلی وجود دارد" -"operationHours" = "مدت‌کارکرد" -"systemLoad" = "بارسیستم" -"systemLoadDesc" = "میانگین بار سیستم برای 1، 5 و 15 دقیقه گذشته" -"connectionTcpCountDesc" = "در تمام‌شبکه‌ها TCP مجموع‌اتصالات" -"connectionUdpCountDesc" = "در تمام‌شبکه‌ها UDP مجموع‌اتصالات" -"connectionCount" = "تعداد کانکشن ها" -"upSpeed" = "سرعت کلی آپلود در تمام‌شبکه‌ها" -"downSpeed" = "‌سرعت کلی دانلود در تمام‌شبکه‌ها" -"totalSent" = "مجموع ترافیک ارسال‌‌شده پس‌از شروع‌به‌کار سیستم‌عامل" -"totalReceive" = "مجموع ترافیک دریافت‌شده پس‌از شروع‌به‌کار سیستم‌عامل" -"xraySwitchVersionDialog" = "تغییر نسخه ایکس‌ری" -"xraySwitchVersionDialogDesc" = "آیا از تغییر نسخه‌ مطمئن هستید؟" -"dontRefresh" = "در حال نصب، لطفا صفحه را رفرش نکنید" -"logs" = "گزارش‌ها" -"config" = "پیکربندی" -"backup" = "پشتیبان‌گیری" -"backupTitle" = "پشتیبان‌گیری دیتابیس" -"backupDescription" = "توصیه‌می‌شود قبل‌از واردکردن یک دیتابیس جدید، نسخه پشتیبان تهیه ‌کنید" -"exportDatabase" = "پشتیبان‌گیری" -"importDatabase" = "بازگرداندن" - -[pages.inbounds] -"title" = "کاربران" -"totalDownUp" = "دریافت/ارسال کل" -"totalUsage" = "‌‌‌مصرف کل" -"inboundCount" = "کل ورودی‌ها" -"operate" = "عملیات" -"enable" = "فعال" -"remark" = "نام" -"protocol" = "پروتکل" -"port" = "پورت" -"traffic" = "ترافیک" -"details" = "توضیحات" -"transportConfig" = "نحوه اتصال" -"expireDate" = "مدت زمان" -"resetTraffic" = "ریست ترافیک" -"addInbound" = "افزودن ورودی" -"generalActions" = "عملیات کلی" -"create" = "افزودن" -"update" = "ویرایش" -"modifyInbound" = "ویرایش ورودی" -"deleteInbound" = "حذف ورودی" -"deleteInboundContent" = "آیا مطمئن به حذف ورودی هستید؟" -"deleteClient" = "حذف کاربر" -"deleteClientContent" = "آیا مطمئن به حذف کاربر هستید؟" -"resetTrafficContent" = "آیا مطمئن به ریست ترافیک هستید؟" -"copyLink" = "کپی لینک" -"address" = "آدرس" -"network" = "شبکه" -"destinationPort" = "پورت مقصد" -"targetAddress" = "آدرس مقصد" -"monitorDesc" = "به‌طور پیش‌فرض خالی‌بگذارید" -"meansNoLimit" = "0 = واحد: گیگابایت) نامحدود)" -"totalFlow" = "ترافیک کل" -"leaveBlankToNeverExpire" = "برای منقضی‌نشدن خالی‌بگذارید" -"noRecommendKeepDefault" = "توصیه‌می‌شود به‌طور پیش‌فرض حفظ‌شود" -"certificatePath" = "مسیر فایل" -"certificateContent" = "محتوای فایل" -"publicKey" = "کلید عمومی" -"privatekey" = "کلید خصوصی" -"clickOnQRcode" = "برای کپی بر روی کدتصویری کلیک کنید" -"client" = "کاربر" -"export" = "استخراج لینک‌ها" -"clone" = "شبیه‌سازی" -"cloneInbound" = "شبیه‌سازی ورودی" -"cloneInboundContent" = "همه موارد این ورودی بجز پورت، آی‌پی و کاربر‌ها شبیه‌سازی خواهند شد" -"cloneInboundOk" = "ساختن شبیه ساز" -"resetAllTraffic" = "ریست ترافیک کل ورودی‌ها" -"resetAllTrafficTitle" = "ریست ترافیک کل ورودی‌ها" -"resetAllTrafficContent" = "آیا مطمئن به ریست ترافیک تمام ورودی‌ها هستید؟" -"resetInboundClientTraffics" = "ریست ترافیک کاربران" -"resetInboundClientTrafficTitle" = "ریست ترافیک کاربران" -"resetInboundClientTrafficContent" = "آیا مطمئن به ریست ترافیک تمام کاربران این‌ ورودی هستید؟" -"resetAllClientTraffics" = "ریست ترافیک کل کاربران" -"resetAllClientTrafficTitle" = "ریست ترافیک کل کاربران" -"resetAllClientTrafficContent" = "آیا مطمئن به ریست ترافیک تمام کاربران هستید؟" -"delDepletedClients" = "حذف کاربران منقضی" -"delDepletedClientsTitle" = "حذف کاربران منقضی" -"delDepletedClientsContent" = "آیا مطمئن به حذف تمام کاربران منقضی‌شده ‌هستید؟" -"email" = "ایمیل" -"emailDesc" = "باید یک ایمیل یکتا باشد" -"IPLimit" = "محدودیت آی‌پی" -"IPLimitDesc" = "(اگر تعداد از مقدار تنظیم شده بیشتر شود، ورودی را غیرفعال می کند. (0 = غیرفعال" -"IPLimitlog" = "گزارش‌ها" -"IPLimitlogDesc" = "گزارش تاریخچه آی‌پی. برای فعال کردن ورودی پس از غیرفعال شدن، گزارش را پاک کنید" -"IPLimitlogclear" = "پاک کردن گزارش‌ها" -"setDefaultCert" = "استفاده از گواهی پنل" -"telegramDesc" = "لطفا شناسه گفتگوی تلگرام را وارد کنید. (از دستور '/id' در ربات استفاده کنید) یا (@userinfobot)" -"subscriptionDesc" = "شما می‌توانید لینک سابسکربپشن خودرا در 'جزئیات' پیدا کنید، همچنین می‌توانید از همین نام برای چندین کاربر استفاده‌کنید" -"info" = "اطلاعات" -"same" = "همسان" -"inboundData" = "داده‌های ورودی" -"exportInbound" = "استخراج ورودی" -"import" = "افزودن" -"importInbound" = "افزودن یک ورودی" - -[pages.client] -"add" = "کاربر جدید" -"edit" = "ویرایش کاربر" -"submitAdd" = "اضافه کردن" -"submitEdit" = "ذخیره تغییرات" -"clientCount" = "تعداد کاربران" -"bulk" = "انبوه‌سازی" -"method" = "روش" -"first" = "از" -"last" = "تا" -"prefix" = "پیشوند" -"postfix" = "پسوند" -"delayedStart" = "شروع‌پس‌از‌اولین‌استفاده" -"expireDays" = "مدت زمان" -"days" = "(روز)" -"renew" = "تمدید خودکار" -"renewDesc" = "(تمدید خودکار پس‌از ‌انقضا. (0 = غیرفعال)(واحد: روز" - -[pages.inbounds.toasts] -"obtain" = "فراهم‌سازی" - -[pages.inbounds.stream.general] -"request" = "درخواست" -"response" = "پاسخ" -"name" = "نام" -"value" = "مقدار" - -[pages.inbounds.stream.tcp] -"version" = "نسخه" -"method" = "متد" -"path" = "مسیر" -"status" = "وضعیت" -"statusDescription" = "توضیحات وضعیت" -"requestHeader" = "سربرگ درخواست" -"responseHeader" = "سربرگ پاسخ" - -[pages.settings] -"title" = "تنظیمات پنل" -"save" = "ذخیره" -"infoDesc" = "برای اعمال تغییرات در این بخش باید پس از ذخیره کردن، پنل را ریستارت کنید" -"restartPanel" = "ریستارت پنل" -"restartPanelDesc" = "آیا مطمئن به ریستارت پنل هستید؟ اگر پس‌از ریستارت نمی‌توانید به پنل دسترسی پیدا کنید، لطفاً گزارش‌های موجود در اسکریپت پنل را بررسی کنید" -"actions" = "عملیات ها" -"resetDefaultConfig" = "برگشت به پیش‌فرض" -"panelSettings" = "پیکربندی" -"securitySettings" = "احرازهویت" -"TGBotSettings" = "ربات تلگرام" -"panelListeningIP" = "آدرس آی‌پی" -"panelListeningIPDesc" = "آدرس آی‌پی برای وب پنل. برای گوش‌دادن به‌تمام آی‌پی‌ها خالی‌بگذارید" -"panelListeningDomain" = "نام دامنه" -"panelListeningDomainDesc" = "آدرس دامنه برای وب پنل. برای گوش دادن به‌تمام دامنه‌ها و آی‌پی‌ها خالی‌بگذارید" -"panelPort" = "پورت" -"panelPortDesc" = "شماره پورت برای وب پنل. باید پورت استفاده نشده‌باشد" -"publicKeyPath" = "مسیر کلید عمومی" -"publicKeyPathDesc" = "مسیر فایل کلیدعمومی برای وب پنل. با '/' شروع‌می‌شود" -"privateKeyPath" = "مسیر کلید خصوصی" -"privateKeyPathDesc" = "مسیر فایل کلیدخصوصی برای وب پنل. با '/' شروع‌می‌شود" -"panelUrlPath" = "URI مسیر" -"panelUrlPathDesc" = "برای وب پنل. با '/' شروع‌ و با '/' خاتمه‌ می‌یابد URI مسیر" -"pageSize" = "اندازه صفحه بندی جدول" -"pageSizeDesc" = "(اندازه صفحه برای جدول ورودی‌ها.(0 = غیرفعال" -"remarkModel" = "نام‌کانفیگ و جداکننده" -"datepicker" = "نوع تقویم" -"datepickerPlaceholder" = "انتخاب تاریخ" -"datepickerDescription" = "وظایف برنامه ریزی شده بر اساس این تقویم اجرا می‌شود" -"sampleRemark" = "نمونه‌نام" -"oldUsername" = "نام‌کاربری فعلی" -"currentPassword" = "رمز‌عبور فعلی" -"newUsername" = "نام‌کاربری جدید" -"newPassword" = "رمزعبور جدید" -"telegramBotEnable" = "فعال‌سازی ربات تلگرام" -"telegramBotEnableDesc" = "ربات تلگرام را فعال می‌کند" -"telegramToken" = "توکن تلگرام" -"telegramTokenDesc" = "دریافت کنید @botfather توکن را می‌توانید از" -"telegramProxy" = "SOCKS پراکسی" -"telegramProxyDesc" = "را برای اتصال به تلگرام فعال می کند SOCKS5 پراکسی" -"telegramAPIServer" = "سرور API تلگرام" -"telegramAPIServerDesc" = "API سرور تلگرام برای اتصال را تغییر میدهد. برای استفاده از سرور پیش فرض خالی بگذارید" -"telegramChatId" = "آی‌دی چت مدیر" -"telegramChatIdDesc" = "دریافت ‌کنید ('/id'یا (دستور (@userinfobot) آی‌دی(های) چت تلگرام مدیر، از" -"telegramNotifyTime" = "زمان نوتیفیکیشن" -"telegramNotifyTimeDesc" = "زمان‌اطلاع‌رسانی ربات تلگرام برای گزارش های دوره‌ای. از فرمت زمانبندی لینوکس استفاده‌کنید‌" -"tgNotifyBackup" = "پشتیبان‌گیری از دیتابیس" -"tgNotifyBackupDesc" = "فایل پشتیبان‌دیتابیس را به‌همراه گزارش ارسال می‌کند" -"tgNotifyLogin" = "اعلان ورود" -"tgNotifyLoginDesc" = "نام‌کاربری، آدرس آی‌پی، و زمان ورود، فردی که سعی می‌کند وارد پنل شود را نمایش می‌دهد" -"sessionMaxAge" = "بیشینه زمان جلسه وب" -"sessionMaxAgeDesc" = "(بیشینه زمانی که می‌توانید لاگین بمانید. (واحد: دقیقه" -"expireTimeDiff" = "آستانه زمان باقی مانده" -"expireTimeDiffDesc" = "(فاصله زمانی هشدار تا رسیدن به زمان انقضا. (واحد: روز" -"trafficDiff" = "آستانه ترافیک باقی مانده" -"trafficDiffDesc" = "(فاصله زمانی هشدار تا رسیدن به اتمام ترافیک. (واحد: گیگابایت" -"tgNotifyCpu" = "آستانه هشدار بار پردازنده" -"tgNotifyCpuDesc" = "(اگر بار روی پردازنده ازاین آستانه فراتر رفت، برای شما پیام ارسال می‌شود. (واحد: درصد" -"timeZone" = "منطقه زمانی" -"timeZoneDesc" = "وظایف برنامه ریزی شده بر اساس این منطقه‌زمانی اجرا می‌شود" -"subSettings" = "سابسکریپشن" -"subEnable" = "فعال‌سازی سرویس سابسکریپشن" -"subEnableDesc" = "سرویس سابسکریپشن‌ را فعال‌می‌کند" -"subListen" = "آدرس آی‌پی" -"subListenDesc" = "آدرس آی‌پی برای سرویس سابسکریپشن. برای گوش دادن به‌تمام آی‌پی‌ها خالی‌بگذارید" -"subPort" = "پورت" -"subPortDesc" = "شماره پورت برای سرویس سابسکریپشن. باید پورت استفاده نشده‌باشد" -"subCertPath" = "مسیر کلید عمومی" -"subCertPathDesc" = "مسیر فایل کلیدعمومی برای سرویس سابیکریپشن. با '/' شروع‌می‌شود" -"subKeyPath" = "مسیر کلید خصوصی" -"subKeyPathDesc" = "مسیر فایل کلیدخصوصی برای سرویس سابسکریپشن. با '/' شروع‌می‌شود" -"subPath" = "URI مسیر" -"subPathDesc" = "برای سرویس سابسکریپشن. با '/' شروع‌ و با '/' خاتمه‌ می‌یابد URI مسیر" -"subDomain" = "نام دامنه" -"subDomainDesc" = "آدرس دامنه برای سرویس سابسکریپشن. برای گوش دادن به تمام دامنه‌ها و آی‌پی‌ها خالی‌بگذارید‌" -"subUpdates" = "فاصله بروزرسانی‌ سابسکریپشن" -"subUpdatesDesc" = "(فاصله مابین بروزرسانی در برنامه‌های کاربری. (واحد: ساعت" -"subEncrypt" = "کدگذاری" -"subEncryptDesc" = "کدگذاری خواهدشد Base64 محتوای برگشتی سرویس سابسکریپشن برپایه" -"subShowInfo" = "نمایش اطلاعات مصرف" -"subShowInfoDesc" = "ترافیک و زمان باقی‌مانده را در برنامه‌های کاربری نمایش می‌دهد" -"subURI" = "پروکسی معکوس URI مسیر" -"subURIDesc" = "سابسکریپشن را برای استفاده در پشت پراکسی‌ها تغییر می‌دهد URI مسیر" -"fragment" = "فرگمنت" -"fragmentDesc" = "فعال کردن فرگمنت برای بسته‌ی نخست تی‌ال‌اس" -"fragmentSett" = "تنظیمات فرگمنت" -"noisesDesc" = "فعال کردن Noises." -"noisesSett" = "تنظیمات Noises" -"mux" = "ماکس" -"muxDesc" = "چندین جریان داده مستقل را در یک جریان داده ثابت منتقل می کند" -"muxSett" = "تنظیمات ماکس" -"direct" = "اتصال مستقیم" -"directDesc" = "به طور مستقیم با دامنه ها یا محدوده آی‌پی یک کشور خاص ارتباط برقرار می کند" - - -[pages.xray] -"title" = "پیکربندی ایکس‌ری" -"save" = "ذخیره" -"restart" = "ریستارت ایکس‌ری" -"basicTemplate" = "پایه" -"advancedTemplate" = "پیشرفته" -"generalConfigs" = "استراتژی‌ کلی" -"generalConfigsDesc" = "این گزینه‌ها استراتژی کلی ترافیک را تعیین می‌کنند" -"logConfigs" = "گزارش" -"logConfigsDesc" = "گزارش‌ها ممکن است بر کارایی سرور شما تأثیر بگذارد. توصیه می شود فقط در صورت نیاز آن را عاقلانه فعال کنید" -"blockConfigs" = "سپر محافظ" -"blockConfigsDesc" = "این گزینه‌ها ترافیک را بر اساس پروتکل‌های درخواستی خاص، و وب سایت‌ها مسدود می‌کند" -"basicRouting" = "مسیریابی پایه" -"blockConnectionsConfigsDesc" = "این گزینه‌ها ترافیک را بر اساس کشور درخواست‌شده خاص مسدود می‌کنند." -"directConnectionsConfigsDesc" = "یک اتصال مستقیم تضمین می‌کند که ترافیک خاص از طریق سرور دیگری مسیریابی نشود." -"blockips" = "مسدود کردن آی‌پی‌ها" -"blockdomains" = "مسدود کردن دامنه‌ها" -"directips" = "آی‌پی‌های مستقیم" -"directdomains" = "دامنه‌های مستقیم" -"ipv4Routing" = "IPv4 مسیریابی" -"ipv4RoutingDesc" = "این گزینه‌ها ترافیک را از طریق آی‌پی نسخه4 سرور، به مقصد هدایت می‌کند" -"warpRouting" = "WARP مسیریابی" -"warpRoutingDesc" = "این گزینه‌ها ترافیک‌ را از طریق وارپ کلادفلر به مقصد هدایت می‌کند" -"Template" = "‌پیکربندی پیشرفته الگو ایکس‌ری" -"TemplateDesc" = "فایل پیکربندی نهایی ایکس‌ری بر اساس این الگو ایجاد می‌شود" -"FreedomStrategy" = "Freedom استراتژی پروتکل" -"FreedomStrategyDesc" = "تعیین می‌کند Freedom استراتژی خروجی شبکه را برای پروتکل" -"RoutingStrategy" = "استراتژی کلی مسیریابی" -"RoutingStrategyDesc" = "استراتژی کلی مسیریابی برای حل تمام درخواست‌ها را تعیین می‌کند" -"Torrent" = "مسدودسازی پروتکل بیت‌تورنت" -"TorrentDesc" = "پروتکل بیت تورنت را مسدود می‌کند" -"Family" = "محافظت خانواده" -"FamilyDesc" = "محتوای مخصوص بزرگسالان، و وب‌سایت‌های ناامن را مسدود می‌کند" -"Inbounds" = "ورودی‌ها" -"InboundsDesc" = "پذیرش کلاینت خاص" -"Outbounds" = "خروجی‌ها" -"Balancers" = "بالانسرها" -"OutboundsDesc" = "مسیر ترافیک خروجی را تنظیم کنید" -"Routings" = "قوانین مسیریابی" -"RoutingsDesc" = "اولویت هر قانون مهم است" -"completeTemplate" = "کامل" -"logLevel" = "سطح گزارش" -"logLevelDesc" = "سطح گزارش برای گزارش های خطا، نشان دهنده اطلاعاتی است که باید ثبت شوند." -"accessLog" = "مسیر گزارش" -"accessLogDesc" = "مسیر فایل برای گزارش دسترسی. مقدار ویژه «هیچ» گزارش‌های دسترسی را غیرفعال میکند." -"errorLog" = "گزارش خطا" -"errorLogDesc" = "مسیر فایل برای ورود به سیستم خطا. مقدار ویژه «هیچ» گزارش های خطا را غیرفعال میکند" -"dnsLog" = "گزارش DNS" -"dnsLogDesc" = "آیا ثبت‌های درخواست DNS را فعال کنید" -"outboundTraffic" = "ترافیک خروجی" -"outboundTrafficDesc" = "فعال کردن ترافیک خروجی" -"maskAddress" = "پنهان کردن آدرس" -"maskAddressDesc" = "پوشش آدرس IP، هنگامی که فعال می‌شود، به طور خودکار آدرس IP که در لاگ ظاهر می‌شود را جایگزین می‌کند." - -[pages.xray.rules] -"first" = "اولین" -"last" = "آخرین" -"up" = "بالا" -"down" = "پایین" -"source" = "مبدا" -"dest" = "مقصد" -"inbound" = "ورودی" -"outbound" = "خروجی" -"balancer" = "بالانسر" -"info" = "اطلاعات" -"add" = "افزودن قانون" -"edit" = "ویرایش قانون" -"useComma" = "موارد جدا شده با کاما" - -[pages.xray.outbound] -"addOutbound" = "افزودن خروجی" -"addReverse" = "افزودن معکوس" -"editOutbound" = "ویرایش خروجی" -"editReverse" = "ویرایش معکوس" -"tag" = "برچسب" -"tagDesc" = "برچسب یگانه" -"address" = "آدرس" -"reverse" = "معکوس" -"domain" = "دامنه" -"type" = "نوع" -"bridge" = "پل" -"portal" = "پورتال" -"intercon" = "اتصال میانی" -"settings" = "تنظیمات" -"accountInfo" = "اطلاعات حساب" -"outboundStatus" = "وضعیت خروجی" -"sendThrough" = "ارسال با" - -[pages.xray.balancer] -"addBalancer" = "افزودن بالانسر" -"editBalancer" = "ویرایش بالانسر" -"balancerStrategy" = "استراتژی" -"balancerSelectors" = "انتخاب‌گرها" -"tag" = "برچسب" -"tagDesc" = "برچسب یگانه" -"balancerDesc" = "امکان استفاده همزمان balancerTag و outboundTag باهم وجود ندارد. درصورت استفاده همزمان فقط outboundTag عمل خواهد کرد." - -[pages.xray.wireguard] -"secretKey" = "کلید شخصی" -"publicKey" = "کلید عمومی" -"allowedIPs" = "آی‌پی‌های مجاز" -"endpoint" = "نقطه پایانی" -"psk" = "کلید مشترک" -"domainStrategy" = "استراتژی حل دامنه" - -[pages.xray.dns] -"enable" = "فعال کردن حل دامنه" -"enableDesc" = "سرور حل دامنه داخلی را فعال کنید" -"tag" = "برچسب" -"tagDesc" = "این برچسب در قوانین مسیریابی به عنوان یک برچسب ورودی قابل استفاده خواهد بود" -"strategy" = "استراتژی پرس‌وجو" -"strategyDesc" = "استراتژی کلی برای حل نام دامنه" -"add" = "افزودن سرور" -"edit" = "ویرایش سرور" -"domains" = "دامنه‌ها" -"expectIPs" = "آی‌پی‌های مورد انتظار" - -[pages.xray.fakedns] -"add" = "افزودن دی‌ان‌اس جعلی" -"edit" = "ویرایش دی‌ان‌اس جعلی" -"ipPool" = "زیرشبکه استخر آی‌پی" -"poolSize" = "اندازه استخر" - -[pages.settings.security] -"admin" = "مدیر" -"secret" = "توکن مخفی" -"loginSecurity" = "ورود ایمن" -"loginSecurityDesc" = "یک لایه اضافی از احراز هویت برای ایجاد امنیت بیشتر اضافه می کند" -"secretToken" = "توکن مخفی" -"secretTokenDesc" = "لطفاً این توکن را در مکانی امن ذخیره کنید. این توکن برای ورود به سیستم مورد نیاز است و قابل بازیابی نیست" - -[pages.settings.toasts] -"modifySettings" = "ویرایش تنظیمات" -"getSettings" = "دریافت تنظیمات" -"modifyUser" = "ویرایش مدیر" -"originalUserPassIncorrect" = "نام‌کاربری یا رمزعبور فعلی اشتباه‌است" -"userPassMustBeNotEmpty" = "نام‌کاربری یا رمزعبور جدید خالی‌است" - -[tgbot] -"keyboardClosed" = "❌ کیبورد سفارشی بسته شد!" -"noResult" = "❗ نتیجه‌ای یافت نشد!" -"noQuery" = "❌ کوئری یافت نشد! لطفاً دستور را مجدداً استفاده کنید!" -"wentWrong" = "❌ مشکلی رخ داده است!" -"noIpRecord" = "❗ رکورد IP یافت نشد!" -"noInbounds" = "❗ هیچ ورودی یافت نشد!" -"unlimited" = "♾ - نامحدود(ریست)" -"add" = "اضافه کردن" -"month" = "ماه" -"months" = "ماه‌" -"day" = "روز" -"days" = "روز" -"hours" = "ساعت‌" -"unknown" = "نامشخص" -"inbounds" = "ورودی‌ها" -"clients" = "کلاینت‌ها" -"offline" = "🔴 آفلاین" -"online" = "🟢 آنلاین" - -[tgbot.commands] -"unknown" = "❗ دستور ناشناخته" -"pleaseChoose" = "👇 لطفاً انتخاب کنید:\r\n" -"help" = "🤖 به این ربات خوش آمدید! این ربات برای ارائه داده‌های خاص از سرور طراحی شده است و به شما امکان تغییرات لازم را می‌دهد.\r\n\r\n" -"start" = "👋 سلام {{ .Firstname }}.\r\n" -"welcome" = "🤖 به ربات مدیریت {{ .Hostname }} خوش آمدید.\r\n" -"status" = "✅ ربات در حالت عادی است!" -"usage" = "❗ لطفاً یک متن برای جستجو وارد کنید!" -"getID" = "🆔 شناسه شما: {{ .ID }}" -"helpAdminCommands" = "برای راه‌اندازی مجدد Xray Core:\r\n/restart force\r\n\r\nبرای جستجوی ایمیل مشتری:\r\n/usage [ایمیل]\r\n\r\nبرای جستجوی ورودی‌ها (با آمار مشتری):\r\n/inbound [توضیحات]\r\n\r\nشناسه گفتگوی تلگرام:\r\n/id" -"helpClientCommands" = "برای جستجوی آمار، از دستور زیر استفاده کنید:\r\n/usage [ایمیل]\r\n\r\nشناسه گفتگوی تلگرام:\r\n/id" -"restartUsage" = "\r\n\r\n/restart force" -"restartSuccess" = "✅ عملیات با موفقیت انجام شد!" -"restartFailed" = "❗ خطا در عملیات.\r\n\r\nخطا: {{ .Error }}." -"xrayNotRunning" = "❗ Xray Core در حال اجرا نیست." - -[tgbot.messages] -"cpuThreshold" = "🔴 بار ‌پردازنده {{ .Percent }}% بیشتر از آستانه است {{ .Threshold }}%" -"selectUserFailed" = "❌ خطا در انتخاب کاربر!" -"userSaved" = "✅ کاربر تلگرام ذخیره شد." -"loginSuccess" = "✅ با موفقیت به پنل وارد شدید.\r\n" -"loginFailed" = "❗️ ورود به پنل ناموفق‌بود \r\n" -"report" = "🕰 گزارشات‌زمان‌بندی‌شده: {{ .RunTime }}\r\n" -"datetime" = "⏰ تاریخ‌وزمان: {{ .DateTime }}\r\n" -"hostname" = "💻 نام‌میزبان: {{ .Hostname }}\r\n" -"version" = "🚀 نسخه‌پنل: {{ .Version }}\r\n" -"xrayVersion" = "📡 نسخه‌هسته: {{ .XrayVersion }}\r\n" -"ipv6" = "🌐 IPv6: {{ .IPv6 }}\r\n" -"ipv4" = "🌐 IPv4: {{ .IPv4 }}\r\n" -"ip" = "🌐 آدرس‌آی‌پی: {{ .IP }}\r\n" -"ips" = "🔢 آدرس‌های آی‌پی:\r\n{{ .IPs }}\r\n" -"serverUpTime" = "⏳ مدت‌کارکردسیستم: {{ .UpTime }} {{ .Unit }}\r\n" -"serverLoad" = "📈 بارسیستم: {{ .Load1 }}, {{ .Load2 }}, {{ .Load3 }}\r\n" -"serverMemory" = "📋 RAM: {{ .Current }}/{{ .Total }}\r\n" -"tcpCount" = "🔹 TCP: {{ .Count }}\r\n" -"udpCount" = "🔸 UDP: {{ .Count }}\r\n" -"traffic" = "🚦 ترافیک: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n" -"xrayStatus" = "ℹ️ وضعیت‌ایکس‌ری: {{ .State }}\r\n" -"username" = "👤 نام‌کاربری: {{ .Username }}\r\n" -"password" = "👤 رمز عبور: {{ .Password }}\r\n" -"time" = "⏰ زمان: {{ .Time }}\r\n" -"inbound" = "📍 نام‌ورودی: {{ .Remark }}\r\n" -"port" = "🔌 پورت: {{ .Port }}\r\n" -"expire" = "📅 تاریخ‌انقضا: {{ .Time }}\r\n\r\n" -"expireIn" = "📅 باقی‌ مانده‌ تا انقضا: {{ .Time }}\r\n\r\n" -"active" = "💡 فعال: {{ .Enable }}\r\n" -"enabled" = "🚨 وضعیت: {{ .Enable }}\r\n" -"online" = "🌐 وضعیت اتصال: {{ .Status }}\r\n" -"email" = "📧 ایمیل: {{ .Email }}\r\n" -"upload" = "🔼 آپلود↑: {{ .Upload }}\r\n" -"download" = "🔽 دانلود↓: {{ .Download }}\r\n" -"total" = "🔄 کل: {{ .UpDown }} / {{ .Total }}\r\n" -"TGUser" = "👤 کاربر تلگرام: {{ .TelegramID }}\r\n" -"exhaustedMsg" = "🚨 {{ .Type }} به‌اتمام‌رسیده‌است:\r\n" -"exhaustedCount" = "🚨 تعداد {{ .Type }} به‌اتمام‌رسیده‌است:\r\n" -"onlinesCount" = "🌐 کاربران‌آنلاین: {{ .Count }}\r\n" -"disabled" = "🛑 غیرفعال: {{ .Disabled }}\r\n" -"depleteSoon" = "🔜 به‌زودی‌به‌پایان‌خواهدرسید: {{ .Deplete }}\r\n\r\n" -"backupTime" = "🗄 زمان‌پشتیبان‌گیری: {{ .Time }}\r\n" -"refreshedOn" = "\r\n📋🔄 تازه‌سازی شده در: {{ .Time }}\r\n\r\n" -"yes" = "✅ بله" -"no" = "❌ خیر" - -[tgbot.buttons] -"closeKeyboard" = "❌ بستن کیبورد" -"cancel" = "❌ لغو" -"cancelReset" = "❌ لغو تنظیم مجدد" -"cancelIpLimit" = "❌ لغو محدودیت آی‌پی" -"confirmResetTraffic" = "✅ تأیید تنظیم مجدد ترافیک؟" -"confirmClearIps" = "✅ تأیید پاک‌سازی آدرس‌های آی‌پی؟" -"confirmRemoveTGUser" = "✅ تأیید حذف کاربر تلگرام؟" -"confirmToggle" = "✅ تایید فعال/غیرفعال کردن کاربر؟" -"dbBackup" = "دریافت پشتیبان" -"serverUsage" = "استفاده از سیستم" -"getInbounds" = "دریافت ورودی‌ها" -"depleteSoon" = "به‌زودی به پایان خواهد رسید" -"clientUsage" = "دریافت آمار کاربر" -"onlines" = "کاربران آنلاین" -"commands" = "دستورات" -"refresh" = "🔄 تازه‌سازی" -"clearIPs" = "❌ پاک‌سازی آدرس‌ها" -"removeTGUser" = "❌ حذف کاربر تلگرام" -"selectTGUser" = "👤 انتخاب کاربر تلگرام" -"selectOneTGUser" = "👤 یک کاربر تلگرام را انتخاب کنید:" -"resetTraffic" = "📈 تنظیم مجدد ترافیک" -"resetExpire" = "📅 تنظیم مجدد تاریخ انقضا" -"ipLog" = "🔢 لاگ آدرس‌های IP" -"ipLimit" = "🔢 محدودیت IP" -"setTGUser" = "👤 تنظیم کاربر تلگرام" -"toggle" = "🔘 فعال / غیرفعال" -"custom" = "🔢 سفارشی" -"confirmNumber" = "✅ تایید: {{ .Num }}" -"confirmNumberAdd" = "✅ تایید اضافه کردن: {{ .Num }}" -"limitTraffic" = "🚧 محدودیت ترافیک" -"getBanLogs" = "گزارش های بلوک را دریافت کنید" -"allClients" = "همه مشتریان" - -[tgbot.answers] -"successfulOperation" = "✅ انجام شد!" -"errorOperation" = "❗ خطا در عملیات." -"getInboundsFailed" = "❌ دریافت ورودی‌ها با خطا مواجه شد." -"getClientsFailed" = "❌ دریافت مشتریان با شکست مواجه شد." -"canceled" = "❌ {{ .Email }} : عملیات لغو شد." -"clientRefreshSuccess" = "✅ {{ .Email }} : کلاینت با موفقیت تازه‌سازی شد." -"IpRefreshSuccess" = "✅ {{ .Email }} : آدرس‌ها با موفقیت تازه‌سازی شدند." -"TGIdRefreshSuccess" = "✅ {{ .Email }} : کاربر تلگرام کلاینت با موفقیت تازه‌سازی شد." -"resetTrafficSuccess" = "✅ {{ .Email }} : ترافیک با موفقیت تنظیم مجدد شد." -"setTrafficLimitSuccess" = "✅ {{ .Email }} : محدودیت ترافیک با موفقیت ذخیره شد." -"expireResetSuccess" = "✅ {{ .Email }} : تاریخ انقضا با موفقیت تنظیم مجدد شد." -"resetIpSuccess" = "✅ {{ .Email }} : محدودیت آدرس IP {{ .Count }} با موفقیت ذخیره شد." -"clearIpSuccess" = "✅ {{ .Email }} : آدرس‌ها با موفقیت پاک‌سازی شدند." -"getIpLog" = "✅ {{ .Email }} : دریافت لاگ آدرس‌های IP." -"getUserInfo" = "✅ {{ .Email }} : دریافت اطلاعات کاربر تلگرام." -"removedTGUserSuccess" = "✅ {{ .Email }} : کاربر تلگرام با موفقیت حذف شد." -"enableSuccess" = "✅ {{ .Email }} : با موفقیت فعال شد." -"disableSuccess" = "✅ {{ .Email }} : با موفقیت غیرفعال شد." -"askToAddUserId" = "پیکربندی شما یافت نشد!\r\nلطفاً از مدیر خود بخواهید که شناسه کاربر تلگرام خود را در پیکربندی (های) خود استفاده کند.\r\n\r\nشناسه کاربری شما: {{ .TgUserID }}" -"chooseClient" = "یک مشتری برای ورودی {{ .Inbound }} انتخاب کنید" -"chooseInbound" = "یک ورودی انتخاب کنید" diff --git a/web/translation/translate.id_ID.toml b/web/translation/translate.id_ID.toml deleted file mode 100644 index 6eb6d9b5..00000000 --- a/web/translation/translate.id_ID.toml +++ /dev/null @@ -1,598 +0,0 @@ -"username" = "Nama Pengguna" -"password" = "Kata Sandi" -"login" = "Masuk" -"confirm" = "Konfirmasi" -"cancel" = "Batal" -"close" = "Tutup" -"copy" = "Salin" -"copied" = "Tersalin" -"download" = "Unduh" -"remark" = "Catatan" -"enable" = "Aktifkan" -"protocol" = "Protokol" -"search" = "Cari" -"filter" = "Filter" -"loading" = "Memuat..." -"second" = "Detik" -"minute" = "Menit" -"hour" = "Jam" -"day" = "Hari" -"check" = "Centang" -"indefinite" = "Tak Terbatas" -"unlimited" = "Tanpa Batas" -"none" = "None" -"qrCode" = "Kode QR" -"info" = "Informasi Lebih Lanjut" -"edit" = "Edit" -"delete" = "Hapus" -"reset" = "Reset" -"copySuccess" = "Berhasil Disalin" -"sure" = "Yakin" -"encryption" = "Enkripsi" -"transmission" = "Transmisi" -"host" = "Host" -"path" = "Jalur" -"camouflage" = "Obfuscation" -"status" = "Status" -"enabled" = "Aktif" -"disabled" = "Nonaktif" -"depleted" = "Habis" -"depletingSoon" = "Akan Habis" -"offline" = "Offline" -"online" = "Online" -"domainName" = "Nama Domain" -"monitor" = "IP Pemantauan" -"certificate" = "Sertifikat Digital" -"fail" = "Gagal" -"comment" = "Komentar" -"success" = "Berhasil" -"getVersion" = "Dapatkan Versi" -"install" = "Instal" -"clients" = "Klien" -"usage" = "Penggunaan" -"secretToken" = "Token Rahasia" -"remained" = "Tersisa" -"security" = "Keamanan" -"secAlertTitle" = "Peringatan keamanan" -"secAlertSsl" = "Koneksi ini tidak aman. Harap hindari memasukkan informasi sensitif sampai TLS diaktifkan untuk perlindungan data." -"secAlertConf" = "Beberapa pengaturan rentan terhadap serangan. Disarankan untuk memperkuat protokol keamanan guna mencegah pelanggaran potensial." -"secAlertSSL" = "Panel kekurangan koneksi yang aman. Harap instal sertifikat TLS untuk perlindungan data." -"secAlertPanelPort" = "Port default panel rentan. Harap konfigurasi port acak atau tertentu." -"secAlertPanelURI" = "Jalur URI default panel tidak aman. Harap konfigurasi jalur URI kompleks." -"secAlertSubURI" = "Jalur URI default langganan tidak aman. Harap konfigurasi jalur URI kompleks." -"secAlertSubJsonURI" = "Jalur URI default JSON langganan tidak aman. Harap konfigurasikan jalur URI kompleks." - -[menu] -"dashboard" = "Ikhtisar" -"inbounds" = "Masuk" -"settings" = "Pengaturan Panel" -"xray" = "Konfigurasi Xray" -"logout" = "Keluar" -"link" = "Kelola" - -[pages.login] -"hello" = "Halo" -"title" = "Selamat Datang" -"loginAgain" = "Sesi Anda telah berakhir, harap masuk kembali" - -[pages.login.toasts] -"invalidFormData" = "Format data input tidak valid." -"emptyUsername" = "Nama Pengguna diperlukan" -"emptyPassword" = "Kata Sandi diperlukan" -"wrongUsernameOrPassword" = "Nama pengguna atau kata sandi tidak valid." -"successLogin" = "Login berhasil" - -[pages.index] -"title" = "Ikhtisar" -"memory" = "RAM" -"hard" = "Disk" -"xrayStatus" = "Xray" -"stopXray" = "Stop" -"restartXray" = "Restart" -"xraySwitch" = "Versi" -"xraySwitchClick" = "Pilih versi yang ingin Anda pindah." -"xraySwitchClickDesk" = "Pilih dengan hati-hati, karena versi yang lebih lama mungkin tidak kompatibel dengan konfigurasi saat ini." -"operationHours" = "Waktu Aktif" -"systemLoad" = "Beban Sistem" -"systemLoadDesc" = "Rata-rata beban sistem selama 1, 5, dan 15 menit terakhir" -"connectionTcpCountDesc" = "Total koneksi TCP di seluruh sistem" -"connectionUdpCountDesc" = "Total koneksi UDP di seluruh sistem" -"connectionCount" = "Statistik Koneksi" -"upSpeed" = "Kecepatan unggah keseluruhan di seluruh sistem" -"downSpeed" = "Kecepatan unduh keseluruhan di seluruh sistem" -"totalSent" = "Total data terkirim di seluruh sistem sejak startup OS" -"totalReceive" = "Total data diterima di seluruh sistem sejak startup OS" -"xraySwitchVersionDialog" = "Ganti Versi Xray" -"xraySwitchVersionDialogDesc" = "Apakah Anda yakin ingin mengubah versi Xray menjadi" -"dontRefresh" = "Instalasi sedang berlangsung, harap jangan menyegarkan halaman ini" -"logs" = "Log" -"config" = "Konfigurasi" -"backup" = "Cadangan & Pulihkan" -"backupTitle" = "Cadangan & Pulihkan Database" -"backupDescription" = "Disarankan untuk membuat cadangan sebelum memulihkan database." -"exportDatabase" = "Cadangkan" -"importDatabase" = "Pulihkan" - -[pages.inbounds] -"title" = "Masuk" -"totalDownUp" = "Total Terkirim/Diterima" -"totalUsage" = "Penggunaan Total" -"inboundCount" = "Total Masuk" -"operate" = "Menu" -"enable" = "Aktifkan" -"remark" = "Catatan" -"protocol" = "Protokol" -"port" = "Port" -"traffic" = "Traffic" -"details" = "Rincian" -"transportConfig" = "Transport" -"expireDate" = "Durasi" -"resetTraffic" = "Reset Traffic" -"addInbound" = "Tambahkan Masuk" -"generalActions" = "Tindakan Umum" -"create" = "Buat" -"update" = "Perbarui" -"modifyInbound" = "Ubah Masuk" -"deleteInbound" = "Hapus Masuk" -"deleteInboundContent" = "Apakah Anda yakin ingin menghapus masuk?" -"deleteClient" = "Hapus Klien" -"deleteClientContent" = "Apakah Anda yakin ingin menghapus klien?" -"resetTrafficContent" = "Apakah Anda yakin ingin mereset traffic?" -"copyLink" = "Salin URL" -"address" = "Alamat" -"network" = "Jaringan" -"destinationPort" = "Port Tujuan" -"targetAddress" = "Alamat Target" -"monitorDesc" = "Biarkan kosong untuk mendengarkan semua IP" -"meansNoLimit" = "= Unlimited. (unit: GB)" -"totalFlow" = "Total Aliran" -"leaveBlankToNeverExpire" = "Biarkan kosong untuk tidak pernah kedaluwarsa" -"noRecommendKeepDefault" = "Disarankan untuk tetap menggunakan pengaturan default" -"certificatePath" = "Path Berkas" -"certificateContent" = "Konten Berkas" -"publicKey" = "Kunci Publik" -"privatekey" = "Kunci Pribadi" -"clickOnQRcode" = "Klik pada Kode QR untuk Menyalin" -"client" = "Klien" -"export" = "Ekspor Semua URL" -"clone" = "Duplikat" -"cloneInbound" = "Duplikat" -"cloneInboundContent" = "Semua pengaturan masuk ini, kecuali Port, Listening IP, dan Klien, akan diterapkan pada duplikat." -"cloneInboundOk" = "Duplikat" -"resetAllTraffic" = "Reset Semua Traffic Masuk" -"resetAllTrafficTitle" = "Reset Semua Traffic Masuk" -"resetAllTrafficContent" = "Apakah Anda yakin ingin mereset traffic semua masuk?" -"resetInboundClientTraffics" = "Reset Traffic Klien Masuk" -"resetInboundClientTrafficTitle" = "Reset Traffic Klien Masuk" -"resetInboundClientTrafficContent" = "Apakah Anda yakin ingin mereset traffic klien masuk ini?" -"resetAllClientTraffics" = "Reset Traffic Semua Klien" -"resetAllClientTrafficTitle" = "Reset Traffic Semua Klien" -"resetAllClientTrafficContent" = "Apakah Anda yakin ingin mereset traffic semua klien?" -"delDepletedClients" = "Hapus Klien Habis" -"delDepletedClientsTitle" = "Hapus Klien Habis" -"delDepletedClientsContent" = "Apakah Anda yakin ingin menghapus semua klien yang habis?" -"email" = "Email" -"emailDesc" = "Harap berikan alamat email yang unik." -"IPLimit" = "Batas IP" -"IPLimitDesc" = "Menonaktifkan masuk jika jumlah melebihi nilai yang ditetapkan. (0 = nonaktif)" -"IPLimitlog" = "Log IP" -"IPLimitlogDesc" = "Log histori IP. (untuk mengaktifkan masuk setelah menonaktifkan, hapus log)" -"IPLimitlogclear" = "Hapus Log" -"setDefaultCert" = "Atur Sertifikat dari Panel" -"telegramDesc" = "Harap berikan ID Obrolan Telegram. (gunakan perintah '/id' di bot) atau (@userinfobot)" -"subscriptionDesc" = "Untuk menemukan URL langganan Anda, buka 'Rincian'. Selain itu, Anda dapat menggunakan nama yang sama untuk beberapa klien." -"info" = "Info" -"same" = "Sama" -"inboundData" = "Data Masuk" -"exportInbound" = "Ekspor Masuk" -"import" = "Impor" -"importInbound" = "Impor Masuk" - -[pages.client] -"add" = "Tambah Klien" -"edit" = "Edit Klien" -"submitAdd" = "Tambah Klien" -"submitEdit" = "Simpan Perubahan" -"clientCount" = "Jumlah Klien" -"bulk" = "Tambahkan Massal" -"method" = "Metode" -"first" = "Pertama" -"last" = "Terakhir" -"prefix" = "Awalan" -"postfix" = "Akhiran" -"delayedStart" = "Mulai Awal" -"expireDays" = "Durasi" -"days" = "Hari" -"renew" = "Perpanjang Otomatis" -"renewDesc" = "Perpanjangan otomatis setelah kedaluwarsa. (0 = nonaktif)(unit: hari)" - -[pages.inbounds.toasts] -"obtain" = "Dapatkan" - -[pages.inbounds.stream.general] -"request" = "Permintaan" -"response" = "Respons" -"name" = "Nama" -"value" = "Nilai" - -[pages.inbounds.stream.tcp] -"version" = "Versi" -"method" = "Metode" -"path" = "Path" -"status" = "Status" -"statusDescription" = "Deskripsi Status" -"requestHeader" = "Header Permintaan" -"responseHeader" = "Header Respons" - -[pages.settings] -"title" = "Pengaturan Panel" -"save" = "Simpan" -"infoDesc" = "Setiap perubahan yang dibuat di sini perlu disimpan. Harap restart panel untuk menerapkan perubahan." -"restartPanel" = "Restart Panel" -"restartPanelDesc" = "Apakah Anda yakin ingin merestart panel? Jika Anda tidak dapat mengakses panel setelah merestart, lihat info log panel di server." -"actions" = "Tindakan" -"resetDefaultConfig" = "Reset ke Default" -"panelSettings" = "Umum" -"securitySettings" = "Otentikasi" -"TGBotSettings" = "Bot Telegram" -"panelListeningIP" = "IP Pendengar" -"panelListeningIPDesc" = "Alamat IP untuk panel web. (biarkan kosong untuk mendengarkan semua IP)" -"panelListeningDomain" = "Domain Pendengar" -"panelListeningDomainDesc" = "Nama domain untuk panel web. (biarkan kosong untuk mendengarkan semua domain dan IP)" -"panelPort" = "Port Pendengar" -"panelPortDesc" = "Nomor port untuk panel web. (harus menjadi port yang tidak digunakan)" -"publicKeyPath" = "Path Kunci Publik" -"publicKeyPathDesc" = "Path berkas kunci publik untuk panel web. (dimulai dengan ‘/‘)" -"privateKeyPath" = "Path Kunci Privat" -"privateKeyPathDesc" = "Path berkas kunci privat untuk panel web. (dimulai dengan ‘/‘)" -"panelUrlPath" = "URI Path" -"panelUrlPathDesc" = "URI path untuk panel web. (dimulai dengan ‘/‘ dan diakhiri dengan ‘/‘)" -"pageSize" = "Ukuran Halaman" -"pageSizeDesc" = "Tentukan ukuran halaman untuk tabel masuk. (0 = nonaktif)" -"remarkModel" = "Model Catatan & Karakter Pemisah" -"datepicker" = "Jenis Kalender" -"datepickerPlaceholder" = "Pilih tanggal" -"datepickerDescription" = "Tugas terjadwal akan berjalan berdasarkan kalender ini." -"sampleRemark" = "Contoh Catatan" -"oldUsername" = "Username Saat Ini" -"currentPassword" = "Kata Sandi Saat Ini" -"newUsername" = "Username Baru" -"newPassword" = "Kata Sandi Baru" -"telegramBotEnable" = "Aktifkan Bot Telegram" -"telegramBotEnableDesc" = "Mengaktifkan bot Telegram." -"telegramToken" = "Token Telegram" -"telegramTokenDesc" = "Token bot Telegram yang diperoleh dari '@BotFather'." -"telegramProxy" = "Proxy SOCKS" -"telegramProxyDesc" = "Mengaktifkan proxy SOCKS5 untuk terhubung ke Telegram. (sesuaikan pengaturan sesuai panduan)" -"telegramAPIServer" = "Telegram API Server" -"telegramAPIServerDesc" = "Server API Telegram yang akan digunakan. Biarkan kosong untuk menggunakan server default." -"telegramChatId" = "ID Obrolan Admin" -"telegramChatIdDesc" = "ID Obrolan Admin Telegram. (dipisahkan koma)(dapatkan di sini @userinfobot) atau (gunakan perintah '/id' di bot)" -"telegramNotifyTime" = "Waktu Notifikasi" -"telegramNotifyTimeDesc" = "Waktu notifikasi bot Telegram yang diatur untuk laporan berkala. (gunakan format waktu crontab)" -"tgNotifyBackup" = "Cadangan Database" -"tgNotifyBackupDesc" = "Kirim berkas cadangan database dengan laporan." -"tgNotifyLogin" = "Notifikasi Login" -"tgNotifyLoginDesc" = "Dapatkan notifikasi tentang username, alamat IP, dan waktu setiap kali seseorang mencoba masuk ke panel web Anda." -"sessionMaxAge" = "Durasi Sesi" -"sessionMaxAgeDesc" = "Durasi di mana Anda dapat tetap masuk. (unit: menit)" -"expireTimeDiff" = "Notifikasi Tanggal Kedaluwarsa" -"expireTimeDiffDesc" = "Dapatkan notifikasi tentang tanggal kedaluwarsa saat mencapai ambang batas ini. (unit: hari)" -"trafficDiff" = "Notifikasi Batas Traffic" -"trafficDiffDesc" = "Dapatkan notifikasi tentang batas traffic saat mencapai ambang batas ini. (unit: GB)" -"tgNotifyCpu" = "Notifikasi Beban CPU" -"tgNotifyCpuDesc" = "Dapatkan notifikasi jika beban CPU melebihi ambang batas ini. (unit: %)" -"timeZone" = "Zone Waktu" -"timeZoneDesc" = "Tugas terjadwal akan berjalan berdasarkan zona waktu ini." -"subSettings" = "Langganan" -"subEnable" = "Aktifkan Layanan Langganan" -"subEnableDesc" = "Mengaktifkan layanan langganan." -"subListen" = "IP Pendengar" -"subListenDesc" = "Alamat IP untuk layanan langganan. (biarkan kosong untuk mendengarkan semua IP)" -"subPort" = "Port Pendengar" -"subPortDesc" = "Nomor port untuk layanan langganan. (harus menjadi port yang tidak digunakan)" -"subCertPath" = "Path Kunci Publik" -"subCertPathDesc" = "Path berkas kunci publik untuk layanan langganan. (dimulai dengan ‘/‘)" -"subKeyPath" = "Path Kunci Privat" -"subKeyPathDesc" = "Path berkas kunci privat untuk layanan langganan. (dimulai dengan ‘/‘)" -"subPath" = "URI Path" -"subPathDesc" = "URI path untuk layanan langganan. (dimulai dengan ‘/‘ dan diakhiri dengan ‘/‘)" -"subDomain" = "Domain Pendengar" -"subDomainDesc" = "Nama domain untuk layanan langganan. (biarkan kosong untuk mendengarkan semua domain dan IP)" -"subUpdates" = "Interval Pembaruan" -"subUpdatesDesc" = "Interval pembaruan URL langganan dalam aplikasi klien. (unit: jam)" -"subEncrypt" = "Encode" -"subEncryptDesc" = "Konten yang dikembalikan dari layanan langganan akan dienkripsi Base64." -"subShowInfo" = "Tampilkan Info Penggunaan" -"subShowInfoDesc" = "Sisa traffic dan tanggal akan ditampilkan di aplikasi klien." -"subURI" = "URI Proxy Terbalik" -"subURIDesc" = "URI path URL langganan untuk penggunaan di belakang proxy." -"fragment" = "Fragmentasi" -"fragmentDesc" = "Aktifkan fragmentasi untuk paket hello TLS" -"fragmentSett" = "Pengaturan Fragmentasi" -"noisesDesc" = "Aktifkan Noises." -"noisesSett" = "Pengaturan Noises" -"mux" = "Mux" -"muxDesc" = "Mengirimkan beberapa aliran data independen dalam aliran data yang sudah ada." -"muxSett" = "Pengaturan Mux" -"direct" = "Koneksi langsung" -"directDesc" = "Secara langsung membuat koneksi dengan domain atau rentang IP negara tertentu." - - -[pages.xray] -"title" = "Konfigurasi Xray" -"save" = "Simpan" -"restart" = "Restart Xray" -"basicTemplate" = "Dasar" -"advancedTemplate" = "Lanjutan" -"generalConfigs" = "Strategi Umum" -"generalConfigsDesc" = "Opsi ini akan menentukan penyesuaian strategi umum." -"logConfigs" = "Catatan" -"logConfigsDesc" = "Log dapat mempengaruhi efisiensi server Anda. Disarankan untuk mengaktifkannya dengan bijak hanya jika diperlukan" -"blockConfigs" = "Pelindung" -"blockConfigsDesc" = "Opsi ini akan memblokir lalu lintas berdasarkan protokol dan situs web yang diminta." -"basicRouting" = "Perutean Dasar" -"blockConnectionsConfigsDesc" = "Opsi ini akan memblokir lalu lintas berdasarkan negara yang diminta." -"directConnectionsConfigsDesc" = "Koneksi langsung memastikan bahwa lalu lintas tertentu tidak dialihkan melalui server lain." -"blockips" = "Blokir IP" -"blockdomains" = "Blokir Domain" -"directips" = "IP Langsung" -"directdomains" = "Domain Langsung" -"ipv4Routing" = "Perutean IPv4" -"ipv4RoutingDesc" = "Opsi ini akan mengalihkan lalu lintas berdasarkan tujuan tertentu melalui IPv4." -"warpRouting" = "Perutean WARP" -"warpRoutingDesc" = "Opsi ini akan mengalihkan lalu lintas berdasarkan tujuan tertentu melalui WARP." -"Template" = "Template Konfigurasi Xray Lanjutan" -"TemplateDesc" = "File konfigurasi Xray akhir akan dibuat berdasarkan template ini." -"FreedomStrategy" = "Strategi Protokol Freedom" -"FreedomStrategyDesc" = "Atur strategi output untuk jaringan dalam Protokol Freedom." -"RoutingStrategy" = "Strategi Pengalihan Keseluruhan" -"RoutingStrategyDesc" = "Atur strategi pengalihan lalu lintas keseluruhan untuk menyelesaikan semua permintaan." -"Torrent" = "Blokir Protokol BitTorrent" -"TorrentDesc" = "Memblokir protokol BitTorrent." -"Family" = "Proteksi Keluarga" -"FamilyDesc" = "Memblokir konten dewasa dan situs web berbahaya." -"Inbounds" = "Masuk" -"InboundsDesc" = "Menerima klien tertentu." -"Outbounds" = "Keluar" -"Balancers" = "Penyeimbang" -"OutboundsDesc" = "Atur jalur lalu lintas keluar." -"Routings" = "Aturan Pengalihan" -"RoutingsDesc" = "Prioritas setiap aturan penting!" -"completeTemplate" = "Semua" -"logLevel" = "Tingkat Log" -"logLevelDesc" = "Tingkat log untuk log kesalahan, menunjukkan informasi yang perlu dicatat." -"accessLog" = "Log Akses" -"accessLogDesc" = "Jalur file untuk log akses. Nilai khusus 'tidak ada' menonaktifkan log akses" -"errorLog" = "Catatan eror" -"errorLogDesc" = "Jalur file untuk log kesalahan. Nilai khusus 'tidak ada' menonaktifkan log kesalahan" -"dnsLog" = "Log DNS" -"dnsLogDesc" = "Apakah akan mengaktifkan log kueri DNS" -"outboundTraffic" = "Lalu Lintas Keluar" -"outboundTrafficDesc" = "Apakah mengaktifkan lalu lintas keluar" -"maskAddress" = "Alamat Masker" -"maskAddressDesc" = "Masker alamat IP, ketika diaktifkan, akan secara otomatis mengganti alamat IP yang muncul di log." - -[pages.xray.rules] -"first" = "Pertama" -"last" = "Terakhir" -"up" = "Naik" -"down" = "Turun" -"source" = "Sumber" -"dest" = "Tujuan" -"inbound" = "Masuk" -"outbound" = "Keluar" -"balancer" = "Pengimbang" -"info" = "Info" -"add" = "Tambahkan Aturan" -"edit" = "Edit Aturan" -"useComma" = "Item yang dipisahkan koma" - -[pages.xray.outbound] -"addOutbound" = "Tambahkan Keluar" -"addReverse" = "Tambahkan Revers" -"editOutbound" = "Edit Keluar" -"editReverse" = "Edit Revers" -"tag" = "Tag" -"tagDesc" = "Tag Unik" -"address" = "Alamat" -"reverse" = "Revers" -"domain" = "Domain" -"type" = "Tipe" -"bridge" = "Jembatan" -"portal" = "Portal" -"intercon" = "Interkoneksi" -"settings" = "Pengaturan" -"accountInfo" = "Informasi Akun" -"outboundStatus" = "Status Keluar" -"sendThrough" = "Kirim Melalui" - -[pages.xray.balancer] -"addBalancer" = "Tambahkan Penyeimbang" -"editBalancer" = "Sunting Penyeimbang" -"balancerStrategy" = "Strategi" -"balancerSelectors" = "Penyeleksi" -"tag" = "Menandai" -"tagDesc" = "Label Unik" -"balancerDesc" = "BalancerTag dan outboundTag tidak dapat digunakan secara bersamaan. Jika digunakan secara bersamaan, hanya outboundTag yang akan berfungsi." - -[pages.xray.wireguard] -"secretKey" = "Kunci Rahasia" -"publicKey" = "Kunci Publik" -"allowedIPs" = "IP yang Diizinkan" -"endpoint" = "Titik Akhir" -"psk" = "Kunci Pra-Bagi" -"domainStrategy" = "Strategi Domain" - -[pages.xray.dns] -"enable" = "Aktifkan DNS" -"enableDesc" = "Aktifkan server DNS bawaan" -"tag" = "Tanda DNS Masuk" -"tagDesc" = "Tanda ini akan tersedia sebagai tanda masuk dalam aturan penataan." -"strategy" = "Strategi Kueri" -"strategyDesc" = "Strategi keseluruhan untuk menyelesaikan nama domain" -"add" = "Tambahkan Server" -"edit" = "Sunting Server" -"domains" = "Domains" -"expectIPs" = "IP yang Diharapkan" - -[pages.xray.fakedns] -"add" = "Tambahkan DNS Palsu" -"edit" = "Edit DNS Palsu" -"ipPool" = "Subnet Kumpulan IP" -"poolSize" = "Ukuran Kolam" - -[pages.settings.security] -"admin" = "Admin" -"secret" = "Token Rahasia" -"loginSecurity" = "Login Aman" -"loginSecurityDesc" = "Menambahkan lapisan otentikasi tambahan untuk memberikan keamanan lebih." -"secretToken" = "Token Rahasia" -"secretTokenDesc" = "Simpan token ini dengan aman di tempat yang aman. Token ini diperlukan untuk login dan tidak dapat dipulihkan." - -[pages.settings.toasts] -"modifySettings" = "Ubah Pengaturan" -"getSettings" = "Dapatkan Pengaturan" -"modifyUser" = "Ubah Admin" -"originalUserPassIncorrect" = "Username atau password saat ini tidak valid" -"userPassMustBeNotEmpty" = "Username dan password baru tidak boleh kosong" - -[tgbot] -"keyboardClosed" = "❌ Papan ketik kustom ditutup!" -"noResult" = "❗ Tidak ada hasil!" -"noQuery" = "❌ Permintaan tidak ditemukan! Harap gunakan perintah lagi!" -"wentWrong" = "❌ Ada yang salah!" -"noIpRecord" = "❗ Tidak ada Catatan IP!" -"noInbounds" = "❗ Tidak ada masuk ditemukan!" -"unlimited" = "♾ Tak terbatas" -"add" = "Tambah" -"month" = "Bulan" -"months" = "Bulan" -"day" = "Hari" -"days" = "Hari" -"hours" = "Jam" -"unknown" = "Tidak diketahui" -"inbounds" = "Masuk" -"clients" = "Klien" -"offline" = "🔴 Offline" -"online" = "🟢 Online" - -[tgbot.commands] -"unknown" = "❗ Perintah tidak dikenal." -"pleaseChoose" = "👇 Harap pilih:\r\n" -"help" = "🤖 Selamat datang di bot ini! Ini dirancang untuk menyediakan data tertentu dari panel web dan memungkinkan Anda melakukan modifikasi sesuai kebutuhan.\r\n\r\n" -"start" = "👋 Halo {{ .Firstname }}.\r\n" -"welcome" = "🤖 Selamat datang di {{.Hostname }} bot managemen.\r\n" -"status" = "✅ Bot dalam keadaan baik!" -"usage" = "❗ Harap berikan teks untuk mencari!" -"getID" = "🆔 ID Anda: {{ .ID }}" -"helpAdminCommands" = "Untuk memulai ulang Xray Core:\r\n/restart force\r\n\r\nUntuk mencari email klien:\r\n/usage [Email]\r\n\r\nUntuk mencari inbound (dengan statistik klien):\r\n/inbound [Catatan]\r\n\r\nID Obrolan Telegram:\r\n/id" -"helpClientCommands" = "Untuk mencari statistik, gunakan perintah berikut:\r\n/usage [Email]\r\n\r\nID Obrolan Telegram:\r\n/id" -"restartUsage" = "\r\n\r\n/restart force" -"restartSuccess" = "✅ Operasi berhasil!" -"restartFailed" = "❗ Kesalahan dalam operasi.\r\n\r\nError: {{ .Error }}." -"xrayNotRunning" = "❗ Xray Core tidak berjalan." - -[tgbot.messages] -"cpuThreshold" = "🔴 Beban CPU {{ .Percent }}% melebihi batas {{ .Threshold }}%" -"selectUserFailed" = "❌ Kesalahan dalam pemilihan pengguna!" -"userSaved" = "✅ Pengguna Telegram tersimpan." -"loginSuccess" = "✅ Berhasil masuk ke panel.\r\n" -"loginFailed" = "❗️ Gagal masuk ke panel.\r\n" -"report" = "🕰 Laporan Terjadwal: {{ .RunTime }}\r\n" -"datetime" = "⏰ Tanggal & Waktu: {{ .DateTime }}\r\n" -"hostname" = "💻 Host: {{ .Hostname }}\r\n" -"version" = "🚀 Versi 3X-UI: {{ .Version }}\r\n" -"xrayVersion" = "📡 Versi Xray: {{ .XrayVersion }}\r\n" -"ipv6" = "🌐 IPv6: {{ .IPv6 }}\r\n" -"ipv4" = "🌐 IPv4: {{ .IPv4 }}\r\n" -"ip" = "🌐 IP: {{ .IP }}\r\n" -"ips" = "🔢 IP:\r\n{{ .IPs }}\r\n" -"serverUpTime" = "⏳ Waktu Aktif: {{ .UpTime }} {{ .Unit }}\r\n" -"serverLoad" = "📈 Beban Sistem: {{ .Load1 }}, {{ .Load2 }}, {{ .Load3 }}\r\n" -"serverMemory" = "📋 RAM: {{ .Current }}/{{ .Total }}\r\n" -"tcpCount" = "🔹 TCP: {{ .Count }}\r\n" -"udpCount" = "🔸 UDP: {{ .Count }}\r\n" -"traffic" = "🚦 Lalu Lintas: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n" -"xrayStatus" = "ℹ️ Status: {{ .State }}\r\n" -"username" = "👤 Nama Pengguna: {{ .Username }}\r\n" -"password" = "👤 Kata Sandi: {{ .Password }}\r\n" -"time" = "⏰ Waktu: {{ .Time }}\r\n" -"inbound" = "📍 Inbound: {{ .Remark }}\r\n" -"port" = "🔌 Port: {{ .Port }}\r\n" -"expire" = "📅 Tanggal Kadaluarsa: {{ .Time }}\r\n" -"expireIn" = "📅 Kadaluarsa Dalam: {{ .Time }}\r\n" -"active" = "💡 Aktif: {{ .Enable }}\r\n" -"enabled" = "🚨 Diaktifkan: {{ .Enable }}\r\n" -"online" = "🌐 Status Koneksi: {{ .Status }}\r\n" -"email" = "📧 Email: {{ .Email }}\r\n" -"upload" = "🔼 Unggah: ↑{{ .Upload }}\r\n" -"download" = "🔽 Unduh: ↓{{ .Download }}\r\n" -"total" = "📊 Total: ↑↓{{ .UpDown }} / {{ .Total }}\r\n" -"TGUser" = "👤 Pengguna Telegram: {{ .TelegramID }}\r\n" -"exhaustedMsg" = "🚨 Habis {{ .Type }}:\r\n" -"exhaustedCount" = "🚨 Jumlah Habis {{ .Type }}:\r\n" -"onlinesCount" = "🌐 Klien Online: {{ .Count }}\r\n" -"disabled" = "🛑 Dinonaktifkan: {{ .Disabled }}\r\n" -"depleteSoon" = "🔜 Habis Sebentar: {{ .Deplete }}\r\n\r\n" -"backupTime" = "🗄 Waktu Backup: {{ .Time }}\r\n" -"refreshedOn" = "\r\n📋🔄 Diperbarui Pada: {{ .Time }}\r\n\r\n" -"yes" = "✅ Ya" -"no" = "❌ Tidak" - -[tgbot.buttons] -"closeKeyboard" = "❌ Tutup Papan Ketik" -"cancel" = "❌ Batal" -"cancelReset" = "❌ Batal Reset" -"cancelIpLimit" = "❌ Batal Batas IP" -"confirmResetTraffic" = "✅ Konfirmasi Reset Lalu Lintas?" -"confirmClearIps" = "✅ Konfirmasi Hapus IPs?" -"confirmRemoveTGUser" = "✅ Konfirmasi Hapus Pengguna Telegram?" -"confirmToggle" = "✅ Konfirmasi Aktifkan/Nonaktifkan Pengguna?" -"dbBackup" = "Dapatkan Cadangan DB" -"serverUsage" = "Penggunaan Server" -"getInbounds" = "Dapatkan Inbounds" -"depleteSoon" = "Habis Sebentar" -"clientUsage" = "Dapatkan Penggunaan" -"onlines" = "Klien Online" -"commands" = "Perintah" -"refresh" = "🔄 Perbarui" -"clearIPs" = "❌ Hapus IPs" -"removeTGUser" = "❌ Hapus Pengguna Telegram" -"selectTGUser" = "👤 Pilih Pengguna Telegram" -"selectOneTGUser" = "👤 Pilih Pengguna Telegram:" -"resetTraffic" = "📈 Reset Lalu Lintas" -"resetExpire" = "📅 Ubah Tanggal Kadaluarsa" -"ipLog" = "🔢 Log IP" -"ipLimit" = "🔢 Batas IP" -"setTGUser" = "👤 Set Pengguna Telegram" -"toggle" = "🔘 Aktifkan / Nonaktifkan" -"custom" = "🔢 Kustom" -"confirmNumber" = "✅ Konfirmasi: {{ .Num }}" -"confirmNumberAdd" = "✅ Konfirmasi menambahkan: {{ .Num }}" -"limitTraffic" = "🚧 Batas Lalu Lintas" -"getBanLogs" = "Dapatkan Log Pemblokiran" -"allClients" = "Semua Klien" - -[tgbot.answers] -"successfulOperation" = "✅ Operasi berhasil!" -"errorOperation" = "❗ Kesalahan dalam operasi." -"getInboundsFailed" = "❌ Gagal mendapatkan inbounds." -"getClientsFailed" = "❌ Gagal mendapatkan klien." -"canceled" = "❌ {{ .Email }}: Operasi dibatalkan." -"clientRefreshSuccess" = "✅ {{ .Email }}: Klien diperbarui dengan berhasil." -"IpRefreshSuccess" = "✅ {{ .Email }}: IP diperbarui dengan berhasil." -"TGIdRefreshSuccess" = "✅ {{ .Email }}: Pengguna Telegram Klien diperbarui dengan berhasil." -"resetTrafficSuccess" = "✅ {{ .Email }}: Lalu lintas direset dengan berhasil." -"setTrafficLimitSuccess" = "✅ {{ .Email }}: Batas lalu lintas disimpan dengan berhasil." -"expireResetSuccess" = "✅ {{ .Email }}: Hari kadaluarsa direset dengan berhasil." -"resetIpSuccess" = "✅ {{ .Email }}: Batas IP {{ .Count }} disimpan dengan berhasil." -"clearIpSuccess" = "✅ {{ .Email }}: IP dihapus dengan berhasil." -"getIpLog" = "✅ {{ .Email }}: Dapatkan Log IP." -"getUserInfo" = "✅ {{ .Email }}: Dapatkan Info Pengguna Telegram." -"removedTGUserSuccess" = "✅ {{ .Email }}: Pengguna Telegram dihapus dengan berhasil." -"enableSuccess" = "✅ {{ .Email }}: Diaktifkan dengan berhasil." -"disableSuccess" = "✅ {{ .Email }}: Dinonaktifkan dengan berhasil." -"askToAddUserId" = "Konfigurasi Anda tidak ditemukan!\r\nSilakan minta admin Anda untuk menggunakan ChatID Telegram Anda dalam konfigurasi Anda.\r\n\r\nChatID Pengguna Anda: {{ .TgUserID }}" -"chooseClient" = "Pilih Klien untuk Inbound {{ .Inbound }}" -"chooseInbound" = "Pilih Inbound" diff --git a/web/translation/translate.ja_JP.toml b/web/translation/translate.ja_JP.toml deleted file mode 100644 index db024b3e..00000000 --- a/web/translation/translate.ja_JP.toml +++ /dev/null @@ -1,598 +0,0 @@ -"username" = "ユーザー名" -"password" = "パスワード" -"login" = "ログイン" -"confirm" = "確認" -"cancel" = "キャンセル" -"close" = "閉じる" -"copy" = "コピー" -"copied" = "コピー済み" -"download" = "ダウンロード" -"remark" = "備考" -"enable" = "有効化" -"protocol" = "プロトコル" -"search" = "検索" -"filter" = "フィルター" -"loading" = "読み込み中..." -"second" = "秒" -"minute" = "分" -"hour" = "時間" -"day" = "日" -"check" = "確認" -"indefinite" = "無期限" -"unlimited" = "無制限" -"none" = "なし" -"qrCode" = "QRコード" -"info" = "詳細情報" -"edit" = "編集" -"delete" = "削除" -"reset" = "リセット" -"copySuccess" = "コピー成功" -"sure" = "確定" -"encryption" = "暗号化" -"transmission" = "伝送" -"host" = "ホスト" -"path" = "パス" -"camouflage" = "偽装" -"status" = "ステータス" -"enabled" = "有効" -"disabled" = "無効" -"depleted" = "消耗済み" -"depletingSoon" = "間もなく消耗" -"offline" = "オフライン" -"online" = "オンライン" -"domainName" = "ドメイン名" -"monitor" = "監視" -"certificate" = "証明書" -"fail" = "失敗" -"comment" = "コメント" -"success" = "成功" -"getVersion" = "バージョン取得" -"install" = "インストール" -"clients" = "クライアント" -"usage" = "利用状況" -"secretToken" = "シークレットトークン" -"remained" = "残り" -"security" = "セキュリティ" -"secAlertTitle" = "セキュリティアラート" -"secAlertSsl" = "この接続は安全ではありません。TLSを有効にしてデータ保護を行うまで、機密情報を入力しないでください。" -"secAlertConf" = "一部の設定は脆弱です。潜在的な脆弱性を防ぐために、セキュリティプロトコルを強化することをお勧めします。" -"secAlertSSL" = "セキュアな接続がありません。データ保護のためにTLS証明書をインストールしてください。" -"secAlertPanelPort" = "デフォルトのポートにはセキュリティリスクがあります。ランダムなポートまたは特定のポートを設定してください。" -"secAlertPanelURI" = "デフォルトのURIパスは安全ではありません。複雑なURIパスを設定してください。" -"secAlertSubURI" = "サブスクリプションのデフォルトURIパスは安全ではありません。複雑なURIパスを設定してください。" -"secAlertSubJsonURI" = "JSONサブスクリプションのデフォルトURIパスは安全ではありません。複雑なURIパスを設定してください。" - -[menu] -"dashboard" = "ダッシュボード" -"inbounds" = "インバウンド一覧" -"settings" = "パネル設定" -"xray" = "Xray設定" -"logout" = "ログアウト" -"link" = "リンク管理" - -[pages.login] -"hello" = "こんにちは" -"title" = "ようこそ" -"loginAgain" = "ログインセッションが切れました。再度ログインしてください。" - -[pages.login.toasts] -"invalidFormData" = "データ形式エラー" -"emptyUsername" = "ユーザー名を入力してください" -"emptyPassword" = "パスワードを入力してください" -"wrongUsernameOrPassword" = "ユーザー名またはパスワードが間違っています" -"successLogin" = "ログイン成功" - -[pages.index] -"title" = "システムステータス" -"memory" = "メモリ" -"hard" = "ハードディスク" -"xrayStatus" = "Xray" -"stopXray" = "停止" -"restartXray" = "再起動" -"xraySwitch" = "バージョン" -"xraySwitchClick" = "切り替えるバージョンを選択してください" -"xraySwitchClickDesk" = "慎重に選択してください。古いバージョンは現在の設定と互換性がない可能性があります。" -"operationHours" = "システム稼働時間" -"systemLoad" = "システム負荷" -"systemLoadDesc" = "過去1、5、15分間のシステム平均負荷" -"connectionTcpCountDesc" = "システム内のすべてのTCP接続数" -"connectionUdpCountDesc" = "システム内のすべてのUDP接続数" -"connectionCount" = "接続数" -"upSpeed" = "総アップロード速度" -"downSpeed" = "総ダウンロード速度" -"totalSent" = "システム起動以降の送信データ量" -"totalReceive" = "システム起動以降の受信データ量" -"xraySwitchVersionDialog" = "Xrayバージョン切り替え" -"xraySwitchVersionDialogDesc" = "Xrayのバージョンを切り替えますか?" -"dontRefresh" = "インストール中、このページをリロードしないでください" -"logs" = "ログ" -"config" = "設定" -"backup" = "バックアップと復元" -"backupTitle" = "データベースのバックアップと復元" -"backupDescription" = "データベースを復元する前にバックアップすることをお勧めします" -"exportDatabase" = "バックアップ" -"importDatabase" = "復元" - -[pages.inbounds] -"title" = "インバウンド一覧" -"totalDownUp" = "総アップロード / ダウンロード" -"totalUsage" = "総使用量" -"inboundCount" = "インバウンド数" -"operate" = "メニュー" -"enable" = "有効化" -"remark" = "備考" -"protocol" = "プロトコル" -"port" = "ポート" -"traffic" = "トラフィック" -"details" = "詳細情報" -"transportConfig" = "トランスポート設定" -"expireDate" = "有効期限" -"resetTraffic" = "トラフィックリセット" -"addInbound" = "インバウンド追加" -"generalActions" = "一般操作" -"create" = "追加" -"update" = "更新" -"modifyInbound" = "インバウンド修正" -"deleteInbound" = "インバウンド削除" -"deleteInboundContent" = "インバウンドを削除してもよろしいですか?" -"deleteClient" = "クライアント削除" -"deleteClientContent" = "クライアントを削除してもよろしいですか?" -"resetTrafficContent" = "トラフィックをリセットしてもよろしいですか?" -"copyLink" = "リンクをコピー" -"address" = "アドレス" -"network" = "ネットワーク" -"destinationPort" = "宛先ポート" -"targetAddress" = "宛先アドレス" -"monitorDesc" = "空白にするとすべてのIPを監視" -"meansNoLimit" = "= 無制限(単位:GB)" -"totalFlow" = "総トラフィック" -"leaveBlankToNeverExpire" = "空白にすると期限なし" -"noRecommendKeepDefault" = "デフォルト値を保持することをお勧めします" -"certificatePath" = "ファイルパス" -"certificateContent" = "ファイル内容" -"publicKey" = "公開鍵" -"privatekey" = "秘密鍵" -"clickOnQRcode" = "QRコードをクリックしてコピー" -"client" = "クライアント" -"export" = "リンクエクスポート" -"clone" = "複製" -"cloneInbound" = "複製" -"cloneInboundContent" = "このインバウンドルールは、ポート(Port)、リスニングIP(Listening IP)、クライアント(Clients)を除くすべての設定がクローンされます" -"cloneInboundOk" = "クローン作成" -"resetAllTraffic" = "すべてのインバウンドトラフィックをリセット" -"resetAllTrafficTitle" = "すべてのインバウンドトラフィックをリセット" -"resetAllTrafficContent" = "すべてのインバウンドトラフィックをリセットしてもよろしいですか?" -"resetInboundClientTraffics" = "クライアントトラフィックをリセット" -"resetInboundClientTrafficTitle" = "すべてのクライアントトラフィックをリセット" -"resetInboundClientTrafficContent" = "このインバウンドクライアントのすべてのトラフィックをリセットしてもよろしいですか?" -"resetAllClientTraffics" = "すべてのクライアントトラフィックをリセット" -"resetAllClientTrafficTitle" = "すべてのクライアントトラフィックをリセット" -"resetAllClientTrafficContent" = "すべてのクライアントのトラフィックをリセットしてもよろしいですか?" -"delDepletedClients" = "トラフィックが尽きたクライアントを削除" -"delDepletedClientsTitle" = "トラフィックが尽きたクライアントを削除" -"delDepletedClientsContent" = "トラフィックが尽きたすべてのクライアントを削除してもよろしいですか?" -"email" = "メールアドレス" -"emailDesc" = "メールアドレスは一意でなければなりません" -"IPLimit" = "IP制限" -"IPLimitDesc" = "設定値を超えるとインバウンドトラフィックが無効になります。(0 = 無効)" -"IPLimitlog" = "IPログ" -"IPLimitlogDesc" = "IP履歴ログ(無効なインバウンドトラフィックを有効にするには、ログをクリアしてください)" -"IPLimitlogclear" = "ログをクリア" -"setDefaultCert" = "パネル設定から証明書を設定" -"telegramDesc" = "TelegramチャットIDを提供してください。(ボットで'/id'コマンドを使用)または(@userinfobot)" -"subscriptionDesc" = "サブスクリプションURLを見つけるには、“詳細情報”に移動してください。また、複数のクライアントに同じ名前を使用することができます。" -"info" = "情報" -"same" = "同じ" -"inboundData" = "インバウンドデータ" -"exportInbound" = "インバウンドルールをエクスポート" -"import" = "インポート" -"importInbound" = "インバウンドルールをインポート" - -[pages.client] -"add" = "クライアント追加" -"edit" = "クライアント編集" -"submitAdd" = "クライアント追加" -"submitEdit" = "変更を保存" -"clientCount" = "クライアント数" -"bulk" = "一括作成" -"method" = "方法" -"first" = "最初" -"last" = "最後" -"prefix" = "プレフィックス" -"postfix" = "サフィックス" -"delayedStart" = "初回使用後に開始" -"expireDays" = "期間" -"days" = "日" -"renew" = "自動更新" -"renewDesc" = "期限が切れた後に自動更新。(0 = 無効)(単位:日)" - -[pages.inbounds.toasts] -"obtain" = "取得" - -[pages.inbounds.stream.general] -"request" = "リクエスト" -"response" = "レスポンス" -"name" = "名前" -"value" = "値" - -[pages.inbounds.stream.tcp] -"version" = "バージョン" -"method" = "方法" -"path" = "パス" -"status" = "ステータス" -"statusDescription" = "ステータス説明" -"requestHeader" = "リクエストヘッダー" -"responseHeader" = "レスポンスヘッダー" - -[pages.settings] -"title" = "パネル設定" -"save" = "保存" -"infoDesc" = "ここでのすべての変更は、保存してパネルを再起動する必要があります" -"restartPanel" = "パネル再起動" -"restartPanelDesc" = "パネルを再起動してもよろしいですか?再起動後にパネルにアクセスできない場合は、サーバーでパネルログを確認してください" -"actions" = "操作" -"resetDefaultConfig" = "デフォルト設定にリセット" -"panelSettings" = "一般" -"securitySettings" = "セキュリティ設定" -"TGBotSettings" = "Telegramボット設定" -"panelListeningIP" = "パネル監視IP" -"panelListeningIPDesc" = "デフォルトではすべてのIPを監視する" -"panelListeningDomain" = "パネル監視ドメイン" -"panelListeningDomainDesc" = "デフォルトで空白の場合、すべてのドメインとIPアドレスを監視する" -"panelPort" = "パネル監視ポート" -"panelPortDesc" = "再起動で有効" -"publicKeyPath" = "パネル証明書公開鍵ファイルパス" -"publicKeyPathDesc" = "'/'で始まる絶対パスを入力" -"privateKeyPath" = "パネル証明書秘密鍵ファイルパス" -"privateKeyPathDesc" = "'/'で始まる絶対パスを入力" -"panelUrlPath" = "パネルURLルートパス" -"panelUrlPathDesc" = "'/'で始まり、'/'で終わる必要があります" -"pageSize" = "ページサイズ" -"pageSizeDesc" = "インバウンドテーブルのページサイズを定義します。0を設定すると無効化されます" -"remarkModel" = "備考モデルと区切り記号" -"datepicker" = "日付ピッカー" -"datepickerPlaceholder" = "日付を選択" -"datepickerDescription" = "日付選択カレンダーで有効期限を指定する" -"sampleRemark" = "備考の例" -"oldUsername" = "旧ユーザー名" -"currentPassword" = "旧パスワード" -"newUsername" = "新しいユーザー名" -"newPassword" = "新しいパスワード" -"telegramBotEnable" = "Telegramボットを有効にする" -"telegramBotEnableDesc" = "Telegramボット機能を有効にする" -"telegramToken" = "Telegramボットトークン" -"telegramTokenDesc" = "'@BotFather'から取得したTelegramボットトークン" -"telegramProxy" = "SOCKS5プロキシ" -"telegramProxyDesc" = "SOCKS5プロキシを有効にしてTelegramに接続する(ガイドに従って設定を調整)" -"telegramAPIServer" = "Telegram APIサーバー" -"telegramAPIServerDesc" = "使用するTelegram APIサーバー。空白の場合はデフォルトサーバーを使用する" -"telegramChatId" = "管理者チャットID" -"telegramChatIdDesc" = "Telegram管理者チャットID(複数の場合はカンマで区切る)@userinfobotで取得するか、ボットで'/id'コマンドを使用して取得する" -"telegramNotifyTime" = "通知時間" -"telegramNotifyTimeDesc" = "定期的なTelegramボット通知時間を設定する(crontab時間形式を使用)" -"tgNotifyBackup" = "データベースバックアップ" -"tgNotifyBackupDesc" = "レポート付きのデータベースバックアップファイルを送信" -"tgNotifyLogin" = "ログイン通知" -"tgNotifyLoginDesc" = "誰かがパネルにログインしようとしたときに、ユーザー名、IPアドレス、時間を表示する" -"sessionMaxAge" = "セッション期間" -"sessionMaxAgeDesc" = "ログイン状態を保持する期間(単位:分)" -"expireTimeDiff" = "有効期限通知のしきい値" -"expireTimeDiffDesc" = "このしきい値に達した場合、有効期限に関する通知を受け取る(単位:日)" -"trafficDiff" = "トラフィック消耗しきい値" -"trafficDiffDesc" = "このしきい値に達した場合、トラフィック消耗に関する通知を受け取る(単位:GB)" -"tgNotifyCpu" = "CPU負荷通知しきい値" -"tgNotifyCpuDesc" = "CPU負荷がこのしきい値を超えた場合、通知を受け取る(単位:%)" -"timeZone" = "タイムゾーン" -"timeZoneDesc" = "定時タスクはこのタイムゾーンの時間に従って実行される" -"subSettings" = "サブスクリプション設定" -"subEnable" = "サブスクリプションサービスを有効にする" -"subEnableDesc" = "サブスクリプションサービス機能を有効にする" -"subListen" = "監視IP" -"subListenDesc" = "サブスクリプションサービスが監視するIPアドレス(空白にするとすべてのIPを監視)" -"subPort" = "監視ポート" -"subPortDesc" = "サブスクリプションサービスが監視するポート番号(使用されていないポートである必要があります)" -"subCertPath" = "公開鍵パス" -"subCertPathDesc" = "サブスクリプションサービスで使用する公開鍵ファイルのパス('/'で始まる)" -"subKeyPath" = "秘密鍵パス" -"subKeyPathDesc" = "サブスクリプションサービスで使用する秘密鍵ファイルのパス('/'で始まる)" -"subPath" = "URIパス" -"subPathDesc" = "サブスクリプションサービスで使用するURIパス('/'で始まり、'/'で終わる)" -"subDomain" = "監視ドメイン" -"subDomainDesc" = "サブスクリプションサービスが監視するドメイン(空白にするとすべてのドメインとIPを監視)" -"subUpdates" = "更新間隔" -"subUpdatesDesc" = "クライアントアプリケーションでサブスクリプションURLの更新間隔(単位:時間)" -"subEncrypt" = "エンコード" -"subEncryptDesc" = "サブスクリプションサービスが返す内容をBase64エンコードする" -"subShowInfo" = "利用情報を表示" -"subShowInfoDesc" = "クライアントアプリで残りのトラフィックと日付情報を表示する" -"subURI" = "リバースプロキシURI" -"subURIDesc" = "プロキシ後ろのサブスクリプションURLのURIパスに使用する" -"fragment" = "フラグメント" -"fragmentDesc" = "TLS helloパケットのフラグメントを有効にする" -"fragmentSett" = "設定" -"noisesDesc" = "Noisesを有効にする" -"noisesSett" = "Noises設定" -"mux" = "マルチプレクサ" -"muxDesc" = "確立されたストリーム内で複数の独立したストリームを伝送する" -"muxSett" = "マルチプレクサ設定" -"direct" = "直接接続" -"directDesc" = "特定の国のドメインまたはIP範囲に直接接続する" - - -[pages.xray] -"title" = "Xray 設定" -"save" = "保存" -"restart" = "Xray 再起動" -"basicTemplate" = "基本設定" -"advancedTemplate" = "高度な設定" -"generalConfigs" = "一般設定" -"generalConfigsDesc" = "これらのオプションは一般設定を決定します" -"logConfigs" = "ログ" -"logConfigsDesc" = "ログはサーバーのパフォーマンスに影響を与える可能性があるため、必要な場合にのみ有効にすることをお勧めします" -"blockConfigs" = "防御フィルター" -"blockConfigsDesc" = "これらのオプションは、特定のプロトコルやウェブサイトへのユーザー接続をブロックします" -"basicRouting" = "基本ルーティング" -"blockConnectionsConfigsDesc" = "これらのオプションにより、特定のリクエスト元の国に基づいてトラフィックをブロックします。" -"directConnectionsConfigsDesc" = "直接接続により、特定のトラフィックが他のサーバーを経由しないようにします。" -"blockips" = "IPをブロック" -"blockdomains" = "ドメインをブロック" -"directips" = "直接IP" -"directdomains" = "直接ドメイン" -"ipv4Routing" = "IPv4 ルーティング" -"ipv4RoutingDesc" = "このオプションはIPv4のみを介してターゲットドメインへルーティングします" -"warpRouting" = "WARP ルーティング" -"warpRoutingDesc" = "注意:これらのオプションを使用する前に、パネルのGitHubの手順に従って、サーバーにsocks5プロキシモードでWARPをインストールしてください。WARPはCloudflareサーバー経由でトラフィックをウェブサイトにルーティングします。" -"Template" = "高度なXray設定テンプレート" -"TemplateDesc" = "最終的なXray設定ファイルはこのテンプレートに基づいて生成されます" -"FreedomStrategy" = "Freedom プロトコル戦略" -"FreedomStrategyDesc" = "Freedomプロトコル内のネットワークの出力戦略を設定する" -"RoutingStrategy" = "ルーティングドメイン戦略設定" -"RoutingStrategyDesc" = "DNS解決の全体的なルーティング戦略を設定する" -"Torrent" = "BitTorrent プロトコルをブロック" -"TorrentDesc" = "BitTorrentの使用を禁止する" -"Family" = "ファミリー保護" -"FamilyDesc" = "アダルトコンテンツや悪意のあるサイトをブロックする" -"Inbounds" = "インバウンドルール" -"InboundsDesc" = "特定のクライアントからのトラフィックを受け入れる" -"Outbounds" = "アウトバウンドルール" -"Balancers" = "負荷分散" -"OutboundsDesc" = "アウトバウンドトラフィックの送信方法を設定する" -"Routings" = "ルーティングルール" -"RoutingsDesc" = "各ルールの優先順位が重要です" -"completeTemplate" = "すべて" -"logLevel" = "ログレベル" -"logLevelDesc" = "エラーログのレベルを指定し、記録する情報を示します" -"accessLog" = "アクセスログ" -"accessLogDesc" = "アクセスログのファイルパス。特殊値 'none' はアクセスログを無効にします" -"errorLog" = "エラーログ" -"errorLogDesc" = "エラーログのファイルパス。特殊値 'none' はエラーログを無効にします" -"dnsLog" = "DNS ログ" -"dnsLogDesc" = "DNSクエリのログを有効にするかどうか" -"outboundTraffic" = "アウトバウンドトラフィック" -"outboundTrafficDesc" = "アウトバウンドトラフィックを有効にするかどうか" -"maskAddress" = "アドレスをマスク" -"maskAddressDesc" = "IPアドレスをマスクし、有効にするとログに表示されるIPアドレスを自動的に置き換えます" - -[pages.xray.rules] -"first" = "最初" -"last" = "最後" -"up" = "上へ" -"down" = "下へ" -"source" = "ソース" -"dest" = "宛先アドレス" -"inbound" = "インバウンド" -"outbound" = "アウトバウンド" -"balancer" = "負荷分散" -"info" = "情報" -"add" = "ルール追加" -"edit" = "ルール編集" -"useComma" = "カンマ区切りの項目" - -[pages.xray.outbound] -"addOutbound" = "アウトバウンド追加" -"addReverse" = "リバース追加" -"editOutbound" = "アウトバウンド編集" -"editReverse" = "リバース編集" -"tag" = "タグ" -"tagDesc" = "一意のタグ" -"address" = "アドレス" -"reverse" = "リバース" -"domain" = "ドメイン" -"type" = "タイプ" -"bridge" = "ブリッジ" -"portal" = "ポータル" -"intercon" = "インターコネクション" -"settings" = "設定" -"accountInfo" = "アカウント情報" -"outboundStatus" = "アウトバウンドステータス" -"sendThrough" = "送信経路" - -[pages.xray.balancer] -"addBalancer" = "負荷分散追加" -"editBalancer" = "負荷分散編集" -"balancerStrategy" = "戦略" -"balancerSelectors" = "セレクター" -"tag" = "タグ" -"tagDesc" = "一意のタグ" -"balancerDesc" = "balancerTagとoutboundTagは同時に使用できません。同時に使用された場合、outboundTagのみが有効になります。" - -[pages.xray.wireguard] -"secretKey" = "シークレットキー" -"publicKey" = "公開鍵" -"allowedIPs" = "許可されたIP" -"endpoint" = "エンドポイント" -"psk" = "共有キー" -"domainStrategy" = "ドメイン戦略" - -[pages.xray.dns] -"enable" = "DNSを有効にする" -"enableDesc" = "組み込みDNSサーバーを有効にする" -"tag" = "DNSインバウンドタグ" -"tagDesc" = "このタグはルーティングルールでインバウンドタグとして使用できます" -"strategy" = "クエリ戦略" -"strategyDesc" = "ドメイン名解決の全体的な戦略" -"add" = "サーバー追加" -"edit" = "サーバー編集" -"domains" = "ドメイン" -"expectIPs" = "期待されるIP" - -[pages.xray.fakedns] -"add" = "フェイクDNS追加" -"edit" = "フェイクDNS編集" -"ipPool" = "IPプールサブネット" -"poolSize" = "プールサイズ" - -[pages.settings.security] -"admin" = "管理者" -"secret" = "セキュリティトークン" -"loginSecurity" = "ログインセキュリティ" -"loginSecurityDesc" = "追加の認証を追加してセキュリティを向上させる" -"secretToken" = "セキュリティトークン" -"secretTokenDesc" = "このトークンを安全な場所に保管してください。このトークンはログインに使用され、紛失すると回復できません。" - -[pages.settings.toasts] -"modifySettings" = "設定を変更" -"getSettings" = "設定を取得" -"modifyUser" = "管理者を変更" -"originalUserPassIncorrect" = "旧ユーザー名または旧パスワードが間違っています" -"userPassMustBeNotEmpty" = "新しいユーザー名と新しいパスワードは空にできません" - -[tgbot] -"keyboardClosed" = "❌ カスタムキーボードが閉じられました!" -"noResult" = "❗ 結果がありません!" -"noQuery" = "❌ クエリが見つかりませんでした!もう一度コマンドを使用してください!" -"wentWrong" = "❌ 問題が発生しました!" -"noIpRecord" = "❗ IP記録がありません!" -"noInbounds" = "❗ インバウンド接続が見つかりません!" -"unlimited" = "♾ 無制限" -"add" = "追加" -"month" = "月" -"months" = "月" -"day" = "日" -"days" = "日" -"hours" = "時間" -"unknown" = "不明" -"inbounds" = "インバウンド接続" -"clients" = "クライアント" -"offline" = "🔴 オフライン" -"online" = "🟢 オンライン" - -[tgbot.commands] -"unknown" = "❗ 不明なコマンド" -"pleaseChoose" = "👇 選択してください:\r\n" -"help" = "🤖 このボットをご利用いただきありがとうございます!サーバーから特定のデータを提供し、必要な変更を行うことができます。\r\n\r\n" -"start" = "👋 こんにちは、{{ .Firstname }}。\r\n" -"welcome" = "🤖 {{ .Hostname }} 管理ボットへようこそ。\r\n" -"status" = "✅ ボットは正常に動作しています!" -"usage" = "❗ 検索するテキストを入力してください!" -"getID" = "🆔 あなたのIDは:{{ .ID }}" -"helpAdminCommands" = "Xray Coreを再起動するには:\r\n/restart force\r\n\r\nクライアントの電子メールを検索するには:\r\n/usage [電子メール]\r\n\r\nインバウンド(クライアントの統計情報を含む)を検索するには:\r\n/inbound [備考]\r\n\r\nTelegramチャットID:\r\n/id" -"helpClientCommands" = "統計情報を検索するには、次のコマンドを使用してください:\r\n/usage [電子メール]\r\n\r\nTelegramチャットID:\r\n/id" -"restartUsage" = "\r\n\r\n/restart force" -"restartSuccess" = "✅ 操作成功!" -"restartFailed" = "❗ 操作エラー。\r\n\r\nエラー: {{ .Error }}" -"xrayNotRunning" = "❗ Xray Core は動作していません。" - -[tgbot.messages] -"cpuThreshold" = "🔴 CPU使用率は{{ .Percent }}%、しきい値{{ .Threshold }}%を超えました" -"selectUserFailed" = "❌ ユーザーの選択に失敗しました!" -"userSaved" = "✅ Telegramユーザーが保存されました。" -"loginSuccess" = "✅ パネルに正常にログインしました。\r\n" -"loginFailed" = "❗️ パネルのログインに失敗しました。\r\n" -"report" = "🕰 定期報告:{{ .RunTime }}\r\n" -"datetime" = "⏰ 日時:{{ .DateTime }}\r\n" -"hostname" = "💻 ホスト名:{{ .Hostname }}\r\n" -"version" = "🚀 X-UI バージョン:{{ .Version }}\r\n" -"xrayVersion" = "📡 Xray バージョン: {{ .XrayVersion }}\r\n" -"ipv6" = "🌐 IPv6:{{ .IPv6 }}\r\n" -"ipv4" = "🌐 IPv4:{{ .IPv4 }}\r\n" -"ip" = "🌐 IP:{{ .IP }}\r\n" -"ips" = "🔢 IPアドレス:\r\n{{ .IPs }}\r\n" -"serverUpTime" = "⏳ サーバー稼働時間:{{ .UpTime }} {{ .Unit }}\r\n" -"serverLoad" = "📈 サーバー負荷:{{ .Load1 }}, {{ .Load2 }}, {{ .Load3 }}\r\n" -"serverMemory" = "📋 サーバーメモリ:{{ .Current }}/{{ .Total }}\r\n" -"tcpCount" = "🔹 TCP接続数:{{ .Count }}\r\n" -"udpCount" = "🔸 UDP接続数:{{ .Count }}\r\n" -"traffic" = "🚦 トラフィック:{{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n" -"xrayStatus" = "ℹ️ Xrayステータス:{{ .State }}\r\n" -"username" = "👤 ユーザー名:{{ .Username }}\r\n" -"password" = "👤 パスワード: {{ .Password }}\r\n" -"time" = "⏰ 時間:{{ .Time }}\r\n" -"inbound" = "📍 インバウンド:{{ .Remark }}\r\n" -"port" = "🔌 ポート:{{ .Port }}\r\n" -"expire" = "📅 有効期限:{{ .Time }}\r\n" -"expireIn" = "📅 残り時間:{{ .Time }}\r\n" -"active" = "💡 有効:{{ .Enable }}\r\n" -"enabled" = "🚨 有効化済み:{{ .Enable }}\r\n" -"online" = "🌐 接続ステータス:{{ .Status }}\r\n" -"email" = "📧 メール:{{ .Email }}\r\n" -"upload" = "🔼 アップロード↑:{{ .Upload }}\r\n" -"download" = "🔽 ダウンロード↓:{{ .Download }}\r\n" -"total" = "📊 合計:{{ .UpDown }} / {{ .Total }}\r\n" -"TGUser" = "👤 Telegramユーザー:{{ .TelegramID }}\r\n" -"exhaustedMsg" = "🚨 消耗済みの {{ .Type }}:\r\n" -"exhaustedCount" = "🚨 消耗済みの {{ .Type }} 数量:\r\n" -"onlinesCount" = "🌐 オンラインクライアント:{{ .Count }}\r\n" -"disabled" = "🛑 無効化:{{ .Disabled }}\r\n" -"depleteSoon" = "🔜 間もなく消耗:{{ .Deplete }}\r\n\r\n" -"backupTime" = "🗄 バックアップ時間:{{ .Time }}\r\n" -"refreshedOn" = "\r\n📋🔄 更新時間:{{ .Time }}\r\n\r\n" -"yes" = "✅ はい" -"no" = "❌ いいえ" - -[tgbot.buttons] -"closeKeyboard" = "❌ キーボードを閉じる" -"cancel" = "❌ キャンセル" -"cancelReset" = "❌ リセットをキャンセル" -"cancelIpLimit" = "❌ IP制限をキャンセル" -"confirmResetTraffic" = "✅ トラフィックをリセットしますか?" -"confirmClearIps" = "✅ IPをクリアしますか?" -"confirmRemoveTGUser" = "✅ Telegramユーザーを削除しますか?" -"confirmToggle" = "✅ ユーザーを有効/無効にしますか?" -"dbBackup" = "データベースバックアップを取得" -"serverUsage" = "サーバーの使用状況" -"getInbounds" = "インバウンド情報を取得" -"depleteSoon" = "間もなく消耗" -"clientUsage" = "使用状況を取得" -"onlines" = "オンラインクライアント" -"commands" = "コマンド" -"refresh" = "🔄 更新" -"clearIPs" = "❌ IPをクリア" -"removeTGUser" = "❌ Telegramユーザーを削除" -"selectTGUser" = "👤 Telegramユーザーを選択" -"selectOneTGUser" = "👤 1人のTelegramユーザーを選択:" -"resetTraffic" = "📈 トラフィックをリセット" -"resetExpire" = "📅 有効期限を変更" -"ipLog" = "🔢 IPログ" -"ipLimit" = "🔢 IP制限" -"setTGUser" = "👤 Telegramユーザーを設定" -"toggle" = "🔘 有効/無効" -"custom" = "🔢 カスタム" -"confirmNumber" = "✅ 確認: {{ .Num }}" -"confirmNumberAdd" = "✅ 追加を確認:{{ .Num }}" -"limitTraffic" = "🚧 トラフィック制限" -"getBanLogs" = "禁止ログ" -"allClients" = "すべてのクライアント" - -[tgbot.answers] -"successfulOperation" = "✅ 成功!" -"errorOperation" = "❗ 操作エラー。" -"getInboundsFailed" = "❌ インバウンド情報の取得に失敗しました。" -"getClientsFailed" = "❌ クライアントの取得に失敗しました。" -"canceled" = "❌ {{ .Email }}:操作がキャンセルされました。" -"clientRefreshSuccess" = "✅ {{ .Email }}:クライアントが正常に更新されました。" -"IpRefreshSuccess" = "✅ {{ .Email }}:IPが正常に更新されました。" -"TGIdRefreshSuccess" = "✅ {{ .Email }}:クライアントのTelegramユーザーが正常に更新されました。" -"resetTrafficSuccess" = "✅ {{ .Email }}:トラフィックが正常にリセットされました。" -"setTrafficLimitSuccess" = "✅ {{ .Email }}:トラフィック制限が正常に保存されました。" -"expireResetSuccess" = "✅ {{ .Email }}:有効期限の日数が正常にリセットされました。" -"resetIpSuccess" = "✅ {{ .Email }}:IP制限数が正常に保存されました:{{ .Count }}。" -"clearIpSuccess" = "✅ {{ .Email }}:IPが正常にクリアされました。" -"getIpLog" = "✅ {{ .Email }}:IPログの取得。" -"getUserInfo" = "✅ {{ .Email }}:Telegramユーザー情報の取得。" -"removedTGUserSuccess" = "✅ {{ .Email }}:Telegramユーザーが正常に削除されました。" -"enableSuccess" = "✅ {{ .Email }}:正常に有効化されました。" -"disableSuccess" = "✅ {{ .Email }}:正常に無効化されました。" -"askToAddUserId" = "設定が見つかりませんでした!\r\n管理者に問い合わせて、設定にTelegramユーザーのChatIDを使用してください。\r\n\r\nあなたのユーザーChatID:{{ .TgUserID }}" -"chooseClient" = "インバウンド {{ .Inbound }} のクライアントを選択" -"chooseInbound" = "インバウンドを選択" diff --git a/web/translation/translate.pt_BR.toml b/web/translation/translate.pt_BR.toml deleted file mode 100644 index bc0c617c..00000000 --- a/web/translation/translate.pt_BR.toml +++ /dev/null @@ -1,598 +0,0 @@ -"username" = "Nome de Usuário" -"password" = "Senha" -"login" = "Entrar" -"confirm" = "Confirmar" -"cancel" = "Cancelar" -"close" = "Fechar" -"copy" = "Copiar" -"copied" = "Copiado" -"download" = "Baixar" -"remark" = "Observação" -"enable" = "Ativado" -"protocol" = "Protocolo" -"search" = "Pesquisar" -"filter" = "Filtrar" -"loading" = "Carregando..." -"second" = "Segundo" -"minute" = "Minuto" -"hour" = "Hora" -"day" = "Dia" -"check" = "Verificar" -"indefinite" = "Indeterminado" -"unlimited" = "Ilimitado" -"none" = "Nada" -"qrCode" = "Código QR" -"info" = "Mais Informações" -"edit" = "Editar" -"delete" = "Excluir" -"reset" = "Redefinir" -"copySuccess" = "Copiado com Sucesso" -"sure" = "Certo" -"encryption" = "Criptografia" -"transmission" = "Transmissão" -"host" = "Servidor" -"path" = "Caminho" -"camouflage" = "Ofuscação" -"status" = "Status" -"enabled" = "Ativado" -"disabled" = "Desativado" -"depleted" = "Encerrado" -"depletingSoon" = "Esgotando" -"offline" = "Offline" -"online" = "Online" -"domainName" = "Nome de Domínio" -"monitor" = "IP de Escuta" -"certificate" = "Certificado Digital" -"fail" = "Falhou" -"comment" = "Comentário" -"success" = "Com Sucesso" -"getVersion" = "Obter Versão" -"install" = "Instalar" -"clients" = "Clientes" -"usage" = "Uso" -"secretToken" = "Token Secreto" -"remained" = "Restante" -"security" = "Segurança" -"secAlertTitle" = "Alerta de Segurança" -"secAlertSsl" = "Esta conexão não é segura. Evite inserir informações confidenciais até que o TLS seja ativado para proteção de dados." -"secAlertConf" = "Algumas configurações estão vulneráveis a ataques. Recomenda-se reforçar os protocolos de segurança para evitar possíveis violações." -"secAlertSSL" = "O painel não possui uma conexão segura. Instale o certificado TLS para proteção de dados." -"secAlertPanelPort" = "A porta padrão do painel é vulnerável. Configure uma porta aleatória ou específica." -"secAlertPanelURI" = "O caminho URI padrão do painel não é seguro. Configure um caminho URI complexo." -"secAlertSubURI" = "O caminho URI padrão de inscrição não é seguro. Configure um caminho URI complexo." -"secAlertSubJsonURI" = "O caminho URI JSON de inscrição padrão não é seguro. Configure um caminho URI complexo." - -[menu] -"dashboard" = "Visão Geral" -"inbounds" = "Inbounds" -"settings" = "Panel Settings" -"xray" = "Xray Configs" -"logout" = "Sair" -"link" = "Gerenciar" - -[pages.login] -"hello" = "Olá" -"title" = "Bem-vindo" -"loginAgain" = "Sua sessão expirou, faça login novamente" - -[pages.login.toasts] -"invalidFormData" = "O formato dos dados de entrada é inválido." -"emptyUsername" = "Nome de usuário é obrigatório" -"emptyPassword" = "Senha é obrigatória" -"wrongUsernameOrPassword" = "Nome de usuário, senha ou segredo inválidos." -"successLogin" = "Login realizado com sucesso" - -[pages.index] -"title" = "Visão Geral" -"memory" = "Memória RAM" -"hard" = "Disco" -"xrayStatus" = "Xray" -"stopXray" = "Parar" -"restartXray" = "Reiniciar" -"xraySwitch" = "Versão" -"xraySwitchClick" = "Escolha a versão para a qual deseja alternar." -"xraySwitchClickDesk" = "Escolha com cuidado, pois versões mais antigas podem não ser compatíveis com as configurações atuais." -"operationHours" = "Tempo de Atividade" -"systemLoad" = "Carga do Sistema" -"systemLoadDesc" = "Média de carga do sistema nos últimos 1, 5 e 15 minutos" -"connectionTcpCountDesc" = "Total de conexões TCP no sistema" -"connectionUdpCountDesc" = "Total de conexões UDP no sistema" -"connectionCount" = "Estatísticas de Conexão" -"upSpeed" = "Velocidade total de upload no sistema" -"downSpeed" = "Velocidade total de download no sistema" -"totalSent" = "Dados totais enviados desde a inicialização do sistema" -"totalReceive" = "Dados totais recebidos desde a inicialização do sistema" -"xraySwitchVersionDialog" = "Alterar Versão do Xray" -"xraySwitchVersionDialogDesc" = "Tem certeza de que deseja alterar a versão do Xray para" -"dontRefresh" = "Instalação em andamento, por favor não atualize a página" -"logs" = "Logs" -"config" = "Configuração" -"backup" = "Backup e Restauração" -"backupTitle" = "Backup e Restauração do Banco de Dados" -"backupDescription" = "É recomendado fazer um backup antes de restaurar o banco de dados." -"exportDatabase" = "Fazer Backup" -"importDatabase" = "Restaurar" - -[pages.inbounds] -"title" = "Inbounds" -"totalDownUp" = "Total Enviado/Recebido" -"totalUsage" = "Uso Total" -"inboundCount" = "Total de Inbounds" -"operate" = "Menu" -"enable" = "Ativado" -"remark" = "Observação" -"protocol" = "Protocolo" -"port" = "Porta" -"traffic" = "Tráfego" -"details" = "Detalhes" -"transportConfig" = "Transporte" -"expireDate" = "Duração" -"resetTraffic" = "Redefinir Tráfego" -"addInbound" = "Adicionar Inbound" -"generalActions" = "Ações Gerais" -"create" = "Criar" -"update" = "Atualizar" -"modifyInbound" = "Modificar Inbound" -"deleteInbound" = "Excluir Inbound" -"deleteInboundContent" = "Tem certeza de que deseja excluir o inbound?" -"deleteClient" = "Excluir Cliente" -"deleteClientContent" = "Tem certeza de que deseja excluir o cliente?" -"resetTrafficContent" = "Tem certeza de que deseja redefinir o tráfego?" -"copyLink" = "Copiar URL" -"address" = "Endereço" -"network" = "Rede" -"destinationPort" = "Porta de Destino" -"targetAddress" = "Endereço de Destino" -"monitorDesc" = "Deixe em branco para ouvir todos os IPs" -"meansNoLimit" = "= Ilimitado. (unidade: GB)" -"totalFlow" = "Fluxo Total" -"leaveBlankToNeverExpire" = "Deixe em branco para nunca expirar" -"noRecommendKeepDefault" = "Recomenda-se manter o padrão" -"certificatePath" = "Caminho" -"certificateContent" = "Conteúdo" -"publicKey" = "Chave Pública" -"privatekey" = "Chave Privada" -"clickOnQRcode" = "Clique no Código QR para Copiar" -"client" = "Cliente" -"export" = "Exportar Todos os URLs" -"clone" = "Clonar" -"cloneInbound" = "Clonar" -"cloneInboundContent" = "Todas as configurações deste inbound, exceto Porta, IP de Escuta e Clientes, serão aplicadas ao clone." -"cloneInboundOk" = "Clonar" -"resetAllTraffic" = "Redefinir Tráfego de Todos os Inbounds" -"resetAllTrafficTitle" = "Redefinir Tráfego de Todos os Inbounds" -"resetAllTrafficContent" = "Tem certeza de que deseja redefinir o tráfego de todos os inbounds?" -"resetInboundClientTraffics" = "Redefinir Tráfego dos Clientes" -"resetInboundClientTrafficTitle" = "Redefinir Tráfego dos Clientes" -"resetInboundClientTrafficContent" = "Tem certeza de que deseja redefinir o tráfego dos clientes deste inbound?" -"resetAllClientTraffics" = "Redefinir Tráfego de Todos os Clientes" -"resetAllClientTrafficTitle" = "Redefinir Tráfego de Todos os Clientes" -"resetAllClientTrafficContent" = "Tem certeza de que deseja redefinir o tráfego de todos os clientes?" -"delDepletedClients" = "Excluir Clientes Esgotados" -"delDepletedClientsTitle" = "Excluir Clientes Esgotados" -"delDepletedClientsContent" = "Tem certeza de que deseja excluir todos os clientes esgotados?" -"email" = "Email" -"emailDesc" = "Por favor, forneça um endereço de e-mail único." -"IPLimit" = "Limite de IP" -"IPLimitDesc" = "Desativa o inbound se o número ultrapassar o valor definido. (0 = desativar)" -"IPLimitlog" = "Log de IP" -"IPLimitlogDesc" = "O histórico de IPs. (para ativar o inbound após a desativação, limpe o log)" -"IPLimitlogclear" = "Limpar o Log" -"setDefaultCert" = "Definir Certificado pelo Painel" -"telegramDesc" = "Por favor, forneça o ID do Chat do Telegram. (use o comando '/id' no bot) ou (@userinfobot)" -"subscriptionDesc" = "Para encontrar seu URL de assinatura, navegue até 'Detalhes'. Além disso, você pode usar o mesmo nome para vários clientes." -"info" = "Informações" -"same" = "Igual" -"inboundData" = "Dados do Inbound" -"exportInbound" = "Exportar Inbound" -"import" = "Importar" -"importInbound" = "Importar um Inbound" - -[pages.client] -"add" = "Adicionar Cliente" -"edit" = "Editar Cliente" -"submitAdd" = "Adicionar Cliente" -"submitEdit" = "Salvar Alterações" -"clientCount" = "Número de Clientes" -"bulk" = "Adicionar Vários" -"method" = "Método" -"first" = "Primeiro" -"last" = "Último" -"prefix" = "Prefixo" -"postfix" = "Sufixo" -"delayedStart" = "Iniciar Após Primeiro Uso" -"expireDays" = "Duração" -"days" = "Dia(s)" -"renew" = "Renovação Automática" -"renewDesc" = "Renovação automática após expiração. (0 = desativado)(unidade: dia)" - -[pages.inbounds.toasts] -"obtain" = "Obter" - -[pages.inbounds.stream.general] -"request" = "Requisição" -"response" = "Resposta" -"name" = "Nome" -"value" = "Valor" - -[pages.inbounds.stream.tcp] -"version" = "Versão" -"method" = "Método" -"path" = "Caminho" -"status" = "Status" -"statusDescription" = "Descrição do Status" -"requestHeader" = "Cabeçalho da Requisição" -"responseHeader" = "Cabeçalho da Resposta" - -[pages.settings] -"title" = "Configurações do Painel" -"save" = "Salvar" -"infoDesc" = "Toda alteração feita aqui precisa ser salva. Reinicie o painel para aplicar as alterações." -"restartPanel" = "Reiniciar Painel" -"restartPanelDesc" = "Tem certeza de que deseja reiniciar o painel? Se não conseguir acessar o painel após reiniciar, consulte os logs do painel no servidor." -"actions" = "Ações" -"resetDefaultConfig" = "Redefinir para Padrão" -"panelSettings" = "Geral" -"securitySettings" = "Autenticação" -"TGBotSettings" = "Bot do Telegram" -"panelListeningIP" = "IP de Escuta" -"panelListeningIPDesc" = "O endereço IP para o painel web. (deixe em branco para escutar em todos os IPs)" -"panelListeningDomain" = "Domínio de Escuta" -"panelListeningDomainDesc" = "O nome de domínio para o painel web. (deixe em branco para escutar em todos os domínios e IPs)" -"panelPort" = "Porta de Escuta" -"panelPortDesc" = "O número da porta para o painel web. (deve ser uma porta não usada)" -"publicKeyPath" = "Caminho da Chave Pública" -"publicKeyPathDesc" = "O caminho do arquivo de chave pública para o painel web. (começa com ‘/‘)" -"privateKeyPath" = "Caminho da Chave Privada" -"privateKeyPathDesc" = "O caminho do arquivo de chave privada para o painel web. (começa com ‘/‘)" -"panelUrlPath" = "Caminho URI" -"panelUrlPathDesc" = "O caminho URI para o painel web. (começa com ‘/‘ e termina com ‘/‘)" -"pageSize" = "Tamanho da Paginação" -"pageSizeDesc" = "Definir o tamanho da página para a tabela de entradas. (0 = desativado)" -"remarkModel" = "Modelo de Observação & Caractere de Separação" -"datepicker" = "Tipo de Calendário" -"datepickerPlaceholder" = "Selecionar data" -"datepickerDescription" = "Tarefas agendadas serão executadas com base neste calendário." -"sampleRemark" = "Exemplo de Observação" -"oldUsername" = "Nome de Usuário Atual" -"currentPassword" = "Senha Atual" -"newUsername" = "Novo Nome de Usuário" -"newPassword" = "Nova Senha" -"telegramBotEnable" = "Ativar Bot do Telegram" -"telegramBotEnableDesc" = "Ativa o bot do Telegram." -"telegramToken" = "Token do Telegram" -"telegramTokenDesc" = "O token do bot do Telegram obtido de '@BotFather'." -"telegramProxy" = "Proxy SOCKS" -"telegramProxyDesc" = "Ativa o proxy SOCKS5 para conectar ao Telegram. (ajuste as configurações conforme o guia)" -"telegramAPIServer" = "API Server do Telegram" -"telegramAPIServerDesc" = "O servidor API do Telegram a ser usado. Deixe em branco para usar o servidor padrão." -"telegramChatId" = "ID de Chat do Administrador" -"telegramChatIdDesc" = "O(s) ID(s) de Chat do Administrador no Telegram. (separado por vírgulas)(obtenha aqui @userinfobot) ou (use o comando '/id' no bot)" -"telegramNotifyTime" = "Hora da Notificação" -"telegramNotifyTimeDesc" = "O horário de notificação do bot do Telegram configurado para relatórios periódicos. (use o formato de tempo do crontab)" -"tgNotifyBackup" = "Backup do Banco de Dados" -"tgNotifyBackupDesc" = "Enviar arquivo de backup do banco de dados junto com o relatório." -"tgNotifyLogin" = "Notificação de Login" -"tgNotifyLoginDesc" = "Receba notificações sobre o nome de usuário, endereço IP e horário sempre que alguém tentar fazer login no seu painel web." -"sessionMaxAge" = "Duração da Sessão" -"sessionMaxAgeDesc" = "A duração pela qual você pode permanecer logado. (unidade: minuto)" -"expireTimeDiff" = "Notificação de Expiração" -"expireTimeDiffDesc" = "Receba notificações sobre a data de expiração ao atingir esse limite. (unidade: dia)" -"trafficDiff" = "Notificação de Limite de Tráfego" -"trafficDiffDesc" = "Receba notificações sobre o limite de tráfego ao atingir esse limite. (unidade: GB)" -"tgNotifyCpu" = "Notificação de Carga da CPU" -"tgNotifyCpuDesc" = "Receba notificações se a carga da CPU ultrapassar esse limite. (unidade: %)" -"timeZone" = "Fuso Horário" -"timeZoneDesc" = "As tarefas agendadas serão executadas com base nesse fuso horário." -"subSettings" = "Assinatura" -"subEnable" = "Ativar Serviço de Assinatura" -"subEnableDesc" = "Ativa o serviço de assinatura." -"subListen" = "IP de Escuta" -"subListenDesc" = "O endereço IP para o serviço de assinatura. (deixe em branco para escutar em todos os IPs)" -"subPort" = "Porta de Escuta" -"subPortDesc" = "O número da porta para o serviço de assinatura. (deve ser uma porta não usada)" -"subCertPath" = "Caminho da Chave Pública" -"subCertPathDesc" = "O caminho do arquivo de chave pública para o serviço de assinatura. (começa com ‘/‘)" -"subKeyPath" = "Caminho da Chave Privada" -"subKeyPathDesc" = "O caminho do arquivo de chave privada para o serviço de assinatura. (começa com ‘/‘)" -"subPath" = "Caminho URI" -"subPathDesc" = "O caminho URI para o serviço de assinatura. (começa com ‘/‘ e termina com ‘/‘)" -"subDomain" = "Domínio de Escuta" -"subDomainDesc" = "O nome de domínio para o serviço de assinatura. (deixe em branco para escutar em todos os domínios e IPs)" -"subUpdates" = "Intervalos de Atualização" -"subUpdatesDesc" = "Os intervalos de atualização da URL de assinatura nos aplicativos de cliente. (unidade: hora)" -"subEncrypt" = "Codificar" -"subEncryptDesc" = "O conteúdo retornado pelo serviço de assinatura será codificado em Base64." -"subShowInfo" = "Mostrar Informações de Uso" -"subShowInfoDesc" = "O tráfego restante e a data serão exibidos nos aplicativos de cliente." -"subURI" = "URI de Proxy Reverso" -"subURIDesc" = "O caminho URI da URL de assinatura para uso por trás de proxies." -"fragment" = "Fragmentação" -"fragmentDesc" = "Ativa a fragmentação para o pacote TLS hello." -"fragmentSett" = "Configurações de Fragmentação" -"noisesDesc" = "Ativar Noises." -"noisesSett" = "Configurações de Noises" -"mux" = "Mux" -"muxDesc" = "Transmitir múltiplos fluxos de dados independentes dentro de um fluxo de dados estabelecido." -"muxSett" = "Configurações de Mux" -"direct" = "Conexão Direta" -"directDesc" = "Estabelece conexões diretamente com domínios ou intervalos de IP de um país específico." - - -[pages.xray] -"title" = "Configurações Xray" -"save" = "Salvar" -"restart" = "Reiniciar Xray" -"basicTemplate" = "Básico" -"advancedTemplate" = "Avançado" -"generalConfigs" = "Geral" -"generalConfigsDesc" = "Essas opções determinam ajustes gerais." -"logConfigs" = "Log" -"logConfigsDesc" = "Os logs podem afetar a eficiência do servidor. É recomendável habilitá-los com sabedoria apenas se necessário." -"blockConfigs" = "Escudo de Proteção" -"blockConfigsDesc" = "Essas opções bloqueiam tráfego com base em protocolos e sites específicos solicitados." -"basicRouting" = "Roteamento Básico" -"blockConnectionsConfigsDesc" = "Essas opções bloquearão o tráfego com base no país solicitado." -"directConnectionsConfigsDesc" = "Uma conexão direta garante que o tráfego específico não seja roteado por outro servidor." -"blockips" = "Bloquear IPs" -"blockdomains" = "Bloquear Domínios" -"directips" = "IPs Diretos" -"directdomains" = "Domínios Diretos" -"ipv4Routing" = "Roteamento IPv4" -"ipv4RoutingDesc" = "Essas opções roteam o tráfego para um destino específico via IPv4." -"warpRouting" = "Roteamento WARP" -"warpRoutingDesc" = "Essas opções roteam o tráfego para um destino específico via WARP." -"Template" = "Modelo de Configuração Avançada do Xray" -"TemplateDesc" = "O arquivo final de configuração do Xray será gerado com base neste modelo." -"FreedomStrategy" = "Estratégia do Protocolo Freedom" -"FreedomStrategyDesc" = "Definir a estratégia de saída para a rede no Protocolo Freedom." -"RoutingStrategy" = "Estratégia Geral de Roteamento" -"RoutingStrategyDesc" = "Definir a estratégia geral de roteamento de tráfego para resolver todas as solicitações." -"Torrent" = "Bloquear Protocolo BitTorrent" -"TorrentDesc" = "Bloqueia o protocolo BitTorrent." -"Family" = "Proteção Familiar" -"FamilyDesc" = "Bloqueia conteúdo adulto e sites maliciosos." -"Inbounds" = "Inbounds" -"InboundsDesc" = "Aceitar clientes específicos." -"Outbounds" = "Outbounds" -"Balancers" = "Balanceadores" -"OutboundsDesc" = "Definir o caminho de saída do tráfego." -"Routings" = "Regras de Roteamento" -"RoutingsDesc" = "A prioridade de cada regra é importante!" -"completeTemplate" = "Todos" -"logLevel" = "Nível de Log" -"logLevelDesc" = "O nível de log para erros, indicando a informação que precisa ser registrada." -"accessLog" = "Log de Acesso" -"accessLogDesc" = "O caminho do arquivo para o log de acesso. O valor especial 'none' desativa os logs de acesso." -"errorLog" = "Log de Erros" -"errorLogDesc" = "O caminho do arquivo para o log de erros. O valor especial 'none' desativa os logs de erro." -"dnsLog" = "Log DNS" -"dnsLogDesc" = "Se ativar logs de consulta DNS" -"outboundTraffic" = "Tráfego de saída" -"outboundTrafficDesc" = "Se deve habilitar o tráfego de saída" -"maskAddress" = "Mascarar Endereço" -"maskAddressDesc" = "Máscara de endereço IP, quando ativado, substitui automaticamente o endereço IP que aparece no log." - -[pages.xray.rules] -"first" = "Primeiro" -"last" = "Último" -"up" = "Cima" -"down" = "Baixo" -"source" = "Fonte" -"dest" = "Destino" -"inbound" = "Entrada" -"outbound" = "Saída" -"balancer" = "Balanceador" -"info" = "Info" -"add" = "Adicionar Regra" -"edit" = "Editar Regra" -"useComma" = "Itens separados por vírgula" - -[pages.xray.outbound] -"addOutbound" = "Adicionar Saída" -"addReverse" = "Adicionar Reverso" -"editOutbound" = "Editar Saída" -"editReverse" = "Editar Reverso" -"tag" = "Tag" -"tagDesc" = "Tag Única" -"address" = "Endereço" -"reverse" = "Reverso" -"domain" = "Domínio" -"type" = "Tipo" -"bridge" = "Ponte" -"portal" = "Portal" -"intercon" = "Interconexão" -"settings" = "Configurações" -"accountInfo" = "Informações da Conta" -"outboundStatus" = "Status de Saída" -"sendThrough" = "Enviar Através de" - -[pages.xray.balancer] -"addBalancer" = "Adicionar Balanceador" -"editBalancer" = "Editar Balanceador" -"balancerStrategy" = "Estratégia" -"balancerSelectors" = "Seletores" -"tag" = "Tag" -"tagDesc" = "Tag Única" -"balancerDesc" = "Não é possível usar balancerTag e outboundTag ao mesmo tempo. Se usados simultaneamente, apenas outboundTag funcionará." - -[pages.xray.wireguard] -"secretKey" = "Chave Secreta" -"publicKey" = "Chave Pública" -"allowedIPs" = "IPs Permitidos" -"endpoint" = "Ponto Final" -"psk" = "Chave Pré-Compartilhada" -"domainStrategy" = "Estratégia de Domínio" - -[pages.xray.dns] -"enable" = "Ativar DNS" -"enableDesc" = "Ativar o servidor DNS integrado" -"tag" = "Tag de Entrada DNS" -"tagDesc" = "Esta tag estará disponível como uma tag de Entrada nas regras de roteamento." -"strategy" = "Estratégia de Consulta" -"strategyDesc" = "Estratégia geral para resolver nomes de domínio" -"add" = "Adicionar Servidor" -"edit" = "Editar Servidor" -"domains" = "Domínios" -"expectIPs" = "IPs Esperadas" - -[pages.xray.fakedns] -"add" = "Adicionar Fake DNS" -"edit" = "Editar Fake DNS" -"ipPool" = "Sub-rede do Pool de IP" -"poolSize" = "Tamanho do Pool" - -[pages.settings.security] -"admin" = "Admin" -"secret" = "Token Secreto" -"loginSecurity" = "Login Seguro" -"loginSecurityDesc" = "Adiciona uma camada extra de autenticação para fornecer mais segurança." -"secretToken" = "Token Secreto" -"secretTokenDesc" = "Por favor, armazene este token em um local seguro. Este token é necessário para o login e não pode ser recuperado." - -[pages.settings.toasts] -"modifySettings" = "Modificar Configurações" -"getSettings" = "Obter Configurações" -"modifyUser" = "Modificar Admin" -"originalUserPassIncorrect" = "O nome de usuário ou senha atual é inválido" -"userPassMustBeNotEmpty" = "O novo nome de usuário e senha não podem estar vazios" - -[tgbot] -"keyboardClosed" = "❌ Teclado personalizado fechado!" -"noResult" = "❗ Nenhum resultado!" -"noQuery" = "❌ Consulta não encontrada! Por favor, use o comando novamente!" -"wentWrong" = "❌ Algo deu errado!" -"noIpRecord" = "❗ Nenhum registro de IP!" -"noInbounds" = "❗ Nenhuma entrada encontrada!" -"unlimited" = "♾ Ilimitado (Reiniciar)" -"add" = "Adicionar" -"month" = "Mês" -"months" = "Meses" -"day" = "Dia" -"days" = "Dias" -"hours" = "Horas" -"unknown" = "Desconhecido" -"inbounds" = "Entradas" -"clients" = "Clientes" -"offline" = "🔴 Offline" -"online" = "🟢 Online" - -[tgbot.commands] -"unknown" = "❗ Comando desconhecido." -"pleaseChoose" = "👇 Escolha:\r\n" -"help" = "🤖 Bem-vindo a este bot! Ele foi projetado para oferecer dados específicos do painel da web e permite que você faça as modificações necessárias.\r\n\r\n" -"start" = "👋 Olá {{ .Firstname }}.\r\n" -"welcome" = "🤖 Bem-vindo ao bot de gerenciamento do {{ .Hostname }}.\r\n" -"status" = "✅ Bot está OK!" -"usage" = "❗ Por favor, forneça um texto para pesquisar!" -"getID" = "🆔 Seu ID: {{ .ID }}" -"helpAdminCommands" = "Para reiniciar o Xray Core:\r\n/restart force\r\n\r\nPara pesquisar por um email de cliente:\r\n/usage [Email]\r\n\r\nPara pesquisar por inbounds (com estatísticas do cliente):\r\n/inbound [Remark]\r\n\r\nTelegram Chat ID:\r\n/id" -"helpClientCommands" = "Para pesquisar por estatísticas, use o seguinte comando:\r\n\r\n/usage [Email]\r\n\r\nTelegram Chat ID:\r\n/id" -"restartUsage" = "\r\n\r\n/restart force" -"restartSuccess" = "✅ Operação bem-sucedida!" -"restartFailed" = "❗ Erro na operação.\r\n\r\nErro: {{ .Error }}." -"xrayNotRunning" = "❗ Xray Core não está em execução." - -[tgbot.messages] -"cpuThreshold" = "🔴 A carga da CPU {{ .Percent }}% excede o limite de {{ .Threshold }}%" -"selectUserFailed" = "❌ Erro na seleção do usuário!" -"userSaved" = "✅ Usuário do Telegram salvo." -"loginSuccess" = "✅ Conectado ao painel com sucesso.\r\n" -"loginFailed" = "❗️Tentativa de login no painel falhou.\r\n" -"report" = "🕰 Relatórios agendados: {{ .RunTime }}\r\n" -"datetime" = "⏰ Data&Hora: {{ .DateTime }}\r\n" -"hostname" = "💻 Host: {{ .Hostname }}\r\n" -"version" = "🚀 Versão 3X-UI: {{ .Version }}\r\n" -"xrayVersion" = "📡 Versão Xray: {{ .XrayVersion }}\r\n" -"ipv6" = "🌐 IPv6: {{ .IPv6 }}\r\n" -"ipv4" = "🌐 IPv4: {{ .IPv4 }}\r\n" -"ip" = "🌐 IP: {{ .IP }}\r\n" -"ips" = "🔢 IPs:\r\n{{ .IPs }}\r\n" -"serverUpTime" = "⏳ Tempo de atividade: {{ .UpTime }} {{ .Unit }}\r\n" -"serverLoad" = "📈 Carga do sistema: {{ .Load1 }}, {{ .Load2 }}, {{ .Load3 }}\r\n" -"serverMemory" = "📋 RAM: {{ .Current }}/{{ .Total }}\r\n" -"tcpCount" = "🔹 TCP: {{ .Count }}\r\n" -"udpCount" = "🔸 UDP: {{ .Count }}\r\n" -"traffic" = "🚦 Tráfego: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n" -"xrayStatus" = "ℹ️ Status: {{ .State }}\r\n" -"username" = "👤 Nome de usuário: {{ .Username }}\r\n" -"password" = "👤 Senha: {{ .Password }}\r\n" -"time" = "⏰ Hora: {{ .Time }}\r\n" -"inbound" = "📍 Inbound: {{ .Remark }}\r\n" -"port" = "🔌 Porta: {{ .Port }}\r\n" -"expire" = "📅 Data de expiração: {{ .Time }}\r\n" -"expireIn" = "📅 Expira em: {{ .Time }}\r\n" -"active" = "💡 Ativo: {{ .Enable }}\r\n" -"enabled" = "🚨 Ativado: {{ .Enable }}\r\n" -"online" = "🌐 Status da conexão: {{ .Status }}\r\n" -"email" = "📧 Email: {{ .Email }}\r\n" -"upload" = "🔼 Upload: ↑{{ .Upload }}\r\n" -"download" = "🔽 Download: ↓{{ .Download }}\r\n" -"total" = "📊 Total: ↑↓{{ .UpDown }} / {{ .Total }}\r\n" -"TGUser" = "👤 Usuário do Telegram: {{ .TelegramID }}\r\n" -"exhaustedMsg" = "🚨 {{ .Type }} esgotado:\r\n" -"exhaustedCount" = "🚨 Contagem de {{ .Type }} esgotado:\r\n" -"onlinesCount" = "🌐 Clientes online: {{ .Count }}\r\n" -"disabled" = "🛑 Desativado: {{ .Disabled }}\r\n" -"depleteSoon" = "🔜 Esgotar em breve: {{ .Deplete }}\r\n\r\n" -"backupTime" = "🗄 Hora do backup: {{ .Time }}\r\n" -"refreshedOn" = "\r\n📋🔄 Atualizado em: {{ .Time }}\r\n\r\n" -"yes" = "✅ Sim" -"no" = "❌ Não" - -[tgbot.buttons] -"closeKeyboard" = "❌ Fechar teclado" -"cancel" = "❌ Cancelar" -"cancelReset" = "❌ Cancelar redefinição" -"cancelIpLimit" = "❌ Cancelar limite de IP" -"confirmResetTraffic" = "✅ Confirmar redefinição de tráfego?" -"confirmClearIps" = "✅ Confirmar limpar IPs?" -"confirmRemoveTGUser" = "✅ Confirmar remover usuário do Telegram?" -"confirmToggle" = "✅ Confirmar ativar/desativar usuário?" -"dbBackup" = "Obter backup do DB" -"serverUsage" = "Uso do servidor" -"getInbounds" = "Obter Inbounds" -"depleteSoon" = "Esgotar em breve" -"clientUsage" = "Obter uso" -"onlines" = "Clientes online" -"commands" = "Comandos" -"refresh" = "🔄 Atualizar" -"clearIPs" = "❌ Limpar IPs" -"removeTGUser" = "❌ Remover usuário do Telegram" -"selectTGUser" = "👤 Selecionar usuário do Telegram" -"selectOneTGUser" = "👤 Selecione um usuário do Telegram:" -"resetTraffic" = "📈 Redefinir tráfego" -"resetExpire" = "📅 Alterar data de expiração" -"ipLog" = "🔢 Log de IP" -"ipLimit" = "🔢 Limite de IP" -"setTGUser" = "👤 Definir usuário do Telegram" -"toggle" = "🔘 Ativar / Desativar" -"custom" = "🔢 Personalizado" -"confirmNumber" = "✅ Confirmar: {{ .Num }}" -"confirmNumberAdd" = "✅ Confirmar adicionar: {{ .Num }}" -"limitTraffic" = "🚧 Limite de tráfego" -"getBanLogs" = "Obter logs de banimento" -"allClients" = "Todos os clientes" - -[tgbot.answers] -"successfulOperation" = "✅ Operação bem-sucedida!" -"errorOperation" = "❗ Erro na operação." -"getInboundsFailed" = "❌ Falha ao obter inbounds." -"getClientsFailed" = "❌ Falha ao obter clientes." -"canceled" = "❌ {{ .Email }}: Operação cancelada." -"clientRefreshSuccess" = "✅ {{ .Email }}: Cliente atualizado com sucesso." -"IpRefreshSuccess" = "✅ {{ .Email }}: IPs atualizados com sucesso." -"TGIdRefreshSuccess" = "✅ {{ .Email }}: Usuário do Telegram do cliente atualizado com sucesso." -"resetTrafficSuccess" = "✅ {{ .Email }}: Tráfego redefinido com sucesso." -"setTrafficLimitSuccess" = "✅ {{ .Email }}: Limite de tráfego salvo com sucesso." -"expireResetSuccess" = "✅ {{ .Email }}: Dias de expiração redefinidos com sucesso." -"resetIpSuccess" = "✅ {{ .Email }}: Limite de IP {{ .Count }} salvo com sucesso." -"clearIpSuccess" = "✅ {{ .Email }}: IPs limpos com sucesso." -"getIpLog" = "✅ {{ .Email }}: Obter log de IP." -"getUserInfo" = "✅ {{ .Email }}: Obter informações do usuário do Telegram." -"removedTGUserSuccess" = "✅ {{ .Email }}: Usuário do Telegram removido com sucesso." -"enableSuccess" = "✅ {{ .Email }}: Ativado com sucesso." -"disableSuccess" = "✅ {{ .Email }}: Desativado com sucesso." -"askToAddUserId" = "Sua configuração não foi encontrada!\r\nPeça ao seu administrador para usar seu Telegram ChatID em suas configurações.\r\n\r\nSeu ChatID: {{ .TgUserID }}" -"chooseClient" = "Escolha um cliente para Inbound {{ .Inbound }}" -"chooseInbound" = "Escolha um Inbound" diff --git a/web/translation/translate.ru_RU.toml b/web/translation/translate.ru_RU.toml deleted file mode 100644 index e0875226..00000000 --- a/web/translation/translate.ru_RU.toml +++ /dev/null @@ -1,598 +0,0 @@ -"username" = "Имя пользователя" -"password" = "Пароль" -"login" = "Войти" -"confirm" = "Подтвердить" -"cancel" = "Отмена" -"close" = "Закрыть" -"copy" = "Копировать" -"copied" = "Скопировано" -"download" = "Скачать" -"remark" = "Примечание" -"enable" = "Включить" -"protocol" = "Протокол" -"search" = "Поиск" -"filter" = "Фильтр" -"loading" = "Загрузка..." -"second" = "Секунда" -"minute" = "Минута" -"hour" = "Час" -"day" = "День" -"check" = "Проверить" -"indefinite" = "Бессрочно" -"unlimited" = "Безлимитно" -"none" = "Пусто" -"qrCode" = "QR-код" -"info" = "Информация" -"edit" = "Изменить" -"delete" = "Удалить" -"reset" = "Сбросить" -"copySuccess" = "Скопировано" -"sure" = "Да" -"encryption" = "Шифрование" -"transmission" = "Протокол передачи" -"host" = "Хост" -"path" = "Путь" -"camouflage" = "Маскировка" -"status" = "Статус" -"enabled" = "Включено" -"disabled" = "Отключено" -"depleted" = "Исчерпано" -"depletingSoon" = "Почти исчерпано" -"offline" = "Офлайн" -"online" = "Онлайн" -"domainName" = "Домен" -"monitor" = "Слушать IP" -"certificate" = "Цифровой сертификат" -"fail" = "Неудачно" -"comment" = "Комментарий" -"success" = "Успешно" -"getVersion" = "Узнать версию" -"install" = "Установка" -"clients" = "Клиенты" -"usage" = "Использование" -"secretToken" = "Секретный токен" -"remained" = "Остаток" -"security" = "Безопасность" -"secAlertTitle" = "Предупреждение системы безопасности" -"secAlertSsl" = "Это соединение не защищено. Пожалуйста, воздержитесь от ввода конфиденциальной информации до тех пор, пока не будет активирован TLS для защиты данных" -"secAlertConf" = "Некоторые настройки уязвимы для атак. Рекомендуется усилить протоколы безопасности, чтобы предотвратить потенциальные нарушения." -"secAlertSSL" = "В панели отсутствует безопасное соединение. Пожалуйста, установите сертификат TLS для защиты данных." -"secAlertPanelPort" = "Порт по умолчанию панели небезопасен. Пожалуйста, настройте случайный или определенный порт." -"secAlertPanelURI" = "URI-путь по умолчанию панели небезопасен. Пожалуйста, настройте сложный URI-путь." -"secAlertSubURI" = "URI-путь по умолчанию подписки небезопасен. Пожалуйста, настройте сложный URI-путь." -"secAlertSubJsonURI" = "URI-путь по умолчанию для JSON подписки небезопасен. Пожалуйста, настройте сложный URI-путь." - -[menu] -"dashboard" = "Статус системы" -"inbounds" = "Подключения" -"settings" = "Настройки панели" -"xray" = "Настройки Xray" -"logout" = "Выход" -"link" = "Управление" - -[pages.login] -"hello" = "Привет" -"title" = "Добро пожаловать" -"loginAgain" = "Ваша сессия истекла. Пожалуйста, войдите в систему снова" - -[pages.login.toasts] -"invalidFormData" = "Недопустимый формат данных" -"emptyUsername" = "Введите имя пользователя" -"emptyPassword" = "Введите пароль" -"wrongUsernameOrPassword" = "Неверное имя пользователя, пароль или секретный токен." -"successLogin" = "Успешный вход" - -[pages.index] -"title" = "Статус системы" -"memory" = "Память" -"hard" = "Жесткий диск" -"xrayStatus" = "Xray" -"stopXray" = "Остановить" -"restartXray" = "Перезапустить" -"xraySwitch" = "Версия" -"xraySwitchClick" = "Выберите желаемую версию" -"xraySwitchClickDesk" = "Выбирайте внимательно, так как старые версии могут быть несовместимы с текущими конфигурациями" -"operationHours" = "Время работы системы" -"systemLoad" = "Системная нагрузка" -"systemLoadDesc" = "Средняя загрузка системы за последние 1, 5 и 15 минут" -"connectionTcpCountDesc" = "Общее количество подключений TCP по всем сетевым картам." -"connectionUdpCountDesc" = "Общее количество подключений UDP по всем сетевым картам." -"connectionCount" = "Количество соединений" -"upSpeed" = "Общая скорость upload для всех сетей" -"downSpeed" = "Общая скорость download для всех сетей" -"totalSent" = "Общий объем загруженных данных для всех сетей с момента запуска системы" -"totalReceive" = "Общий объем полученных данных для всех сетей с момента запуска системы." -"xraySwitchVersionDialog" = "Переключить версию Xray" -"xraySwitchVersionDialogDesc" = "Вы точно хотите сменить версию Xray?" -"dontRefresh" = "Идёт установка. Пожалуйста, не обновляйте эту страницу" -"logs" = "Логи" -"config" = "Конфигурация" -"backup" = "Бэкап и восстановление" -"backupTitle" = "База данных бэкапа и восстановления" -"backupDescription" = "Рекомендуется сделать резервную копию перед восстановлением базы данных." -"exportDatabase" = "Экспорт базы данных" -"importDatabase" = "Импорт базы данных" - -[pages.inbounds] -"title" = "Подключения" -"totalDownUp" = "Всего uploads/downloads" -"totalUsage" = "Всего использовано" -"inboundCount" = "Количество подключений" -"operate" = "Меню" -"enable" = "Включить" -"remark" = "Примечание" -"protocol" = "Протокол" -"port" = "Порт" -"traffic" = "Трафик" -"details" = "Подробнее" -"transportConfig" = "Транспорт" -"expireDate" = "Дата окончания" -"resetTraffic" = "Сбросить трафик" -"addInbound" = "Добавить подключение" -"generalActions" = "Общие действия" -"create" = "Создать" -"update" = "Обновить" -"modifyInbound" = "Изменить подключение" -"deleteInbound" = "Удалить подключение" -"deleteInboundContent" = "Вы уверены, что хотите удалить подключение?" -"deleteClient" = "Удалить клиента" -"deleteClientContent" = "Вы уверены, что хотите удалить клиента?" -"resetTrafficContent" = "Вы уверены, что хотите сбросить трафик?" -"copyLink" = "Копировать ключ" -"address" = "Адрес" -"network" = "Сеть" -"destinationPort" = "Порт назначения" -"targetAddress" = "Целевой адрес" -"monitorDesc" = "Оставьте пустым для прослушивания всех IP-адресов" -"meansNoLimit" = "= Без ограничений (значение: ГБ)" -"totalFlow" = "Общий расход" -"leaveBlankToNeverExpire" = "Оставьте пустым, чтобы не истекало" -"noRecommendKeepDefault" = "Рекомендуется оставить настройки по умолчанию" -"certificatePath" = "Путь к файлу" -"certificateContent" = "Содержимое файла" -"publicKey" = "Публичный ключ" -"privatekey" = "Приватный ключ" -"clickOnQRcode" = "Нажмите на QR-код, чтобы скопировать" -"client" = "Клиент" -"export" = "Экспорт ключей" -"clone" = "Клонировать" -"cloneInbound" = "Клонировать" -"cloneInboundContent" = "Все настройки этого подключения, кроме порта, IP-адреса прослушки и клиентов, будут клонированы" -"cloneInboundOk" = "Клонировано" -"resetAllTraffic" = "Сбросить трафик всех подключений" -"resetAllTrafficTitle" = "Сброс трафика всех подключений" -"resetAllTrafficContent" = "Вы уверены, что хотите сбросить трафик всех подключений?" -"resetInboundClientTraffics" = "Сбросить трафик пользователей" -"resetInboundClientTrafficTitle" = "Сброс трафика пользователей" -"resetInboundClientTrafficContent" = "Вы уверены, что хотите сбросить весь трафик для этих пользователей?" -"resetAllClientTraffics" = "Сбросить трафик всех пользователей" -"resetAllClientTrafficTitle" = "Сброс трафика всех пользователей" -"resetAllClientTrafficContent" = "Вы уверены, что хотите сбросить трафик всех пользователей?" -"delDepletedClients" = "Удалить отключенных пользователей" -"delDepletedClientsTitle" = "Удаление отключенных пользователей" -"delDepletedClientsContent" = "Вы уверены, что хотите удалить всех отключенных пользователей?" -"email" = "Email" -"emailDesc" = "Пожалуйста, укажите уникальный Email" -"IPLimit" = "Ограничение по IP" -"IPLimitDesc" = "Сбросить подключение, если подключено больше введенного значения (введите 0, чтобы отключить ограничение IP-адресов)" -"IPLimitlog" = "IP лог" -"IPLimitlogDesc" = "Лог IP-адресов (перед включением лога IP-адресов, вы должны очистить список)" -"IPLimitlogclear" = "Очистить лог" -"setDefaultCert" = "Установить сертификат с панели" -"telegramDesc" = "Пожалуйста, укажите ID чата Telegram. (используйте команду '/id' в боте) или (@userinfobot)" -"subscriptionDesc" = "Вы можете найти свою ссылку подписки в разделе 'Подробнее', также вы можете использовать одно и то же имя для нескольких конфигураций" -"info" = "Информация" -"same" = "Тот же" -"inboundData" = "Входящие данные" -"exportInbound" = "Экспорт входящих" -"import" = "Импортировать" -"importInbound" = "Импортировать подключение" - -[pages.client] -"add" = "Добавить пользователя" -"edit" = "Редактировать пользователя" -"submitAdd" = "Добавить пользователя" -"submitEdit" = "Сохранить изменения" -"clientCount" = "Количество пользователей" -"bulk" = "Добавить несколько" -"method" = "Метод" -"first" = "Первый" -"last" = "Последний" -"prefix" = "Префикс" -"postfix" = "Постфикс" -"delayedStart" = "Начало использования" -"expireDays" = "Длительность" -"days" = "дней" -"renew" = "Автопродление" -"renewDesc" = "Автопродление после истечения срока действия. (0 = отключить)(единица: день)" - -[pages.inbounds.toasts] -"obtain" = "Получить" - -[pages.inbounds.stream.general] -"request" = "Запрос" -"response" = "Ответ" -"name" = "Имя" -"value" = "Значение" - -[pages.inbounds.stream.tcp] -"version" = "Версия" -"method" = "Метод" -"path" = "Путь" -"status" = "Статус" -"statusDescription" = "Описание статуса" -"requestHeader" = "Заголовок запроса" -"responseHeader" = "Заголовок ответа" - -[pages.settings] -"title" = "Настройки" -"save" = "Сохранить" -"infoDesc" = "Каждое выполненное изменение необходимо сохранить. Пожалуйста, перезапустите панель, чтобы изменения вступили в силу" -"restartPanel" = "Перезапуск панели" -"restartPanelDesc" = "Вы уверены, что хотите перезапустить панель? Нажмите ОК для перезапуска панели через 3 сек. Если вы не можете пользоваться панелью после перезапуска, пожалуйста, посмотрите лог панели на сервере" -"actions" = "Действия" -"resetDefaultConfig" = "Сбросить на конфигурацию по умолчанию" -"panelSettings" = "Настройки панели" -"securitySettings" = "Настройки безопасности" -"TGBotSettings" = "Настройки Telegram бота" -"panelListeningIP" = "IP-адрес панели" -"panelListeningIPDesc" = "Оставьте пустым для подключения с любого IP" -"panelListeningDomain" = "Домен прослушивания панели" -"panelListeningDomainDesc" = "По умолчанию оставьте пустым, чтобы отслеживать все домены и IP-адреса" -"panelPort" = "Порт панели" -"panelPortDesc" = "Порт, используемый для отображения этой панели" -"publicKeyPath" = "Путь к файлу публичного ключа сертификата панели" -"publicKeyPathDesc" = "Введите полный путь, начинающийся с '/'" -"privateKeyPath" = "Путь к файлу приватного ключа сертификата панели" -"privateKeyPathDesc" = "Введите полный путь, начинающийся с '/'" -"panelUrlPath" = "Корневой путь URL адреса панели" -"panelUrlPathDesc" = "Должен начинаться с '/' и заканчиваться на '/'" -"pageSize" = "Размер нумерации страниц" -"pageSizeDesc" = "Определить размер страницы для входящей таблицы. Установите 0, чтобы отключить" -"remarkModel" = "Модель примечания и символ разделения" -"datepicker" = "Выбор даты" -"datepickerPlaceholder" = "Выберите дату" -"datepickerDescription" = "Запланированные задачи выполняются в соответствии с данным календарём" -"sampleRemark" = "Пример замечания" -"oldUsername" = "Текущий логин" -"currentPassword" = "Текущий пароль" -"newUsername" = "Новый логин" -"newPassword" = "Новый пароль" -"telegramBotEnable" = "Включить Telegram бота" -"telegramBotEnableDesc" = "Подключайтесь к функциям этой панели через Telegram бота" -"telegramToken" = "Токен Telegram бота" -"telegramTokenDesc" = "Необходимо получить токен у менеджера ботов Telegram @botfather" -"telegramProxy" = "Прокси Socks5" -"telegramProxyDesc" = "Если для подключения к Telegram вам нужен прокси Socks5. Настройте его параметры согласно руководству." -"telegramAPIServer" = "API-сервер Telegram" -"telegramAPIServerDesc" = "Используемый API-сервер Telegram. Оставьте пустым, чтобы использовать сервер по умолчанию." -"telegramChatId" = "Идентификатор Telegram администратора бота" -"telegramChatIdDesc" = "Один или несколько идентификаторов администратора бота. Чтобы получить идентификатор, используйте @userinfobot или команду '/id' в боте." -"telegramNotifyTime" = "Частота уведомлений бота Telegram" -"telegramNotifyTimeDesc" = "Используйте формат времени Crontab" -"tgNotifyBackup" = "Резервное копирование базы данных" -"tgNotifyBackupDesc" = "Включать файл резервной копии базы данных с уведомлением об отчете" -"tgNotifyLogin" = "Уведомление о входе" -"tgNotifyLoginDesc" = "Отображает имя пользователя, IP-адрес и время, когда кто-то пытается войти в вашу панель." -"sessionMaxAge" = "Продолжительность сессии" -"sessionMaxAgeDesc" = "Продолжительность сессии в системе (значение: минута)" -"expireTimeDiff" = "Порог истечения срока сессии для уведомления" -"expireTimeDiffDesc" = "Получение уведомления об истечении срока действия сессии до достижения порогового значения (значение: день)" -"trafficDiff" = "Порог трафика для уведомления" -"trafficDiffDesc" = "Получение уведомления об исчерпании трафика до достижения порога (значение: ГБ)" -"tgNotifyCpu" = "Порог нагрузки на ЦП для уведомления" -"tgNotifyCpuDesc" = "Получение уведомления, если нагрузка на ЦП превышает этот порог (значение: %)" -"timeZone" = "Часовой пояс" -"timeZoneDesc" = "Запланированные задачи выполняются в соответствии со временем в этом часовом поясе" -"subSettings" = "Подписка" -"subEnable" = "Включить службу" -"subEnableDesc" = "Функция подписки с отдельной конфигурацией" -"subListen" = "Прослушивание IP" -"subListenDesc" = "Оставьте пустым по умолчанию, чтобы отслеживать все IP-адреса" -"subPort" = "Порт подписки" -"subPortDesc" = "Номер порта для обслуживания службы подписки не должен использоваться на сервере" -"subCertPath" = "Путь к файлу открытого ключа сертификата подписки" -"subCertPathDesc" = "Введите полный путь, начинающийся с '/'" -"subKeyPath" = "Путь к файлу закрытого ключа сертификата подписки" -"subKeyPathDesc" = "Введите полный путь, начинающийся с '/'" -"subPath" = "Корневой путь URL-адреса подписки" -"subPathDesc" = "Должен начинаться с '/' и заканчиваться на '/'" -"subDomain" = "Домен прослушивания" -"subDomainDesc" = "Оставьте пустым по умолчанию, чтобы отслеживать все домены и IP-адреса" -"subUpdates" = "Интервалы обновления подписки" -"subUpdatesDesc" = "Интервал между обновлениями в клиентском приложении (в часах)" -"subEncrypt" = "Шифровать конфиги" -"subEncryptDesc" = "Шифровать возвращенные конфиги в подписке" -"subShowInfo" = "Показать информацию об использовании" -"subShowInfoDesc" = "Показывать оставшиеся трафик и дату после имени конфигурации" -"subURI" = "URI обратного прокси" -"subURIDesc" = "Изменить базовый URI URL-адреса подписки для использования за прокси-серверами" -"fragment" = "Фрагментация" -"fragmentDesc" = "Включить фрагментацию для пакета приветствия TLS" -"fragmentSett" = "Настройки фрагментации" -"noisesDesc" = "Включить Noises." -"noisesSett" = "Настройки Noises" -"mux" = "Mux" -"muxDesc" = "Передача нескольких независимых потоков данных в рамках установленного потока данных." -"muxSett" = "Mux Настройки" -"direct" = "Прямая связь" -"directDesc" = "Напрямую устанавливает соединения с доменами или диапазонами IP конкретной страны." - - -[pages.xray] -"title" = "Настройки Xray" -"save" = "Сохранить настройки" -"restart" = "Перезапустить Xray" -"basicTemplate" = "Базовый шаблон" -"advancedTemplate" = "Расширенный шаблон" -"generalConfigs" = "Основные настройки" -"generalConfigsDesc" = "Эти параметры описывают общие настройки" -"logConfigs" = "Журнал" -"logConfigsDesc" = "Журналы могут повлиять на эффективность вашего сервера. Рекомендуется включать их только в случае необходимости!" -"blockConfigs" = "Блокировка конфигураций" -"blockConfigsDesc" = "Эти параметры не позволят пользователям подключаться к определенным протоколам и веб-сайтам" -"basicRouting" = "Базовые соединения" -"blockConnectionsConfigsDesc" = "Эти параметры будут блокировать трафик в зависимости от запрашиваемой страны." -"directConnectionsConfigsDesc" = "Прямое соединение гарантирует, что определенный трафик не будет перенаправлен через другой сервер." -"blockips" = "Блокировать IP" -"blockdomains" = "Блокировать домены" -"directips" = "Прямые IP" -"directdomains" = "Прямые домены" -"ipv4Routing" = "Правила IPv4" -"ipv4RoutingDesc" = "Эти параметры позволят пользователям маршрутизироваться к целевым доменам только через IPv4" -"warpRouting" = "Правила WARP" -"warpRoutingDesc" = "Внимание: перед использованием этих параметров установите WARP в режиме прокси-сервера socks5 на свой сервер, следуя инструкциям на GitHub панели. WARP будет направлять трафик на веб-сайты через серверы Cloudflare" -"Template" = "Шаблон конфигурации Xray" -"TemplateDesc" = "Создание файла конфигурации Xray на основе этого шаблона" -"FreedomStrategy" = "Настройка стратегии протокола Freedom" -"FreedomStrategyDesc" = "Установка стратегии вывода сети в протоколе Freedom" -"RoutingStrategy" = "Настройка стратегии маршрутизации доменов" -"RoutingStrategyDesc" = "Установка общей стратегии маршрутизации разрешения DNS" -"Torrent" = "Запрет использования BitTorrent" -"TorrentDesc" = "Изменение шаблона конфигурации для предупреждения использования BitTorrent пользователями" -"Family" = "Блокируйте вредоносное ПО и контент для взрослых" -"FamilyDesc" = "DNS-преобразователи Cloudflare для блокировки вредоносного ПО и контента для взрослых в целях защиты семьи." -"Inbounds" = "Входящие" -"InboundsDesc" = "Изменение шаблона конфигурации для подключения определенных пользователей" -"Outbounds" = "Исходящие" -"Balancers" = "Балансировщик нагрузки" -"OutboundsDesc" = "Изменение шаблона конфигурации, чтобы определить исходящие пути для этого сервера" -"Routings" = "Правила маршрутизации" -"RoutingsDesc" = "Важен приоритет каждого правила!" -"completeTemplate" = "Все" -"logLevel" = "Уровень журнала" -"logLevelDesc" = "Уровень журнала для журналов ошибок, указывающий информацию, которую необходимо записать." -"accessLog" = "Журнал доступа" -"accessLogDesc" = "Путь к файлу журнала доступа. Специальное значение «none» отключает журналы доступа." -"errorLog" = "Журнал ошибок" -"errorLogDesc" = "Путь к файлу журнала ошибок. Специальное значение «none» отключает журналы ошибок." -"dnsLog" = "DNS Журнал" -"dnsLogDesc" = "Включить логи запросов DNS" -"outboundTraffic" = "Исходящий трафик" -"outboundTrafficDesc" = "Включить исходящий трафик" -"maskAddress" = "Маскировать Адрес" -"maskAddressDesc" = "Маска IP-адреса, при активации автоматически заменяет IP-адрес, который появляется в логе." - -[pages.xray.rules] -"first" = "Первый" -"last" = "Последний" -"up" = "Вверх" -"down" = "Вниз" -"source" = "Источник" -"dest" = "Пункт назначения" -"inbound" = "Входящий" -"outbound" = "Исходящий" -"balancer" = "Балансировщик" -"info" = "Информация" -"add" = "Добавить правило" -"edit" = "Редактировать правило" -"useComma" = "Элементы, разделенные запятыми" - -[pages.xray.outbound] -"addOutbound" = "Добавить исходящий" -"addReverse" = "Добавить реверс" -"editOutbound" = "Изменить исходящий" -"editReverse" = "Редактировать реверс" -"tag" = "Тег" -"tagDesc" = "Уникальный тег" -"address" = "Адрес" -"reverse" = "Обратный" -"domain" = "Домен" -"type" = "Тип" -"bridge" = "Мост" -"portal" = "Портал" -"intercon" = "Соединение" -"settings" = "Настройки" -"accountInfo" = "Информация об учетной записи" -"outboundStatus" = "Исходящий статус" -"sendThrough" = "Отправить через" - -[pages.xray.balancer] -"addBalancer" = "Добавить балансировщик" -"editBalancer" = "Редактировать балансировщик" -"balancerStrategy" = "Стратегия" -"balancerSelectors" = "Селекторы" -"tag" = "Тег" -"tagDesc" = "Уникальный тег" -"balancerDesc" = "Невозможно одновременно использовать balancerTag и outboundTag. При одновременном использовании будет работать только outboundTag." - -[pages.xray.wireguard] -"secretKey" = "Приватный ключ" -"publicKey" = "Публичный ключ" -"allowedIPs" = "Разрешенные IP-адреса" -"endpoint" = "Конечная точка" -"psk" = "Общий ключ" -"domainStrategy" = "Стратегия домена" - -[pages.xray.dns] -"enable" = "Включить DNS" -"enableDesc" = "Включить встроенный DNS-сервер" -"tag" = "Входящий тег DNS" -"tagDesc" = "Этот тег будет доступен как входящий тег в правилах маршрутизации." -"strategy" = "Стратегия запроса" -"strategyDesc" = "Общая стратегия разрешения доменных имен" -"add" = "Добавить сервер" -"edit" = "Редактировать сервер" -"domains" = "Домены" -"expectIPs" = "Ожидаемые IP" - -[pages.xray.fakedns] -"add" = "Добавить поддельный DNS" -"edit" = "Редактировать поддельный DNS" -"ipPool" = "Подсеть пула IP" -"poolSize" = "Размер пула" - -[pages.settings.security] -"admin" = "Админ" -"secret" = "Секретный токен" -"loginSecurity" = "Безопасность входа" -"loginSecurityDesc" = "Включить дополнительные меры безопасности входа пользователя" -"secretToken" = "Секретный токен" -"secretTokenDesc" = "Пожалуйста, скопируйте и сохраните этот токен в безопасном месте. Этот токен необходим для входа в систему и не может быть восстановлен с помощью инструмента x-ui" - -[pages.settings.toasts] -"modifySettings" = "Изменение настроек" -"getSettings" = "Просмотр настроек" -"modifyUser" = "Изменение пользователя" -"originalUserPassIncorrect" = "Неверное имя пользователя или пароль" -"userPassMustBeNotEmpty" = "Новое имя пользователя и новый пароль должны быть заполнены" - -[tgbot] -"keyboardClosed" = "❌ Закрыта настраиваемая клавиатура!" -"noResult" = "❗ Нет результатов!" -"noQuery" = "❌ Запрос не найден! Пожалуйста, повторите команду!" -"wentWrong" = "❌ Что-то пошло не так!" -"noIpRecord" = "❗ Нет записей об IP-адресе!" -"noInbounds" = "❗ Входящих соединений не найдено!" -"unlimited" = "♾ Неограниченно" -"add" = "Добавить" -"month" = "Месяц" -"months" = "Месяцев" -"day" = "День" -"days" = "Дней" -"hours" = "Часов" -"unknown" = "Неизвестно" -"inbounds" = "Входящие" -"clients" = "Клиенты" -"offline" = "🔴 Офлайн" -"online" = "🟢 Онлайн" - -[tgbot.commands] -"unknown" = "❗ Неизвестная команда" -"pleaseChoose" = "👇 Пожалуйста, выберите:\r\n" -"help" = "🤖 Добро пожаловать в этого бота! Он предназначен для предоставления вам конкретных данных с сервера и позволяет вносить необходимые изменения.\r\n\r\n" -"start" = "👋 Привет, {{ .Firstname }}.\r\n" -"welcome" = "🤖 Добро пожаловать в бота управления {{ .Hostname }}.\r\n" -"status" = "✅ Бот работает нормально!" -"usage" = "❗ Пожалуйста, укажите текст для поиска!" -"getID" = "🆔 Ваш ID: {{ .ID }}" -"helpAdminCommands" = "Для перезапуска Xray Core:\r\n/restart force\r\n\r\nДля поиска электронной почты клиента:\r\n/usage [Email]\r\n\r\nДля поиска входящих (со статистикой клиента):\r\n/inbound [Примечание]\r\n\r\nID чата Telegram:\r\n/id" -"helpClientCommands" = "Для поиска статистики используйте следующую команду:\r\n/usage [Email]\r\n\r\nID чата Telegram:\r\n/id" -"restartUsage" = "\r\n\r\n/restart force" -"restartSuccess" = "✅ Операция успешно завершена!" -"restartFailed" = "❗ Ошибка в операции.\r\n\r\nОшибка: {{ .Error }}." -"xrayNotRunning" = "❗ Xray Core не запущен." - -[tgbot.messages] -"cpuThreshold" = "🔴 Загрузка процессора составляет {{ .Percent }}%, что превышает пороговое значение {{ .Threshold }}%" -"selectUserFailed" = "❌ Ошибка при выборе пользователя!" -"userSaved" = "✅ Пользователь Telegram сохранен." -"loginSuccess" = "✅ Успешный вход в панель.\r\n" -"loginFailed" = "❗️ Ошибка входа в панель.\r\n" -"report" = "🕰 Запланированные отчеты: {{ .RunTime }}\r\n" -"datetime" = "⏰ Дата и время: {{ .DateTime }}\r\n" -"hostname" = "💻 Имя хоста: {{ .Hostname }}\r\n" -"version" = "🚀 Версия X-UI: {{ .Version }}\r\n" -"xrayVersion" = "📡 Версия Xray: {{ .XrayVersion }}\r\n" -"ipv6" = "🌐 IPv6: {{ .IPv6 }}\r\n" -"ipv4" = "🌐 IPv4: {{ .IPv4 }}\r\n" -"ip" = "🌐 IP: {{ .IP }}\r\n" -"ips" = "🔢 IP-адреса:\r\n{{ .IPs }}\r\n" -"serverUpTime" = "⏳ Время работы сервера: {{ .UpTime }} {{ .Unit }}\r\n" -"serverLoad" = "📈 Загрузка сервера: {{ .Load1 }}, {{ .Load2 }}, {{ .Load3 }}\r\n" -"serverMemory" = "📋 Память сервера: {{ .Current }}/{{ .Total }}\r\n" -"tcpCount" = "🔹 Количество TCP-соединений: {{ .Count }}\r\n" -"udpCount" = "🔸 Количество UDP-соединений: {{ .Count }}\r\n" -"traffic" = "🚦 Трафик: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n" -"xrayStatus" = "ℹ️ Состояние Xray: {{ .State }}\r\n" -"username" = "👤 Имя пользователя: {{ .Username }}\r\n" -"password" = "👤 Пароль: {{ .Password }}\r\n" -"time" = "⏰ Время: {{ .Time }}\r\n" -"inbound" = "📍 Входящий поток: {{ .Remark }}\r\n" -"port" = "🔌 Порт: {{ .Port }}\r\n" -"expire" = "📅 Дата окончания: {{ .Time }}\r\n" -"expireIn" = "📅 Окончание через: {{ .Time }}\r\n" -"active" = "💡 Активен: {{ .Enable }}\r\n" -"enabled" = "🚨 Включен: {{ .Enable }}\r\n" -"online" = "🌐 Статус соединения: {{ .Status }}\r\n" -"email" = "📧 Email: {{ .Email }}\r\n" -"upload" = "🔼 Исходящий трафик: ↑{{ .Upload }}\r\n" -"download" = "🔽 Входящий трафик: ↓{{ .Download }}\r\n" -"total" = "📊 Всего: ↑↓{{ .UpDown }} из {{ .Total }}\r\n" -"TGUser" = "👤 Пользователь Telegram: {{ .TelegramID }}\r\n" -"exhaustedMsg" = "🚨 Исчерпаны {{ .Type }}:\r\n" -"exhaustedCount" = "🚨 Количество исчерпанных {{ .Type }}:\r\n" -"onlinesCount" = "🌐 Клиентов онлайн: {{ .Count }}\r\n" -"disabled" = "🛑 Отключено: {{ .Disabled }}\r\n" -"depleteSoon" = "🔜 Скоро исчерпание: {{ .Deplete }}\r\n\r\n" -"backupTime" = "🗄 Время резервного копирования: {{ .Time }}\r\n" -"refreshedOn" = "\r\n📋🔄 Обновлено: {{ .Time }}\r\n\r\n" -"yes" = "✅ Да" -"no" = "❌ Нет" - -[tgbot.buttons] -"closeKeyboard" = "❌ Закрыть клавиатуру" -"cancel" = "❌ Отмена" -"cancelReset" = "❌ Отменить сброс" -"cancelIpLimit" = "❌ Отменить лимит IP" -"confirmResetTraffic" = "✅ Подтвердить сброс трафика?" -"confirmClearIps" = "✅ Подтвердить очистку IP?" -"confirmRemoveTGUser" = "✅ Подтвердить удаление пользователя Telegram?" -"confirmToggle" = "✅ Подтвердить вкл/выкл пользователя?" -"dbBackup" = "Получить резервную копию DB" -"serverUsage" = "Использование сервера" -"getInbounds" = "Получить входящие потоки" -"depleteSoon" = "Скоро исчерпание" -"clientUsage" = "Получить использование" -"onlines" = "Онлайн-клиенты" -"commands" = "Команды" -"refresh" = "🔄 Обновить" -"clearIPs" = "❌ Очистить IP" -"removeTGUser" = "❌ Удалить пользователя Telegram" -"selectTGUser" = "👤 Выбрать пользователя Telegram" -"selectOneTGUser" = "👤 Выберите пользователя Telegram:" -"resetTraffic" = "📈 Сбросить трафик" -"resetExpire" = "📅 Изменить дату окончания" -"ipLog" = "🔢 Лог IP" -"ipLimit" = "🔢 Лимит IP" -"setTGUser" = "👤 Установить пользователя Telegram" -"toggle" = "🔘 Вкл./Выкл." -"custom" = "🔢 Свой" -"confirmNumber" = "✅ Подтвердить: {{ .Num }}" -"confirmNumberAdd" = "✅ Подтвердить добавление: {{ .Num }}" -"limitTraffic" = "🚧 Лимит трафика" -"getBanLogs" = "Логи блокировок" -"allClients" = "Все клиенты" - -[tgbot.answers] -"successfulOperation" = "✅ Успешный!" -"errorOperation" = "❗ Ошибка в операции." -"getInboundsFailed" = "❌ Не удалось получить входящие потоки." -"getClientsFailed" = "❌ Не удалось получить клиентов." -"canceled" = "❌ {{ .Email }}: Операция отменена." -"clientRefreshSuccess" = "✅ {{ .Email }}: Клиент успешно обновлен." -"IpRefreshSuccess" = "✅ {{ .Email }}: IP-адреса успешно обновлены." -"TGIdRefreshSuccess" = "✅ {{ .Email }}: Пользователь Telegram клиента успешно обновлен." -"resetTrafficSuccess" = "✅ {{ .Email }}: Трафик успешно сброшен." -"setTrafficLimitSuccess" = "✅ {{ .Email }}: Лимит Трафик успешно сохранен." -"expireResetSuccess" = "✅ {{ .Email }}: Дни истечения успешно сброшены." -"resetIpSuccess" = "✅ {{ .Email }}: Лимит IP ({{ .Count }}) успешно сохранен." -"clearIpSuccess" = "✅ {{ .Email }}: IP-адреса успешно очищены." -"getIpLog" = "✅ {{ .Email }}: Получен лог IP." -"getUserInfo" = "✅ {{ .Email }}: Получена информация о пользователе Telegram." -"removedTGUserSuccess" = "✅ {{ .Email }}: Пользователь Telegram успешно удален." -"enableSuccess" = "✅ {{ .Email }}: Включено успешно." -"disableSuccess" = "✅ {{ .Email }}: Отключено успешно." -"askToAddUserId" = "Ваша конфигурация не найдена!\r\nПожалуйста, попросите администратора использовать ваш идентификатор пользователя Telegram в ваших конфигурациях.\r\n\r\nВаш идентификатор пользователя: {{ .TgUserID }}" -"chooseClient" = "Выберите пользователя для подключения {{ .Inbound }}" -"chooseInbound" = "Выберите подключение" diff --git a/web/translation/translate.tr_TR.toml b/web/translation/translate.tr_TR.toml deleted file mode 100644 index 662c87c3..00000000 --- a/web/translation/translate.tr_TR.toml +++ /dev/null @@ -1,598 +0,0 @@ -"username" = "Kullanıcı Adı" -"password" = "Şifre" -"login" = "Giriş Yap" -"confirm" = "Onayla" -"cancel" = "İptal" -"close" = "Kapat" -"copy" = "Kopyala" -"copied" = "Kopyalandı" -"download" = "İndir" -"remark" = "Açıklama" -"enable" = "Etkin" -"protocol" = "Protokol" -"search" = "Ara" -"filter" = "Filtrele" -"loading" = "Yükleniyor..." -"second" = "Saniye" -"minute" = "Dakika" -"hour" = "Saat" -"day" = "Gün" -"check" = "Kontrol Et" -"indefinite" = "Belirsiz" -"unlimited" = "Sınırsız" -"none" = "Hiçbiri" -"qrCode" = "QR Kod" -"info" = "Daha Fazla Bilgi" -"edit" = "Düzenle" -"delete" = "Sil" -"reset" = "Sıfırla" -"copySuccess" = "Başarıyla Kopyalandı" -"sure" = "Emin misiniz" -"encryption" = "Şifreleme" -"transmission" = "İletim" -"host" = "Sunucu" -"path" = "Yol" -"camouflage" = "Kandırma" -"status" = "Durum" -"enabled" = "Etkin" -"disabled" = "Devre Dışı" -"depleted" = "Bitti" -"depletingSoon" = "Bitmek Üzere" -"offline" = "Çevrimdışı" -"online" = "Çevrimiçi" -"domainName" = "Alan Adı" -"monitor" = "Dinleme IP" -"certificate" = "Dijital Sertifika" -"fail" = "Başarısız" -"comment" = "Yorum" -"success" = "Başarılı" -"getVersion" = "Sürümü Al" -"install" = "Yükle" -"clients" = "Müşteriler" -"usage" = "Kullanım" -"secretToken" = "Gizli Anahtar" -"remained" = "Kalan" -"security" = "Güvenlik" -"secAlertTitle" = "Güvenlik Uyarısı" -"secAlertSsl" = "Bu bağlantı güvenli değil. Verilerin korunması için TLS etkinleştirilene kadar hassas bilgiler girmekten kaçının." -"secAlertConf" = "Bazı ayarlar saldırılara açıktır. Olası ihlalleri önlemek için güvenlik protokollerini güçlendirmeniz önerilir." -"secAlertSSL" = "Panelde güvenli bağlantı yok. Verilerin korunması için TLS sertifikası yükleyin." -"secAlertPanelPort" = "Panel varsayılan portu savunmasız. Rastgele veya belirli bir port yapılandırın." -"secAlertPanelURI" = "Panel varsayılan URI yolu güvensiz. Karmaşık bir URI yolu yapılandırın." -"secAlertSubURI" = "Abonelik varsayılan URI yolu güvensiz. Karmaşık bir URI yolu yapılandırın." -"secAlertSubJsonURI" = "Abonelik JSON varsayılan URI yolu güvensiz. Karmaşık bir URI yolu yapılandırın." - -[menu] -"dashboard" = "Genel Bakış" -"inbounds" = "Gelenler" -"settings" = "Panel Ayarları" -"xray" = "Xray Yapılandırmaları" -"logout" = "Çıkış Yap" -"link" = "Yönet" - -[pages.login] -"hello" = "Merhaba" -"title" = "Hoş Geldiniz" -"loginAgain" = "Oturum süreniz doldu, lütfen tekrar giriş yapın" - -[pages.login.toasts] -"invalidFormData" = "Girdi verisi formatı geçersiz." -"emptyUsername" = "Kullanıcı adı gerekli" -"emptyPassword" = "Şifre gerekli" -"wrongUsernameOrPassword" = "Geçersiz kullanıcı adı veya şifre veya gizli anahtar." -"successLogin" = "Giriş Başarılı" - -[pages.index] -"title" = "Genel Bakış" -"memory" = "RAM" -"hard" = "Disk" -"xrayStatus" = "Xray" -"stopXray" = "Durdur" -"restartXray" = "Yeniden Başlat" -"xraySwitch" = "Sürüm" -"xraySwitchClick" = "Geçiş yapmak istediğiniz sürümü seçin." -"xraySwitchClickDesk" = "Dikkatli seçin, eski sürümler mevcut yapılandırmalarla uyumlu olmayabilir." -"operationHours" = "Çalışma Süresi" -"systemLoad" = "Sistem Yükü" -"systemLoadDesc" = "Geçmiş 1, 5 ve 15 dakika için sistem yük ortalaması" -"connectionTcpCountDesc" = "Sistem genelinde toplam TCP bağlantıları" -"connectionUdpCountDesc" = "Sistem genelinde toplam UDP bağlantıları" -"connectionCount" = "Bağlantı İstatistikleri" -"upSpeed" = "Sistem genelinde toplam yükleme hızı" -"downSpeed" = "Sistem genelinde toplam indirme hızı" -"totalSent" = "İşletim sistemi başlatıldığından beri sistem genelinde gönderilen toplam veri" -"totalReceive" = "İşletim sistemi başlatıldığından beri sistem genelinde alınan toplam veri" -"xraySwitchVersionDialog" = "Xray Sürümünü Değiştir" -"xraySwitchVersionDialogDesc" = "Xray sürümünü değiştirmek istediğinizden emin misiniz" -"dontRefresh" = "Kurulum devam ediyor, lütfen bu sayfayı yenilemeyin" -"logs" = "Günlükler" -"config" = "Yapılandırma" -"backup" = "Yedekle & Geri Yükle" -"backupTitle" = "Veritabanı Yedekleme & Geri Yükleme" -"backupDescription" = "Veritabanını geri yüklemeden önce yedek almanız önerilir." -"exportDatabase" = "Yedekle" -"importDatabase" = "Geri Yükle" - -[pages.inbounds] -"title" = "Gelenler" -"totalDownUp" = "Toplam Gönderilen/Alınan" -"totalUsage" = "Toplam Kullanım" -"inboundCount" = "Toplam Gelen" -"operate" = "Menü" -"enable" = "Etkin" -"remark" = "Açıklama" -"protocol" = "Protokol" -"port" = "Port" -"traffic" = "Trafik" -"details" = "Detaylar" -"transportConfig" = "Taşıma" -"expireDate" = "Süre" -"resetTraffic" = "Trafiği Sıfırla" -"addInbound" = "Gelen Ekle" -"generalActions" = "Genel Eylemler" -"create" = "Oluştur" -"update" = "Güncelle" -"modifyInbound" = "Geleni Düzenle" -"deleteInbound" = "Geleni Sil" -"deleteInboundContent" = "Geleni silmek istediğinizden emin misiniz?" -"deleteClient" = "Müşteriyi Sil" -"deleteClientContent" = "Müşteriyi silmek istediğinizden emin misiniz?" -"resetTrafficContent" = "Trafiği sıfırlamak istediğinizden emin misiniz?" -"copyLink" = "URL'yi Kopyala" -"address" = "Adres" -"network" = "Ağ" -"destinationPort" = "Hedef Port" -"targetAddress" = "Hedef Adres" -"monitorDesc" = "Tüm IP'leri dinlemek için boş bırakın" -"meansNoLimit" = "= Sınırsız. (birim: GB)" -"totalFlow" = "Toplam Akış" -"leaveBlankToNeverExpire" = "Hiçbir zaman sona ermemesi için boş bırakın" -"noRecommendKeepDefault" = "Varsayılanı korumanız önerilir" -"certificatePath" = "Dosya Yolu" -"certificateContent" = "Dosya İçeriği" -"publicKey" = "Genel Anahtar" -"privatekey" = "Özel Anahtar" -"clickOnQRcode" = "Kopyalamak için QR Kodu Tıklayın" -"client" = "Müşteri" -"export" = "Tüm URL'leri Dışa Aktar" -"clone" = "Klonla" -"cloneInbound" = "Klonla" -"cloneInboundContent" = "Bu gelenin tüm ayarları, Port, Dinleme IP ve Müşteriler hariç, klona uygulanacaktır." -"cloneInboundOk" = "Klonla" -"resetAllTraffic" = "Tüm Gelen Trafiğini Sıfırla" -"resetAllTrafficTitle" = "Tüm Gelen Trafiğini Sıfırla" -"resetAllTrafficContent" = "Tüm gelenlerin trafiğini sıfırlamak istediğinizden emin misiniz?" -"resetInboundClientTraffics" = "Müşteri Trafiklerini Sıfırla" -"resetInboundClientTrafficTitle" = "Müşteri Trafiklerini Sıfırla" -"resetInboundClientTrafficContent" = "Bu gelenin müşterilerinin trafiğini sıfırlamak istediğinizden emin misiniz?" -"resetAllClientTraffics" = "Tüm Müşteri Trafiklerini Sıfırla" -"resetAllClientTrafficTitle" = "Tüm Müşteri Trafiklerini Sıfırla" -"resetAllClientTrafficContent" = "Tüm müşterilerin trafiğini sıfırlamak istediğinizden emin misiniz?" -"delDepletedClients" = "Bitmiş Müşterileri Sil" -"delDepletedClientsTitle" = "Bitmiş Müşterileri Sil" -"delDepletedClientsContent" = "Tüm bitmiş müşterileri silmek istediğinizden emin misiniz?" -"email" = "E-posta" -"emailDesc" = "Lütfen benzersiz bir e-posta adresi sağlayın." -"IPLimit" = "IP Limiti" -"IPLimitDesc" = "Sayının aşılması durumunda gelen devre dışı bırakılır. (0 = devre dışı)" -"IPLimitlog" = "IP Günlüğü" -"IPLimitlogDesc" = "IP geçmiş günlüğü. (devre dışı bırakıldıktan sonra gelini etkinleştirmek için günlüğü temizleyin)" -"IPLimitlogclear" = "Günlüğü Temizle" -"setDefaultCert" = "Panelden Sertifikayı Ayarla" -"telegramDesc" = "Lütfen Telegram Sohbet Kimliği sağlayın. (botta '/id' komutunu kullanın) veya (@userinfobot)" -"subscriptionDesc" = "Abonelik URL'inizi bulmak için 'Detaylar'a gidin. Ayrıca, aynı adı birden fazla müşteri için kullanabilirsiniz." -"info" = "Bilgi" -"same" = "Aynı" -"inboundData" = "Gelenin Verileri" -"exportInbound" = "Geleni Dışa Aktar" -"import" = "İçe Aktar" -"importInbound" = "Bir Gelen İçe Aktar" - -[pages.client] -"add" = "Müşteri Ekle" -"edit" = "Müşteriyi Düzenle" -"submitAdd" = "Müşteri Ekle" -"submitEdit" = "Değişiklikleri Kaydet" -"clientCount" = "Müşteri Sayısı" -"bulk" = "Toplu Ekle" -"method" = "Yöntem" -"first" = "İlk" -"last" = "Son" -"prefix" = "Önek" -"postfix" = "Sonek" -"delayedStart" = "İlk Kullanımdan Sonra Başlat" -"expireDays" = "Süre" -"days" = "Gün" -"renew" = "Otomatik Yenile" -"renewDesc" = "Süresi dolduktan sonra otomatik yenileme. (0 = devre dışı)(birim: gün)" - -[pages.inbounds.toasts] -"obtain" = "Elde Et" - -[pages.inbounds.stream.general] -"request" = "İstek" -"response" = "Yanıt" -"name" = "Ad" -"value" = "Değer" - -[pages.inbounds.stream.tcp] -"version" = "Sürüm" -"method" = "Yöntem" -"path" = "Yol" -"status" = "Durum" -"statusDescription" = "Durum Açıklaması" -"requestHeader" = "İstek Başlığı" -"responseHeader" = "Yanıt Başlığı" - -[pages.settings] -"title" = "Panel Ayarları" -"save" = "Kaydet" -"infoDesc" = "Burada yapılan her değişikliğin kaydedilmesi gerekir. Değişikliklerin uygulanması için paneli yeniden başlatın." -"restartPanel" = "Paneli Yeniden Başlat" -"restartPanelDesc" = "Paneli yeniden başlatmak istediğinizden emin misiniz? Yeniden başlattıktan sonra panele erişemezseniz, sunucudaki panel günlük bilgilerini görüntüleyin." -"actions" = "Eylemler" -"resetDefaultConfig" = "Varsayılana Sıfırla" -"panelSettings" = "Genel" -"securitySettings" = "Kimlik Doğrulama" -"TGBotSettings" = "Telegram Bot" -"panelListeningIP" = "Dinleme IP" -"panelListeningIPDesc" = "Web paneli için IP adresi. (tüm IP'leri dinlemek için boş bırakın)" -"panelListeningDomain" = "Dinleme Alan Adı" -"panelListeningDomainDesc" = "Web paneli için alan adı. (tüm alan adlarını ve IP'leri dinlemek için boş bırakın)" -"panelPort" = "Dinleme Portu" -"panelPortDesc" = "Web paneli için port numarası. (kullanılmayan bir port olmalıdır)" -"publicKeyPath" = "Genel Anahtar Yolu" -"publicKeyPathDesc" = "Web paneli için genel anahtar dosya yolu. ('/' ile başlar)" -"privateKeyPath" = "Özel Anahtar Yolu" -"privateKeyPathDesc" = "Web paneli için özel anahtar dosya yolu. ('/' ile başlar)" -"panelUrlPath" = "URI Yolu" -"panelUrlPathDesc" = "Web paneli için URI yolu. ('/' ile başlar ve '/' ile biter)" -"pageSize" = "Sayfa Boyutu" -"pageSizeDesc" = "Gelenler tablosu için sayfa boyutunu belirleyin. (0 = devre dışı)" -"remarkModel" = "Açıklama Modeli & Ayırma Karakteri" -"datepicker" = "Takvim Türü" -"datepickerPlaceholder" = "Tarih Seçin" -"datepickerDescription" = "Planlanmış görevler bu takvime göre çalışacaktır." -"sampleRemark" = "Örnek Açıklama" -"oldUsername" = "Mevcut Kullanıcı Adı" -"currentPassword" = "Mevcut Şifre" -"newUsername" = "Yeni Kullanıcı Adı" -"newPassword" = "Yeni Şifre" -"telegramBotEnable" = "Telegram Botunu Etkinleştir" -"telegramBotEnableDesc" = "Telegram botunu etkinleştirir." -"telegramToken" = "Telegram Token" -"telegramTokenDesc" = "'@BotFather'dan alınan Telegram bot token." -"telegramProxy" = "SOCKS Proxy" -"telegramProxyDesc" = "Telegram'a bağlanmak için SOCKS5 proxy'sini etkinleştirir. (ayarları kılavuzda belirtilen şekilde ayarlayın)" -"telegramAPIServer" = "Telegram API Server" -"telegramAPIServerDesc" = "Kullanılacak Telegram API sunucusu. Varsayılan sunucuyu kullanmak için boş bırakın." -"telegramChatId" = "Yönetici Sohbet Kimliği" -"telegramChatIdDesc" = "Telegram Yönetici Sohbet Kimliği(leri). (virgülle ayrılmış)(buradan alın @userinfobot) veya (botta '/id' komutunu kullanın)" -"telegramNotifyTime" = "Bildirim Zamanı" -"telegramNotifyTimeDesc" = "Periyodik raporlar için ayarlanan Telegram bot bildirim zamanı. (crontab zaman formatını kullanın)" -"tgNotifyBackup" = "Veritabanı Yedeği" -"tgNotifyBackupDesc" = "Bir rapor ile birlikte veritabanı yedek dosyasını gönder." -"tgNotifyLogin" = "Giriş Bildirimi" -"tgNotifyLoginDesc" = "Birisi web panelinize giriş yapmaya çalıştığında kullanıcı adı, IP adresi ve zaman hakkında bildirim alın." -"sessionMaxAge" = "Oturum Süresi" -"sessionMaxAgeDesc" = "Giriş yaptıktan sonra oturum süresi. (birim: dakika)" -"expireTimeDiff" = "Son Kullanma Tarihi Bildirimi" -"expireTimeDiffDesc" = "Bu eşik seviyesine ulaşıldığında son kullanma tarihi hakkında bildirim alın. (birim: gün)" -"trafficDiff" = "Trafik Sınırı Bildirimi" -"trafficDiffDesc" = "Bu eşik seviyesine ulaşıldığında trafik sınırı hakkında bildirim alın. (birim: GB)" -"tgNotifyCpu" = "CPU Yükü Bildirimi" -"tgNotifyCpuDesc" = "CPU yükü bu eşik seviyesini aşarsa bildirim alın. (birim: %)" -"timeZone" = "Saat Dilimi" -"timeZoneDesc" = "Planlanmış görevler bu saat dilimine göre çalışacaktır." -"subSettings" = "Abonelik" -"subEnable" = "Abonelik Hizmetini Etkinleştir" -"subEnableDesc" = "Abonelik hizmetini etkinleştirir." -"subListen" = "Dinleme IP" -"subListenDesc" = "Abonelik hizmeti için IP adresi. (tüm IP'leri dinlemek için boş bırakın)" -"subPort" = "Dinleme Portu" -"subPortDesc" = "Abonelik hizmeti için port numarası. (kullanılmayan bir port olmalıdır)" -"subCertPath" = "Genel Anahtar Yolu" -"subCertPathDesc" = "Abonelik hizmeti için genel anahtar dosya yolu. ('/' ile başlar)" -"subKeyPath" = "Özel Anahtar Yolu" -"subKeyPathDesc" = "Abonelik hizmeti için özel anahtar dosya yolu. ('/' ile başlar)" -"subPath" = "URI Yolu" -"subPathDesc" = "Abonelik hizmeti için URI yolu. ('/' ile başlar ve '/' ile biter)" -"subDomain" = "Dinleme Alan Adı" -"subDomainDesc" = "Abonelik hizmeti için alan adı. (tüm alan adlarını ve IP'leri dinlemek için boş bırakın)" -"subUpdates" = "Güncelleme Aralıkları" -"subUpdatesDesc" = "Müşteri uygulamalarındaki abonelik URL'sinin güncelleme aralıkları. (birim: saat)" -"subEncrypt" = "Şifrele" -"subEncryptDesc" = "Abonelik hizmetinin döndürülen içeriği Base64 ile şifrelenir." -"subShowInfo" = "Kullanım Bilgisini Göster" -"subShowInfoDesc" = "Kalan trafik ve tarih müşteri uygulamalarında görüntülenir." -"subURI" = "Ters Proxy URI" -"subURIDesc" = "Proxy arkasında kullanılacak abonelik URL'sinin URI yolu." -"fragment" = "Parçalama" -"fragmentDesc" = "TLS merhaba paketinin parçalanmasını etkinleştir." -"fragmentSett" = "Parçalama Ayarları" -"noisesDesc" = "Noises'i Etkinleştir." -"noisesSett" = "Noises Ayarları" -"mux" = "Mux" -"muxDesc" = "Kurulmuş bir veri akışında birden çok bağımsız veri akışını iletir." -"muxSett" = "Mux Ayarları" -"direct" = "Doğrudan Bağlantı" -"directDesc" = "Belirli bir ülkenin alan adları veya IP aralıkları ile doğrudan bağlantı kurar." - - -[pages.xray] -"title" = "Xray Yapılandırmaları" -"save" = "Kaydet" -"restart" = "Xray'i Yeniden Başlat" -"basicTemplate" = "Temeller" -"advancedTemplate" = "Gelişmiş" -"generalConfigs" = "Genel" -"generalConfigsDesc" = "Bu seçenekler genel ayarlamaları belirler." -"logConfigs" = "Günlük" -"logConfigsDesc" = "Günlükler sunucunuzun verimliliğini etkileyebilir. Yalnızca ihtiyaç durumunda akıllıca etkinleştirmeniz önerilir" -"blockConfigs" = "Koruma Kalkanı" -"blockConfigsDesc" = "Bu seçenekler belirli istek protokolleri ve web siteleri temelinde trafiği engeller." -"basicRouting" = "Temel Yönlendirme" -"blockConnectionsConfigsDesc" = "Bu seçenekler belirli bir istenen ülkeye göre trafiği engelleyecektir." -"directConnectionsConfigsDesc" = "Doğrudan bağlantı, belirli bir trafiğin başka bir sunucu üzerinden yönlendirilmediğini sağlar." -"blockips" = "IP'leri Engelle" -"blockdomains" = "Alan Adlarını Engelle" -"directips" = "Doğrudan IP'ler" -"directdomains" = "Doğrudan Alan Adları" -"ipv4Routing" = "IPv4 Yönlendirme" -"ipv4RoutingDesc" = "Bu seçenekler belirli bir varış yerine IPv4 üzerinden trafiği yönlendirir." -"warpRouting" = "WARP Yönlendirme" -"warpRoutingDesc" = "Bu seçenekler belirli bir varış yerine WARP üzerinden trafiği yönlendirir." -"Template" = "Gelişmiş Xray Yapılandırma Şablonu" -"TemplateDesc" = "Nihai Xray yapılandırma dosyası bu şablona göre oluşturulacaktır." -"FreedomStrategy" = "Freedom Protokol Stratejisi" -"FreedomStrategyDesc" = "Freedom Protokolünde ağın çıkış stratejisini ayarlayın." -"RoutingStrategy" = "Genel Yönlendirme Stratejisi" -"RoutingStrategyDesc" = "Tüm istekleri çözmek için genel trafik yönlendirme stratejisini ayarlayın." -"Torrent" = "BitTorrent Protokolünü Engelle" -"TorrentDesc" = "BitTorrent protokolünü engeller." -"Family" = "Aile Koruması" -"FamilyDesc" = "Yetişkin içerikli ve kötü amaçlı yazılım web sitelerini engeller." -"Inbounds" = "Gelenler" -"InboundsDesc" = "Belirli müşterileri kabul eder." -"Outbounds" = "Gidenler" -"Balancers" = "Dengeler" -"OutboundsDesc" = "Giden trafiğin yolunu ayarlayın." -"Routings" = "Yönlendirme Kuralları" -"RoutingsDesc" = "Her kuralın önceliği önemlidir!" -"completeTemplate" = "Tümü" -"logLevel" = "Günlük Seviyesi" -"logLevelDesc" = "Hata günlükleri için günlük seviyesi, kaydedilmesi gereken bilgileri belirtir." -"accessLog" = "Erişim Günlüğü" -"accessLogDesc" = "Erişim günlüğü için dosya yolu. 'none' özel değeri erişim günlüklerini devre dışı bırakır" -"errorLog" = "Hata Günlüğü" -"errorLogDesc" = "Hata günlüğü için dosya yolu. 'none' özel değeri hata günlüklerini devre dışı bırakır" -"dnsLog" = "DNS Günlüğü" -"dnsLogDesc" = "DNS sorgu günlüklerini etkinleştirin" -"outboundTraffic" = "Gidenler trafiği" -"outboundTrafficDesc" = "Çıkış trafiğini etkinleştirip etkinleştirmeyeceğiniz" -"maskAddress" = "Adres Maskesi" -"maskAddressDesc" = "IP adresi maskesi, etkinleştirildiğinde, günlükte görünen IP adresini otomatik olarak değiştirecektir." - -[pages.xray.rules] -"first" = "İlk" -"last" = "Son" -"up" = "Yukarı" -"down" = "Aşağı" -"source" = "Kaynak" -"dest" = "Hedef" -"inbound" = "Gelen" -"outbound" = "Giden" -"balancer" = "Dengeler" -"info" = "Bilgi" -"add" = "Kural Ekle" -"edit" = "Kuralı Düzenle" -"useComma" = "Virgülle ayrılmış öğeler" - -[pages.xray.outbound] -"addOutbound" = "Giden Ekle" -"addReverse" = "Ters Ekle" -"editOutbound" = "Gideni Düzenle" -"editReverse" = "Tersi Düzenle" -"tag" = "Etiket" -"tagDesc" = "Benzersiz Etiket" -"address" = "Adres" -"reverse" = "Ters" -"domain" = "Alan Adı" -"type" = "Tür" -"bridge" = "Köprü" -"portal" = "Portal" -"intercon" = "Bağlantı" -"settings" = "Ayarlar" -"accountInfo" = "Hesap Bilgileri" -"outboundStatus" = "Giden Durumu" -"sendThrough" = "Üzerinden Gönder" - -[pages.xray.balancer] -"addBalancer" = "Dengeleyici Ekle" -"editBalancer" = "Dengeleyiciyi Düzenle" -"balancerStrategy" = "Strateji" -"balancerSelectors" = "Seçiciler" -"tag" = "Etiket" -"tagDesc" = "Benzersiz Etiket" -"balancerDesc" = "Dengeleyici Etiketi ve Giden Etiketi aynı anda kullanılamaz. Aynı anda kullanıldığında yalnızca giden etiketi çalışır." - -[pages.xray.wireguard] -"secretKey" = "Gizli Anahtar" -"publicKey" = "Genel Anahtar" -"allowedIPs" = "İzin Verilen IP'ler" -"endpoint" = "Uç Nokta" -"psk" = "Ön Paylaşılan Anahtar" -"domainStrategy" = "Alan Adı Stratejisi" - -[pages.xray.dns] -"enable" = "DNS'yi Etkinleştir" -"enableDesc" = "Dahili DNS sunucusunu etkinleştir" -"tag" = "DNS Gelen Etiketi" -"tagDesc" = "Bu etiket, yönlendirme kurallarında Gelen etiketi olarak kullanılabilir." -"strategy" = "Sorgu Stratejisi" -"strategyDesc" = "Alan adlarını çözmek için genel strateji" -"add" = "Sunucu Ekle" -"edit" = "Sunucuyu Düzenle" -"domains" = "Alan Adları" -"expectIPs" = "Beklenen IP'ler" - -[pages.xray.fakedns] -"add" = "Sahte DNS Ekle" -"edit" = "Sahte DNS'i Düzenle" -"ipPool" = "IP Havuzu Alt Ağı" -"poolSize" = "Havuz Boyutu" - -[pages.settings.security] -"admin" = "Yönetici" -"secret" = "Gizli Anahtar" -"loginSecurity" = "Güvenli Giriş" -"loginSecurityDesc" = "Daha fazla güvenlik sağlamak için ek bir kimlik doğrulama katmanı ekler." -"secretToken" = "Gizli Anahtar" -"secretTokenDesc" = "Bu anahtarı güvenli bir yerde saklayın. Bu anahtar giriş için gereklidir ve geri alınamaz." - -[pages.settings.toasts] -"modifySettings" = "Ayarları Değiştir" -"getSettings" = "Ayarları Al" -"modifyUser" = "Yönetici Değiştir" -"originalUserPassIncorrect" = "Mevcut kullanıcı adı veya şifre geçersiz" -"userPassMustBeNotEmpty" = "Yeni kullanıcı adı ve şifre boş olamaz" - -[tgbot] -"keyboardClosed" = "❌ Özel klavye kapalı!" -"noResult" = "❗ Sonuç yok!" -"noQuery" = "❌ Sorgu bulunamadı! Lütfen komutu tekrar kullanın!" -"wentWrong" = "❌ Bir şeyler yanlış gitti!" -"noIpRecord" = "❗ IP Kaydı yok!" -"noInbounds" = "❗ Gelen bulunamadı!" -"unlimited" = "♾ Sınırsız(Sıfırla)" -"add" = "Ekle" -"month" = "Ay" -"months" = "Aylar" -"day" = "Gün" -"days" = "Günler" -"hours" = "Saatler" -"unknown" = "Bilinmiyor" -"inbounds" = "Gelenler" -"clients" = "Müşteriler" -"offline" = "🔴 Çevrimdışı" -"online" = "🟢 Çevrimiçi" - -[tgbot.commands] -"unknown" = "❗ Bilinmeyen komut." -"pleaseChoose" = "👇 Lütfen seçin:\r\n" -"help" = "🤖 Bu bota hoş geldiniz! Web panelinden belirli verileri sunmak ve gerektiğinde değişiklik yapmanıza olanak tanımak için tasarlanmıştır.\r\n\r\n" -"start" = "👋 Merhaba {{ .Firstname }}.\r\n" -"welcome" = "🤖 {{ .Hostname }} yönetim botuna hoş geldiniz.\r\n" -"status" = "✅ Bot çalışıyor!" -"usage" = "❗ Lütfen aramak için bir metin sağlayın!" -"getID" = "🆔 Kimliğiniz: {{ .ID }}" -"helpAdminCommands" = "Xray Core'u yeniden başlatmak için:\r\n/restart force\r\n\r\nBir müşteri e-postasını aramak için:\r\n/usage [E-posta]\r\n\r\nGelenleri aramak için (müşteri istatistikleri ile):\r\n/inbound [Açıklama]\r\n\r\nTelegram Sohbet Kimliği:\r\n/id" -"helpClientCommands" = "İstatistikleri aramak için şu komutu kullanın:\r\n\r\n/usage [E-posta]\r\n\r\nTelegram Sohbet Kimliği:\r\n/id" -"restartUsage" = "\r\n\r\n/restart force" -"restartSuccess" = "✅ İşlem başarılı!" -"restartFailed" = "❗ İşlem hatası.\r\n\r\nHata: {{ .Error }}." -"xrayNotRunning" = "❗ Xray Core çalışmıyor." - -[tgbot.messages] -"cpuThreshold" = "🔴 CPU Yükü {{ .Percent }}% eşiği {{ .Threshold }}%'yi aşıyor" -"selectUserFailed" = "❌ Kullanıcı seçiminde hata!" -"userSaved" = "✅ Telegram Kullanıcısı kaydedildi." -"loginSuccess" = "✅ Panele başarıyla giriş yapıldı.\r\n" -"loginFailed" = "❗️Panele giriş denemesi başarısız oldu.\r\n" -"report" = "🕰 Planlanmış Raporlar: {{ .RunTime }}\r\n" -"datetime" = "⏰ Tarih&Zaman: {{ .DateTime }}\r\n" -"hostname" = "💻 Sunucu: {{ .Hostname }}\r\n" -"version" = "🚀 3X-UI Sürümü: {{ .Version }}\r\n" -"xrayVersion" = "📡 Xray Sürümü: {{ .XrayVersion }}\r\n" -"ipv6" = "🌐 IPv6: {{ .IPv6 }}\r\n" -"ipv4" = "🌐 IPv4: {{ .IPv4 }}\r\n" -"ip" = "🌐 IP: {{ .IP }}\r\n" -"ips" = "🔢 IP'ler:\r\n{{ .IPs }}\r\n" -"serverUpTime" = "⏳ Çalışma Süresi: {{ .UpTime }} {{ .Unit }}\r\n" -"serverLoad" = "📈 Sistem Yükü: {{ .Load1 }}, {{ .Load2 }}, {{ .Load3 }}\r\n" -"serverMemory" = "📋 RAM: {{ .Current }}/{{ .Total }}\r\n" -"tcpCount" = "🔹 TCP: {{ .Count }}\r\n" -"udpCount" = "🔸 UDP: {{ .Count }}\r\n" -"traffic" = "🚦 Trafik: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n" -"xrayStatus" = "ℹ️ Durum: {{ .State }}\r\n" -"username" = "👤 Kullanıcı Adı: {{ .Username }}\r\n" -"password" = "👤 Şifre: {{ .Password }}\r\n" -"time" = "⏰ Zaman: {{ .Time }}\r\n" -"inbound" = "📍 Gelen: {{ .Remark }}\r\n" -"port" = "🔌 Port: {{ .Port }}\r\n" -"expire" = "📅 Son Kullanma Tarihi: {{ .Time }}\r\n" -"expireIn" = "📅 Sona Erecek: {{ .Time }}\r\n" -"active" = "💡 Aktif: {{ .Enable }}\r\n" -"enabled" = "🚨 Etkin: {{ .Enable }}\r\n" -"online" = "🌐 Bağlantı durumu: {{ .Status }}\r\n" -"email" = "📧 E-posta: {{ .Email }}\r\n" -"upload" = "🔼 Yükleme: ↑{{ .Upload }}\r\n" -"download" = "🔽 İndirme: ↓{{ .Download }}\r\n" -"total" = "📊 Toplam: ↑↓{{ .UpDown }} / {{ .Total }}\r\n" -"TGUser" = "👤 Telegram Kullanıcısı: {{ .TelegramID }}\r\n" -"exhaustedMsg" = "🚨 Tükenmiş {{ .Type }}:\r\n" -"exhaustedCount" = "🚨 Tükenmiş {{ .Type }} sayısı:\r\n" -"onlinesCount" = "🌐 Çevrimiçi Müşteriler: {{ .Count }}\r\n" -"disabled" = "🛑 Devre Dışı: {{ .Disabled }}\r\n" -"depleteSoon" = "🔜 Yakında Tükenecek: {{ .Deplete }}\r\n\r\n" -"backupTime" = "🗄 Yedekleme Zamanı: {{ .Time }}\r\n" -"refreshedOn" = "\r\n📋🔄 Yenilendi: {{ .Time }}\r\n\r\n" -"yes" = "✅ Evet" -"no" = "❌ Hayır" - -[tgbot.buttons] -"closeKeyboard" = "❌ Klavyeyi Kapat" -"cancel" = "❌ İptal" -"cancelReset" = "❌ Sıfırlamayı İptal Et" -"cancelIpLimit" = "❌ IP Limitini İptal Et" -"confirmResetTraffic" = "✅ Trafiği Sıfırlamayı Onayla?" -"confirmClearIps" = "✅ IP'leri Temizlemeyi Onayla?" -"confirmRemoveTGUser" = "✅ Telegram Kullanıcısını Kaldırmayı Onayla?" -"confirmToggle" = "✅ Kullanıcıyı Etkinleştirme/Devre Dışı Bırakmayı Onayla?" -"dbBackup" = "Veritabanı Yedeği Al" -"serverUsage" = "Sunucu Kullanımı" -"getInbounds" = "Gelenleri Al" -"depleteSoon" = "Yakında Tükenecek" -"clientUsage" = "Kullanımı Al" -"onlines" = "Çevrimiçi Müşteriler" -"commands" = "Komutlar" -"refresh" = "🔄 Yenile" -"clearIPs" = "❌ IP'leri Temizle" -"removeTGUser" = "❌ Telegram Kullanıcısını Kaldır" -"selectTGUser" = "👤 Telegram Kullanıcısını Seç" -"selectOneTGUser" = "👤 Bir Telegram Kullanıcısını Seçin:" -"resetTraffic" = "📈 Trafiği Sıfırla" -"resetExpire" = "📅 Son Kullanma Tarihini Değiştir" -"ipLog" = "🔢 IP Günlüğü" -"ipLimit" = "🔢 IP Limiti" -"setTGUser" = "👤 Telegram Kullanıcısını Ayarla" -"toggle" = "🔘 Etkinleştir / Devre Dışı Bırak" -"custom" = "🔢 Özel" -"confirmNumber" = "✅ Onayla: {{ .Num }}" -"confirmNumberAdd" = "✅ Ekleme onayı: {{ .Num }}" -"limitTraffic" = "🚧 Trafik Sınırı" -"getBanLogs" = "Yasak Günlüklerini Al" -"allClients" = "Tüm Müşteriler" - -[tgbot.answers] -"successfulOperation" = "✅ İşlem başarılı!" -"errorOperation" = "❗ İşlemde hata." -"getInboundsFailed" = "❌ Gelenler alınamadı." -"getClientsFailed" = "❌ Müşteriler alınamadı." -"canceled" = "❌ {{ .Email }}: İşlem iptal edildi." -"clientRefreshSuccess" = "✅ {{ .Email }}: Müşteri başarıyla yenilendi." -"IpRefreshSuccess" = "✅ {{ .Email }}: IP'ler başarıyla yenilendi." -"TGIdRefreshSuccess" = "✅ {{ .Email }}: Müşterinin Telegram Kullanıcısı başarıyla yenilendi." -"resetTrafficSuccess" = "✅ {{ .Email }}: Trafik başarıyla sıfırlandı." -"setTrafficLimitSuccess" = "✅ {{ .Email }}: Trafik limiti başarıyla kaydedildi." -"expireResetSuccess" = "✅ {{ .Email }}: Son kullanma günleri başarıyla sıfırlandı." -"resetIpSuccess" = "✅ {{ .Email }}: IP limiti {{ .Count }} başarıyla kaydedildi." -"clearIpSuccess" = "✅ {{ .Email }}: IP'ler başarıyla temizlendi." -"getIpLog" = "✅ {{ .Email }}: IP Günlüğü alındı." -"getUserInfo" = "✅ {{ .Email }}: Telegram Kullanıcı Bilgisi alındı." -"removedTGUserSuccess" = "✅ {{ .Email }}: Telegram Kullanıcısı başarıyla kaldırıldı." -"enableSuccess" = "✅ {{ .Email }}: Başarıyla etkinleştirildi." -"disableSuccess" = "✅ {{ .Email }}: Başarıyla devre dışı bırakıldı." -"askToAddUserId" = "Yapılandırmanız bulunamadı!\r\nLütfen yöneticinizden yapılandırmalarınıza Telegram ChatID'nizi eklemesini isteyin.\r\n\r\nKullanıcı ChatID'niz: {{ .TgUserID }}" -"chooseClient" = "Gelen {{ .Inbound }} için bir Müşteri Seçin" -"chooseInbound" = "Bir Gelen Seçin" diff --git a/web/translation/translate.uk_UA.toml b/web/translation/translate.uk_UA.toml deleted file mode 100644 index 1619dda1..00000000 --- a/web/translation/translate.uk_UA.toml +++ /dev/null @@ -1,598 +0,0 @@ -"username" = "Ім'я користувача" -"password" = "Пароль" -"login" = "Увійти" -"confirm" = "Підтвердити" -"cancel" = "Скасувати" -"close" = "Закрити" -"copy" = "Копіювати" -"copied" = "Скопійовано" -"download" = "Завантажити" -"remark" = "Примітка" -"enable" = "Увімкнути" -"protocol" = "Протокол" -"search" = "Пошук" -"filter" = "Фільтр" -"loading" = "Завантаження..." -"second" = "Секунда" -"minute" = "Хвилина" -"hour" = "Година" -"day" = "День" -"check" = "Перевірка" -"indefinite" = "Безстроково" -"unlimited" = "Безлімітний" -"none" = "Немає" -"qrCode" = "QR-Код" -"info" = "Більше інформації" -"edit" = "Редагувати" -"delete" = "Видалити" -"reset" = "Скидання" -"copySuccess" = "Скопійовано успішно" -"sure" = "Звичайно" -"encryption" = "Шифрування" -"transmission" = "Протокол передачи" -"host" = "Хост" -"path" = "Шлях" -"camouflage" = "Маскування" -"status" = "Статус" -"enabled" = "Увімкнено" -"disabled" = "Вимкнено" -"depleted" = "Вичерпано" -"depletingSoon" = "Вичерпується" -"offline" = "Офлайн" -"online" = "Онлайн" -"domainName" = "Доменне ім`я" -"monitor" = "Слухати IP" -"certificate" = "Цифровий сертифікат" -"fail" = "Помилка" -"comment" = "Коментар" -"success" = "Успішно" -"getVersion" = "Отримати версію" -"install" = "Встановити" -"clients" = "Клієнти" -"usage" = "Використання" -"secretToken" = "Секретний маркер" -"remained" = "Залишилося" -"security" = "Беспека" -"secAlertTitle" = "Попередження системи безпеки" -"secAlertSsl" = "Це з'єднання не є безпечним. Будь ласка, уникайте введення конфіденційної інформації, поки TLS не буде активовано для захисту даних." -"secAlertConf" = "Деякі налаштування вразливі до атак. Рекомендується посилити протоколи безпеки, щоб запобігти можливим порушенням." -"secAlertSSL" = "Панель не має безпечного з'єднання. Будь ласка, встановіть сертифікат TLS для захисту даних." -"secAlertPanelPort" = "Стандартний порт панелі вразливий. Будь ласка, сконфігуруйте випадковий або конкретний порт." -"secAlertPanelURI" = "Стандартний URI-шлях панелі небезпечний. Будь ласка, сконфігуруйте складний URI-шлях." -"secAlertSubURI" = "Стандартний URI-шлях підписки небезпечний. Будь ласка, сконфігуруйте складний URI-шлях." -"secAlertSubJsonURI" = "Стандартний URI-шлях JSON підписки небезпечний. Будь ласка, сконфігуруйте складний URI-шлях." - -[menu] -"dashboard" = "Огляд" -"inbounds" = "Вхідні" -"settings" = "Параметри панелі" -"xray" = "Конфігурації Xray" -"logout" = "Вийти" -"link" = "Керувати" - -[pages.login] -"hello" = "Привіт" -"title" = "Ласкаво просимо" -"loginAgain" = "Ваш сеанс закінчився, увійдіть знову" - -[pages.login.toasts] -"invalidFormData" = "Формат вхідних даних недійсний." -"emptyUsername" = "Потрібне ім'я користувача" -"emptyPassword" = "Потрібен пароль" -"wrongUsernameOrPassword" = "Невірне ім'я користувача або пароль." -"successLogin" = "Вхід" - -[pages.index] -"title" = "Огляд" -"memory" = "Пам'ять" -"hard" = "Диск" -"xrayStatus" = "Xray" -"stopXray" = "Зупинити" -"restartXray" = "Перезапустити" -"xraySwitch" = "Версія" -"xraySwitchClick" = "Виберіть версію, на яку ви хочете перейти." -"xraySwitchClickDesk" = "Вибирайте уважно, оскільки старіші версії можуть бути несумісними з поточними конфігураціями." -"operationHours" = "Час роботи" -"systemLoad" = "Завантаження системи" -"systemLoadDesc" = "Середнє завантаження системи за останні 1, 5 і 15 хвилин" -"connectionTcpCountDesc" = "Загальна кількість TCP-з'єднань у системі" -"connectionUdpCountDesc" = "Загальна кількість UDP-з'єднань у системі" -"connectionCount" = "Статистика з'єднання" -"upSpeed" = "Загальна швидкість завантаження в системі" -"downSpeed" = "Загальна швидкість завантаження в системі" -"totalSent" = "Загальна кількість даних, надісланих через систему з моменту запуску ОС" -"totalReceive" = "Загальна кількість даних, отриманих системою з моменту запуску ОС" -"xraySwitchVersionDialog" = "Змінити версію Xray" -"xraySwitchVersionDialogDesc" = "Ви впевнені, що бажаєте змінити версію Xray на" -"dontRefresh" = "Інсталяція триває, будь ласка, не оновлюйте цю сторінку" -"logs" = "Журнали" -"config" = "Конфігурація" -"backup" = "Резервне копіювання та відновлення" -"backupTitle" = "Резервне копіювання та відновлення бази даних" -"backupDescription" = "Рекомендується зробити резервну копію перед відновленням бази даних." -"exportDatabase" = "Резервне копіювання" -"importDatabase" = "Відновити" - -[pages.inbounds] -"title" = "Вхідні" -"totalDownUp" = "Всього надісланих/отриманих" -"totalUsage" = "Всього використанно" -"inboundCount" = "Загальна кількість вхідних" -"operate" = "Меню" -"enable" = "Увімкнено" -"remark" = "Примітка" -"protocol" = "Протокол" -"port" = "Порт" -"traffic" = "Трафік" -"details" = "Деталі" -"transportConfig" = "Транспорт" -"expireDate" = "Тривалість" -"resetTraffic" = "Скинути трафік" -"addInbound" = "Додати вхідний" -"generalActions" = "Загальні дії" -"create" = "Створити" -"update" = "Оновити" -"modifyInbound" = "Змінити вхідний" -"deleteInbound" = "Видалити вхідні" -"deleteInboundContent" = "Ви впевнені, що хочете видалити вхідні?" -"deleteClient" = "Видалити клієнта" -"deleteClientContent" = "Ви впевнені, що хочете видалити клієнт?" -"resetTrafficContent" = "Ви впевнені, що хочете скинути трафік?" -"copyLink" = "Копіювати URL" -"address" = "Адреса" -"network" = "Мережа" -"destinationPort" = "Порт призначення" -"targetAddress" = "Цільова адреса" -"monitorDesc" = "Залиште порожнім, щоб слухати всі IP-адреси" -"meansNoLimit" = "= Необмежено. (одиниця: ГБ)" -"totalFlow" = "Загальна витрата" -"leaveBlankToNeverExpire" = "Залиште порожнім, щоб ніколи не закінчувався" -"noRecommendKeepDefault" = "Рекомендується зберегти значення за замовчуванням" -"certificatePath" = "Шлях до файлу" -"certificateContent" = "Вміст файлу" -"publicKey" = "Публічний ключ" -"privatekey" = "Закритий ключ" -"clickOnQRcode" = "Натисніть QR-код, щоб скопіювати" -"client" = "Клієнт" -"export" = "Експортувати всі URL-адреси" -"clone" = "Клон" -"cloneInbound" = "Клонувати" -"cloneInboundContent" = "Усі налаштування цього вхідного потоку, крім порту, IP-адреси прослуховування та клієнтів, будуть застосовані до клону." -"cloneInboundOk" = "Клонувати" -"resetAllTraffic" = "Скинути весь вхідний трафік" -"resetAllTrafficTitle" = "Скинути весь вхідний трафік" -"resetAllTrafficContent" = "Ви впевнені, що бажаєте скинути трафік усіх вхідних?" -"resetInboundClientTraffics" = "Скинути трафік клієнтів" -"resetInboundClientTrafficTitle" = "Скинути трафік клієнтів" -"resetInboundClientTrafficContent" = "Ви впевнені, що бажаєте скинути трафік клієнтів цього вхідного потоку?" -"resetAllClientTraffics" = "Скинути весь трафік клієнтів" -"resetAllClientTrafficTitle" = "Скинути весь трафік клієнтів" -"resetAllClientTrafficContent" = "Ви впевнені, що бажаєте скинути трафік усіх клієнтів?" -"delDepletedClients" = "Видалити вичерпані клієнти" -"delDepletedClientsTitle" = "Видалити вичерпані клієнти" -"delDepletedClientsContent" = "Ви впевнені, що хочете видалити всі вичерпані клієнти?" -"email" = "Електронна пошта" -"emailDesc" = "Будь ласка, надайте унікальну адресу електронної пошти." -"IPLimit" = "Обмеження IP" -"IPLimitDesc" = "Вимикає вхідний, якщо кількість перевищує встановлене значення. (0 = вимкнено)" -"IPLimitlog" = "Журнал IP" -"IPLimitlogDesc" = "Журнал історії IP-адрес. (щоб увімкнути вхідну після вимкнення, очистіть журнал)" -"IPLimitlogclear" = "Очистити журнал" -"setDefaultCert" = "Установити сертифікат з панелі" -"telegramDesc" = "Будь ласка, вкажіть ID чату Telegram. (використовуйте команду '/id' у боті) або (@userinfobot)" -"subscriptionDesc" = "Щоб знайти URL-адресу вашої підписки, перейдіть до «Деталі». Крім того, ви можете використовувати одне ім'я для кількох клієнтів." -"info" = "Інформація" -"same" = "Те саме" -"inboundData" = "Вхідні дані" -"exportInbound" = "Експортувати вхідні" -"import" = "Імпорт" -"importInbound" = "Імпортувати вхідний" - -[pages.client] -"add" = "Додати клієнта" -"edit" = "Редагувати клієнта" -"submitAdd" = "Додати клієнта" -"submitEdit" = "Зберегти зміни" -"clientCount" = "Кількість клієнтів" -"bulk" = "Додати групу" -"method" = "Метод" -"first" = "Перший" -"last" = "Останній" -"prefix" = "Префікс" -"postfix" = "Постфікс" -"delayedStart" = "Початок використання" -"expireDays" = "Тривалість" -"days" = "Дні(в)" -"renew" = "Автоматичне оновлення" -"renewDesc" = "Автоматичне поновлення після закінчення терміну дії. (0 = вимкнено)(одиниця: день)" - -[pages.inbounds.toasts] -"obtain" = "Отримати" - -[pages.inbounds.stream.general] -"request" = "Запит" -"response" = "Відповідь" -"name" = "Ім'я" -"value" = "Значення" - -[pages.inbounds.stream.tcp] -"version" = "Версія" -"method" = "Метод" -"path" = "Шлях" -"status" = "Статус" -"statusDescription" = "Опис стану" -"requestHeader" = "Заголовок запиту" -"responseHeader" = "Заголовок відповіді" - -[pages.settings] -"title" = "Параметри панелі" -"save" = "Зберегти" -"infoDesc" = "Кожна внесена тут зміна повинна бути збережена. Перезапустіть панель, щоб застосувати зміни." -"restartPanel" = "Перезапустити панель" -"restartPanelDesc" = "Ви впевнені, що бажаєте перезапустити панель? Якщо ви не можете отримати доступ до панелі після перезапуску, будь ласка, перегляньте інформацію журналу панелі на сервері." -"actions" = "Дії" -"resetDefaultConfig" = "Відновити значення за замовчуванням" -"panelSettings" = "Загальні" -"securitySettings" = "Автентифікація" -"TGBotSettings" = "Telegram Бот" -"panelListeningIP" = "Слухати IP" -"panelListeningIPDesc" = "IP-адреса для веб-панелі. (залиште порожнім, щоб слухати всі IP-адреси)" -"panelListeningDomain" = "Домен прослуховування" -"panelListeningDomainDesc" = "Доменне ім'я для веб-панелі. (залиште порожнім, щоб слухати всі домени та IP-адреси)" -"panelPort" = "Порт прослуховування" -"panelPortDesc" = "Номер порту для веб-панелі. (має бути невикористаний порт)" -"publicKeyPath" = "Шлях відкритого ключа" -"publicKeyPathDesc" = "Шлях до файлу відкритого ключа для веб-панелі. (починається з ‘/‘)" -"privateKeyPath" = "Шлях приватного ключа" -"privateKeyPathDesc" = "Шлях до файлу приватного ключа для веб-панелі. (починається з ‘/‘)" -"panelUrlPath" = "Шлях URL" -"panelUrlPathDesc" = "Шлях URL для веб-панелі. (починається з ‘/‘ і закінчується ‘/‘)" -"pageSize" = "Розмір сторінки" -"pageSizeDesc" = "Визначити розмір сторінки для вхідної таблиці. (0 = вимкнено)" -"remarkModel" = "Модель зауваження та роздільний символ" -"datepicker" = "Тип календаря" -"datepickerPlaceholder" = "Виберіть дату" -"datepickerDescription" = "Заплановані завдання виконуватимуться на основі цього календаря." -"sampleRemark" = "Зразок зауваження" -"oldUsername" = "Поточне ім'я користувача" -"currentPassword" = "Поточний пароль" -"newUsername" = "Нове ім'я користувача" -"newPassword" = "Новий пароль" -"telegramBotEnable" = "Увімкнути Telegram Bot" -"telegramBotEnableDesc" = "Вмикає бота Telegram." -"telegramToken" = "Telegram Токен" -"telegramTokenDesc" = "Токен бота Telegram, отриманий від '@BotFather'." -"telegramProxy" = "SOCKS Проксі" -"telegramProxyDesc" = "Вмикає проксі-сервер SOCKS5 для підключення до Telegram. (відкоригуйте параметри відповідно до посібника)" -"telegramAPIServer" = "Сервер Telegram API" -"telegramAPIServerDesc" = "Сервер Telegram API для використання. Залиште поле порожнім, щоб використовувати сервер за умовчанням." -"telegramChatId" = "Ідентифікатор чату адміністратора" -"telegramChatIdDesc" = "Ідентифікатори чату адміністратора Telegram. (розділені комами) (отримайте тут @userinfobot) або (використовуйте команду '/id' у боті)" -"telegramNotifyTime" = "Час сповіщення" -"telegramNotifyTimeDesc" = "Час повідомлення бота Telegram, встановлений для періодичних звітів. (використовуйте формат часу crontab)" -"tgNotifyBackup" = "Резервне копіювання бази даних" -"tgNotifyBackupDesc" = "Надіслати файл резервної копії бази даних зі звітом." -"tgNotifyLogin" = "Сповіщення про вхід" -"tgNotifyLoginDesc" = "Отримувати сповіщення про ім'я користувача, IP-адресу та час щоразу, коли хтось намагається увійти у вашу веб-панель." -"sessionMaxAge" = "Тривалість сеансу" -"sessionMaxAgeDesc" = "Тривалість, протягом якої ви можете залишатися в системі. (одиниця: хвилина)" -"expireTimeDiff" = "Повідомлення про дату закінчення" -"expireTimeDiffDesc" = "Отримувати сповіщення про термін дії при досягненні цього порогу. (одиниця: день)" -"trafficDiff" = "Повідомлення про обмеження трафіку" -"trafficDiffDesc" = "Отримувати сповіщення про обмеження трафіку при досягненні цього порогу. (одиниця: ГБ)" -"tgNotifyCpu" = "Сповіщення про завантаження ЦП" -"tgNotifyCpuDesc" = "Отримувати сповіщення, якщо навантаження ЦП перевищує це порогове значення. (одиниця: %)" -"timeZone" = "Часовий пояс" -"timeZoneDesc" = "Заплановані завдання виконуватимуться на основі цього часового поясу." -"subSettings" = "Підписка" -"subEnable" = "Увімкнути службу підписки" -"subEnableDesc" = "Вмикає службу підписки." -"subListen" = "Слухати IP" -"subListenDesc" = "IP-адреса для служби підписки. (залиште порожнім, щоб слухати всі IP-адреси)" -"subPort" = "Слухати порт" -"subPortDesc" = "Номер порту для служби підписки. (має бути невикористаний порт)" -"subCertPath" = "Шлях відкритого ключа" -"subCertPathDesc" = "Шлях до файлу відкритого ключа для служби підписки. (починається з ‘/‘)" -"subKeyPath" = "Шлях приватного ключа" -"subKeyPathDesc" = "Шлях до файлу приватного ключа для служби підписки. (починається з ‘/‘)" -"subPath" = "Шлях URI" -"subPathDesc" = "Шлях URI для служби підписки. (починається з ‘/‘ і закінчується ‘/‘)" -"subDomain" = "Домен прослуховування" -"subDomainDesc" = "Ім'я домену для служби підписки. (залиште порожнім, щоб слухати всі домени та IP-адреси)" -"subUpdates" = "Інтервали оновлення" -"subUpdatesDesc" = "Інтервали оновлення URL-адреси підписки в клієнтських програмах. (одиниця: година)" -"subEncrypt" = "Закодувати" -"subEncryptDesc" = "Повернений вміст послуги підписки матиме кодування Base64." -"subShowInfo" = "Показати інформацію про використання" -"subShowInfoDesc" = "Залишок трафіку та дата відображатимуться в клієнтських програмах." -"subURI" = "URI зворотного проксі" -"subURIDesc" = "URI до URL-адреси підписки для використання за проксі." -"fragment" = "Фрагментація" -"fragmentDesc" = "Увімкнути фрагментацію для пакету привітання TLS" -"fragmentSett" = "Параметри фрагментації" -"noisesDesc" = "Увімкнути Noises." -"noisesSett" = "Налаштування Noises" -"mux" = "Mux" -"muxDesc" = "Передавати кілька незалежних потоків даних у межах встановленого потоку даних." -"muxSett" = "Налаштування Mux" -"direct" = "Пряме підключення" -"directDesc" = "Безпосередньо встановлює з’єднання з доменами або діапазонами IP певної країни." - - -[pages.xray] -"title" = "Xray конфігурації" -"save" = "Зберегти" -"restart" = "Перезапустити Xray" -"basicTemplate" = "Базовий шаблон" -"advancedTemplate" = "Додатково" -"generalConfigs" = "Загальні конфігурації" -"generalConfigsDesc" = "Ці параметри визначатимуть загальні налаштування." -"logConfigs" = "Журнал" -"logConfigsDesc" = "Журнали можуть вплинути на ефективність вашого сервера. Рекомендується вмикати його з розумом лише у випадку ваших потреб" -"blockConfigs" = "Захисний екран" -"blockConfigsDesc" = "Ці параметри блокуватимуть трафік на основі конкретних запитуваних протоколів і веб-сайтів." -"basicRouting" = "Основна Маршрутизація" -"blockConnectionsConfigsDesc" = "Ці параметри блокуватимуть трафік на основі запитаних країн." -"directConnectionsConfigsDesc" = "Пряме з'єднання гарантує, що певний трафік не буде маршрутизовано через інший сервер." -"blockips" = "Блокувати IP" -"blockdomains" = "Блокувати домени" -"directips" = "Прямі IP" -"directdomains" = "Прямі домени" -"ipv4Routing" = "Маршрутизація IPv4" -"ipv4RoutingDesc" = "Ці параметри спрямовуватимуть трафік на основі певного призначення через IPv4." -"warpRouting" = "WARP Маршрутизація" -"warpRoutingDesc" = "Ці параметри маршрутизуватимуть трафік на основі певного пункту призначення через WARP." -"Template" = "Шаблон розширеної конфігурації Xray" -"TemplateDesc" = "Остаточний конфігураційний файл Xray буде створено на основі цього шаблону." -"FreedomStrategy" = "Стратегія протоколу свободи" -"FreedomStrategyDesc" = "Установити стратегію виведення для мережі в протоколі свободи." -"RoutingStrategy" = "Загальна стратегія маршрутизації" -"RoutingStrategyDesc" = "Установити загальну стратегію маршрутизації трафіку для вирішення всіх запитів." -"Torrent" = "Блокувати протокол BitTorrent" -"TorrentDesc" = "Блокує протокол BitTorrent." -"Family" = "Захист сім'ї" -"FamilyDesc" = "Блокує вміст для дорослих і веб-сайти з шкідливими програмами." -"Inbounds" = "Вхідні" -"InboundsDesc" = "Прийняття певних клієнтів." -"Outbounds" = "Вихід" -"Balancers" = "Балансери" -"OutboundsDesc" = "Встановити шлях вихідного трафіку." -"Routings" = "Правила маршрутизації" -"RoutingsDesc" = "Пріоритет кожного правила важливий!" -"completeTemplate" = "Усі" -"logLevel" = "Рівень журналу" -"logLevelDesc" = "Рівень журналу для журналів помилок із зазначенням інформації, яку потрібно записати." -"accessLog" = "Журнал доступу" -"accessLogDesc" = "Шлях до файлу журналу доступу. Спеціальне значення 'none' вимикає журнали доступу" -"errorLog" = "Журнал помилок" -"errorLogDesc" = "Шлях до файлу журналу помилок. Спеціальне значення 'none' вимикає журнали помилок" -"dnsLog" = "Журнал DNS" -"dnsLogDesc" = "Чи включити журнали запитів DNS" -"outboundTraffic" = "Вихідний трафік" -"outboundTrafficDesc" = "Чи потрібно увімкнути вихідний трафік" -"maskAddress" = "Маскувати Адресу" -"maskAddressDesc" = "Маска IP-адреси, при активації автоматично замінює IP-адресу, яка з'являється у журналі." - -[pages.xray.rules] -"first" = "Перший" -"last" = "Останній" -"up" = "Вгору" -"down" = "Вниз" -"source" = "Джерело" -"dest" = "Пункт призначення" -"inbound" = "Вхідний" -"outbound" = "Вихідний" -"balancer" = "Балансувальник" -"info" = "Інформація" -"add" = "Додати правило" -"edit" = "Редагувати правило" -"useComma" = "Елементи, розділені комами" - -[pages.xray.outbound] -"addOutbound" = "Додати вихідний" -"addReverse" = "Додати реверс" -"editOutbound" = "Редагувати вихідні" -"editReverse" = "Редагувати реверс" -"tag" = "Тег" -"tagDesc" = "Унікальний тег" -"address" = "Адреса" -"reverse" = "Зворотний" -"domain" = "Домен" -"type" = "Тип" -"bridge" = "Міст" -"portal" = "Портал" -"intercon" = "Взаємозв'язок" -"settings" = "Налаштування" -"accountInfo" = "Інформація про обліковий запис" -"outboundStatus" = "Статус виходу" -"sendThrough" = "Надіслати через" - -[pages.xray.balancer] -"addBalancer" = "Додати балансир" -"editBalancer" = "Редагувати балансир" -"balancerStrategy" = "Стратегія" -"balancerSelectors" = "Селектори" -"tag" = "Тег" -"tagDesc" = "Унікальний тег" -"balancerDesc" = "Неможливо використовувати balancerTag і outboundTag одночасно. Якщо використовувати одночасно, працюватиме лише outboundTag." - -[pages.xray.wireguard] -"secretKey" = "Приватний ключ" -"publicKey" = "Публічний ключ" -"allowedIPs" = "Дозволені IP-адреси" -"endpoint" = "Кінцева точка" -"psk" = "Спільний ключ" -"domainStrategy" = "Стратегія домену" - -[pages.xray.dns] -"enable" = "Увімкнути DNS" -"enableDesc" = "Увімкнути вбудований DNS-сервер" -"tag" = "Мітка вхідного DNS" -"tagDesc" = "Ця мітка буде доступна як вхідна мітка в правилах маршрутизації." -"strategy" = "Стратегія запиту" -"strategyDesc" = "Загальна стратегія вирішення доменних імен" -"add" = "Додати сервер" -"edit" = "Редагувати сервер" -"domains" = "Домени" -"expectIPs" = "Очікувані IP" - -[pages.xray.fakedns] -"add" = "Додати підроблений DNS" -"edit" = "Редагувати підроблений DNS" -"ipPool" = "Підмережа IP-пулу" -"poolSize" = "Розмір пулу" - -[pages.settings.security] -"admin" = "Адміністратор" -"secret" = "Секретний маркер" -"loginSecurity" = "Безпечний вхід" -"loginSecurityDesc" = "Додає додатковий рівень автентифікації для забезпечення більшої безпеки." -"secretToken" = "Секретний маркер" -"secretTokenDesc" = "Будь ласка, надійно зберігайте цей маркер у безпечному місці. Цей маркер потрібен для входу, і його неможливо відновити." - -[pages.settings.toasts] -"modifySettings" = "Змінити налаштування" -"getSettings" = "Отримати налаштування" -"modifyUser" = "Змінити адміністратора" -"originalUserPassIncorrect" = "Поточне ім'я користувача або пароль недійсні" -"userPassMustBeNotEmpty" = "Нове ім'я користувача та пароль порожні" - -[tgbot] -"keyboardClosed" = "❌ Спеціальна клавіатура закрита!" -"noResult" = "❗ Немає результату!" -"noQuery" = "❌ Запит не знайдено! Скористайтеся командою ще раз!" -"wentWrong" = "❌ Щось пішло не так!" -"noIpRecord" = "❗ Немає IP-запису!" -"noInbounds" = "❗ Вхідних не знайдено!" -"unlimited" = "♾ Необмежений (скинути)" -"add" = "Додати" -"month" = "Місяць" -"months" = "Місяці" -"day" = "День" -"days" = "Дні" -"hours" = "Годинник" -"unknown" = "Невідомо" -"inbounds" = "Вхідні" -"clients" = "Клієнти" -"offline" = "🔴 Офлайн" -"online" = "🟢 Онлайн" - -[tgbot.commands] -"unknown" = "❗ Невідома команда." -"pleaseChoose" = "👇 Будь ласка, виберіть:\r\n" -"help" = "🤖 Ласкаво просимо до цього бота! Він розроблений, щоб надавати певні дані з веб-панелі та дозволяє вносити зміни за потреби.\r\n\r\n" -"start" = "👋 Привіт {{ .Firstname }}.\r\n" -"welcome" = "🤖 Ласкаво просимо до {{ .Hostname }} бота керування.\r\n" -"status" = "✅ Бот в порядку!" -"usage" = "❗ Введіть текст для пошуку!" -"getID" = "🆔 Ваш ідентифікатор: {{ .ID }}" -"helpAdminCommands" = "Для перезапуску Xray Core:\r\n/restart force\r\n\r\nДля пошуку електронної пошти клієнта:\r\n/usage [Електронна пошта]\r\n\r\nДля пошуку вхідних (зі статистикою клієнта):\r\n/inbound [Примітка]\r\n\r\nID чату Telegram:\r\n/id" -"helpClientCommands" = "Для пошуку статистики використовуйте наступну команду:\r\n/usage [Електронна пошта]\r\n\r\nID чату Telegram:\r\n/id" -"restartUsage" = "\r\n\r\n/restart force" -"restartSuccess" = "✅ Операція успішна!" -"restartFailed" = "❗ Помилка в операції.\r\n\r\nПомилка: {{ .Error }}." -"xrayNotRunning" = "❗ Xray Core не запущений." - -[tgbot.messages] -"cpuThreshold" = "🔴 Навантаження ЦП {{ .Percent }}% перевищує порогове значення {{ .Threshold }}%" -"selectUserFailed" = "❌ Помилка під час вибору користувача!" -"userSaved" = "✅ Користувача Telegram збережено." -"loginSuccess" = "✅ Успішно ввійшли в панель\r\n" -"loginFailed" = "❗️ Помилка входу в панель.\r\n" -"report" = "🕰 Заплановані звіти: {{ .RunTime }}\r\n" -"datetime" = "⏰ Дата й час: {{ .DateTime }}\r\n" -"hostname" = "💻 Хост: {{ .Hostname }}\r\n" -"version" = "🚀 3X-UI Версія: {{ .Version }}\r\n" -"xrayVersion" = "📡 Xray Версія: {{ .XrayVersion }}\r\n" -"ipv6" = "🌐 IPv6: {{ .IPv6 }}\r\n" -"ipv4" = "🌐 IPv4: {{ .IPv4 }}\r\n" -"ip" = "🌐 IP: {{ .IP }}\r\n" -"ips" = "🔢 IP-адреси:\r\n{{ .IPs }}\r\n" -"serverUpTime" = "⏳ Час роботи: {{ .UpTime }} {{ .Unit }}\r\n" -"serverLoad" = "📈 Завантаження системи: {{ .Load1 }}, {{ .Load2 }}, {{ .Load3 }}\r\n" -"serverMemory" = "📋 RAM: {{ .Current }}/{{ .Total }}\r\n" -"tcpCount" = "🔹 TCP: {{ .Count }}\r\n" -"udpCount" = "🔸 UDP: {{ .Count }}\r\n" -"traffic" = "🚦 Трафік: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n" -"xrayStatus" = "ℹ️ Статус: {{ .State }}\r\n" -"username" = "👤 Ім'я користувача: {{ .Username }}\r\n" -"password" = "👤 Пароль: {{ .Password }}\r\n" -"time" = "⏰ Час: {{ .Time }}\r\n" -"inbound" = "📍 Inbound: {{ .Remark }}\r\n" -"port" = "🔌 Порт: {{ .Port }}\r\n" -"expire" = "📅 Дата закінчення: {{ .Time }}\r\n" -"expireIn" = "📅 Термін дії: {{ .Time }}\r\n" -"active" = "💡 Активний: {{ .Enable }}\r\n" -"enabled" = "🚨 Увімкнено: {{ .Enable }}\r\n" -"online" = "🌐 Стан підключення: {{ .Status }}\r\n" -"email" = "📧 Електронна пошта: {{ .Email }}\r\n" -"upload" = "🔼 Upload: ↑{{ .Upload }}\r\n" -"download" = "🔽 Download: ↓{{ .Download }}\r\n" -"total" = "📊 Всього: ↑↓{{ .UpDown }} / {{ .Total }}\r\n" -"TGUser" = "👤 Користувач Telegram: {{ .TelegramID }}\r\n" -"exhaustedMsg" = "🚨 Вичерпано {{ .Type }}:\r\n" -"exhaustedCount" = "🚨 Вичерпано кількість {{ .Type }} count:\r\n" -"onlinesCount" = "🌐 Онлайн-клієнти: {{ .Count }}\r\n" -"disabled" = "🛑 Вимкнено: {{ .Disabled }}\r\n" -"depleteSoon" = "🔜 Скоро вичерпається: {{ .Deplete }}\r\n\r\n" -"backupTime" = "🗄 Час резервного копіювання: {{ .Time }}\r\n" -"refreshedOn" = "\r\n📋🔄 Оновлено: {{ .Time }}\r\n\r\n" -"yes" = "✅ Так" -"no" = "❌ Ні" - -[tgbot.buttons] -"closeKeyboard" = "❌ Закрити клавіатуру" -"cancel" = "❌ Скасувати" -"cancelReset" = "❌ Скасувати скидання" -"cancelIpLimit" = "❌ Скасувати обмеження IP" -"confirmResetTraffic" = "✅ Підтвердити скидання трафіку?" -"confirmClearIps" = "✅ Підтвердити очищення IP-адрес?" -"confirmRemoveTGUser" = "✅ Підтвердити видалення користувача Telegram?" -"confirmToggle" = "✅ Підтвердити ввімкнути/вимкнути користувача?" -"dbBackup" = "Отримати резервну копію БД" -"serverUsage" = "Використання сервера" -"getInbounds" = "Отримати вхідні" -"depleteSoon" = "Скоро вичерпати" -"clientUsage" = "Отримати використання" -"onlines" = "Онлайн-клієнти" -"commands" = "Команди" -"refresh" = "🔄 Оновити" -"clearIPs" = "❌ Очистити IP-адреси" -"removeTGUser" = "❌ Видалити користувача Telegram" -"selectTGUser" = "👤 Виберіть користувача Telegram" -"selectOneTGUser" = "👤 Виберіть користувача Telegram:" -"resetTraffic" = "📈 Скинути трафік" -"resetExpire" = "📅 Змінити термін дії" -"ipLog" = "🔢 IP журнал" -"ipLimit" = "🔢 IP Ліміт" -"setTGUser" = "👤 Встановити користувача Telegram" -"toggle" = "🔘 Увімкнути / Вимкнути" -"custom" = "🔢 Custom" -"confirmNumber" = "✅ Підтвердити: {{ .Num }}" -"confirmNumberAdd" = "✅ Підтвердити додавання: {{ .Num }}" -"limitTraffic" = "🚧 Ліміт трафіку" -"getBanLogs" = "Отримати журнали заборон" -"allClients" = "Всі Клієнти" - -[tgbot.answers] -"successfulOperation" = "✅ Операція успішна!" -"errorOperation" = "❗ Помилка в роботі." -"getInboundsFailed" = "❌ Не вдалося отримати вхідні повідомлення." -"getClientsFailed" = "❌ Не вдалося отримати клієнтів." -"canceled" = "❌ {{ .Email }}: Операцію скасовано." -"clientRefreshSuccess" = "✅ {{ .Email }}: Клієнт успішно оновлено." -"IpRefreshSuccess" = "✅ {{ .Email }}: IP-адреси успішно оновлено." -"TGIdRefreshSuccess" = "✅ {{ .Email }}: Користувач Telegram клієнта успішно оновлено." -"resetTrafficSuccess" = "✅ {{ .Email }}: Трафік скинуто успішно." -"setTrafficLimitSuccess" = "✅ {{ .Email }}: Ліміт трафіку успішно збережено." -"expireResetSuccess" = "✅ {{ .Email }}: Успішно скинуто дні закінчення терміну дії." -"resetIpSuccess" = "✅ {{ .Email }}: IP обмеження {{ .Count }} успішно збережено." -"clearIpSuccess" = "✅ {{ .Email }}: IP успішно очищено." -"getIpLog" = "✅ {{ .Email }}: Отримати IP-журнал." -"getUserInfo" = "✅ {{ .Email }}: Отримати інформацію про користувача Telegram." -"removedTGUserSuccess" = "✅ {{ .Email }}: Користувача Telegram видалено успішно." -"enableSuccess" = "✅ {{ .Email }}: Увімкнути успішно." -"disableSuccess" = "✅ {{ .Email }}: Успішно вимкнено." -"askToAddUserId" = "Вашу конфігурацію не знайдено!\r\nБудь ласка, попросіть свого адміністратора використовувати ваш ідентифікатор Telegram у вашій конфігурації.\r\n\r\nВаш ідентифікатор користувача: {{ .TgUserID }}" -"chooseClient" = "Виберіть клієнта для Вхідного {{ .Inbound }}" -"chooseInbound" = "Виберіть Вхідний" diff --git a/web/translation/translate.vi_VN.toml b/web/translation/translate.vi_VN.toml deleted file mode 100644 index 8f6edb32..00000000 --- a/web/translation/translate.vi_VN.toml +++ /dev/null @@ -1,598 +0,0 @@ -"username" = "Tên người dùng" -"password" = "Mật khẩu" -"login" = "Đăng nhập" -"confirm" = "Xác nhận" -"cancel" = "Hủy bỏ" -"close" = "Đóng" -"copy" = "Sao chép" -"copied" = "Đã sao chép" -"download" = "Tải xuống" -"remark" = "Ghi chú" -"enable" = "Kích hoạt" -"protocol" = "Giao thức" -"search" = "Tìm kiếm" -"filter" = "Bộ lọc" -"loading" = "Đang tải" -"second" = "Giây" -"minute" = "Phút" -"hour" = "Giờ" -"day" = "Ngày" -"check" = "Kiểm tra" -"indefinite" = "Không xác định" -"unlimited" = "Không giới hạn" -"none" = "None" -"qrCode" = "Mã QR" -"info" = "Thông tin thêm" -"edit" = "Chỉnh sửa" -"delete" = "Xóa" -"reset" = "Đặt lại" -"copySuccess" = "Đã sao chép thành công" -"sure" = "Chắc chắn" -"encryption" = "Mã hóa" -"transmission" = "Truyền tải" -"host" = "Máy chủ" -"path" = "Đường dẫn" -"camouflage" = "Ngụy trang" -"status" = "Trạng thái" -"enabled" = "Đã kích hoạt" -"disabled" = "Đã tắt" -"depleted" = "Depleted" -"depletingSoon" = "Depleting..." -"offline" = "Ngoại tuyến" -"online" = "Trực tuyến" -"domainName" = "Tên miền" -"monitor" = "Listening IP" -"certificate" = "Chứng chỉ số" -"fail" = "Thất bại" -"comment" = "Bình luận" -"success" = "Thành công" -"getVersion" = "Lấy phiên bản" -"install" = "Cài đặt" -"clients" = "Các khách hàng" -"usage" = "Sử dụng" -"secretToken" = "Mã bí mật" -"remained" = "Còn lại" -"security" = "Bảo vệ" -"secAlertTitle" = "Cảnh báo an ninh-Tiếng Việt by Ohoang7" -"secAlertSsl" = "Kết nối này không an toàn; Vui lòng không nhập thông tin nhạy cảm cho đến khi TLS được kích hoạt để bảo vệ dữ liệu của Bạn" -"secAlertConf" = "Một số cài đặt có thể dễ bị tấn công. Đề xuất tăng cường các giao thức bảo mật để ngăn chặn các vi phạm tiềm ẩn." -"secAlertSSL" = "Bảng điều khiển thiếu kết nối an toàn. Vui lòng cài đặt chứng chỉ TLS để bảo vệ dữ liệu." -"secAlertPanelPort" = "Cổng mặc định của bảng điều khiển có thể dễ bị tấn công. Vui lòng cấu hình một cổng ngẫu nhiên hoặc cụ thể." -"secAlertPanelURI" = "Đường dẫn URI mặc định của bảng điều khiển không an toàn. Vui lòng cấu hình một đường dẫn URI phức tạp." -"secAlertSubURI" = "Đường dẫn URI mặc định của đăng ký không an toàn. Vui lòng cấu hình một đường dẫn URI phức tạp." -"secAlertSubJsonURI" = "Đường dẫn URI JSON mặc định của đăng ký không an toàn. Vui lòng cấu hình một đường dẫn URI phức tạp." - -[menu] -"dashboard" = "Trạng thái hệ thống" -"inbounds" = "Đầu vào khách hàng" -"settings" = "Cài đặt bảng điều khiển" -"logout" = "Đăng xuất" -"xray" = "Cài đặt Xray" -"link" = "Quản lý" - -[pages.login] -"hello" = "Xin chào" -"title" = "Chào mừng" -"loginAgain" = "Thời hạn đăng nhập đã hết. Vui lòng đăng nhập lại." - -[pages.login.toasts] -"invalidFormData" = "Dạng dữ liệu nhập không hợp lệ." -"emptyUsername" = "Vui lòng nhập tên người dùng." -"emptyPassword" = "Vui lòng nhập mật khẩu." -"wrongUsernameOrPassword" = "Tên người dùng hoặc mật khẩu không đúng." -"successLogin" = "Đăng nhập thành công." - -[pages.index] -"title" = "Trạng thái hệ thống" -"memory" = "Ram" -"hard" = "Dung lượng" -"xrayStatus" = "Xray" -"stopXray" = "Dừng lại" -"restartXray" = "Khởi động lại" -"xraySwitch" = "Phiên bản" -"xraySwitchClick" = "Chọn phiên bản mà bạn muốn chuyển đổi sang." -"xraySwitchClickDesk" = "Hãy lựa chọn thận trọng, vì các phiên bản cũ có thể không tương thích với các cấu hình hiện tại." -"operationHours" = "Thời gian hoạt động" -"systemLoad" = "Tải hệ thống" -"systemLoadDesc" = "trung bình tải hệ thống trong 1, 5 và 15 phút qua" -"connectionTcpCountDesc" = "Tổng số kết nối TCP trên tất cả các thẻ mạng." -"connectionUdpCountDesc" = "Tổng số kết nối UDP trên tất cả các thẻ mạng." -"connectionCount" = "Số lượng kết nối" -"upSpeed" = "Tổng tốc độ tải lên cho tất cả các thẻ mạng." -"downSpeed" = "Tổng tốc độ tải xuống cho tất cả các thẻ mạng." -"totalSent" = "Tổng lưu lượng tải lên của tất cả các thẻ mạng kể từ khi hệ thống khởi động." -"totalReceive" = "Tổng lưu lượng tải xuống trên tất cả các thẻ mạng kể từ khi hệ thống khởi động." -"xraySwitchVersionDialog" = "Chuyển đổi Phiên bản Xray" -"xraySwitchVersionDialogDesc" = "Bạn có chắc chắn muốn chuyển đổi phiên bản Xray sang" -"dontRefresh" = "Đang tiến hành cài đặt, vui lòng không làm mới trang này." -"logs" = "Nhật ký" -"config" = "Cấu hình" -"backup" = "Sao lưu & Khôi phục" -"backupTitle" = "Sao lưu & Khôi phục Cơ sở dữ liệu" -"backupDescription" = "Hãy nhớ sao lưu trước khi nhập cơ sở dữ liệu mới." -"exportDatabase" = "Tải về Cơ sở dữ liệu" -"importDatabase" = "Tải lên Cơ sở dữ liệu" - -[pages.inbounds] -"title" = "Điểm vào (Inbounds)" -"totalDownUp" = "Tổng tải lên/tải xuống" -"totalUsage" = "Tổng sử dụng" -"inboundCount" = "Số lượng điểm vào" -"operate" = "Thao tác" -"enable" = "Kích hoạt" -"remark" = "Chú thích" -"protocol" = "Giao thức" -"port" = "Cổng" -"traffic" = "Lưu lượng" -"details" = "Chi tiết" -"transportConfig" = "Giao vận" -"expireDate" = "Ngày hết hạn" -"resetTraffic" = "Đặt lại lưu lượng" -"addInbound" = "Thêm điểm vào" -"generalActions" = "Hành động chung" -"create" = "Tạo mới" -"update" = "Cập nhật" -"modifyInbound" = "Chỉnh sửa điểm vào (Inbound)" -"deleteInbound" = "Xóa điểm vào (Inbound)" -"deleteInboundContent" = "Xác nhận xóa điểm vào? (Inbound)" -"deleteClient" = "Xóa người dùng" -"deleteClientContent" = "Bạn có chắc chắn muốn xóa người dùng không?" -"resetTrafficContent" = "Xác nhận đặt lại lưu lượng?" -"copyLink" = "Sao chép liên kết" -"address" = "Địa chỉ" -"network" = "Mạng" -"destinationPort" = "Cổng đích" -"targetAddress" = "Địa chỉ mục tiêu" -"monitorDesc" = "Mặc định để trống" -"meansNoLimit" = "= Không giới hạn (đơn vị: GB)" -"totalFlow" = "Tổng lưu lượng" -"leaveBlankToNeverExpire" = "Để trống để không bao giờ hết hạn" -"noRecommendKeepDefault" = "Không yêu cầu đặc biệt để giữ nguyên cài đặt mặc định" -"certificatePath" = "Đường dẫn tập" -"certificateContent" = "Nội dung tập" -"publicKey" = "Khóa công khai" -"privatekey" = "Khóa cá nhân" -"clickOnQRcode" = "Nhấn vào Mã QR để sao chép" -"client" = "Người dùng" -"export" = "Xuất liên kết" -"clone" = "Sao chép" -"cloneInbound" = "Sao chép điểm vào (Inbound)" -"cloneInboundContent" = "Tất cả cài đặt của điểm vào này, trừ Cổng, IP nghe và máy khách, sẽ được áp dụng cho bản sao." -"cloneInboundOk" = "Sao chép" -"resetAllTraffic" = "Đặt lại lưu lượng cho tất cả điểm vào" -"resetAllTrafficTitle" = "Đặt lại lưu lượng cho tất cả điểm vào" -"resetAllTrafficContent" = "Bạn có chắc chắn muốn đặt lại lưu lượng cho tất cả điểm vào không?" -"resetInboundClientTraffics" = "Đặt lại lưu lượng toàn bộ người dùng của điểm vào" -"resetInboundClientTrafficTitle" = "Đặt lại lưu lượng cho toàn bộ người dùng của điểm vào" -"resetInboundClientTrafficContent" = "Bạn có chắc chắn muốn đặt lại tất cả lưu lượng cho các người dùng của điểm vào này không?" -"resetAllClientTraffics" = "Đặt lại lưu lượng cho toàn bộ người dùng" -"resetAllClientTrafficTitle" = "Đặt lại lưu lượng cho toàn bộ người dùng" -"resetAllClientTrafficContent" = "Bạn có chắc chắn muốn đặt lại tất cả lưu lượng cho toàn bộ người dùng không?" -"delDepletedClients" = "Xóa các người dùng đã cạn kiệt" -"delDepletedClientsTitle" = "Xóa các người dùng đã cạn kiệt" -"delDepletedClientsContent" = "Bạn có chắc chắn muốn xóa toàn bộ người dùng đã cạn kiệt không?" -"email" = "Email" -"emailDesc" = "Vui lòng cung cấp một địa chỉ email duy nhất." -"IPLimit" = "Giới hạn IP" -"IPLimitDesc" = "Vô hiệu hóa điểm vào nếu số lượng vượt quá giá trị đã nhập (nhập 0 để vô hiệu hóa giới hạn IP)." -"IPLimitlog" = "Lịch sử IP" -"IPLimitlogDesc" = "Lịch sử đăng nhập IP (trước khi kích hoạt điểm vào sau khi bị vô hiệu hóa bởi giới hạn IP, bạn nên xóa lịch sử)." -"IPLimitlogclear" = "Xóa Lịch sử" -"setDefaultCert" = "Đặt chứng chỉ từ bảng điều khiển" -"telegramDesc" = "Vui lòng cung cấp ID Trò chuyện Telegram. (sử dụng lệnh '/id' trong bot) hoặc (@userinfobot)" -"subscriptionDesc" = "Bạn có thể tìm liên kết gói đăng ký của mình trong Chi tiết, cũng như bạn có thể sử dụng cùng tên cho nhiều cấu hình khác nhau" -"info" = "Thông tin" -"same" = "Giống nhau" -"inboundData" = "Dữ liệu gửi đến" -"exportInbound" = "Xuất nhập khẩu" -"import" = "Nhập" -"importInbound" = "Nhập inbound" - -[pages.client] -"add" = "Thêm người dùng" -"edit" = "Chỉnh sửa người dùng" -"submitAdd" = "Thêm" -"submitEdit" = "Lưu thay đổi" -"clientCount" = "Số lượng người dùng" -"bulk" = "Thêm hàng loạt" -"method" = "Phương pháp" -"first" = "Đầu tiên" -"last" = "Cuối cùng" -"prefix" = "Tiền tố" -"postfix" = "Hậu tố" -"delayedStart" = "Bắt đầu ở Lần Đầu" -"expireDays" = "Khoảng thời gian" -"days" = "ngày" -"renew" = "Tự động gia hạn" -"renewDesc" = "Tự động gia hạn sau khi hết hạn. (0 = tắt)(đơn vị: ngày)" - -[pages.inbounds.toasts] -"obtain" = "Nhận" - -[pages.inbounds.stream.general] -"request" = "Lời yêu cầu" -"response" = "Phản ứng" -"name" = "Tên" -"value" = "Giá trị" - -[pages.inbounds.stream.tcp] -"version" = "Phiên bản" -"method" = "Phương pháp" -"path" = "Đường dẫn" -"status" = "Trạng thái" -"statusDescription" = "Tình trạng Mô tả" -"requestHeader" = "Header yêu cầu" -"responseHeader" = "Header phản hồi" - -[pages.settings] -"title" = "Cài đặt" -"save" = "Lưu" -"infoDesc" = "Mọi thay đổi được thực hiện ở đây cần phải được lưu. Vui lòng khởi động lại bảng điều khiển để áp dụng các thay đổi." -"restartPanel" = "Khởi động lại bảng điều khiển" -"restartPanelDesc" = "Bạn có chắc chắn muốn khởi động lại bảng điều khiển? Nhấn OK để khởi động lại sau 3 giây. Nếu bạn không thể truy cập bảng điều khiển sau khi khởi động lại, vui lòng xem thông tin nhật ký của bảng điều khiển trên máy chủ." -"actions" = "Hành động" -"resetDefaultConfig" = "Đặt lại cấu hình mặc định" -"panelSettings" = "Bảng điều khiển" -"securitySettings" = "Bảo mật" -"TGBotSettings" = "Bot Telegram" -"panelListeningIP" = "IP Nghe của bảng điều khiển" -"panelListeningIPDesc" = "Mặc định để trống để nghe tất cả các IP." -"panelListeningDomain" = "Tên miền của nghe bảng điều khiển" -"panelListeningDomainDesc" = "Mặc định để trống để nghe tất cả các tên miền và IP" -"panelPort" = "Cổng bảng điều khiển" -"panelPortDesc" = "Cổng được sử dụng để kết nối với bảng điều khiển này" -"publicKeyPath" = "Đường dẫn file chứng chỉ bảng điều khiển" -"publicKeyPathDesc" = "Điền vào đường dẫn đầy đủ (bắt đầu từ '/')" -"privateKeyPath" = "Đường dẫn file khóa của chứng chỉ bảng điều khiển" -"privateKeyPathDesc" = "Điền vào đường dẫn đầy đủ (bắt đầu từ '/')" -"panelUrlPath" = "Đường dẫn gốc URL bảng điều khiển" -"panelUrlPathDesc" = "Phải bắt đầu và kết thúc bằng '/'" -"pageSize" = "Kích thước phân trang" -"pageSizeDesc" = "Xác định kích thước trang cho bảng gửi đến. Đặt 0 để tắt" -"remarkModel" = "Ghi chú mô hình và ký tự phân tách" -"datepicker" = "Kiểu lịch" -"datepickerPlaceholder" = "Chọn ngày" -"datepickerDescription" = "Tác vụ chạy theo lịch trình sẽ chạy theo kiểu lịch này." -"sampleRemark" = "Nhận xét mẫu" -"oldUsername" = "Tên người dùng hiện tại" -"currentPassword" = "Mật khẩu hiện tại" -"newUsername" = "Tên người dùng mới" -"newPassword" = "Mật khẩu mới" -"telegramBotEnable" = "Bật Bot Telegram" -"telegramBotEnableDesc" = "Kết nối với các tính năng của bảng điều khiển này thông qua bot Telegram" -"telegramToken" = "Token Telegram" -"telegramTokenDesc" = "Bạn phải nhận token từ quản lý bot Telegram @botfather" -"telegramProxy" = "Socks5 Proxy" -"telegramProxyDesc" = "Nếu bạn cần socks5 proxy để kết nối với Telegram. Điều chỉnh cài đặt của nó theo hướng dẫn." -"telegramAPIServer" = "Telegram API Server" -"telegramAPIServerDesc" = "Máy chủ API Telegram để sử dụng. Để trống để sử dụng máy chủ mặc định." -"telegramChatId" = "Chat ID Telegram của quản trị viên" -"telegramChatIdDesc" = "Nhiều Chat ID phân tách bằng dấu phẩy. Sử dụng @userinfobot hoặc sử dụng lệnh '/id' trong bot để lấy Chat ID của bạn." -"telegramNotifyTime" = "Thời gian thông báo của bot Telegram" -"telegramNotifyTimeDesc" = "Sử dụng định dạng thời gian Crontab." -"tgNotifyBackup" = "Sao lưu Cơ sở dữ liệu" -"tgNotifyBackupDesc" = "Bao gồm tệp sao lưu cơ sở dữ liệu với thông báo báo cáo." -"tgNotifyLogin" = "Thông báo Đăng nhập" -"tgNotifyLoginDesc" = "Hiển thị tên người dùng, địa chỉ IP và thời gian khi ai đó cố gắng đăng nhập vào bảng điều khiển của bạn." -"sessionMaxAge" = "Thời gian tối đa của phiên" -"sessionMaxAgeDesc" = "Thời gian của phiên đăng nhập (đơn vị: phút)" -"expireTimeDiff" = "Ngưỡng hết hạn cho thông báo" -"expireTimeDiffDesc" = "Nhận thông báo về việc hết hạn tài khoản trước ngưỡng này (đơn vị: ngày)" -"trafficDiff" = "Ngưỡng lưu lượng cho thông báo" -"trafficDiffDesc" = "Nhận thông báo về việc cạn kiệt lưu lượng trước khi đạt đến ngưỡng này (đơn vị: GB)" -"tgNotifyCpu" = "Ngưỡng cảnh báo tỷ lệ CPU" -"tgNotifyCpuDesc" = "Nhận thông báo nếu tỷ lệ sử dụng CPU vượt quá ngưỡng này (đơn vị: %)" -"timeZone" = "Múi giờ" -"timeZoneDesc" = "Các tác vụ được lên lịch chạy theo thời gian trong múi giờ này." -"subSettings" = "Gói đăng ký" -"subEnable" = "Bật dịch vụ" -"subEnableDesc" = "Tính năng gói đăng ký với cấu hình riêng" -"subListen" = "Listening IP" -"subListenDesc" = "Mặc định để trống để nghe tất cả các IP" -"subPort" = "Cổng gói đăng ký" -"subPortDesc" = "Số cổng dịch vụ đăng ký phải chưa được sử dụng trên máy chủ" -"subCertPath" = "Đường dẫn file chứng chỉ gói đăng ký" -"subCertPathDesc" = "Điền vào đường dẫn đầy đủ (bắt đầu với '/')" -"subKeyPath" = "Đường dẫn file khóa của chứng chỉ gói đăng ký" -"subKeyPathDesc" = "Điền vào đường dẫn đầy đủ (bắt đầu với '/')" -"subPath" = "Đường dẫn gốc URL gói đăng ký" -"subPathDesc" = "Phải bắt đầu và kết thúc bằng '/'" -"subDomain" = "Tên miền con" -"subDomainDesc" = "Mặc định để trống để nghe tất cả các tên miền và IP" -"subUpdates" = "Khoảng thời gian cập nhật gói đăng ký" -"subUpdatesDesc" = "Số giờ giữa các cập nhật trong ứng dụng khách" -"subEncrypt" = "Mã hóa cấu hình" -"subEncryptDesc" = "Mã hóa các cấu hình được trả về trong gói đăng ký" -"subShowInfo" = "Hiển thị thông tin sử dụng" -"subShowInfoDesc" = "Hiển thị lưu lượng truy cập còn lại và ngày sau tên cấu hình" -"subURI" = "URI proxy trung gian" -"subURIDesc" = "Thay đổi URI cơ sở của URL gói đăng ký để sử dụng cho proxy trung gian" -"fragment" = "Sự phân mảnh" -"fragmentDesc" = "Kích hoạt phân mảnh cho gói TLS hello" -"fragmentSett" = "Cài đặt phân mảnh" -"noisesDesc" = "Bật Noises." -"noisesSett" = "Cài đặt Noises" -"mux" = "Mux" -"muxDesc" = "Truyền nhiều luồng dữ liệu độc lập trong luồng dữ liệu đã thiết lập." -"muxSett" = "Mux Cài đặt" -"direct" = "Kết nối trực tiếp" -"directDesc" = "Trực tiếp thiết lập kết nối với tên miền hoặc dải IP của một quốc gia cụ thể." - - -[pages.xray] -"title" = "Cài đặt Xray" -"save" = "Lưu cài đặt" -"restart" = "Khởi động lại Xray" -"basicTemplate" = "Mẫu Cơ bản" -"advancedTemplate" = "Mẫu Nâng cao" -"generalConfigs" = "Cấu hình Chung" -"generalConfigsDesc" = "Những tùy chọn này sẽ cung cấp điều chỉnh tổng quát." -"logConfigs" = "Nhật ký" -"logConfigsDesc" = "Nhật ký có thể ảnh hưởng đến hiệu suất máy chủ của bạn. Bạn chỉ nên kích hoạt nó một cách khôn ngoan trong trường hợp bạn cần" -"blockConfigs" = "Cấu hình Chặn" -"blockConfigsDesc" = "Những tùy chọn này sẽ ngăn người dùng kết nối đến các giao thức và trang web cụ thể." -"basicRouting" = "Định tuyến Cơ bản" -"blockConnectionsConfigsDesc" = "Các tùy chọn này sẽ chặn lưu lượng truy cập dựa trên quốc gia được yêu cầu cụ thể." -"directConnectionsConfigsDesc" = "Kết nối trực tiếp đảm bảo rằng lưu lượng truy cập cụ thể không được định tuyến qua máy chủ khác." -"blockips" = "Chặn IP" -"blockdomains" = "Chặn Tên Miền" -"directips" = "IP Trực Tiếp" -"directdomains" = "Tên Miền Trực Tiếp" -"ipv4Routing" = "Định tuyến IPv4" -"ipv4RoutingDesc" = "Những tùy chọn này sẽ chỉ định kết nối đến các tên miền mục tiêu qua IPv4." -"warpRouting" = "Định tuyến WARP" -"warpRoutingDesc" = "Cảnh báo: Trước khi sử dụng những tùy chọn này, hãy cài đặt WARP ở chế độ proxy socks5 trên máy chủ của bạn bằng cách làm theo các bước trên GitHub của bảng điều khiển. WARP sẽ định tuyến lưu lượng đến các trang web qua máy chủ Cloudflare." -"Template" = "Mẫu Cấu hình Xray" -"TemplateDesc" = "Tạo tệp cấu hình Xray cuối cùng dựa trên mẫu này." -"FreedomStrategy" = "Cấu hình Chiến lược cho Giao thức Freedom" -"FreedomStrategyDesc" = "Đặt chiến lược đầu ra của mạng trong Giao thức Freedom." -"RoutingStrategy" = "Cấu hình Chiến lược Định tuyến Tên miền" -"RoutingStrategyDesc" = "Đặt chiến lược định tuyến tổng thể cho việc giải quyết DNS." -"Torrent" = "Cấu hình sử dụng BitTorrent" -"TorrentDesc" = "Thay đổi mẫu cấu hình để tránh việc người dùng sử dụng BitTorrent." -"Family" = "Chặn phần mềm độc hại và nội dung người lớn" -"FamilyDesc" = "Trình phân giải DNS của Cloudflare để chặn phần mềm độc hại và nội dung người lớn để bảo vệ gia đình." -"Inbounds" = "Đầu vào" -"InboundsDesc" = "Thay đổi mẫu cấu hình để chấp nhận các máy khách cụ thể." -"Outbounds" = "Đầu ra" -"Balancers" = "Cân bằng" -"OutboundsDesc" = "Thay đổi mẫu cấu hình để xác định các cách ra đi cho máy chủ này." -"Routings" = "Quy tắc định tuyến" -"RoutingsDesc" = "Mức độ ưu tiên của mỗi quy tắc đều quan trọng!" -"completeTemplate" = "All" -"logLevel" = "Mức đăng nhập" -"logLevelDesc" = "Cấp độ nhật ký cho nhật ký lỗi, cho biết thông tin cần được ghi lại." -"accessLog" = "Nhật ký truy cập" -"accessLogDesc" = "Đường dẫn tệp cho nhật ký truy cập. Nhật ký truy cập bị vô hiệu hóa có giá trị đặc biệt 'không'" -"errorLog" = "Nhật ký lỗi" -"errorLogDesc" = "Đường dẫn tệp cho nhật ký lỗi. Nhật ký lỗi bị vô hiệu hóa có giá trị đặc biệt 'không'" -"dnsLog" = "Nhật ký DNS" -"dnsLogDesc" = "Có bật nhật ký truy vấn DNS không" -"outboundTraffic" = "Lưu lượng đi ra" -"outboundTrafficDesc" = "Có nên bật lưu lượng ra không" -"maskAddress" = "Ẩn Địa Chỉ" -"maskAddressDesc" = "Mặt nạ địa chỉ IP, khi được bật, sẽ tự động thay thế địa chỉ IP xuất hiện trong nhật ký." - -[pages.xray.rules] -"first" = "Đầu tiên" -"last" = "Cuối cùng" -"up" = "Lên" -"down" = "Xuống" -"source" = "Nguồn" -"dest" = "Đích" -"inbound" = "Vào" -"outbound" = "Ra" -"balancer" = "Cân bằng" -"info" = "Thông tin" -"add" = "Thêm quy tắc" -"edit" = "Chỉnh sửa quy tắc" -"useComma" = "Các mục được phân tách bằng dấu phẩy" - -[pages.xray.outbound] -"addOutbound" = "Thêm thư đi" -"addReverse" = "Thêm đảo ngược" -"editOutbound" = "Chỉnh sửa gửi đi" -"editReverse" = "Chỉnh sửa ngược lại" -"tag" = "Thẻ" -"tagDesc" = "thẻ duy nhất" -"address" = "Địa chỉ" -"reverse" = "Đảo ngược" -"domain" = "Miền" -"type" = "Loại" -"bridge" = "Cầu" -"portal" = "Cổng thông tin" -"intercon" = "Kết nối" -"settings" = "cài đặt" -"accountInfo" = "Thông tin tài khoản" -"outboundStatus" = "Trạng thái đầu ra" -"sendThrough" = "Gửi qua" - -[pages.xray.balancer] -"addBalancer" = "Thêm cân bằng" -"editBalancer" = "Chỉnh sửa cân bằng" -"balancerStrategy" = "Chiến lược" -"balancerSelectors" = "Bộ chọn" -"tag" = "Thẻ" -"tagDesc" = "thẻ duy nhất" -"balancerDesc" = "Không thể sử dụng balancerTag và outboundTag cùng một lúc. Nếu sử dụng cùng lúc thì chỉ outboundTag mới hoạt động." - -[pages.xray.wireguard] -"secretKey" = "Khoá bí mật" -"publicKey" = "Khóa công khai" -"allowedIPs" = "IP được phép" -"endpoint" = "Điểm cuối" -"psk" = "Khóa chia sẻ" -"domainStrategy" = "Chiến lược tên miền" - -[pages.xray.dns] -"enable" = "Kích hoạt DNS" -"enableDesc" = "Kích hoạt máy chủ DNS tích hợp" -"tag" = "Thẻ gửi đến DNS" -"tagDesc" = "Thẻ này sẽ có sẵn dưới dạng thẻ Gửi đến trong quy tắc định tuyến." -"strategy" = "Chiến lược truy vấn" -"strategyDesc" = "Chiến lược tổng thể để phân giải tên miền" -"add" = "Thêm máy chủ" -"edit" = "Chỉnh sửa máy chủ" -"domains" = "Tên miền" -"expectIPs" = "Các IP Dự Kiến" - -[pages.xray.fakedns] -"add" = "Thêm DNS giả" -"edit" = "Chỉnh sửa DNS giả" -"ipPool" = "Mạng con nhóm IP" -"poolSize" = "Kích thước bể bơi" - -[pages.settings.security] -"admin" = "Quản trị viên" -"secret" = "Mã thông báo bí mật" -"loginSecurity" = "Bảo mật đăng nhập" -"loginSecurityDesc" = "Bật bước bảo mật đăng nhập bổ sung cho người dùng" -"secretToken" = "Mã bí mật" -"secretTokenDesc" = "Vui lòng sao chép và lưu trữ mã này một cách an toàn ở nơi an toàn. Mã này cần thiết để đăng nhập và không thể phục hồi từ công cụ lệnh x-ui." - -[pages.settings.toasts] -"modifySettings" = "Chỉnh sửa cài đặt " -"getSettings" = "Lấy cài đặt " -"modifyUser" = "Chỉnh sửa người dùng " -"originalUserPassIncorrect" = "Tên người dùng hoặc mật khẩu gốc không đúng" -"userPassMustBeNotEmpty" = "Tên người dùng mới và mật khẩu mới không thể để trống" - -[tgbot] -"keyboardClosed" = "❌ Bàn phím tùy chỉnh đã đóng!" -"noResult" = "❗ Không có kết quả!" -"noQuery" = "❌ Không tìm thấy truy vấn! Vui lòng sử dụng lệnh lại!" -"wentWrong" = "❌ Đã xảy ra lỗi!" -"noIpRecord" = "❗ Không có bản ghi IP!" -"noInbounds" = "❗ Không tìm thấy inbound!" -"unlimited" = "♾ Không giới hạn" -"add" = "Thêm" -"month" = "Tháng" -"months" = "Tháng" -"day" = "Ngày" -"days" = "Ngày" -"hours" = "Giờ" -"unknown" = "Không rõ" -"inbounds" = "Vào" -"clients" = "Các người dùng" -"offline" = "🔴 Ngoại tuyến" -"online" = "🟢 Trực tuyến" - -[tgbot.commands] -"unknown" = "❗ Lệnh không rõ" -"pleaseChoose" = "👇 Vui lòng chọn:\r\n" -"help" = "🤖 Chào mừng bạn đến với bot này! Bot được thiết kế để cung cấp cho bạn dữ liệu cụ thể từ máy chủ và cho phép bạn thực hiện các thay đổi cần thiết.\r\n\r\n" -"start" = "👋 Xin chào {{ .Firstname }}.\r\n" -"welcome" = "🤖 Chào mừng đến với bot quản lý của {{ .Hostname }}.\r\n" -"status" = "✅ Bot hoạt động bình thường!" -"usage" = "❗ Vui lòng cung cấp văn bản để tìm kiếm!" -"getID" = "🆔 ID của bạn: {{ .ID }}" -"helpAdminCommands" = "Để khởi động lại Xray Core:\r\n/restart force\r\n\r\nĐể tìm kiếm email của khách hàng:\r\n/usage [Email]\r\n\r\nĐể tìm kiếm các nhập (với số liệu thống kê của khách hàng):\r\n/inbound [Ghi chú]\r\n\r\nID Trò chuyện Telegram:\r\n/id" -"helpClientCommands" = "Để tìm kiếm thống kê, sử dụng lệnh sau:\r\n/usage [Email]\r\n\r\nID Trò chuyện Telegram:\r\n/id" -"restartUsage" = "\r\n\r\n/restart force" -"restartSuccess" = "✅ Hoạt động thành công!" -"restartFailed" = "❗ Lỗi trong quá trình hoạt động.\r\n\r\nLỗi: {{ .Error }}." -"xrayNotRunning" = "❗ Xray Core không chạy." - -[tgbot.messages] -"cpuThreshold" = "🔴 Sử dụng CPU {{ .Percent }}% vượt quá ngưỡng {{ .Threshold }}%" -"selectUserFailed" = "❌ Lỗi khi chọn người dùng!" -"userSaved" = "✅ Người dùng Telegram đã được lưu." -"loginSuccess" = "✅ Đăng nhập thành công vào bảng điều khiển.\r\n" -"loginFailed" = "❗️ Đăng nhập vào bảng điều khiển thất bại.\r\n" -"report" = "🕰 Báo cáo định kỳ: {{ .RunTime }}\r\n" -"datetime" = "⏰ Ngày-Giờ: {{ .DateTime }}\r\n" -"hostname" = "💻 Tên máy chủ: {{ .Hostname }}\r\n" -"version" = "🚀 Phiên bản X-UI: {{ .Version }}\r\n" -"xrayVersion" = "📡 Phiên bản Xray: {{ .XrayVersion }}\r\n" -"ipv6" = "🌐 IPv6: {{ .IPv6 }}\r\n" -"ipv4" = "🌐 IPv4: {{ .IPv4 }}\r\n" -"ip" = "🌐 IP: {{ .IP }}\r\n" -"ips" = "🔢 Các IP:\r\n{{ .IPs }}\r\n" -"serverUpTime" = "⏳ Thời gian hoạt động của máy chủ: {{ .UpTime }} {{ .Unit }}\r\n" -"serverLoad" = "📈 Tải máy chủ: {{ .Load1 }}, {{ .Load2 }}, {{ .Load3 }}\r\n" -"serverMemory" = "📋 Bộ nhớ máy chủ: {{ .Current }}/{{ .Total }}\r\n" -"tcpCount" = "🔹 Số lượng kết nối TCP: {{ .Count }}\r\n" -"udpCount" = "🔸 Số lượng kết nối UDP: {{ .Count }}\r\n" -"traffic" = "🚦 Lưu lượng: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n" -"xrayStatus" = "ℹ️ Trạng thái Xray: {{ .State }}\r\n" -"username" = "👤 Tên người dùng: {{ .Username }}\r\n" -"password" = "👤 Mật khẩu: {{ .Password }}\r\n" -"time" = "⏰ Thời gian: {{ .Time }}\r\n" -"inbound" = "📍 Inbound: {{ .Remark }}\r\n" -"port" = "🔌 Cổng: {{ .Port }}\r\n" -"expire" = "📅 Ngày hết hạn: {{ .Time }}\r\n" -"expireIn" = "📅 Hết hạn sau: {{ .Time }}\r\n" -"active" = "💡 Đang hoạt động: {{ .Enable }}\r\n" -"enabled" = "🚨 Đã bật: {{ .Enable }}\r\n" -"online" = "🌐 Trạng thái kết nối: {{ .Status }}\r\n" -"email" = "📧 Email: {{ .Email }}\r\n" -"upload" = "🔼 Tải lên: ↑{{ .Upload }}\r\n" -"download" = "🔽 Tải xuống: ↓{{ .Download }}\r\n" -"total" = "📊 Tổng cộng: ↑↓{{ .UpDown }} / {{ .Total }}\r\n" -"TGUser" = "👤 Người dùng Telegram: {{ .TelegramID }}\r\n" -"exhaustedMsg" = "🚨 Sự cạn kiệt {{ .Type }}:\r\n" -"exhaustedCount" = "🚨 Số lần cạn kiệt {{ .Type }}:\r\n" -"onlinesCount" = "🌐 Khách hàng trực tuyến: {{ .Count }}\r\n" -"disabled" = "🛑 Vô hiệu hóa: {{ .Disabled }}\r\n" -"depleteSoon" = "🔜 Sắp cạn kiệt: {{ .Deplete }}\r\n\r\n" -"backupTime" = "🗄 Thời gian sao lưu: {{ .Time }}\r\n" -"refreshedOn" = "\r\n📋🔄 Đã cập nhật lần cuối vào: {{ .Time }}\r\n\r\n" -"yes" = "✅ Có" -"no" = "❌ Không" - -[tgbot.buttons] -"closeKeyboard" = "❌ Đóng Bàn Phím" -"cancel" = "❌ Hủy" -"cancelReset" = "❌ Hủy Đặt Lại" -"cancelIpLimit" = "❌ Hủy Giới Hạn IP" -"confirmResetTraffic" = "✅ Xác Nhận Đặt Lại Lưu Lượng?" -"confirmClearIps" = "✅ Xác Nhận Xóa Các IP?" -"confirmRemoveTGUser" = "✅ Xác Nhận Xóa Người Dùng Telegram?" -"confirmToggle" = "✅ Xác nhận Bật/Tắt người dùng?" -"dbBackup" = "Tải bản sao lưu cơ sở dữ liệu" -"serverUsage" = "Sử Dụng Máy Chủ" -"getInbounds" = "Lấy cổng vào" -"depleteSoon" = "Depleted Soon" -"clientUsage" = "Lấy Sử Dụng" -"onlines" = "Khách hàng trực tuyến" -"commands" = "Lệnh" -"refresh" = "🔄 Cập Nhật" -"clearIPs" = "❌ Xóa IP" -"removeTGUser" = "❌ Xóa Người Dùng Telegram" -"selectTGUser" = "👤 Chọn Người Dùng Telegram" -"selectOneTGUser" = "👤 Chọn một người dùng telegram:" -"resetTraffic" = "📈 Đặt Lại Lưu Lượng" -"resetExpire" = "📅 Thay đổi ngày hết hạn" -"ipLog" = "🔢 Nhật ký địa chỉ IP" -"ipLimit" = "🔢 Giới Hạn địa chỉ IP" -"setTGUser" = "👤 Đặt Người Dùng Telegram" -"toggle" = "🔘 Bật / Tắt" -"custom" = "🔢 Tùy chỉnh" -"confirmNumber" = "✅ Xác nhận: {{ .Num }}" -"confirmNumberAdd" = "✅ Xác nhận thêm: {{ .Num }}" -"limitTraffic" = "🚧 Giới hạn lưu lượng" -"getBanLogs" = "Cấm nhật ký" -"allClients" = "Tất cả Khách hàng" - -[tgbot.answers] -"successfulOperation" = "✅ Thành công!" -"errorOperation" = "❗ Lỗi Trong Quá Trình Thực Hiện." -"getInboundsFailed" = "❌ Không Thể Lấy Được Inbounds" -"getClientsFailed" = "❌ Không thể lấy khách hàng." -"canceled" = "❌ {{ .Email }} : Thao Tác Đã Bị Hủy." -"clientRefreshSuccess" = "✅ {{ .Email }} : Cập Nhật Thành Công Cho Khách Hàng." -"IpRefreshSuccess" = "✅ {{ .Email }} : Cập Nhật Thành Công Cho IPs." -"TGIdRefreshSuccess" = "✅ {{ .Email }} : Cập Nhật Thành Công Cho Người Dùng Telegram." -"resetTrafficSuccess" = "✅ {{ .Email }} : Đặt Lại Lưu Lượng Thành Công." -"setTrafficLimitSuccess" = "✅ {{ .Email }} : Đã lưu thành công giới hạn lưu lượng." -"expireResetSuccess" = "✅ {{ .Email }} : Đặt Lại Ngày Hết Hạn Thành Công." -"resetIpSuccess" = "✅ {{ .Email }} : Giới Hạn IP {{ .Count }} Đã Được Lưu Thành Công." -"clearIpSuccess" = "✅ {{ .Email }} : IP Đã Được Xóa Thành Công." -"getIpLog" = "✅ {{ .Email }} : Lấy nhật ký IP Thành Công." -"getUserInfo" = "✅ {{ .Email }} : Lấy Thông Tin Người Dùng Telegram Thành Công." -"removedTGUserSuccess" = "✅ {{ .Email }} : Người Dùng Telegram Đã Được Xóa Thành Công." -"enableSuccess" = "✅ {{ .Email }} : Đã Bật Thành Công." -"disableSuccess" = "✅ {{ .Email }} : Đã Tắt Thành Công." -"askToAddUserId" = "Cấu hình của bạn không được tìm thấy!\r\nVui lòng yêu cầu Quản trị viên sử dụng ID người dùng telegram của bạn trong cấu hình của bạn.\r\n\r\nID người dùng của bạn: {{ .TgUserID }}" -"chooseClient" = "Chọn một Khách hàng cho Inbound {{ .Inbound }}" -"chooseInbound" = "Chọn một Inbound" diff --git a/web/translation/translate.zh_CN.toml b/web/translation/translate.zh_CN.toml deleted file mode 100644 index 843fee1b..00000000 --- a/web/translation/translate.zh_CN.toml +++ /dev/null @@ -1,598 +0,0 @@ -"username" = "用户名" -"password" = "密码" -"login" = "登录" -"confirm" = "确定" -"cancel" = "取消" -"close" = "关闭" -"copy" = "复制" -"copied" = "已复制" -"download" = "下载" -"remark" = "备注" -"enable" = "启用" -"protocol" = "协议" -"search" = "搜索" -"filter" = "筛选" -"loading" = "加载中..." -"second" = "秒" -"minute" = "分钟" -"hour" = "小时" -"day" = "天" -"check" = "查看" -"indefinite" = "无限期" -"unlimited" = "无限制" -"none" = "无" -"qrCode" = "二维码" -"info" = "更多信息" -"edit" = "编辑" -"delete" = "删除" -"reset" = "重置" -"copySuccess" = "复制成功" -"sure" = "确定" -"encryption" = "加密" -"transmission" = "传输" -"host" = "主机" -"path" = "路径" -"camouflage" = "伪装" -"status" = "状态" -"enabled" = "开启" -"disabled" = "关闭" -"depleted" = "耗尽" -"depletingSoon" = "即将耗尽" -"offline" = "离线" -"online" = "在线" -"domainName" = "域名" -"monitor" = "监听" -"certificate" = "数字证书" -"fail" = "失败" -"comment" = "评论" -"success" = "成功" -"getVersion" = "获取版本" -"install" = "安装" -"clients" = "客户端" -"usage" = "使用情况" -"secretToken" = "安全密钥" -"remained" = "剩余" -"security" = "安全" -"secAlertTitle" = "安全警报" -"secAlertSsl" = "此连接不安全。在激活 TLS 进行数据保护之前,请勿输入敏感信息。" -"secAlertConf" = "某些设置易受攻击。建议加强安全协议以防止潜在漏洞。" -"secAlertSSL" = "面板缺少安全连接。请安装 TLS 证书以保护数据安全。" -"secAlertPanelPort" = "面板默认端口存在安全风险。请配置随机端口或特定端口。" -"secAlertPanelURI" = "面板默认 URI 路径不安全。请配置复杂的 URI 路径。" -"secAlertSubURI" = "订阅默认 URI 路径不安全。请配置复杂的 URI 路径。" -"secAlertSubJsonURI" = "订阅 JSON 默认 URI 路径不安全。请配置复杂的 URI 路径。" - -[menu] -"dashboard" = "系统状态" -"inbounds" = "入站列表" -"settings" = "面板设置" -"xray" = "Xray 设置" -"logout" = "退出登录" -"link" = "管理" - -[pages.login] -"hello" = "你好" -"title" = "欢迎" -"loginAgain" = "登录时效已过,请重新登录" - -[pages.login.toasts] -"invalidFormData" = "数据格式错误" -"emptyUsername" = "请输入用户名" -"emptyPassword" = "请输入密码" -"wrongUsernameOrPassword" = "用户名或密码错误" -"successLogin" = "登录" - -[pages.index] -"title" = "系统状态" -"memory" = "内存" -"hard" = "磁盘" -"xrayStatus" = "Xray" -"stopXray" = "停止" -"restartXray" = "重启" -"xraySwitch" = "版本" -"xraySwitchClick" = "选择你要切换到的版本" -"xraySwitchClickDesk" = "请谨慎选择,因为较旧版本可能与当前配置不兼容" -"operationHours" = "系统正常运行时间" -"systemLoad" = "系统负载" -"systemLoadDesc" = "过去 1、5 和 15 分钟的系统平均负载" -"connectionTcpCountDesc" = "系统中所有 TCP 连接数" -"connectionUdpCountDesc" = "系统中所有 UDP 连接数" -"connectionCount" = "连接数" -"upSpeed" = "总上传速度" -"downSpeed" = "总下载速度" -"totalSent" = "系统启动以来发送的总数据量" -"totalReceive" = "系统启动以来接收的总数据量" -"xraySwitchVersionDialog" = "切换 Xray 版本" -"xraySwitchVersionDialogDesc" = "是否切换 Xray 版本至" -"dontRefresh" = "安装中,请勿刷新此页面" -"logs" = "日志" -"config" = "配置" -"backup" = "备份和恢复" -"backupTitle" = "备份和恢复数据库" -"backupDescription" = "恢复数据库之前建议进行备份" -"exportDatabase" = "备份" -"importDatabase" = "恢复" - -[pages.inbounds] -"title" = "入站列表" -"totalDownUp" = "总上传 / 下载" -"totalUsage" = "总用量" -"inboundCount" = "入站数量" -"operate" = "菜单" -"enable" = "启用" -"remark" = "备注" -"protocol" = "协议" -"port" = "端口" -"traffic" = "流量" -"details" = "详细信息" -"transportConfig" = "传输配置" -"expireDate" = "到期时间" -"resetTraffic" = "重置流量" -"addInbound" = "添加入站" -"generalActions" = "通用操作" -"create" = "添加" -"update" = "修改" -"modifyInbound" = "修改入站" -"deleteInbound" = "删除入站" -"deleteInboundContent" = "确定要删除入站吗?" -"deleteClient" = "删除客户端" -"deleteClientContent" = "确定要删除客户端吗?" -"resetTrafficContent" = "确定要重置流量吗?" -"copyLink" = "复制链接" -"address" = "地址" -"network" = "网络" -"destinationPort" = "目标端口" -"targetAddress" = "目标地址" -"monitorDesc" = "留空表示监听所有 IP" -"meansNoLimit" = "= 无限制(单位:GB)" -"totalFlow" = "总流量" -"leaveBlankToNeverExpire" = "留空表示永不过期" -"noRecommendKeepDefault" = "建议保留默认值" -"certificatePath" = "文件路径" -"certificateContent" = "文件内容" -"publicKey" = "公钥" -"privatekey" = "私钥" -"clickOnQRcode" = "点击二维码复制" -"client" = "客户" -"export" = "导出链接" -"clone" = "克隆" -"cloneInbound" = "克隆" -"cloneInboundContent" = "此入站规则除端口(Port)、监听 IP(Listening IP)和客户端(Clients)以外的所有配置都将应用于克隆" -"cloneInboundOk" = "创建克隆" -"resetAllTraffic" = "重置所有入站流量" -"resetAllTrafficTitle" = "重置所有入站流量" -"resetAllTrafficContent" = "确定要重置所有入站流量吗?" -"resetInboundClientTraffics" = "重置客户端流量" -"resetInboundClientTrafficTitle" = "重置所有客户端流量" -"resetInboundClientTrafficContent" = "确定要重置此入站客户端的所有流量吗?" -"resetAllClientTraffics" = "重置所有客户端流量" -"resetAllClientTrafficTitle" = "重置所有客户端流量" -"resetAllClientTrafficContent" = "确定要重置所有客户端的所有流量吗?" -"delDepletedClients" = "删除流量耗尽的客户端" -"delDepletedClientsTitle" = "删除流量耗尽的客户端" -"delDepletedClientsContent" = "确定要删除所有流量耗尽的客户端吗?" -"email" = "电子邮件" -"emailDesc" = "电子邮件必须完全唯一" -"IPLimit" = "IP 限制" -"IPLimitDesc" = "如果数量超过设置值,则禁用入站流量。(0 = 禁用)" -"IPLimitlog" = "IP 日志" -"IPLimitlogDesc" = "IP 历史日志(要启用被禁用的入站流量,请清除日志)" -"IPLimitlogclear" = "清除日志" -"setDefaultCert" = "从面板设置证书" -"telegramDesc" = "请提供Telegram聊天ID。(在机器人中使用'/id'命令)或(@userinfobot" -"subscriptionDesc" = "要找到你的订阅 URL,请导航到“详细信息”。此外,你可以为多个客户端使用相同的名称。" -"info" = "信息" -"same" = "相同" -"inboundData" = "入站数据" -"exportInbound" = "导出入站规则" -"import"="导入" -"importInbound" = "导入入站规则" - -[pages.client] -"add" = "添加客户端" -"edit" = "编辑客户端" -"submitAdd" = "添加客户端" -"submitEdit" = "保存修改" -"clientCount" = "客户端数量" -"bulk" = "批量创建" -"method" = "方法" -"first" = "置顶" -"last" = "置底" -"prefix" = "前缀" -"postfix" = "后缀" -"delayedStart" = "首次使用后开始" -"expireDays" = "期间" -"days" = "天" -"renew" = "自动续订" -"renewDesc" = "到期后自动续订。(0 = 禁用)(单位: 天)" - -[pages.inbounds.toasts] -"obtain" = "获取" - -[pages.inbounds.stream.general] -"request" = "请求" -"response" = "响应" -"name" = "名称" -"value" = "值" - -[pages.inbounds.stream.tcp] -"version" = "版本" -"method" = "方法" -"path" = "路径" -"status" = "状态" -"statusDescription" = "状态说明" -"requestHeader" = "请求头" -"responseHeader" = "响应头" - -[pages.settings] -"title" = "面板设置" -"save" = "保存" -"infoDesc" = "此处的所有更改都需要保存并重启面板才能生效" -"restartPanel" = "重启面板" -"restartPanelDesc" = "确定要重启面板吗?若重启后无法访问面板,请前往服务器查看面板日志信息" -"actions" = "操作" -"resetDefaultConfig" = "重置为默认配置" -"panelSettings" = "常规" -"securitySettings" = "安全设定" -"TGBotSettings" = "Telegram 机器人配置" -"panelListeningIP" = "面板监听 IP" -"panelListeningIPDesc" = "默认留空监听所有 IP" -"panelListeningDomain" = "面板监听域名" -"panelListeningDomainDesc" = "默认情况下留空以监视所有域名和 IP 地址" -"panelPort" = "面板监听端口" -"panelPortDesc" = "重启面板生效" -"publicKeyPath" = "面板证书公钥文件路径" -"publicKeyPathDesc" = "填写一个 '/' 开头的绝对路径" -"privateKeyPath" = "面板证书密钥文件路径" -"privateKeyPathDesc" = "填写一个 '/' 开头的绝对路径" -"panelUrlPath" = "面板 url 根路径" -"panelUrlPathDesc" = "必须以 '/' 开头,以 '/' 结尾" -"pageSize" = "分页大小" -"pageSizeDesc" = "定义入站表的页面大小。设置 0 表示禁用" -"remarkModel" = "备注模型和分隔符" -"datepicker" = "日期选择器" -"datepickerPlaceholder" = "选择日期" -"datepickerDescription" = "选择器日历类型指定到期日期" -"sampleRemark" = "备注示例" -"oldUsername" = "原用户名" -"currentPassword" = "原密码" -"newUsername" = "新用户名" -"newPassword" = "新密码" -"telegramBotEnable" = "启用 Telegram 机器人" -"telegramBotEnableDesc" = "启用 Telegram 机器人功能" -"telegramToken" = "Telegram 机器人令牌(token)" -"telegramTokenDesc" = "从 '@BotFather' 获取的 Telegram 机器人令牌" -"telegramProxy" = "SOCKS5 Proxy" -"telegramProxyDesc" = "启用 SOCKS5 代理连接到 Telegram(根据指南调整设置)" -"telegramAPIServer" = "Telegram API Server" -"telegramAPIServerDesc" = "要使用的 Telegram API 服务器。留空以使用默认服务器。" -"telegramChatId" = "管理员聊天 ID" -"telegramChatIdDesc" = "Telegram 管理员聊天 ID (多个以逗号分隔)(可通过 @userinfobot 获取,或在机器人中使用 '/id' 命令获取)" -"telegramNotifyTime" = "通知时间" -"telegramNotifyTimeDesc" = "设置周期性的 Telegram 机器人通知时间(使用 crontab 时间格式)" -"tgNotifyBackup" = "数据库备份" -"tgNotifyBackupDesc" = "发送带有报告的数据库备份文件" -"tgNotifyLogin" = "登录通知" -"tgNotifyLoginDesc" = "当有人试图登录你的面板时显示用户名、IP 地址和时间" -"sessionMaxAge" = "会话时长" -"sessionMaxAgeDesc" = "保持登录状态的时长(单位:分钟)" -"expireTimeDiff" = "到期通知阈值" -"expireTimeDiffDesc" = "达到此阈值时,将收到有关到期时间的通知(单位:天)" -"trafficDiff" = "流量耗尽阈值" -"trafficDiffDesc" = "达到此阈值时,将收到有关流量耗尽的通知(单位:GB)" -"tgNotifyCpu" = "CPU 负载通知阈值" -"tgNotifyCpuDesc" = "CPU 负载超过此阈值时,将收到通知(单位:%)" -"timeZone" = "时区" -"timeZoneDesc" = "定时任务将按照该时区的时间运行" -"subSettings" = "订阅设置" -"subEnable" = "启用订阅服务" -"subEnableDesc" = "启用订阅服务功能" -"subListen" = "监听 IP" -"subListenDesc" = "订阅服务监听的 IP 地址(留空表示监听所有 IP)" -"subPort" = "监听端口" -"subPortDesc" = "订阅服务监听的端口号(必须是未使用的端口)" -"subCertPath" = "公钥路径" -"subCertPathDesc" = "订阅服务使用的公钥文件路径(以 '/' 开头)" -"subKeyPath" = "私钥路径" -"subKeyPathDesc" = "订阅服务使用的私钥文件路径(以 '/' 开头)" -"subPath" = "URI 路径" -"subPathDesc" = "订阅服务使用的 URI 路径(以 '/' 开头,以 '/' 结尾)" -"subDomain" = "监听域名" -"subDomainDesc" = "订阅服务监听的域名(留空表示监听所有域名和 IP)" -"subUpdates" = "更新间隔" -"subUpdatesDesc" = "客户端应用中订阅 URL 的更新间隔(单位:小时)" -"subEncrypt" = "编码" -"subEncryptDesc" = "订阅服务返回的内容将采用 Base64 编码" -"subShowInfo" = "显示使用信息" -"subShowInfoDesc" = "客户端应用中将显示剩余流量和日期信息" -"subURI" = "反向代理 URI" -"subURIDesc" = "用于代理后面的订阅 URL 的 URI 路径" -"fragment" = "分片" -"fragmentDesc" = "启用 TLS hello 数据包分片" -"fragmentSett" = "设置" -"noisesDesc" = "启用 Noises." -"noisesSett" = "Noises 设置" -"mux" = "多路复用器" -"muxDesc" = "在已建立的数据流内传输多个独立的数据流" -"muxSett" = "复用器设置" -"direct" = "直接连接" -"directDesc" = "直接与特定国家的域或IP范围建立连接" - - -[pages.xray] -"title" = "Xray 配置" -"save" = "保存" -"restart" = "重新启动 Xray" -"basicTemplate" = "基础配置" -"advancedTemplate" = "高级配置" -"generalConfigs" = "常规配置" -"generalConfigsDesc" = "这些选项将决定常规配置" -"logConfigs" = "日志" -"logConfigsDesc" = "日志可能会影响服务器的性能,建议仅在需要时启用" -"blockConfigs" = "防护屏蔽" -"blockConfigsDesc" = "这些选项将阻止用户连接到特定协议和网站" -"basicRouting" = "基本路由" -"blockConnectionsConfigsDesc" = "这些选项将根据特定的请求国家阻止流量。" -"directConnectionsConfigsDesc" = "直接连接确保特定的流量不会通过其他服务器路由。" -"blockips" = "阻止IP" -"blockdomains" = "阻止域名" -"directips" = "直接IP" -"directdomains" = "直接域名" -"ipv4Routing" = "IPv4 路由" -"ipv4RoutingDesc" = "此选项将仅通过 IPv4 路由到目标域" -"warpRouting" = "WARP 路由" -"warpRoutingDesc" = "注意:在使用这些选项之前,请按照面板 GitHub 上的步骤在你的服务器上以 socks5 代理模式安装 WARP。WARP 将通过 Cloudflare 服务器将流量路由到网站。" -"Template" = "高级 Xray 配置模板" -"TemplateDesc" = "最终的 Xray 配置文件将基于此模板生成" -"FreedomStrategy" = "Freedom 协议策略" -"FreedomStrategyDesc" = "设置 Freedom 协议中网络的输出策略" -"RoutingStrategy" = "配置路由域策略" -"RoutingStrategyDesc" = "设置 DNS 解析的整体路由策略" -"Torrent" = "屏蔽 BitTorrent 协议" -"TorrentDesc" = "禁止使用 BitTorrent" -"Family" = "家庭保护" -"FamilyDesc" = "屏蔽成人内容和恶意网站" -"Inbounds" = "入站规则" -"InboundsDesc" = "接受来自特定客户端的流量" -"Outbounds" = "出站规则" -"Balancers" = "负载均衡" -"OutboundsDesc" = "设置出站流量传出方式" -"Routings" = "路由规则" -"RoutingsDesc" = "每条规则的优先级都很重要" -"completeTemplate" = "全部" -"logLevel" = "日志级别" -"logLevelDesc" = "错误日志的日志级别,用于指示需要记录的信息" -"accessLog" = "访问日志" -"accessLogDesc" = "访问日志的文件路径。特殊值 'none' 禁用访问日志" -"errorLog" = "错误日志" -"errorLogDesc" = "错误日志的文件路径。特殊值 'none' 禁用错误日志" -"dnsLog" = "DNS 日志" -"dnsLogDesc" = "是否启用 DNS 查询日志" -"outboundTraffic" = "出站流量" -"outboundTrafficDesc" = "是否启用出站流量" -"maskAddress" = "隐藏地址" -"maskAddressDesc" = "IP 地址掩码,启用时会自动替换日志中出现的 IP 地址。" - -[pages.xray.rules] -"first" = "置顶" -"last" = "置底" -"up" = "向上" -"down" = "向下" -"source" = "来源" -"dest" = "目的地址" -"inbound" = "入站" -"outbound" = "出站" -"balancer" = "负载均衡" -"info" = "信息" -"add" = "添加规则" -"edit" = "编辑规则" -"useComma" = "逗号分隔的项目" - -[pages.xray.outbound] -"addOutbound" = "添加出站" -"addReverse" = "添加反向" -"editOutbound" = "编辑出站" -"editReverse" = "编辑反向" -"tag" = "标签" -"tagDesc" = "唯一标签" -"address" = "地址" -"reverse" = "反向" -"domain" = "域名" -"type" = "类型" -"bridge" = "Bridge" -"portal" = "Portal" -"intercon" = "互连" -"settings" = "设置" -"accountInfo" = "帐户信息" -"outboundStatus" = "出站状态" -"sendThrough" = "发送通过" - -[pages.xray.balancer] -"addBalancer" = "添加负载均衡" -"editBalancer" = "编辑负载均衡" -"balancerStrategy" = "策略" -"balancerSelectors" = "选择器" -"tag" = "标签" -"tagDesc" = "唯一标签" -"balancerDesc" = "无法同时使用 balancerTag 和 outboundTag。如果同时使用,则只有 outboundTag 会生效。" - -[pages.xray.wireguard] -"secretKey" = "密钥" -"publicKey" = "公钥" -"allowedIPs" = "允许的 IP" -"endpoint" = "端点" -"psk" = "共享密钥" -"domainStrategy" = "域策略" - -[pages.xray.dns] -"enable" = "启用 DNS" -"enableDesc" = "启用内置 DNS 服务器" -"tag" = "DNS 入站标签" -"tagDesc" = "此标签将在路由规则中可用作入站标签" -"strategy" = "查询策略" -"strategyDesc" = "解析域名的总体策略" -"add" = "添加服务器" -"edit" = "编辑服务器" -"domains" = "域" -"expectIPs" = "预期 IP" - -[pages.xray.fakedns] -"add" = "添加假 DNS" -"edit" = "编辑假 DNS" -"ipPool" = "IP 池子网" -"poolSize" = "池大小" - -[pages.settings.security] -"admin" = "管理员" -"secret" = "安全令牌" -"loginSecurity" = "登录安全" -"loginSecurityDesc" = "添加额外的身份验证以提高安全性" -"secretToken" = "安全令牌" -"secretTokenDesc" = "请将此令牌存储在安全的地方。此令牌用于登录,丢失无法恢复。" - -[pages.settings.toasts] -"modifySettings" = "修改设置" -"getSettings" = "获取设置" -"modifyUser" = "修改管理员" -"originalUserPassIncorrect" = "原用户名或原密码错误" -"userPassMustBeNotEmpty" = "新用户名和新密码不能为空" - -[tgbot] -"keyboardClosed" = "❌ 自定义键盘已关闭!" -"noResult" = "❗ 没有结果!" -"noQuery" = "❌ 未找到查询!请重新使用命令!" -"wentWrong" = "❌ 出了点问题!" -"noIpRecord" = "❗ 没有 IP 记录!" -"noInbounds" = "❗ 没有找到入站连接!" -"unlimited" = "♾ 无限制" -"add" = "添加" -"month" = "月" -"months" = "月" -"day" = "天" -"days" = "天" -"hours" = "小时" -"unknown" = "未知" -"inbounds" = "入站连接" -"clients" = "客户端" -"offline" = "🔴 离线" -"online" = "🟢 在线" - -[tgbot.commands] -"unknown" = "❗ 未知命令" -"pleaseChoose" = "👇 请选择:\r\n" -"help" = "🤖 欢迎使用本机器人!它旨在为您提供来自服务器的特定数据,并允许您进行必要的修改。\r\n\r\n" -"start" = "👋 你好,{{ .Firstname }}。\r\n" -"welcome" = "🤖 欢迎来到 {{ .Hostname }} 管理机器人。\r\n" -"status" = "✅ 机器人正常运行!" -"usage" = "❗ 请输入要搜索的文本!" -"getID" = "🆔 您的 ID 为:{{ .ID }}" -"helpAdminCommands" = "要重新启动 Xray Core:\r\n/restart force\r\n\r\n要搜索客户电子邮件:\r\n/usage [电子邮件]\r\n\r\n要搜索入站(带有客户统计数据):\r\n/inbound [备注]\r\n\r\nTelegram聊天ID:\r\n/id" -"helpClientCommands" = "要搜索统计数据,请使用以下命令:\r\n/usage [电子邮件]\r\n\r\nTelegram聊天ID:\r\n/id" -"restartUsage" = "\r\n\r\n/restart force" -"restartSuccess" = "✅ 操作成功!" -"restartFailed" = "❗ 操作错误。\r\n\r\n错误: {{ .Error }}." -"xrayNotRunning" = "❗ Xray Core 未运行。" - -[tgbot.messages] -"cpuThreshold" = "🔴 CPU 使用率为 {{ .Percent }}%,超过阈值 {{ .Threshold }}%" -"selectUserFailed" = "❌ 用户选择错误!" -"userSaved" = "✅ 电报用户已保存。" -"loginSuccess" = "✅ 成功登录到面板。\r\n" -"loginFailed" = "❗️ 面板登录失败。\r\n" -"report" = "🕰 定时报告:{{ .RunTime }}\r\n" -"datetime" = "⏰ 日期时间:{{ .DateTime }}\r\n" -"hostname" = "💻 主机名:{{ .Hostname }}\r\n" -"version" = "🚀 X-UI 版本:{{ .Version }}\r\n" -"xrayVersion" = "📡 Xray 版本: {{ .XrayVersion }}\r\n" -"ipv6" = "🌐 IPv6:{{ .IPv6 }}\r\n" -"ipv4" = "🌐 IPv4:{{ .IPv4 }}\r\n" -"ip" = "🌐 IP:{{ .IP }}\r\n" -"ips" = "🔢 IP 地址:\r\n{{ .IPs }}\r\n" -"serverUpTime" = "⏳ 服务器运行时间:{{ .UpTime }} {{ .Unit }}\r\n" -"serverLoad" = "📈 服务器负载:{{ .Load1 }}, {{ .Load2 }}, {{ .Load3 }}\r\n" -"serverMemory" = "📋 服务器内存:{{ .Current }}/{{ .Total }}\r\n" -"tcpCount" = "🔹 TCP 连接数:{{ .Count }}\r\n" -"udpCount" = "🔸 UDP 连接数:{{ .Count }}\r\n" -"traffic" = "🚦 流量:{{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n" -"xrayStatus" = "ℹ️ Xray 状态:{{ .State }}\r\n" -"username" = "👤 用户名:{{ .Username }}\r\n" -"password" = "👤 密码: {{ .Password }}\r\n" -"time" = "⏰ 时间:{{ .Time }}\r\n" -"inbound" = "📍 入站:{{ .Remark }}\r\n" -"port" = "🔌 端口:{{ .Port }}\r\n" -"expire" = "📅 过期日期:{{ .Time }}\r\n" -"expireIn" = "📅 剩余时间:{{ .Time }}\r\n" -"active" = "💡 激活:{{ .Enable }}\r\n" -"enabled" = "🚨 已启用:{{ .Enable }}\r\n" -"online" = "🌐 连接状态:{{ .Status }}\r\n" -"email" = "📧 邮箱:{{ .Email }}\r\n" -"upload" = "🔼 上传↑:{{ .Upload }}\r\n" -"download" = "🔽 下载↓:{{ .Download }}\r\n" -"total" = "📊 总计:{{ .UpDown }} / {{ .Total }}\r\n" -"TGUser" = "👤 电报用户:{{ .TelegramID }}\r\n" -"exhaustedMsg" = "🚨 耗尽的 {{ .Type }}:\r\n" -"exhaustedCount" = "🚨 耗尽的 {{ .Type }} 数量:\r\n" -"onlinesCount" = "🌐 在线客户:{{ .Count }}\r\n" -"disabled" = "🛑 禁用:{{ .Disabled }}\r\n" -"depleteSoon" = "🔜 即将耗尽:{{ .Deplete }}\r\n\r\n" -"backupTime" = "🗄 备份时间:{{ .Time }}\r\n" -"refreshedOn" = "\r\n📋🔄 刷新时间:{{ .Time }}\r\n\r\n" -"yes" = "✅ 是的" -"no" = "❌ 没有" - -[tgbot.buttons] -"closeKeyboard" = "❌ 关闭键盘" -"cancel" = "❌ 取消" -"cancelReset" = "❌ 取消重置" -"cancelIpLimit" = "❌ 取消 IP 限制" -"confirmResetTraffic" = "✅ 确认重置流量?" -"confirmClearIps" = "✅ 确认清除 IP?" -"confirmRemoveTGUser" = "✅ 确认移除 Telegram 用户?" -"confirmToggle" = "✅ 确认启用/禁用用户?" -"dbBackup" = "获取数据库备份" -"serverUsage" = "服务器使用情况" -"getInbounds" = "获取入站信息" -"depleteSoon" = "即将耗尽" -"clientUsage" = "获取使用情况" -"onlines" = "在线客户端" -"commands" = "命令" -"refresh" = "🔄 刷新" -"clearIPs" = "❌ 清除 IP" -"removeTGUser" = "❌ 移除 Telegram 用户" -"selectTGUser" = "👤 选择 Telegram 用户" -"selectOneTGUser" = "👤 选择一个 Telegram 用户:" -"resetTraffic" = "📈 重置流量" -"resetExpire" = "📅 更改到期日期" -"ipLog" = "🔢 IP 日志" -"ipLimit" = "🔢 IP 限制" -"setTGUser" = "👤 设置 Telegram 用户" -"toggle" = "🔘 启用/禁用" -"custom" = "🔢 风俗" -"confirmNumber" = "✅ 确认: {{ .Num }}" -"confirmNumberAdd" = "✅ 确认添加:{{ .Num }}" -"limitTraffic" = "🚧 流量限制" -"getBanLogs" = "禁止日志" -"allClients" = "所有客户" - -[tgbot.answers] -"successfulOperation" = "✅ 成功!" -"errorOperation" = "❗ 操作错误。" -"getInboundsFailed" = "❌ 获取入站信息失败。" -"getClientsFailed" = "❌ 获取客户失败。" -"canceled" = "❌ {{ .Email }}:操作已取消。" -"clientRefreshSuccess" = "✅ {{ .Email }}:客户端刷新成功。" -"IpRefreshSuccess" = "✅ {{ .Email }}:IP 刷新成功。" -"TGIdRefreshSuccess" = "✅ {{ .Email }}:客户端的 Telegram 用户刷新成功。" -"resetTrafficSuccess" = "✅ {{ .Email }}:流量已重置成功。" -"setTrafficLimitSuccess" = "✅ {{ .Email }}: 流量限制保存成功。" -"expireResetSuccess" = "✅ {{ .Email }}:过期天数已重置成功。" -"resetIpSuccess" = "✅ {{ .Email }}:成功保存 IP 限制数量为 {{ .Count }}。" -"clearIpSuccess" = "✅ {{ .Email }}:IP 已成功清除。" -"getIpLog" = "✅ {{ .Email }}:获取 IP 日志。" -"getUserInfo" = "✅ {{ .Email }}:获取 Telegram 用户信息。" -"removedTGUserSuccess" = "✅ {{ .Email }}:Telegram 用户已成功移除。" -"enableSuccess" = "✅ {{ .Email }}:已成功启用。" -"disableSuccess" = "✅ {{ .Email }}:已成功禁用。" -"askToAddUserId" = "未找到您的配置!\r\n请向管理员询问,在您的配置中使用您的 Telegram 用户 ChatID。\r\n\r\n您的用户 ChatID:{{ .TgUserID }}" -"chooseClient" = "为入站 {{ .Inbound }} 选择一个客户" -"chooseInbound" = "选择一个入站" diff --git a/web/translation/translate.zh_TW.toml b/web/translation/translate.zh_TW.toml deleted file mode 100644 index 3e4dda7f..00000000 --- a/web/translation/translate.zh_TW.toml +++ /dev/null @@ -1,598 +0,0 @@ -"username" = "使用者名稱" -"password" = "密碼" -"login" = "登入" -"confirm" = "確定" -"cancel" = "取消" -"close" = "關閉" -"copy" = "複製" -"copied" = "已複製" -"download" = "下載" -"remark" = "備註" -"enable" = "啟用" -"protocol" = "協議" -"search" = "搜尋" -"filter" = "篩選" -"loading" = "載入中..." -"second" = "秒" -"minute" = "分鐘" -"hour" = "小時" -"day" = "天" -"check" = "檢視" -"indefinite" = "無限期" -"unlimited" = "無限制" -"none" = "無" -"qrCode" = "二維碼" -"info" = "更多資訊" -"edit" = "編輯" -"delete" = "刪除" -"reset" = "重置" -"copySuccess" = "複製成功" -"sure" = "確定" -"encryption" = "加密" -"transmission" = "傳輸" -"host" = "主機" -"path" = "路徑" -"camouflage" = "偽裝" -"status" = "狀態" -"enabled" = "開啟" -"disabled" = "關閉" -"depleted" = "耗盡" -"depletingSoon" = "即將耗盡" -"offline" = "離線" -"online" = "線上" -"domainName" = "域名" -"monitor" = "監聽" -"certificate" = "憑證" -"fail" = "失敗" -"comment" = "評論" -"success" = "成功" -"getVersion" = "獲取版本" -"install" = "安裝" -"clients" = "客戶端" -"usage" = "使用情況" -"secretToken" = "安全金鑰" -"remained" = "剩餘" -"security" = "安全" -"secAlertTitle" = "安全警報" -"secAlertSsl" = "此連線不安全。在啟用 TLS 進行資料保護之前,請勿輸入敏感資訊。" -"secAlertConf" = "某些設定易受攻擊。建議加強安全協議以防止潛在漏洞。" -"secAlertSSL" = "面板缺少安全連線。請安裝 TLS 證書以保護資料安全。" -"secAlertPanelPort" = "面板預設埠存在安全風險。請配置隨機埠或特定埠。" -"secAlertPanelURI" = "面板預設 URI 路徑不安全。請配置複雜的 URI 路徑。" -"secAlertSubURI" = "訂閱預設 URI 路徑不安全。請配置複雜的 URI 路徑。" -"secAlertSubJsonURI" = "訂閱 JSON 預設 URI 路徑不安全。請配置複雜的 URI 路徑。" - -[menu] -"dashboard" = "系統狀態" -"inbounds" = "入站列表" -"settings" = "面板設定" -"xray" = "Xray 設定" -"logout" = "退出登入" -"link" = "管理" - -[pages.login] -"hello" = "你好" -"title" = "歡迎" -"loginAgain" = "登入時效已過,請重新登入" - -[pages.login.toasts] -"invalidFormData" = "資料格式錯誤" -"emptyUsername" = "請輸入使用者名稱" -"emptyPassword" = "請輸入密碼" -"wrongUsernameOrPassword" = "使用者名稱或密碼錯誤" -"successLogin" = "登入" - -[pages.index] -"title" = "系統狀態" -"memory" = "記憶體" -"hard" = "磁碟" -"xrayStatus" = "Xray" -"stopXray" = "停止" -"restartXray" = "重啟" -"xraySwitch" = "版本" -"xraySwitchClick" = "選擇你要切換到的版本" -"xraySwitchClickDesk" = "請謹慎選擇,因為較舊版本可能與當前配置不相容" -"operationHours" = "系統正常執行時間" -"systemLoad" = "系統負載" -"systemLoadDesc" = "過去 1、5 和 15 分鐘的系統平均負載" -"connectionTcpCountDesc" = "系統中所有 TCP 連線數" -"connectionUdpCountDesc" = "系統中所有 UDP 連線數" -"connectionCount" = "連線數" -"upSpeed" = "總上傳速度" -"downSpeed" = "總下載速度" -"totalSent" = "系統啟動以來傳送的總資料量" -"totalReceive" = "系統啟動以來接收的總資料量" -"xraySwitchVersionDialog" = "切換 Xray 版本" -"xraySwitchVersionDialogDesc" = "是否切換 Xray 版本至" -"dontRefresh" = "安裝中,請勿重新整理此頁面" -"logs" = "日誌" -"config" = "配置" -"backup" = "備份和恢復" -"backupTitle" = "備份和恢復資料庫" -"backupDescription" = "恢復資料庫之前建議進行備份" -"exportDatabase" = "備份" -"importDatabase" = "恢復" - -[pages.inbounds] -"title" = "入站列表" -"totalDownUp" = "總上傳 / 下載" -"totalUsage" = "總用量" -"inboundCount" = "入站數量" -"operate" = "選單" -"enable" = "啟用" -"remark" = "備註" -"protocol" = "協議" -"port" = "埠" -"traffic" = "流量" -"details" = "詳細資訊" -"transportConfig" = "傳輸配置" -"expireDate" = "到期時間" -"resetTraffic" = "重置流量" -"addInbound" = "新增入站" -"generalActions" = "通用操作" -"create" = "新增" -"update" = "修改" -"modifyInbound" = "修改入站" -"deleteInbound" = "刪除入站" -"deleteInboundContent" = "確定要刪除入站嗎?" -"deleteClient" = "刪除客戶端" -"deleteClientContent" = "確定要刪除客戶端嗎?" -"resetTrafficContent" = "確定要重置流量嗎?" -"copyLink" = "複製連結" -"address" = "地址" -"network" = "網路" -"destinationPort" = "目標埠" -"targetAddress" = "目標地址" -"monitorDesc" = "留空表示監聽所有 IP" -"meansNoLimit" = "= 無限制(單位:GB)" -"totalFlow" = "總流量" -"leaveBlankToNeverExpire" = "留空表示永不過期" -"noRecommendKeepDefault" = "建議保留預設值" -"certificatePath" = "檔案路徑" -"certificateContent" = "檔案內容" -"publicKey" = "公鑰" -"privatekey" = "私鑰" -"clickOnQRcode" = "點選二維碼複製" -"client" = "客戶" -"export" = "匯出連結" -"clone" = "複製" -"cloneInbound" = "複製" -"cloneInboundContent" = "此入站規則除埠(Port)、監聽 IP(Listening IP)和客戶端(Clients)以外的所有配置都將應用於克隆" -"cloneInboundOk" = "建立克隆" -"resetAllTraffic" = "重置所有入站流量" -"resetAllTrafficTitle" = "重置所有入站流量" -"resetAllTrafficContent" = "確定要重置所有入站流量嗎?" -"resetInboundClientTraffics" = "重置客戶端流量" -"resetInboundClientTrafficTitle" = "重置所有客戶端流量" -"resetInboundClientTrafficContent" = "確定要重置此入站客戶端的所有流量嗎?" -"resetAllClientTraffics" = "重置所有客戶端流量" -"resetAllClientTrafficTitle" = "重置所有客戶端流量" -"resetAllClientTrafficContent" = "確定要重置所有客戶端的所有流量嗎?" -"delDepletedClients" = "刪除流量耗盡的客戶端" -"delDepletedClientsTitle" = "刪除流量耗盡的客戶端" -"delDepletedClientsContent" = "確定要刪除所有流量耗盡的客戶端嗎?" -"email" = "電子郵件" -"emailDesc" = "電子郵件必須完全唯一" -"IPLimit" = "IP 限制" -"IPLimitDesc" = "如果數量超過設定值,則禁用入站流量。(0 = 禁用)" -"IPLimitlog" = "IP 日誌" -"IPLimitlogDesc" = "IP 歷史日誌(要啟用被禁用的入站流量,請清除日誌)" -"IPLimitlogclear" = "清除日誌" -"setDefaultCert" = "從面板設定證書" -"telegramDesc" = "請提供Telegram聊天ID。(在機器人中使用'/id'命令)或(@userinfobot" -"subscriptionDesc" = "要找到你的訂閱 URL,請導航到“詳細資訊”。此外,你可以為多個客戶端使用相同的名稱。" -"info" = "資訊" -"same" = "相同" -"inboundData" = "入站資料" -"exportInbound" = "匯出入站規則" -"import"="匯入" -"importInbound" = "匯入入站規則" - -[pages.client] -"add" = "新增客戶端" -"edit" = "編輯客戶端" -"submitAdd" = "新增客戶端" -"submitEdit" = "儲存修改" -"clientCount" = "客戶端數量" -"bulk" = "批量建立" -"method" = "方法" -"first" = "置頂" -"last" = "置底" -"prefix" = "字首" -"postfix" = "字尾" -"delayedStart" = "首次使用後開始" -"expireDays" = "期間" -"days" = "天" -"renew" = "自動續訂" -"renewDesc" = "到期後自動續訂。(0 = 禁用)(單位: 天)" - -[pages.inbounds.toasts] -"obtain" = "獲取" - -[pages.inbounds.stream.general] -"request" = "請求" -"response" = "響應" -"name" = "名稱" -"value" = "值" - -[pages.inbounds.stream.tcp] -"version" = "版本" -"method" = "方法" -"path" = "路徑" -"status" = "狀態" -"statusDescription" = "狀態說明" -"requestHeader" = "請求頭" -"responseHeader" = "響應頭" - -[pages.settings] -"title" = "面板設定" -"save" = "儲存" -"infoDesc" = "此處的所有更改都需要儲存並重啟面板才能生效" -"restartPanel" = "重啟面板" -"restartPanelDesc" = "確定要重啟面板嗎?若重啟後無法訪問面板,請前往伺服器檢視面板日誌資訊" -"actions" = "操作" -"resetDefaultConfig" = "重置為預設配置" -"panelSettings" = "常規" -"securitySettings" = "安全設定" -"TGBotSettings" = "Telegram 機器人配置" -"panelListeningIP" = "面板監聽 IP" -"panelListeningIPDesc" = "預設留空監聽所有 IP" -"panelListeningDomain" = "面板監聽域名" -"panelListeningDomainDesc" = "預設情況下留空以監視所有域名和 IP 地址" -"panelPort" = "面板監聽埠" -"panelPortDesc" = "重啟面板生效" -"publicKeyPath" = "面板證書公鑰檔案路徑" -"publicKeyPathDesc" = "填寫一個 '/' 開頭的絕對路徑" -"privateKeyPath" = "面板證書金鑰檔案路徑" -"privateKeyPathDesc" = "填寫一個 '/' 開頭的絕對路徑" -"panelUrlPath" = "面板 url 根路徑" -"panelUrlPathDesc" = "必須以 '/' 開頭,以 '/' 結尾" -"pageSize" = "分頁大小" -"pageSizeDesc" = "定義入站表的頁面大小。設定 0 表示禁用" -"remarkModel" = "備註模型和分隔符" -"datepicker" = "日期選擇器" -"datepickerPlaceholder" = "選擇日期" -"datepickerDescription" = "選擇器日曆類型指定到期日期" -"sampleRemark" = "備註示例" -"oldUsername" = "原使用者名稱" -"currentPassword" = "原密碼" -"newUsername" = "新使用者名稱" -"newPassword" = "新密碼" -"telegramBotEnable" = "啟用 Telegram 機器人" -"telegramBotEnableDesc" = "啟用 Telegram 機器人功能" -"telegramToken" = "Telegram 機器人令牌(token)" -"telegramTokenDesc" = "從 '@BotFather' 獲取的 Telegram 機器人令牌" -"telegramProxy" = "SOCKS5 Proxy" -"telegramProxyDesc" = "啟用 SOCKS5 代理連線到 Telegram(根據指南調整設定)" -"telegramAPIServer" = "Telegram API Server" -"telegramAPIServerDesc" = "要使用的 Telegram API 伺服器。留空以使用預設伺服器。" -"telegramChatId" = "管理員聊天 ID" -"telegramChatIdDesc" = "Telegram 管理員聊天 ID (多個以逗號分隔)(可通過 @userinfobot 獲取,或在機器人中使用 '/id' 命令獲取)" -"telegramNotifyTime" = "通知時間" -"telegramNotifyTimeDesc" = "設定週期性的 Telegram 機器人通知時間(使用 crontab 時間格式)" -"tgNotifyBackup" = "資料庫備份" -"tgNotifyBackupDesc" = "傳送帶有報告的資料庫備份檔案" -"tgNotifyLogin" = "登入通知" -"tgNotifyLoginDesc" = "當有人試圖登入你的面板時顯示使用者名稱、IP 地址和時間" -"sessionMaxAge" = "會話時長" -"sessionMaxAgeDesc" = "保持登入狀態的時長(單位:分鐘)" -"expireTimeDiff" = "到期通知閾值" -"expireTimeDiffDesc" = "達到此閾值時,將收到有關到期時間的通知(單位:天)" -"trafficDiff" = "流量耗盡閾值" -"trafficDiffDesc" = "達到此閾值時,將收到有關流量耗盡的通知(單位:GB)" -"tgNotifyCpu" = "CPU 負載通知閾值" -"tgNotifyCpuDesc" = "CPU 負載超過此閾值時,將收到通知(單位:%)" -"timeZone" = "時區" -"timeZoneDesc" = "定時任務將按照該時區的時間執行" -"subSettings" = "訂閱設定" -"subEnable" = "啟用訂閱服務" -"subEnableDesc" = "啟用訂閱服務功能" -"subListen" = "監聽 IP" -"subListenDesc" = "訂閱服務監聽的 IP 地址(留空表示監聽所有 IP)" -"subPort" = "監聽埠" -"subPortDesc" = "訂閱服務監聽的埠號(必須是未使用的埠)" -"subCertPath" = "公鑰路徑" -"subCertPathDesc" = "訂閱服務使用的公鑰檔案路徑(以 '/' 開頭)" -"subKeyPath" = "私鑰路徑" -"subKeyPathDesc" = "訂閱服務使用的私鑰檔案路徑(以 '/' 開頭)" -"subPath" = "URI 路徑" -"subPathDesc" = "訂閱服務使用的 URI 路徑(以 '/' 開頭,以 '/' 結尾)" -"subDomain" = "監聽域名" -"subDomainDesc" = "訂閱服務監聽的域名(留空表示監聽所有域名和 IP)" -"subUpdates" = "更新間隔" -"subUpdatesDesc" = "客戶端應用中訂閱 URL 的更新間隔(單位:小時)" -"subEncrypt" = "編碼" -"subEncryptDesc" = "訂閱服務返回的內容將採用 Base64 編碼" -"subShowInfo" = "顯示使用資訊" -"subShowInfoDesc" = "客戶端應用中將顯示剩餘流量和日期資訊" -"subURI" = "反向代理 URI" -"subURIDesc" = "用於代理後面的訂閱 URL 的 URI 路徑" -"fragment" = "分片" -"fragmentDesc" = "啟用 TLS hello 資料包分片" -"fragmentSett" = "設定" -"noisesDesc" = "啟用 Noises." -"noisesSett" = "Noises 設定" -"mux" = "多路複用器" -"muxDesc" = "在已建立的資料流內傳輸多個獨立的資料流" -"muxSett" = "複用器設定" -"direct" = "直接連線" -"directDesc" = "直接與特定國家的域或IP範圍建立連線" - - -[pages.xray] -"title" = "Xray 配置" -"save" = "儲存" -"restart" = "重新啟動 Xray" -"basicTemplate" = "基礎配置" -"advancedTemplate" = "高階配置" -"generalConfigs" = "常規配置" -"generalConfigsDesc" = "這些選項將決定常規配置" -"logConfigs" = "日誌" -"logConfigsDesc" = "日誌可能會影響伺服器的效能,建議僅在需要時啟用" -"blockConfigs" = "防護遮蔽" -"blockConfigsDesc" = "這些選項將阻止使用者連線到特定協議和網站" -"basicRouting" = "基本路由" -"blockConnectionsConfigsDesc" = "這些選項將根據特定的請求國家阻止流量。" -"directConnectionsConfigsDesc" = "直接連線確保特定的流量不會通過其他伺服器路由。" -"blockips" = "阻止IP" -"blockdomains" = "阻止域名" -"directips" = "直接IP" -"directdomains" = "直接域名" -"ipv4Routing" = "IPv4 路由" -"ipv4RoutingDesc" = "此選項將僅通過 IPv4 路由到目標域" -"warpRouting" = "WARP 路由" -"warpRoutingDesc" = "注意:在使用這些選項之前,請按照面板 GitHub 上的步驟在你的伺服器上以 socks5 代理模式安裝 WARP。WARP 將通過 Cloudflare 伺服器將流量路由到網站。" -"Template" = "高階 Xray 配置模板" -"TemplateDesc" = "最終的 Xray 配置檔案將基於此模板生成" -"FreedomStrategy" = "Freedom 協議策略" -"FreedomStrategyDesc" = "設定 Freedom 協議中網路的輸出策略" -"RoutingStrategy" = "配置路由域策略" -"RoutingStrategyDesc" = "設定 DNS 解析的整體路由策略" -"Torrent" = "遮蔽 BitTorrent 協議" -"TorrentDesc" = "禁止使用 BitTorrent" -"Family" = "家庭保護" -"FamilyDesc" = "遮蔽成人內容和惡意網站" -"Inbounds" = "入站規則" -"InboundsDesc" = "接受來自特定客戶端的流量" -"Outbounds" = "出站規則" -"Balancers" = "負載均衡" -"OutboundsDesc" = "設定出站流量傳出方式" -"Routings" = "路由規則" -"RoutingsDesc" = "每條規則的優先順序都很重要" -"completeTemplate" = "全部" -"logLevel" = "日誌級別" -"logLevelDesc" = "錯誤日誌的日誌級別,用於指示需要記錄的資訊" -"accessLog" = "訪問日誌" -"accessLogDesc" = "訪問日誌的檔案路徑。特殊值 'none' 禁用訪問日誌" -"errorLog" = "錯誤日誌" -"errorLogDesc" = "錯誤日誌的檔案路徑。特殊值 'none' 禁用錯誤日誌" -"dnsLog" = "DNS 日誌" -"dnsLogDesc" = "是否啟用 DNS 查詢日誌" -"outboundTraffic" = "出站流量" -"outboundTrafficDesc" = "是否啟用出站流量" -"maskAddress" = "隱藏地址" -"maskAddressDesc" = "IP 地址掩碼,啟用時會自動替換日誌中出現的 IP 地址。" - -[pages.xray.rules] -"first" = "置頂" -"last" = "置底" -"up" = "向上" -"down" = "向下" -"source" = "來源" -"dest" = "目的地址" -"inbound" = "入站" -"outbound" = "出站" -"balancer" = "負載均衡" -"info" = "資訊" -"add" = "新增規則" -"edit" = "編輯規則" -"useComma" = "逗號分隔的項目" - -[pages.xray.outbound] -"addOutbound" = "新增出站" -"addReverse" = "新增反向" -"editOutbound" = "編輯出站" -"editReverse" = "編輯反向" -"tag" = "標籤" -"tagDesc" = "唯一標籤" -"address" = "地址" -"reverse" = "反向" -"domain" = "域名" -"type" = "類型" -"bridge" = "Bridge" -"portal" = "Portal" -"intercon" = "互連" -"settings" = "設定" -"accountInfo" = "帳戶資訊" -"outboundStatus" = "出站狀態" -"sendThrough" = "傳送通過" - -[pages.xray.balancer] -"addBalancer" = "新增負載均衡" -"editBalancer" = "編輯負載均衡" -"balancerStrategy" = "策略" -"balancerSelectors" = "選擇器" -"tag" = "標籤" -"tagDesc" = "唯一標籤" -"balancerDesc" = "無法同時使用 balancerTag 和 outboundTag。如果同時使用,則只有 outboundTag 會生效。" - -[pages.xray.wireguard] -"secretKey" = "金鑰" -"publicKey" = "公鑰" -"allowedIPs" = "允許的 IP" -"endpoint" = "端點" -"psk" = "共享金鑰" -"domainStrategy" = "域策略" - -[pages.xray.dns] -"enable" = "啟用 DNS" -"enableDesc" = "啟用內建 DNS 伺服器" -"tag" = "DNS 入站標籤" -"tagDesc" = "此標籤將在路由規則中可用作入站標籤" -"strategy" = "查詢策略" -"strategyDesc" = "解析域名的總體策略" -"add" = "新增伺服器" -"edit" = "編輯伺服器" -"domains" = "域" -"expectIPs" = "預期 IP" - -[pages.xray.fakedns] -"add" = "新增假 DNS" -"edit" = "編輯假 DNS" -"ipPool" = "IP 池子網" -"poolSize" = "池大小" - -[pages.settings.security] -"admin" = "管理員" -"secret" = "安全令牌" -"loginSecurity" = "登入安全" -"loginSecurityDesc" = "新增額外的身份驗證以提高安全性" -"secretToken" = "安全令牌" -"secretTokenDesc" = "請將此令牌儲存在安全的地方。此令牌用於登入,丟失無法恢復。" - -[pages.settings.toasts] -"modifySettings" = "修改設定" -"getSettings" = "獲取設定" -"modifyUser" = "修改管理員" -"originalUserPassIncorrect" = "原使用者名稱或原密碼錯誤" -"userPassMustBeNotEmpty" = "新使用者名稱和新密碼不能為空" - -[tgbot] -"keyboardClosed" = "❌ 自定義鍵盤已關閉!" -"noResult" = "❗ 沒有結果!" -"noQuery" = "❌ 未找到查詢!請重新使用命令!" -"wentWrong" = "❌ 出了點問題!" -"noIpRecord" = "❗ 沒有 IP 記錄!" -"noInbounds" = "❗ 沒有找到入站連線!" -"unlimited" = "♾ 無限制" -"add" = "新增" -"month" = "月" -"months" = "月" -"day" = "天" -"days" = "天" -"hours" = "小時" -"unknown" = "未知" -"inbounds" = "入站連線" -"clients" = "客戶端" -"offline" = "🔴 離線" -"online" = "🟢 線上" - -[tgbot.commands] -"unknown" = "❗ 未知命令" -"pleaseChoose" = "👇 請選擇:\r\n" -"help" = "🤖 歡迎使用本機器人!它旨在為您提供來自伺服器的特定資料,並允許您進行必要的修改。\r\n\r\n" -"start" = "👋 你好,{{ .Firstname }}。\r\n" -"welcome" = "🤖 歡迎來到 {{ .Hostname }} 管理機器人。\r\n" -"status" = "✅ 機器人正常執行!" -"usage" = "❗ 請輸入要搜尋的文字!" -"getID" = "🆔 您的 ID 為:{{ .ID }}" -"helpAdminCommands" = "要重新啟動 Xray Core:\r\n/restart force\r\n\r\n要搜尋客戶電子郵件:\r\n/usage [電子郵件]\r\n\r\n要搜尋入站(帶有客戶統計資料):\r\n/inbound [備註]\r\n\r\nTelegram聊天ID:\r\n/id" -"helpClientCommands" = "要搜尋統計資料,請使用以下命令:\r\n/usage [電子郵件]\r\n\r\nTelegram聊天ID:\r\n/id" -"restartUsage" = "\r\n\r\n/restart force" -"restartSuccess" = "✅ 操作成功!" -"restartFailed" = "❗ 操作錯誤。\r\n\r\n錯誤: {{ .Error }}." -"xrayNotRunning" = "❗ Xray Core 未運行。" - -[tgbot.messages] -"cpuThreshold" = "🔴 CPU 使用率為 {{ .Percent }}%,超過閾值 {{ .Threshold }}%" -"selectUserFailed" = "❌ 使用者選擇錯誤!" -"userSaved" = "✅ 電報使用者已儲存。" -"loginSuccess" = "✅ 成功登入到面板。\r\n" -"loginFailed" = "❗️ 面板登入失敗。\r\n" -"report" = "🕰 定時報告:{{ .RunTime }}\r\n" -"datetime" = "⏰ 日期時間:{{ .DateTime }}\r\n" -"hostname" = "💻 主機名:{{ .Hostname }}\r\n" -"version" = "🚀 X-UI 版本:{{ .Version }}\r\n" -"xrayVersion" = "📡 Xray 版本: {{ .XrayVersion }}\r\n" -"ipv6" = "🌐 IPv6:{{ .IPv6 }}\r\n" -"ipv4" = "🌐 IPv4:{{ .IPv4 }}\r\n" -"ip" = "🌐 IP:{{ .IP }}\r\n" -"ips" = "🔢 IP 地址:\r\n{{ .IPs }}\r\n" -"serverUpTime" = "⏳ 伺服器執行時間:{{ .UpTime }} {{ .Unit }}\r\n" -"serverLoad" = "📈 伺服器負載:{{ .Load1 }}, {{ .Load2 }}, {{ .Load3 }}\r\n" -"serverMemory" = "📋 伺服器記憶體:{{ .Current }}/{{ .Total }}\r\n" -"tcpCount" = "🔹 TCP 連線數:{{ .Count }}\r\n" -"udpCount" = "🔸 UDP 連線數:{{ .Count }}\r\n" -"traffic" = "🚦 流量:{{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n" -"xrayStatus" = "ℹ️ Xray 狀態:{{ .State }}\r\n" -"username" = "👤 使用者名稱:{{ .Username }}\r\n" -"password" = "👤 密碼: {{ .Password }}\r\n" -"time" = "⏰ 時間:{{ .Time }}\r\n" -"inbound" = "📍 入站:{{ .Remark }}\r\n" -"port" = "🔌 埠:{{ .Port }}\r\n" -"expire" = "📅 過期日期:{{ .Time }}\r\n" -"expireIn" = "📅 剩餘時間:{{ .Time }}\r\n" -"active" = "💡 啟用:{{ .Enable }}\r\n" -"enabled" = "🚨 已啟用:{{ .Enable }}\r\n" -"online" = "🌐 連線狀態:{{ .Status }}\r\n" -"email" = "📧 郵箱:{{ .Email }}\r\n" -"upload" = "🔼 上傳↑:{{ .Upload }}\r\n" -"download" = "🔽 下載↓:{{ .Download }}\r\n" -"total" = "📊 總計:{{ .UpDown }} / {{ .Total }}\r\n" -"TGUser" = "👤 電報使用者:{{ .TelegramID }}\r\n" -"exhaustedMsg" = "🚨 耗盡的 {{ .Type }}:\r\n" -"exhaustedCount" = "🚨 耗盡的 {{ .Type }} 數量:\r\n" -"onlinesCount" = "🌐 線上客戶:{{ .Count }}\r\n" -"disabled" = "🛑 禁用:{{ .Disabled }}\r\n" -"depleteSoon" = "🔜 即將耗盡:{{ .Deplete }}\r\n\r\n" -"backupTime" = "🗄 備份時間:{{ .Time }}\r\n" -"refreshedOn" = "\r\n📋🔄 重新整理時間:{{ .Time }}\r\n\r\n" -"yes" = "✅ 是的" -"no" = "❌ 沒有" - -[tgbot.buttons] -"closeKeyboard" = "❌ 關閉鍵盤" -"cancel" = "❌ 取消" -"cancelReset" = "❌ 取消重置" -"cancelIpLimit" = "❌ 取消 IP 限制" -"confirmResetTraffic" = "✅ 確認重置流量?" -"confirmClearIps" = "✅ 確認清除 IP?" -"confirmRemoveTGUser" = "✅ 確認移除 Telegram 使用者?" -"confirmToggle" = "✅ 確認啟用/禁用使用者?" -"dbBackup" = "獲取資料庫備份" -"serverUsage" = "伺服器使用情況" -"getInbounds" = "獲取入站資訊" -"depleteSoon" = "即將耗盡" -"clientUsage" = "獲取使用情況" -"onlines" = "線上客戶端" -"commands" = "命令" -"refresh" = "🔄 重新整理" -"clearIPs" = "❌ 清除 IP" -"removeTGUser" = "❌ 移除 Telegram 使用者" -"selectTGUser" = "👤 選擇 Telegram 使用者" -"selectOneTGUser" = "👤 選擇一個 Telegram 使用者:" -"resetTraffic" = "📈 重置流量" -"resetExpire" = "📅 更改到期日期" -"ipLog" = "🔢 IP 日誌" -"ipLimit" = "🔢 IP 限制" -"setTGUser" = "👤 設定 Telegram 使用者" -"toggle" = "🔘 啟用/禁用" -"custom" = "🔢 風俗" -"confirmNumber" = "✅ 確認: {{ .Num }}" -"confirmNumberAdd" = "✅ 確認新增:{{ .Num }}" -"limitTraffic" = "🚧 流量限制" -"getBanLogs" = "禁止日誌" -"allClients" = "所有客戶" - -[tgbot.answers] -"successfulOperation" = "✅ 成功!" -"errorOperation" = "❗ 操作錯誤。" -"getInboundsFailed" = "❌ 獲取入站資訊失敗。" -"getClientsFailed" = "❌ 獲取客戶失敗。" -"canceled" = "❌ {{ .Email }}:操作已取消。" -"clientRefreshSuccess" = "✅ {{ .Email }}:客戶端重新整理成功。" -"IpRefreshSuccess" = "✅ {{ .Email }}:IP 重新整理成功。" -"TGIdRefreshSuccess" = "✅ {{ .Email }}:客戶端的 Telegram 使用者重新整理成功。" -"resetTrafficSuccess" = "✅ {{ .Email }}:流量已重置成功。" -"setTrafficLimitSuccess" = "✅ {{ .Email }}: 流量限制儲存成功。" -"expireResetSuccess" = "✅ {{ .Email }}:過期天數已重置成功。" -"resetIpSuccess" = "✅ {{ .Email }}:成功儲存 IP 限制數量為 {{ .Count }}。" -"clearIpSuccess" = "✅ {{ .Email }}:IP 已成功清除。" -"getIpLog" = "✅ {{ .Email }}:獲取 IP 日誌。" -"getUserInfo" = "✅ {{ .Email }}:獲取 Telegram 使用者資訊。" -"removedTGUserSuccess" = "✅ {{ .Email }}:Telegram 使用者已成功移除。" -"enableSuccess" = "✅ {{ .Email }}:已成功啟用。" -"disableSuccess" = "✅ {{ .Email }}:已成功禁用。" -"askToAddUserId" = "未找到您的配置!\r\n請向管理員詢問,在您的配置中使用您的 Telegram 使用者 ChatID。\r\n\r\n您的使用者 ChatID:{{ .TgUserID }}" -"chooseClient" = "為入站 {{ .Inbound }} 選擇一個客戶" -"chooseInbound" = "選擇一個入站" diff --git a/web/web.go b/web/web.go deleted file mode 100644 index 35ccec70..00000000 --- a/web/web.go +++ /dev/null @@ -1,402 +0,0 @@ -package web - -import ( - "context" - "crypto/tls" - "embed" - "html/template" - "io" - "io/fs" - "net" - "net/http" - "os" - "strconv" - "strings" - "time" - - "x-ui/config" - "x-ui/logger" - "x-ui/util/common" - "x-ui/web/controller" - "x-ui/web/job" - "x-ui/web/locale" - "x-ui/web/middleware" - "x-ui/web/network" - "x-ui/web/service" - - "github.com/gin-contrib/gzip" - "github.com/gin-contrib/sessions" - "github.com/gin-contrib/sessions/cookie" - "github.com/gin-gonic/gin" - "github.com/robfig/cron/v3" -) - -//go:embed assets/* -var assetsFS embed.FS - -//go:embed html/* -var htmlFS embed.FS - -//go:embed translation/* -var i18nFS embed.FS - -var startTime = time.Now() - -type wrapAssetsFS struct { - embed.FS -} - -func (f *wrapAssetsFS) Open(name string) (fs.File, error) { - file, err := f.FS.Open("assets/" + name) - if err != nil { - return nil, err - } - return &wrapAssetsFile{ - File: file, - }, nil -} - -type wrapAssetsFile struct { - fs.File -} - -func (f *wrapAssetsFile) Stat() (fs.FileInfo, error) { - info, err := f.File.Stat() - if err != nil { - return nil, err - } - return &wrapAssetsFileInfo{ - FileInfo: info, - }, nil -} - -type wrapAssetsFileInfo struct { - fs.FileInfo -} - -func (f *wrapAssetsFileInfo) ModTime() time.Time { - return startTime -} - -type Server struct { - httpServer *http.Server - listener net.Listener - - index *controller.IndexController - server *controller.ServerController - panel *controller.XUIController - api *controller.APIController - - xrayService service.XrayService - settingService service.SettingService - tgbotService service.Tgbot - - cron *cron.Cron - - ctx context.Context - cancel context.CancelFunc -} - -func NewServer() *Server { - ctx, cancel := context.WithCancel(context.Background()) - return &Server{ - ctx: ctx, - cancel: cancel, - } -} - -func (s *Server) getHtmlFiles() ([]string, error) { - files := make([]string, 0) - dir, _ := os.Getwd() - err := fs.WalkDir(os.DirFS(dir), "web/html", func(path string, d fs.DirEntry, err error) error { - if err != nil { - return err - } - if d.IsDir() { - return nil - } - files = append(files, path) - return nil - }) - if err != nil { - return nil, err - } - return files, nil -} - -func (s *Server) getHtmlTemplate(funcMap template.FuncMap) (*template.Template, error) { - t := template.New("").Funcs(funcMap) - err := fs.WalkDir(htmlFS, "html", func(path string, d fs.DirEntry, err error) error { - if err != nil { - return err - } - - if d.IsDir() { - newT, err := t.ParseFS(htmlFS, path+"/*.html") - if err != nil { - // ignore - return nil - } - t = newT - } - return nil - }) - if err != nil { - return nil, err - } - return t, nil -} - -func (s *Server) initRouter() (*gin.Engine, error) { - if config.IsDebug() { - gin.SetMode(gin.DebugMode) - } else { - gin.DefaultWriter = io.Discard - gin.DefaultErrorWriter = io.Discard - gin.SetMode(gin.ReleaseMode) - } - - engine := gin.Default() - - webDomain, err := s.settingService.GetWebDomain() - if err != nil { - return nil, err - } - - if webDomain != "" { - engine.Use(middleware.DomainValidatorMiddleware(webDomain)) - } - - secret, err := s.settingService.GetSecret() - if err != nil { - return nil, err - } - - basePath, err := s.settingService.GetBasePath() - if err != nil { - return nil, err - } - engine.Use(gzip.Gzip(gzip.DefaultCompression, gzip.WithExcludedPaths([]string{basePath + "panel/API/"}))) - assetsBasePath := basePath + "assets/" - - store := cookie.NewStore(secret) - engine.Use(sessions.Sessions("3x-ui", store)) - engine.Use(func(c *gin.Context) { - c.Set("base_path", basePath) - }) - engine.Use(func(c *gin.Context) { - uri := c.Request.RequestURI - if strings.HasPrefix(uri, assetsBasePath) { - c.Header("Cache-Control", "max-age=31536000") - } - }) - - // init i18n - err = locale.InitLocalizer(i18nFS, &s.settingService) - if err != nil { - return nil, err - } - - // Apply locale middleware for i18n - i18nWebFunc := func(key string, params ...string) string { - return locale.I18n(locale.Web, key, params...) - } - engine.FuncMap["i18n"] = i18nWebFunc - engine.Use(locale.LocalizerMiddleware()) - - // set static files and template - if config.IsDebug() { - // for development - files, err := s.getHtmlFiles() - if err != nil { - return nil, err - } - engine.LoadHTMLFiles(files...) - engine.StaticFS(basePath+"assets", http.FS(os.DirFS("web/assets"))) - } else { - // for production - template, err := s.getHtmlTemplate(engine.FuncMap) - if err != nil { - return nil, err - } - engine.SetHTMLTemplate(template) - engine.StaticFS(basePath+"assets", http.FS(&wrapAssetsFS{FS: assetsFS})) - } - - // Apply the redirect middleware (`/xui` to `/panel`) - engine.Use(middleware.RedirectMiddleware(basePath)) - - g := engine.Group(basePath) - - s.index = controller.NewIndexController(g) - s.server = controller.NewServerController(g) - s.panel = controller.NewXUIController(g) - s.api = controller.NewAPIController(g) - - return engine, nil -} - -func (s *Server) startTask() { - err := s.xrayService.RestartXray(true) - if err != nil { - logger.Warning("start xray failed:", err) - } - // Check whether xray is running every second - s.cron.AddJob("@every 1s", job.NewCheckXrayRunningJob()) - - // Check if xray needs to be restarted every 30 seconds - s.cron.AddFunc("@every 30s", func() { - if s.xrayService.IsNeedRestartAndSetFalse() { - err := s.xrayService.RestartXray(false) - if err != nil { - logger.Error("restart xray failed:", err) - } - } - }) - - go func() { - time.Sleep(time.Second * 5) - // Statistics every 10 seconds, start the delay for 5 seconds for the first time, and staggered with the time to restart xray - s.cron.AddJob("@every 10s", job.NewXrayTrafficJob()) - }() - - // check client ips from log file every 10 sec - s.cron.AddJob("@every 10s", job.NewCheckClientIpJob()) - - // check client ips from log file every day - s.cron.AddJob("@daily", job.NewClearLogsJob()) - - // Make a traffic condition every day, 8:30 - var entry cron.EntryID - isTgbotenabled, err := s.settingService.GetTgbotEnabled() - if (err == nil) && (isTgbotenabled) { - runtime, err := s.settingService.GetTgbotRuntime() - if err != nil || runtime == "" { - logger.Errorf("Add NewStatsNotifyJob error[%s], Runtime[%s] invalid, will run default", err, runtime) - runtime = "@daily" - } - logger.Infof("Tg notify enabled,run at %s", runtime) - _, err = s.cron.AddJob(runtime, job.NewStatsNotifyJob()) - if err != nil { - logger.Warning("Add NewStatsNotifyJob error", err) - return - } - - // check for Telegram bot callback query hash storage reset - s.cron.AddJob("@every 2m", job.NewCheckHashStorageJob()) - - // Check CPU load and alarm to TgBot if threshold passes - cpuThreshold, err := s.settingService.GetTgCpu() - if (err == nil) && (cpuThreshold > 0) { - s.cron.AddJob("@every 10s", job.NewCheckCpuJob()) - } - } else { - s.cron.Remove(entry) - } -} - -func (s *Server) Start() (err error) { - // This is an anonymous function, no function name - defer func() { - if err != nil { - s.Stop() - } - }() - - loc, err := s.settingService.GetTimeLocation() - if err != nil { - return err - } - s.cron = cron.New(cron.WithLocation(loc), cron.WithSeconds()) - s.cron.Start() - - engine, err := s.initRouter() - if err != nil { - return err - } - - certFile, err := s.settingService.GetCertFile() - if err != nil { - return err - } - keyFile, err := s.settingService.GetKeyFile() - if err != nil { - return err - } - listen, err := s.settingService.GetListen() - if err != nil { - return err - } - port, err := s.settingService.GetPort() - if err != nil { - return err - } - listenAddr := net.JoinHostPort(listen, strconv.Itoa(port)) - listener, err := net.Listen("tcp", listenAddr) - if err != nil { - return err - } - if certFile != "" || keyFile != "" { - cert, err := tls.LoadX509KeyPair(certFile, keyFile) - if err == nil { - c := &tls.Config{ - Certificates: []tls.Certificate{cert}, - } - listener = network.NewAutoHttpsListener(listener) - listener = tls.NewListener(listener, c) - logger.Info("Web server running HTTPS on", listener.Addr()) - } else { - logger.Error("Error loading certificates:", err) - logger.Info("Web server running HTTP on", listener.Addr()) - } - } else { - logger.Info("Web server running HTTP on", listener.Addr()) - } - s.listener = listener - - s.httpServer = &http.Server{ - Handler: engine, - } - - go func() { - s.httpServer.Serve(listener) - }() - - s.startTask() - - isTgbotenabled, err := s.settingService.GetTgbotEnabled() - if (err == nil) && (isTgbotenabled) { - tgBot := s.tgbotService.NewTgbot() - tgBot.Start(i18nFS) - } - - return nil -} - -func (s *Server) Stop() error { - s.cancel() - s.xrayService.StopXray() - if s.cron != nil { - s.cron.Stop() - } - if s.tgbotService.IsRunning() { - s.tgbotService.Stop() - } - var err1 error - var err2 error - if s.httpServer != nil { - err1 = s.httpServer.Shutdown(s.ctx) - } - if s.listener != nil { - err2 = s.listener.Close() - } - return common.Combine(err1, err2) -} - -func (s *Server) GetCtx() context.Context { - return s.ctx -} - -func (s *Server) GetCron() *cron.Cron { - return s.cron -}