浏览代码

添加 StoreNode 和 UploadChannel 两个 model, 和其相应的页面及 crud 接口

reghao 2 年之前
父节点
当前提交
881331f003
共有 28 个文件被更改,包括 781 次插入660 次删除
  1. 0 68
      oss-web/src/main/java/cn/reghao/oss/web/app/controller/BuildDeployController.java
  2. 3 5
      oss-web/src/main/java/cn/reghao/oss/web/app/controller/OssController.java
  3. 19 23
      oss-web/src/main/java/cn/reghao/oss/web/app/controller/StoreNodeController.java
  4. 58 0
      oss-web/src/main/java/cn/reghao/oss/web/app/controller/UploadChannelController.java
  5. 9 3
      oss-web/src/main/java/cn/reghao/oss/web/app/controller/page/StatusPageController.java
  6. 20 44
      oss-web/src/main/java/cn/reghao/oss/web/app/controller/page/StoreNodePageController.java
  7. 40 18
      oss-web/src/main/java/cn/reghao/oss/web/app/controller/page/UploadChannelPageController.java
  8. 13 0
      oss-web/src/main/java/cn/reghao/oss/web/app/db/repository/StoreNodeRepository.java
  9. 13 0
      oss-web/src/main/java/cn/reghao/oss/web/app/db/repository/UploadChannelRepository.java
  10. 0 33
      oss-web/src/main/java/cn/reghao/oss/web/app/model/dto/AppConfigUpdateDto.java
  11. 0 42
      oss-web/src/main/java/cn/reghao/oss/web/app/model/dto/CopyAppDto.java
  12. 23 0
      oss-web/src/main/java/cn/reghao/oss/web/app/model/dto/StoreNodeUpdateDto.java
  13. 24 0
      oss-web/src/main/java/cn/reghao/oss/web/app/model/dto/UploadChannelDto.java
  14. 1 6
      oss-web/src/main/java/cn/reghao/oss/web/app/model/po/AppConfig.java
  15. 28 0
      oss-web/src/main/java/cn/reghao/oss/web/app/model/po/StoreNode.java
  16. 33 0
      oss-web/src/main/java/cn/reghao/oss/web/app/model/po/UploadChannel.java
  17. 32 8
      oss-web/src/main/java/cn/reghao/oss/web/app/service/OssService.java
  18. 30 0
      oss-web/src/main/java/cn/reghao/oss/web/app/service/RemoteService.java
  19. 65 0
      oss-web/src/main/java/cn/reghao/oss/web/app/service/StoreNodeService.java
  20. 42 0
      oss-web/src/main/java/cn/reghao/oss/web/app/service/UploadChannelService.java
  21. 34 0
      oss-web/src/main/java/cn/reghao/oss/web/util/JwtUtil.java
  22. 75 0
      oss-web/src/main/resources/templates/app/bd/add.html
  23. 61 78
      oss-web/src/main/resources/templates/app/bd/index.html
  24. 12 48
      oss-web/src/main/resources/templates/app/config/app/detail.html
  25. 6 138
      oss-web/src/main/resources/templates/app/config/app/edit.html
  26. 14 40
      oss-web/src/main/resources/templates/app/config/app/index.html
  27. 59 58
      oss-web/src/main/resources/templates/app/stat/index1.html
  28. 67 48
      oss-web/src/main/resources/templates/app/stat/index2.html

+ 0 - 68
oss-web/src/main/java/cn/reghao/oss/web/app/controller/BuildDeployController.java

@@ -1,68 +0,0 @@
-package cn.reghao.oss.web.app.controller;
-
-import cn.reghao.jutil.jdk.result.WebResult;
-import cn.reghao.oss.api.dto.media.VideoInfo;
-import cn.reghao.oss.api.iface.media.VideoFileService;
-import io.swagger.annotations.Api;
-import io.swagger.annotations.ApiImplicitParam;
-import io.swagger.annotations.ApiImplicitParams;
-import io.swagger.annotations.ApiOperation;
-import lombok.extern.slf4j.Slf4j;
-import org.apache.dubbo.config.annotation.DubboReference;
-import org.springframework.http.MediaType;
-import org.springframework.web.bind.annotation.*;
-
-/**
- * @author reghao
- * @date 2019-08-30 18:49:15
- */
-@Slf4j
-@Api(tags = "应用构建部署接口")
-@RestController
-@RequestMapping("/api/app/bd")
-public class BuildDeployController {
-    @DubboReference(check = false)
-    private VideoFileService videoFileService;
-
-    @ApiOperation(value = "构建部署应用")
-    @ApiImplicitParams(@ApiImplicitParam(name="appId", value="应用 ID", paramType="path", dataType = "String"))
-    @PostMapping(value = "/update/{appId}", produces = MediaType.APPLICATION_JSON_VALUE)
-    public String buildAndDeploy(@PathVariable("appId") String appId) throws Exception {
-        return WebResult.successWithMsg("构建部署请求已提交");
-    }
-
-    @ApiOperation(value = "构建应用")
-    @ApiImplicitParams(@ApiImplicitParam(name="appId", value="应用 ID", paramType="path", dataType = "String"))
-    @PostMapping(value = "/build/{appId}", produces = MediaType.APPLICATION_JSON_VALUE)
-    public String build(@PathVariable("appId") String appId) throws Exception {
-        return WebResult.successWithMsg("构建请求已提交");
-    }
-
-    @ApiOperation(value = "部署应用")
-    @ApiImplicitParams({
-            @ApiImplicitParam(name="buildLogId", value="构建 ID", paramType="path", dataType = "String"),
-            @ApiImplicitParam(name="machineId", value="机器 ID", paramType="path", dataType = "String")
-    })
-    @PostMapping(value = "/deploy/{buildLogId}/{machineId}", produces = MediaType.APPLICATION_JSON_VALUE)
-    public String deploy(@PathVariable("buildLogId") String buildLogId, @PathVariable("machineId") String machineId)
-            throws Exception {
-        if (buildLogId == null || buildLogId.equals("null")) {
-            return WebResult.failWithMsg("应用尚未构建");
-        }
-
-        return WebResult.successWithMsg("部署请求已提交");
-    }
-
-    @ApiOperation(value = "重置应用构建状态")
-    @PostMapping(value = "/reset", produces = MediaType.APPLICATION_JSON_VALUE)
-    public String resetState() {
-        VideoInfo videoInfo = videoFileService.getVideoInfo("abcdefg");
-        return WebResult.successWithMsg("应用构建状态已重置");
-    }
-
-    @ApiOperation(value = "webhook 自动构建部署")
-    @PostMapping("/hook")
-    public String hook(@RequestBody String body) throws Exception {
-        return WebResult.success();
-    }
-}

+ 3 - 5
oss-web/src/main/java/cn/reghao/oss/web/app/controller/OssController.java

@@ -1,10 +1,10 @@
 package cn.reghao.oss.web.app.controller;
 
 import cn.reghao.jutil.jdk.result.WebResult;
+import cn.reghao.oss.api.constant.UploadChannel;
 import cn.reghao.oss.api.dto.DownloadUrl;
 import cn.reghao.oss.api.dto.ObjectInfo;
 import cn.reghao.oss.api.dto.ServerInfo;
-import cn.reghao.oss.api.dto.media.ImageUrlDto;
 import cn.reghao.oss.web.app.service.OssService;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
@@ -14,9 +14,6 @@ import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.RestController;
 
-import java.util.List;
-import java.util.Set;
-
 /**
  * @author reghao
  * @date 2024-02-23 09:27:36
@@ -34,7 +31,8 @@ public class OssController {
     @ApiOperation(value = "获取 oss-store 节点")
     @GetMapping(value = "/server/info", produces = MediaType.APPLICATION_JSON_VALUE)
     public String getServerInfo(@RequestParam("userId") Long userId, @RequestParam("channelId") Integer channelId) {
-        ServerInfo serverInfo = ossService.getServerInfo(userId, channelId);
+        String channelPrefix = UploadChannel.getUploadChannel(channelId).getPrefix();
+        ServerInfo serverInfo = ossService.getServerInfo(channelPrefix, userId, channelId);
         return WebResult.success(serverInfo);
     }
 

+ 19 - 23
oss-web/src/main/java/cn/reghao/oss/web/app/controller/AppConfigController.java → oss-web/src/main/java/cn/reghao/oss/web/app/controller/StoreNodeController.java

@@ -1,10 +1,11 @@
 package cn.reghao.oss.web.app.controller;
 
-import cn.reghao.oss.web.app.model.dto.AppConfigDto;
-import cn.reghao.oss.web.app.model.dto.AppConfigUpdateDto;
-import cn.reghao.oss.web.app.model.dto.CopyAppDto;
+import cn.reghao.jutil.jdk.result.Result;
+import cn.reghao.oss.web.app.model.dto.StoreNodeUpdateDto;
 import cn.reghao.oss.web.app.model.po.AppConfig;
 import cn.reghao.jutil.jdk.result.WebResult;
+import cn.reghao.oss.web.app.model.po.StoreNode;
+import cn.reghao.oss.web.app.service.StoreNodeService;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import lombok.extern.slf4j.Slf4j;
@@ -16,30 +17,31 @@ import java.util.List;
 
 /**
  * @author reghao
- * @date 2019-11-27 11:29:43
+ * @date 2024-02-23 11:29:14
  */
 @Slf4j
-@Api(tags = "应用配置 CRUD 接口")
+@Api(tags = "存储节点数据接口")
 @RestController
-@RequestMapping("/api/app/config/app")
-public class AppConfigController {
-    @ApiOperation(value = "添加应用配置")
-    @PostMapping(produces = MediaType.APPLICATION_JSON_VALUE)
-    public String addAppConfig(@Validated AppConfigDto appConfigDto) {
-        return WebResult.success();
+@RequestMapping("/api/oss/store")
+public class StoreNodeController {
+    private final StoreNodeService storeNodeService;
+
+    public StoreNodeController(StoreNodeService storeNodeService) {
+        this.storeNodeService = storeNodeService;
     }
 
-    // TODO 使用 @PathVariable 注解时会自动填充实体
-    @ApiOperation(value = "复制应用配置")
-    @PostMapping(value = "/copy", produces = MediaType.APPLICATION_JSON_VALUE)
-    public String copyAppConfig(@Validated CopyAppDto copyAppDto) {
+    @ApiOperation(value = "添加应用配置")
+    @PostMapping(value = "/node", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String addAppConfig(@RequestBody @Validated StoreNode storeNode) {
+        storeNodeService.add(storeNode);
         return WebResult.success();
     }
 
     @ApiOperation(value = "修改应用配置")
     @PostMapping(value = "/update", produces = MediaType.APPLICATION_JSON_VALUE)
-    public String updateAppConfig(@Validated AppConfigUpdateDto appConfigUpdateDto) {
-        return WebResult.success();
+    public String updateAppConfig(@Validated StoreNodeUpdateDto storeNodeUpdateDto) {
+        Result result = storeNodeService.updateDomain(storeNodeUpdateDto);
+        return WebResult.result(result);
     }
 
     @ApiOperation(value = "删除应用配置")
@@ -49,12 +51,6 @@ public class AppConfigController {
         return WebResult.success();
     }
 
-    @ApiOperation(value = "清空应用本地仓库")
-    @DeleteMapping(value = "/repo/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
-    public String deleteAppLocalRepo(@PathVariable("id") AppConfig app) {
-        return WebResult.success();
-    }
-
     @PostMapping(value = "/enable", produces = MediaType.APPLICATION_JSON_VALUE)
     public String deleteAll(@RequestParam(value = "ids") List<String> appIds) {
         return WebResult.success();

+ 58 - 0
oss-web/src/main/java/cn/reghao/oss/web/app/controller/UploadChannelController.java

@@ -0,0 +1,58 @@
+package cn.reghao.oss.web.app.controller;
+
+import cn.reghao.jutil.jdk.result.WebResult;
+import cn.reghao.oss.web.app.model.dto.UploadChannelDto;
+import cn.reghao.oss.web.app.service.UploadChannelService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.MediaType;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * @author reghao
+ * @date 2024-02-23 15:05:30
+ */
+@Slf4j
+@Api(tags = "上传通道接口")
+@RestController
+@RequestMapping("/api/channel/upload")
+public class UploadChannelController {
+    private final UploadChannelService uploadChannelService;
+
+    public UploadChannelController(UploadChannelService uploadChannelService) {
+        this.uploadChannelService = uploadChannelService;
+    }
+
+    @ApiOperation(value = "添加上传通道")
+    @PostMapping(value = "/add", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String buildAndDeploy(@Validated UploadChannelDto uploadChannelDto) {
+        uploadChannelService.add(uploadChannelDto);
+        return WebResult.success();
+    }
+
+    @ApiOperation(value = "构建应用")
+    @PostMapping(value = "/build/appId", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String build() throws Exception {
+        return WebResult.successWithMsg("构建请求已提交");
+    }
+
+    @ApiOperation(value = "部署应用")
+    @PostMapping(value = "/deploy", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String deploy() {
+        return WebResult.success();
+    }
+
+    @ApiOperation(value = "重置应用构建状态")
+    @PostMapping(value = "/reset", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String resetState() {
+        return WebResult.success();
+    }
+
+    @ApiOperation(value = "webhook 自动构建部署")
+    @PostMapping("/hook")
+    public String hook() throws Exception {
+        return WebResult.success();
+    }
+}

+ 9 - 3
oss-web/src/main/java/cn/reghao/oss/web/app/controller/page/StatusPageController.java

@@ -23,12 +23,12 @@ import java.util.*;
  * @date 2019-08-30 18:49:15
  */
 @Slf4j
-@Api(tags = "应用状态页面")
+@Api(tags = "我的节点页面")
 @Controller
 @RequestMapping("/app/stat")
 public class StatusPageController {
-    @ApiOperation(value = "应用运行状态列表页面")
-    @GetMapping
+    @ApiOperation(value = "我的节点列表页面")
+    @GetMapping(value = "/list")
     public String statusPage1(@RequestParam(value = "env", required = false) String env,
                               @RequestParam(value = "appType", required = false) String appType,
                               @RequestParam(value = "status", required = false) Boolean status,
@@ -42,6 +42,12 @@ public class StatusPageController {
         return "/app/stat/index1";
     }
 
+    @ApiOperation(value = "我的节点创建页面")
+    @GetMapping(value = "/add")
+    public String statusPage(Model model) {
+        return "/app/stat/index2";
+    }
+
     @ApiOperation(value = "应用运行状态详情页面")
     @GetMapping(value = "/detail/{appId}")
     public String statusPage2(@PathVariable(value = "appId") String appId, Model model) {

+ 20 - 44
oss-web/src/main/java/cn/reghao/oss/web/app/controller/page/AppConfigPageController.java → oss-web/src/main/java/cn/reghao/oss/web/app/controller/page/StoreNodePageController.java

@@ -1,7 +1,8 @@
 package cn.reghao.oss.web.app.controller.page;
 
+import cn.reghao.oss.web.app.db.repository.StoreNodeRepository;
 import cn.reghao.oss.web.app.model.po.AppConfig;
-import cn.reghao.oss.web.app.model.vo.KeyValue;
+import cn.reghao.oss.web.app.model.po.StoreNode;
 import cn.reghao.oss.web.util.DefaultSetting;
 import cn.reghao.oss.web.util.db.PageSort;
 import cn.reghao.oss.web.app.db.query.AppConfigQuery;
@@ -16,24 +17,25 @@ import org.springframework.ui.Model;
 import org.springframework.web.bind.annotation.*;
 
 import java.util.*;
-import java.util.stream.Collectors;
 
 /**
  * @author reghao
- * @date 2019-08-30 18:49:15
+ * @date 2024-02-23 11:29:14
  */
 @Slf4j
 @Api(tags = "应用配置页面")
 @Controller
 @RequestMapping("/app/config/app")
-public class AppConfigPageController {
+public class StoreNodePageController {
     private final AppConfigQuery appConfigQuery;
+    private final StoreNodeRepository storeNodeRepository;
 
-    public AppConfigPageController(AppConfigQuery appConfigQuery) {
+    public StoreNodePageController(AppConfigQuery appConfigQuery, StoreNodeRepository storeNodeRepository) {
         this.appConfigQuery = appConfigQuery;
+        this.storeNodeRepository = storeNodeRepository;
     }
 
-    @ApiOperation(value = "应用配置页面")
+    @ApiOperation(value = "存储节点列表页面")
     @GetMapping
     public String appConfigPage(@RequestParam(value = "env", required = false) String env,
                                 @RequestParam(value = "type", required = false) String type,
@@ -61,53 +63,27 @@ public class AppConfigPageController {
         }
 
         PageRequest pageRequest = PageSort.pageRequest();
-        Page<AppConfig> appPage = appConfigQuery.findByEnvAndType(env, type, pageRequest);
+        Page<StoreNode> page = storeNodeRepository.findAll(pageRequest);
 
         model.addAttribute("env", env);
         model.addAttribute("type", type);
-        model.addAttribute("page", appPage);
-        model.addAttribute("list", appPage.getContent());
+        model.addAttribute("page", page);
+        model.addAttribute("list", page.getContent());
         return "/app/config/app/index";
     }
 
-    @GetMapping("/add")
-    public String addAppConfigPage(Model model) {
-        setAppModel(model);
-        return "/app/config/app/add";
-    }
-
-    private void setAppModel(Model model) {
-        List<KeyValue> packTypes = new ArrayList<>();
-        setCommon(model);
-    }
-
-    private void setCommon(Model model) {
-    }
-
-    @GetMapping("/edit/{id}")
-    public String editAppConfigPage(@PathVariable("id") AppConfig app, Model model) {
-        setAppModel(model);
-        model.addAttribute("app", app);
+    @ApiOperation(value = "存储节点域名设置页面")
+    @GetMapping("/domain/{id}")
+    public String editAppConfigPage(@PathVariable("id") StoreNode storeNode, Model model) {
+        model.addAttribute("storeNode", storeNode);
         return "/app/config/app/edit";
     }
 
-    @GetMapping("/copy/{appId}")
-    public String copyAppConfigPage(@PathVariable("appId") String appId, Model model) {
-        List<KeyValue> envs = getEnvList();
-        model.addAttribute("environments", envs);
-        model.addAttribute("appId", appId);
-        return "/app/config/app/copy";
-    }
-
-    private List<KeyValue> getEnvList() {
-        List<KeyValue> envs = new ArrayList<>();
-        return envs;
-    }
-
-    @GetMapping("/detail/{appId}")
-    public String appConfigPage(@PathVariable("appId") String appId, Model model) {
-        AppConfig app = appConfigQuery.findByAppId(appId);
-        model.addAttribute("app", app);
+    @ApiOperation(value = "存储节点详情页面")
+    @GetMapping("/detail/{id}")
+    public String appConfigPage(@PathVariable("id") Integer id, Model model) {
+        StoreNode storeNode = storeNodeRepository.findById(id).orElse(null);
+        model.addAttribute("storeNode", storeNode);
         return "/app/config/app/detail";
     }
 }

+ 40 - 18
oss-web/src/main/java/cn/reghao/oss/web/app/controller/page/BuildDeployPageController.java → oss-web/src/main/java/cn/reghao/oss/web/app/controller/page/UploadChannelPageController.java

@@ -1,8 +1,11 @@
 package cn.reghao.oss.web.app.controller.page;
 
+import cn.reghao.oss.web.app.db.repository.UploadChannelRepository;
+import cn.reghao.oss.web.app.model.po.UploadChannel;
+import cn.reghao.oss.web.app.model.vo.KeyValue;
+import cn.reghao.oss.web.app.service.StoreNodeService;
 import cn.reghao.oss.web.util.DefaultSetting;
 import cn.reghao.oss.web.util.db.PageSort;
-import cn.reghao.jutil.jdk.db.PageList;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import lombok.extern.slf4j.Slf4j;
@@ -12,17 +15,26 @@ import org.springframework.ui.Model;
 import org.springframework.web.bind.annotation.*;
 
 import java.util.*;
+import java.util.stream.Collectors;
 
 /**
  * @author reghao
- * @date 2019-08-30 18:49:15
+ * @date 2024-02-23 15:05:30
  */
 @Slf4j
-@Api(tags = "构建部署页面")
+@Api(tags = "上传通道页面")
 @Controller
 @RequestMapping("/app/bd")
-public class BuildDeployPageController {
-    @ApiOperation(value = "应用构建页面")
+public class UploadChannelPageController {
+    private final UploadChannelRepository uploadChannelRepository;
+    private final StoreNodeService storeNodeService;
+
+    public UploadChannelPageController(UploadChannelRepository uploadChannelRepository, StoreNodeService storeNodeService) {
+        this.uploadChannelRepository = uploadChannelRepository;
+        this.storeNodeService = storeNodeService;
+    }
+
+    @ApiOperation(value = "上传通道列表页面")
     @GetMapping(value = "/build")
     public String index(@RequestParam(value = "env", required = false) String env,
                         @RequestParam(value = "type", required = false) String type,
@@ -36,24 +48,34 @@ public class BuildDeployPageController {
         }
 
         PageRequest pageRequest = PageSort.pageRequest();
-        int pageNumber = pageRequest.getPageNumber()+1;
-        int pageSize = pageRequest.getPageSize();
+        Page<UploadChannel> page = uploadChannelRepository.findAll(pageRequest);
+
         model.addAttribute("env", env);
         model.addAttribute("type", type);
-        model.addAttribute("page", Page.empty());
-        model.addAttribute("list", Collections.emptyList());
+        model.addAttribute("page", page);
+        model.addAttribute("list", page.getContent());
         return "/app/bd/index";
     }
 
-    @ApiOperation(value = "应用部署页面")
-    @GetMapping("/deploy")
-    public String deployPage(@RequestParam("appId") String appId,
-                             @RequestParam("buildLogId") String buildLogId,
-                             Model model) {
-        model.addAttribute("appId", appId);
-        model.addAttribute("buildLogId", buildLogId);
-        model.addAttribute("list", Collections.emptyList());
-        return "/app/bd/deploy";
+    @ApiOperation(value = "新增上传通道页面")
+    @GetMapping("/add")
+    public String deployPage(Model model) {
+        List<KeyValue> list1 = new ArrayList<>();
+        list1.add(new KeyValue(1024L*1024*2+"", "2MiB"));
+        list1.add(new KeyValue(1024L*1024*10+"", "10MiB"));
+        list1.add(new KeyValue(1024L*1024*100+"", "100MiB"));
+        list1.add(new KeyValue(1024L*1024*1024+"", "1GiB"));
+        list1.add(new KeyValue(1024L*1024*1024*10+"", "10GiB"));
+
+        List<KeyValue> list = storeNodeService.getStoreNodes().stream()
+                .map(storeNode -> {
+                    String domain = storeNode.getDomain();
+                    return new KeyValue(domain, domain);
+                }).collect(Collectors.toList());
+
+        model.addAttribute("sizeList", list1);
+        model.addAttribute("storeNodes", list);
+        return "/app/bd/add";
     }
 
     @ApiOperation(value = "构建结果页面")

+ 13 - 0
oss-web/src/main/java/cn/reghao/oss/web/app/db/repository/StoreNodeRepository.java

@@ -0,0 +1,13 @@
+package cn.reghao.oss.web.app.db.repository;
+
+import cn.reghao.oss.web.app.model.po.StoreNode;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+/**
+ * @author reghao
+ * @date 2024-02-23 11:31:05
+ */
+public interface StoreNodeRepository extends JpaRepository<StoreNode, Integer> {
+    StoreNode findByIpv4Addr(String ipv4Addr);
+    StoreNode findByDomain(String domain);
+}

+ 13 - 0
oss-web/src/main/java/cn/reghao/oss/web/app/db/repository/UploadChannelRepository.java

@@ -0,0 +1,13 @@
+package cn.reghao.oss.web.app.db.repository;
+
+import cn.reghao.oss.web.app.model.po.UploadChannel;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+/**
+ * @author reghao
+ * @date 2024-02-23 15:05:30
+ */
+public interface UploadChannelRepository extends JpaRepository<UploadChannel, Integer> {
+    /*UploadChannel findByCode(int code);*/
+    UploadChannel findByPrefix(String prefix);
+}

+ 0 - 33
oss-web/src/main/java/cn/reghao/oss/web/app/model/dto/AppConfigUpdateDto.java

@@ -1,33 +0,0 @@
-package cn.reghao.oss.web.app.model.dto;
-
-import lombok.Getter;
-import lombok.NoArgsConstructor;
-import lombok.Setter;
-
-import javax.validation.constraints.NotBlank;
-
-/**
- * @author reghao
- * @date 2023-03-10 15:00:20
- */
-@NoArgsConstructor
-@Setter
-@Getter
-public class AppConfigUpdateDto {
-    private String appId;
-    @NotBlank(message = "必须指定应用名字")
-    private String appName;
-    @NotBlank(message = "必须指定仓库分支")
-    private String repoBranch;
-    private String appRootPath;
-    private String bindPorts;
-
-    // buildConfig
-    @NotBlank(message = "必须指定仓库认证")
-    private String repoAuthConfig;
-    @NotBlank(message = "必须指定编译配置")
-    private String compilerConfig;
-    @NotBlank(message = "必须指定打包配置")
-    private String packerConfig;
-    private String dockerfile;
-}

+ 0 - 42
oss-web/src/main/java/cn/reghao/oss/web/app/model/dto/CopyAppDto.java

@@ -1,42 +0,0 @@
-package cn.reghao.oss.web.app.model.dto;
-
-import cn.reghao.oss.web.app.model.po.AppConfig;
-import lombok.Data;
-import org.hibernate.validator.constraints.Length;
-
-import javax.validation.constraints.NotBlank;
-import javax.validation.constraints.Pattern;
-import java.io.Serializable;
-
-/**
- * @author reghao
- * @date 2021-06-03 16:15:23
- */
-@Data
-public class CopyAppDto implements Serializable {
-    private static final long serialVersionUID = 1L;
-
-    @Pattern(regexp = "^\\S*$", message = "应用 ID 不能包含空白符")
-    @Length(max = 20, message = "应用 ID 的最大长度为 20 个字符")
-    private String appId;
-    @Pattern(regexp = "^\\S*$", message = "应用 ID 不能包含空白符")
-    @Length(max = 20, message = "应用 ID 的最大长度为 20 个字符")
-    private String newAppId;
-    @NotBlank(message = "新应用环境不能为空白字符串")
-    @Length(max = 20, message = "新应用环境最大长度为 10 个字符")
-    private String newEnv;
-    @NotBlank(message = "新仓库分支不能为空白字符串")
-    @Length(max = 20, message = "新仓库分支最大长度为 40 个字符")
-    private String newRepoBranch;
-
-    public AppConfig app(AppConfig from) {
-        from.setAppId(newAppId);
-        from.setEnv(newEnv);
-        from.setRepoBranch(newRepoBranch);
-
-        from.setId(null);
-        from.setCreateTime(null);
-        from.setUpdateTime(null);
-        return from;
-    }
-}

+ 23 - 0
oss-web/src/main/java/cn/reghao/oss/web/app/model/dto/StoreNodeUpdateDto.java

@@ -0,0 +1,23 @@
+package cn.reghao.oss.web.app.model.dto;
+
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+
+/**
+ * @author reghao
+ * @date 2023-03-10 15:00:20
+ */
+@NoArgsConstructor
+@Setter
+@Getter
+public class StoreNodeUpdateDto {
+    @NotNull
+    private Integer id;
+    private String ipv4Addr;
+    @NotBlank(message = "必须指定域名")
+    private String domain;
+}

+ 24 - 0
oss-web/src/main/java/cn/reghao/oss/web/app/model/dto/UploadChannelDto.java

@@ -0,0 +1,24 @@
+package cn.reghao.oss.web.app.model.dto;
+
+import lombok.Data;
+import org.hibernate.validator.constraints.Length;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+
+/**
+ * @author reghao
+ * @date 2021-06-03 16:15:23
+ */
+@Data
+public class UploadChannelDto {
+    @NotBlank
+    private String domain;
+    @NotBlank
+    private String channelPrefix;
+    @NotNull
+    private Long maxSize;
+    @NotBlank(message = "")
+    @Length(max = 20, message = "新仓库分支最大长度为 40 个字符")
+    private String description;
+}

+ 1 - 6
oss-web/src/main/java/cn/reghao/oss/web/app/model/po/AppConfig.java

@@ -20,7 +20,7 @@ import javax.validation.constraints.NotNull;
 @Getter
 @Setter
 @Entity
-public class AppConfig extends BaseEntity implements Cloneable {
+public class AppConfig extends BaseEntity {
     @Column(nullable = false, unique = true)
     @NotBlank(message = "应用 ID 不能为空字符串")
     @Length(min = 4, max = 64, message = "应用 ID 仅可包含数字,大小写字母和 '-' 等字符, 长度为 4 ~ 64 个字符")
@@ -53,9 +53,4 @@ public class AppConfig extends BaseEntity implements Cloneable {
     @Lob
     @Column(columnDefinition="text")
     private String dockerfile;
-
-    @Override
-    public Object clone() throws CloneNotSupportedException {
-        return super.clone();
-    }
 }

+ 28 - 0
oss-web/src/main/java/cn/reghao/oss/web/app/model/po/StoreNode.java

@@ -0,0 +1,28 @@
+package cn.reghao.oss.web.app.model.po;
+
+import cn.reghao.oss.web.util.db.BaseEntity;
+import lombok.Getter;
+import lombok.Setter;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.validation.constraints.NotNull;
+
+/**
+ * @author reghao
+ * @date 2024-02-23 11:29:14
+ */
+@Getter
+@Setter
+@Entity
+public class StoreNode extends BaseEntity {
+    @Column(nullable = false, unique = true)
+    private String ipv4Addr;
+    @NotNull
+    private Integer httpPort;
+    @NotNull
+    private Integer rpcPort;
+    private String domain;
+    private Long total;
+    private Long avail;
+}

+ 33 - 0
oss-web/src/main/java/cn/reghao/oss/web/app/model/po/UploadChannel.java

@@ -0,0 +1,33 @@
+package cn.reghao.oss.web.app.model.po;
+
+import cn.reghao.oss.web.app.model.dto.UploadChannelDto;
+import cn.reghao.oss.web.util.db.BaseEntity;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+import javax.persistence.Entity;
+
+/**
+ * @author reghao
+ * @date 2024-02-23 15:05:30
+ */
+@NoArgsConstructor
+@Getter
+@Setter
+@Entity
+public class UploadChannel extends BaseEntity {
+/*    private String name;
+    private Integer code;*/
+    private String prefix;
+    private Long maxSize;
+    private String description;
+    private String bindDomain;
+
+    public UploadChannel(UploadChannelDto uploadChannelDto) {
+        this.prefix = uploadChannelDto.getChannelPrefix();
+        this.maxSize = uploadChannelDto.getMaxSize();
+        this.description = uploadChannelDto.getDescription();
+        this.bindDomain = uploadChannelDto.getDomain();
+    }
+}

+ 32 - 8
oss-web/src/main/java/cn/reghao/oss/web/app/service/OssService.java

@@ -1,9 +1,11 @@
 package cn.reghao.oss.web.app.service;
 
+import cn.reghao.oss.api.constant.ChannelAction;
 import cn.reghao.oss.api.dto.*;
 import cn.reghao.oss.api.iface.ObjectService;
-import cn.reghao.oss.api.iface.OssServerService;
-import org.apache.dubbo.config.annotation.DubboReference;
+import cn.reghao.oss.web.app.db.repository.UploadChannelRepository;
+import cn.reghao.oss.web.app.model.po.UploadChannel;
+import cn.reghao.oss.web.util.JwtUtil;
 import org.springframework.stereotype.Service;
 
 import java.util.Collections;
@@ -15,20 +17,42 @@ import java.util.List;
  */
 @Service
 public class OssService {
-    @DubboReference(check = false)
-    private OssServerService ossServerService;
-    @DubboReference(check = false)
-    private ObjectService objectService;
+    private final UploadChannelRepository uploadChannelRepository;
 
-    public ServerInfo getServerInfo(long userId, int channelId) {
-        return ossServerService.getServerInfo(userId, channelId);
+    public OssService(UploadChannelRepository uploadChannelRepository) {
+        this.uploadChannelRepository = uploadChannelRepository;
+    }
+
+    public ServerInfo getServerInfo(String channelPrefix, long userId, int channelId) {
+        UploadChannel uploadChannel = uploadChannelRepository.findByPrefix(channelPrefix);
+        if (uploadChannel == null) {
+            return null;
+        }
+
+        String domain = uploadChannel.getBindDomain();
+        String ossUrl = String.format("https://%s/", domain);
+        long maxSize = uploadChannel.getMaxSize();
+
+        String action = ChannelAction.upload.getName();
+        long expireAt = System.currentTimeMillis() + 3600*1000;
+        OssPayload ossPayload = new OssPayload(action, channelId, userId);
+        String token = JwtUtil.createToken(ossPayload, expireAt, "secretKey");
+        return new ServerInfo(ossUrl, channelId, maxSize, token);
     }
 
     public ObjectInfo getObjectInfo(String objectId) {
+        RemoteService<ObjectService> remoteService = new RemoteService<>();
+        String host = "";
+        int port = 11011;
+        ObjectService objectService = remoteService.getService(host, port, ObjectService.class);
         return objectService.getObjectInfo(objectId);
     }
 
     public DownloadUrl getDownloadUrl(String objectId, int channelId, long userId) {
+        RemoteService<ObjectService> remoteService = new RemoteService<>();
+        String host = "";
+        int port = 11011;
+        ObjectService objectService = remoteService.getService(host, port, ObjectService.class);
         return objectService.getDownloadUrl(objectId, channelId, userId);
     }
 

+ 30 - 0
oss-web/src/main/java/cn/reghao/oss/web/app/service/RemoteService.java

@@ -0,0 +1,30 @@
+package cn.reghao.oss.web.app.service;
+
+import cn.reghao.oss.api.iface.media.VideoFileService;
+import org.apache.dubbo.config.ApplicationConfig;
+import org.apache.dubbo.config.ReferenceConfig;
+
+/**
+ * @author reghao
+ * @date 2024-02-23 13:19:20
+ */
+public class RemoteService<T> {
+    public T getService(String host, int port, Class<T> clazz) {
+        String serviceName = "remote-service";
+        String interfaceName = VideoFileService.class.getName();
+        String dubboUrl = String.format("dubbo://%s:%s/%s", host, port, clazz.getName());
+
+        // 当前应用配置
+        ApplicationConfig application = new ApplicationConfig();
+        application.setName(serviceName);
+
+        // 注意:ReferenceConfig为重对象,内部封装了与注册中心的连接,以及与服务提供方的连接
+        // 引用远程服务
+        // 此实例很重,封装了与注册中心的连接以及与提供者的连接,请自行缓存,否则可能造成内存和连接泄漏
+        ReferenceConfig<T> reference = new ReferenceConfig<>();
+        reference.setApplication(application);
+        reference.setInterface(clazz);
+        reference.setUrl(dubboUrl);
+        return reference.get();
+    }
+}

+ 65 - 0
oss-web/src/main/java/cn/reghao/oss/web/app/service/StoreNodeService.java

@@ -0,0 +1,65 @@
+package cn.reghao.oss.web.app.service;
+
+import cn.reghao.jutil.jdk.result.Result;
+import cn.reghao.oss.web.app.db.repository.StoreNodeRepository;
+import cn.reghao.oss.web.app.model.dto.StoreNodeUpdateDto;
+import cn.reghao.oss.web.app.model.po.StoreNode;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * @author reghao
+ * @date 2024-02-23 13:01:38
+ */
+@Service
+public class StoreNodeService {
+    private final StoreNodeRepository storeNodeRepository;
+
+    public StoreNodeService(StoreNodeRepository storeNodeRepository) {
+        this.storeNodeRepository = storeNodeRepository;
+    }
+
+    public void add(StoreNode storeNode) {
+        String ipv4 = storeNode.getIpv4Addr();
+        StoreNode entity = storeNodeRepository.findByIpv4Addr(ipv4);
+        if (entity == null) {
+            storeNodeRepository.save(storeNode);
+        }
+    }
+
+    public Result updateDomain(StoreNodeUpdateDto storeNodeUpdateDto) {
+        int id = storeNodeUpdateDto.getId();
+        StoreNode storeNode = storeNodeRepository.findById(id).orElse(null);
+        if (storeNode == null) {
+            return Result.fail(String.format("id %s not exist", id));
+        }
+
+        String domain = storeNodeUpdateDto.getDomain();
+        StoreNode entity = storeNodeRepository.findByDomain(domain);
+        if (entity != null) {
+            return Result.fail(String.format("domain %s exist", domain));
+        }
+
+        if (domain.equals(storeNode.getDomain())) {
+            return Result.success();
+        }
+
+        storeNode.setDomain(domain);
+        storeNodeRepository.save(storeNode);
+        return Result.success();
+    }
+
+    public StoreNode get() {
+        List<StoreNode> list = storeNodeRepository.findAll();
+        if (list.isEmpty()) {
+            return null;
+        }
+
+        return list.get(0);
+    }
+
+    public List<StoreNode> getStoreNodes() {
+        return storeNodeRepository.findAll();
+    }
+}

+ 42 - 0
oss-web/src/main/java/cn/reghao/oss/web/app/service/UploadChannelService.java

@@ -0,0 +1,42 @@
+package cn.reghao.oss.web.app.service;
+
+import cn.reghao.jutil.jdk.result.Result;
+import cn.reghao.oss.web.app.db.repository.StoreNodeRepository;
+import cn.reghao.oss.web.app.db.repository.UploadChannelRepository;
+import cn.reghao.oss.web.app.model.dto.UploadChannelDto;
+import cn.reghao.oss.web.app.model.po.StoreNode;
+import cn.reghao.oss.web.app.model.po.UploadChannel;
+import org.springframework.stereotype.Service;
+
+/**
+ * @author reghao
+ * @date 2024-02-23 15:14:53
+ */
+@Service
+public class UploadChannelService {
+    private final UploadChannelRepository uploadChannelRepository;
+    private final StoreNodeRepository storeNodeRepository;
+
+    public UploadChannelService(UploadChannelRepository uploadChannelRepository, StoreNodeRepository storeNodeRepository) {
+        this.uploadChannelRepository = uploadChannelRepository;
+        this.storeNodeRepository = storeNodeRepository;
+    }
+
+    public Result add(UploadChannelDto uploadChannelDto) {
+        String domain = uploadChannelDto.getDomain();
+        StoreNode storeNode = storeNodeRepository.findByDomain(domain);
+        if (storeNode == null) {
+            return Result.fail(String.format("store_node with domain %s not exist", domain));
+        }
+
+        String channelPrefix = uploadChannelDto.getChannelPrefix();
+        UploadChannel uploadChannel = uploadChannelRepository.findByPrefix(channelPrefix);
+        if (uploadChannel != null) {
+            return Result.fail(String.format("channel_prefix %s exist", channelPrefix));
+        }
+
+        uploadChannel = new UploadChannel(uploadChannelDto);
+        uploadChannelRepository.save(uploadChannel);
+        return Result.success();
+    }
+}

+ 34 - 0
oss-web/src/main/java/cn/reghao/oss/web/util/JwtUtil.java

@@ -0,0 +1,34 @@
+package cn.reghao.oss.web.util;
+
+import cn.reghao.oss.api.dto.OssPayload;
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.SignatureAlgorithm;
+
+import java.util.Date;
+
+/**
+ * JWT 令牌
+ *
+ * @author reghao
+ * @date 2023-08-23 09:10:58
+ */
+public class JwtUtil {
+    public static String createToken(OssPayload ossPayload, long expireAt, String signKey) {
+        return Jwts.builder()
+                .claim("action", ossPayload.getAction())
+                .claim("channelId", ossPayload.getChannelId())
+                .setSubject(String.valueOf(ossPayload.getUserId()))
+                .setExpiration(new Date(expireAt))
+                .signWith(SignatureAlgorithm.HS256, signKey)
+                .compact();
+    }
+
+    public static OssPayload getOssPayload(String token, String signKey) {
+        Claims claims = Jwts.parser().setSigningKey(signKey).parseClaimsJws(token).getBody();
+        String action = (String) claims.get("action");
+        int channelId = (Integer) claims.get("channelId");
+        String userIdStr = claims.getSubject();
+        return new OssPayload(action, channelId, Long.parseLong(userIdStr));
+    }
+}

+ 75 - 0
oss-web/src/main/resources/templates/app/bd/add.html

@@ -0,0 +1,75 @@
+<!DOCTYPE html>
+<html xmlns:th="http://www.thymeleaf.org">
+<head th:replace="/common/template :: header(~{::title},~{::link},~{::style})"></head>
+
+<body>
+<div class="layui-form timo-compile">
+    <form th:action="@{/api/channel/upload/add}">
+        <table class="layui-table timo-detail-table">
+            <tbody>
+            <tr>
+                <th>
+                    <label class="layui-form-label required">存储节点</label>
+                </th>
+                <td>
+                    <div class="layui-form-item">
+                        <div class="layui-input-inline">
+                            <select name="domain">
+                                <option th:each="item : ${storeNodes}" th:value="${item.key}">[[${item.value}]]</option>
+                            </select>
+                        </div>
+                    </div>
+                </td>
+            </tr>
+            <tr>
+                <th>
+                    <label class="layui-form-label required">通道前缀</label>
+                </th>
+                <td>
+                    <div class="layui-form-item">
+                        <div class="layui-input-inline">
+                            <label>
+                                <input class="layui-input" type="text" name="channelPrefix" placeholder="请输入通道前缀" required>
+                            </label>
+                        </div>
+                    </div>
+                </td>
+            </tr>
+            <tr>
+                <th>
+                    <label class="layui-form-label required">最大文件</label>
+                </th>
+                <td>
+                    <div class="layui-form-item">
+                        <div class="layui-input-inline">
+                            <select name="maxSize">
+                                <option th:each="item : ${sizeList}" th:value="${item.key}">[[${item.value}]]</option>
+                            </select>
+                        </div>
+                    </div>
+                </td>
+            </tr>
+            <tr>
+                <th>
+                    <label class="layui-form-label required">通道简介</label>
+                </th>
+                <td>
+                    <div class="layui-form-item">
+                        <div class="layui-input-inline">
+                            <textarea class="layui-textarea" name="description" placeholder="请填写通道简介"></textarea>
+                        </div>
+                    </div>
+                </td>
+            </tr>
+            </tbody>
+        </table>
+        <div class="layui-form-item timo-finally">
+            <button class="layui-btn ajax-submit"><i class="fa fa-check-circle"></i> 保存</button>
+            <button class="layui-btn btn-secondary close-popup"><i class="fa fa-times-circle"></i> 关闭</button>
+        </div>
+    </form>
+</div>
+
+<script th:replace="/common/template :: script"></script>
+</body>
+</html>

+ 61 - 78
oss-web/src/main/resources/templates/app/bd/index.html

@@ -1,49 +1,54 @@
 <!DOCTYPE html>
 <html xmlns:th="http://www.thymeleaf.org"
       xmlns:mo="https://gitee.com/aun/Timo">
-<head th:replace="/common/template :: header(~{::title},~{::link},~{::style})"></head>
+<head th:replace="/common/template :: header(~{::title},~{::link},~{::style})">
+    <link rel="stylesheet" th:href="@{/lib/zTree_v3/css/zTreeStyle/zTreeStyle.css}" type="text/css">
+</head>
 
 <body class="timo-layout-page">
-<div class="layui-card" th:attr="data-url=@{'/app/build/list?env=' + ${env}}">
+<div class="layui-card">
     <div class="layui-card-header timo-card-header">
-        <span><i class="fa fa-bars"></i> 构建部署</span>
+        <span><i class="fa fa-bars"></i> 上传通道列表</span>
         <i class="layui-icon layui-icon-refresh refresh-btn"></i>
     </div>
     <div class="layui-card-body">
         <div class="layui-row timo-card-screen put-row">
-            <div class="pull-left layui-form-pane">
-                <div class="layui-inline">
-                    <label class="layui-form-label">环境</label>
-                    <div class="layui-input-block timo-search-status">
-                        <select id="getPageByEnv" class="timo-search-select" name="env" onchange="getPageByCriteria()"
-                                mo:dict="ENVIRONMENT" mo-selected="${env}"></select>
+            <div class="layui-row timo-card-screen put-row">
+                <div class="pull-left layui-form-pane">
+                    <div class="layui-inline">
+                        <label class="layui-form-label">环境</label>
+                        <div class="layui-input-block timo-search-status">
+                            <select id="getPageByEnv" class="timo-search-select" name="env" onchange="getPageByCriteria()"
+                                    mo:dict="ENVIRONMENT" mo-selected="${env}"></select>
+                        </div>
                     </div>
-                </div>
-                <div class="layui-inline">
-                    <label class="layui-form-label">类型</label>
-                    <div class="layui-input-block timo-search-status">
-                        <select id="getPageByType" class="timo-search-select" name="type" onchange="getPageByCriteria()"
-                                mo:dict="APP_TYPE" mo-selected="${type}"></select>
+                    <div class="layui-inline">
+                        <label class="layui-form-label">类型</label>
+                        <div class="layui-input-block timo-search-status">
+                            <select id="getPageByType" class="timo-search-select" name="type" onchange="getPageByCriteria()"
+                                    mo:dict="APP_TYPE" mo-selected="${type}"></select>
+                        </div>
                     </div>
-                </div>
-                <div class="layui-inline timo-search-box">
-                    <label class="layui-form-label">应用名</label>
-                    <div class="layui-input-block">
-                        <input type="text" name="appName" th:value="${param.appName}" placeholder="请输入应用名"
-                               autocomplete="off" class="layui-input">
+                    <div class="layui-inline timo-search-box">
+                        <label class="layui-form-label">应用</label>
+                        <div class="layui-input-block">
+                            <input type="text" name="appName" th:value="${param.appName}" placeholder="请输入应用名"
+                                   autocomplete="off" class="layui-input">
+                        </div>
+                    </div>
+                    <div class="layui-inline">
+                        <button class="layui-btn timo-search-btn">
+                            <i class="fa fa-search"></i>
+                        </button>
                     </div>
                 </div>
-                <div class="layui-inline">
-                    <button class="layui-btn timo-search-btn">
-                        <i class="fa fa-search"></i>
-                    </button>
-                </div>
-            </div>
-            <div class="pull-right">
-                <div class="btn-group-right">
-                    <button class="layui-btn">
-                        <i class="fa fa-recycle"><a class="ajax-post" th:href="@{/api/app/bd/reset}">重置</a></i>
-                    </button>
+                <div class="pull-right">
+                    <div class="btn-group-right">
+                        <button class="layui-btn open-popup" data-title="添加应用" th:attr="data-url=@{/app/bd/add}"
+                                data-size="640,480">
+                            <i class="fa fa-plus"></i> 添加
+                        </button>
+                    </div>
                 </div>
             </div>
         </div>
@@ -51,47 +56,36 @@
             <table class="layui-table timo-table">
                 <thead>
                 <tr>
-                    <th class="sortable" data-field="appName">应用</th>
-                    <th class="sortable" data-field="appType">类型</th>
-                    <th class="sortable" data-field="repoBranch">分支</th>
-                    <th class="sortable" data-field="bindPorts">监听端口</th>
-                    <th class="sortable" data-field="commitId">当前版本</th>
-                    <th class="sortable" data-field="commitTime">提交时间</th>
-                    <th class="sortable" data-field="buildTime">构建时间</th>
-                    <th class="sortable" data-field="result">结果</th>
-                    <th class="sortable" data-field="buildBy">用户</th>
+                    <th class="timo-table-checkbox">
+                        <label class="timo-checkbox"><input type="checkbox">
+                            <i class="layui-icon layui-icon-ok"></i></label>
+                    </th>
+                    <th class="sortable" data-field="appName">域名</th>
+                    <th class="sortable" data-field="appId">前缀</th>
+                    <th class="sortable" data-field="repoBranch">最大文件</th>
+                    <th class="sortable" data-field="appType">简介</th>
                     <th>操作</th>
                 </tr>
                 </thead>
                 <tbody>
                 <tr th:each="item:${list}">
-                    <!--<td><label class="timo-checkbox"><input type="checkbox" th:value="${item.appId}">
-                        <i class="layui-icon layui-icon-ok"></i></label>
-                    </td>-->
-                    <td>
-                        <a class="open-popup" data-title="应用配置详情" data-size="1200,500" href="#"
-                           th:text="${item.appName}" th:attr="data-url=@{'/app/config/app/detail/'+${item.appId}}"></a>
-                    </td>
-                    <td th:text="${item.appType}">类型</td>
-                    <td th:text="${item.repoBranch}">分支</td>
-                    <td th:text="${item.bindPorts}">监听端口</td>
-                    <td th:text="${item.commitId}">当前版本</td>
-                    <td th:text="${item.commitTime}">提交时间</td>
-                    <td th:text="${item.buildTime}">构建时间</td>
                     <td>
-                        <a class="open-popup build-result" data-title="构建结果" href="#"
-                           th:text="${item.buildResult}" th:attr="data-url=@{'/app/bd/build/result?buildLogId='+${item.buildLogId}}"></a>
+                        <label class="timo-checkbox">
+                            <input type="checkbox" th:value="${item.id}">
+                            <i class="layui-icon layui-icon-ok"></i>
+                        </label>
                     </td>
-                    <td th:text="${item.buildBy}">用户</td>
+                    <td th:text="${item.bindDomain}">域名</td>
+                    <td th:text="${item.prefix}">前缀</td>
+                    <td th:text="${item.maxSize}">最大文件</td>
+                    <td th:text="${item.description}">简介</td>
                     <td>
-                        <a class="ajax-post" th:href="@{'/api/app/bd/update/'+${item.appId}}">更新</a>
-                        <a class="ajax-post" th:href="@{'/api/app/bd/build/'+${item.appId}}">构建</a>
-                        <a class="open-popup"
-                           th:attr="data-title=@{${item.appId} + ' 部署列表'}, data-url=@{'/app/bd/deploy?appId=' + ${item.appId} + '&buildLogId=' + ${item.buildLogId}}"
-                           data-size="1000,500" href="#">部署</a>
-                        <a class="ajax-redirect" th:href="@{'/api/app/bd/packageurl/'+${item.buildLogId}}">下载</a>
-                        <a class="open-popup" th:attr="data-title=@{${item.appId} + ' 历史构建列表'}, data-url=@{'/app/bd/build/history?appId='+${item.appId}}"
-                           data-size="max" href="#">历史构建</a>
+                        <a class="open-popup" data-title="应用详细信息" th:attr="data-url=@{'/app/config/app/detail/'+${item.id}}"
+                           data-size="1200,500" href="#">详细</a>
+                        <a class="open-popup" data-title="编辑" th:attr="data-url=@{'/app/config/app/edit/'+${item.id}}"
+                           data-size="1200,500" href="#">编辑</a>
+                        <a class="ajax-delete" th:attr="data-msg='确定要删除 '+ ${item.id}"
+                           th:href="@{'/api/app/config/app/' + ${item.id}}">删除</a>
                     </td>
                 </tr>
                 </tbody>
@@ -105,6 +99,8 @@
 <script type="text/javascript" th:src="@{/js/plugins/jquery-2.2.4.min.js}"></script>
 <script type="text/javascript">
     function getPageByCriteria() {
+        console.log('select 事件')
+
         var envSelectedOption = $("#getPageByEnv option:selected")
         var envParam = envSelectedOption.text()
 
@@ -114,19 +110,6 @@
         url = '?env=' + envParam + '&type=' + typeParam
         window.location.href = window.location.pathname + url;
     }
-
-    $(".build-result").each(function () {
-        var text = $(this).text().trim()
-        if (text === '尚未构建') {
-            $(this).css("color", "#4169e1")
-        } else if (text === '正在构建') {
-            $(this).css("color", "#e3cf57")
-        } else if (text === '构建成功') {
-            $(this).css("color", "#009688")
-        } else if (text === '构建失败') {
-            $(this).css("color", "#ff0000")
-        }
-    })
 </script>
 </body>
 </html>

+ 12 - 48
oss-web/src/main/resources/templates/app/config/app/detail.html

@@ -8,35 +8,15 @@
             <tbody>
             <tr>
                 <th>应用 ID</th>
-                <td th:text="${app.appId}"></td>
+                <td th:text="${storeNode.domain}"></td>
                 <th>应用名称</th>
-                <td th:text="${app.appName}"></td>
+                <td th:text="${storeNode.domain}"></td>
             </tr>
             <tr>
-                <th>所属环境</th>
-                <td th:text="${app.env}"></td>
-                <th>应用类型</th>
-                <td th:text="${app.appType}"></td>
-            </tr>
-            <tr>
-                <th>应用仓库</th>
-                <td th:text="${app.appRepo}"></td>
-                <th>仓库分支</th>
-                <td th:text="${app.repoBranch}"></td>
-            </tr>
-            <tr>
-                <th>应用根路径</th>
-                <td th:text="${app.appRootPath}"></td>
-                <th>Dockerfile</th>
-                <td>
-                    <textarea class="layui-textarea" readonly="readonly" th:text="${app.dockerfile}"></textarea>
-                </td>
-            </tr>
-            <tr>
-                <th>监听端口</th>
-                <td th:text="${app.bindPorts}"></td>
-                <th>健康检查</th>
-                <td th:text="${app.healthCheck}"></td>
+                <th>总容量</th>
+                <td th:text="${storeNode.total}"></td>
+                <th>可用容量</th>
+                <td th:text="${storeNode.avail}"></td>
             </tr>
             </tbody>
         </table>
@@ -44,28 +24,12 @@
         <table class="layui-table timo-detail-table">
             <tbody>
             <tr>
-                <th>仓库类型</th>
-                <td th:text="${app.repoAuthConfig.type}"></td>
-                <th>仓库认证名字</th>
-                <td th:text="${app.repoAuthConfig.name}"></td>
-                <th>认证类型</th>
-                <td th:text="${app.repoAuthConfig.authType}"></td>
-            </tr>
-            <tr>
-                <th>编译器类型</th>
-                <td th:text="${app.compilerConfig.type}"></td>
-                <th>编译器名字</th>
-                <td th:text="${app.compilerConfig.name}"></td>
-                <th>编译命令</th>
-                <td th:text="${app.compilerConfig.compileScript}"></td>
-            </tr>
-            <tr>
-                <th>打包类型</th>
-                <td th:text="${app.packerConfig.type}"></td>
-                <th>打包名字</th>
-                <td th:text="${app.packerConfig.name}"></td>
-                <th>打包路径</th>
-                <td th:text="${app.packerConfig.targetPath}"></td>
+                <th>地址</th>
+                <td th:text="${storeNode.ipv4Addr}"></td>
+                <th>HTTP 端口</th>
+                <td th:text="${storeNode.httpPort}"></td>
+                <th>RPC 端口</th>
+                <td th:text="${storeNode.rpcPort}"></td>
             </tr>
             </tbody>
         </table>

+ 6 - 138
oss-web/src/main/resources/templates/app/config/app/edit.html

@@ -4,149 +4,30 @@
 
 <body>
 <div class="layui-form timo-compile">
-    <form th:action="@{/api/app/config/app/update}">
-        <div class="timo-detail-title">基础配置<i id="config_tips" class="fa fa-question-circle"></i></div>
+    <form th:action="@{/api/oss/store/update}">
+        <input class="layui-input" type="hidden" name="id" readonly="readonly" th:value="${storeNode.id}">
         <table class="layui-table timo-detail-table">
             <tbody>
             <tr>
                 <th>
-                    <label class="layui-form-label">应用 ID</label>
+                    <label class="layui-form-label">节点地址</label>
                 </th>
                 <td>
                     <div class="layui-form-item">
                         <div class="layui-input-inline">
-                            <input class="layui-input" type="text" name="appId" readonly="readonly" th:value="${app.appId}">
-                        </div>
-                    </div>
-                </td>
-                <th>
-                    <label class="layui-form-label required">应用名</label>
-                </th>
-                <td >
-                    <div class="layui-form-item">
-                        <div class="layui-input-inline">
-                            <input class="layui-input" type="text" name="appName" required th:value="${app.appName}">
-                        </div>
-                    </div>
-                </td>
-            </tr>
-            <tr>
-                <th>
-                    <label class="layui-form-label">应用类型</label>
-                </th>
-                <td>
-                    <div class="layui-form-item">
-                        <div class="layui-input-inline">
-                            <input class="layui-input" type="text" name="appType" readonly="readonly" th:value="${app.appType}">
-                        </div>
-                    </div>
-                </td>
-                <th>
-                    <label class="layui-form-label">所属环境</label>
-                </th>
-                <td>
-                    <div class="layui-form-item">
-                        <div class="layui-input-inline">
-                            <input class="layui-input" type="text" name="env" readonly="readonly" th:value="${app.env}">
-                        </div>
-                    </div>
-                </td>
-            </tr>
-            <tr>
-                <th>
-                    <label class="layui-form-label">应用仓库</label>
-                </th>
-                <td>
-                    <div class="layui-form-item">
-                        <div class="layui-input-inline">
-                            <input class="layui-input" type="text" name="appRepo" readonly required th:value="${app.appRepo}">
-                        </div>
-                    </div>
-                </td>
-                <th>
-                    <label class="layui-form-label required">仓库分支</label>
-                </th>
-                <td>
-                    <div class="layui-form-item">
-                        <div class="layui-input-inline">
-                            <input class="layui-input" type="text" name="repoBranch" required th:value="${app.repoBranch}">
-                        </div>
-                    </div>
-                </td>
-            </tr>
-            <tr>
-                <th>
-                    <label class="layui-form-label required">应用路径</label>
-                </th>
-                <td>
-                    <div class="layui-form-item">
-                        <div class="layui-input-inline">
-                            <input class="layui-input" type="text" name="appRootPath" required th:value="${app.appRootPath}">
-                        </div>
-                    </div>
-                </td>
-                <th>
-                    <label class="layui-form-label required">监听端口</label>
-                </th>
-                <td>
-                    <div class="layui-form-item">
-                        <div class="layui-input-inline">
-                            <input class="layui-input" type="text" name="bindPorts" th:value="${app.bindPorts}">
-                        </div>
-                    </div>
-                </td>
-            </tr>
-            </tbody>
-        </table>
-        <div class="timo-detail-title">构建配置</div>
-        <table class="layui-table timo-detail-table">
-            <tbody>
-            <tr>
-                <th>
-                    <label class="layui-form-label required">仓库认证</label>
-                </th>
-                <td>
-                    <div class="layui-form-item">
-                        <div class="layui-input-inline">
-                            <select name="repoAuthConfig">
-                                <option th:each="item : ${repoAuths}" th:value="${item.key}" th:selected="${app.repoAuthConfig?.name} eq ${item.key}">[[${item.value}]]</option>
-                            </select>
-                        </div>
-                    </div>
-                </td>
-                <th>
-                    <label class="layui-form-label required">编译工具</label>
-                </th>
-                <td>
-                    <div class="layui-form-item">
-                        <div class="layui-input-inline">
-                            <select name="compilerConfig">
-                                <option th:each="item : ${compilers}" th:value="${item.key}" th:selected="${app.compilerConfig?.name} eq ${item.key}">[[${item.value}]]</option>
-                            </select>
+                            <input class="layui-input" type="text" name="ipv4Addr" readonly="readonly" th:value="${storeNode.ipv4Addr}">
                         </div>
                     </div>
                 </td>
             </tr>
             <tr>
                 <th>
-                    <label class="layui-form-label required">打包工具</label>
+                    <label class="layui-form-label required">域名</label>
                 </th>
                 <td>
                     <div class="layui-form-item">
                         <div class="layui-input-inline">
-                            <select name="packerConfig">
-                                <option th:each="item : ${packers}" th:value="${item.key}" th:selected="${app.packerConfig?.name} eq ${item.key}">[[${item.value}]]</option>
-                            </select>
-                        </div>
-                    </div>
-                </td>
-                <th>
-                    <label class="layui-form-label required">Dockerfile</label>
-                </th>
-                <td>
-                    <div class="layui-form-item">
-                        <div class="layui-input-inline">
-                            <textarea class="layui-textarea" name="dockerfile" th:text="${app.dockerfile}"></textarea>
+                            <input class="layui-input" type="text" name="domain" required th:value="${storeNode.domain}">
                         </div>
                     </div>
                 </td>
@@ -161,18 +42,5 @@
 </div>
 
 <script th:replace="/common/template :: script"></script>
-<script type="text/javascript" th:src="@{/js/plugins/jquery-2.2.4.min.js}"></script>
-<script type="text/javascript">
-    $(function () {
-        $("#config_tips").hover(function () {
-            var tips = layer.tips('带 * 号的是可修改项<br />', '#config_tips',{
-                tips: [1, '#555555']
-            });
-            sleep(1000);
-            layer.close(tips);
-        })
-    })
-
-</script>
 </body>
 </html>

+ 14 - 40
oss-web/src/main/resources/templates/app/config/app/index.html

@@ -8,7 +8,7 @@
 <body class="timo-layout-page">
 <div class="layui-card">
     <div class="layui-card-header timo-card-header">
-        <span><i class="fa fa-bars"></i> 应用配置</span>
+        <span><i class="fa fa-bars"></i> 存储节点列表</span>
         <i class="layui-icon layui-icon-refresh refresh-btn"></i>
     </div>
     <div class="layui-card-body">
@@ -42,61 +42,35 @@
                         </button>
                     </div>
                 </div>
-                <div class="pull-right">
-                    <div class="btn-group-right">
-                        <button class="layui-btn open-popup" data-title="添加应用" th:attr="data-url=@{/app/config/app/add}"
-                                data-size="max">
-                            <i class="fa fa-plus"></i> 添加
-                        </button>
-                    </div>
-                </div>
             </div>
         </div>
         <div class="timo-table-wrap">
             <table class="layui-table timo-table">
                 <thead>
                 <tr>
-                    <th class="timo-table-checkbox">
-                        <label class="timo-checkbox"><input type="checkbox">
-                            <i class="layui-icon layui-icon-ok"></i></label>
-                    </th>
-                    <th class="sortable" data-field="appName">应用名</th>
-                    <th class="sortable" data-field="appId">应用 ID</th>
-                    <th class="sortable" data-field="repoBranch">分支</th>
-                    <th class="sortable" data-field="appType">应用类型</th>
-                    <th class="sortable" data-field="bindPorts">监听端口</th>
-                    <th>部署配置</th>
+                    <th class="sortable" data-field="appName">节点地址</th>
+                    <th class="sortable" data-field="appId">HTTP 端口</th>
+                    <th class="sortable" data-field="repoBranch">RPC 端口</th>
+                    <th class="sortable" data-field="appName">节点域名</th>
+                    <th>设置域名</th>
                     <th>操作</th>
                 </tr>
                 </thead>
                 <tbody>
                 <tr th:each="item:${list}">
+                    <td th:text="${item.ipv4Addr}">应用名</td>
+                    <td th:text="${item.httpPort}">应用 ID</td>
+                    <td th:text="${item.rpcPort}">分支</td>
+                    <td th:text="${item.domain}">应用名</td>
                     <td>
-                        <label class="timo-checkbox">
-                            <input type="checkbox" th:value="${item.appId}">
-                            <i class="layui-icon layui-icon-ok"></i>
-                        </label>
-                    </td>
-                    <td th:text="${item.appName}">应用名</td>
-                    <td th:text="${item.appId}">应用 ID</td>
-                    <td th:text="${item.repoBranch}">分支</td>
-                    <td th:text="${item.appType}">应用类型</td>
-                    <td th:text="${item.bindPorts?: ''}">监听端口</td>
-                    <td>
-                        <a class="open-popup" data-title="部署配置" th:attr="data-url=@{'/app/config/app/deploy/'+${item.appId}}"
-                           data-size="max" href="#">设置</a>
+                        <a class="open-popup" data-title="设置域名" th:attr="data-url=@{'/app/config/app/domain/'+${item.id}}"
+                           data-size="640,480" href="#">设置</a>
                     </td>
                     <td>
-                        <a class="open-popup" data-title="拷贝应用" th:attr="data-url=@{'/app/config/app/copy/'+${item.appId}}"
-                           href="#">拷贝</a>
-                        <a class="open-popup" data-title="应用详细信息" th:attr="data-url=@{'/app/config/app/detail/'+${item.appId}}"
+                        <a class="open-popup" data-title="应用详细信息" th:attr="data-url=@{'/app/config/app/detail/'+${item.id}}"
                            data-size="1200,500" href="#">详细</a>
-                        <a class="open-popup" data-title="编辑" th:attr="data-url=@{'/app/config/app/edit/'+${item.id}}"
-                           data-size="1200,500" href="#">编辑</a>
-                        <a class="ajax-delete" th:attr="data-msg='确定要删除 '+ ${item.appId}"
+                        <a class="ajax-delete" th:attr="data-msg='确定要删除 '+ ${item.id}"
                            th:href="@{'/api/app/config/app/' + ${item.id}}">删除</a>
-                        <a class="ajax-delete" th:attr="data-msg='确定要清空 ' + ${item.appId} + ' 的本地仓库'"
-                           th:href="@{'/api/app/config/app/repo/' + ${item.id}}">清空本地仓库</a>
                     </td>
                 </tr>
                 </tbody>

+ 59 - 58
oss-web/src/main/resources/templates/app/stat/index1.html

@@ -8,37 +8,47 @@
 <body class="timo-layout-page">
 <div class="layui-card">
     <div class="layui-card-header timo-card-header">
-        <span><i class="fa fa-bars"></i> 应用运行状态</span>
+        <span><i class="fa fa-bars"></i> 我的节点列表</span>
         <i class="layui-icon layui-icon-refresh refresh-btn"></i>
     </div>
     <div class="layui-card-body">
         <div class="layui-row timo-card-screen put-row">
-            <div class="pull-left layui-form-pane">
-                <div class="layui-inline">
-                    <label class="layui-form-label">环境</label>
-                    <div class="layui-input-block timo-search-status">
-                        <select id="getPageByEnv" class="timo-search-select" name="env" onchange="getPageByEnv()"
-                                mo:dict="ENVIRONMENT" mo-selected="${env}"></select>
+            <div class="layui-row timo-card-screen put-row">
+                <div class="pull-left layui-form-pane">
+                    <div class="layui-inline">
+                        <label class="layui-form-label">环境</label>
+                        <div class="layui-input-block timo-search-status">
+                            <select id="getPageByEnv" class="timo-search-select" name="env" onchange="getPageByCriteria()"
+                                    mo:dict="ENVIRONMENT" mo-selected="${env}"></select>
+                        </div>
                     </div>
-                </div>
-                <div class="layui-inline">
-                    <label class="layui-form-label">类型</label>
-                    <div class="layui-input-block timo-search-status">
-                        <select id="getPageByAppType" class="timo-search-select" name="type" onchange="getPageByAppType()"
-                                mo:dict="APP_TYPE" mo-selected="${type}"></select>
+                    <div class="layui-inline">
+                        <label class="layui-form-label">类型</label>
+                        <div class="layui-input-block timo-search-status">
+                            <select id="getPageByType" class="timo-search-select" name="type" onchange="getPageByCriteria()"
+                                    mo:dict="APP_TYPE" mo-selected="${type}"></select>
+                        </div>
                     </div>
-                </div>
-                <div class="layui-inline timo-search-box">
-                    <label class="layui-form-label">应用名</label>
-                    <div class="layui-input-block">
-                        <input type="text" name="appName" th:value="${param.appName}" placeholder="请输入应用名"
-                               autocomplete="off" class="layui-input">
+                    <div class="layui-inline timo-search-box">
+                        <label class="layui-form-label">应用</label>
+                        <div class="layui-input-block">
+                            <input type="text" name="appName" th:value="${param.appName}" placeholder="请输入应用名"
+                                   autocomplete="off" class="layui-input">
+                        </div>
+                    </div>
+                    <div class="layui-inline">
+                        <button class="layui-btn timo-search-btn">
+                            <i class="fa fa-search"></i>
+                        </button>
                     </div>
                 </div>
-                <div class="layui-inline">
-                    <button class="layui-btn timo-search-btn">
-                        <i class="fa fa-search"></i>
-                    </button>
+                <div class="pull-right">
+                    <div class="btn-group-right">
+                        <button class="layui-btn open-popup" data-title="新建节点" th:attr="data-url=@{/app/stat/add}"
+                                data-size="640,480">
+                            <i class="fa fa-plus"></i> 新建
+                        </button>
+                    </div>
                 </div>
             </div>
         </div>
@@ -46,29 +56,29 @@
             <table class="layui-table timo-table">
                 <thead>
                 <tr>
-                    <th class="sortable" data-field="appName">应用</th>
-                    <th class="sortable" data-field="bindPorts">监听端口</th>
-                    <th class="sortable" data-field="totalDeployed">部署数量</th>
-                    <th>运行状态</th>
+                    <th class="sortable" data-field="appName">节点地址</th>
+                    <th class="sortable" data-field="appId">HTTP 端口</th>
+                    <th class="sortable" data-field="repoBranch">RPC 端口</th>
+                    <th class="sortable" data-field="appName">节点域名</th>
+                    <th>设置域名</th>
+                    <th>操作</th>
                 </tr>
                 </thead>
                 <tbody>
                 <tr th:each="item:${list}">
+                    <td th:text="${item.ipv4Addr}">应用名</td>
+                    <td th:text="${item.httpPort}">应用 ID</td>
+                    <td th:text="${item.rpcPort}">分支</td>
+                    <td th:text="${item.domain}">应用名</td>
                     <td>
-                        <a class="open-popup" data-title="应用配置详情" data-size="1000,500" href="#"
-                           th:text="${item.appName}" th:attr="data-url=@{'/app/config/app/detail/'+${item.appId}}"></a>
+                        <a class="open-popup" data-title="设置域名" th:attr="data-url=@{'/app/config/app/domain/'+${item.id}}"
+                           data-size="640,480" href="#">设置</a>
                     </td>
-                    <td th:text="${item.bindPorts}">监听端口</td>
-                    <td th:text="${item.totalDeployed}">部署数量</td>
                     <td>
-                        <a class="open-popup"
-                           th:attr="data-title=${item.appName} + ' - 运行状态', data-url=@{'/app/stat/detail/'+${item.appId}}"
-                           data-size="1200,500" href="#">查看
-                        </a>
-                        <a class="open-popup"
-                           th:attr="data-title=${item.appName} + ' - 运行日志', data-url=@{'/app/stat/log/'+${item.appId}}"
-                           data-size="max" href="#">日志
-                        </a>
+                        <a class="open-popup" data-title="应用详细信息" th:attr="data-url=@{'/app/config/app/detail/'+${item.id}}"
+                           data-size="1200,500" href="#">详细</a>
+                        <a class="ajax-delete" th:attr="data-msg='确定要删除 '+ ${item.id}"
+                           th:href="@{'/api/app/config/app/' + ${item.id}}">删除</a>
                     </td>
                 </tr>
                 </tbody>
@@ -77,31 +87,22 @@
         <div th:replace="/common/fragment :: page"></div>
     </div>
 </div>
+
 <script th:replace="/common/template :: script"></script>
 <script type="text/javascript" th:src="@{/js/plugins/jquery-2.2.4.min.js}"></script>
 <script type="text/javascript">
-    function getPageByEnv() {
-        var selectedOption = $("#getPageByEnv option:selected")
-        var param = selectedOption.text()
-        url = '?env=' + param
-        window.location.href = window.location.pathname + url;
-    }
+    function getPageByCriteria() {
+        console.log('select 事件')
+
+        var envSelectedOption = $("#getPageByEnv option:selected")
+        var envParam = envSelectedOption.text()
 
-    function getPageByAppType() {
-        var selectedOption = $("#getPageByAppType option:selected")
-        var param = selectedOption.text()
-        url = '?appType=' + param
+        var typeSelectedOption = $("#getPageByType option:selected")
+        var typeParam = typeSelectedOption.text()
+
+        url = '?env=' + envParam + '&type=' + typeParam
         window.location.href = window.location.pathname + url;
     }
-
-    $(".app-status").each(function () {
-        var text = $(this).text().trim()
-        if (text === 'Online') {
-            $(this).css("color", "#009688")
-        } else {
-            $(this).css("color", "#ff0000")
-        }
-    })
 </script>
 </body>
 </html>

+ 67 - 48
oss-web/src/main/resources/templates/app/stat/index2.html

@@ -1,56 +1,75 @@
 <!DOCTYPE html>
 <html xmlns:th="http://www.thymeleaf.org">
-<head th:replace="/common/template :: header(~{::title},~{::link},~{::style})">
-    <link rel="stylesheet" th:href="@{/lib/zTree_v3/css/zTreeStyle/zTreeStyle.css}" type="text/css">
-</head>
-<body class="timo-layout-page">
-<div class="layui-card">
-    <div class="layui-card-body">
-        <div class="timo-table-wrap">
-            <table class="layui-table timo-table">
-                <thead>
-                <tr>
-                    <th class="sortable" data-field="machineIpv4">机器地址</th>
-                    <th class="sortable" data-field="packagePath">使用的包</th>
-                    <th class="sortable" data-field="status">运行状态</th>
-                    <th class="sortable" data-field="startTime">启动时间</th>
-                    <th class="sortable" data-field="pid">PID</th>
-                    <th class="sortable" data-field="lastCheck">上次检查</th>
-                    <th>操作</th>
-                </tr>
-                </thead>
-                <tbody>
-                <tr th:each="item:${list}">
-                    <td th:text="${item.machineIpv4}">机器地址</td>
-                    <td th:text="${item.packagePath}">包路径</td>
-                    <td class="app-status" th:text="${item.status}">运行状态</td>
-                    <td th:text="${item.startTime}">启动时间</td>
-                    <td th:text="${item.pid}">PID</td>
-                    <td th:text="${item.lastCheck}">上次检查</td>
-                    <td>
-                        <a class="ajax-post" th:href="@{'/api/app/status/restart/'+${item.appId}+'/'+${item.machineId}}">重启</a>
-                        <a class="ajax-post" th:href="@{'/api/app/status/stop/'+${item.appId}+'/'+${item.machineId}}">停止</a>
-                        <a class="ajax-post" th:href="@{'/api/app/status/start/'+${item.appId}+'/'+${item.machineId}}">启动</a>
-                        <a class="ajax-get" th:href="@{'/api/app/status/'+${item.appId}+'/'+${item.machineId}}">当前状态</a>
-                    </td>
-                </tr>
-                </tbody>
-            </table>
+<head th:replace="/common/template :: header(~{::title},~{::link},~{::style})"></head>
+
+<body>
+<div class="layui-form timo-compile">
+    <form th:action="@{/api/channel/upload/add}">
+        <table class="layui-table timo-detail-table">
+            <tbody>
+            <tr>
+                <th>
+                    <label class="layui-form-label required">存储节点</label>
+                </th>
+                <td>
+                    <div class="layui-form-item">
+                        <div class="layui-input-inline">
+                            <select name="domain">
+                                <option th:each="item : ${storeNodes}" th:value="${item.key}">[[${item.value}]]</option>
+                            </select>
+                        </div>
+                    </div>
+                </td>
+            </tr>
+            <tr>
+                <th>
+                    <label class="layui-form-label required">通道前缀</label>
+                </th>
+                <td>
+                    <div class="layui-form-item">
+                        <div class="layui-input-inline">
+                            <label>
+                                <input class="layui-input" type="text" name="channelPrefix" placeholder="请输入通道前缀" required>
+                            </label>
+                        </div>
+                    </div>
+                </td>
+            </tr>
+            <tr>
+                <th>
+                    <label class="layui-form-label required">最大文件</label>
+                </th>
+                <td>
+                    <div class="layui-form-item">
+                        <div class="layui-input-inline">
+                            <select name="maxSize">
+                                <option th:each="item : ${sizeList}" th:value="${item.key}">[[${item.value}]]</option>
+                            </select>
+                        </div>
+                    </div>
+                </td>
+            </tr>
+            <tr>
+                <th>
+                    <label class="layui-form-label required">通道简介</label>
+                </th>
+                <td>
+                    <div class="layui-form-item">
+                        <div class="layui-input-inline">
+                            <textarea class="layui-textarea" name="description" placeholder="请填写通道简介"></textarea>
+                        </div>
+                    </div>
+                </td>
+            </tr>
+            </tbody>
+        </table>
+        <div class="layui-form-item timo-finally">
+            <button class="layui-btn ajax-submit"><i class="fa fa-check-circle"></i> 保存</button>
+            <button class="layui-btn btn-secondary close-popup"><i class="fa fa-times-circle"></i> 关闭</button>
         </div>
-    </div>
+    </form>
 </div>
 
 <script th:replace="/common/template :: script"></script>
-<script type="text/javascript" th:src="@{/js/plugins/jquery-2.2.4.min.js}"></script>
-<script type="text/javascript">
-    $(".app-status").each(function () {
-        var text = $(this).text().trim()
-        if (text === 'Online') {
-            $(this).css("color", "#009688")
-        } else {
-            $(this).css("color", "#ff0000")
-        }
-    })
-</script>
 </body>
 </html>