#3723 V20230215

Merged
ychao_1983 merged 384 commits from V20230215 into develop 1 year ago
  1. +0
    -1
      .gitignore
  2. +1
    -1
      Makefile
  3. +199
    -10
      README.md
  4. +13
    -0
      assets/abandon.svg
  5. +11
    -0
      assets/code1.svg
  6. +11
    -0
      assets/code2.svg
  7. +13
    -0
      assets/codeLog.svg
  8. +11
    -0
      assets/drag.svg
  9. BIN
      assets/drag2.png
  10. +9
    -0
      assets/more.svg
  11. +11
    -0
      assets/submit.svg
  12. +13
    -0
      assets/submit2.svg
  13. +11
    -0
      assets/warehouse.svg
  14. +5
    -0
      custom/conf/app.ini.sample
  15. +0
    -45
      custom/public/img/logo-w-origin.svg
  16. +45
    -1
      custom/public/img/logo-w.svg
  17. BIN
      go_build_code_gitea_io_gitea
  18. +8
    -17
      models/ai_model_manage.go
  19. +126
    -46
      models/cloudbrain.go
  20. +31
    -47
      models/cloudbrain_static.go
  21. +14
    -0
      models/error.go
  22. +27
    -1
      models/issue.go
  23. +3
    -0
      models/models.go
  24. +9
    -4
      models/point_account.go
  25. +1
    -0
      models/repo.go
  26. +30
    -0
      models/repo_list.go
  27. +71
    -0
      models/role.go
  28. +661
    -0
      models/tech_converge_info.go
  29. +15
    -15
      models/user_business_analysis.go
  30. +6
    -1
      modules/auth/wechat/cloudbrain.go
  31. +48
    -0
      modules/auth/wechat/point.go
  32. +8
    -5
      modules/auth/wechat/template.go
  33. +217
    -0
      modules/base/tool.go
  34. +2
    -6
      modules/cloudbrain/cloudbrain.go
  35. +2
    -2
      modules/context/repo.go
  36. +1
    -1
      modules/cron/tasks_basic.go
  37. +92
    -0
      modules/git/repo_branch.go
  38. +3
    -8
      modules/grampus/resty.go
  39. +1
    -1
      modules/modelarts/modelarts.go
  40. +2
    -0
      modules/notification/base/notifier.go
  41. +5
    -0
      modules/notification/base/null.go
  42. +8
    -0
      modules/notification/notification.go
  43. +7
    -0
      modules/notification/wechat/wechat.go
  44. +6
    -0
      modules/redis/redis_key/cloudbrain_redis_key.go
  45. +4
    -0
      modules/redis/redis_key/reward_redis_key.go
  46. +33
    -8
      modules/repofiles/content.go
  47. +6
    -2
      modules/repofiles/tree.go
  48. +411
    -0
      modules/repofiles/update.go
  49. +48
    -8
      modules/setting/setting.go
  50. +1
    -5
      modules/storage/storage.go
  51. +28
    -0
      modules/structs/repo_file.go
  52. +18
    -0
      modules/structs/repo_tree.go
  53. +18
    -0
      modules/structs/tech.go
  54. +13
    -6
      modules/templates/helper.go
  55. +17
    -1
      options/locale/locale_en-US.ini
  56. +18
    -0
      options/locale/locale_zh-CN.ini
  57. +22281
    -172
      package-lock.json
  58. +7
    -1
      package.json
  59. BIN
      public/0acc017d3b9b32f1f61a9af2315d5187.png
  60. BIN
      public/0f1600ced2415ea9530a1a55ec045fef.png
  61. BIN
      public/1f2e7b26b1be5e67e613178f38625008.png
  62. BIN
      public/276e9642cca7e5f8958c004269a6c0d7.png
  63. BIN
      public/41fa1ffe704082c381ae88ea686c9f39.png
  64. BIN
      public/5920c99e273bded4a2c702d4a1ed59ee.png
  65. BIN
      public/6f562e238482d4479212cbf367f6a293.png
  66. BIN
      public/71261d74041a4133ae4b9908d4a8109f.png
  67. BIN
      public/d4f9fbedc79f92e41f984283b6227127.png
  68. +93
    -21
      public/home/home.js
  69. +21
    -0
      public/html/js/nbview/LICENSE.txt
  70. +22
    -0
      public/html/js/nbview/Makefile
  71. +25
    -0
      public/html/js/nbview/README.md
  72. +35
    -0
      public/html/js/nbview/css/nbpreview.css
  73. +85
    -0
      public/html/js/nbview/css/notebook.css
  74. BIN
      public/html/js/nbview/css/vendor/fonts/KaTeX_AMS-Regular.ttf
  75. BIN
      public/html/js/nbview/css/vendor/fonts/KaTeX_AMS-Regular.woff
  76. BIN
      public/html/js/nbview/css/vendor/fonts/KaTeX_AMS-Regular.woff2
  77. BIN
      public/html/js/nbview/css/vendor/fonts/KaTeX_Caligraphic-Bold.ttf
  78. BIN
      public/html/js/nbview/css/vendor/fonts/KaTeX_Caligraphic-Bold.woff
  79. BIN
      public/html/js/nbview/css/vendor/fonts/KaTeX_Caligraphic-Bold.woff2
  80. BIN
      public/html/js/nbview/css/vendor/fonts/KaTeX_Caligraphic-Regular.ttf
  81. BIN
      public/html/js/nbview/css/vendor/fonts/KaTeX_Caligraphic-Regular.woff
  82. BIN
      public/html/js/nbview/css/vendor/fonts/KaTeX_Caligraphic-Regular.woff2
  83. BIN
      public/html/js/nbview/css/vendor/fonts/KaTeX_Fraktur-Bold.ttf
  84. BIN
      public/html/js/nbview/css/vendor/fonts/KaTeX_Fraktur-Bold.woff
  85. BIN
      public/html/js/nbview/css/vendor/fonts/KaTeX_Fraktur-Bold.woff2
  86. BIN
      public/html/js/nbview/css/vendor/fonts/KaTeX_Fraktur-Regular.ttf
  87. BIN
      public/html/js/nbview/css/vendor/fonts/KaTeX_Fraktur-Regular.woff
  88. BIN
      public/html/js/nbview/css/vendor/fonts/KaTeX_Fraktur-Regular.woff2
  89. BIN
      public/html/js/nbview/css/vendor/fonts/KaTeX_Main-Bold.ttf
  90. BIN
      public/html/js/nbview/css/vendor/fonts/KaTeX_Main-Bold.woff
  91. BIN
      public/html/js/nbview/css/vendor/fonts/KaTeX_Main-Bold.woff2
  92. BIN
      public/html/js/nbview/css/vendor/fonts/KaTeX_Main-BoldItalic.ttf
  93. BIN
      public/html/js/nbview/css/vendor/fonts/KaTeX_Main-BoldItalic.woff
  94. BIN
      public/html/js/nbview/css/vendor/fonts/KaTeX_Main-BoldItalic.woff2
  95. BIN
      public/html/js/nbview/css/vendor/fonts/KaTeX_Main-Italic.ttf
  96. BIN
      public/html/js/nbview/css/vendor/fonts/KaTeX_Main-Italic.woff
  97. BIN
      public/html/js/nbview/css/vendor/fonts/KaTeX_Main-Italic.woff2
  98. BIN
      public/html/js/nbview/css/vendor/fonts/KaTeX_Main-Regular.ttf
  99. BIN
      public/html/js/nbview/css/vendor/fonts/KaTeX_Main-Regular.woff
  100. BIN
      public/html/js/nbview/css/vendor/fonts/KaTeX_Main-Regular.woff2

+ 0
- 1
.gitignore View File

@@ -77,7 +77,6 @@ coverage.all
/integrations/pgsql.ini
/integrations/mssql.ini
/node_modules
/yarn.lock
/public/js
/public/css
/public/fonts


+ 1
- 1
Makefile View File

@@ -614,7 +614,7 @@ webpack_end:

$(WEBPACK_DEST): $(WEBPACK_SOURCES) $(WEBPACK_CONFIGS) package-lock.json | node_modules
rm -rf $(WEBPACK_DEST_DIRS)
npx webpack --hide-modules --display-entrypoints=false
npx webpack --hide-modules --display-entrypoints=false --progress
@touch $(WEBPACK_DEST)

.PHONY: update-translations


+ 199
- 10
README.md View File

@@ -5,7 +5,6 @@
[![release](https://img.shields.io/badge/release-1.21.11.1-blue)](https://openi.pcl.ac.cn/OpenI/aiforge/releases/latest)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)


## AiForge

启智AI开发协作平台是一个在线Web应用,旨在为人工智能算法、模型开发提供在线协同工作环境,它提供了<b>代码托管、数据集管理与共享、免费云端算力资源支持(GPU/NPU)、共享镜像</b>等功能。
@@ -15,43 +14,233 @@
本项目是基于[Gitea](https://github.com/go-gitea/gitea)发展而来的,我们对其进行了Fork并基于此扩展了人工智能开发中需要的功能,如数据集管理和模型训练等。对于和代码托管相关的功能,您可以参考[Gitea的文档](https://docs.gitea.io/zh-cn/)。

### 系统总体架构

下图展示了系统总体架构,本项目分为Web前端和服务后端,Web页面面向算法开发者、应用开发者、科研工作者、学生等用户群体,通过统一的Web页面入口,使用系统提供的系统服务。

后端服务涵盖了AI模型开发流水线,包括代码协同开发、数据管理、模型调试、训练、推理和部署等(*目前尚未支持模型部署*)。在不同的开发阶段,我们还将提供丰富的开发工具供用户使用,如数据标注、数据筛选、模型转换、模型压缩、代码检测等。我们也欢迎社区提供更多丰富的工具接入,提高利用平台进行开发的效率。
![系统架构图](assets/架构图.png)

## 在线服务使用

本项目的在线服务平台的详细使用帮助文档,可参阅本项目[百科](https://openi.pcl.ac.cn/OpenI/aiforge/wiki)内容。

- 如何创建账号

- 如何创建组织及管理成员权限

- 如何创建项目仓库

- 如何使用数据集功能

- 如何使用计算资源进行模型调试和训练

- 使用小技巧

- 常见问题(FAQ)
## 安装
您也可以基于本项目代码,在本地环境安装部署服务。
### 数据库准备
[数据库准备说明](https://docs.gitea.io/zh-cn/database-prep/)
### 从源代码安装
## 安装
您也可以基于本项目代码,在本地环境安装部署服务。
### 数据库准备
[数据库准备说明](https://docs.gitea.io/zh-cn/database-prep/)
### 从源代码安装

- node版本 >= v10.13.0

- golang版本 >= 1.13.3

[从源代码安装说明](https://docs.gitea.io/zh-cn/install-from-source/)

## 开发者指南

#### Linux下通过Docker快速搭建开发环境:

前提条件:已安装Docker,了解Docker的基本操作;熟悉git的基本操作(拉取代码,提交代码,合并代码,创建分支)。

1. 拉取镜像:
aiforge-postgres是数据库镜像,初始化了数据库;
aiforge-dev是开发环境镜像,安装了go,nodejs,openssh等依赖。
如果执行命令提示没有权限,在命令前加sudo
```
docker pull swr.cn-north-4.myhuaweicloud.com/openi/aiforge-postgres:v1
docker pull swr.cn-north-4.myhuaweicloud.com/openi/aiforge-dev:v1
```

2. 启动镜像:
注意:由于linux下的回车符和windows不一致,直接拷贝命令执行可能失败。 如果失败:去掉'\\',把命令改成一行命令再执行。
```
docker run --name postgres-openi \
-e POSTGRES_PASSWORD=openi \
-e PGDATA=/var/lib/postgresql/data/pgdata \
-p 5432:5432 \
-v /home/openi/postgresql/data:/var/lib/postgresql/data \
-d swr.cn-north-4.myhuaweicloud.com/openi/aiforge-postgres:v1
```
```
docker run --name aiforgedev \
-p 8787:3000 \
-p 2222:22 \
-v /home/openi/data:/data \
-d swr.cn-north-4.myhuaweicloud.com/openi/aiforge-dev:v1
```

3. 进入开发容器并下载代码:
- 执行sudo docker ps,找到名称为aiforgedev的容器id(CONTAINER ID )
- 执行docker exec -it {开发容器id} /bin/bash
- 执行cd /data
- 执行git clone {aiforge项目地址或派生项目地址,例如https://openi.pcl.ac.cn/OpenI/aiforge.git,后续说明已此地址为例}

4. 设置配置文件:
- 在/data/aiforge/custom/conf目录下创建app.ini
- 文件内容如下:
注意:需要将文件中的两个Local_IP替换为本机IP地址
> APP_NAME = aiforge
> RUN_MODE = prod
>
> [repository]
> ROOT = /data/git/repositories
>
> [repository.local]
> LOCAL_COPY_PATH = /data/gitea/tmp/local-repo
>
> [repository.upload]
> TEMP_PATH = /data/gitea/uploads
>
> [server]
> SSH_DOMAIN = 0.0.0.0
> DOMAIN = 0.0.0.0
> HTTP_PORT = 3000
> ROOT_URL = http://0.0.0.0:3000
> DISABLE_SSH = false
> SSH_PORT = 22
> LFS_START_SERVER = true
> LFS_CONTENT_PATH = /data/git/lfs
> LFS_JWT_SECRET = tqVLRkZYpP4UlAoZtZcdX2paFZ6G7FN_Y47It6PfJAE
> OFFLINE_MODE = false
>
> [database]
> DB_TYPE = postgres
> HOST = Local_IP:5432
> NAME = gitea
> USER = gitea
> PASSWD = gitea
> SCHEMA =
> SSL_MODE = disable
> CHARSET = utf8
> PATH = /data/gitea/gitea.db
>
> [database_statistic]
> DB_TYPE = postgres
> HOST = Local_IP:5432
> NAME = statistic
> USER = gitea
> PASSWD = gitea
> SCHEMA =
> SSL_MODE = disable
> CHARSET = utf8
> PATH = /data/gitea/statistic.db
>
> [indexer]
> ISSUE_INDEXER_PATH = /data/gitea/indexers/issues.bleve
>
> [session]
> PROVIDER_CONFIG = /data/gitea/sessions
>
> [picture]
> AVATAR_UPLOAD_PATH = /data/gitea/avatars
> REPOSITORY_AVATAR_UPLOAD_PATH = /data/gitea/repo-avatars
>
> [attachment]
> PATH = /data/gitea/attachments
>
> [log]
> MODE = file
> LEVEL = info
> ROOT_PATH = /data/gitea/log
>
> [security]
> INSTALL_LOCK = true
> SECRET_KEY = sdfsagg453535
>
> [service]
> DISABLE_REGISTRATION = false
> REQUIRE_SIGNIN_VIEW = false
>
> [obs]
>
> ENDPOINT =
>
> ACCESS_KEY_ID =
> SECRET_ACCESS_KEY =
> BUCKET =
> LOCATION =
> BASE_PATH =
> CODE_PATH_PREFIX =
> Output_Path =
> TrainJobModel_Path =
>
> [picture]
> DISABLE_GRAVATAR = true
> ENABLE_FEDERATED_AVATAR = false

5. 编译:
```
cd /data/aiforge
make build
```

6. 运行:
```
./opendata web
```

7. 访问:http://本机ip:8787
注意:代码提交到aiforge请参考[代码提交](https://openi.pcl.ac.cn/docs/index.html#/repo/code.md),[合并请求](https://openi.pcl.ac.cn/docs/index.html#/repo/pr?id=%e5%90%88%e5%b9%b6%e8%af%b7%e6%b1%82)
**特别说明:** 目前无法给开发者提供云脑和智算网络的开发环境,开发者暂时只能参与代码管理相关的任务开发。

## 授权许可

本项目采用 MIT 开源授权许可证,完整的授权说明已放置在 [LICENSE](https://openi.pcl.ac.cn/OpenI/aiforge/src/branch/develop/LICENSE) 文件中。

## 需要帮助?

## 需要帮助?
如果您在使用或者开发过程中遇到问题,可以在以下渠道咨询:
- 点击[这里](https://openi.pcl.ac.cn/OpenI/aiforge/issues)在线提交问题(点击页面右上角绿色按钮**创建任务**)
- 加入微信群实时交流,获得进一步的支持
<img src="https://openi.pcl.ac.cn/OpenI/aiforge/wiki/raw/img/wechatgroup.jpg" width=200px />

- 点击[这里](https://openi.pcl.ac.cn/OpenI/aiforge/issues)在线提交问题(点击页面右上角绿色按钮**创建任务**)

- 加入微信群实时交流,获得进一步的支持
<img src="https://openi.pcl.ac.cn/OpenI/aiforge/wiki/raw/img/wechatgroup.jpg" width=200px />

## 启智社区小白训练营:

- 结合案例给大家详细讲解如何使用社区平台,帮助无技术背景的小白成长为启智社区达人 (https://openi.pcl.ac.cn/zeizei/OpenI_Learning)

## 平台引用

如果本平台对您的科研工作提供了帮助,可在论文致谢中加入:
英文版:```Thanks for the support provided by OpenI Community (https://openi.pcl.ac.cn).```
中文版:```感谢启智社区提供的技术支持(https://openi.pcl.ac.cn)。```


+ 13
- 0
assets/abandon.svg View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="14px" height="14px" viewBox="0 0 14 14" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>放弃更改</title>
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Manaco-提交" transform="translate(-414.000000, -114.000000)" fill="#9096A3" fill-rule="nonzero">
<g id="编组-8" transform="translate(90.000000, 114.000000)">
<g id="放弃更改" transform="translate(324.000000, 0.000000)">
<path d="M5.64465565,9.44839769 C3.70098527,7.81473925 1.87574642,6.2803543 0.00696656051,4.7093949 C1.86355494,3.14888535 3.69053543,1.61624204 5.60633957,0.00696656051 L5.60633957,2.18575836 C6.64958201,2.31638137 7.63535032,2.37385549 8.58976911,2.57240247 C10.5734972,2.98342954 12.1549064,3.94655653 12.8445959,5.99472532 C13.8408141,8.95028861 12.6599821,12.2263137 10.0057225,13.8564889 C9.96914809,13.8791302 9.92386545,13.8913217 9.82981688,13.9296377 C10.1851115,13.4959693 10.5282146,13.1267416 10.8173268,12.7174562 C11.3885848,11.9128185 11.7212381,11.0176154 11.7595541,10.0213973 C11.7943869,9.12271099 11.3955514,8.46959594 10.6431628,8.01328623 C9.76363455,7.47860271 8.77786624,7.28876393 7.77816481,7.20516521 C7.08499204,7.14769108 6.38485271,7.19471537 5.64465565,7.19471537 L5.64465565,9.44839769 Z" id="路径"></path>
</g>
</g>
</g>
</g>
</svg>

+ 11
- 0
assets/code1.svg View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="22px" height="24px" viewBox="0 0 22 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>代码1</title>
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Manaco-代码" transform="translate(-24.000000, -54.000000)" fill="#FFFFFF" fill-rule="nonzero">
<g id="编组-2" transform="translate(23.000000, 54.000000)">
<path d="M18.691358,20.6530612 C19.3209877,20.6530612 19.8024691,21.4013605 19.8148148,22.3265306 C19.8148148,23.2380952 19.308642,24 18.7037037,24 L5.2962963,24 C4.67901235,24 4.18518519,23.2517007 4.18518519,22.3265306 C4.18518519,21.414966 4.69135802,20.6530612 5.2962963,20.6530612 L18.691358,20.6530612 Z M21.4320988,1.24344979e-14 C22.2962963,1.24344979e-14 23,0.74829932 23,1.67346939 L23,16.707483 L22.9876543,16.707483 C22.9876543,17.6190476 22.2839506,18.3809524 21.4197531,18.3809524 L2.56790123,18.3809524 C1.7037037,18.3809524 1,17.6326531 1,16.707483 L1,1.67346939 C1,0.761904762 1.7037037,1.24344979e-14 2.56790123,1.24344979e-14 L21.4320988,1.24344979e-14 Z M7.40275426,5.59798965 L4.42406571,7.86899844 C4.2208615,8.02209757 4.09361684,8.24213052 4.07053819,8.48032233 C4.04570362,8.87774887 4.05154704,9.27649576 4.08952935,9.67260195 C4.11436392,9.92346921 4.25168453,10.1518904 4.4693523,10.3024108 L7.45096256,12.4215789 C7.80850744,12.6781677 8.30844535,12.6529526 8.6334608,12.3784779 L8.71752605,12.2974656 L8.71752605,12.2974656 C9.05638834,11.8920818 8.97886013,11.3169104 8.54222314,10.996917 L5.87323641,9.10088868 L8.58750971,7.02925332 C8.99780991,6.70122471 9.06310176,6.14433333 8.73797805,5.74586928 C8.58953291,5.5575319 8.36035163,5.43424637 8.10640394,5.40612098 C7.85245625,5.37799559 7.59712068,5.44761958 7.40275426,5.59798965 Z M13.2750251,5.43601868 C13.041601,5.50337484 12.8462407,5.65606881 12.7322875,5.86022349 L9.67682551,11.2791811 C9.51225291,11.5570107 9.51537327,11.896483 9.68502809,12.1715632 C9.85162755,12.4442956 10.1628838,12.6086547 10.4957167,12.5996479 C10.8286146,12.587652 11.1280084,12.4046164 11.2763291,12.1224175 L14.3331583,6.70475316 C14.4492093,6.49817249 14.4756869,6.25696469 14.4069815,6.03223337 C14.340046,5.80948374 14.182814,5.62076308 13.9695104,5.50715059 L13.9681433,5.50715059 C13.7568457,5.39554401 13.5067057,5.36987315 13.2750251,5.43601868 Z M15.8922987,5.40611327 C15.6385591,5.43425376 15.4096876,5.55776951 15.2618761,5.74633526 C14.9356259,6.14558659 15.0029615,6.70437583 15.4166517,7.0307509 L18.129604,9.10060055 L15.4619162,10.9975227 C15.0590628,11.2928349 14.9620467,11.8055279 15.2158996,12.20143 L15.2866986,12.297779 L15.2866986,12.297779 C15.6035504,12.6462741 16.1627866,12.7017165 16.5526457,12.4205444 L19.5328049,10.3044927 C19.750759,10.1515096 19.8883325,9.92333042 19.912443,9.67482542 C19.9489467,9.27880827 19.9547872,8.87883095 19.9270444,8.48149375 L19.9270444,8.48017369 C19.903977,8.24203541 19.7767943,8.02205192 19.573689,7.86898721 L16.5949899,5.59848887 C16.4011465,5.4478137 16.1460382,5.37797278 15.8922987,5.40611327 Z" id="代码1"></path>
</g>
</g>
</g>
</svg>

+ 11
- 0
assets/code2.svg View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="22px" height="24px" viewBox="0 0 22 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>代码</title>
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Manaco-提交" transform="translate(-24.000000, -54.000000)" fill="#000000" fill-rule="nonzero">
<g id="编组-2" transform="translate(23.000000, 54.000000)">
<path d="M18.691358,20.6530612 C19.3209877,20.6530612 19.8024691,21.4013605 19.8148148,22.3265306 C19.8148148,23.2380952 19.308642,24 18.7037037,24 L5.2962963,24 C4.67901235,24 4.18518519,23.2517007 4.18518519,22.3265306 C4.18518519,21.414966 4.69135802,20.6530612 5.2962963,20.6530612 L18.691358,20.6530612 Z M21.4320988,1.24344979e-14 C22.2962963,1.24344979e-14 23,0.74829932 23,1.67346939 L23,16.707483 L22.9876543,16.707483 C22.9876543,17.6190476 22.2839506,18.3809524 21.4197531,18.3809524 L2.56790123,18.3809524 C1.7037037,18.3809524 1,17.6326531 1,16.707483 L1,1.67346939 C1,0.761904762 1.7037037,1.24344979e-14 2.56790123,1.24344979e-14 L21.4320988,1.24344979e-14 Z M7.40275426,5.59798965 L4.42406571,7.86899844 C4.2208615,8.02209757 4.09361684,8.24213052 4.07053819,8.48032233 C4.04570362,8.87774887 4.05154704,9.27649576 4.08952935,9.67260195 C4.11436392,9.92346921 4.25168453,10.1518904 4.4693523,10.3024108 L7.45096256,12.4215789 C7.80850744,12.6781677 8.30844535,12.6529526 8.6334608,12.3784779 L8.71752605,12.2974656 L8.71752605,12.2974656 C9.05638834,11.8920818 8.97886013,11.3169104 8.54222314,10.996917 L5.87323641,9.10088868 L8.58750971,7.02925332 C8.99780991,6.70122471 9.06310176,6.14433333 8.73797805,5.74586928 C8.58953291,5.5575319 8.36035163,5.43424637 8.10640394,5.40612098 C7.85245625,5.37799559 7.59712068,5.44761958 7.40275426,5.59798965 Z M13.2750251,5.43601868 C13.041601,5.50337484 12.8462407,5.65606881 12.7322875,5.86022349 L9.67682551,11.2791811 C9.51225291,11.5570107 9.51537327,11.896483 9.68502809,12.1715632 C9.85162755,12.4442956 10.1628838,12.6086547 10.4957167,12.5996479 C10.8286146,12.587652 11.1280084,12.4046164 11.2763291,12.1224175 L14.3331583,6.70475316 C14.4492093,6.49817249 14.4756869,6.25696469 14.4069815,6.03223337 C14.340046,5.80948374 14.182814,5.62076308 13.9695104,5.50715059 L13.9681433,5.50715059 C13.7568457,5.39554401 13.5067057,5.36987315 13.2750251,5.43601868 Z M15.8922987,5.40611327 C15.6385591,5.43425376 15.4096876,5.55776951 15.2618761,5.74633526 C14.9356259,6.14558659 15.0029615,6.70437583 15.4166517,7.0307509 L18.129604,9.10060055 L15.4619162,10.9975227 C15.0590628,11.2928349 14.9620467,11.8055279 15.2158996,12.20143 L15.2866986,12.297779 L15.2866986,12.297779 C15.6035504,12.6462741 16.1627866,12.7017165 16.5526457,12.4205444 L19.5328049,10.3044927 C19.750759,10.1515096 19.8883325,9.92333042 19.912443,9.67482542 C19.9489467,9.27880827 19.9547872,8.87883095 19.9270444,8.48149375 L19.9270444,8.48017369 C19.903977,8.24203541 19.7767943,8.02205192 19.573689,7.86898721 L16.5949899,5.59848887 C16.4011465,5.4478137 16.1460382,5.37797278 15.8922987,5.40611327 Z" id="代码"></path>
</g>
</g>
</g>
</svg>

+ 13
- 0
assets/codeLog.svg View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="14px" height="16px" viewBox="0 0 14 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>代码文件</title>
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Manaco-提交" transform="translate(-90.000000, -169.000000)" fill="#6F6E6E" fill-rule="nonzero">
<g id="编组-8" transform="translate(90.000000, 114.000000)">
<g id="编组-5" transform="translate(0.000000, 55.000000)">
<path d="M9.72883333,0 C10.0462727,0 10.3500145,0.136417787 10.57,0.377846154 L13.6733333,3.78092308 C13.882631,4.01000077 13.9997061,4.31568263 14,4.63384615 L14,14.7692308 C14,15.4489658 13.4776655,16 12.8333333,16 L1.16666667,16 C0.522334459,16 0,15.4489658 0,14.7692308 L0,1.23076923 C0,0.551034154 0.522334459,0 1.16666667,0 L9.72883333,0 Z M6.36548862,5.17542359 C6.11720692,4.93565488 5.72153765,4.94252988 5.48173828,5.19078174 L5.48173828,5.19078174 L3.18299167,7.48923751 C2.93900277,7.73326904 2.93900277,8.1288445 3.18299173,8.37287604 L3.18299173,8.37287604 L5.48173833,10.6750818 C5.72394113,10.908979 6.1079261,10.908979 6.3501289,10.6750818 C6.59841088,10.4353128 6.60528832,10.0396952 6.36548895,9.79144331 L6.36548895,9.79144331 L4.50923605,7.93043799 L6.3808487,6.05906212 C6.62064776,5.81081183 6.61377188,5.41519262 6.36548862,5.17542359 Z M11.009075,5.01869617 C10.6741417,4.93523201 10.3349555,5.13905256 10.2514799,5.47394202 L10.2514799,5.47394202 L9.02147908,10.0608692 C8.9320786,10.3942696 9.12991167,10.7370085 9.46335425,10.8263983 C9.79679682,10.9157874 10.139579,10.7179794 10.2289801,10.384579 L10.2289801,10.384579 L11.4577308,5.79640184 L11.4589816,5.79640005 L11.4643785,5.77619549 C11.5478532,5.44130448 11.3440069,5.10216127 11.009075,5.01869617 Z" id="代码文件"></path>
</g>
</g>
</g>
</g>
</svg>

+ 11
- 0
assets/drag.svg View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="22px" height="10px" viewBox="0 0 22 10" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>左右拖动</title>
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Manaco-代码" transform="translate(-440.000000, -546.000000)" fill="#6F6E6E" fill-rule="nonzero">
<g id="左右拖动" transform="translate(440.000000, 546.000000)">
<path d="M5.89500984,9.90606823 L0.154266811,5.3158823 C0.0566144941,5.23813069 0,5.12195442 0,4.99931775 C0,4.87668107 0.0566144941,4.76050481 0.154266811,4.6827532 L5.89500984,0.0925672631 C6.02101948,-0.00758660268 6.19515807,-0.0282927778 6.34232468,0.0393787107 C6.48949128,0.107050199 6.58336056,0.250994188 6.58344941,0.409131811 L6.58344941,2.76972089 C6.58344941,2.99579905 6.77215853,3.1790716 7.00494302,3.1790716 L14.996462,3.1790716 C15.2292464,3.1790716 15.4179556,2.99579905 15.4179556,2.76972089 L15.4179556,0.410496313 C15.4180444,0.252358691 15.5119137,0.108414701 15.6590803,0.040743213 C15.8062469,-0.0269282754 15.9803855,-0.00622210032 16.1063951,0.0939317654 L21.8457332,4.6841177 C21.9433855,4.76186931 22,4.87804558 22,5.00068225 C22,5.12331893 21.9433855,5.23949519 21.8457332,5.3172468 L16.1063951,9.90743274 C15.9803855,10.0075866 15.8062469,10.0282928 15.6590803,9.96062129 C15.5119137,9.8929498 15.4180444,9.74900581 15.4179556,9.59086819 L15.4179556,7.23027911 C15.4179556,7.00420095 15.2292464,6.8209284 14.996462,6.8209284 L7.00494302,6.8209284 C6.77215853,6.8209284 6.58344941,7.00420095 6.58344941,7.23027911 L6.58344941,9.59086819 C6.58336056,9.74900581 6.48949128,9.8929498 6.34232468,9.96062129 C6.19515807,10.0282928 6.02101948,10.0075866 5.89500984,9.90743274 L5.89500984,9.90606823 Z" id="路径"></path>
</g>
</g>
</g>
</svg>

BIN
assets/drag2.png View File

Before After
Width: 28  |  Height: 28  |  Size: 801 B

+ 9
- 0
assets/more.svg View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>更多</title>
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Manaco-代码" transform="translate(-412.000000, -59.000000)" fill="#9FA5B0">
<path d="M426,59 C427.104569,59 428,59.8954305 428,61 L428,73 C428,74.1045695 427.104569,75 426,75 L414,75 C412.895431,75 412,74.1045695 412,73 L412,61 C412,59.8954305 412.895431,59 414,59 L426,59 Z M416,66 C415.447715,66 415,66.4477153 415,67 C415,67.5522847 415.447715,68 416,68 C416.552285,68 417,67.5522847 417,67 C417,66.4477153 416.552285,66 416,66 Z M420,66 C419.447715,66 419,66.4477153 419,67 C419,67.5522847 419.447715,68 420,68 C420.552285,68 421,67.5522847 421,67 C421,66.4477153 420.552285,66 420,66 Z M424,66 C423.447715,66 423,66.4477153 423,67 C423,67.5522847 423.447715,68 424,68 C424.552285,68 425,67.5522847 425,67 C425,66.4477153 424.552285,66 424,66 Z" id="更多"></path>
</g>
</g>
</svg>

+ 11
- 0
assets/submit.svg View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>提交</title>
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Manaco-代码" transform="translate(-23.000000, -124.000000)" fill="#000000" fill-rule="nonzero">
<g id="编组-3" transform="translate(23.000000, 124.000000)">
<path d="M20.1289472,24 L7.67629341,24 C5.45869754,24 3.92343885,22.0588235 3.92343885,19.7647059 C3.92343885,17.4705882 5.79986613,15.8823529 8.01746202,15.8823529 L8.1880463,15.8823529 C8.35863061,14.2941177 9.5527207,12.882353 11.2585637,13.0588235 C11.7703166,13.0588235 12.1114852,13.2352941 12.6232381,13.4117647 C13.3055753,12 14.6702497,11.117647 16.3760926,11.117647 C18.9348571,11.117647 21.152453,13.2352941 21.152453,15.8823529 L21.152453,16.0588235 C23.1994646,16.5882353 24.3935547,18.8823529 23.8818017,21 C23.3700488,22.7647059 21.8347902,23.8235294 20.1289472,24 Z M16.3760926,9.35294117 C14.4996653,9.35294117 12.7938224,10.2352941 11.5997323,11.6470588 L10.7468108,11.6470588 C9.38213639,11.6470588 8.1880463,12.5294118 7.50570911,13.7647059 L0,13.7647059 L0,7.76470589 L22.5171273,7.76470589 L22.5171273,14.2941176 C21.6642058,12 20.1289472,9.17647058 16.3760926,9.35294117 Z M4.09402315,9.1764706 C3.24110165,9.1764706 2.55876448,9.88235296 2.55876448,10.7647059 C2.55876448,11.6470588 3.24110167,12.3529412 4.09402315,12.3529412 C4.94694463,12.3529412 5.62928185,11.6470588 5.62928185,10.7647059 C5.62928185,9.88235296 4.94694465,9.1764706 4.09402315,9.1764706 Z M1.70584298,-5.32907052e-15 L20.8112844,-5.32907052e-15 C21.8347902,-5.32907052e-15 22.5171273,0.705882356 22.5171273,1.7647059 L22.5171273,6 L0,6 L0,1.7647059 C0,0.882352951 0.682337196,-5.32907052e-15 1.70584298,-5.32907052e-15 Z M4.09402315,4.5882353 C4.94694465,4.5882353 5.62928185,3.88235294 5.62928185,3.00000002 C5.62928185,2.11764709 4.94694465,1.41176471 4.09402315,1.41176471 C3.24110165,1.41176471 2.55876448,2.11764707 2.55876448,3.00000002 C2.55876448,3.88235297 3.24110167,4.5882353 4.09402315,4.5882353 L4.09402315,4.5882353 Z M2.55876448,19.9411765 C2.55876448,21.5294117 3.24110167,23.117647 4.43519176,24 L1.70584298,24 C0.682337196,24 0,23.2941176 0,22.2352941 L0,15.3529412 L5.11752893,15.3529412 C3.58227026,16.2352941 2.55876446,18 2.55876448,19.9411765 Z" id="提交"></path>
</g>
</g>
</g>
</svg>

+ 13
- 0
assets/submit2.svg View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="14px" height="16px" viewBox="0 0 14 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>提交</title>
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Manaco-提交" transform="translate(-91.000000, -59.000000)" fill="#000000" fill-rule="nonzero">
<g id="编组-17" transform="translate(91.000000, 59.000000)">
<g id="提交" transform="translate(-0.000000, 0.000000)">
<path d="M13.4778367,15.988073 L9.55310265,15.988073 L9.55310265,15.43347 L9.55310265,15.0458442 L12.972039,15.0458442 L12.972039,0.954155797 L6.14056884,0.954155797 L6.14056884,4.72903466 C6.14056884,4.98922102 5.91411232,5.20014909 5.63477113,5.20014909 C5.62509697,5.20014909 5.61549961,5.19983898 5.60595988,5.19933805 C5.59642015,5.19983898 5.5868228,5.20014909 5.57714861,5.20014909 L1.01856687,5.20014909 L1.01856687,15.0458442 L4.43110069,15.0458442 L4.43110069,15.43347 L4.43110069,15.988073 L0.512769174,15.988073 C0.233427991,15.988073 0.00697148198,15.777145 0.00697148198,15.5169586 L0.00697148198,4.52031308 C0.00697148198,4.40469325 0.0517633795,4.29885948 0.125968379,4.21687961 C0.160010492,4.16720388 0.201492303,4.11953783 0.250183335,4.07557511 L4.51104873,0.228985469 C4.60098467,0.0984748438 4.75803806,0.0119269531 4.93689838,0.0119269531 L13.4778367,0.0119269531 C13.7571778,0.0119269531 13.9836343,0.222855016 13.9836343,0.483041375 L13.9836343,15.5169586 C13.9836343,15.777145 13.7571778,15.988073 13.4778367,15.988073 Z M5.12897344,0.983311219 L1.50169969,4.25792023 L5.12897344,4.25792023 L5.12897344,0.983311219 Z M2.5954584,10.7580947 L2.59340321,9.61971525 L11.8174309,9.61904138 L11.8194925,10.7574268 L2.5954584,10.7580947 Z M2.59340321,6.38751248 L11.8174309,6.38683861 L11.8194925,7.525224 L2.5954584,7.52589191 L2.59340321,6.38751248 Z M2.5954584,9.1419933 L2.59340321,8.00361386 L11.8174309,8.00294 L11.8194925,9.14132537 L2.5954584,9.1419933 Z M7.04972419,11.4171182 L8.63383774,12.8930302 L7.63185892,12.8930302 L7.63239672,14.8444696 L6.41020382,14.845054 L6.40966602,12.8930302 L5.46561062,12.8930302 L7.04972419,11.4171182 Z" id="形状"></path>
</g>
</g>
</g>
</g>
</svg>

+ 11
- 0
assets/warehouse.svg View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="14px" height="16px" viewBox="0 0 14 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>代码仓库</title>
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Manaco-代码" transform="translate(-90.000000, -59.000000)" fill="#000000" fill-rule="nonzero">
<g id="代码仓库" transform="translate(90.000000, 59.000000)">
<path d="M13.5574941,6.11252215 L7.7074972,0.29324975 L7.70749726,0.293249807 C7.31718961,-0.0974428356 6.68402439,-0.0977943985 6.29328388,0.29246314 C6.29302151,0.292725179 6.2927593,0.292987362 6.29249722,0.293249694 L0.442500285,6.11252209 L0.442500249,6.11252213 C0.159278149,6.3940287 0,6.77683852 0,7.17613743 L0,14.5002084 L0,14.5002082 C0,15.3285311 0.671573248,16.0000193 1.5,16.0000193 L12.5,16.0000193 L12.5,16.0000193 C13.3284268,16.0000193 14,15.3285311 14,14.5002082 C14,14.5002082 14,14.5002082 14,14.5002082 L14,7.17613718 L14,7.17619816 C14,6.77689769 13.8407531,6.39408162 13.5575435,6.11256411 L13.5574941,6.11252215 Z M6.36499767,11.8617945 L6.36499768,11.8617945 C6.60479705,12.1100463 6.5979196,12.505664 6.34963762,12.745433 C6.10743482,12.9793301 5.72344986,12.9793301 5.48124706,12.745433 L3.18250042,10.4432272 L3.18250045,10.4432272 C2.93851149,10.1991957 2.93851149,9.80362021 3.18250039,9.55958868 L5.48124703,7.26113288 L5.481247,7.2611329 C5.72104637,7.01288105 6.11671564,7.00600604 6.36499734,7.24577476 C6.6132806,7.48554379 6.62015648,7.881163 6.38035743,8.12941329 C6.37532678,8.13462123 6.37020601,8.13974136 6.3649974,8.14477137 L4.50874477,10.0007892 L6.36499767,11.8617945 Z M10.978743,7.86855544 L9.74999237,12.4567327 L9.74999239,12.4567326 C9.66059128,12.790133 9.31780908,12.987941 8.98436651,12.8985518 C8.65092393,12.8091621 8.45309086,12.4664232 8.54249134,12.1330228 L9.77249213,7.54609562 L9.77249214,7.54609561 C9.8559678,7.21120616 10.1951539,7.00738561 10.5300873,7.09084977 C10.8650191,7.17431487 11.0688655,7.51345807 10.9853908,7.84834909 C10.9837046,7.85511356 10.9819054,7.86184935 10.9799938,7.86855364 L10.978743,7.86855544 Z" id="形状"></path>
</g>
</g>
</g>
</svg>

+ 5
- 0
custom/conf/app.ini.sample View File

@@ -11,6 +11,11 @@ RUN_USER = git
; Either "dev", "prod" or "test", default is "dev"
RUN_MODE = dev

; Is show MLOps
MLOPS = false
; If the MLOPS host is not set, the default setting is AppSubUrl
MLOPS_HOST = https://dev-1-37.apulis.com.cn

[repository]
ROOT =
SCRIPT_TYPE = bash


+ 0
- 45
custom/public/img/logo-w-origin.svg View File

@@ -1,45 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 26.3.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 160 64" style="enable-background:new 0 0 160 64;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
.st1{display:none;fill:#FFFFFF;}
</style>
<g>
<path class="st0" d="M105.3,33.3H87.1c-2.6,0.1-4,1.3-4,3.8v8.3c0.1,2.2,1.5,3.3,4,3.5h18c2.5-0.1,3.8-1.3,3.9-3.6v-8.2
c0.1-2-1.4-3.6-3.4-3.8C105.6,33.3,105.4,33.3,105.3,33.3z M104.6,43.9c-0.1,1-0.7,1.5-1.9,1.7H89.3c-1.3-0.3-1.8-0.7-1.9-1.7v-5.4
c0-1,0.8-1.9,1.8-1.9c0,0,0.1,0,0.1,0h13.1c1.1,0,2,0.8,2.1,2L104.6,43.9z"/>
<path class="st0" d="M81,25.3v-4.7c0-1.1,0.9-2.1,2.1-2.1h19c1.4-0.1,2.1,0.5,2.1,1.8v3.3c0,1.1-0.7,1.7-1.9,1.7H82.8v3.2H105
c2.3,0,3.6-1.1,3.6-3.2v-6.4c0.2-1.8-1.1-3.4-2.8-3.6c-0.3,0-0.5,0-0.8,0h-9.7v-2.6h-4.6v2.6H80.3c-2.6,0-3.9,1.2-3.9,3.9v12.3
c0,5.8-0.9,11.6-2.6,17.1l4.3,0.8c1.8-5.5,2.7-11.3,2.8-17.1v-7.2H81z"/>
<path class="st0" d="M116.2,30.4l4.4,2.4c2.6-1.9,4.4-4.6,5.1-7.6h7.8v-3.2h-7.1c0.2-1.3,0.3-2.6,0.3-3.9h6.6v-3.2h-12.3v-2h-4.6
V18h5.8c0,1.3-0.1,2.6-0.3,3.9h-6.7v3.3h5.8C120.4,27.5,118.6,29.4,116.2,30.4z"/>
<path class="st0" d="M126.5,26.7c1.2,1.8,2.1,3.8,2.9,5.8h4.9c-0.8-2-1.8-4-2.9-5.8H126.5z"/>
<path class="st0" d="M145.4,33.5h-24.7c-2.4,0.3-3.8,1.4-3.9,3.5v6.2h28.4v1.3c0,1.1-0.7,1.7-2.1,1.7h-19.7c-1.3,0-1.9-0.5-1.9-1.5
h-4.5V46c0,1.9,1.6,3.5,3.5,3.6h25.1c2.6-0.1,4-1.3,4-3.5v-9C149.2,35,147.5,33.5,145.4,33.5z M145,39.9h-23.7v-1.1
c0-1,0.8-1.9,1.8-1.9c0,0,0.1,0,0.1,0H143c1.3,0,2.1,0.7,2.1,1.9L145,39.9z"/>
<path class="st0" d="M147.3,14h-8.2c-2.2,0.1-3.5,1.1-3.8,2.9v11.7c0.1,1.6,1.4,2.9,3.1,2.9h8.7c1.9-0.1,3.1-1.1,3.1-2.9V17
C150.4,15.4,149.4,14.3,147.3,14z M146,26.2c0,1.4-0.7,2.1-1.9,2.1h-2.4c-1,0-1.8-0.8-1.8-1.8c0-0.1,0-0.1,0-0.2v-7
c0-1,0.8-1.9,1.8-1.9c0,0,0.1,0,0.1,0h2c1.1,0,2.1,0.8,2.1,1.9l0,0L146,26.2z"/>
</g>
<path class="st1" d="M67.2,44.1V20c0-2.6-1.4-5.1-3.7-6.4l-20.9-12c-2.3-1.3-5.1-1.3-7.4,0l-20.9,12c-2.3,1.3-3.7,3.8-3.7,6.4v24.1
c0,2.6,1.4,5.1,3.7,6.4l20.9,12c2.3,1.3,5.1,1.3,7.4,0l20.9-12C65.8,49.2,67.2,46.7,67.2,44.1z"/>
<path class="st0" d="M61.9,15.4L42,3.9c-1.9-1.1-4.3-1.1-6.2,0L15.9,15.4c-1.9,1.1-3.1,3.2-3.1,5.4v22.9c0,2.2,1.2,4.3,3.1,5.4
l3.8,2c0.8,0.4,0.8,1,0.8,1.9c0,0,0,0.1,0,0.1c0.1,1.6,1.8,3.5,4.2,3.5c2.3,0,4.3-1.9,4.4-4.2c0-1.6-0.8-3.1-2.3-3.9
c-0.6-0.3-1.7-0.5-2.9-0.4c-0.9,0.1-1,0.6-2.8-0.5l-2.8-1.6c-0.8-0.5-1.7-1.3-1.6-2.3V22c0-1.8,0.8-2.8,2.1-3.5L37,7.8
C38.1,7,40.2,7.2,41.5,8l16.4,9.5c2.9,1.6,3,3,3,4v19.7c0,2.3-1.5,4-2.3,4.5l-15.1,8.5C42.8,54.5,42,54,42,53.3l0-2.5
c0-0.1,0-0.3,0-0.4v-2.5c0-0.8,0.4-1.6,1.1-2l8.8-5.7c1.4-0.9,2-1.7,2-3.8L53.7,28c0-1,0.4-1.9,1.2-2.5c1.5-1.3,2-3.7,0.8-5.6
c-0.8-1.4-2.4-2.2-4-2c-1.6,0.1-2.8,1-3.5,2.4c-0.5,1-0.6,2.2-0.3,3.3c0.2,0.8,0.7,1.4,1.3,2c0.7,0.6,1.2,1.5,1.2,2.5v7.2
c0,1.1-0.5,2.1-1.5,2.7l-5.5,2.8c-0.7,0.5-1.6,0-1.6-0.8l-0.2-17.1c0-1,0.5-1.9,1.2-2.5c0.4-0.3,0.7-0.8,1-1.3
c0.7-1.3,0.7-2.9-0.1-4.3c-0.8-1.4-2.5-2.2-4.1-2.1c-2.9,0.3-4.6,3.1-3.8,5.7c0.2,0.8,0.7,1.4,1.3,2c0.7,0.6,1.2,1.5,1.2,2.5v9.9
c0,0.8-0.8,1.3-1.5,0.9L34,32.5c-0.7-0.4-1.2-1.2-1.2-2.1v-2.2c0-1,0.4-1.9,1.2-2.5c1.5-1.3,2-3.7,0.8-5.6c-0.8-1.3-2.4-2.1-4-2
c-2.9,0.2-4.7,3.1-3.8,5.7c0.2,0.8,0.7,1.5,1.3,2c0.7,0.6,1.2,1.5,1.2,2.5l0,3.5c0,1,0,2,1.6,3.1l5.5,3c0.7,0.4,1.2,1.2,1.2,2.1v4.5
c0,0.8-0.8,1.3-1.5,0.9L26.6,41c-0.7-0.4-1.2-1.2-1.2-2.1l-0.2-6.1c0-1,0.4-1.8,1.1-2.5c1.4-1.3,1.9-3.4,0.9-5.3
c-0.8-1.4-2.4-2.3-4-2.2c-2.9,0.2-4.6,3-3.8,5.6c0.2,0.7,0.7,1.4,1.2,1.9c0.7,0.6,1.1,1.5,1.1,2.5l-0.5,6.8c0,1.9,0.3,2.8,1.8,3.5
l11.8,6.3c1,0.6,1.6,1.7,1.6,2.8l0,3.1c0,3.1,4.3,5.7,8.7,3.3l16.4-9.8c1.9-1,3.2-3,3.4-5.2V20.7C65,18.5,63.8,16.5,61.9,15.4z
M24.2,50.7c1.1,0,2,0.9,2,2c0,1.1-0.9,2-2,2c-1.1,0-2-0.9-2-2C22.2,51.7,23.1,50.7,24.2,50.7z M48.7,21.5c0-1.1,0.9-2,2-2
s2,0.9,2,2c0,1.1-0.9,2-2,2S48.7,22.6,48.7,21.5z M30.6,23.8c-1.1,0-2-0.9-2-2s0.9-2,2-2c1.1,0,2,0.9,2,2S31.8,23.8,30.6,23.8z
M22.2,26.4c0-1.1,0.9-2,2-2c1.1,0,2,0.9,2,2s-0.9,2-2,2C23.1,28.4,22.2,27.5,22.2,26.4z M40,17.9c-1.1,0-2-0.9-2-2c0-1.1,0.9-2,2-2
c1.1,0,2,0.9,2,2C42,17,41.1,17.9,40,17.9z"/>
</svg>

+ 45
- 1
custom/public/img/logo-w.svg
File diff suppressed because it is too large
View File


BIN
go_build_code_gitea_io_gitea View File


+ 8
- 17
models/ai_model_manage.go View File

@@ -237,13 +237,10 @@ func QueryModelConvertByName(name string, repoId int64) ([]*AiModelConvert, erro
func QueryModelConvertById(id string) (*AiModelConvert, error) {
sess := x.NewSession()
defer sess.Close()
sess.Select("*").Table(new(AiModelConvert)).Where("id='" + id + "'")
aiModelManageConvertList := make([]*AiModelConvert, 0)
err := sess.Find(&aiModelManageConvertList)
if err == nil {
if len(aiModelManageConvertList) == 1 {
return aiModelManageConvertList[0], nil
}
re := new(AiModelConvert)
isExist, err := sess.Table(new(AiModelConvert)).ID(id).Get(re)
if err == nil && isExist {
return re, nil
}
return nil, err
}
@@ -251,16 +248,10 @@ func QueryModelConvertById(id string) (*AiModelConvert, error) {
func QueryModelById(id string) (*AiModelManage, error) {
sess := x.NewSession()
defer sess.Close()
sess.Select("*").Table("ai_model_manage").
Where("id='" + id + "'")
aiModelManageList := make([]*AiModelManage, 0)
err := sess.Find(&aiModelManageList)
if err == nil {
if len(aiModelManageList) == 1 {
return aiModelManageList[0], nil
}
} else {
log.Info("error=" + err.Error())
re := new(AiModelManage)
isExist, err := sess.Table(new(AiModelManage)).ID(id).Get(re)
if err == nil && isExist {
return re, nil
}
return nil, err
}


+ 126
- 46
models/cloudbrain.go View File

@@ -63,6 +63,7 @@ const (
JobTypeSnn4imagenet JobType = "SNN4IMAGENET"
JobTypeBrainScore JobType = "BRAINSCORE"
JobTypeSnn4Ecoset JobType = "SNN4ECOSET"
JobTypeSim2BrainSNN JobType = "SIM2BRAIN_SNN"
JobTypeTrain JobType = "TRAIN"
JobTypeInference JobType = "INFERENCE"

@@ -287,14 +288,54 @@ func (task *Cloudbrain) CorrectCreateUnix() {
}
}

func AllTerminalStatus() []string {
return []string{string(ModelArtsTrainJobCompleted), string(ModelArtsTrainJobFailed),
string(ModelArtsTrainJobKilled), string(ModelArtsStopped),
string(JobStopped), string(JobFailed),
string(JobSucceeded), GrampusStatusFailed,
GrampusStatusSucceeded, GrampusStatusStopped}
}

func AllStoppingStatus() []string {
return []string{string(ModelArtsStopping), string(ModelArtsDeleting),
string(ModelArtsTrainJobKilling), GrampusStatusStopping}
}
func AllRunningOrWaitingStatus() []string {
return []string{string(JobRunning), string(JobWaiting),
string(ModelArtsCreating), string(ModelArtsStartQueuing),
string(ModelArtsReadyToStart), string(ModelArtsCreateQueue),
string(ModelArtsStarting), string(ModelArtsRestarting),
string(ModelArtsRunning), string(ModelArtsTrainJobSubmitTrying),
string(ModelArtsTrainJobWaiting), string(ModelArtsTrainJobRunning),
GrampusStatusPending, GrampusStatusRunning, GrampusStatusWaiting,
}
}

func AllStoppingAndTerminalStatus() []string {
var status = AllTerminalStatus()
return append(status, AllStoppingStatus()...)
}

func (task *Cloudbrain) IsTerminal() bool {
status := task.Status
return status == string(ModelArtsTrainJobCompleted) || status == string(ModelArtsTrainJobFailed) ||
status == string(ModelArtsTrainJobKilled) || status == string(ModelArtsStopped) ||
status == string(JobStopped) || status == string(JobFailed) ||
status == string(JobSucceeded) || status == GrampusStatusFailed ||
status == GrampusStatusSucceeded || status == GrampusStatusStopped
for _, s := range AllTerminalStatus() {
if status == s {
return true
}
}
return false
}

func (task *Cloudbrain) IsTerminalOrStopping() bool {
status := task.Status
for _, s := range AllStoppingAndTerminalStatus() {
if status == s {
return true
}
}
return false
}

func (task *Cloudbrain) IsRunning() bool {
status := task.Status
return status == string(ModelArtsTrainJobRunning) || status == string(ModelArtsRunning) ||
@@ -346,8 +387,32 @@ func IsModelArtsDebugJobTerminal(status string) bool {
func IsCloudBrainOneDebugJobTerminal(status string) bool {
return status == string(JobStopped) || status == string(JobFailed) || status == string(JobSucceeded)
}
func IsModelBenchMarkJobType(jobType string) bool {
return jobType == string(JobTypeSnn4imagenet) || jobType == string(JobTypeBrainScore) || jobType == string(JobTypeSnn4Ecoset)
func IsBenchMarkJobType(jobType string) bool {
types := AllBenchMarkJobType()
for _, t := range types {
if jobType == t {
return true
}
}
return false
}

func AllJobType() []string {
jobTypes := make([]string, 0)
jobTypes = append(jobTypes, string(JobTypeDebug), string(JobTypeTrain), string(JobTypeInference))
jobTypes = append(jobTypes, AllBenchMarkJobType()...)
return jobTypes
}

func AllBenchMarkJobType() []string {
jobTypes := make([]string, 0)
jobTypes = append(jobTypes, string(JobTypeBenchmark), string(JobTypeModelSafety))
jobTypes = append(jobTypes, AllModelMarkJobType()...)
return jobTypes
}

func AllModelMarkJobType() []string {
return []string{string(JobTypeSnn4imagenet), string(JobTypeBrainScore), string(JobTypeSnn4Ecoset), string(JobTypeSim2BrainSNN)}
}

func ParseAndSetDurationFromCloudBrainOne(result JobResultPayload, task *Cloudbrain) {
@@ -355,7 +420,7 @@ func ParseAndSetDurationFromCloudBrainOne(result JobResultPayload, task *Cloudbr
if task.StartTime == 0 && isActivated {
task.StartTime = timeutil.TimeStamp(result.JobStatus.CreatedTime / 1000)
}
if task.EndTime == 0 && IsCloudBrainOneDebugJobTerminal(task.Status) && isActivated {
if task.EndTime == 0 && IsCloudBrainOneDebugJobTerminal(result.JobStatus.State) && isActivated {
if result.JobStatus.CompletedTime > 0 {
task.EndTime = timeutil.TimeStamp(result.JobStatus.CompletedTime / 1000)
}
@@ -463,32 +528,33 @@ type GetImagesPayload struct {

type CloudbrainsOptions struct {
ListOptions
RepoID int64 // include all repos if empty
UserID int64
JobID string
SortType string
CloudbrainIDs []int64
JobStatus []string
JobStatusNot bool
Keyword string
Type int
JobTypes []string
VersionName string
IsLatestVersion string
JobTypeNot bool
NeedRepoInfo bool
RepoIDList []int64
BeginTime time.Time
EndTime time.Time
ComputeResource string
BeginTimeUnix int64
EndTimeUnix int64
AiCenter string
NeedDeleteInfo string
Cluster string
AccCardType string
AccCardsNum int
WorkServerNumber int
RepoID int64 // include all repos if empty
UserID int64
JobID string
SortType string
CloudbrainIDs []int64
JobStatus []string
JobStatusNot bool
Keyword string
Type int
JobTypes []string
VersionName string
IsLatestVersion string
JobTypeNot bool
NeedRepoInfo bool
RepoIDList []int64
BeginTime time.Time
EndTime time.Time
ComputeResource string
BeginTimeUnix int64
EndTimeUnix int64
DateBeginTimeUnix int64
AiCenter string
NeedDeleteInfo string
Cluster string
AccCardType string
AccCardsNum int
WorkServerNumber int
}

type TaskPod struct {
@@ -1661,6 +1727,15 @@ type Metrics struct {
Value []string `json:"value"` //获取的监控值的序列,元素为String类型
}

type NewModelArtsMetricStatisticResult struct {
MetricsInfo []NewModelArtsMetrics `json:"metrics"` //监控详情
}

type NewModelArtsMetrics struct {
Metric string `json:"metric"` //监控指标项
Value []float32 `json:"value"` //获取的监控值的序列,元素为float类型
}

func Cloudbrains(opts *CloudbrainsOptions) ([]*CloudbrainInfo, int64, error) {
sess := x.NewSession()
defer sess.Close()
@@ -1826,10 +1901,10 @@ func QueryModelTrainJobVersionList(jobId string) ([]*Cloudbrain, int, error) {
cond = cond.And(
builder.Eq{"cloudbrain.job_id": jobId},
)
cond = cond.And(
builder.In("cloudbrain.Status", "COMPLETED", "SUCCEEDED"),
//builder.Eq{"cloudbrain.Status": "COMPLETED"},
)
// cond = cond.And(
// builder.In("cloudbrain.Status", "COMPLETED", "SUCCEEDED"),
// //builder.Eq{"cloudbrain.Status": "COMPLETED"},
// )

sess.OrderBy("cloudbrain.created_unix DESC")
cloudbrains := make([]*Cloudbrain, 0)
@@ -1848,9 +1923,9 @@ func QueryModelTrainJobList(repoId int64) ([]*Cloudbrain, int, error) {
cond = cond.And(
builder.Eq{"repo_id": repoId},
)
cond = cond.And(
builder.In("Status", "COMPLETED", "SUCCEEDED"),
)
// cond = cond.And(
// builder.In("Status", "COMPLETED", "SUCCEEDED"),
// )
cond = cond.And(
builder.Eq{"job_type": "TRAIN"},
)
@@ -2182,7 +2257,7 @@ func GetCloudBrainUnStoppedJob() ([]*Cloudbrain, error) {
JobStopped, JobSucceeded, JobFailed, ModelArtsCreateFailed, ModelArtsStartFailed, ModelArtsUnavailable, ModelArtsResizFailed, ModelArtsDeleted,
ModelArtsStopped, ModelArtsTrainJobCanceled, ModelArtsTrainJobCheckFailed, ModelArtsTrainJobCompleted, ModelArtsTrainJobDeleteFailed, ModelArtsTrainJobDeployServiceFailed,
ModelArtsTrainJobFailed, ModelArtsTrainJobImageFailed, ModelArtsTrainJobKilled, ModelArtsTrainJobLost, ModelArtsTrainJobSubmitFailed, ModelArtsTrainJobSubmitModelFailed).
Limit(100).
// Limit(1000).
Find(&cloudbrains)
}

@@ -2315,11 +2390,10 @@ func GetWaitingCloudbrainCount(cloudbrainType int, computeResource string, jobTy
}
return sess.Count(new(Cloudbrain))
}
func GetNotFinalStatusTaskCount(userID int64, notFinalStatus []string, jobTypes []JobType, cloudbrainTypes []int, computeResource string) (int, error) {
func GetNotFinalStatusTaskCount(userID int64, notFinalStatus []string, jobTypes []string) (int, error) {
count, err := x.In("status", notFinalStatus).
In("job_type", jobTypes).
In("type", cloudbrainTypes).
And("user_id = ? and compute_resource = ?", userID, computeResource).Count(new(Cloudbrain))
And("user_id = ? ", userID).Count(new(Cloudbrain))
return int(count), err
}

@@ -2546,6 +2620,12 @@ func CloudbrainAllStatic(opts *CloudbrainsOptions) ([]*CloudbrainInfo, int64, er
builder.And(builder.Gte{"cloudbrain.created_unix": opts.BeginTimeUnix}, builder.Lte{"cloudbrain.created_unix": opts.EndTimeUnix}),
)
}
if opts.DateBeginTimeUnix > 0 || len(opts.JobStatus) > 0 {
cond = cond.And(builder.Or(
builder.Gte{"cloudbrain.end_time": opts.DateBeginTimeUnix},
builder.In("cloudbrain.status", opts.JobStatus),
))
}
var count int64
var err error
count, err = sess.Unscoped().Where(cond).Count(new(Cloudbrain))
@@ -2806,7 +2886,7 @@ func LoadSpecs(tasks []*Cloudbrain) error {
}
specMap := make(map[int64]*CloudbrainSpec)
for _, v := range specs {
specMap[v.SpecId] = v
specMap[v.CloudbrainID] = v
}
for _, v := range tasks {
if specMap[v.ID] != nil {


+ 31
- 47
models/cloudbrain_static.go View File

@@ -6,7 +6,6 @@ import (

"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
"xorm.io/builder"
)

@@ -234,57 +233,42 @@ func GetRunningTop() ([]*CloudbrainInfo, error) {
return cloudbrains, nil
}

func getCreatePeriodCount(dateBeginTime string, dateEndTime string, hourBeginTime string, hourEndTime string) (int64, error) {
countSql := "SELECT count(*) FROM " +
"public.cloudbrain where to_char(to_timestamp(created_unix), 'YYYY-MM-DD') >= '" + dateBeginTime +
"' and to_char(to_timestamp(created_unix), 'YYYY-MM-DD') < '" + dateEndTime +
"' and to_char(to_timestamp(created_unix), 'HH24:MI:SS') >= '" + hourBeginTime +
"' and to_char(to_timestamp(created_unix), 'HH24:MI:SS') < '" + hourEndTime + "'"
return x.SQL(countSql).Count()
}

//SELECT * FROM xxx WHERE NOT ((endTime < hourBeginTime) OR (startTime > hourEndTime))
func getRunPeriodCount(dateBeginTime string, dateEndTime string, hourBeginTime string, hourEndTime string) (int64, error) {
countSql := "SELECT count(*) FROM " +
"public.cloudbrain where not ((to_char(to_timestamp(start_time), ' HH24:MI:SS') > '" + hourEndTime +
"') or (to_char(to_timestamp(end_time), 'HH24:MI:SS') < '" + hourBeginTime + "'))" +
" and (to_char(to_timestamp(start_time), 'YYYY-MM-DD') >= '" + dateBeginTime +
"' and to_char(to_timestamp(start_time), 'YYYY-MM-DD') < '" + dateEndTime + "')"
return x.SQL(countSql).Count()

func GetCreateHourPeriodCount(cloudbrains []*CloudbrainInfo, dateBeginTime time.Time, dateEndTime time.Time) (map[string]int64, error) {
var createHourPeriodCount = make(map[string]int64)
for _, cloudbrain := range cloudbrains {
if cloudbrain.StartTime != 0 && cloudbrain.EndTime == 0 {
cloudbrain.EndTime = timeutil.TimeStamp(time.Now().Unix())
}
hourBeginTime := dateBeginTime.Unix()
hourEndTime := hourBeginTime + int64(3600)
for hour := 0; hour < 24; hour++ {
if int64(cloudbrain.Cloudbrain.CreatedUnix) >= hourBeginTime && int64(cloudbrain.Cloudbrain.CreatedUnix) < hourEndTime {
createHourPeriodCount[strconv.Itoa(hour)] = createHourPeriodCount[strconv.Itoa(hour)] + 1
}
hourBeginTime = hourEndTime
hourEndTime = hourEndTime + int64(3600)
}
}
return createHourPeriodCount, nil
}

func GetCreateHourPeriodCount(dateBeginTime string, dateEndTime string) (map[string]interface{}, error) {
//0 to 23 for each hour,
dateHourMap := make(map[string]interface{})
var slice = []int64{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23}
for key, value := range slice {
hourBeginHour := util.AddZero(value) + ":00:00"
hourEndHour := util.AddZero(value+1) + ":00:00"
cout, err := getCreatePeriodCount(dateBeginTime, dateEndTime, hourBeginHour, hourEndHour)
if err != nil {
log.Error("Can not query getCreatePeriodCount.", err)
return nil, nil
func GetRunHourPeriodCount(cloudbrains []*CloudbrainInfo, dateBeginTime time.Time, dateEndTime time.Time) (map[string]int64, error) {
var runHourPeriodCount = make(map[string]int64)
for _, cloudbrain := range cloudbrains {
if cloudbrain.StartTime != 0 && cloudbrain.EndTime == 0 {
cloudbrain.EndTime = timeutil.TimeStamp(time.Now().Unix())
}
dateHourMap[strconv.Itoa(key)] = cout
}
return dateHourMap, nil
}

func GetRunHourPeriodCount(dateBeginTime string, dateEndTime string) (map[string]interface{}, error) {
dateHourMap := make(map[string]interface{})
var slice = []int64{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23}
for key, value := range slice {
hourBeginHour := util.AddZero(value) + ":00:00"
hourEndHour := util.AddZero(value+1) + ":00:00"
cout, err := getRunPeriodCount(dateBeginTime, dateEndTime, hourBeginHour, hourEndHour)
if err != nil {
log.Error("Can not query getRunPeriodCount.", err)
return nil, nil
hourBeginTime := dateBeginTime.Unix()
hourEndTime := hourBeginTime + int64(3600)
for hour := 0; hour < 24; hour++ {
if cloudbrain.StartTime.AsTime().Unix() < hourEndTime && cloudbrain.EndTime.AsTime().Unix() > hourBeginTime {
runHourPeriodCount[strconv.Itoa(hour)] = runHourPeriodCount[strconv.Itoa(hour)] + 1
}
hourBeginTime = hourEndTime
hourEndTime = hourEndTime + int64(3600)
}
dateHourMap[strconv.Itoa(key)] = cout
}
return dateHourMap, nil
return runHourPeriodCount, nil
}

func GetCloudbrainRunning() ([]*CloudbrainInfo, error) {


+ 14
- 0
models/error.go View File

@@ -7,6 +7,7 @@ package models

import (
"fmt"
pg "github.com/lib/pq"

"code.gitea.io/gitea/modules/git"
)
@@ -2036,3 +2037,16 @@ func IsErrInsufficientPointsBalance(err error) bool {
func (err ErrInsufficientPointsBalance) Error() string {
return fmt.Sprintf("Insufficient points balance")
}

func IsPGErrUniqueViolation(err error) bool {
if err != nil {
e := err.(*pg.Error)
//23505 is postgrey error code for unique_violation
//see https://www.postgresql.org/docs/current/errcodes-appendix.html
//if unique_violation,user role record exists,just return
if e.Code == "23505" {
return true
}
}
return false
}

+ 27
- 1
models/issue.go View File

@@ -69,7 +69,8 @@ type Issue struct {

//block_chain
Amount int64
IsTransformed bool `xorm:"INDEX NOT NULL DEFAULT false"`
IsTransformed bool `xorm:"INDEX NOT NULL DEFAULT false"`
StayTop int64 `xorm:"NOT NULL DEFAULT 0"`
}

var (
@@ -775,6 +776,19 @@ func (issue *Issue) ChangeContent(doer *User, content string) (err error) {
return sess.Commit()
}

func (issue *Issue) ChangeStayTop(doer *User, stayTop int64) (err error) {
issue.StayTop = stayTop
sess := x.NewSession()
defer sess.Close()
if err = sess.Begin(); err != nil {
return err
}
if err = updateIssueCols(sess, issue, "stay_top"); err != nil {
return fmt.Errorf("UpdateIssueCols,stayTop: %v", err)
}
return sess.Commit()
}

// ChangeRef changes issue ref, as the given user.
func (issue *Issue) ChangeRef(doer *User, newRef string) (err error) {
oldRef := issue.Ref
@@ -1116,6 +1130,7 @@ type IssuesOptions struct {
// sortIssuesSession sort an issues-related session based on the provided
// sortType string
func sortIssuesSession(sess *xorm.Session, sortType string, priorityRepoID int64) {
sess.Desc("issue.stay_top")
switch sortType {
case "oldest":
sess.Asc("issue.created_unix")
@@ -1966,3 +1981,14 @@ func UpdateReactionsMigrationsByType(gitServiceType structs.GitServiceType, orig
})
return err
}

func GetMaxStayTop(repoId int64) int64 {
re := new(Issue)
isExist, err := x.Table(new(Issue)).Where("repo_id="+fmt.Sprint(repoId)).Desc("stay_top").Limit(1, 0).Get(re)
if err == nil {
if isExist {
return re.StayTop + 1
}
}
return 1
}

+ 3
- 0
models/models.go View File

@@ -167,6 +167,9 @@ func init() {
new(Badge),
new(BadgeUser),
new(BadgeUserLog),
new(TechConvergeBaseInfo),
new(RepoConvergeInfo),
new(UserRole),
)

tablesStatistic = append(tablesStatistic,


+ 9
- 4
models/point_account.go View File

@@ -30,8 +30,8 @@ type PointAccount struct {
func (account *PointAccount) Increase(amount int64, sourceId string) error {
sess := x.NewSession()
defer sess.Close()
sql := "update point_account set balance = balance + ?,total_earned = total_earned + ? ,version = version + 1 where account_code = ? "
_, err := sess.Exec(sql, amount, amount, account.AccountCode)
sql := "update point_account set balance = balance + ?,total_earned = total_earned + ? ,version = version + 1,updated_unix = ? where account_code = ? "
_, err := sess.Exec(sql, amount, amount, timeutil.TimeStampNow(), account.AccountCode)
if err != nil {
sess.Rollback()
return err
@@ -58,8 +58,8 @@ func (account *PointAccount) Increase(amount int64, sourceId string) error {
func (account *PointAccount) Decrease(amount int64, sourceId string) error {
sess := x.NewSession()
defer sess.Close()
sql := "update point_account set balance = balance - ?,total_consumed = total_consumed + ? ,version = version + 1 where account_code = ? "
_, err := sess.Exec(sql, amount, amount, account.AccountCode)
sql := "update point_account set balance = balance - ?,total_consumed = total_consumed + ? ,version = version + 1 ,updated_unix = ? where account_code = ? "
_, err := sess.Exec(sql, amount, amount, timeutil.TimeStampNow(), account.AccountCode)
if err != nil {
sess.Rollback()
return err
@@ -140,3 +140,8 @@ func GetPointAccountMapByUserIds(userIds []int64) (map[int64]*PointAccount, erro
}
return accountMap, nil
}

type PointDeductCondition struct {
SpecUnitPrice int
WorkServerNumber int
}

+ 1
- 0
models/repo.go View File

@@ -231,6 +231,7 @@ type Repository struct {
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`

CurrentBranch string
Hot int64 `xorm:"-"`
Active int64 `xorm:"-"`
Alias string `xorm:"INDEX"`


+ 30
- 0
models/repo_list.go View File

@@ -192,6 +192,8 @@ type SearchRepoOptions struct {
// False -> include just no courses
Course util.OptionalBool
OnlySearchPrivate bool

RepoIds []int64
}

//SearchOrderBy is used to sort the result
@@ -206,6 +208,7 @@ type FindReposResponse struct {
Page int
PageSize int
Total int64
RepoIds []int64
}

// Strings for sorting result
@@ -281,6 +284,33 @@ func SearchRepositoryCondition(opts *SearchRepoOptions) builder.Cond {
if opts.StarredByID > 0 {
cond = cond.And(builder.In("id", builder.Select("repo_id").From("star").Where(builder.Eq{"uid": opts.StarredByID})))
}
if len(opts.RepoIds) > 0 {
const MaxINItems = 1000
if len(opts.RepoIds) <= MaxINItems {
cond = cond.And(builder.In("id", opts.RepoIds))
} else {
repoIdsMap := make(map[int][]int64, 0)
i := 0

for j := 0; j < len(opts.RepoIds); j++ {
if _, ok := repoIdsMap[i]; !ok {
repoIdsMap[i] = make([]int64, 0)
}
repoIdsMap[i] = append(repoIdsMap[i], opts.RepoIds[j])
if (j+1)%MaxINItems == 0 {
i += 1
}

}
subCond := builder.NewCond()
for _, repoSplit := range repoIdsMap {
subCond = subCond.Or(builder.In("id", repoSplit))
}
cond = cond.And(subCond)

}

}

// Restrict repositories to those the OwnerID owns or contributes to as per opts.Collaborate
if opts.OwnerID > 0 {


+ 71
- 0
models/role.go View File

@@ -0,0 +1,71 @@
package models

import (
"code.gitea.io/gitea/modules/timeutil"
"fmt"
)

type RoleType string

const (
TechProgramAdmin RoleType = "TechProgramAdmin"
)

type Role struct {
Type RoleType
Name string
Description string
}

type UserRole struct {
ID int64 `xorm:"pk autoincr"`
UserId int64 `xorm:"INDEX UNIQUE(uq_user_role)"`
RoleType RoleType `xorm:"UNIQUE(uq_user_role)"`
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
}

func NewUserRole(r UserRole) (int64, error) {
return x.Insert(&r)
}
func DeleteUserRole(roleType RoleType, userId int64) (int64, error) {
return x.Where("role_type = ? and user_id = ?", roleType, userId).Delete(&UserRole{})
}

func GetUserRoleByUserAndRole(userId int64, roleType RoleType) (*UserRole, error) {
r := &UserRole{}
has, err := x.Where("role_type = ? and user_id = ?", roleType, userId).Get(r)
if err != nil {
return nil, err
} else if !has {
return nil, ErrRecordNotExist{}
}
return r, nil
}

func GetRoleByCode(code string) (*Role, error) {
r := &Role{}
has, err := x.Where("code = ?", code).Get(r)
if err != nil {
return nil, err
} else if !has {
return nil, ErrRecordNotExist{}
}
return r, nil
}

type ErrRoleNotExists struct {
}

func IsErrRoleNotExists(err error) bool {
_, ok := err.(ErrRoleNotExists)
return ok
}

func (err ErrRoleNotExists) Error() string {
return fmt.Sprintf("role is not exists")
}

type OperateRoleReq struct {
UserName string `json:"user_name" binding:"Required"`
RoleType RoleType `json:"role_type" binding:"Required"`
}

+ 661
- 0
models/tech_converge_info.go View File

@@ -0,0 +1,661 @@
package models

import (
"strconv"
"strings"
"time"

"code.gitea.io/gitea/modules/timeutil"
"xorm.io/builder"
)

const (
TechShow = 1
TechHide = 2
TechMigrating = 3
TechMigrateFailed = 4
TechNotExist = 5
)

const DefaultTechApprovedStatus = TechShow

type TechConvergeBaseInfo struct {
ID int64 `xorm:"pk autoincr"`
ProjectNumber string `xorm:"UNIQUE NOT NULL"` //项目立项编号
ProjectName string //科技项目名称
Institution string //项目承担单位
ApplyYear int //申报年度
Province string //所属省(省市)
Category string //单位性质
Recommend string //推荐单位
Owner string //项目负责人
Phone string //负责人电话
Email string //负责人邮箱
Contact string //项目联系人
ContactPhone string //联系人电话
ContactEmail string //联系人邮箱
ExecuteMonth int //执行周期(月)
ExecuteStartYear int //执行开始年份
ExecuteEndYear int //执行结束年份
ExecutePeriod string //执行期限
Type string //项目类型
StartUp string //启动会时间
NumberTopic int
Topic1 string
Topic2 string
Topic3 string
Topic4 string
Topic5 string
Topic6 string
Topic7 string
AllInstitution string `xorm:"TEXT"`
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
}

func (t *TechConvergeBaseInfo) Brief() *TechConvergeBrief {
return &TechConvergeBrief{
ProjectNumber: t.ProjectNumber,
ProjectName: t.ProjectName,
Institution: t.Institution,
AllInstitution: t.AllInstitution,
}
}
func (t *TechConvergeBaseInfo) IsValidInstitution(institution string) bool {
if t.AllInstitution == "" && t.Institution == "" {
return false
}

allInstitution := make([]string, 0)
if t.AllInstitution != "" {
allInstitution = strings.Split(t.AllInstitution, ",")
}
if t.Institution != "" {
allInstitution = append(allInstitution, t.Institution)
}

newInstitution := strings.Split(institution, ",")
total := len(newInstitution)
matched := 0
for _, n := range newInstitution {
for _, s := range allInstitution {
if s == n {
matched++
break
}
}
}
if matched == total {
return true
}
return false
}

type RepoConvergeInfo struct {
ID int64 `xorm:"pk autoincr"`
RepoID int64
Url string
BaseInfoID int64
Institution string
UID int64
Status int
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
User *User `xorm:"-"`
Repo *Repository `xorm:"-"`
BaseInfo *TechConvergeBaseInfo `xorm:"-"`
}

func (r *RepoConvergeInfo) InsertOrUpdate() error {
if r.ID != 0 {
_, err := x.ID(r.ID).Update(r)
return err
} else {
_, err := x.InsertOne(r)
return err
}
}

func GetTechConvergeBaseInfoByProjectNumber(projectNumber string) (*TechConvergeBaseInfo, error) {
tb := &TechConvergeBaseInfo{ProjectNumber: projectNumber}
return getTechConvergeBaseInfo(tb)
}

func GetTechConvergeBaseInfoById(id int64) (*TechConvergeBaseInfo, error) {
tb := &TechConvergeBaseInfo{ID: id}
return getTechConvergeBaseInfo(tb)
}

func (baseInfo *TechConvergeBaseInfo) InsertOrUpdate() error {
if baseInfo.ID != 0 {
_, err := x.ID(baseInfo.ID).Update(baseInfo)
return err
} else {
_, err := x.InsertOne(baseInfo)
return err
}
}

type ErrTechConvergeBaseInfoNotExist struct {
ID string
}

func (err ErrTechConvergeBaseInfoNotExist) Error() string {
return "tech.tech_not_exist"
}

func IsErrTechConvergeBaseInfoNotExist(err error) bool {
_, ok := err.(ErrTechConvergeBaseInfoNotExist)
return ok
}

func getTechConvergeBaseInfo(tb *TechConvergeBaseInfo) (*TechConvergeBaseInfo, error) {
has, err := x.Get(tb)
if err != nil {
return nil, err
} else if !has {
if tb.ProjectNumber != "" {
return nil, ErrTechConvergeBaseInfoNotExist{tb.ProjectNumber}
} else {
return nil, ErrTechConvergeBaseInfoNotExist{strconv.FormatInt(tb.ID, 10)}
}
}
return tb, nil
}

func GetProjectNames() []string {
var names []string
x.Table("tech_converge_base_info").Distinct("project_name").Find(&names)
return names
}
func GetIdByProjectName(name string) []string {
var ids []int64
x.Table("tech_converge_base_info").Cols("id").Where("project_name=?", name).Find(&ids)

idStrs := make([]string, 0, len(ids))
for _, id := range ids {
idStrs = append(idStrs, strconv.FormatInt(id, 10))
}
return idStrs
}
func GetSummitRepoIds() []int64 {
var ids []int64
x.Table("repo_converge_info").Cols("repo_id").Find(&ids)
return ids
}

func GetTechRepoTopics(limit int) []string {

repoIds := GetSummitRepoIds()
if len(repoIds) == 0 {
return []string{}
}

//select name, repo_count from topic a, repo_topic b
//where a.id=b.topic_id and repo_id in (1,3) order by repo_count desc
inCondition := "repo_id in ("

const MaxINItems = 1000
for i := 0; i < len(repoIds); i++ {
if i == len(repoIds)-1 {
inCondition += strconv.FormatInt(repoIds[i], 10)
} else if (i+1)%MaxINItems == 0 {
inCondition += strconv.FormatInt(repoIds[i], 10) + ") or repo_id in ("
} else {
inCondition += strconv.FormatInt(repoIds[i], 10) + ","
}

}
inCondition += ")"

sql := "select distinct name, repo_count from topic a, repo_topic b where a.id=b.topic_id and (" + inCondition + ") order by repo_count desc"
if limit > 0 {
sql += "limit " + strconv.Itoa(limit)
}

result, err := x.QueryString(sql)
if err != nil {
return []string{}
}
var topics []string
for _, record := range result {
topics = append(topics, record["name"])
}
return topics

}

func GetProjectTypes() []string {

sql := "SELECT COUNT(id) AS theCount, type from tech_converge_base_info GROUP BY type ORDER BY theCount DESC"
result, err := x.QueryString(sql)
if err != nil {
return []string{}
}
var projectTypes []string
for _, record := range result {
projectTypes = append(projectTypes, record["type"])
}
return projectTypes

}
func GetApplyExecuteYears() ([]int, []int) {
apply, executeStart, executeEnd := GetYearInfos()
applyEnd := time.Now().Year()

var applyArray []int
var executeArray []int

for i := apply; i <= applyEnd; i++ {
applyArray = append(applyArray, i)

}
for i := executeStart; i <= executeEnd; i++ {
executeArray = append(executeArray, i)

}
return applyArray, executeArray
}
func GetYearInfos() (int, int, int) {

sql := "select min(apply_year) as apply_year,min(CASE WHEN execute_start_year != 0 THEN execute_start_year END) as execute_start_year,max(execute_end_year) as execute_end_year from tech_converge_base_info"
result, err := x.QueryString(sql)
if err != nil {
return 2018, 2019, 2024
}

for _, record := range result {
apply, _ := strconv.Atoi(record["apply_year"])
executeStart, _ := strconv.Atoi(record["execute_start_year"])
executeEnd, _ := strconv.Atoi(record["execute_end_year"])
return apply, executeStart, executeEnd
}
return 2018, 2019, 2024
}

func GetAllInstitutions() []string {
var names []string
x.Table("tech_converge_base_info").Cols("all_institution").Find(&names)
var allNames []string
for _, name := range names {
singleNames := strings.Split(name, ",")
for _, singleName := range singleNames {
if singleName != "" {
if !contains(allNames, singleName) {
allNames = append(allNames, singleName)
}
}
}

}
return allNames
}

func contains(s []string, e string) bool {
for _, a := range s {
if a == e {
return true
}
}
return false
}

type SearchTechOpt struct {
Q string //科技项目名称
ProjectType string
Institution string
ApplyYear int
ExecuteYear int
OrderBy string
ListOptions
}

type SearchRepoOpt struct {
Q string //项目名称,简介
ProjectName string
Topic string
Institution string
OrderBy string
ListOptions
}

type SearchUserRepoOpt struct {
User *User
ListOptions
}

type TechRepoInfoUser struct {
ID int64 `json:"id"`
ProjectNumber string `json:"no"`
ProjectName string `json:"name"`
Institution string `json:"institution"`
AllInstitution string `json:"all_institution"`
Url string `json:"url"`
RepoName string `json:"repo_name"`
RepoOwnerName string `json:"repo_owner_name"`
ContributionInstitution string `json:"contribution_institution"`
UserName string `json:"user_name"`
Status int `json:"status"`
CreatedUnix timeutil.TimeStamp `json:"created_unix"`
UpdatedUnix timeutil.TimeStamp `json:"updated_unix"`
}
type TechRepoInfoAdmin struct {
ID int64 `json:"id"`
Url string `json:"url"`
ContributionInstitution string `json:"contribution_institution"`
Status int `json:"status"`
ProjectNumber string `json:"no"`
ProjectName string `json:"name"`
ApplyYear int `json:"apply_year"`
Institution string `json:"institution"`
Province string `json:"province"`
Category string `json:"category"`
Recommend string `json:"recommend"`
Owner string `json:"owner"`
Phone string `json:"phone"`
Email string `json:"email"`
Contact string `json:"contact"`
ContactPhone string `json:"contact_phone"`
ContactEmail string `json:"contact_email"`
ExecuteMonth int `json:"execute_month"`
ExecutePeriod string `json:"execute_period"`
Type string `json:"type"`
StartUp string `json:"start_up"`
NumberTopic int `json:"number_topic"`
Topic1 string `json:"topic1"`
Topic2 string `json:"topic2"`
Topic3 string `json:"topic3"`
Topic4 string `json:"topic4"`
Topic5 string `json:"topic5"`
AllInstitution string `json:"all_institution"`
RepoName string `json:"repo_name"`
RepoOwnerName string `json:"repo_owner_name"`
UserName string `json:"user_name"`
CreatedUnix timeutil.TimeStamp `json:"created_unix"`
UpdatedUnix timeutil.TimeStamp `json:"updated_unix"`
}

type RepoWithInstitution struct {
ID int64 `json:"id"`
OwnerID int64 `json:"owner_id"`
OwnerName string `json:"owner_name"`
Name string `json:"name"`
Alias string `json:"alias"`
Topics []string `json:"topics"`
Description string `json:"description"`
Institution string `json:"institution"`
RelAvatarLink string `json:"rel_avatar_link"`
UpdatedUnix timeutil.TimeStamp `json:"updated_unix"`
}

type TechRepoInfo struct {
ID int64 `json:"id"`
ProjectName string `json:"project_name"`
Institution string `json:"institution"`
Type string `json:"type"`
ApplyYear int `json:"apply_year"`
ExecutePeriod string `json:"execute_period"`
AllInstitution string `json:"all_institution"`
RepoCount int `json:"repo_numer"`
Repos []*RepoWithInstitution
}

func ShowTechRepo(ids []int64, show bool) error {

status := TechShow
if !show {
status = TechHide
}

idStrs := make([]string, 0, len(ids))
for _, id := range ids {
idStrs = append(idStrs, strconv.FormatInt(id, 10))
}

sql := "update repo_converge_info set status=? where id in (" + strings.Join(idStrs, ",") + ") and (status=" + strconv.Itoa(TechShow) + " or status=" + strconv.Itoa(TechHide) + ")"
_, err := x.Exec(sql, status)
return err
}

func GetTechRepoInfoForUser(opts *SearchUserRepoOpt) ([]*RepoConvergeInfo, int64, error) {
cond := buildTechRepoForUserCondition(opts)
total, err := x.Where(cond).Count(new(RepoConvergeInfo))
if err != nil {
return nil, 0, err
}
repoConvergeInfos := make([]*RepoConvergeInfo, 0)
err = x.Where(cond).Limit(opts.PageSize, (opts.Page-1)*opts.PageSize).Desc("status").Find(&repoConvergeInfos)
if err != nil {
return nil, 0, err
}

loadAttributes(repoConvergeInfos)

return repoConvergeInfos, total, nil

}

func loadAttributes(infos []*RepoConvergeInfo) {
for _, info := range infos {
info.User, _ = GetUserByID(info.UID)
info.Repo, _ = GetRepositoryByID(info.RepoID)
info.BaseInfo, _ = GetTechConvergeBaseInfoById(info.BaseInfoID)
}
}

func buildTechRepoForUserCondition(opts *SearchUserRepoOpt) builder.Cond {
var cond = builder.NewCond()
if opts.User != nil {
cond = cond.And(builder.Eq{"uid": opts.User.ID})
}
return cond
}

func GetAvailableRepoConvergeInfo(opt *SearchRepoOpt) ([]*RepoConvergeInfo, error) {

repos := make([]*RepoConvergeInfo, 0)
err := x.Table("repo_converge_info").Where(buildRepoFilterCond(opt)).Find(&repos)
return repos, err

}

func buildRepoFilterCond(opt *SearchRepoOpt) string {

sql := "status=" + strconv.Itoa(TechShow)

if opt.Institution != "" {
sql += " and (institution like '%" + opt.Institution + ",%'" + " or institution like '%," + opt.Institution + "%'" + " or institution = '" + opt.Institution + "')"
}

if opt.ProjectName != "" {
baseInfoIds := GetIdByProjectName(opt.ProjectName)
if len(baseInfoIds) > 0 {

sql += " and base_info_id in (" + strings.Join(baseInfoIds, ",") + ")"
}

}
return sql
}

func SearchTechRepoInfo(opt *SearchTechOpt) ([]*TechRepoInfo, int64, error) {

sql := `select a.*,COALESCE(b.count,0) as repo_count, COALESCE(b.max,0) as max from tech_converge_base_info a left join
(select base_info_id,count(id),max(updated_unix) from repo_converge_info where status=` + strconv.Itoa(TechShow) + ` GROUP BY base_info_id ) b
on a.id=b.base_info_id`
totalSql := "select count(*) from (" + sql + ") c" + buildTechFilterCond(opt)
total, err := x.SQL(totalSql).Count(new(TechConvergeBaseInfo))
resultList := make([]*TechRepoInfo, 0)
if err != nil {
return resultList, total, err

}
resultSql := "select id,project_name, institution,type,apply_year,execute_period,all_institution,repo_count from (" +
sql + ") c " + buildTechFilterCond(opt) + opt.OrderBy + " offset " + strconv.Itoa((opt.Page-1)*opt.PageSize) + " limit " + strconv.Itoa(opt.PageSize)

resultMap, err := x.QueryInterface(resultSql)
if err == nil {
for _, record := range resultMap {
resultList = append(resultList, &TechRepoInfo{
ID: record["id"].(int64),
ProjectName: record["project_name"].(string),
Institution: record["institution"].(string),
Type: record["type"].(string),
ApplyYear: int(record["apply_year"].(int64)),
ExecutePeriod: record["execute_period"].(string),
AllInstitution: record["all_institution"].(string),
RepoCount: int(record["repo_count"].(int64)),
})
}
}

loadRepoInfoForTech(resultList)

return resultList, total, err

}

func buildTechFilterCond(opt *SearchTechOpt) string {

sql := ""

if opt.Q != "" {
sql += getWherePrefix(sql) + " project_name like '%" + opt.Q + "%'"

}
if opt.ProjectType != "" {
sql += getWherePrefix(sql) + " type ='" + opt.ProjectType + "'"
}
if opt.ApplyYear != 0 {
sql += getWherePrefix(sql) + " apply_year =" + strconv.Itoa(opt.ApplyYear)
}
if opt.Institution != "" {
sql += getWherePrefix(sql) + " (all_institution like '%" + opt.Institution + ",%'" + " or all_institution like '%," + opt.Institution + "%'" + " or all_institution = '" + opt.Institution + "')"
}

if opt.ExecuteYear != 0 {
sql += getWherePrefix(sql) + " execute_start_year <=" + strconv.Itoa(opt.ExecuteYear) + " and execute_end_year >=" + strconv.Itoa(opt.ExecuteYear)
}
return sql
}

func getWherePrefix(sql string) string {
if sql == "" {
return " where "
}
return " and "
}

func loadRepoInfoForTech(list []*TechRepoInfo) {

for _, techRepo := range list {
techRepo.Repos = []*RepoWithInstitution{}
if techRepo.RepoCount > 0 {
var repoIds []int64
x.Table("repo_converge_info").Cols("repo_id").Where("base_info_id=? and status=?", techRepo.ID, TechShow).Limit(2).Desc("updated_unix").Find(&repoIds)

resultMap, err := GetRepositoriesMapByIDs(repoIds)

if err == nil {

for _, repoId := range repoIds {
repo, ok := resultMap[repoId]
if ok {
techRepo.Repos = append(techRepo.Repos, &RepoWithInstitution{
ID: repo.ID,
Institution: techRepo.Institution,
OwnerID: repo.OwnerID,
OwnerName: repo.OwnerName,
Name: repo.Name,
Alias: repo.Alias,
Topics: repo.Topics,
Description: repo.Description,
RelAvatarLink: repo.RelAvatarLink(),
UpdatedUnix: repo.UpdatedUnix,
})
}

}

}
}
}

}

type TechConvergeBrief struct {
ProjectNumber string `json:"no"` //项目立项编号
ProjectName string `json:"name"` //科技项目名称
Institution string `json:"institution"` //项目承担单位
AllInstitution string `json:"all_institution"`
}

type FindTechOpt struct {
TechNo string
ProjectName string
Institution string
}

func FindTech(opt FindTechOpt) ([]*TechConvergeBaseInfo, error) {
var cond = builder.NewCond()
if opt.TechNo != "" {
cond = cond.And(builder.Like{"project_number", opt.TechNo})
}
if opt.ProjectName != "" {
cond = cond.And(builder.Like{"project_name", opt.ProjectName})
}
if opt.Institution != "" {
cond = cond.And(builder.Like{"institution", opt.Institution}.Or(builder.Like{"all_institution", opt.Institution}))
}

r := make([]*TechConvergeBaseInfo, 0)
err := x.Where(cond).OrderBy("updated_unix desc").Find(&r)
if err != nil {
return nil, err
}
return r, nil
}

func GetTechByTechNo(techNo string) (*TechConvergeBaseInfo, error) {
var tech = &TechConvergeBaseInfo{}
has, err := x.Where("project_number = ?", techNo).Get(tech)
if err != nil {
return nil, err
} else if !has {
return nil, ErrTechConvergeBaseInfoNotExist{}
}
return tech, nil

}

type GetRepoConvergeOpts struct {
RepoId int64
BaseInfoId int64
Status []int
}

func GetRepoConverge(opts GetRepoConvergeOpts) ([]*RepoConvergeInfo, error) {
r := make([]*RepoConvergeInfo, 0)
cond := builder.NewCond()
if opts.RepoId > 0 {
cond = cond.And(builder.Eq{"repo_id": opts.RepoId})
}
if opts.BaseInfoId > 0 {
cond = cond.And(builder.Eq{"base_info_id": opts.BaseInfoId})
}
if len(opts.Status) > 0 {
cond = cond.And(builder.In("status", opts.Status))
}
err := x.Where(cond).Find(&r)
if err != nil {
return nil, err
}
return r, nil

}

func UpdateRepoConvergeStatus(id int64, status int) (int64, error) {
return x.ID(id).Update(&RepoConvergeInfo{
Status: status,
})
}

+ 15
- 15
models/user_business_analysis.go View File

@@ -846,13 +846,17 @@ func RefreshUserYearTable(pageStartTime time.Time, pageEndTime time.Time) {
scoreMap["codescore"] = codescore
cloudBrainInfo := getCloudBrainInfo(&dateRecordAll, CloudBrainTaskItemMap, scoreMap)
playARoll := getPlayARoll(bonusMap, dateRecordAll.Name, scoreMap)
exteral := 0
if int(subTime.Hours())%24 > 0 {
exteral = 1
}
re := &UserSummaryCurrentYear{
ID: dateRecordAll.ID,
Name: dateRecordAll.Name,
Email: dateRecordAll.Email,
Phone: dateRecordAll.Phone,
RegistDate: dateRecordAll.RegistDate,
DateCount: int(subTime.Hours()) / 24,
DateCount: int(subTime.Hours())/24 + exteral,
MostActiveDay: mostActiveDay,
RepoInfo: repoInfo,
DataSetInfo: dataSetInfo,
@@ -870,17 +874,6 @@ func RefreshUserYearTable(pageStartTime time.Time, pageEndTime time.Time) {
log.Info("update user year data finished. ")
}

func isUserYearData(tableName string) bool {
if tableName == "user_business_analysis_current_year" {
currentTimeNow := time.Now()
if currentTimeNow.Year() >= 2023 {
return false
}
return true
}
return false
}

func getBonusWeekDataMap() map[int64][]int {
bonusMap := make(map[int64][]int)
url := setting.RecommentRepoAddr + "bonus/weekdata/record.txt"
@@ -2307,7 +2300,7 @@ func queryUserCreateRepo(start_unix int64, end_unix int64) (map[int64]int, map[s
var indexTotal int64
indexTotal = 0
for {
sess.Select("id,owner_id,name,is_private,clone_cnt").Table("repository").Where(cond).OrderBy("id asc").Limit(PAGE_SIZE, int(indexTotal))
sess.Select("id,owner_id,name,is_private,clone_cnt,alias").Table("repository").Where(cond).OrderBy("id asc").Limit(PAGE_SIZE, int(indexTotal))
repoList := make([]*Repository, 0)
sess.Find(&repoList)
log.Info("query Repository size=" + fmt.Sprint(len(repoList)))
@@ -2530,6 +2523,13 @@ func queryUserModelConvert(start_unix int64, end_unix int64) map[int64]int {
return resultMap
}

func isBenchMark(JobType string) bool {
if JobType == "BENCHMARK" || JobType == "MODELSAFETY" || JobType == "SNN4IMAGENET" || JobType == "BRAINSCORE" || JobType == "SNN4ECOSET" {
return true
}
return false
}

func queryCloudBrainTask(start_unix int64, end_unix int64) (map[int64]int, map[string]int) {
sess := x.NewSession()
defer sess.Close()
@@ -2565,7 +2565,7 @@ func queryCloudBrainTask(start_unix int64, end_unix int64) (map[int64]int, map[s
setMapKey("NpuTrainJob", cloudTaskRecord.UserID, 1, resultItemMap)
} else if cloudTaskRecord.JobType == "INFERENCE" {
setMapKey("NpuInferenceJob", cloudTaskRecord.UserID, 1, resultItemMap)
} else if cloudTaskRecord.JobType == "BENCHMARK" || cloudTaskRecord.JobType == "MODELSAFETY" {
} else if isBenchMark(cloudTaskRecord.JobType) {
setMapKey("GpuBenchMarkJob", cloudTaskRecord.UserID, 1, resultItemMap)
} else {
setMapKey("NpuDebugJob", cloudTaskRecord.UserID, 1, resultItemMap)
@@ -2576,7 +2576,7 @@ func queryCloudBrainTask(start_unix int64, end_unix int64) (map[int64]int, map[s
setMapKey("GpuTrainJob", cloudTaskRecord.UserID, 1, resultItemMap)
} else if cloudTaskRecord.JobType == "INFERENCE" {
setMapKey("GpuInferenceJob", cloudTaskRecord.UserID, 1, resultItemMap)
} else if cloudTaskRecord.JobType == "BENCHMARK" || cloudTaskRecord.JobType == "MODELSAFETY" {
} else if isBenchMark(cloudTaskRecord.JobType) {
setMapKey("GpuBenchMarkJob", cloudTaskRecord.UserID, 1, resultItemMap)
} else {
setMapKey("GpuDebugJob", cloudTaskRecord.UserID, 1, resultItemMap)


+ 6
- 1
modules/auth/wechat/cloudbrain.go View File

@@ -105,6 +105,7 @@ func (CloudbrainStopMsg) TemplateId(ctx *TemplateContext) string {

var startMsg = &CloudbrainStartMsg{}
var stopMsg = &CloudbrainStopMsg{}
var ComingStopMsg = &CloudbrainComingToStopMsg{}

func GetTemplateFromOperateType(operate JobOperateType) Template {
switch operate {
@@ -160,7 +161,11 @@ func getJobTypeDisplayName(jobType string) string {
return "评测任务"
case string(models.JobTypeTrain):
return "训练任务"
case string(models.JobTypeInference):
case string(models.JobTypeInference),
string(models.JobTypeModelSafety),
string(models.JobTypeSnn4imagenet),
string(models.JobTypeBrainScore),
string(models.JobTypeSnn4Ecoset):
return "推理任务"
}
return ""


+ 48
- 0
modules/auth/wechat/point.go View File

@@ -0,0 +1,48 @@
package wechat

import (
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"fmt"
"time"
)

type CloudbrainComingToStopMsg struct {
}

func (CloudbrainComingToStopMsg) Data(ctx *TemplateContext) *DefaultWechatTemplate {
var balance int64
if ctx.PointAccount != nil {
balance = ctx.PointAccount.Balance
}
return &DefaultWechatTemplate{
First: TemplateValue{Value: setting.CloudbrainComingStopTitle},
Keyword1: TemplateValue{Value: ctx.Cloudbrain.DisplayJobName + "(任务名称)"},
Keyword2: TemplateValue{Value: fmt.Sprintf("%d积分", balance)},
Keyword3: TemplateValue{Value: setting.CloudbrainComingStopChargeLink},
Keyword4: TemplateValue{Value: time.Unix(int64(ctx.EstimatedEndTime), 0).Format("2006-01-02 15:04:05") + "(预计停止时间)"},
Remark: TemplateValue{Value: setting.CloudbrainComingStopRemark},
}
}

func (CloudbrainComingToStopMsg) ShouldSend(ctx *TemplateContext) bool {
return setting.CloudbrainComingStopSendFlag
}

func (CloudbrainComingToStopMsg) MsgId(ctx *TemplateContext) string {
return "coming_stop_" + fmt.Sprint(ctx.Cloudbrain.ID)
}

func (CloudbrainComingToStopMsg) Url(ctx *TemplateContext) string {
repo, err := models.GetRepositoryByID(ctx.Cloudbrain.RepoID)
if err != nil {
log.Error("CloudbrainComingToStopMsg GetRepositoryByID error,%v", err)
return ""
}
return getCloudbrainTemplateUrl(*ctx.Cloudbrain, repo)
}

func (CloudbrainComingToStopMsg) TemplateId(ctx *TemplateContext) string {
return setting.CloudbrainComingStopTemplateId
}

+ 8
- 5
modules/auth/wechat/template.go View File

@@ -3,6 +3,7 @@ package wechat
import (
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/timeutil"
"errors"
"fmt"
)
@@ -16,7 +17,9 @@ type Template interface {
}

type TemplateContext struct {
Cloudbrain *models.Cloudbrain
Cloudbrain *models.Cloudbrain
EstimatedEndTime timeutil.TimeStamp
PointAccount *models.PointAccount
}

func SendTemplateMsg(template Template, ctx *TemplateContext, userId int64) error {
@@ -45,19 +48,19 @@ func SendTemplateMsg(template Template, ctx *TemplateContext, userId int64) erro
}
err, retryFlag := sendTemplateMsg(req)
if retryFlag {
log.Info("SendTemplateMsg calling")
log.Info("SendTemplateMsg calling.userId = %d", userId)
refreshAccessToken()
err, _ = sendTemplateMsg(req)
if err != nil {
log.Error("SendTemplateMsg err. %v", err)
log.Error("SendTemplateMsg userId = %d err. %v", userId, err)
return err
}
return nil
}
if err != nil {
log.Error("SendTemplateMsg err. %v", err)
log.Error("SendTemplateMsg userId = %d err. %v", userId, err)
return err
}
log.Info("SendTemplateMsg success")
log.Info("SendTemplateMsg success userId = %d", userId)
return nil
}

+ 217
- 0
modules/base/tool.go View File

@@ -421,3 +421,220 @@ func SetupGiteaRoot() string {
}
return giteaRoot
}

// JudgeLanguageBySuffix get language from fileName
func JudgeLanguageBySuffix(filename string) string {
if len(filename) == 0 {
return "shell"
}
arr := strings.Split(filename, ".")
suffix := strings.ToLower(arr[len(arr)-1])
suffixLanguageMap := map[string]string{
"py": "python",
"c": "cpp",
"h": "cpp",
"g4": "cpp",
"sy": "cpp",
"cc": "cpp",
"cxx": "cpp",
"c++": "cpp",
"cu": "cpp",
"cpp": "cpp",
"dynamips": "cpp",
"java": "java",
"php": "php",
"html": "html",
"css": "css",
"scss": "scss",
"go": "go",
"r": "r",
"graphql": "graphql",
"swift": "swift",
"xml": "xml",
"yaml": "yaml",
"json": "json",
"lua": "lua",
"scheme": "scheme",
"less": "less",
"ini": "ini",
"jpg": "jpg",
"jpeg": "jpeg",
"png": "png",
"gif": "gif",
"webp": "webp",
"bmp": "bmp",
"avi": "avi",
"mp4": "mp4",
"mov": "mov",
"mp3": "mp3",
"wav": "wav",
"ogg": "ogg",
"coffee": "coffeescript",
"litcoffee": "coffeescript",
"js": "javascript",
"vue": "javascript",
"ejs": "html",
"cs": "csharp",
"kt": "kotlin",
"md": "markdown",
"sql": "mysql",
"ctrl": "mysql",
"m": "objective-c",
"mm": "objective-c",
"pas": "pascal",
"perl": "perl",
"pl": "perl",
"rb": "ruby",
"rs": "rust", "rust": "rust",
"tsx": "typescript",
"ipynb": "json",
"sh": "shell",
"bash": "shell",
}

if suffixLanguageMap[suffix] == "" {
return "shell"
} else {
return suffixLanguageMap[suffix]
}

}

// JudgeFileTypeBySuffix get language from fileName
func JudgeFileTypeBySuffix(filename string) string {
if len(filename) == 0 {
return "other"
}
arr := strings.Split(filename, ".")
suffix := strings.ToLower(arr[len(arr)-1])
suffixFileTypeMap := map[string]string{
"py": "txt",
"h": "txt",
"c": "txt",
"cpp": "txt",
"cc": "txt",
"java": "txt",
"php": "txt",
"html": "txt",
"css": "txt",
"scss": "txt",
"go": "txt",
"r": "txt",
"graphql": "txt",
"swift": "txt",
"xml": "txt",
"yaml": "txt",
"json": "txt",
"lua": "txt",
"scheme": "txt",
"less": "txt",
"ini": "txt",
"coffee": "txt",
"litcoffee": "txt",
"js": "txt",
"cs": "txt",
"kt": "txt",
"md": "txt",
"sql": "txt",
"m": "txt",
"mm": "txt",
"pas": "txt",
"perl": "txt",
"ejs": "txt",
"pl": "txt",
"rb": "txt",
"rs": "txt",
"rust": "txt",
"sh": "txt",
"makefile": "txt",
"circ": "txt",
"readme": "txt",
"yml": "txt",
"sml": "txt",
"conf": "txt",
"txt": "txt",
"gitignore": "txt",
"in": "txt",
"cu": "txt",
"gemfile": "txt",
"scala": "txt",
"net": "txt",
"l": "txt",
"v": "txt",
"config": "txt",
"properties": "txt",
"log": "txt",
"htm": "txt",
"cnf": "txt",
"hex": "txt",
"bat": "txt",
"asm": "txt",
"bash": "txt",
"ts": "txt",
"tsx": "txt",
"sass": "txt",
"jsx": "txt",
"jsp": "txt",
"gitkeep": "txt",
"sv": "txt",
"hql": "txt",
"y": "txt",
"jj": "txt",
"pls": "txt",
"sol": "txt",
"ignore": "txt",
"ctrl": "txt",
"vue": "txt",
"tex": "txt",
"bib": "txt",
"cls": "txt",
"bst": "txt",
"toc": "txt",
"sty": "txt",
"g4": "txt",
"sy": "txt",
"jpg": "image",
"jpeg": "image",
"png": "image",
"gif": "image",
"webp": "image",
"bmp": "image",
"avi": "video",
"mp4": "video",
"mov": "video",
"mp3": "audio",
"wav": "audio",
"ogg": "audio",
"pptm": "office",
"pptx": "office",
"ppt": "office",
"pot": "office",
"pps": "office",
"ppa": "office",
"potx": "office",
"ppsx": "office",
"ppam": "office",
"potm": "office",
"ppsm": "office",
"doc": "office",
"docx": "office",
"dot": "office",
"dotx": "office",
"docm": "office",
"dotm": "office",
"xls": "office",
"xlsx": "office",
"csv": "office",
"xlt": "office",
"xla": "office",
"xltx": "office",
"xlsm": "office",
"xltm": "office",
"xlam": "office",
"xlsb": "office",
"pdf": "pdf",
"ipynb": "ipynb",
}

return suffixFileTypeMap[suffix]
}

+ 2
- 6
modules/cloudbrain/cloudbrain.go View File

@@ -35,6 +35,7 @@ const (
Snn4imagenetCommand = `/opt/conda/bin/python /benchmark/testSNN_script.py --modelname '%s' --modelpath '/pretrainmodel/%s' --modeldescription '%s' >/model/benchmark-log.txt`
BrainScoreCommand = `bash /benchmark/brainscore_test_par4shSrcipt.sh -b '%s' -n '%s' -p '/pretrainmodel/%s' -d '%s' >/model/benchmark-log.txt`
Snn4EcosetCommand = `/opt/conda/bin/python /benchmark/testSNN_script.py --datapath '/dataset' --modelname '%s' --modelpath '/pretrainmodel/%s' --modeldescription '%s' >/model/benchmark-log.txt`
Sim2BrianSnnCommand = `cd /code && /opt/conda/bin/python /benchmark/score_a_model.py --benchmark '%s' --dataset-path '/dataset' --metric 'RSA' --model-name '%s' --model-checkpoint '/pretrainmodel/%s' --model-description '%s' >/model/benchmark-log.txt`
SubTaskName = "task1"

Success = "S000"
@@ -378,7 +379,7 @@ func GenerateTask(req GenerateCloudBrainTaskReq) (string, error) {

stringId := strconv.FormatInt(task.ID, 10)

if IsBenchmarkJob(req.JobType) {
if models.IsBenchMarkJobType(req.JobType) {
notification.NotifyOtherTask(req.Ctx.User, req.Ctx.Repo.Repository, stringId, req.DisplayJobName, models.ActionCreateBenchMarkTask)
} else if string(models.JobTypeTrain) == req.JobType {
notification.NotifyOtherTask(req.Ctx.User, req.Ctx.Repo.Repository, jobID, req.DisplayJobName, models.ActionCreateGPUTrainTask)
@@ -391,10 +392,6 @@ func GenerateTask(req GenerateCloudBrainTaskReq) (string, error) {
return jobID, nil
}

func IsBenchmarkJob(jobType string) bool {
return string(models.JobTypeModelSafety) == jobType || string(models.JobTypeBenchmark) == jobType || string(models.JobTypeBrainScore) == jobType || string(models.JobTypeSnn4imagenet) == jobType || string(models.JobTypeSnn4Ecoset) == jobType
}

func GetWaitingCloudbrainCount(cloudbrainType int, computeResource string, jobTypes ...models.JobType) int64 {
num, err := models.GetWaitingCloudbrainCount(cloudbrainType, computeResource, jobTypes...)
if err != nil {
@@ -661,7 +658,6 @@ func IsElementExist(s []string, str string) bool {
return false
}


func GetCloudBrainByIdOrJobId(id string, initialQuery string) (*models.Cloudbrain, error) {
_, err := strconv.ParseInt(id, 10, 64)
var job *models.Cloudbrain


+ 2
- 2
modules/context/repo.go View File

@@ -532,7 +532,7 @@ func RepoAssignment() macaron.Handler {

duration := time.Since(startTime)
log.Info("GetTags cost: %v seconds", duration.Seconds())
brs, _, err := ctx.Repo.GitRepo.GetBranches(0, 0)
brs, total, err := ctx.Repo.GitRepo.GetBranchNames(0, 0)
if err != nil {
ctx.ServerError("GetBranches", err)
return
@@ -542,7 +542,7 @@ func RepoAssignment() macaron.Handler {
log.Info("GetBranches cost: %v seconds", duration.Seconds())

ctx.Data["Branches"] = brs
ctx.Data["BranchesCount"] = len(brs)
ctx.Data["BranchesCount"] = total

ctx.Data["TagName"] = ctx.Repo.TagName



+ 1
- 1
modules/cron/tasks_basic.go View File

@@ -231,7 +231,7 @@ func registerSyncCloudbrainStatus() {
RegisterTaskFatal("sync_cloudbrain_status", &BaseConfig{
Enabled: true,
RunAtStart: false,
Schedule: "@every 10m",
Schedule: setting.SyncCloudbrainStatusDuration,
}, func(ctx context.Context, _ *models.User, _ Config) error {
repo.SyncCloudbrainStatus()
return nil


+ 92
- 0
modules/git/repo_branch.go View File

@@ -173,6 +173,98 @@ func (repo *Repository) GetBranches(skip, limit int) ([]string, int, error) {
return callShowRef(repo.Path, BranchPrefix, "--heads", skip, limit)
}

func (repo *Repository) GetBranchNames(skip, limit int) ([]string, int, error) {
return callShowRefNew(repo.Path, []string{BranchPrefix}, skip, limit)
}

func callShowRefNew(repoPath string, extraArgs []string, skip, limit int) (branchNames []string, countAll int, err error) {

countAll, err = walkShowRef(repoPath, extraArgs, skip, limit, func(branchName string) error {
branchNames = append(branchNames, branchName)
return nil
})
return branchNames, countAll, err
}

func walkShowRef(repoPath string, extraArgs []string, skip, limit int, walkfn func(refname string) error) (countAll int, err error) {
stdoutReader, stdoutWriter := io.Pipe()
defer func() {
_ = stdoutReader.Close()
_ = stdoutWriter.Close()
}()

go func() {
stderrBuilder := &strings.Builder{}
args := []string{"for-each-ref", "--format=%(refname:short)"}
args = append(args, extraArgs...)
err := NewCommand(args...).RunInDirPipeline(repoPath, stdoutWriter, stderrBuilder)
if err != nil {
if stderrBuilder.Len() == 0 {
_ = stdoutWriter.Close()
return
}
_ = stdoutWriter.CloseWithError(ConcatenateError(err, stderrBuilder.String()))
} else {
_ = stdoutWriter.Close()
}
}()

i := 0
bufReader := bufio.NewReader(stdoutReader)
for i < skip {
_, isPrefix, err := bufReader.ReadLine()
if err == io.EOF {
return i, nil
}
if err != nil {
return 0, err
}
if !isPrefix {
i++
}
}
for limit == 0 || i < skip+limit {
// The output of show-ref is simply a list:
// <sha> SP <ref> LF
branchName, err := bufReader.ReadString('\n')
if err == io.EOF {
// This shouldn't happen... but we'll tolerate it for the sake of peace
return i, nil
}
if err != nil {
return i, err
}

if len(branchName) > 0 {
branchName = branchName[:len(branchName)-1]
}

err = walkfn(branchName)
if err != nil {
return i, err
}
i++
}
// count all refs
for limit != 0 {
_, isPrefix, err := bufReader.ReadLine()
if err == io.EOF {
return i, nil
}
if err != nil {
return 0, err
}
if !isPrefix {
i++
}
}
return i, nil
}

func isBranchMatch(branchName string, search string) bool {
return search == "" || (search != "" && strings.Contains(branchName, search))
}

// callShowRef return refs, if limit = 0 it will not limit
func callShowRef(repoPath, prefix, arg string, skip, limit int) (branchNames []string, countAll int, err error) {
stdoutReader, stdoutWriter := io.Pipe()


+ 3
- 8
modules/grampus/resty.go View File

@@ -315,10 +315,10 @@ func GetTrainJobLog(jobID string) (string, error) {
return logContent, nil
}

func GetGrampusMetrics(jobID string) (models.GetTrainJobMetricStatisticResult, error) {
func GetGrampusMetrics(jobID string) (models.NewModelArtsMetricStatisticResult, error) {
checkSetting()
client := getRestyClient()
var result models.GetTrainJobMetricStatisticResult
var result models.NewModelArtsMetricStatisticResult
res, err := client.R().
SetAuthToken(TOKEN).
Get(HOST + urlTrainJob + "/" + jobID + "/task/0/replica/0/metrics")
@@ -331,12 +331,7 @@ func GetGrampusMetrics(jobID string) (models.GetTrainJobMetricStatisticResult, e
return result, fmt.Errorf("json.Unmarshal failed(%s): %v", res.String(), err.Error())
}
if res.StatusCode() != http.StatusOK {
log.Error("Call GrampusMetrics failed(%d):%s(%s)", res.StatusCode(), result.ErrorCode, result.ErrorMsg)
return result, fmt.Errorf("Call GrampusMetrics failed(%d):%d(%s)", res.StatusCode(), result.ErrorCode, result.ErrorMsg)
}
if !result.IsSuccess {
log.Error("GetGrampusMetrics(%s) failed", jobID)
return result, fmt.Errorf("GetGrampusMetrics failed:%s", result.ErrorMsg)
return result, fmt.Errorf("Call GrampusMetrics failed(%d)", res.StatusCode())
}
return result, nil
}


+ 1
- 1
modules/modelarts/modelarts.go View File

@@ -234,7 +234,7 @@ func GenerateNotebook2(ctx *context.Context, req cloudbrain.GenerateModelArtsNot
ComputeResource: models.NPUResource,
Image: imageName,
BootFile: req.BootFile,
BranchName: req.BranchName,
BranchName: req.BranchName,
Description: req.Description,
CreatedUnix: createTime,
UpdatedUnix: createTime,


+ 2
- 0
modules/notification/base/notifier.go View File

@@ -8,6 +8,7 @@ import (
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/timeutil"
)

// Notifier defines an interface to notify receiver
@@ -64,4 +65,5 @@ type Notifier interface {
NotifyChangeUserAvatar(user *models.User, form auth.AvatarForm)

NotifyChangeCloudbrainStatus(cloudbrain *models.Cloudbrain, oldStatus string)
NotifyCloudbrainTaskComingToFinished(cloudbrain *models.Cloudbrain, endTime timeutil.TimeStamp, account *models.PointAccount)
}

+ 5
- 0
modules/notification/base/null.go View File

@@ -8,6 +8,7 @@ import (
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/timeutil"
)

// NullNotifier implements a blank notifier
@@ -180,3 +181,7 @@ func (*NullNotifier) NotifyChangeUserAvatar(user *models.User, form auth.AvatarF
func (*NullNotifier) NotifyChangeCloudbrainStatus(cloudbrain *models.Cloudbrain, oldStatus string) {

}

func (*NullNotifier) NotifyCloudbrainTaskComingToFinished(cloudbrain *models.Cloudbrain, endTime timeutil.TimeStamp, account *models.PointAccount) {

}

+ 8
- 0
modules/notification/notification.go View File

@@ -17,6 +17,7 @@ import (
wechatNotifier "code.gitea.io/gitea/modules/notification/wechat"
"code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil"
)

var (
@@ -316,3 +317,10 @@ func NotifyChangeCloudbrainStatus(cloudbrain *models.Cloudbrain, oldStatus strin
notifier.NotifyChangeCloudbrainStatus(cloudbrain, oldStatus)
}
}

// NotifyCloudbrainTaskComingToFinished
func NotifyCloudbrainTaskComingToFinished(cloudbrain *models.Cloudbrain, estimatedEndTime timeutil.TimeStamp, account *models.PointAccount) {
for _, notifier := range notifiers {
notifier.NotifyCloudbrainTaskComingToFinished(cloudbrain, estimatedEndTime, account)
}
}

+ 7
- 0
modules/notification/wechat/wechat.go View File

@@ -9,6 +9,7 @@ import (
"code.gitea.io/gitea/modules/auth/wechat"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/notification/base"
"code.gitea.io/gitea/modules/timeutil"
)

type wechatNotifier struct {
@@ -34,3 +35,9 @@ func (*wechatNotifier) NotifyChangeCloudbrainStatus(cloudbrain *models.Cloudbrai
template := wechat.GetTemplateFromOperateType(operateType)
go wechat.SendTemplateMsg(template, &wechat.TemplateContext{Cloudbrain: cloudbrain}, cloudbrain.UserID)
}

func (*wechatNotifier) NotifyCloudbrainTaskComingToFinished(cloudbrain *models.Cloudbrain, endTime timeutil.TimeStamp, account *models.PointAccount) {
log.Info("NotifyCloudbrainTaskComingToFinished cloudbrain.id=%d", cloudbrain.ID)
template := wechat.ComingStopMsg
go wechat.SendTemplateMsg(template, &wechat.TemplateContext{Cloudbrain: cloudbrain, EstimatedEndTime: endTime, PointAccount: account}, cloudbrain.UserID)
}

+ 6
- 0
modules/redis/redis_key/cloudbrain_redis_key.go View File

@@ -1,7 +1,13 @@
package redis_key

import "fmt"

const CLOUDBRAIN_PREFIX = "cloudbrain"

func CloudbrainBindingJobNameKey(repoId string, jobType string, jobName string) string {
return KeyJoin(CLOUDBRAIN_PREFIX, repoId, jobType, jobName, "redis_key")
}

func CloudbrainUniquenessKey(userId int64, jobType string) string {
return KeyJoin(CLOUDBRAIN_PREFIX, fmt.Sprint(userId), jobType, "uniqueness")
}

+ 4
- 0
modules/redis/redis_key/reward_redis_key.go View File

@@ -19,3 +19,7 @@ func RewardOperateNotification() string {
func RewardTaskRunningLock(taskId int64) string {
return KeyJoin(REWARD_REDIS_PREFIX, "periodic_task", fmt.Sprint(taskId), "lock")
}

func RewardMsgNewestTime(userId int64, msgType string, code string) string {
return KeyJoin(REWARD_REDIS_PREFIX, "msg", fmt.Sprint(userId), msgType, code, "newest_time")
}

+ 33
- 8
modules/repofiles/content.go View File

@@ -5,7 +5,10 @@
package repofiles

import (
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/log"
"fmt"
"io"
"net/url"
"path"
"strings"
@@ -95,6 +98,7 @@ func GetContentsOrList(repo *models.Repository, treePath, ref string) (interface
}
fileList = append(fileList, fileContentResponse)
}

return fileList, nil
}

@@ -187,16 +191,14 @@ func GetContents(repo *models.Repository, treePath, ref string, forList bool) (*
contentsResponse.SubmoduleGitURL = &submodule.URL
}
// Handle links
if entry.IsRegular() || entry.IsLink() {
downloadURL, err := url.Parse(fmt.Sprintf("%s/raw/%s/%s/%s", repo.HTMLURL(), refType, ref, treePath))
if err != nil {
return nil, err
}
downloadURLString := downloadURL.String()
contentsResponse.DownloadURL = &downloadURLString
downloadURL, err := url.Parse(fmt.Sprintf("%s/raw/%s/%s/%s", repo.HTMLURL(), refType, url.QueryEscape(ref), treePath))
if err != nil {
return nil, err
}
downloadURLString := downloadURL.String()
contentsResponse.DownloadURL = &downloadURLString
if !entry.IsSubModule() {
htmlURL, err := url.Parse(fmt.Sprintf("%s/src/%s/%s/%s", repo.HTMLURL(), refType, ref, treePath))
htmlURL, err := url.Parse(fmt.Sprintf("%s/src/%s/%s/%s", repo.HTMLURL(), refType, url.QueryEscape(ref), treePath))
if err != nil {
return nil, err
}
@@ -213,5 +215,28 @@ func GetContents(repo *models.Repository, treePath, ref string, forList bool) (*
contentsResponse.Links.GitURL = &gitURLString
}

// set file language and file type
contentsResponse.Language = base.JudgeLanguageBySuffix(treePath)
var fileType = base.JudgeFileTypeBySuffix(treePath)
if fileType == "" {
blob := entry.Blob()
dataRc, _ := blob.DataAsync()
defer func(dataRc io.ReadCloser) {
err := dataRc.Close()
if err != nil {
log.Error("DataAsync", err)
}
}(dataRc)
buf := make([]byte, 1024)
n, _ := dataRc.Read(buf)
buf = buf[:n]
if base.IsTextFile(buf) {
fileType = "txt"
} else {
fileType = "other"
}
}
contentsResponse.FileType = fileType

return contentsResponse, nil
}

+ 6
- 2
modules/repofiles/tree.go View File

@@ -14,7 +14,7 @@ import (
)

// GetTreeBySHA get the GitTreeResponse of a repository using a sha hash.
func GetTreeBySHA(repo *models.Repository, sha string, page, perPage int, recursive bool) (*api.GitTreeResponse, error) {
func GetTreeBySHA(repo *models.Repository, sha string, page, perPage int, recursive bool, truncate bool) (*api.GitTreeResponse, error) {
gitRepo, err := git.OpenRepository(repo.RepoPath())
if err != nil {
return nil, err
@@ -55,7 +55,11 @@ func GetTreeBySHA(repo *models.Repository, sha string, page, perPage int, recurs
copyPos := len(treeURL) - 40

if perPage <= 0 || perPage > setting.API.DefaultGitTreesPerPage {
perPage = setting.API.DefaultGitTreesPerPage
if truncate {
perPage = setting.API.DefaultGitTreesPerPage
} else {
perPage = len(entries)
}
}
if page <= 0 {
page = 1


+ 411
- 0
modules/repofiles/update.go View File

@@ -7,6 +7,7 @@ package repofiles
import (
"bytes"
"container/list"
"encoding/base64"
"fmt"
"path"
"strings"
@@ -50,6 +51,7 @@ type UpdateRepoFileOptions struct {
Content string
SHA string
IsNewFile bool
Type string
Author *IdentityOptions
Committer *IdentityOptions
Dates *CommitDateOptions
@@ -462,6 +464,415 @@ func CreateOrUpdateRepoFile(repo *models.Repository, doer *models.User, opts *Up
return file, nil
}

func OneTimeSubmission(repo *models.Repository, doer *models.User, optsList[] *UpdateRepoFileOptions) error {
base := *optsList[0]

t, err := NewTemporaryUploadRepository(repo)
if err != nil {
log.Error("%v", err)
}
defer t.Close()
if err := t.Clone(base.OldBranch); err != nil {
return err
}
if err := t.SetDefaultIndex(); err != nil {
return err
}

// Get the commit of the original branch
commit, err := t.GetBranchCommit(base.OldBranch)
if err != nil {
return err // Couldn't get a commit for the branch
}

message := strings.TrimSpace(base.Message)
author, committer := GetAuthorAndCommitterUsers(base.Author, base.Committer, doer)


for _, opts := range optsList {
// 解码文件内容
content, err := base64.StdEncoding.DecodeString(opts.Content)
if err != nil {
return err
}
opts.Content = string(content)

// If no branch name is set, assume master
if opts.OldBranch == "" {
opts.OldBranch = repo.DefaultBranch
}
if opts.NewBranch == "" {
opts.NewBranch = opts.OldBranch
}

if _, err := repo_module.GetBranch(repo, opts.OldBranch); err != nil {
return err
}

if opts.NewBranch != opts.OldBranch {
existingBranch, err := repo_module.GetBranch(repo, opts.NewBranch)
if existingBranch != nil {
return models.ErrBranchAlreadyExists{
BranchName: opts.NewBranch,
}
}
if err != nil && !git.IsErrBranchNotExist(err) {
return err
}
} else {
protectedBranch, err := repo.GetBranchProtection(opts.OldBranch)
if err != nil {
return err
}
if protectedBranch != nil {
if !protectedBranch.CanUserPush(doer.ID) {
return models.ErrUserCannotCommit{
UserName: doer.LowerName,
}
}
if protectedBranch.RequireSignedCommits {
_, _, err := repo.SignCRUDAction(doer, repo.RepoPath(), opts.OldBranch)
if err != nil {
if !models.IsErrWontSign(err) {
return err
}
return models.ErrUserCannotCommit{
UserName: doer.LowerName,
}
}
}
patterns := protectedBranch.GetProtectedFilePatterns()
for _, pat := range patterns {
if pat.Match(strings.ToLower(opts.TreePath)) {
return models.ErrFilePathProtected{
Path: opts.TreePath,
}
}
}
}
}


// If FromTreePath is not set, set it to the opts.TreePath
if opts.TreePath != "" && opts.FromTreePath == "" {
opts.FromTreePath = opts.TreePath
}

// Check that the path given in opts.treePath is valid (not a git path)
treePath := CleanUploadFileName(opts.TreePath)
if treePath == "" {
return models.ErrFilenameInvalid{
Path: opts.TreePath,
}
}

// Assigned LastCommitID in opts if it hasn't been set
if opts.LastCommitID == "" {
opts.LastCommitID = commit.ID.String()
} else {
lastCommitID, err := t.gitRepo.ConvertToSHA1(opts.LastCommitID)
if err != nil {
return fmt.Errorf("DeleteRepoFile: Invalid last commit ID: %v", err)
}
opts.LastCommitID = lastCommitID.String()
}

// 添加修改和删除的区别
if opts.Type == "add" || opts.Type == "update"{

// If there is a fromTreePath (we are copying it), also clean it up
fromTreePath := CleanUploadFileName(opts.FromTreePath)
if fromTreePath == "" && opts.FromTreePath != "" {
return models.ErrFilenameInvalid{
Path: opts.FromTreePath,
}
}

encoding := "UTF-8"
bom := false
executable := false

if !opts.IsNewFile {
fromEntry, err := commit.GetTreeEntryByPath(fromTreePath)
if err != nil {
return err
}
if opts.SHA != "" {
// If a SHA was given and the SHA given doesn't match the SHA of the fromTreePath, throw error
if opts.SHA != fromEntry.ID.String() {
return models.ErrSHADoesNotMatch{
Path: treePath,
GivenSHA: opts.SHA,
CurrentSHA: fromEntry.ID.String(),
}
}
} else if opts.LastCommitID != "" {
// If a lastCommitID was given and it doesn't match the commitID of the head of the branch throw
// an error, but only if we aren't creating a new branch.
if commit.ID.String() != opts.LastCommitID && opts.OldBranch == opts.NewBranch {
if changed, err := commit.FileChangedSinceCommit(treePath, opts.LastCommitID); err != nil {
return err
} else if changed {
return models.ErrCommitIDDoesNotMatch{
GivenCommitID: opts.LastCommitID,
CurrentCommitID: opts.LastCommitID,
}
}
// The file wasn't modified, so we are good to delete it
}
} else {
// When updating a file, a lastCommitID or SHA needs to be given to make sure other commits
// haven't been made. We throw an error if one wasn't provided.
return models.ErrSHAOrCommitIDNotProvided{}
}
encoding, bom = detectEncodingAndBOM(fromEntry, repo)
executable = fromEntry.IsExecutable()
}

treePathParts := strings.Split(treePath, "/")
subTreePath := ""
for index, part := range treePathParts {
subTreePath = path.Join(subTreePath, part)
entry, err := commit.GetTreeEntryByPath(subTreePath)
if err != nil {
if git.IsErrNotExist(err) {
// Means there is no item with that name, so we're good
break
}
return err
}
if index < len(treePathParts)-1 {
if !entry.IsDir() {
return models.ErrFilePathInvalid{
Message: fmt.Sprintf("a file exists where you’re trying to create a subdirectory [path: %s]", subTreePath),
Path: subTreePath,
Name: part,
Type: git.EntryModeBlob,
}
}
} else if entry.IsLink() {
return models.ErrFilePathInvalid{
Message: fmt.Sprintf("a symbolic link exists where you’re trying to create a subdirectory [path: %s]", subTreePath),
Path: subTreePath,
Name: part,
Type: git.EntryModeSymlink,
}
} else if entry.IsDir() {
return models.ErrFilePathInvalid{
Message: fmt.Sprintf("a directory exists where you’re trying to create a file [path: %s]", subTreePath),
Path: subTreePath,
Name: part,
Type: git.EntryModeTree,
}
} else if fromTreePath != treePath || opts.IsNewFile {
// The entry shouldn't exist if we are creating new file or moving to a new path
return models.ErrRepoFileAlreadyExists{
Path: treePath,
}
}

}

// Get the two paths (might be the same if not moving) from the index if they exist
filesInIndex, err := t.LsFiles(opts.TreePath, opts.FromTreePath)
if err != nil {
return fmt.Errorf("UpdateRepoFile: %v", err)
}
// If is a new file (not updating) then the given path shouldn't exist
if opts.IsNewFile {
for _, file := range filesInIndex {
if file == opts.TreePath {
return models.ErrRepoFileAlreadyExists{
Path: opts.TreePath,
}
}
}
}

// Remove the old path from the tree
if fromTreePath != treePath && len(filesInIndex) > 0 {
for _, file := range filesInIndex {
if file == fromTreePath {
if err := t.RemoveFilesFromIndex(opts.FromTreePath); err != nil {
return err
}
}
}
}

content := opts.Content
if bom {
content = string(charset.UTF8BOM) + content
}
if encoding != "UTF-8" {
charsetEncoding, _ := stdcharset.Lookup(encoding)
if charsetEncoding != nil {
result, _, err := transform.String(charsetEncoding.NewEncoder(), content)
if err != nil {
// Look if we can't encode back in to the original we should just stick with utf-8
log.Error("Error re-encoding %s (%s) as %s - will stay as UTF-8: %v", opts.TreePath, opts.FromTreePath, encoding, err)
result = content
}
content = result
} else {
log.Error("Unknown encoding: %s", encoding)
}
}
// Reset the opts.Content to our adjusted content to ensure that LFS gets the correct content
opts.Content = content
var lfsMetaObject *models.LFSMetaObject

if setting.LFS.StartServer {
// Check there is no way this can return multiple infos
filename2attribute2info, err := t.CheckAttribute("filter", treePath)
if err != nil {
return err
}

if filename2attribute2info[treePath] != nil && filename2attribute2info[treePath]["filter"] == "lfs" {
// OK so we are supposed to LFS this data!
oid, err := models.GenerateLFSOid(strings.NewReader(opts.Content))
if err != nil {
return err
}
lfsMetaObject = &models.LFSMetaObject{Oid: oid, Size: int64(len(opts.Content)), RepositoryID: repo.ID}
content = lfsMetaObject.Pointer()
}
}
// Add the object to the database
objectHash, err := t.HashObject(strings.NewReader(content))
if err != nil {
return err
}

// Add the object to the index
if executable {
if err := t.AddObjectToIndex("100755", objectHash, treePath); err != nil {
return err
}
} else {
if err := t.AddObjectToIndex("100644", objectHash, treePath); err != nil {
return err
}
}

if lfsMetaObject != nil {
// We have an LFS object - create it
lfsMetaObject, err = models.NewLFSMetaObject(lfsMetaObject)
if err != nil {
return err
}
contentStore := &lfs.ContentStore{BasePath: setting.LFS.ContentPath}
if !contentStore.Exists(lfsMetaObject) {
if err := contentStore.Put(lfsMetaObject, strings.NewReader(opts.Content)); err != nil {
if _, err2 := repo.RemoveLFSMetaObjectByOid(lfsMetaObject.Oid); err2 != nil {
return fmt.Errorf("Error whilst removing failed inserted LFS object %s: %v (Prev Error: %v)", lfsMetaObject.Oid, err2, err)
}
return err
}
}
}
} else {
// Get the files in the index
filesInIndex, err := t.LsFiles(opts.TreePath)
if err != nil {
return fmt.Errorf("DeleteRepoFile: %v", err)
}

// Find the file we want to delete in the index
inFilelist := false
for _, file := range filesInIndex {
if file == opts.TreePath {
inFilelist = true
break
}
}
if !inFilelist {
return models.ErrRepoFileDoesNotExist{
Path: opts.TreePath,
}
}

// Get the entry of treePath and check if the SHA given is the same as the file
entry, err := commit.GetTreeEntryByPath(treePath)
if err != nil {
return err
}
if opts.SHA != "" {
// If a SHA was given and the SHA given doesn't match the SHA of the fromTreePath, throw error
if opts.SHA != entry.ID.String() {
return models.ErrSHADoesNotMatch{
Path: treePath,
GivenSHA: opts.SHA,
CurrentSHA: entry.ID.String(),
}
}
} else if opts.LastCommitID != "" {
// If a lastCommitID was given and it doesn't match the commitID of the head of the branch throw
// an error, but only if we aren't creating a new branch.
if commit.ID.String() != opts.LastCommitID && opts.OldBranch == opts.NewBranch {
// CommitIDs don't match, but we don't want to throw a ErrCommitIDDoesNotMatch unless
// this specific file has been edited since opts.LastCommitID
if changed, err := commit.FileChangedSinceCommit(treePath, opts.LastCommitID); err != nil {
return err
} else if changed {
return models.ErrCommitIDDoesNotMatch{
GivenCommitID: opts.LastCommitID,
CurrentCommitID: opts.LastCommitID,
}
}
// The file wasn't modified, so we are good to delete it
}
} else {
// When deleting a file, a lastCommitID or SHA needs to be given to make sure other commits haven't been
// made. We throw an error if one wasn't provided.
return models.ErrSHAOrCommitIDNotProvided{}
}

// Remove the file from the index
if err := t.RemoveFilesFromIndex(opts.TreePath); err != nil {
return err
}
}

base = *opts
}

// Now write the tree
treeHash, err := t.WriteTree()
if err != nil {
return err
}
// Now commit the tree
var commitHash string
if base.Dates != nil {
commitHash, err = t.CommitTreeWithDate(author, committer, treeHash, message, base.Dates.Author, base.Dates.Committer)
} else {
commitHash, err = t.CommitTree(author, committer, treeHash, message)

}
if err != nil {
return err
}

// Then push this tree to NewBranch
if err := t.Push(doer, commitHash, base.NewBranch); err != nil {
log.Error("%T %v", err, err)
return err
}

commit, err = t.GetCommit(commitHash)
if err != nil {
return err
}

_, err = GetFileCommitResponse(repo, commit)
if err != nil {
return err
}
return nil
}

// PushUpdateOptions defines the push update options
type PushUpdateOptions struct {
PusherID int64


+ 48
- 8
modules/setting/setting.go View File

@@ -112,6 +112,8 @@ var (
AppVer string
AppBuiltWith string
AppName string
MLOPS bool
MlopsHost string
AppURL string
AppSubURL string
AppSubURLDepth int // Number of slashes
@@ -543,6 +545,7 @@ var (
BrainScoreOwner string
BrainScoreName string
BrainScoreServerHost string
BrainScoreRegion []string

IsSnn4EcosetEnabled bool
Snn4EcosetOwner string
@@ -550,6 +553,13 @@ var (
Snn4EcosetServerHost string
Snn4AttachmentName string

IsSim2BrainSnnEnabled bool
Sim2BrainSnnOwner string
Sim2BrainSnnName string
Sim2BrainSnnServerHost string
Sim2BrainSnnDatasetTypes []string
Sim2BrainSnnAttachNames []string

//blockchain config
BlockChainHost string
CommitValidDate string
@@ -661,6 +671,7 @@ var (
CloudBrainPayInterval time.Duration
DeductTaskRange time.Duration
DeductTaskRangeForFirst time.Duration
DeductTaskMinTimestamp int64

//badge config
BadgeIconMaxFileSize int64
@@ -677,19 +688,27 @@ var (
TreePathOfSubscribe string

//wechat template msg config
CloudbrainStartedTemplateId string
CloudbrainStartedNotifyList []string
CloudbrainStartedTitle string
CloudbrainStartedRemark string
CloudbrainStoppedTemplateId string
CloudbrainStoppedNotifyList []string
CloudbrainStoppedTitle string
CloudbrainStoppedRemark string
CloudbrainStartedTemplateId string
CloudbrainStartedNotifyList []string
CloudbrainStartedTitle string
CloudbrainStartedRemark string
CloudbrainStoppedTemplateId string
CloudbrainStoppedNotifyList []string
CloudbrainStoppedTitle string
CloudbrainStoppedRemark string
CloudbrainComingStopTemplateId string
CloudbrainComingStopTitle string
CloudbrainComingStopChargeLink string
CloudbrainComingStopRemark string
CloudbrainComingStopSendFlag bool

//repo square config
IncubationSourceOrgName string
PaperRepoTopicName string

CloudbrainUniquenessLockTime time.Duration
SyncCloudbrainStatusDuration string

//nginx proxy
PROXYURL string
RadarMap = struct {
@@ -1041,6 +1060,9 @@ func NewContext() {
AppSubURL = strings.TrimSuffix(appURL.Path, "/")
StaticURLPrefix = strings.TrimSuffix(sec.Key("STATIC_URL_PREFIX").MustString(AppSubURL), "/")
AppSubURLDepth = strings.Count(AppSubURL, "/")

MLOPS = Cfg.Section("").Key("MLOPS").MustBool(false)
MlopsHost = Cfg.Section("").Key("MLOPS_HOST").MustString(AppSubURL)
// Check if Domain differs from AppURL domain than update it to AppURL's domain
// TODO: Can be replaced with url.Hostname() when minimal GoLang version is 1.8
urlHostname := strings.SplitN(appURL.Host, ":", 2)[0]
@@ -1506,6 +1528,9 @@ func NewContext() {
CullInterval = sec.Key("CULL_INTERVAL").MustString("60")
DebugAttachSize = sec.Key("DEBUG_ATTACH_SIZE").MustInt(20)

CloudbrainUniquenessLockTime = sec.Key("UNIQUENESS_LOCK_TIME").MustDuration(5 * time.Minute)
SyncCloudbrainStatusDuration = sec.Key("SYNC_STATUS_DURATION").MustString("@every 1m")

sec = Cfg.Section("benchmark")
IsBenchmarkEnabled = sec.Key("ENABLED").MustBool(false)
BenchmarkOwner = sec.Key("OWNER").MustString("")
@@ -1529,6 +1554,7 @@ func NewContext() {
BrainScoreOwner = sec.Key("OWNER").MustString("")
BrainScoreName = sec.Key("NAME").MustString("")
BrainScoreServerHost = sec.Key("HOST").MustString("")
BrainScoreRegion = strings.Split(sec.Key("REGION").MustString(""), ",")

sec = Cfg.Section("snn4ecoset")
IsSnn4EcosetEnabled = sec.Key("ENABLED").MustBool(false)
@@ -1537,6 +1563,14 @@ func NewContext() {
Snn4EcosetServerHost = sec.Key("HOST").MustString("")
Snn4AttachmentName = sec.Key("DATASET").MustString("")

sec = Cfg.Section("sim2brain_snn")
IsSim2BrainSnnEnabled = sec.Key("ENABLED").MustBool(false)
Sim2BrainSnnOwner = sec.Key("OWNER").MustString("")
Sim2BrainSnnName = sec.Key("NAME").MustString("")
Sim2BrainSnnServerHost = sec.Key("HOST").MustString("")
Sim2BrainSnnDatasetTypes = strings.Split(sec.Key("DATASET_TYPE").MustString(""), ",")
Sim2BrainSnnAttachNames = strings.Split(sec.Key("ATTACH_NAME").MustString(""), ",")

sec = Cfg.Section("blockchain")
BlockChainHost = sec.Key("HOST").MustString("http://192.168.136.66:3302/")
CommitValidDate = sec.Key("COMMIT_VALID_DATE").MustString("2021-01-15")
@@ -1609,6 +1643,11 @@ func NewContext() {
CloudbrainStoppedNotifyList = strings.Split(sec.Key("CLOUDBRAIN_STOPPED_NOTIFY_LIST").MustString("TRAIN"), ",")
CloudbrainStoppedTitle = sec.Key("CLOUDBRAIN_STOPPED_TITLE").MustString("您好,您申请的算力资源已结束使用,任务已完成运行,状态为%s,请您关注运行结果")
CloudbrainStoppedRemark = sec.Key("CLOUDBRAIN_STOPPED_REMARK").MustString("感谢您的耐心等待。")
CloudbrainComingStopTemplateId = sec.Key("CLOUDBRAIN_COMING_STOP_TEMPLATE_ID").MustString("")
CloudbrainComingStopTitle = sec.Key("CLOUDBRAIN_COMING_STOP_TITLE").MustString("因算力积分余额不足,您正在运行的任务即将被停止。")
CloudbrainComingStopChargeLink = sec.Key("CLOUDBRAIN_COMING_STOP_CHARGE_LINK").MustString("请参考“个人中心 > 算力积分”页面的“积分获取说明”获取积分。")
CloudbrainComingStopRemark = sec.Key("CLOUDBRAIN_COMING_STOP_REMARK").MustString("为了不影响您使用,请您尽快获取算力积分。")
CloudbrainComingStopSendFlag = sec.Key("CLOUDBRAIN_COMING_STOP_SEND_FLAG").MustBool(true)

sec = Cfg.Section("repo-square")
IncubationSourceOrgName = sec.Key("INCUBATION_ORG_NAME").MustString("OpenI")
@@ -1620,6 +1659,7 @@ func NewContext() {
CloudBrainPayInterval = sec.Key("CLOUDBRAIN_PAY_INTERVAL").MustDuration(60 * time.Minute)
DeductTaskRange = sec.Key("DEDUCT_TASK_RANGE").MustDuration(30 * time.Minute)
DeductTaskRangeForFirst = sec.Key("DEDUCT_TASK_RANGE_FOR_FIRST").MustDuration(3 * time.Hour)
DeductTaskMinTimestamp = sec.Key("DEDUCT_TASK_MIN_TIMESTAMP").MustInt64(0)

sec = Cfg.Section("icons")
BadgeIconMaxFileSize = sec.Key("BADGE_ICON_MAX_FILE_SIZE").MustInt64(1048576)


+ 1
- 5
modules/storage/storage.go View File

@@ -74,13 +74,9 @@ func Init() error {
ObsCli, err = obs.New(setting.AccessKeyID, setting.SecretAccessKey, setting.Endpoint)
if err != nil {
log.Error("obs.New failed:", err)
return err
//return err
}
log.Info("obs cli inited.")

if err != nil {
return err
}

return nil
}

+ 28
- 0
modules/structs/repo_file.go View File

@@ -48,6 +48,32 @@ type UpdateFileOptions struct {
FromPath string `json:"from_path" binding:"MaxSize(500)"`
}

// CommitMultiFileOptions options for commit multi files
type CommitMultiFileOptions struct {
Files []CommitFileOptions `json:"files"`
Msg string `json:"msg"`
CurrentBranch string `json:"currentBranch"`
CommitBranch string `json:"commitBranch"`
}

// CommitFileOptions options for commit files
// Note: `author` and `committer` are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used)
type CommitFileOptions struct {
FileOptions
// file path
FilePath string `json:"filePath"`
// content must be base64 encoded
Content string `json:"content"`
// sha is the SHA for the file that already exists
SHA string `json:"sha"`

// from_path (optional) is the path of the original file which will be moved/renamed to the path in the URL
FromPath string `json:"from_path" binding:"MaxSize(500)"`
// operation: add, update, delete
// required: true
Operation string `json:"operation"`
}

// FileLinksResponse contains the links for a repo's file
type FileLinksResponse struct {
Self *string `json:"self"`
@@ -76,6 +102,8 @@ type ContentsResponse struct {
// `submodule_git_url` is populated when `type` is `submodule`, otherwise null
SubmoduleGitURL *string `json:"submodule_git_url"`
Links *FileLinksResponse `json:"_links"`
Language string `json:"language"`
FileType string `json:"fileType"`
}

// FileCommitResponse contains information generated from a Git commit for a repo's file.


+ 18
- 0
modules/structs/repo_tree.go View File

@@ -14,6 +14,14 @@ type GitEntry struct {
URL string `json:"url"`
}

// GitEntryWithChildren represents a git tree entry with children
type GitEntryWithChildren struct {
GitEntry
Children []GitEntryWithChildren `json:"children"`
Name string `json:"name"`
Suffix string `json:"suffix"`
}

// GitTreeResponse returns a git tree
type GitTreeResponse struct {
SHA string `json:"sha"`
@@ -23,3 +31,13 @@ type GitTreeResponse struct {
Page int `json:"page"`
TotalCount int `json:"total_count"`
}

// GitTreeWithChildrenResponse returns a git tree with children
type GitTreeWithChildrenResponse struct {
SHA string `json:"sha"`
URL string `json:"url"`
Entries []GitEntryWithChildren `json:"tree"`
Truncated bool `json:"truncated"`
Page int `json:"page"`
TotalCount int `json:"total_count"`
}

+ 18
- 0
modules/structs/tech.go View File

@@ -0,0 +1,18 @@
package structs

type NotOpenITechRepo struct {
Url string `json:"url" binding:"Required"`
TechNo string `json:"no"`
Institution string `json:"institution"`
UID int64 `json:"uid"` //启智项目uid
RepoName string `json:"repo_name" binding:"Required;AlphaDashDot;MaxSize(100)"`
Alias string `json:"alias" binding:"Required;AlphaDashDotChinese;MaxSize(100)"`
Topics []string `json:"topics"` //关键词
Description string `json:"description" binding:"MaxSize(255)"`
}

type OpenITechRepo struct {
Url string `json:"url" binding:"Required"`
TechNo string `json:"no"`
Institution string `json:"institution"`
}

+ 13
- 6
modules/templates/helper.go View File

@@ -42,11 +42,11 @@ import (
)

const (
REF_HEADS_PREFIX = "refs/heads/"
REF_TAGS_PREFIX = "refs/tags/"
REF_TYPE_BRANCH = "branch"
REF_TYPE_TAG = "tag"
REF_TYPE_PATTERN = "(refs/heads/|refs/tags/)"
REF_HEADS_PREFIX = "refs/heads/"
REF_TAGS_PREFIX = "refs/tags/"
REF_TYPE_BRANCH = "branch"
REF_TYPE_TAG = "tag"
REF_TYPE_PATTERN = "(refs/heads/|refs/tags/)"
DURATION_STR_ZERO = "00:00:00"
)

@@ -65,6 +65,12 @@ func NewFuncMap() []template.FuncMap {
"AppName": func() string {
return setting.AppName
},
"MLOPS": func() bool {
return setting.MLOPS
},
"MlopsHost": func() string {
return setting.MlopsHost
},
"AppSubUrl": func() string {
return setting.AppSubURL
},
@@ -368,7 +374,7 @@ func NewTextFuncMap() []texttmpl.FuncMap {
"AppDomain": func() string {
return setting.Domain
},
"TimeStampNow": timeutil.TimeStampNow,
"TimeStampNow": timeutil.TimeStampNow,
"TimeSince": timeutil.TimeSince,
"TimeSinceUnix": timeutil.TimeSinceUnix,
"TimeSinceUnix1": timeutil.TimeSinceUnix1,
@@ -479,6 +485,7 @@ func subOne(length int) int {
func addOne(length int64) int64 {
return length + 1
}

// Escape escapes a HTML string
func Escape(raw string) string {
return html.EscapeString(raw)


+ 17
- 1
options/locale/locale_en-US.ini View File

@@ -1174,7 +1174,7 @@ modelarts.train_job.new_train=New Train Task
modelarts.train_job.new_infer=New Inference Task
modelarts.train_job.config=Configuration information
modelarts.train_job.new=New train Task
modelarts.train_job.new_place=The description should not exceed 256 characters
modelarts.train_job.new_place=The description should not exceed 255 characters
modelarts.model_name=Model Name
modelarts.model_size=Model Size
modelarts.import_model=Import Model
@@ -1732,6 +1732,9 @@ issues.attachment.open_tab = `Click to see "%s" in a new tab`
issues.attachment.download = `Click to download "%s"`
issues.subscribe = Subscribe
issues.unsubscribe = Unsubscribe
issues.top_task=Top issue
issues.top = Top
issues.cancel_topping = Cancel topping
issues.lock = Lock conversation
issues.unlock = Unlock conversation
issues.lock.unknown_reason = Cannot lock an issue with an unknown reason.
@@ -3310,6 +3313,7 @@ SNN4IMAGENET = BENCHMARK
BRAINSCORE = BENCHMARK
SNN4ECOSET = BENCHMARK
MODELSAFETY = BENCHMARK
SIM2BRAIN_SNN = BENCHMARK
TRAIN = TRAIN
INFERENCE = INFERENCE
BENCHMARK = BENCHMARK
@@ -3321,6 +3325,7 @@ Already_stopped=The job is already stopped.
Stopped_failed=Fail to stop the job, please try again later.
Stopped_success_update_status_fail=Succeed in stopping th job, but failed to update the job status and duration time.
load_code_failed=Fail to load code, please check if the right branch is selected.
dataset_load_fail=Fail to load dataset.

error.debug_datasetsize = The size of dataset exceeds limitation (%dGB)
error.dataset_select = dataset select error:the count exceed the limit or has same name
@@ -3358,3 +3363,14 @@ get_file_fail= Can not get the image content, please try again later.
content_type_unsupported=Allowed image type is jpg, jpeg or png.
process_image_fail=Fail to process image, please try again later.

[tech]
get_file_fail= Can not get the file content, please try again later.
content_type_unsupported=The format of the file or file content is wrong.
sql_err=Fail to process data, please try again later.
show_or_hide_fail=Failed to %s the repo(s).
incorrect_openi_format = The OpenI address format is incorrect
openi_repo_not_exist = OpenI repository is not exists
tech_not_exist = The project approval number does not exist
institution_not_valid = The submitted contributing unit is not among the participating units of the technology project
repo_converge_exists = The technology project [%s] already has [%s], please do not submit it again
to_migrate_repo_exists = The project has been migrated to OpenI, please use the OpenI way to submit

+ 18
- 0
options/locale/locale_zh-CN.ini View File

@@ -1749,6 +1749,9 @@ issues.attachment.open_tab=`在新的标签页中查看 '%s'`
issues.attachment.download=`点击下载 '%s'`
issues.subscribe=订阅
issues.unsubscribe=取消订阅
issues.top_task=置顶任务
issues.top=置顶
issues.cancel_topping=取消置顶
issues.lock=锁定对话
issues.unlock=解锁对话
issues.lock.unknown_reason=由于未知原因无法锁定。
@@ -3331,6 +3334,7 @@ SNN4IMAGENET = 评测任务
BRAINSCORE = 评测任务
SNN4ECOSET = 评测任务
MODELSAFETY = 评测任务
SIM2BRAIN_SNN = 评测任务
TRAIN = 训练任务
INFERENCE = 推理任务
BENCHMARK = 评测任务
@@ -3342,6 +3346,7 @@ Already_stopped=任务已停止。
Stopped_failed=任务停止失败,请稍后再试。
Stopped_success_update_status_fail=任务停止成功,状态及运行时间更新失败。
load_code_failed=代码加载失败,请确认选择了正确的分支。
dataset_load_fail=数据集加载失败。

error.debug_datasetsize = 数据集大小超过限制(%dGB)
error.dataset_select = 数据集选择错误:数量超过限制或者有同名数据集
@@ -3378,4 +3383,17 @@ new_model_security_evaluation_tips = 模型安全评测只适用于图像分类
get_file_fail= 获取上传文件失败,请稍后再试。
content_type_unsupported=请上传jpg、jpeg或png图片。
process_image_fail=图片处理失败,请稍后再试。
[tech]
get_file_fail= 获取上传文件失败。
content_type_unsupported=上传文件的格式有误。
sql_err=数据处理错误,请稍后再试。
show_or_hide_fail=%s失败。
incorrect_openi_format = 启智项目地址格式错误
openi_repo_not_exist = 启智项目不存在
tech_not_exist = 项目立项编号不存在
institution_not_valid = 当前提交的贡献单位不在该科技项目的参与单位中
repo_converge_exists = 科技项目[%s]中已存在[%s],请勿重复提交
to_migrate_repo_exists = 该项目已迁移到启智,请使用启智社区方式提交申请




+ 22281
- 172
package-lock.json
File diff suppressed because it is too large
View File


+ 7
- 1
package.json View File

@@ -52,6 +52,7 @@
"svgo-loader": "2.2.1",
"swagger-ui": "3.25.3",
"terser-webpack-plugin": "3.0.1",
"uuidv1": "1.6.14",
"vue": "2.6.11",
"vue-bar-graph": "1.2.0",
"vue-calendar-heatmap": "0.8.4",
@@ -59,8 +60,10 @@
"vue-loader": "15.9.2",
"vue-router": "3.3.4",
"vue-template-compiler": "2.6.11",
"vue-tree-list": "1.5.0",
"webpack": "4.43.0",
"webpack-cli": "3.3.11",
"webpack-cli": "3.3.12",
"webpack-dev-server": "4.9.3",
"webpack-fix-style-only-entries": "0.4.0",
"worker-loader": "2.0.0",
"xlsx": "0.17.3"
@@ -78,6 +81,9 @@
"typescript": "4.5.5",
"updates": "10.2.11"
},
"scripts": {
"start": "make watch-frontend && ./opendata"
},
"browserslist": [
"defaults"
]


BIN
public/0acc017d3b9b32f1f61a9af2315d5187.png View File

Before After
Width: 44  |  Height: 48  |  Size: 994 B

BIN
public/0f1600ced2415ea9530a1a55ec045fef.png View File

Before After
Width: 28  |  Height: 28  |  Size: 801 B

BIN
public/1f2e7b26b1be5e67e613178f38625008.png View File

Before After
Width: 44  |  Height: 48  |  Size: 1.0 KiB

BIN
public/276e9642cca7e5f8958c004269a6c0d7.png View File

Before After
Width: 360  |  Height: 313  |  Size: 24 KiB

BIN
public/41fa1ffe704082c381ae88ea686c9f39.png View File

Before After
Width: 44  |  Height: 20  |  Size: 567 B

BIN
public/5920c99e273bded4a2c702d4a1ed59ee.png View File

Before After
Width: 28  |  Height: 32  |  Size: 608 B

BIN
public/6f562e238482d4479212cbf367f6a293.png View File

Before After
Width: 28  |  Height: 32  |  Size: 542 B

BIN
public/71261d74041a4133ae4b9908d4a8109f.png View File

Before After
Width: 48  |  Height: 48  |  Size: 1.1 KiB

BIN
public/d4f9fbedc79f92e41f984283b6227127.png View File

Before After
Width: 48  |  Height: 48  |  Size: 1.2 KiB

+ 93
- 21
public/home/home.js View File

@@ -812,6 +812,73 @@ function getRecommendModule() {
function initHomeTopBanner() {
var homeSlideTimer = null;
var homeSlideDuration = 8000;

function getBannerData() {
$.ajax({
type: "GET",
url: "/dashboard/invitation",
headers: { authorization: token, },
dataType: "json",
data: { filename: 'home/banner', },
success: function (data) {
try {
var banners = JSON.parse(data);
var count = 0, bannerList = [];
for (var i = 0; i < banners.length; i++) {
(function (banner, index) {
$.ajax({
type: "GET",
url: "/dashboard/invitation",
headers: { authorization: token, },
data: { filename: 'home/banners/' + banner },
success: function (data) {
count++;
bannerList[index] = {
banner: banner,
data: data,
};
if (count == banners.length) {
renderBanners(bannerList);
}
},
error: function (err) {
count++;
if (count == data.length) {
startSlide();
}
console.log(err);
}
});
})(banners[i], i);
}
if (!banners.length) {
startSlide();
}
} catch (err) {
console.log(err);
startSlide();
}
},
error: function (err) {
console.log(err);
startSlide();
}
});
}

function renderBanners(bannerList) {
var hmPageC = $('._hm-bg-container ._hm-pg-c');
var hmPageSlidePaginationC = $('._hm-slide-pagination-c');
for (var i = 0, iLen = bannerList.length; i < iLen; i++) {
var banner = bannerList[i];
if (banner.data) {
hmPageC.append($(banner.data));
hmPageSlidePaginationC.append('<div class="_hm-slide-pagination-item"></div>');
}
}
startSlide();
}

function homeSlide(direction, index) {
var slidePages = $('._hm-pg-c ._hm-pg');
var currentPage = slidePages.filter('._hm-pg-show');
@@ -832,8 +899,9 @@ function initHomeTopBanner() {
}

function startSlide() {
$('._hm-slide-pagination-c').show();
homeSlideTimer && clearTimeout(homeSlideTimer);
homeSlideTimer = setTimeout(function() {
homeSlideTimer = setTimeout(function () {
homeSlide('right');
startSlide();
}, homeSlideDuration);
@@ -843,26 +911,30 @@ function initHomeTopBanner() {
homeSlideTimer && clearTimeout(homeSlideTimer);
}

$('._hm-slide-btn').on('click', function () {
if ($(this).hasClass('_hm-slide-btn-left')) {
homeSlide('left');
} else {
homeSlide('right');
}
startSlide();
});
$('._hm-pg #homenews').on('mouseenter', function() {
stopSlide();
}).on('mouseleave', function() {
startSlide();
});
$('._hm-slide-pagination-c ._hm-slide-pagination-item').on('click', function() {
var self = $(this);
if (self.hasClass('_hm-slide-pagination-item-active')) return;
homeSlide('', self.index());
startSlide();
});
setTimeout(function() { startSlide(); }, 500);
function eventInit() {
$('._hm-slide-btn').on('click', function () {
if ($(this).hasClass('_hm-slide-btn-left')) {
homeSlide('left');
} else {
homeSlide('right');
}
startSlide();
});
$('._hm-pg #homenews').on('mouseenter', function () {
stopSlide();
}).on('mouseleave', function () {
startSlide();
});
$('._hm-slide-pagination-c').on('click', '._hm-slide-pagination-item', function () {
var self = $(this);
if (self.hasClass('_hm-slide-pagination-item-active')) return;
homeSlide('', self.index());
startSlide();
});
}

getBannerData();
eventInit();
}

initHomeTopBanner();


+ 21
- 0
public/html/js/nbview/LICENSE.txt View File

@@ -0,0 +1,21 @@
The MIT License (MIT)

Copyright (c) 2015, Jeremy Singer-Vine

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

+ 22
- 0
public/html/js/nbview/Makefile View File

@@ -0,0 +1,22 @@
default:

cdnjs=https://cdnjs.cloudflare.com/ajax/libs/

.PHONY: js/vendor css/vendor
js/vendor:
curl -s $(cdnjs)es5-shim/4.5.14/es5-shim.min.js > $@/es5-shim.min.js
curl -s $(cdnjs)marked/4.1.1/marked.min.js > $@/marked.min.js
curl -s $(cdnjs)dompurify/2.4.0/purify.min.js > $@/purify.min.js
curl -s $(cdnjs)KaTeX/0.12.0/katex.min.js > $@/katex.min.js
curl -s $(cdnjs)KaTeX/0.12.0/contrib/auto-render.min.js > $@/katex-auto-render.min.js
@echo "💫 Manually download prism.min.js from 'https://prismjs.com/download.html#themes=prism&languages=markup+clike+javascript+julia+python+r'"

css/vendor:
curl -s $(cdnjs)KaTeX/0.12.0/katex.min.css > $@/katex.min.css
curl -sL https://github.com/KaTeX/katex-fonts/archive/master.zip > katex-fonts.zip
rm -rf $@/fonts
unzip -j katex-fonts.zip "katex-fonts-master/fonts/*" -d $@/fonts
rm -f katex-fonts.zip
@echo "💫 Manually download prism.css from 'https://prismjs.com/download.html#themes=prism&languages=markup+clike+javascript+julia+python+r'"

vendor: js/vendor css/vendor

+ 25
- 0
public/html/js/nbview/README.md View File

@@ -0,0 +1,25 @@
# nbpreview

`nbpreview` is a [Jupyter](http://jupyter.org/)/[IPython](http://ipython.org/) notebook previewer. It does not require an internet connection, or even having Jupyter/IPython installed.

You can use [__this hosted version__](https://jsvine.github.io/nbpreview/) or `git clone` your own. Just drag-and-drop your `.ipynb` file onto the filepicker, and *voilà!*

## Local / Offline Usage

To run `nbpreview` on your own computer, clone or [download](archive/master.zip) this repository, and then open `index.html`.

Alternatively, you can run a local server by executing `python3 -m http.server 8000` in the `nbpreview` directory, after which you can visit [http://localhost:8000](http://localhost:8000).

## Built on ...

- [notebookjs](https://github.com/jsvine/notebookjs), for notebook-rendering
- [dompurify](https://github.com/cure53/DOMPurify), for HTML sanitization
- [prism](http://prismjs.com/), for code-highlighting
- [marked](https://github.com/chjj/marked), for markdown-rendering
- [ansi_up](https://github.com/drudru/ansi_up), for ANSI-rendering
- [katex](https://github.com/KaTeX/KaTeX), for math typesetting
- [es5-shim](https://github.com/es-shims/es5-shim), for JavaScript compatibility

## Contributing

Contributions are welcome. If you'd like to suggest a new feature, please open an issue to discuss the proposal before submitting a pull request.

+ 35
- 0
public/html/js/nbview/css/nbpreview.css View File

@@ -0,0 +1,35 @@
body {
font-family: Helvetica Neue, Helvetica, sans-serif;
font-size: 14px;
}

#main {
width: 99%;
max-width: 750px;
margin: 0 auto;
}

#header {
line-height: 2;
margin-bottom: 0.25em;
font-weight: bold;
}

#controls {
border: 1px dotted #CCC;
padding: 0.75em;
margin-bottom: 0.5em;
background-color: #EEF;
}

#footer {
border: 1px dotted #CCC;
background-color: #EEF;
font-size: 0.8em;
padding: 0.5em;
text-align: center;
}

#footer a, #footer a:visited {
color: #0077EE;
}

+ 85
- 0
public/html/js/nbview/css/notebook.css View File

@@ -0,0 +1,85 @@
.nb-notebook {
line-height: 1.5;
}

.nb-stdout, .nb-stderr {
white-space: pre-wrap;
margin: 1em 0;
padding: 0.1em 0.5em;
}

.nb-stderr {
background-color: #FAA;
}

.nb-cell + .nb-cell {
margin-top: 0.5em;
}

.nb-output table {
border: 1px solid #000;
border-collapse: collapse;
}

.nb-output th {
font-weight: bold;
}

.nb-output th, .nb-output td {
border: 1px solid #000;
padding: 0.25em;
text-align: left;
vertical-align: middle;
border-collapse: collapse;
}

.nb-notebook blockquote {
border-left: 5px solid #CCC;
margin-left: 0;
padding-left: 1em;
}

.nb-cell {
position: relative;
}

.nb-raw-cell {
white-space: pre-wrap;
background-color: #f5f2f0;
font-family: Consolas, Monaco, 'Andale Mono', monospace;
padding: 1em;
margin: .5em 0;
}

.nb-output {
min-height: 1em;
width: 100%;
overflow-x: scroll;
border-right: 1px dotted #CCC;
}

.nb-output img {
max-width: 100%;
}

.nb-output:before, .nb-input:before {
position: absolute;
font-family: monospace;
color: #999;
left: -7.5em;
width: 7em;
text-align: right;
}

.nb-input:before {
content: "In [" attr(data-prompt-number) "]:";
}
.nb-output:before {
content: "Out [" attr(data-prompt-number) "]:";
}

// Fix pandas dataframe formatting
div[style="max-height:1000px;max-width:1500px;overflow:auto;"] {
max-height: none !important;
}


BIN
public/html/js/nbview/css/vendor/fonts/KaTeX_AMS-Regular.ttf View File


BIN
public/html/js/nbview/css/vendor/fonts/KaTeX_AMS-Regular.woff View File


BIN
public/html/js/nbview/css/vendor/fonts/KaTeX_AMS-Regular.woff2 View File


BIN
public/html/js/nbview/css/vendor/fonts/KaTeX_Caligraphic-Bold.ttf View File


BIN
public/html/js/nbview/css/vendor/fonts/KaTeX_Caligraphic-Bold.woff View File


BIN
public/html/js/nbview/css/vendor/fonts/KaTeX_Caligraphic-Bold.woff2 View File


BIN
public/html/js/nbview/css/vendor/fonts/KaTeX_Caligraphic-Regular.ttf View File


BIN
public/html/js/nbview/css/vendor/fonts/KaTeX_Caligraphic-Regular.woff View File


BIN
public/html/js/nbview/css/vendor/fonts/KaTeX_Caligraphic-Regular.woff2 View File


BIN
public/html/js/nbview/css/vendor/fonts/KaTeX_Fraktur-Bold.ttf View File


BIN
public/html/js/nbview/css/vendor/fonts/KaTeX_Fraktur-Bold.woff View File


BIN
public/html/js/nbview/css/vendor/fonts/KaTeX_Fraktur-Bold.woff2 View File


BIN
public/html/js/nbview/css/vendor/fonts/KaTeX_Fraktur-Regular.ttf View File


BIN
public/html/js/nbview/css/vendor/fonts/KaTeX_Fraktur-Regular.woff View File


BIN
public/html/js/nbview/css/vendor/fonts/KaTeX_Fraktur-Regular.woff2 View File


BIN
public/html/js/nbview/css/vendor/fonts/KaTeX_Main-Bold.ttf View File


BIN
public/html/js/nbview/css/vendor/fonts/KaTeX_Main-Bold.woff View File


BIN
public/html/js/nbview/css/vendor/fonts/KaTeX_Main-Bold.woff2 View File


BIN
public/html/js/nbview/css/vendor/fonts/KaTeX_Main-BoldItalic.ttf View File


BIN
public/html/js/nbview/css/vendor/fonts/KaTeX_Main-BoldItalic.woff View File


BIN
public/html/js/nbview/css/vendor/fonts/KaTeX_Main-BoldItalic.woff2 View File


BIN
public/html/js/nbview/css/vendor/fonts/KaTeX_Main-Italic.ttf View File


BIN
public/html/js/nbview/css/vendor/fonts/KaTeX_Main-Italic.woff View File


BIN
public/html/js/nbview/css/vendor/fonts/KaTeX_Main-Italic.woff2 View File


BIN
public/html/js/nbview/css/vendor/fonts/KaTeX_Main-Regular.ttf View File


BIN
public/html/js/nbview/css/vendor/fonts/KaTeX_Main-Regular.woff View File


BIN
public/html/js/nbview/css/vendor/fonts/KaTeX_Main-Regular.woff2 View File


Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save