suochencheng 7 anni fa
parent
commit
b04527ef08

+ 1 - 1
src/main/java/com/izouma/awesomeadmin/dao/UserInfoMapper.xml

@@ -31,7 +31,7 @@
     <sql id="Base_Column_List">
         id, username, nickname, icon, birthday, sex, open_id, union_id, phone, mail,
         country, province, city, district, create_time, del_flag, work_number, config,
-        mis_child, inherit_flag
+        mis_child, inherit_flag, password
     </sql>
     <select id="selectByPrimaryKey" resultMap="BaseResultMap" parameterType="java.lang.Integer">
         select

+ 2 - 0
src/main/java/com/izouma/awesomeadmin/service/UserInfoService.java

@@ -26,6 +26,8 @@ public interface UserInfoService {
 
     boolean updateUserInfo(UserInfo record);
 
+    boolean changePassword(UserInfo record);
+
     UserInfo login(String username, String password);
 
     UserInfo loginSms(String phone, String code, String sessionId) throws UserInfoServiceImpl.LoginException;

+ 24 - 6
src/main/java/com/izouma/awesomeadmin/service/impl/UserInfoServiceImpl.java

@@ -41,19 +41,19 @@ import java.util.*;
 @Service
 public class UserInfoServiceImpl implements UserInfoService {
 
-    private static Logger    logger    = Logger.getLogger(UserInfoServiceImpl.class);
-    private        RongCloud rongCloud = RongCloud.getInstance(PropertiesFileLoader.getProperties("rongyunappkey"), PropertiesFileLoader.getProperties("rongyunappsecret"));
+    private static Logger logger = Logger.getLogger(UserInfoServiceImpl.class);
+    private RongCloud rongCloud = RongCloud.getInstance(PropertiesFileLoader.getProperties("rongyunappkey"), PropertiesFileLoader.getProperties("rongyunappsecret"));
 
     @Autowired
-    private UserInfoMapper    userInfoMapper;
+    private UserInfoMapper userInfoMapper;
     @Autowired
-    private SysRoleMapper     sysRoleMapper;
+    private SysRoleMapper sysRoleMapper;
     @Autowired
-    private DepartInfoMapper  departInfoMapper;
+    private DepartInfoMapper departInfoMapper;
     @Autowired
     private SysAppTokenMapper sysAppTokenMapper;
     @Autowired
-    private OSSFileService    ossFileService;
+    private OSSFileService ossFileService;
 
     @Override
     public List<UserInfo> getUserInfoList(UserInfo record) {
@@ -184,6 +184,24 @@ public class UserInfoServiceImpl implements UserInfoService {
         return false;
     }
 
+    @Override
+    public boolean changePassword(UserInfo record) {
+
+        logger.info("changePassword");
+        try {
+
+            int updates = userInfoMapper.updateByPrimaryKeySelective(record);
+            if (updates > 0) {
+
+                return true;
+            }
+        } catch (Exception e) {
+            logger.error("changePassword", e);
+        }
+
+        return false;
+    }
+
     @Override
     public UserInfo login(String username, String password) {
         logger.info("login");

+ 51 - 4
src/main/java/com/izouma/awesomeadmin/web/UserInfoController.java

@@ -16,6 +16,7 @@ import com.izouma.awesomeadmin.service.SysMenuService;
 import com.izouma.awesomeadmin.shiro.PhoneCodeToken;
 import com.izouma.awesomeadmin.util.CookieUtil;
 import com.izouma.awesomeadmin.util.ExportExcelUtil;
+import com.izouma.awesomeadmin.util.MD5Util;
 import org.activiti.engine.IdentityService;
 import org.apache.commons.lang.StringUtils;
 import org.apache.shiro.SecurityUtils;
@@ -46,15 +47,15 @@ import javax.servlet.http.HttpSession;
 public class UserInfoController {
 
     @Autowired
-    private UserInfoService   userInfoService;
+    private UserInfoService userInfoService;
     @Autowired
-    private PowerInfoService  powerInfoService;
+    private PowerInfoService powerInfoService;
     @Autowired
-    private SysMenuService    sysMenuService;
+    private SysMenuService sysMenuService;
     @Autowired
     private DepartInfoService departInfoService;
     @Autowired
-    private IdentityService   identityService;
+    private IdentityService identityService;
 
     /**
      * <p>获取全部记录。</p>
@@ -153,6 +154,52 @@ public class UserInfoController {
         return new Result(false, "保存异常");
     }
 
+    @RequiresAuthentication
+    @RequestMapping(value = "/initPassword", method = RequestMethod.POST)
+    @ResponseBody
+    public Result initPassword(String id) {
+        UserInfo record = new UserInfo();
+        record.setId(Integer.valueOf(id));
+        record.setPassword(MD5Util.getMD5("123456"));
+        boolean num = userInfoService.changePassword(record);
+        if (num) {
+            return new Result(true, "保存成功");
+        }
+        return new Result(false, "保存异常");
+    }
+
+    @RequiresAuthentication
+    @RequestMapping(value = "/changePassword", method = RequestMethod.POST)
+    @ResponseBody
+    public Result changePassword(String old, String newPassword) {
+
+        try {
+            Subject subject = SecurityUtils.getSubject();
+            UserInfo userInfo = (UserInfo) subject.getPrincipal();
+            if (userInfo != null) {
+                userInfo = userInfoService.getUserInfoById(userInfo.getId().toString());
+                if (userInfo != null) {
+                    if (MD5Util.getMD5(old).equals(userInfo.getPassword())) {
+
+                        UserInfo record = new UserInfo();
+                        record.setId(userInfo.getId());
+                        record.setPassword(MD5Util.getMD5(newPassword));
+                        boolean num = userInfoService.changePassword(record);
+                        if (num) {
+                            return new Result(true, "修改密码成功");
+                        }
+                    } else {
+                        return new Result(false, "原始密码不正确");
+                    }
+                }
+            }
+        } catch (Exception ignored) {
+
+        }
+
+        return new Result(false, "保存异常");
+    }
+
     /**
      * <p>删除。</p>
      */

+ 2 - 0
src/main/vue/src/entries/admin.js

@@ -34,6 +34,8 @@ const baseUrl = process.env.NODE_ENV === 'production' ? '../' : `http://${locati
 Vue.prototype.$baseUrl = baseUrl;
 Vue.prototype.$widgetBaseUrl = 'http://116.62.160.108:8180/';
 Vue.prototype.$handleUrl = 'http://120.27.240.144:8082/webService/call'
+//  Vue.prototype.$widgetBaseUrl = 'http://172.16.86.43:8180/';
+//  Vue.prototype.$handleUrl = 'http://180.169.224.24:8082/webService/call'
 axios.defaults.withCredentials = true;
 axios.defaults.baseURL = baseUrl;
 Vue.prototype.$http = {

+ 4 - 0
src/main/vue/src/pages/Admin.vue

@@ -29,6 +29,7 @@
                 </div>
 
                 <el-dropdown-menu slot="dropdown">
+                    <el-dropdown-item command="changePassword">修改密码</el-dropdown-item>
                     <el-dropdown-item command="logout">退出登录</el-dropdown-item>
                 </el-dropdown-menu>
             </el-dropdown>
@@ -187,7 +188,10 @@ export default {
                         this.$router.replace('/login');
                     }
                 })
+            }else if (command === 'changePassword') {
+                this.$router.push('/changePassword');
             }
+
         },
         removeTab(temp) {
             console.log(temp)

+ 1 - 1
src/main/vue/src/pages/User.vue

@@ -13,7 +13,7 @@
             <el-form-item prop="phone" label="手机">
                 <el-input v-model="formData.phone"></el-input>
             </el-form-item>
-            <el-form-item prop="phone" label="密码" v-if="!$route.query.id">
+            <el-form-item prop="password" label="密码" v-if="!$route.query.id">
                 <el-input v-model="formData.password"></el-input>
             </el-form-item>
             <el-form-item prop="roleId" label="角色">

+ 127 - 152
src/main/vue/src/pages/Users.vue

@@ -1,8 +1,8 @@
 <template>
     <div>
         <div class="filters-container">
-            <el-input placeholder="用户名" size="small" v-model="filter1" clearable class="filter-item"></el-input>
-            <el-select placeholder="性别" size="small" v-model="filter2" clearable class="filter-item">
+            <!-- <el-input placeholder="用户名" size="small" v-model="filter1" clearable class="filter-item"></el-input> -->
+            <!-- <el-select placeholder="性别" size="small" v-model="filter2" clearable class="filter-item">
                 <el-option
                     label="女"
                     value="item1">
@@ -11,15 +11,15 @@
                     label="男"
                     value="item2">
                 </el-option>
-            </el-select>
-            <el-button @click="getData" type="primary" size="small" icon="el-icon-search" class="filter-item">搜索
-            </el-button>
-            <el-button @click="$router.push('/user')" type="primary" size="small" icon="el-icon-edit"
-                       class="filter-item">添加
+            </el-select> -->
+            <!-- <el-button @click="getData" type="primary" size="small" icon="el-icon-search" class="filter-item">搜索
+            </el-button> -->
+            <el-button @click="$router.push('/user')" type="primary" size="small" icon="el-icon-edit" class="filter-item">添加
             </el-button>
             <el-dropdown trigger="click" size="medium" class="table-column-filter">
                 <span>
-                  筛选数据<i class="el-icon-arrow-down el-icon--right"></i>
+                    筛选数据
+                    <i class="el-icon-arrow-down el-icon--right"></i>
                 </span>
                 <el-dropdown-menu slot="dropdown" class="table-column-filter-wrapper">
                     <el-checkbox v-for="item in tableColumns" :key="item.value" v-model="item.show">{{item.label}}
@@ -27,185 +27,160 @@
                 </el-dropdown-menu>
             </el-dropdown>
         </div>
-        <el-table
-            :data="tableData"
-            :height="tableHeight"
-            row-key="id"
-            ref="table">
-            <el-table-column
-                v-if="multipleMode"
-                align="center"
-                type="selection"
-                width="50">
+        <el-table :data="tableData" :height="tableHeight" row-key="id" ref="table">
+            <el-table-column v-if="multipleMode" align="center" type="selection" width="50">
             </el-table-column>
-            <el-table-column
-                type="index"
-                min-width="50"
-                align="center">
+            <el-table-column type="index" min-width="50" align="center">
             </el-table-column>
-            <el-table-column
-                v-if="isColumnShow('username')"
-                prop="username"
-                label="用户名"
-                min-width="300">
+            <el-table-column v-if="isColumnShow('username')" prop="username" label="用户名" min-width="300">
             </el-table-column>
-            <el-table-column
-                v-if="isColumnShow('nickname')"
-                prop="nickname"
-                label="昵称"
-                min-width="300">
+            <el-table-column v-if="isColumnShow('nickname')" prop="nickname" label="昵称" min-width="300">
             </el-table-column>
-            <el-table-column
-                v-if="isColumnShow('icon')"
-                label="头像"
-                min-width="300">
+            <el-table-column v-if="isColumnShow('icon')" label="头像" min-width="300">
                 <template slot-scope="scope">
-                    <img :src="scope.row.icon"
-                         style="width: 32px;height: 32px;border-radius: 50%;vertical-align: middle;"/>
+                    <img :src="scope.row.icon" style="width: 32px;height: 32px;border-radius: 50%;vertical-align: middle;" />
                 </template>
             </el-table-column>
-            <el-table-column
-                v-if="isColumnShow('sex')"
-                prop="sex"
-                label="性别"
-                min-width="300">
-            </el-table-column>
-            <el-table-column
-                v-if="isColumnShow('openId')"
-                prop="openId"
-                label="openId"
-                min-width="300">
+            <el-table-column v-if="isColumnShow('sex')" prop="sex" label="性别" min-width="300">
             </el-table-column>
-            <el-table-column
-                label="操作"
-                align="center"
-                fixed="right">
+            <!-- <el-table-column v-if="isColumnShow('openId')" prop="openId" label="openId" min-width="300">
+            </el-table-column> -->
+            <el-table-column label="操作" align="center" fixed="right" width="200">
                 <template slot-scope="scope">
                     <el-button @click="editRow(scope.row)" type="primary" size="mini" plain>编辑</el-button>
+                    <el-button @click="initPassword(scope.row)" type="primary" size="mini" plain>密码初始化</el-button>
                 </template>
             </el-table-column>
         </el-table>
         <div class="pagination-wrapper">
-            <div class="multiple-mode-wrapper">
+            <!-- <div class="multiple-mode-wrapper">
                 <el-button size="small" v-if="!multipleMode" @click="toggleMultipleMode(true)">批量编辑</el-button>
                 <el-button-group v-else>
                     <el-button size="small" @click="operation1">批量操作1</el-button>
                     <el-button size="small" @click="operation2">批量操作2</el-button>
                     <el-button size="small" @click="toggleMultipleMode(false)">取消</el-button>
                 </el-button-group>
-            </div>
-            <el-pagination
-                background
-                @size-change="pageSizeChange"
-                @current-change="currentPageChange"
-                :current-page="currentPage"
-                :page-sizes="[10, 20, 30, 40, 50]"
-                :page-size="pageSize"
-                layout="total, sizes, prev, pager, next, jumper"
-                :total="totalNumber">
+            </div> -->
+            <el-pagination background @size-change="pageSizeChange" @current-change="currentPageChange" :current-page="currentPage" :page-sizes="[10, 20, 30, 40, 50]" :page-size="pageSize" layout="total, sizes, prev, pager, next, jumper" :total="totalNumber">
             </el-pagination>
         </div>
     </div>
 </template>
 <script>
-    import {mapState} from 'vuex'
+import { mapState } from 'vuex'
 
-    export default {
-        created() {
+export default {
+    created() {
+        this.getData();
+    },
+    data() {
+        return {
+            totalNumber: 0,
+            totalPage: 0,
+            currentPage: 1,
+            pageSize: 20,
+            tableData: [],
+            filter1: '',
+            filter2: '',
+            tableColumns: [{
+                label: '用户名',
+                value: 'username',
+                show: true
+            }, {
+                label: '昵称',
+                value: 'nickname',
+                show: true
+            }, {
+                label: '头像',
+                value: 'icon',
+                show: true
+            }, {
+                label: '性别',
+                value: 'sex',
+                show: true
+            }, {
+                label: 'openId',
+                value: 'openId',
+                show: true
+            }],
+            multipleMode: false
+        }
+    },
+    computed: {
+        ...mapState(['tableHeight']),
+        selection() {
+            return this.$refs.table.selection.map(i => i.id);
+        }
+    },
+    methods: {
+        pageSizeChange(size) {
+            this.pageSize = size;
             this.getData();
         },
-        data() {
-            return {
-                totalNumber: 0,
-                totalPage: 0,
-                currentPage: 1,
-                pageSize: 20,
-                tableData: [],
-                filter1: '',
-                filter2: '',
-                tableColumns: [{
-                    label: '用户名',
-                    value: 'username',
-                    show: true
-                }, {
-                    label: '昵称',
-                    value: 'nickname',
-                    show: true
-                }, {
-                    label: '头像',
-                    value: 'icon',
-                    show: true
-                }, {
-                    label: '性别',
-                    value: 'sex',
-                    show: true
-                }, {
-                    label: 'openId',
-                    value: 'openId',
-                    show: true
-                }],
-                multipleMode: false
-            }
+        currentPageChange(page) {
+            this.currentPage = page;
+            this.getData();
+        },
+        getData() {
+            this.$http.get({
+                url: '/userInfo/page',
+                data: {
+                    currentPage: this.currentPage,
+                    pageNumber: this.pageSize
+                }
+            }).then(res => {
+                if (res.success) {
+                    this.totalNumber = res.data.page.totalNumber;
+                    this.tableData = res.data.pp;
+                }
+            })
         },
-        computed: {
-            ...mapState(['tableHeight']),
-            selection() {
-                return this.$refs.table.selection.map(i => i.id);
+        isColumnShow(column) {
+            var row = this.tableColumns.find(i => i.value === column);
+            return row ? row.show : false;
+        },
+        toggleMultipleMode(multipleMode) {
+            this.multipleMode = multipleMode;
+            if (!multipleMode) {
+                this.$refs.table.clearSelection();
             }
         },
-        methods: {
-            pageSizeChange(size) {
-                this.pageSize = size;
-                this.getData();
-            },
-            currentPageChange(page) {
-                this.currentPage = page;
-                this.getData();
-            },
-            getData() {
-                this.$http.get({
-                    url: '/userInfo/page',
-                    data: {
-                        currentPage: this.currentPage,
-                        pageNumber: this.pageSize
-                    }
-                }).then(res => {
-                    if (res.success) {
-                        this.totalNumber = res.data.page.totalNumber;
-                        this.tableData = res.data.pp;
-                    }
-                })
-            },
-            isColumnShow(column) {
-                var row = this.tableColumns.find(i => i.value === column);
-                return row ? row.show : false;
-            },
-            toggleMultipleMode(multipleMode) {
-                this.multipleMode = multipleMode;
-                if (!multipleMode) {
-                    this.$refs.table.clearSelection();
+        editRow(row) {
+            this.$router.push({
+                path: '/user',
+                query: {
+                    id: row.id
                 }
-            },
-            editRow(row) {
-                this.$router.push({
-                    path: '/user',
-                    query: {
-                        id: row.id
-                    }
+            })
+        },
+        initPassword(row) {
+
+            this.$alert('初始化密码将无法恢复,确认要初始化么?', '警告', { type: 'error' }).then(() => {
+                return this.$http.post({
+                    url: '/userInfo/initPassword',
+                    data: { id: row.id }
                 })
-            },
-            operation1() {
-                this.$notify({
-                    title: '提示',
-                    message: this.selection
-                });
-            },
-            operation2() {
-                this.$message('操作2');
-            }
+            }).then(() => {
+                this.$message.success('成功');
+            }).catch(action => {
+                if (action === 'cancel') {
+                    this.$message.info('取消');
+                } else {
+                    this.$message.error('失败');
+                }
+            })
+        },
+        operation1() {
+            this.$notify({
+                title: '提示',
+                message: this.selection
+            });
+        },
+        operation2() {
+            this.$message('操作2');
         }
     }
+}
 </script>
 <style lang="less" scoped>
-
 </style>

+ 148 - 0
src/main/vue/src/pages/changePassword.vue

@@ -0,0 +1,148 @@
+<template>
+    <div>
+        <el-form :model="formData" :rules="rules" ref="form" label-width="80px" label-position="right" size="small" style="max-width: 500px;">
+            <el-form-item label="旧密码" prop="old">
+                <el-input v-model="formData.old" type="password"></el-input>
+            </el-form-item>
+            <el-form-item label="新密码" prop="newPassword">
+                <el-input v-model="newPassword" type="password"></el-input>
+                密码强度:
+                <el-tag :type="strength == '弱' ?  'danger' :  'gray' ">弱</el-tag>
+                <el-tag :type="strength == '中' ?  'warning' : 'gray'">中</el-tag>
+                <el-tag :type="strength == '强' ?'success' :  'gray' ">强</el-tag>
+            </el-form-item>
+            <el-form-item label="确认新密码" prop="confirm">
+                <el-input v-model="formData.confirm" type="password"></el-input>
+            </el-form-item>
+            <el-form-item>
+                <el-button @click="onSave" :loading="$store.state.fetchingData" type="primary">保存</el-button>
+                <el-button @click="$router.go(-1)">取消</el-button>
+            </el-form-item>
+        </el-form>
+    </div>
+</template>
+<script>
+import formValidator from '../formValidator'
+
+export default {
+    created() {
+    },
+    data() {
+        return {
+            saving: false,
+            formData: {
+                old: '',
+                newPassword: '',
+                confirm: ''
+            },
+            rules: {
+                old: [
+                    { required: true, message: '请填写原始密码', trigger: 'blur' },
+                ],
+                confirm: [
+                    { required: true, message: '请输入确认密码', trigger: 'blur' },
+                ],
+            },
+            newPassword: '',
+            strengthType: '',
+            strength: ''
+
+        }
+    },
+    methods: {
+        onSave() {
+            this.$refs.form.validate((valid) => {
+                if (valid) {
+                    this.submit();
+                } else {
+                    return false;
+                }
+            });
+        },
+        submit() {
+
+            if (this.strengthType != 'warning' && this.strengthType != 'success') {
+                this.$message.error('密码必须包含数字与字母混合,无法修改');
+            }
+            else if (this.newPassword != this.formData.confirm) {
+                this.$message.error('新密码不一致');
+            } else if (this.strengthType == "danger") {
+                this.$message.error("密码强度太弱,安全程度不高!");
+            }
+            else {
+                this.formData.newPassword = this.newPassword;
+                var data = JSON.parse(JSON.stringify(this.formData));
+                this.$http.post({
+                    url: '/userInfo/changePassword',
+                    data: data
+                }).then(res => {
+                    if (res.success) {
+                        this.$message.success('成功');
+                        this.$router.go(-1);
+                    } else {
+                        this.$message.warning(res.error)
+                    }
+                });
+            }
+
+
+        },
+        pwStrength() {
+            //正则表达式
+            var is_all_num = /^\d+$/.test(this.newPassword);
+            var have_num = /\d/.test(this.newPassword);
+            var is_all_abc = /^[a-zA-Z]+$/.test(this.newPassword);
+            var have_abc = /[a-zA-Z]/.test(this.newPassword);
+            var have_strong = /[^a-zA-Z0-9]/.test(this.newPassword);
+            var is_very_strong = this.newPassword.split(/[^a-zA-Z_0-9]/).length > 2;
+
+            //空白
+            var tmppwd = this.newPassword.replace(/\s+/g, "");
+            if (tmppwd != this.newPassword) {
+                this.$message.error('新密码不能含有空白字符');
+            }
+
+            /**
+             * 弱:
+             *  1)全部为数字或字母
+             *  2)数字与字母混合且少于8位
+             */
+            if ((is_all_num || is_all_abc) || (have_num && have_abc && this.newPassword.length < 8)) {
+                this.strength = "弱";
+                this.strengthType = "danger";
+            }
+            /**
+              * 中:
+              *  1)数字与字母混合且大于等于8位
+              *  2)数字与字母与其它可打印字符且小于8位
+              *  3)两两组合
+           */
+            if ((have_num && have_abc && this.newPassword.length >= 6) || (have_num && have_abc && have_strong && this.newPassword.length >= 6)
+                || ((have_num && have_strong && this.newPassword.length >= 6) || (have_abc && have_strong && this.newPassword.length >= 6))) {
+                this.strength = "中";
+                this.strengthType = "warning";
+            }
+
+            /**
+                 * 强:
+                 *  1)数字与字母与其它可打印字符且大于等于8位
+                 *  2)数字与字母与其它可打印字符(大于2位)且小于8位
+                 *  3)两两组合大于等于8位
+                 */
+            if ((have_num && have_abc && have_strong && this.newPassword.length >= 8)
+                || (have_num && have_abc && is_very_strong && this.newPassword.length >= 8)
+                || (((have_num && have_strong) || (have_abc && have_strong)) && this.newPassword.length >= 8)) {
+                this.strength = "强";
+                this.strengthType = "success";
+            }
+        }
+    },
+    watch: {
+        newPassword: function () {
+            this.pwStrength();
+        }
+    }
+}
+</script>
+<style lang="less" scoped>
+</style>

+ 6 - 0
src/main/vue/src/router/index.js

@@ -242,6 +242,12 @@ const router = new Router({
                     component: () =>
                         import ('../pages/TestBbbCopy')
                 },
+                {
+                    path: '/changePassword',
+                    name: 'changePassword',
+                    component: () =>
+                        import ('../pages/changePassword')
+                },
                 {
                     path: '/dataSourceInfos',
                     name: 'dataSourceInfos',

+ 1 - 1
src/main/webapp/WEB-INF/html/admin.html

@@ -1 +1 @@
-<!DOCTYPE html><html><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><title>管理后台</title><link rel=icon href=/static/favicon.ico><script src=/static/polyfill.min.js></script><script src=/static/fontawesome-v5.2.0.js></script><link href=/static/css/admin.d791ef1bdd7a2c1225e39648be052101.css rel=stylesheet></head><body><div id=app></div><script type=text/javascript src=/static/js/manifest.86717a4b524af26bed87.js></script><script type=text/javascript src=/static/js/vendor.d2b660c0668e43d11ce1.js></script><script type=text/javascript src=/static/js/admin.9775a41c2188ee44ef24.js></script></body></html>
+<!DOCTYPE html><html><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><title>管理后台</title><link rel=icon href=/static/favicon.ico><script src=/static/polyfill.min.js></script><script src=/static/fontawesome-v5.2.0.js></script><link href=/static/css/admin.a5a3806e960043aed26e73fa944b14c7.css rel=stylesheet></head><body><div id=app></div><script type=text/javascript src=/static/js/manifest.8fddef132d8aca6f05ff.js></script><script type=text/javascript src=/static/js/vendor.daaf8438f908dd26eb02.js></script><script type=text/javascript src=/static/js/admin.f495f77e593ff47dad9e.js></script></body></html>