Compare commits

...
13 Commits
Author SHA1 Message Date
ssongliu c8bbfada8c fix: 解决容器、终端界面 ios 适配的问题 (#1385) 2023-06-15 19:32:03 +08:00
ssongliu 47dda4ac9f fix: 修改应用导入恢复时数据库连接信息 (#1367) 2023-06-13 08:32:12 +00:00
ssongliu b1d40960c4 fix: 容器存储卷目录按钮样式修改 (#1366) 2023-06-13 06:28:12 +00:00
ssongliu 5222caecf9 fix: 应用恢复去掉版本限制 (#1365) 2023-06-13 04:28:11 +00:00
ssongliu bc8d4cbb0f feat: 应用导入恢复增加提示信息 (#1362) 2023-06-13 02:46:11 +00:00
凹凸曼and凹凸曼 0bb31f6caf fix: 修复创建数据库时误删数据库用户的bug (#1358)
Co-authored-by: 凹凸曼 <xx@xx>
2023-06-13 10:29:47 +08:00
凹凸曼and凹凸曼 03c8e4d3cb feat: 容器存储卷快捷进入挂载点文件目录 #1325 (#1357)
Co-authored-by: 凹凸曼 <xx@xx>
2023-06-13 10:27:55 +08:00
凹凸曼and凹凸曼 68ad653545 fix: 修复无法读取ssh成功日志 (#1356)
Co-authored-by: 凹凸曼 <xx@xx>
2023-06-13 10:26:39 +08:00
凹凸曼and凹凸曼 fd5b34d644 fix: 修复英文语言包缺少"operateConfirm"的问题 (#1354)
Co-authored-by: 凹凸曼 <xx@xx>
2023-06-13 10:25:01 +08:00
吴小白 000096bc26 fix: 修正 actions 脚本 2023-06-12 21:16:54 +08:00
ssongliu 02023102d3 fix: 解决计划任务执行周期错误的问题 (#1349) 2023-06-12 10:42:11 +00:00
ssongliu 95bc3d9855 feat: 应用导入备份恢复优化 (#1347) 2023-06-12 09:48:11 +00:00
凹凸曼 f9fb16198b feat: 计划任务和容器日志的前端改进 (#1319)
* feat: 计划任务增加批量开启和停止 #1010 

* feat: 容器日志增加全屏按钮 #1176
2023-06-12 15:44:15 +08:00
15 changed files with 194 additions and 42 deletions
+1 -1
View File
@@ -49,4 +49,4 @@ jobs:
access-key-secret: ${{ secrets.OSS_ACCESS_KEY_SECRET }}
ossutil-version: '1.7.14'
- name: Upload Assets to OSS
- run: ossutil cp -r dist/ oss://resource-fit2cloud-com/1panel/package/stable/${{ github.ref_name }}/release/ --include "*.tar.gz" --include "checksums.txt" --only-current-dir
run: ossutil cp -r dist/ oss://resource-fit2cloud-com/1panel/package/stable/${{ github.ref_name }}/release/ --include "*.tar.gz" --include "checksums.txt" --only-current-dir
+2 -2
View File
@@ -6,7 +6,7 @@ type CronjobCreate struct {
Name string `json:"name" validate:"required"`
Type string `json:"type" validate:"required"`
SpecType string `json:"specType" validate:"required"`
Week int `json:"week" validate:"number,max=7,min=1"`
Week int `json:"week" validate:"number,max=6,min=0"`
Day int `json:"day" validate:"number"`
Hour int `json:"hour" validate:"number"`
Minute int `json:"minute" validate:"number"`
@@ -27,7 +27,7 @@ type CronjobUpdate struct {
ID uint `json:"id" validate:"required"`
Name string `json:"name" validate:"required"`
SpecType string `json:"specType" validate:"required"`
Week int `json:"week" validate:"number,max=7,min=1"`
Week int `json:"week" validate:"number,max=6,min=0"`
Day int `json:"day" validate:"number"`
Hour int `json:"hour" validate:"number"`
Minute int `json:"minute" validate:"number"`
+9
View File
@@ -19,6 +19,7 @@ type IAppInstallResourceRpo interface {
GetFirst(opts ...DBOption) (model.AppInstallResource, error)
Create(ctx context.Context, resource *model.AppInstallResource) error
DeleteBy(ctx context.Context, opts ...DBOption) error
BatchUpdateBy(maps map[string]interface{}, opts ...DBOption) error
}
func NewIAppInstallResourceRpo() IAppInstallResourceRpo {
@@ -71,3 +72,11 @@ func (a AppInstallResourceRpo) Create(ctx context.Context, resource *model.AppIn
func (a AppInstallResourceRpo) DeleteBy(ctx context.Context, opts ...DBOption) error {
return getTx(ctx, opts...).Delete(&model.AppInstallResource{}).Error
}
func (a *AppInstallResourceRpo) BatchUpdateBy(maps map[string]interface{}, opts ...DBOption) error {
db := getDb(opts...).Model(&model.AppInstallResource{})
if len(opts) == 0 {
db = db.Where("1=1")
}
return db.Updates(&maps).Error
}
+59 -4
View File
@@ -148,7 +148,7 @@ func handleAppRecover(install *model.AppInstall, recoverFile string, isRollback
if err := json.Unmarshal(appjson, &oldInstall); err != nil {
return fmt.Errorf("unmarshal app.json failed, err: %v", err)
}
if oldInstall.App.Key != install.App.Key || oldInstall.Name != install.Name || oldInstall.Version != install.Version || oldInstall.ID != install.ID {
if oldInstall.App.Key != install.App.Key || oldInstall.Name != install.Name {
return errors.New("the current backup file does not match the application")
}
@@ -172,6 +172,7 @@ func handleAppRecover(install *model.AppInstall, recoverFile string, isRollback
}()
}
newEnvFile := ""
resource, _ := appInstallResourceRepo.GetFirst(appInstallResourceRepo.WithAppInstallId(install.ID))
if resource.ID != 0 && install.App.Key != "mysql" {
mysqlInfo, err := appInstallRepo.LoadBaseInfo(resource.Key, "")
@@ -182,7 +183,22 @@ func handleAppRecover(install *model.AppInstall, recoverFile string, isRollback
if err != nil {
return err
}
if err := handleMysqlRecover(mysqlInfo, tmpPath, db.Name, fmt.Sprintf("%s.sql.gz", install.Name), true); err != nil {
newDB, envMap, err := reCreateDB(db.ID, oldInstall.Env)
if err != nil {
return err
}
oldHost := fmt.Sprintf("\"PANEL_DB_HOST\":\"%v\"", envMap["PANEL_DB_HOST"].(string))
newHost := fmt.Sprintf("\"PANEL_DB_HOST\":\"%v\"", mysqlInfo.ServiceName)
oldInstall.Env = strings.ReplaceAll(oldInstall.Env, oldHost, newHost)
envMap["PANEL_DB_HOST"] = mysqlInfo.ServiceName
newEnvFile, err = coverEnvJsonToStr(oldInstall.Env)
if err != nil {
return err
}
_ = appInstallResourceRepo.BatchUpdateBy(map[string]interface{}{"resource_id": newDB.ID}, commonRepo.WithByID(resource.ID))
if err := handleMysqlRecover(mysqlInfo, tmpPath, newDB.Name, fmt.Sprintf("%s.sql.gz", install.Name), true); err != nil {
global.LOG.Errorf("handle recover from sql.gz failed, err: %v", err)
return err
}
@@ -193,11 +209,50 @@ func handleAppRecover(install *model.AppInstall, recoverFile string, isRollback
return err
}
oldInstall.Status = constant.Running
if err := appInstallRepo.Save(context.Background(), install); err != nil {
if len(newEnvFile) != 0 {
envPath := fmt.Sprintf("%s/%s/%s/.env", constant.AppInstallDir, install.App.Key, install.Name)
file, err := os.OpenFile(envPath, os.O_WRONLY|os.O_TRUNC, 0640)
if err != nil {
return err
}
defer file.Close()
_, _ = file.WriteString(newEnvFile)
}
oldInstall.ID = install.ID
oldInstall.Status = constant.StatusRunning
oldInstall.AppId = install.AppId
oldInstall.AppDetailId = install.AppDetailId
if err := appInstallRepo.Save(context.Background(), &oldInstall); err != nil {
global.LOG.Errorf("save db app install failed, err: %v", err)
return err
}
isOk = true
return nil
}
func reCreateDB(dbID uint, oldEnv string) (*model.DatabaseMysql, map[string]interface{}, error) {
mysqlService := NewIMysqlService()
ctx := context.Background()
_ = mysqlService.Delete(ctx, dto.MysqlDBDelete{ID: dbID, DeleteBackup: true, ForceDelete: true})
envMap := make(map[string]interface{})
if err := json.Unmarshal([]byte(oldEnv), &envMap); err != nil {
return nil, envMap, err
}
oldName, _ := envMap["PANEL_DB_NAME"].(string)
oldUser, _ := envMap["PANEL_DB_USER"].(string)
oldPassword, _ := envMap["PANEL_DB_USER_PASSWORD"].(string)
createDB, err := mysqlService.Create(context.Background(), dto.MysqlDBCreate{
Name: oldName,
Format: "utf8mb4",
Username: oldUser,
Password: oldPassword,
Permission: "%",
})
if err != nil {
return nil, envMap, err
}
return createDB, envMap, nil
}
+16 -14
View File
@@ -14,7 +14,6 @@ import (
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/app/model"
"github.com/1Panel-dev/1Panel/backend/app/repo"
"github.com/1Panel-dev/1Panel/backend/buserr"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/global"
@@ -100,7 +99,7 @@ func (u *MysqlService) Create(ctx context.Context, req dto.MysqlDBCreate) (*mode
}
return nil, err
}
if err := u.createUser(app, req); err != nil {
if err := u.createUser(app.ContainerName, app.Password, app.Version, req); err != nil {
return nil, err
}
@@ -291,7 +290,7 @@ func (u *MysqlService) ChangeAccess(info dto.ChangeDBInfo) error {
}
}
if err := u.createUser(app, dto.MysqlDBCreate{
if err := u.createUser(app.ContainerName, app.Password, app.Version, dto.MysqlDBCreate{
Username: mysql.Username,
Name: mysql.Name,
Permission: info.Value,
@@ -470,7 +469,7 @@ func (u *MysqlService) LoadStatus() (*dto.MysqlStatus, error) {
return &info, nil
}
func (u *MysqlService) createUser(app *repo.RootInfo, req dto.MysqlDBCreate) error {
func (u *MysqlService) createUser(container, password, version string, req dto.MysqlDBCreate) error {
var userlist []string
if strings.Contains(req.Permission, ",") {
ips := strings.Split(req.Permission, ",")
@@ -484,32 +483,35 @@ func (u *MysqlService) createUser(app *repo.RootInfo, req dto.MysqlDBCreate) err
}
for _, user := range userlist {
if err := excSQL(app.ContainerName, app.Password, fmt.Sprintf("create user %s identified by '%s';", user, req.Password)); err != nil {
handleCreateError(req.Name, userlist, app)
if err := excSQL(container, password, fmt.Sprintf("create user %s identified by '%s';", user, req.Password)); err != nil {
if strings.Contains(err.Error(), "ERROR 1396") {
handleCreateError(container, password, req.Name, userlist, false)
return buserr.New(constant.ErrUserIsExist)
}
handleCreateError(container, password, req.Name, userlist, true)
return err
}
grantStr := fmt.Sprintf("grant all privileges on `%s`.* to %s", req.Name, user)
if req.Name == "*" {
grantStr = fmt.Sprintf("grant all privileges on *.* to %s", user)
}
if strings.HasPrefix(app.Version, "5.7") {
if strings.HasPrefix(version, "5.7") {
grantStr = fmt.Sprintf("%s identified by '%s' with grant option;", grantStr, req.Password)
}
if err := excSQL(app.ContainerName, app.Password, grantStr); err != nil {
handleCreateError(req.Name, userlist, app)
if err := excSQL(container, password, grantStr); err != nil {
handleCreateError(container, password, req.Name, userlist, true)
return err
}
}
return nil
}
func handleCreateError(dbName string, userlist []string, app *repo.RootInfo) {
_ = excSQL(app.ContainerName, app.Password, fmt.Sprintf("drop database `%s`", dbName))
for _, user := range userlist {
if err := excSQL(app.ContainerName, app.Password, fmt.Sprintf("drop user if exists %s", user)); err != nil {
global.LOG.Errorf("drop user failed, err: %v", err)
func handleCreateError(contaienr, password, dbName string, userlist []string, dropUser bool) {
_ = excSQL(contaienr, password, fmt.Sprintf("drop database `%s`", dbName))
if dropUser {
for _, user := range userlist {
if err := excSQL(contaienr, password, fmt.Sprintf("drop user if exists %s", user)); err != nil {
global.LOG.Errorf("drop user failed, err: %v", err)
}
}
}
}
+1 -1
View File
@@ -248,7 +248,7 @@ func (u *SSHService) LoadLog(req dto.SearchSSHLog) (*dto.SSHLog, error) {
data.Logs = append(data.Logs, dataItem...)
}
}
commandItem := fmt.Sprintf("cat %s | grep Accepted %s", fileList[i], command)
commandItem := fmt.Sprintf("cat %s | grep -a Accepted %s", fileList[i], command)
dataItem := loadSuccessDatas(commandItem)
data.TotalCount += len(dataItem)
if req.Status != constant.StatusFailed {
@@ -9,6 +9,9 @@
<el-divider v-if="slots.buttons" direction="vertical" />
<slot v-if="slots.buttons" name="buttons"></slot>
</template>
<template #extra>
<slot v-if="slots.extra" name="extra"></slot>
</template>
</el-page-header>
</template>
+10 -1
View File
@@ -280,7 +280,16 @@ const buttons = [
{
label: i18n.global.t('commons.button.recover'),
click: (row: File.File) => {
onRecover(row);
if (type.value !== 'app') {
onRecover(row);
} else {
ElMessageBox.confirm(i18n.global.t('app.restoreWarn'), i18n.global.t('commons.button.recover'), {
confirmButtonText: i18n.global.t('commons.button.confirm'),
cancelButtonText: i18n.global.t('commons.button.cancel'),
}).then(async () => {
onRecover(row);
});
}
},
},
{
+7 -3
View File
@@ -97,6 +97,7 @@ const message = {
updateSuccess: 'Update Success',
uploadSuccess: 'Update Success',
operate: 'Operate',
operateConfirm: 'If you are sure about the operation, please input it manually',
inputOrSelect: 'Please select or enter',
backupSuccess: 'Backup Success',
unSupportType: 'Current file type is not supported!',
@@ -553,6 +554,7 @@ const message = {
cache: 'Cache',
volume: 'Volume',
volumeDir: 'Volume dir',
volumeName: 'Name',
nfsEnable: 'Enable NFS storage',
nfsAddress: 'Address',
@@ -632,8 +634,10 @@ const message = {
cronjob: {
cronTask: 'Task',
changeStatus: 'Change status',
disableMsg: 'The cronjob cannot continue to run after it is stopped. Do you want to stop it?',
enableMsg: 'The cronjob has been stopped. Enable now?',
disableMsg:
'Stopping the scheduled task will result in the task no longer automatically executing. Do you want to continue?',
enableMsg:
'Enabling the scheduled task will allow the task to automatically execute on a regular basis. Do you want to continue?',
taskType: 'Task type',
record: 'Records',
shell: 'Shell script',
@@ -1139,7 +1143,7 @@ const message = {
backupdate: 'Backup time',
restore: 'Restore',
restoreWarn:
'The restore operation will restart the application and replace the data. This operation cannot be rolled back. Do you want to continue?',
'The recovery operation will delete the current data of this application and perform a restart. This action is irreversible. Do you wish to continue?',
update: 'update',
upgrade: 'upgrade',
versionSelect: 'Please select a version',
+4 -3
View File
@@ -564,6 +564,7 @@ const message = {
gateway: '网关',
volume: '存储卷',
volumeDir: '存储卷目录',
nfsEnable: '启用 NFS 存储',
nfsAddress: '地址',
nfsAddressHelper: '支持输入 ip 或者域名',
@@ -633,8 +634,8 @@ const message = {
cronjob: {
cronTask: '计划任务',
changeStatus: '状态修改',
disableMsg: '计划任务停止后将无法继续运行,是否停止?',
enableMsg: '该计划任务已停止,是否启用?',
disableMsg: '停止计划任务会导致该任务不再自动执行。是否继续?',
enableMsg: '启用计划任务会让该任务定期自动执行。是否继续?',
taskType: '任务类型',
record: '报告',
shell: 'Shell 脚本',
@@ -1109,7 +1110,7 @@ const message = {
backupPath: '文件路径',
backupdate: '备份时间',
restore: '恢复',
restoreWarn: '恢复操作会重启应用,并替换数据,此操作不可回滚,是否继续?',
restoreWarn: '恢复操作将删除该应用当前数据并重启。此操作不可回滚,是否继续?',
update: '更新',
upgrade: '升级',
versionSelect: '请选择版本',
@@ -1,8 +1,17 @@
<template>
<div>
<el-drawer v-model="logVisiable" :destroy-on-close="true" :close-on-click-modal="false" size="50%">
<el-drawer
v-model="logVisiable"
:destroy-on-close="true"
:close-on-click-modal="false"
:size="globalStore.isFullScreen ? '100%' : '50%'"
>
<template #header>
<DrawerHeader :header="$t('commons.button.log')" :resource="logSearch.container" :back="handleClose" />
<DrawerHeader :header="$t('commons.button.log')" :resource="logSearch.container" :back="handleClose">
<template #extra v-if="!mobile">
<el-button @click="toggleFullscreen" class="fullScreen" icon="FullScreen" plain></el-button>
</template>
</DrawerHeader>
</template>
<div>
<el-select @change="searchLogs" style="width: 30%; float: left" v-model="logSearch.mode">
@@ -47,23 +56,28 @@
import { cleanContainerLog, logContainer } from '@/api/modules/container';
import i18n from '@/lang';
import { dateFormatForName } from '@/utils/util';
import { nextTick, onBeforeUnmount, reactive, ref, shallowRef } from 'vue';
import { computed, nextTick, onBeforeUnmount, reactive, ref, shallowRef, watch } from 'vue';
import { Codemirror } from 'vue-codemirror';
import { javascript } from '@codemirror/lang-javascript';
import { oneDark } from '@codemirror/theme-one-dark';
import DrawerHeader from '@/components/drawer-header/index.vue';
import { ElMessageBox } from 'element-plus';
import { MsgSuccess } from '@/utils/message';
import screenfull from 'screenfull';
import { GlobalStore } from '@/store';
const extensions = [javascript(), oneDark];
const logVisiable = ref(false);
const mobile = computed(() => {
return globalStore.isMobile();
});
const logInfo = ref();
const view = shallowRef();
const handleReady = (payload) => {
view.value = payload.view;
};
const globalStore = GlobalStore();
const logSearch = reactive({
isWatch: false,
@@ -93,12 +107,19 @@ const timeOptions = ref([
},
]);
function toggleFullscreen() {
if (screenfull.isEnabled) {
screenfull.toggle();
}
}
const handleClose = async () => {
logVisiable.value = false;
clearInterval(Number(timer));
timer = null;
};
watch(logVisiable, (val) => {
if (screenfull.isEnabled && !val && !mobile.value) screenfull.exit();
});
const searchLogs = async () => {
const res = await logContainer(logSearch);
logInfo.value = res.data || '';
@@ -150,6 +171,12 @@ const acceptParams = (props: DialogProps): void => {
searchLogs();
}
}, 1000 * 5);
if (!mobile.value) {
screenfull.on('change', () => {
globalStore.isFullScreen = screenfull.isFullscreen;
});
}
};
onBeforeUnmount(() => {
@@ -166,4 +193,7 @@ defineExpose({
.margin-button {
margin-left: 20px;
}
.fullScreen {
border: none;
}
</style>
+12 -1
View File
@@ -49,6 +49,15 @@
<Tooltip @click="onInspect(row.name)" :text="row.name" />
</template>
</el-table-column>
<el-table-column :label="$t('container.volumeDir')" min-width="50">
<template #default="{ row }">
<el-button type="primary" link @click="toFolder(row.mountpoint)">
<el-icon>
<FolderOpened />
</el-icon>
</el-button>
</template>
</el-table-column>
<el-table-column
:label="$t('container.mountpoint')"
show-overflow-tooltip
@@ -124,7 +133,9 @@ const loadStatus = async () => {
const goSetting = async () => {
router.push({ name: 'ContainerSetting' });
};
const toFolder = (folder: string) => {
router.push({ path: '/hosts/files', query: { path: folder } });
};
const dialogCreateRef = ref<DialogExpose>();
interface DialogExpose {
+20
View File
@@ -15,6 +15,12 @@
<el-button type="primary" @click="onOpenDialog('create')">
{{ $t('commons.button.create') }}{{ $t('cronjob.cronTask') }}
</el-button>
<el-button plain :disabled="selects.length === 0" @click="onBatchChangeStatus('enable')">
{{ $t('commons.button.enable') }}
</el-button>
<el-button plain :disabled="selects.length === 0" @click="onBatchChangeStatus('disable')">
{{ $t('commons.button.disable') }}
</el-button>
<el-button plain :disabled="selects.length === 0" @click="onDelete(null)">
{{ $t('commons.button.delete') }}
</el-button>
@@ -320,6 +326,20 @@ const onChangeStatus = async (id: number, status: string) => {
});
};
const onBatchChangeStatus = async (status: string) => {
ElMessageBox.confirm(i18n.global.t('cronjob.' + status + 'Msg'), i18n.global.t('cronjob.changeStatus'), {
confirmButtonText: i18n.global.t('commons.button.confirm'),
cancelButtonText: i18n.global.t('commons.button.cancel'),
}).then(async () => {
let itemStatus = status === 'enable' ? 'Enable' : 'Disable';
for (const item of selects.value) {
await updateStatus({ id: item.id, status: itemStatus });
}
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
search();
});
};
const onHandle = async (row: Cronjob.CronjobInfo) => {
loading.value = true;
await handleOnce(row.id)
+4 -2
View File
@@ -330,7 +330,7 @@ const weekOptions = [
{ label: i18n.global.t('cronjob.thursday'), value: 4 },
{ label: i18n.global.t('cronjob.friday'), value: 5 },
{ label: i18n.global.t('cronjob.saturday'), value: 6 },
{ label: i18n.global.t('cronjob.sunday'), value: 7 },
{ label: i18n.global.t('cronjob.sunday'), value: 0 },
];
const rules = reactive({
name: [Rules.requiredInput],
@@ -456,7 +456,9 @@ function checkScript() {
case 'perMonth':
return row.day > 0 && row.day < 32 && row.hour >= 0 && row.hour < 24 && row.minute >= 0 && row.minute < 60;
case 'perWeek':
return row.week > 0 && row.week < 8 && row.hour >= 0 && row.hour < 24 && row.minute >= 0 && row.minute < 60;
return (
row.week >= 0 && row.week < 7 && row.hour >= 0 && row.hour < 24 && row.minute >= 0 && row.minute < 60
);
case 'perDay':
return row.hour >= 0 && row.hour < 24 && row.minute >= 0 && row.minute < 60;
case 'perHour':
@@ -123,14 +123,14 @@
></el-empty>
</div>
</el-tabs>
<el-button @click="toggleFullscreen" class="fullScreen" icon="FullScreen"></el-button>
<el-button @click="toggleFullscreen" v-if="!mobile" class="fullScreen" icon="FullScreen"></el-button>
<HostDialog ref="dialogRef" @on-conn-terminal="onConnTerminal" @load-host-tree="loadHostTree" />
</div>
</template>
<script setup lang="ts">
import { ref, getCurrentInstance, watch, nextTick } from 'vue';
import { ref, getCurrentInstance, watch, nextTick, computed } from 'vue';
import Terminal from '@/components/terminal/index.vue';
import HostDialog from '@/views/host/terminal/terminal/host-create.vue';
import type Node from 'element-plus/es/components/tree/src/model/node';
@@ -145,15 +145,15 @@ import { GlobalStore } from '@/store';
const dialogRef = ref();
const ctx = getCurrentInstance() as any;
const globalStore = GlobalStore();
const mobile = computed(() => {
return globalStore.isMobile();
});
function toggleFullscreen() {
if (screenfull.isEnabled) {
screenfull.toggle();
}
}
screenfull.on('change', () => {
globalStore.isFullScreen = screenfull.isFullscreen;
});
const localHostID = ref();
@@ -209,6 +209,12 @@ const acceptParams = async () => {
}
}
}
if (!mobile.value) {
screenfull.on('change', () => {
globalStore.isFullScreen = screenfull.isFullscreen;
});
}
};
const cleanTimer = () => {
clearInterval(Number(timer));