Feat: Optimize global settings (#554)

diff --git a/src/assets/lang/en.ts b/src/assets/lang/en.ts
index f29b106..38e570a 100644
--- a/src/assets/lang/en.ts
+++ b/src/assets/lang/en.ts
@@ -181,7 +181,7 @@
   logCategory: 'Log Category',
   errorCatalog: 'Error Catalog',
   logDetail: 'Log Detail ',
-  timeReload: 'The time interval must be greater than 0',
+  timeReload: 'Notice: The time interval must be greater than 0',
   errorInfo: 'Error Info',
   stack: 'Stack',
   serviceVersion: 'Service Version',
@@ -258,6 +258,7 @@
   yes: 'Yes',
   no: 'No',
   cacheReminderContent: 'SkyWalking detected dashboard template updates, do you want to update?',
+  language: 'Language',
 };
 
 export default m;
diff --git a/src/assets/lang/zh.ts b/src/assets/lang/zh.ts
index 3000465..684a256 100644
--- a/src/assets/lang/zh.ts
+++ b/src/assets/lang/zh.ts
@@ -180,7 +180,7 @@
   logCategory: '日志类别',
   errorCatalog: '错误类目',
   logDetail: '日志详情',
-  timeReload: '时间间隔必须大于0',
+  timeReload: '注意:时间间隔必须大于0',
   errorInfo: '错误信息',
   stack: '堆栈',
   serviceVersion: '服务版本',
@@ -256,6 +256,7 @@
   yes: '是的',
   no: '不',
   cacheReminderContent: 'SkyWalking检测到仪表板模板更新,是否需要更新?',
+  language: '语言',
 };
 
 export default m;
diff --git a/src/components/index.ts b/src/components/index.ts
index 7918f5a..04a0933 100644
--- a/src/components/index.ts
+++ b/src/components/index.ts
@@ -32,6 +32,7 @@
 import RkIcon from './rk-icon.vue';
 import RkRadio from './rk-radio.vue';
 import RkAlert from './rk-alert.vue';
+import RkSwitch from './rk-switch.vue';
 
 const components: any = {
   RkProgress,
@@ -50,6 +51,7 @@
   RkIcon,
   RkRadio,
   RkAlert,
+  RkSwitch,
 };
 
 const componentsName: string[] = Object.keys(components);
diff --git a/src/components/rk-date.vue b/src/components/rk-date.vue
index b4f5a79..20b0978 100755
--- a/src/components/rk-date.vue
+++ b/src/components/rk-date.vue
@@ -436,7 +436,7 @@
   }
 
   .datepicker-range {
-    min-width: 280px;
+    min-width: 238px;
   }
 
   .datepicker-range .datepicker-popup {
diff --git a/src/components/rk-footer-time.vue b/src/components/rk-footer-time.vue
index b16a679..8f713f0 100644
--- a/src/components/rk-footer-time.vue
+++ b/src/components/rk-footer-time.vue
@@ -15,7 +15,7 @@
 <template>
   <div>
     <span class="rk-time-tips" v-show="timeRange">{{ $t('timeTips') }}</span>
-    <RkDate class="mr-10" v-model="time" position="top" format="YYYY-MM-DD HH:mm" />
+    <RkDate class="mr-10" v-model="time" position="bottom" format="YYYY-MM-DD HH:mm" />
   </div>
 </template>
 
diff --git a/src/components/rk-sidebox.vue b/src/components/rk-sidebox.vue
index 7cc1660..2e0e41a 100644
--- a/src/components/rk-sidebox.vue
+++ b/src/components/rk-sidebox.vue
@@ -82,12 +82,12 @@
     position: fixed;
     right: 0;
     top: 50px;
-    bottom: 30px;
+    bottom: 0;
     z-index: 200;
     background-color: #fff;
   }
   .rk-sidebox-inner {
-    padding: 20px;
+    padding: 0 20px;
   }
   .rk-sidebox-inner-fixed {
     height: 100%;
diff --git a/src/components/rk-switch.vue b/src/components/rk-switch.vue
new file mode 100644
index 0000000..ee70857
--- /dev/null
+++ b/src/components/rk-switch.vue
@@ -0,0 +1,86 @@
+<!-- Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License. -->
+
+<template>
+  <button type="button" class="rk-switch" :class="checked ? 'switch-checked' : ''" @click="changeStatus">
+    <div class="switch-handle"></div>
+    <span class="switch-inner"></span>
+  </button>
+</template>
+
+<script lang="ts">
+  import { Vue, Component, Prop } from 'vue-property-decorator';
+  @Component
+  export default class RkSwitch extends Vue {
+    @Prop() private checked!: boolean;
+
+    private changeStatus() {
+      this.$emit('onChange', !this.checked);
+    }
+  }
+</script>
+<style lang="scss" scoped>
+  .rk-switch {
+    padding: 0;
+    color: #000000d9;
+    font-size: 14px;
+    line-height: 1.5715;
+    position: relative;
+    display: inline-block;
+    min-width: 44px;
+    height: 22px;
+    line-height: 22px;
+    background-color: #00000040;
+    border: 0;
+    border-radius: 100px;
+    cursor: pointer;
+    transition: all 0.2s;
+  }
+  .switch-checked {
+    background-color: #448dfe;
+  }
+  .switch-inner {
+    margin: 0 25px 0 7px;
+    display: block;
+    margin: 0 7px 0 25px;
+    color: #fff;
+    font-size: 12px;
+    transition: all 0.2s;
+  }
+  .switch-handle {
+    position: absolute;
+    top: 2px;
+    left: 2px;
+    width: 18px;
+    height: 18px;
+    transition: all 0.2s ease-in-out;
+    &:before {
+      position: absolute;
+      top: 0;
+      right: 0;
+      bottom: 0;
+      left: 0;
+      background-color: #fff;
+      border-radius: 9px;
+      box-shadow: 0 2px 4px #00230b33;
+      transition: all 0.2s ease-in-out;
+      content: '';
+    }
+  }
+  .switch-checked .switch-handle {
+    left: calc(100% - 20px);
+    transition: all 0.2s;
+  }
+</style>
diff --git a/src/views/components/common/page-tool-bar.vue b/src/views/components/common/page-tool-bar.vue
new file mode 100644
index 0000000..ce61f51
--- /dev/null
+++ b/src/views/components/common/page-tool-bar.vue
@@ -0,0 +1,240 @@
+<!-- Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License. -->
+<template>
+  <div class="sm flex-h page-tools">
+    <RkFooterTime />
+    <!-- <span class="mr-10 cp" @click="openSettings">{{ lang === 'zh' ? '中' : 'En' }}</span> -->
+    <span class="mr-10 cp" @click="openSettings">
+      UTC{{ utcHour >= 0 ? '+' : '' }}{{ `${this.utcHour}:${this.utcMin}` }}
+    </span>
+    <span class="mr-10 sm" :class="auto ? 'blue' : 'ghost'" @click="openSettings">
+      <a>{{ $t('auto') }}</a>
+    </span>
+    <a class="sm ghost" @click="handleReload">
+      <rk-icon icon="retry" :loading="auto" />
+      <span>{{ $t('reload') }}</span>
+    </a>
+    <div class="tool-bar-setting " v-show="showSetting">
+      <div class="flex-h item">
+        <span class="label">{{ $t('language') }}</span>
+        <span>Zh</span>
+        <rk-switch class="mr-5 ml-5" :checked="lang === 'en'" @onChange="setLang" />
+        <span>En</span>
+      </div>
+      <div class="flex-h item">
+        <span class="label">{{ $t('serverZone') }}</span>
+        <div>
+          <span>UTC{{ utcHour >= 0 ? '+' : '' }}</span>
+          <input v-model="utcHour" min="-12" max="14" class="rk-utc" type="number" />
+          <span>:</span>
+          <span class="utc-min">{{ utcMin > 9 || utcMin === 0 ? null : 0 }}</span>
+          <input v-model="utcMin" min="0" max="59" class="rk-utc" type="number" />
+        </div>
+      </div>
+      <div class="flex-h item">
+        <span class="label">{{ $t('auto') }}</span>
+        <rk-switch class="mr-10" :checked="auto" @onChange="handleAuto" />
+        <div class="auto-time">
+          <span class="rk-auto-select">
+            <input v-model="autoTime" type="number" @change="changeAutoTime" min="1" />
+          </span>
+          {{ $t('second') }}
+          <i class="ml-10">{{ $t('timeReload') }}</i>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script lang="ts">
+  import { Vue, Component, Watch } from 'vue-property-decorator';
+  import { State, Action, Getter } from 'vuex-class';
+  import timeFormat from '@/utils/timeFormat';
+
+  @Component
+  export default class PageToolBar extends Vue {
+    @Getter('duration') private duration: any;
+    @State('rocketbot') private rocketbotGlobal: any;
+    @Action('SET_DURATION') private SET_DURATION: any;
+    @Action('SET_UTC') private SET_UTC: any;
+    private lang: string | null = '';
+    private utcHour: number = 0;
+    private utcMin: number = 0;
+    private show: boolean = false;
+    private auto: boolean = false;
+    private autoTime: number = 6;
+    private timer: any = null;
+    private showSetting: boolean = false;
+
+    private beforeMount() {
+      let utc = localStorage.getItem('utc') || '';
+      if (!utc.includes(':')) {
+        utc = (localStorage.getItem('utc') || -(new Date().getTimezoneOffset() / 60)) + ':0';
+      }
+      const utcArr = (utc || '').split(':');
+      this.utcHour = isNaN(Number(utcArr[0])) ? 0 : Number(utcArr[0]);
+      this.utcMin = isNaN(Number(utcArr[1])) ? 0 : Number(utcArr[1]);
+      this.SET_UTC(`${this.utcHour}:${this.utcMin}`);
+      this.lang = window.localStorage.getItem('lang');
+    }
+
+    private mounted() {
+      document.addEventListener('click', this.closeSettings, true);
+    }
+
+    private openSettings() {
+      this.showSetting = true;
+    }
+
+    private setLang() {
+      if (this.lang === 'zh') {
+        this.$i18n.locale = 'en';
+        window.localStorage.setItem('lang', 'en');
+        this.lang = 'en';
+      } else {
+        this.$i18n.locale = 'zh';
+        window.localStorage.setItem('lang', 'zh');
+        this.lang = 'zh';
+      }
+    }
+
+    private handleReload() {
+      const gap = this.duration.end.getTime() - this.duration.start.getTime();
+      const time: Date[] = [new Date(new Date().getTime() - gap), new Date()];
+      this.SET_DURATION(timeFormat(time));
+    }
+    private handleAuto(status: boolean) {
+      if (this.autoTime < 1) {
+        return;
+      }
+      this.auto = status;
+      if (this.auto) {
+        this.handleReload();
+        this.timer = setInterval(this.handleReload, this.autoTime * 1000);
+      } else {
+        clearInterval(this.timer);
+      }
+    }
+    private handleHide() {
+      this.show = false;
+    }
+    private handleShow() {
+      this.show = !this.show;
+    }
+    private changeAutoTime() {
+      if (this.autoTime < 1) {
+        return;
+      }
+      clearInterval(this.timer);
+      if (this.auto) {
+        this.handleReload();
+        this.timer = setInterval(this.handleReload, this.autoTime * 1000);
+      }
+    }
+
+    private closeSettings(e: any) {
+      this.showSetting = this.$el.contains(e.target) && !this.showSetting;
+    }
+
+    @Watch('utcHour')
+    private onUtcUpdate() {
+      if (this.utcHour < -12) {
+        this.utcHour = -12;
+      }
+      if (this.utcHour > 14) {
+        this.utcHour = 14;
+      }
+      if (isNaN(this.utcHour)) {
+        this.utcHour = 0;
+      }
+      this.SET_UTC(`${this.utcHour}:${this.utcMin}`);
+      localStorage.setItem('utc', `${this.utcHour}:${this.utcMin}`);
+    }
+
+    @Watch('utcMin')
+    private onUtcMinUpdate() {
+      if (this.utcMin < 0) {
+        this.utcMin = 0;
+      }
+      if (this.utcMin > 59) {
+        this.utcMin = 59;
+      }
+      if (isNaN(this.utcMin)) {
+        this.utcMin = 0;
+      }
+      this.SET_UTC(`${this.utcHour}:${this.utcMin}`);
+      localStorage.setItem('utc', `${this.utcHour}:${this.utcMin}`);
+    }
+  }
+</script>
+
+<style lang="scss" scoped>
+  .page-tools {
+    position: relative;
+  }
+  .rk-utc {
+    color: inherit;
+    background: 0;
+    border: 0;
+    outline: none;
+    width: 40px;
+    padding-bottom: 0;
+  }
+  .utc-min {
+    display: inline-block;
+    padding-top: 2px;
+  }
+  .rk-auto-select {
+    border-radius: 3px;
+    background-color: #fff;
+    padding: 1px;
+    border-radius: 3px;
+
+    input {
+      width: 38px;
+      border-style: unset;
+      outline: 0;
+    }
+  }
+  .tool-bar-setting {
+    position: absolute;
+    top: 30px;
+    right: 0;
+    color: #666;
+    font-family: inherit;
+    font-size: 12px;
+    background: #fff;
+    z-index: 10000;
+    padding: 20px;
+    border-radius: 3px;
+    box-shadow: 0 2px 4px #00230b33;
+    width: 550px;
+    .item {
+      margin-top: 10px;
+    }
+    input {
+      outline: 0;
+      width: 40px;
+      border-radius: 3px;
+      border: 1px solid #ccc;
+      text-align: center;
+      height: 22px;
+    }
+    .label {
+      width: 100px;
+      display: inline-block;
+    }
+  }
+</style>
diff --git a/src/views/components/common/rk-footer.vue b/src/views/components/common/rk-footer.vue
deleted file mode 100644
index 6448b51..0000000
--- a/src/views/components/common/rk-footer.vue
+++ /dev/null
@@ -1,137 +0,0 @@
-<!-- Licensed to the Apache Software Foundation (ASF) under one or more
-contributor license agreements.  See the NOTICE file distributed with
-this work for additional information regarding copyright ownership.
-The ASF licenses this file to You under the Apache License, Version 2.0
-(the "License"); you may not use this file except in compliance with
-the License.  You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License. -->
-<template>
-  <footer class="rk-footer trans" :class="{ 'rk-footer-dark': $route.path === '/topology' }">
-    <div class="rk-footer-inner">
-      <div class="flex-h"></div>
-      <div class="sm flex-h">
-        <RkFooterTime />
-        <span class="mr-15 cp" @click="setLang">{{ lang === 'zh' ? '中' : 'En' }}</span>
-        <span>{{ $t('serverZone') }} UTC {{ utcHour >= 0 ? '+' : '' }}</span>
-        <input v-model="utcHour" min="-12" max="14" class="rk-footer-utc" type="number" />
-        <span>:&nbsp;</span>
-        <span class="utc-min">{{ utcMin > 9 ? null : 0 }}</span>
-        <input v-model="utcMin" min="0" max="59" class="rk-footer-utc" type="number" />
-      </div>
-    </div>
-  </footer>
-</template>
-
-<script lang="ts">
-  import { Vue, Component, Watch } from 'vue-property-decorator';
-  import { State, Action } from 'vuex-class';
-
-  @Component
-  export default class Footerssd extends Vue {
-    @State('rocketbot') private rocketbotGlobal: any;
-    @Action('SET_DURATION') private SET_DURATION: any;
-    @Action('SET_UTC') private SET_UTC: any;
-    private lang: string | null = '';
-    private utcHour: number = 0;
-    private utcMin: number = 0;
-
-    private beforeMount() {
-      let utc = localStorage.getItem('utc') || '';
-      if (!utc.includes(':')) {
-        utc = (localStorage.getItem('utc') || -(new Date().getTimezoneOffset() / 60)) + ':0';
-      }
-      const utcArr = (utc || '').split(':');
-      this.utcHour = isNaN(Number(utcArr[0])) ? 0 : Number(utcArr[0]);
-      this.utcMin = isNaN(Number(utcArr[1])) ? 0 : Number(utcArr[1]);
-      this.SET_UTC(`${this.utcHour}:${this.utcMin}`);
-      this.lang = window.localStorage.getItem('lang');
-    }
-
-    private setLang() {
-      if (this.lang === 'zh') {
-        this.$i18n.locale = 'en';
-        window.localStorage.setItem('lang', 'en');
-        this.lang = 'en';
-      } else {
-        this.$i18n.locale = 'zh';
-        window.localStorage.setItem('lang', 'zh');
-        this.lang = 'zh';
-      }
-    }
-
-    @Watch('utcHour')
-    private onUtcUpdate() {
-      if (this.utcHour < -12) {
-        this.utcHour = -12;
-      }
-      if (this.utcHour > 14) {
-        this.utcHour = 14;
-      }
-      if (isNaN(this.utcHour)) {
-        this.utcHour = 0;
-      }
-      this.SET_UTC(`${this.utcHour}:${this.utcMin}`);
-      localStorage.setItem('utc', `${this.utcHour}:${this.utcMin}`);
-    }
-
-    @Watch('utcMin')
-    private onUtcMinUpdate() {
-      if (this.utcMin < 0) {
-        this.utcMin = 0;
-      }
-      if (this.utcMin > 59) {
-        this.utcMin = 59;
-      }
-      if (isNaN(this.utcMin)) {
-        this.utcMin = 0;
-      }
-      this.SET_UTC(`${this.utcHour}:${this.utcMin}`);
-      localStorage.setItem('utc', `${this.utcHour}:${this.utcMin}`);
-    }
-  }
-</script>
-
-<style scoped>
-  .rk-footer {
-    color: #515a6e;
-    flex-shrink: 0;
-    padding-right: 15px;
-    padding-left: 15px;
-    padding-bottom: 1px;
-    box-shadow: 0 -1px 0px rgba(0, 0, 0, 0.08);
-    z-index: 2;
-  }
-  .rk-footer-dark {
-    color: #ddd;
-    background: #252a2f;
-    border-top: 1px solid #252a2f;
-  }
-  .rk-footer-edit {
-    color: #eee;
-    background: #448dfe;
-    border-top: 1px solid #448dfe;
-  }
-  .rk-footer-utc {
-    color: inherit;
-    background: 0;
-    border: 0;
-    outline: none;
-    width: 40px;
-    padding-bottom: 0;
-  }
-  .rk-footer-inner {
-    justify-content: space-between;
-    display: flex;
-  }
-  .utc-min {
-    display: inline-block;
-    padding-top: 2px;
-  }
-</style>
diff --git a/src/views/components/common/rk-header.vue b/src/views/components/common/rk-header.vue
index 1b784fe..476bb19 100644
--- a/src/views/components/common/rk-header.vue
+++ b/src/views/components/common/rk-header.vue
@@ -30,85 +30,29 @@
         <span class="vm hide-xs ml-5">{{ $t(menu.meta.title) }}</span>
       </router-link>
     </div>
-    <div class="flex-h">
-      <a
-        class="rk-btn mr-5 sm"
-        :class="auto ? 'blue' : 'ghost'"
-        @click="handleAuto"
-        v-tooltip:bottom="{ content: $t('timeReload') }"
-      >
-        <span class="vm">{{ $t('auto') }}</span>
-      </a>
-      <div class="auto-time">
-        <span class="rk-auto-select">
-          <input v-model="autoTime" type="number" @change="changeAutoTime" min="1" />
-        </span>
-        {{ $t('second') }}
-      </div>
-      <a class="rk-btn sm ghost" @click="handleReload">
-        <rk-icon icon="retry" :loading="auto" />
-        <span class="vm">{{ $t('reload') }}</span>
-      </a>
-    </div>
+    <PageToolBar />
   </header>
 </template>
 
 <script lang="ts">
   import { Vue, Component } from 'vue-property-decorator';
-  import { Action, Getter } from 'vuex-class';
   import { routes } from '@/router';
-  import timeFormat from '@/utils/timeFormat';
+  import PageToolBar from './page-tool-bar.vue';
 
-  @Component
+  @Component({
+    components: {
+      PageToolBar,
+    },
+  })
   export default class Header extends Vue {
-    @Getter('duration') private duration: any;
-    @Action('SET_DURATION') private SET_DURATION: any;
-    private show: boolean = false;
-    private auto: boolean = false;
-    private autoTime: number = 6;
-    private timer: any = null;
-
     private get menus() {
       return routes[0].children;
     }
 
-    private handleReload() {
-      const gap = this.duration.end.getTime() - this.duration.start.getTime();
-      const time: Date[] = [new Date(new Date().getTime() - gap), new Date()];
-      this.SET_DURATION(timeFormat(time));
-    }
-    private handleAuto() {
-      if (this.autoTime < 1) {
-        return;
-      }
-      this.auto = !this.auto;
-      if (this.auto) {
-        this.handleReload();
-        this.timer = setInterval(this.handleReload, this.autoTime * 1000);
-      } else {
-        clearInterval(this.timer);
-      }
-    }
-    private handleHide() {
-      this.show = false;
-    }
-    private handleShow() {
-      this.show = !this.show;
-    }
     private handleSignout() {
       localStorage.removeItem('skywalking-authority');
       this.$router.push('/login');
     }
-    private changeAutoTime() {
-      if (this.autoTime < 1) {
-        return;
-      }
-      clearInterval(this.timer);
-      if (this.auto) {
-        this.handleReload();
-        this.timer = setInterval(this.handleReload, this.autoTime * 1000);
-      }
-    }
   }
 </script>
 
@@ -177,16 +121,4 @@
       background-color: #dededf;
     }
   }
-  .rk-auto-select {
-    border-radius: 3px;
-    background-color: #fff;
-    padding: 1px;
-    border-radius: 3px;
-
-    input {
-      width: 38px;
-      border-style: unset;
-      outline: 0;
-    }
-  }
 </style>
diff --git a/src/views/containers/index.vue b/src/views/containers/index.vue
index 8d6fe9c..593e630 100644
--- a/src/views/containers/index.vue
+++ b/src/views/containers/index.vue
@@ -14,34 +14,22 @@
 limitations under the License. -->
 <template>
   <div id="app">
-    <RkHeader @reloadFooter="reloadFooter" />
+    <RkHeader />
     <router-view></router-view>
-    <RkFooter ref="footer" />
     <AlertsContent />
   </div>
 </template>
 
 <script lang="ts">
   import { Component, Vue } from 'vue-property-decorator';
-  import { State } from 'vuex-class';
-  import { State as optionState } from '@/store/modules/global/selectors';
   import RkHeader from '@/views/components/common/rk-header.vue';
-  import RkFooter from '@/views/components/common/rk-footer.vue';
   import AlertsContent from '@/views/components/common/alerts-content.vue';
 
   @Component({
     components: {
       RkHeader,
-      RkFooter,
       AlertsContent,
     },
   })
-  export default class RouterIndex extends Vue {
-    @State('rocketOption') private stateDashboardOption!: optionState;
-    private isRouterAlive: boolean = true;
-    public reloadFooter(timeArray: Date[]): void {
-      const footer: any = this.$refs.footer;
-      footer.time = timeArray;
-    }
-  }
+  export default class RouterIndex extends Vue {}
 </script>