Explorar o código

0.3.5 (#451)

* initial completion of the sharing function

* Adjusting multi-partition disk mounts

* Add file sharing function

* update usb auto mount shell

* update samba config

* add umount disk function

* update change log

* update usb auto mount \

* update usb auto mount

* Update periodical.go
link %!s(int64=2) %!d(string=hai) anos
pai
achega
cee34ec1c2

+ 1 - 0
.gitignore

@@ -37,3 +37,4 @@ __debug_bin
 main
 CasaOS
 github.com
+.all-contributorsrc

+ 16 - 0
CHANGELOG.md

@@ -18,6 +18,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ### Fixed
 
+## [0.3.5-alpha] - 2022-08-08
+
+### Added
+
+- [File] Mount the shared samba
+- [File] File sharing via Samba
+- [System] You can share casaos on Twitter, facebook, reddit
+
+### Changed
+
+- [Disk] Support for mounting existing data disks
+
+### Fixed
+
+- [App] fixed uninstalling imported docker container apps results in wiping ALL your config data from them ([#360](https://github.com/IceWhaleTech/CasaOS/issues/360))
+
 ## [0.3.4] - 2022-07-29(UTC)
 
 ### Added

+ 5 - 6
go.mod

@@ -6,7 +6,6 @@ require (
 	github.com/Curtis-Milo/nat-type-identifier-go v0.0.0-20220215191915-18d42168c63d
 	github.com/Microsoft/go-winio v0.5.0 // indirect
 	github.com/Microsoft/hcsshim v0.8.22 // indirect
-	github.com/StackExchange/wmi v0.0.0-20210224194228-fe8f1750fd46 // indirect
 	github.com/ambelovsky/go-structs v1.1.0 // indirect
 	github.com/ambelovsky/gosf v0.0.0-20201109201340-237aea4d6109
 	github.com/ambelovsky/gosf-socketio v0.0.0-20201109193639-add9d32f8b19 // indirect
@@ -22,17 +21,18 @@ require (
 	github.com/gin-contrib/gzip v0.0.2
 	github.com/gin-gonic/gin v1.7.2
 	github.com/go-ini/ini v1.62.0
-	github.com/go-ole/go-ole v1.2.5 // indirect
 	github.com/go-playground/validator/v10 v10.6.1 // indirect
 	github.com/gogo/googleapis v1.4.1 // indirect
 	github.com/golang-jwt/jwt/v4 v4.4.1
 	github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
+	github.com/golang/mock v1.6.0
 	github.com/gomodule/redigo v1.8.5
 	github.com/google/go-github/v36 v36.0.0
 	github.com/google/uuid v1.3.0 // indirect
 	github.com/googollee/go-socket.io v1.6.2
 	github.com/gorilla/mux v1.8.0 // indirect
 	github.com/gorilla/websocket v1.4.2
+	github.com/hirochachacha/go-smb2 v1.1.0
 	github.com/jinzhu/copier v0.3.2
 	github.com/json-iterator/go v1.1.11 // indirect
 	github.com/klauspost/compress v1.13.6 // indirect
@@ -47,16 +47,17 @@ require (
 	github.com/opencontainers/image-spec v1.0.2 // indirect
 	github.com/opencontainers/selinux v1.8.5 // indirect
 	github.com/patrickmn/go-cache v2.1.0+incompatible
+	github.com/pilebones/go-udev v0.9.0
 	github.com/pkg/errors v0.9.1
 	github.com/prometheus/procfs v0.7.3 // indirect
 	github.com/robfig/cron v1.2.0
 	github.com/satori/go.uuid v1.2.0
-	github.com/shirou/gopsutil/v3 v3.21.5
+	github.com/shirou/gopsutil/v3 v3.22.7
 	github.com/sirupsen/logrus v1.8.1
 	github.com/smartystreets/assertions v1.2.0 // indirect
 	github.com/smartystreets/goconvey v1.6.4 // indirect
+	github.com/stretchr/testify v1.8.0
 	github.com/tidwall/gjson v1.10.2
-	github.com/tklauser/go-sysconf v0.3.6 // indirect
 	github.com/ugorji/go v1.2.6 // indirect
 	go.opencensus.io v0.23.0 // indirect
 	go.uber.org/zap v1.10.0
@@ -64,7 +65,6 @@ require (
 	golang.org/x/mod v0.5.0 // indirect
 	golang.org/x/net v0.0.0-20211020060615-d418f374d309 // indirect
 	golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f
-	golang.org/x/sys v0.0.0-20211020174200-9d6173849985 // indirect
 	golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b // indirect
 	golang.org/x/text v0.3.7 // indirect
 	golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect
@@ -75,7 +75,6 @@ require (
 	gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
 	gopkg.in/ini.v1 v1.62.0 // indirect
 	gopkg.in/natefinch/lumberjack.v2 v2.0.0
-	gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
 	gorm.io/driver/sqlite v1.2.6
 	gorm.io/gorm v1.22.5
 )

+ 32 - 22
go.sum

@@ -86,9 +86,6 @@ github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAE
 github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
 github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
 github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ=
-github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
-github.com/StackExchange/wmi v0.0.0-20210224194228-fe8f1750fd46 h1:5sXbqlSomvdjlRbWyNqkPsJ3Fg+tQZCbgeX1VGljbQY=
-github.com/StackExchange/wmi v0.0.0-20210224194228-fe8f1750fd46/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
 github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
 github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
 github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
@@ -330,6 +327,8 @@ github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWo
 github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
 github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXtdwXqoenmZCw6S+25EAm2MkxbG0deNDu4cbSA=
 github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
+github.com/geoffgarside/ber v1.1.0 h1:qTmFG4jJbwiSzSXoNJeHcOprVzZ8Ulde2Rrrifu5U9w=
+github.com/geoffgarside/ber v1.1.0/go.mod h1:jVPKeCbj6MvQZhwLYsGwaGI52oUorHoHKNecGT85ZCc=
 github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
 github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
 github.com/gin-contrib/gzip v0.0.2 h1:VMBkd4ZB1Hl7e1lOA5gEZ/qdD3d9vLIq57xKWgPCCV8=
@@ -356,9 +355,8 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9
 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
 github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
 github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
-github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
-github.com/go-ole/go-ole v1.2.5 h1:t4MGB5xEDZvXI+0rMjjsfBsD7yAgp/s9ZDkL1JndXwY=
-github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
+github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
+github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
 github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg=
 github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
 github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc=
@@ -455,8 +453,9 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
 github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
 github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
+github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY=
 github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
 github.com/google/go-github/v36 v36.0.0 h1:ndCzM616/oijwufI7nBRa+5eZHLldT+4yIB68ib5ogs=
@@ -513,6 +512,8 @@ github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHh
 github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
 github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
 github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
+github.com/hirochachacha/go-smb2 v1.1.0 h1:b6hs9qKIql9eVXAiN0M2wSFY5xnhbHAQoCwRKbaRTZI=
+github.com/hirochachacha/go-smb2 v1.1.0/go.mod h1:8F1A4d5EZzrGu5R7PU163UcMRDJQl4FtcxjBfsY8TZE=
 github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
 github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
 github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
@@ -577,6 +578,8 @@ github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
 github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
 github.com/lucas-clemente/quic-go v0.25.0 h1:K+X9Gvd7JXsOHtU0N2icZ2Nw3rx82uBej3mP4CLgibc=
 github.com/lucas-clemente/quic-go v0.25.0/go.mod h1:YtzP8bxRVCBlO77yRanE264+fY/T2U9ZlW1AaHOsMOg=
+github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
+github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
 github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
 github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
 github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
@@ -702,6 +705,8 @@ github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrap
 github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
 github.com/pierrec/lz4/v4 v4.1.2 h1:qvY3YFXRQE/XB8MlLzJH7mSzBs74eA2gg52YTk6jUPM=
 github.com/pierrec/lz4/v4 v4.1.2/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
+github.com/pilebones/go-udev v0.9.0 h1:N1uEO/SxUwtIctc0WLU0t69JeBxIYEYnj8lT/Nabl9Q=
+github.com/pilebones/go-udev v0.9.0/go.mod h1:T2eI2tUSK0hA2WS5QLjXJUfQkluZQu+18Cqvem3CaXI=
 github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -709,6 +714,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
+github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
 github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA=
 github.com/prometheus/client_golang v0.0.0-20180209125602-c332b6f63c06/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
 github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
@@ -756,8 +763,8 @@ github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
 github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
 github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo=
 github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
-github.com/shirou/gopsutil/v3 v3.21.5 h1:YUBf0w/KPLk7w1803AYBnH7BmA+1Z/Q5MEZxpREUaB4=
-github.com/shirou/gopsutil/v3 v3.21.5/go.mod h1:ghfMypLDrFSWN2c9cDYFLHyynQ+QUht0cv/18ZqVczw=
+github.com/shirou/gopsutil/v3 v3.22.7 h1:flKnuCMfUUrO+oAvwAd6GKZgnPzr098VA/UJ14nhJd4=
+github.com/shirou/gopsutil/v3 v3.22.7/go.mod h1:s648gW4IywYzUfE/KjXxUsqrqx/T2xO5VqOXxONeRfI=
 github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY=
 github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM=
 github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0=
@@ -818,14 +825,17 @@ github.com/stretchr/objx v0.0.0-20180129172003-8a3f7159479f/go.mod h1:HFkY916IF+
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
 github.com/stretchr/testify v0.0.0-20180303142811-b89eecf5ca5d/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
 github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
 github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
 github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
 github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
@@ -837,12 +847,10 @@ github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
 github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
 github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
 github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
-github.com/tklauser/go-sysconf v0.3.4/go.mod h1:Cl2c8ZRWfHD5IrfHo9VN+FX9kCFjIOyVklgXycLB6ek=
-github.com/tklauser/go-sysconf v0.3.6 h1:oc1sJWvKkmvIxhDHeKWvZS4f6AW+YcoguSfRF2/Hmo4=
-github.com/tklauser/go-sysconf v0.3.6/go.mod h1:MkWzOF4RMCshBAMXuhXJs64Rte09mITnppBXY/rYEFI=
-github.com/tklauser/numcpus v0.2.1/go.mod h1:9aU+wOc6WjUIZEwWMP62PL/41d65P+iks1gBkr4QyP8=
-github.com/tklauser/numcpus v0.2.2 h1:oyhllyrScuYI6g+h/zUvNXNp1wy7x8qQy3t/piefldA=
-github.com/tklauser/numcpus v0.2.2/go.mod h1:x3qojaO3uyYt0i56EW/VUYs7uBvdl2fkfZFu0T9wgjM=
+github.com/tklauser/go-sysconf v0.3.10 h1:IJ1AZGZRWbY8T5Vfk04D9WOA5WSejdflXxP03OUqALw=
+github.com/tklauser/go-sysconf v0.3.10/go.mod h1:C8XykCvCb+Gn0oNCWPIlcb0RuglQTYaQ2hGm7jmxEFk=
+github.com/tklauser/numcpus v0.4.0 h1:E53Dm1HjH1/R2/aoCtXtPgzmElmn51aOkhCFSuZq//o=
+github.com/tklauser/numcpus v0.4.0/go.mod h1:1+UI3pD8NW14VMwdgJNJ1ESk2UnwhAnz5hMwiKKqXCQ=
 github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
 github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
 github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c/go.mod h1:hzIxponao9Kjc7aWznkXaL4U4TWaDSs8zcsY4Ka08nM=
@@ -883,6 +891,8 @@ github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
 github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
 github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
+github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=
+github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
 github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs=
 github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA=
 github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg=
@@ -1103,10 +1113,9 @@ golang.org/x/sys v0.0.0-20201117170446-d9b008d0a637/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201202213521-69691e467435/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210217105451-b926d437f341/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -1116,8 +1125,9 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20211020174200-9d6173849985 h1:LOlKVhfDyahgmqa97awczplwkjzNaELFg3zRIJ13RYo=
-golang.org/x/sys v0.0.0-20211020174200-9d6173849985/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k=
+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b h1:9zKuko04nR4gjZ4+DNjHqRlAJqbJETHwiNKDqTfOjfE=
 golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@@ -1340,8 +1350,8 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
 gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
-gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gorm.io/driver/sqlite v1.2.6 h1:SStaH/b+280M7C8vXeZLz/zo9cLQmIGwwj3cSj7p6l4=
 gorm.io/driver/sqlite v1.2.6/go.mod h1:gyoX0vHiiwi0g49tv+x2E7l8ksauLK0U/gShcdUsjWY=
 gorm.io/gorm v1.22.3/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0=

+ 15 - 1
main.go

@@ -15,6 +15,7 @@ import (
 	"github.com/IceWhaleTech/CasaOS/pkg/utils/random"
 	"github.com/IceWhaleTech/CasaOS/route"
 	"github.com/IceWhaleTech/CasaOS/service"
+	"github.com/IceWhaleTech/CasaOS/types"
 
 	"github.com/robfig/cron"
 	"gorm.io/gorm"
@@ -26,21 +27,31 @@ var configFlag = flag.String("c", "", "config address")
 var dbFlag = flag.String("db", "", "db path")
 var resetUser = flag.Bool("ru", false, "reset user")
 var user = flag.String("user", "", "user name")
+var version = flag.Bool("v", false, "show version")
 
 func init() {
 	flag.Parse()
+	if *version {
+		fmt.Println("v" + types.CURRENTVERSION)
+		return
+	}
 	config.InitSetup(*configFlag)
 	config.UpdateSetup()
+
 	loger.LogInit()
 	if len(*dbFlag) == 0 {
 		*dbFlag = config.AppInfo.DBPath + "/db"
 	}
+
 	sqliteDB = sqlite.GetDb(*dbFlag)
 	//gredis.GetRedisConn(config.RedisInfo),
+
 	service.MyService = service.NewService(sqliteDB)
+
 	service.Cache = cache.Init()
 
 	service.GetToken()
+
 	service.NewVersionApp = make(map[string]string)
 	route.InitFunction()
 
@@ -62,6 +73,9 @@ func init() {
 // @BasePath /v1
 func main() {
 	service.NotifyMsg = make(chan notify.Message, 10)
+	if *version {
+		return
+	}
 	if *resetUser {
 		if user == nil || len(*user) == 0 {
 			fmt.Println("user is empty")
@@ -81,7 +95,7 @@ func main() {
 		return
 	}
 	go route.SocketInit(service.NotifyMsg)
-
+	go route.MonitoryUSB()
 	//model.Setup()
 	//gredis.Setup()
 	r := route.InitRouter()

+ 20 - 0
model/connections.go

@@ -0,0 +1,20 @@
+/*
+ * @Author: LinkLeong link@icewhale.org
+ * @Date: 2022-07-27 10:30:43
+ * @LastEditors: LinkLeong
+ * @LastEditTime: 2022-08-04 20:06:04
+ * @FilePath: /CasaOS/model/connections.go
+ * @Description:
+ * @Website: https://www.casaos.io
+ * Copyright (c) 2022 by icewhale, All Rights Reserved.
+ */
+package model
+
+type Connections struct {
+	ID         uint   `json:"id"`
+	Username   string `json:"username"`
+	Password   string `json:"password,omitempty"`
+	Host       string `json:"host"`
+	Port       string `json:"port"`
+	MountPoint string `json:"mount_point"`
+}

+ 31 - 19
model/disk.go

@@ -2,7 +2,7 @@
  * @Author: LinkLeong link@icewhale.com
  * @Date: 2022-07-13 10:43:45
  * @LastEditors: LinkLeong
- * @LastEditTime: 2022-07-13 11:00:04
+ * @LastEditTime: 2022-08-03 14:45:35
  * @FilePath: /CasaOS/model/disk.go
  * @Description:
  * @Website: https://www.casaos.io
@@ -39,6 +39,7 @@ type LSBLKModel struct {
 	Serial      string       `json:"serial"`
 	Children    []LSBLKModel `json:"children"`
 	SubSystems  string       `json:"subsystems"`
+	Label       string       `json:"label"`
 	//详情特有
 	StartSector uint64 `json:"start_sector,omitempty"`
 	Rota        bool   `json:"rota"` //true(hhd) false(ssd)
@@ -47,35 +48,46 @@ type LSBLKModel struct {
 }
 
 type Drive struct {
-	Name        string `json:"name"`
-	Size        uint64 `json:"size"`
-	Model       string `json:"model"`
-	Health      string `json:"health"`
-	Temperature int    `json:"temperature"`
-	DiskType    string `json:"disk_type"`
-	NeedFormat  bool   `json:"need_format"`
-	Serial      string `json:"serial"`
-	Path        string `json:"path"`
+	Name           string `json:"name"`
+	Size           uint64 `json:"size"`
+	Model          string `json:"model"`
+	Health         string `json:"health"`
+	Temperature    int    `json:"temperature"`
+	DiskType       string `json:"disk_type"`
+	NeedFormat     bool   `json:"need_format"`
+	Serial         string `json:"serial"`
+	Path           string `json:"path"`
+	ChildrenNumber int    `json:"children_number"`
 }
 
 type DriveUSB struct {
-	Name  string `json:"name"`
-	Size  uint64 `json:"size"`
-	Used  uint64 `json:"use"`
-	Model string `json:"model"`
-	Mount bool   `json:"mount"`
-	Avail uint64 `json:"avail"`
+	Name     string        `json:"name"`
+	Size     uint64        `json:"size"`
+	Model    string        `json:"model"`
+	Avail    uint64        `json:"avail"`
+	Children []USBChildren `json:"children"`
+}
+type USBChildren struct {
+	Name       string `json:"name"`
+	Size       uint64 `json:"size"`
+	Avail      uint64 `json:"avail"`
+	MountPoint string `json:"mount_point"`
 }
 
 type Storage struct {
-	Name       string `json:"name"`
-	MountPoint string `json:"mountpoint"`
+	MountPoint string `json:"mount_point"`
 	Size       string `json:"size"`
 	Avail      string `json:"avail"` //可用空间
 	Type       string `json:"type"`
-	CreatedAt  int64  `json:"create_at"`
 	Path       string `json:"path"`
 	DriveName  string `json:"drive_name"`
+	Label      string `json:"label"`
+}
+type Storages struct {
+	DiskName string    `json:"disk_name"`
+	Size     uint64    `json:"size"`
+	Path     string    `json:"path"`
+	Children []Storage `json:"children"`
 }
 
 type Summary struct {

+ 17 - 0
model/share.go

@@ -0,0 +1,17 @@
+/*
+ * @Author: LinkLeong link@icewhale.org
+ * @Date: 2022-07-26 11:12:12
+ * @LastEditors: LinkLeong
+ * @LastEditTime: 2022-07-27 14:58:55
+ * @FilePath: /CasaOS/model/share.go
+ * @Description:
+ * @Website: https://www.casaos.io
+ * Copyright (c) 2022 by icewhale, All Rights Reserved.
+ */
+package model
+
+type Shares struct {
+	ID        uint   `json:"id"`
+	Anonymous bool   `json:"anonymous"`
+	Path      string `json:"path"`
+}

+ 19 - 8
model/zima.go

@@ -1,14 +1,25 @@
+/*
+ * @Author: LinkLeong link@icewhale.org
+ * @Date: 2022-05-13 18:15:46
+ * @LastEditors: LinkLeong
+ * @LastEditTime: 2022-08-01 18:32:57
+ * @FilePath: /CasaOS/model/zima.go
+ * @Description:
+ * @Website: https://www.casaos.io
+ * Copyright (c) 2022 by icewhale, All Rights Reserved.
+ */
 package model
 
 import "time"
 
 type Path struct {
-	Name  string    `json:"name"`   //File name or document name
-	Path  string    `json:"path"`   //Full path to file or folder
-	IsDir bool      `json:"is_dir"` //Is it a folder
-	Date  time.Time `json:"date"`
-	Size  int64     `json:"size"` //File Size
-	Type  string    `json:"type,omitempty"`
-	Label string    `json:"label,omitempty"`
-	Write bool      `json:"write"`
+	Name       string                 `json:"name"`   //File name or document name
+	Path       string                 `json:"path"`   //Full path to file or folder
+	IsDir      bool                   `json:"is_dir"` //Is it a folder
+	Date       time.Time              `json:"date"`
+	Size       int64                  `json:"size"` //File Size
+	Type       string                 `json:"type,omitempty"`
+	Label      string                 `json:"label,omitempty"`
+	Write      bool                   `json:"write"`
+	Extensions map[string]interface{} `json:"extensions"`
 }

+ 75 - 0
pkg/samba/smaba.go

@@ -0,0 +1,75 @@
+/*
+ * @Author: LinkLeong link@icewhale.org
+ * @Date: 2022-07-27 10:35:29
+ * @LastEditors: LinkLeong
+ * @LastEditTime: 2022-08-01 13:56:44
+ * @FilePath: /CasaOS/pkg/samba/smaba.go
+ * @Description:
+ * @Website: https://www.casaos.io
+ * Copyright (c) 2022 by icewhale, All Rights Reserved.
+ */
+package samba
+
+import (
+	"errors"
+	"net"
+
+	"github.com/hirochachacha/go-smb2"
+)
+
+func ConnectSambaService(host, port, username, password, directory string) error {
+	conn, err := net.Dial("tcp", host+":"+port)
+	if err != nil {
+		return err
+	}
+	defer conn.Close()
+	d := &smb2.Dialer{
+		Initiator: &smb2.NTLMInitiator{
+			User:     username,
+			Password: password,
+		},
+	}
+
+	s, err := d.Dial(conn)
+	if err != nil {
+		return err
+	}
+	defer s.Logoff()
+	names, err := s.ListSharenames()
+	if err != nil {
+		return err
+	}
+
+	for _, name := range names {
+		if name == directory {
+			return nil
+		}
+	}
+	return errors.New("directory not found")
+}
+
+//get share name list
+func GetSambaSharesList(host, port, username, password string) ([]string, error) {
+	conn, err := net.Dial("tcp", host+":"+port)
+	if err != nil {
+		return nil, err
+	}
+	defer conn.Close()
+	d := &smb2.Dialer{
+		Initiator: &smb2.NTLMInitiator{
+			User:     username,
+			Password: password,
+		},
+	}
+
+	s, err := d.Dial(conn)
+	if err != nil {
+		return nil, err
+	}
+	defer s.Logoff()
+	names, err := s.ListSharenames()
+	if err != nil {
+		return nil, err
+	}
+	return names, err
+}

+ 2 - 2
pkg/sqlite/db.go

@@ -2,7 +2,7 @@
  * @Author: LinkLeong link@icewhale.com
  * @Date: 2022-05-13 18:15:46
  * @LastEditors: LinkLeong
- * @LastEditTime: 2022-07-11 18:10:53
+ * @LastEditTime: 2022-07-27 11:25:26
  * @FilePath: /CasaOS/pkg/sqlite/db.go
  * @Description:
  * @Website: https://www.casaos.io
@@ -53,7 +53,7 @@ func GetDb(dbPath string) *gorm.DB {
 	drop table o_user;	
 	`)
 
-	err = db.AutoMigrate(&model2.AppNotify{}, &model2.AppListDBModel{}, &model2.SerialDisk{}, model2.UserDBModel{})
+	err = db.AutoMigrate(&model2.AppNotify{}, &model2.AppListDBModel{}, &model2.SerialDisk{}, model2.UserDBModel{}, model2.SharesDBModel{}, model2.ConnectionsDBModel{})
 	db.Exec("DROP TABLE IF EXISTS o_application")
 	db.Exec("DROP TABLE IF EXISTS o_friend")
 	db.Exec("DROP TABLE IF EXISTS o_person_download")

+ 14 - 6
pkg/utils/common_err/e.go

@@ -27,6 +27,9 @@ const (
 	PORT_IS_OCCUPIED                = 20004
 	COMMAND_ERROR_INVALID_OPERATION = 20005
 	VERIFICATION_FAILURE            = 20006
+	Record_NOT_EXIST                = 20007
+	Record_ALREADY_EXIST            = 20008
+	SERVICE_NOT_RUNNING             = 20009
 
 	//disk
 	NAME_NOT_AVAILABLE       = 40001
@@ -48,8 +51,9 @@ const (
 	DIR_NOT_EXISTS      = 60004
 	SOURCE_DES_SAME     = 60005
 
-	//shortcuts
-	SHORTCUTS_URL_ERROR = 70001
+	//share
+	SHARE_ALREADY_EXISTS      = 70001
+	SHARE_NAME_ALREADY_EXISTS = 70002
 )
 
 var MsgFlags = map[int]string{
@@ -78,6 +82,9 @@ var MsgFlags = map[int]string{
 	FILE_OR_DIR_EXISTS:   "File or folder already exists",
 	PORT_IS_OCCUPIED:     "Port is occupied",
 	VERIFICATION_FAILURE: "Verification failure",
+	Record_ALREADY_EXIST: "Record already exists",
+	Record_NOT_EXIST:     "Record does not exist",
+	SERVICE_NOT_RUNNING:  "Service is not running",
 
 	//app
 	UNINSTALL_APP_ERROR:  "Error uninstalling app",
@@ -91,16 +98,17 @@ var MsgFlags = map[int]string{
 	REMOVE_MOUNT_POINT_ERROR: "Failed to remove mount point",
 	DISK_BUSYING:             "Drive is busy",
 	FORMAT_ERROR:             "Formatting failed, please check if the directory is occupied",
-
+	//share
+	SHARE_ALREADY_EXISTS:      "Share already exists",
+	SHARE_NAME_ALREADY_EXISTS: "Share name already exists",
 	//
 	SOURCE_DES_SAME:     "Source and destination cannot be the same.",
 	FILE_DOES_NOT_EXIST: "File does not exist",
 
 	DIR_NOT_EXISTS: "Directory does not exist",
 
-	FILE_READ_ERROR:     "File read error",
-	FILE_DELETE_ERROR:   "Delete error",
-	SHORTCUTS_URL_ERROR: "URL error",
+	FILE_READ_ERROR:   "File read error",
+	FILE_DELETE_ERROR: "Delete error",
 
 	COMMAND_ERROR_INVALID_OPERATION: "invalid operation",
 }

+ 17 - 0
pkg/utils/file/file.go

@@ -547,3 +547,20 @@ func MoveFile(sourcePath, destPath string) error {
 	}
 	return nil
 }
+
+func ReadLine(lineNumber int, path string) string {
+	file, err := os.Open(path)
+	if err != nil {
+		return ""
+	}
+	fileScanner := bufio.NewScanner(file)
+	lineCount := 1
+	for fileScanner.Scan() {
+		if lineCount == lineNumber {
+			return fileScanner.Text()
+		}
+		lineCount++
+	}
+	defer file.Close()
+	return ""
+}

+ 33 - 0
pkg/utils/udev_helper.go

@@ -0,0 +1,33 @@
+/*
+ * @Author: LinkLeong link@icewhale.org
+ * @Date: 2022-08-10 16:06:12
+ * @LastEditors: LinkLeong
+ * @LastEditTime: 2022-08-10 16:11:37
+ * @FilePath: /CasaOS/pkg/utils/udev_helper.go
+ * @Description:
+ * @Website: https://www.casaos.io
+ * Copyright (c) 2022 by icewhale, All Rights Reserved.
+ */
+package utils
+
+// func getOptionnalMatcher() (matcher netlink.Matcher, err error) {
+// 	if filePath == nil || *filePath == "" {
+// 		return nil, nil
+// 	}
+
+// 	stream, err := ioutil.ReadFile(*filePath)
+// 	if err != nil {
+// 		return nil, err
+// 	}
+
+// 	if stream == nil {
+// 		return nil, fmt.Errorf("Empty, no rules provided in \"%s\", err: %w", *filePath, err)
+// 	}
+
+// 	var rules netlink.RuleDefinitions
+// 	if err := json.Unmarshal(stream, &rules); err != nil {
+// 		return nil, fmt.Errorf("Wrong rule syntax, err: %w", err)
+// 	}
+
+// 	return &rules, nil
+// }

+ 25 - 0
route/darwin.go

@@ -0,0 +1,25 @@
+//go:build darwin
+// +build darwin
+
+/*
+ * @Author: LinkLeong link@icewhale.org
+ * @Date: 2022-08-12 14:22:28
+ * @LastEditors: LinkLeong
+ * @LastEditTime: 2022-08-12 18:41:14
+ * @FilePath: /CasaOS/route/darwin.go
+ * @Description:
+ * @Website: https://www.casaos.io
+ * Copyright (c) 2022 by icewhale, All Rights Reserved.
+ */
+
+package route
+
+func MonitoryUSB() {
+
+}
+func SendAllHardwareStatusBySocket() {
+
+}
+func SendUSBBySocket() {
+
+}

+ 49 - 19
route/init.go

@@ -1,11 +1,13 @@
 package route
 
 import (
+	"fmt"
 	"os"
 	"strconv"
 	"strings"
 
 	"github.com/IceWhaleTech/CasaOS/pkg/config"
+	"github.com/IceWhaleTech/CasaOS/pkg/samba"
 	"github.com/IceWhaleTech/CasaOS/pkg/utils/command"
 	"github.com/IceWhaleTech/CasaOS/pkg/utils/encryption"
 	"github.com/IceWhaleTech/CasaOS/pkg/utils/file"
@@ -15,15 +17,14 @@ import (
 )
 
 func InitFunction() {
-
 	ShellInit()
 	CheckSerialDiskMount()
-
 	CheckToken2_11()
 	ImportApplications()
+	// Soon to be removed
 	ChangeAPIUrl()
-
 	MoveUserToDB()
+	InitNetworkMount()
 }
 
 func CheckSerialDiskMount() {
@@ -40,25 +41,25 @@ func CheckSerialDiskMount() {
 		command.ExecEnabledSMART(v.Path)
 		if v.Children != nil {
 			for _, h := range v.Children {
-				if len(h.MountPoint) == 0 && len(v.Children) == 1 && h.FsType == "ext4" {
-					if m, ok := mountPoint[h.UUID]; ok {
-						//mount point check
-						volume := m
-						if !file.CheckNotExist(m) {
-							for i := 0; file.CheckNotExist(volume); i++ {
-								volume = m + strconv.Itoa(i+1)
-							}
-						}
-						service.MyService.Disk().MountDisk(h.Path, volume)
-						if volume != m {
-							ms := model2.SerialDisk{}
-							ms.UUID = v.UUID
-							ms.MountPoint = volume
-							service.MyService.Disk().UpdateMountPoint(ms)
+				//if len(h.MountPoint) == 0 && len(v.Children) == 1 && h.FsType == "ext4" {
+				if m, ok := mountPoint[h.UUID]; ok {
+					//mount point check
+					volume := m
+					if !file.CheckNotExist(m) {
+						for i := 0; file.CheckNotExist(volume); i++ {
+							volume = m + strconv.Itoa(i+1)
 						}
-
 					}
+					service.MyService.Disk().MountDisk(h.Path, volume)
+					if volume != m {
+						ms := model2.SerialDisk{}
+						ms.UUID = v.UUID
+						ms.MountPoint = volume
+						service.MyService.Disk().UpdateMountPoint(ms)
+					}
+
 				}
+				//}
 			}
 		}
 	}
@@ -138,3 +139,32 @@ func MoveUserToDB() {
 
 	}
 }
+
+func InitNetworkMount() {
+	connections := service.MyService.Connections().GetConnectionsList()
+	for _, v := range connections {
+		connection := service.MyService.Connections().GetConnectionByID(fmt.Sprint(v.ID))
+		directories, err := samba.GetSambaSharesList(connection.Host, connection.Port, connection.Username, connection.Password)
+		if err != nil {
+			service.MyService.Connections().DeleteConnection(fmt.Sprint(connection.ID))
+			continue
+		}
+		baseHostPath := "/mnt/" + connection.Host
+
+		mountPointList := service.MyService.System().GetDirPath(baseHostPath)
+		for _, v := range mountPointList {
+			service.MyService.Connections().UnmountSmaba(v.Path)
+		}
+
+		os.RemoveAll(baseHostPath)
+
+		file.IsNotExistMkDir(baseHostPath)
+		for _, v := range directories {
+			mountPoint := baseHostPath + "/" + v
+			file.IsNotExistMkDir(mountPoint)
+			service.MyService.Connections().MountSmaba(connection.Username, connection.Host, v, connection.Port, mountPoint, connection.Password)
+		}
+		connection.Directories = strings.Join(directories, ",")
+		service.MyService.Connections().UpdateConnection(&connection)
+	}
+}

+ 57 - 21
route/periodical.go

@@ -1,8 +1,11 @@
+//go:build !darwin
+// +build !darwin
+
 /*
  * @Author: LinkLeong link@icewhale.com
  * @Date: 2022-07-01 15:11:36
  * @LastEditors: LinkLeong
- * @LastEditTime: 2022-07-21 15:25:07
+ * @LastEditTime: 2022-08-12 18:58:00
  * @FilePath: /CasaOS/route/periodical.go
  * @Description:
  * @Website: https://www.casaos.io
@@ -11,14 +14,20 @@
 package route
 
 import (
+	"os"
+	"os/signal"
 	"reflect"
 	"strconv"
 	"strings"
+	"syscall"
 	"time"
 	"unsafe"
 
 	"github.com/IceWhaleTech/CasaOS/model"
+	"github.com/IceWhaleTech/CasaOS/pkg/utils/loger"
 	"github.com/IceWhaleTech/CasaOS/service"
+	"github.com/pilebones/go-udev/netlink"
+	"go.uber.org/zap"
 )
 
 func SendNetINfoBySocket() {
@@ -129,26 +138,22 @@ func SendUSBBySocket() {
 	usb := []model.DriveUSB{}
 	for _, v := range usbList {
 		if v.Tran == "usb" {
+			isMount := false
 			temp := model.DriveUSB{}
 			temp.Model = v.Model
 			temp.Name = v.Name
 			temp.Size = v.Size
-			mountTemp := true
-			if len(v.Children) == 0 {
-				mountTemp = false
-			}
 			for _, child := range v.Children {
 				if len(child.MountPoint) > 0 {
+					isMount = true
 					avail, _ := strconv.ParseUint(child.FSAvail, 10, 64)
 					temp.Avail += avail
-					used, _ := strconv.ParseUint(child.FSUsed, 10, 64)
-					temp.Used += used
-				} else {
-					mountTemp = false
+
 				}
 			}
-			temp.Mount = mountTemp
-			usb = append(usb, temp)
+			if isMount {
+				usb = append(usb, temp)
+			}
 		}
 	}
 	service.MyService.Notify().SendUSBInfoBySocket(usb)
@@ -246,26 +251,22 @@ func SendAllHardwareStatusBySocket() {
 	usb := []model.DriveUSB{}
 	for _, v := range usbList {
 		if v.Tran == "usb" {
+			isMount = false
 			temp := model.DriveUSB{}
 			temp.Model = v.Model
 			temp.Name = v.Name
 			temp.Size = v.Size
-			mountTemp := true
-			if len(v.Children) == 0 {
-				mountTemp = false
-			}
 			for _, child := range v.Children {
 				if len(child.MountPoint) > 0 {
+					isMount = true
 					avail, _ := strconv.ParseUint(child.FSAvail, 10, 64)
 					temp.Avail += avail
-					used, _ := strconv.ParseUint(child.FSUsed, 10, 64)
-					temp.Used += used
-				} else {
-					mountTemp = false
 				}
 			}
-			temp.Mount = mountTemp
-			usb = append(usb, temp)
+			if isMount {
+				usb = append(usb, temp)
+			}
+
 		}
 	}
 	memInfo := service.MyService.System().GetMemInfo()
@@ -273,3 +274,38 @@ func SendAllHardwareStatusBySocket() {
 	service.MyService.Notify().SendAllHardwareStatusBySocket(summary, usb, memInfo, cpuData, newNet)
 
 }
+func MonitoryUSB() {
+	var matcher netlink.Matcher
+
+	conn := new(netlink.UEventConn)
+	if err := conn.Connect(netlink.UdevEvent); err != nil {
+		loger.Error("udev err", zap.Any("Unable to connect to Netlink Kobject UEvent socket", err))
+	}
+	defer conn.Close()
+
+	queue := make(chan netlink.UEvent)
+	errors := make(chan error)
+	quit := conn.Monitor(queue, errors, matcher)
+
+	signals := make(chan os.Signal, 1)
+	signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
+	go func() {
+		<-signals
+		close(quit)
+		os.Exit(0)
+	}()
+
+	for {
+		select {
+		case uevent := <-queue:
+			if uevent.Env["DEVTYPE"] == "disk" {
+				time.Sleep(time.Microsecond * 500)
+				SendUSBBySocket()
+				continue
+			}
+		case err := <-errors:
+			loger.Error("udev err", zap.Any("err", err))
+		}
+	}
+
+}

+ 23 - 0
route/route.go

@@ -196,6 +196,9 @@ func InitRouter() *gin.Engine {
 
 			//v1DisksGroup.POST("", v1.PostMountDisk)
 			v1DisksGroup.GET("", v1.GetDiskList)
+			v1DisksGroup.GET("/usb", v1.GetDisksUSBList)
+			v1DisksGroup.DELETE("/usb", v1.DeleteDiskUSB)
+			v1DisksGroup.DELETE("", v1.DeleteDisksUmount)
 			// //format storage
 			// v1DiskGroup.POST("/format", v1.PostDiskFormat)
 
@@ -218,6 +221,26 @@ func InitRouter() *gin.Engine {
 			v1StorageGroup.PUT("", v1.PostDiskFormat)
 
 			v1StorageGroup.DELETE("", v1.PostDiskUmount)
+			v1StorageGroup.GET("", v1.GetStorageList)
+		}
+		v1SambaGroup := v1Group.Group("/samba")
+		v1SambaGroup.Use()
+		{
+			v1ConnectionsGroup := v1SambaGroup.Group("/connections")
+			v1ConnectionsGroup.Use()
+			{
+				v1ConnectionsGroup.GET("", v1.GetSambaConnectionsList)
+				v1ConnectionsGroup.POST("", v1.PostSambaConnectionsCreate)
+				v1ConnectionsGroup.DELETE("/:id", v1.DeleteSambaConnections)
+			}
+			v1SharesGroup := v1SambaGroup.Group("/shares")
+			v1SharesGroup.Use()
+			{
+				v1SharesGroup.GET("", v1.GetSambaSharesList)
+				v1SharesGroup.POST("", v1.PostSambaSharesCreate)
+				v1SharesGroup.DELETE("/:id", v1.DeleteSambaShares)
+				v1SharesGroup.GET("/status", v1.GetSambaStatus)
+			}
 		}
 	}
 	return r

+ 172 - 75
route/v1/disk.go

@@ -1,7 +1,6 @@
 package v1
 
 import (
-	"fmt"
 	"net/http"
 	"reflect"
 	"strconv"
@@ -45,19 +44,12 @@ func GetDiskList(c *gin.Context) {
 				temp.Model = v.Model
 				temp.Name = v.Name
 				temp.Size = v.Size
-				mountTemp := true
-				if len(v.Children) == 0 {
-					mountTemp = false
-				}
 				for _, child := range v.Children {
 					if len(child.MountPoint) > 0 {
 						avail, _ := strconv.ParseUint(child.FSAvail, 10, 64)
 						temp.Avail += avail
-					} else {
-						mountTemp = false
 					}
 				}
-				temp.Mount = mountTemp
 				data = append(data, temp)
 			}
 		}
@@ -87,14 +79,13 @@ func GetDiskList(c *gin.Context) {
 		disk.Size = list[i].Size
 		disk.Path = list[i].Path
 		disk.Model = list[i].Model
-
+		disk.ChildrenNumber = len(list[i].Children)
 		if len(list[i].Children) > 0 && findSystem == 0 {
 			for j := 0; j < len(list[i].Children); j++ {
 				if len(list[i].Children[j].Children) > 0 {
 					for _, v := range list[i].Children[j].Children {
 						if v.MountPoint == "/" {
 							stor := model.Storage{}
-							stor.Name = "System"
 							stor.MountPoint = v.MountPoint
 							stor.Size = v.FSSize
 							stor.Avail = v.FSAvail
@@ -118,7 +109,6 @@ func GetDiskList(c *gin.Context) {
 				} else {
 					if list[i].Children[j].MountPoint == "/" {
 						stor := model.Storage{}
-						stor.Name = "System"
 						stor.MountPoint = list[i].Children[j].MountPoint
 						stor.Size = list[i].Children[j].FSSize
 						stor.Avail = list[i].Children[j].FSAvail
@@ -152,33 +142,31 @@ func GetDiskList(c *gin.Context) {
 			if reflect.DeepEqual(temp, model.SmartctlA{}) {
 				temp.SmartStatus.Passed = true
 			}
-			if len(list[i].Children) == 1 && len(list[i].Children[0].MountPoint) > 0 {
-				stor := model.Storage{}
-				stor.MountPoint = list[i].Children[0].MountPoint
-				stor.Size = list[i].Children[0].FSSize
-				stor.Avail = list[i].Children[0].FSAvail
-				stor.Path = list[i].Children[0].Path
-				stor.Type = list[i].Children[0].FsType
-				stor.DriveName = list[i].Name
-				pathArr := strings.Split(list[i].Children[0].MountPoint, "/")
-				if len(pathArr) == 3 {
-					stor.Name = pathArr[2]
-				}
-				if t, ok := part[list[i].Children[0].MountPoint]; ok {
-					stor.CreatedAt = t
-				}
-				storage = append(storage, stor)
-			} else {
-				//todo   长度有问题
-				if len(list[i].Children) == 1 && list[i].Children[0].FsType == "ext4" {
-					disk.NeedFormat = false
-					avail = append(avail, disk)
-				} else {
-					disk.NeedFormat = true
-					avail = append(avail, disk)
+			isAvail := true
+			for _, v := range list[i].Children {
+				if v.MountPoint != "" {
+					stor := model.Storage{}
+					stor.MountPoint = v.MountPoint
+					stor.Size = v.FSSize
+					stor.Avail = v.FSAvail
+					stor.Path = v.Path
+					stor.Type = v.FsType
+					stor.DriveName = list[i].Name
+					storage = append(storage, stor)
+					isAvail = false
 				}
 			}
 
+			if isAvail {
+				//if len(list[i].Children) == 1 && list[i].Children[0].FsType == "ext4" {
+				disk.NeedFormat = false
+				avail = append(avail, disk)
+				// } else {
+				// 	disk.NeedFormat = true
+				// 	avail = append(avail, disk)
+				// }
+			}
+
 			disk.Temperature = temp.Temperature.Current
 			disk.Health = strconv.FormatBool(temp.SmartStatus.Passed)
 
@@ -193,6 +181,107 @@ func GetDiskList(c *gin.Context) {
 	c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: data})
 }
 
+// @Summary disk list
+// @Produce  application/json
+// @Accept application/json
+// @Tags disk
+// @Security ApiKeyAuth
+// @Success 200 {string} string "ok"
+// @Router /disk/list [get]
+func GetDisksUSBList(c *gin.Context) {
+	list := service.MyService.Disk().LSBLK(false)
+	data := []model.DriveUSB{}
+	for _, v := range list {
+		if v.Tran == "usb" {
+			temp := model.DriveUSB{}
+			temp.Model = v.Model
+			temp.Name = v.Label
+			if temp.Name == "" {
+				temp.Name = v.Name
+			}
+			temp.Size = v.Size
+			children := []model.USBChildren{}
+			for _, child := range v.Children {
+
+				if len(child.MountPoint) > 0 {
+					tempChildren := model.USBChildren{}
+					tempChildren.MountPoint = child.MountPoint
+					tempChildren.Size, _ = strconv.ParseUint(child.FSSize, 10, 64)
+					tempChildren.Avail, _ = strconv.ParseUint(child.FSAvail, 10, 64)
+					tempChildren.Name = child.Label
+					avail, _ := strconv.ParseUint(child.FSAvail, 10, 64)
+					children = append(children, tempChildren)
+					temp.Avail += avail
+				}
+			}
+
+			temp.Children = children
+			data = append(data, temp)
+		}
+	}
+	c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: data})
+
+}
+
+func DeleteDisksUmount(c *gin.Context) {
+	id := c.GetHeader("user_id")
+	js := make(map[string]string)
+	c.ShouldBind(&js)
+
+	path := js["path"]
+	pwd := js["password"]
+
+	if len(path) == 0 {
+		c.JSON(common_err.CLIENT_ERROR, model.Result{Success: common_err.INVALID_PARAMS, Message: common_err.GetMsg(common_err.INVALID_PARAMS)})
+		return
+	}
+	user := service.MyService.User().GetUserAllInfoById(id)
+	if user.Id == 0 {
+		c.JSON(common_err.SERVICE_ERROR, model.Result{Success: common_err.USER_NOT_EXIST, Message: common_err.GetMsg(common_err.USER_NOT_EXIST)})
+		return
+	}
+	if encryption.GetMD5ByStr(pwd) != user.Password {
+		c.JSON(common_err.CLIENT_ERROR, model.Result{Success: common_err.PWD_INVALID, Message: common_err.GetMsg(common_err.PWD_INVALID)})
+		return
+	}
+
+	if _, ok := diskMap[path]; ok {
+		c.JSON(common_err.SERVICE_ERROR, model.Result{Success: common_err.DISK_BUSYING, Message: common_err.GetMsg(common_err.DISK_BUSYING)})
+		return
+	}
+
+	diskInfo := service.MyService.Disk().GetDiskInfo(path)
+	for _, v := range diskInfo.Children {
+		service.MyService.Disk().UmountPointAndRemoveDir(v.Path)
+		//delete data
+		service.MyService.Disk().DeleteMountPoint(v.Path, v.MountPoint)
+	}
+
+	service.MyService.Disk().RemoveLSBLKCache()
+
+	//send notify to client
+	msg := notify.StorageMessage{}
+	msg.Action = "REMOVED"
+	msg.Path = path
+	msg.Volume = ""
+	msg.Size = 0
+	msg.Type = ""
+	service.MyService.Notify().SendStorageBySocket(msg)
+	c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: path})
+}
+
+func DeleteDiskUSB(c *gin.Context) {
+	js := make(map[string]string)
+	c.ShouldBind(&js)
+	mountPoint := js["mount_point"]
+	if file.CheckNotExist(mountPoint) {
+		c.JSON(common_err.CLIENT_ERROR, model.Result{Success: common_err.DIR_NOT_EXISTS, Message: common_err.GetMsg(common_err.DIR_NOT_EXISTS)})
+		return
+	}
+	service.MyService.Disk().UmountUSB(mountPoint)
+	c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: mountPoint})
+}
+
 // @Summary get disk list
 // @Produce  application/json
 // @Accept application/json
@@ -278,10 +367,10 @@ func PostDiskAddPartition(c *gin.Context) {
 	js := make(map[string]interface{})
 	c.ShouldBind(&js)
 	path := js["path"].(string)
-	name := js["name"].(string)
+	//name := js["name"].(string)
 	format := js["format"].(bool)
 
-	if len(name) == 0 || len(path) == 0 {
+	if len(path) == 0 {
 		c.JSON(common_err.CLIENT_ERROR, model.Result{Success: common_err.INVALID_PARAMS, Message: common_err.GetMsg(common_err.INVALID_PARAMS)})
 		return
 	}
@@ -289,20 +378,17 @@ func PostDiskAddPartition(c *gin.Context) {
 		c.JSON(common_err.SERVICE_ERROR, model.Result{Success: common_err.DISK_BUSYING, Message: common_err.GetMsg(common_err.DISK_BUSYING)})
 		return
 	}
-	if !file.CheckNotExist("/DATA/" + name) {
-		// /mnt/name exist
-		c.JSON(common_err.SERVICE_ERROR, model.Result{Success: common_err.NAME_NOT_AVAILABLE, Message: common_err.GetMsg(common_err.NAME_NOT_AVAILABLE)})
-		return
-	}
+
+	//diskInfo := service.MyService.Disk().GetDiskInfo(path)
+
+	// if !file.CheckNotExist("/DATA/" + name) {
+	// 	// /mnt/name exist
+	// 	c.JSON(common_err.SERVICE_ERROR, model.Result{Success: common_err.NAME_NOT_AVAILABLE, Message: common_err.GetMsg(common_err.NAME_NOT_AVAILABLE)})
+	// 	return
+	// }
 	diskMap[path] = "busying"
 	currentDisk := service.MyService.Disk().GetDiskInfo(path)
-	if !format {
-		if len(currentDisk.Children) != 1 || !(len(currentDisk.Children) > 0 && currentDisk.Children[0].FsType == "ext4") {
-			delete(diskMap, path)
-			c.JSON(common_err.SERVICE_ERROR, model.Result{Success: common_err.DISK_NEEDS_FORMAT, Message: common_err.GetMsg(common_err.DISK_NEEDS_FORMAT)})
-			return
-		}
-	} else {
+	if format {
 		// format := service.MyService.Disk().FormatDisk(path+"1", "ext4")
 		// if len(format) == 0 {
 		// 	delete(diskMap, path)
@@ -312,34 +398,45 @@ func PostDiskAddPartition(c *gin.Context) {
 		service.MyService.Disk().AddPartition(path)
 	}
 
-	formatBool := true
-	for formatBool {
-		currentDisk = service.MyService.Disk().GetDiskInfo(path)
-		fmt.Println(currentDisk.Children)
-		if len(currentDisk.Children) > 0 {
-			formatBool = false
-			break
-		}
-		time.Sleep(time.Second)
-	}
+	// formatBool := true
+	// for formatBool {
+	// 	currentDisk = service.MyService.Disk().GetDiskInfo(path)
+	// 	if len(currentDisk.Children) > 0 {
+	// 		formatBool = false
+	// 		break
+	// 	}
+	// 	time.Sleep(time.Second)
+	// }
 	currentDisk = service.MyService.Disk().GetDiskInfo(path)
-	if len(currentDisk.Children) != 1 {
-		c.JSON(common_err.SERVICE_ERROR, model.Result{Success: common_err.DISK_NEEDS_FORMAT, Message: common_err.GetMsg(common_err.DISK_NEEDS_FORMAT)})
-		return
+	// if len(currentDisk.Children) != 1 {
+	// 	c.JSON(common_err.SERVICE_ERROR, model.Result{Success: common_err.DISK_NEEDS_FORMAT, Message: common_err.GetMsg(common_err.DISK_NEEDS_FORMAT)})
+	// 	return
+	// }
+	for i := 0; i < len(currentDisk.Children); i++ {
+		childrenName := currentDisk.Children[i].Label
+		if len(childrenName) == 0 {
+			childrenName = "Storage_" + currentDisk.Children[i].Name
+		}
+		mountPath := "/DATA/" + childrenName
+		if !file.CheckNotExist(mountPath) {
+			ls := service.MyService.System().GetDirPath(mountPath)
+			if len(ls) > 0 {
+				// exist
+				c.JSON(common_err.SERVICE_ERROR, model.Result{Success: common_err.NAME_NOT_AVAILABLE, Message: common_err.GetMsg(common_err.NAME_NOT_AVAILABLE)})
+				return
+			}
+		}
+		m := model2.SerialDisk{}
+		m.MountPoint = mountPath
+		m.Path = currentDisk.Children[i].Path
+		m.UUID = currentDisk.Children[i].UUID
+		m.State = 0
+		m.CreatedAt = time.Now().Unix()
+		service.MyService.Disk().SaveMountPoint(m)
+		//mount dir
+		service.MyService.Disk().MountDisk(currentDisk.Children[i].Path, mountPath)
 	}
 
-	mountPath := "/DATA/" + name
-	m := model2.SerialDisk{}
-	m.MountPoint = mountPath
-	m.Path = currentDisk.Children[0].Path
-	m.UUID = currentDisk.Children[0].UUID
-	m.State = 0
-	m.CreatedAt = time.Now().Unix()
-	service.MyService.Disk().SaveMountPoint(m)
-
-	//mount dir
-	service.MyService.Disk().MountDisk(currentDisk.Children[0].Path, mountPath)
-
 	service.MyService.Disk().RemoveLSBLKCache()
 
 	delete(diskMap, path)
@@ -348,7 +445,7 @@ func PostDiskAddPartition(c *gin.Context) {
 	msg := notify.StorageMessage{}
 	msg.Action = "ADDED"
 	msg.Path = currentDisk.Children[0].Path
-	msg.Volume = mountPath
+	msg.Volume = "/DATA/"
 	msg.Size = currentDisk.Children[0].Size
 	msg.Type = currentDisk.Children[0].Tran
 	service.MyService.Notify().SendStorageBySocket(msg)

+ 4 - 5
route/v1/docker.go

@@ -209,7 +209,7 @@ func InstallApp(c *gin.Context) {
 		dockerImage = m.Image
 		dockerImageVersion = "latest"
 	}
-
+	m.Image = dockerImage + ":" + dockerImageVersion
 	for _, u := range m.Ports {
 
 		if u.Protocol == "udp" {
@@ -334,11 +334,11 @@ func InstallApp(c *gin.Context) {
 			return
 		}
 
-		for !service.MyService.Docker().IsExistImage(dockerImage + ":" + dockerImageVersion) {
+		for !service.MyService.Docker().IsExistImage(m.Image) {
 			time.Sleep(time.Second)
 		}
 
-		_, err = service.MyService.Docker().DockerContainerCreate(dockerImage+":"+dockerImageVersion, m)
+		_, err = service.MyService.Docker().DockerContainerCreate(m, "")
 		if err != nil {
 			//service.MyService.Redis().Set(id, "{\"id\"\""+id+"\",\"state\":false,\"message\":\""+err.Error()+"\",\"speed\":80}", 100)
 			notify := notify.Application{}
@@ -829,7 +829,6 @@ func UpdateSetting(c *gin.Context) {
 	// 	c.JSON(http.StatusOK, model.Result{Success: common_err.ERROR_APP_NAME_EXIST, Message: common_err.GetMsg(common_err.ERROR_APP_NAME_EXIST)})
 	// 	return
 	// }
-
 	service.MyService.Docker().DockerContainerStop(id)
 	portMap, _ := strconv.Atoi(m.PortMap)
 	if !port2.IsPortAvailable(portMap, "tcp") {
@@ -874,7 +873,7 @@ func UpdateSetting(c *gin.Context) {
 	service.MyService.Docker().DockerContainerUpdateName(id, id)
 	//service.MyService.Docker().DockerContainerRemove(id, true)
 
-	containerId, err := service.MyService.Docker().DockerContainerCreate(m.Image, m)
+	containerId, err := service.MyService.Docker().DockerContainerCreate(m, id)
 	if err != nil {
 		service.MyService.Docker().DockerContainerUpdateName(m.ContainerName, id)
 		service.MyService.Docker().DockerContainerStart(id)

+ 17 - 1
route/v1/file.go

@@ -1,6 +1,7 @@
 package v1
 
 import (
+	"fmt"
 	"io"
 	"io/ioutil"
 	"log"
@@ -218,6 +219,11 @@ func GetDownloadSingleFile(c *gin.Context) {
 func DirPath(c *gin.Context) {
 	path := c.DefaultQuery("path", "")
 	info := service.MyService.System().GetDirPath(path)
+	shares := service.MyService.Shares().GetSharesList()
+	sharesMap := make(map[string]string)
+	for _, v := range shares {
+		sharesMap[v.Path] = fmt.Sprint(v.ID)
+	}
 	if path == "/DATA/AppData" {
 		list := service.MyService.Docker().DockerContainerList()
 		apps := make(map[string]string, len(list))
@@ -257,7 +263,17 @@ func DirPath(c *gin.Context) {
 			}
 		}
 	}
-
+	for i := 0; i < len(info); i++ {
+		if v, ok := sharesMap[info[i].Path]; ok {
+			ex := make(map[string]interface{})
+
+			shareEx := make(map[string]string)
+			shareEx["shared"] = "true"
+			shareEx["id"] = v
+			ex["share"] = shareEx
+			info[i].Extensions = ex
+		}
+	}
 	//Hide the files or folders in operation
 	fileQueue := make(map[string]string)
 	if len(service.OpStrArr) > 0 {

+ 180 - 0
route/v1/samba.go

@@ -0,0 +1,180 @@
+/*
+ * @Author: LinkLeong link@icewhale.com
+ * @Date: 2022-07-26 11:08:48
+ * @LastEditors: LinkLeong
+ * @LastEditTime: 2022-08-05 12:16:39
+ * @FilePath: /CasaOS/route/v1/samba.go
+ * @Description:
+ * @Website: https://www.casaos.io
+ * Copyright (c) 2022 by icewhale, All Rights Reserved.
+ */
+package v1
+
+import (
+	"fmt"
+	"os"
+	"path/filepath"
+	"strings"
+
+	"github.com/IceWhaleTech/CasaOS/model"
+	"github.com/IceWhaleTech/CasaOS/pkg/samba"
+	"github.com/IceWhaleTech/CasaOS/pkg/utils/common_err"
+	"github.com/IceWhaleTech/CasaOS/pkg/utils/file"
+	"github.com/IceWhaleTech/CasaOS/service"
+	model2 "github.com/IceWhaleTech/CasaOS/service/model"
+	"github.com/gin-gonic/gin"
+)
+
+// service
+
+func GetSambaStatus(c *gin.Context) {
+	status := service.MyService.System().IsServiceRunning("smbd")
+
+	if !status {
+		c.JSON(common_err.SERVICE_ERROR, model.Result{Success: common_err.SERVICE_NOT_RUNNING, Message: common_err.GetMsg(common_err.SERVICE_NOT_RUNNING)})
+		return
+	}
+	needInit := true
+	if file.Exists("/etc/samba/smb.conf") {
+		str := file.ReadLine(1, "/etc/samba/smb.conf")
+		if strings.Contains(str, "# Copyright (c) 2021-2022 CasaOS Inc. All rights reserved.") {
+			needInit = false
+		}
+	}
+	data := make(map[string]string, 1)
+	data["need_init"] = fmt.Sprintf("%v", needInit)
+	c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: data})
+}
+
+func GetSambaSharesList(c *gin.Context) {
+	shares := service.MyService.Shares().GetSharesList()
+	shareList := []model.Shares{}
+	for _, v := range shares {
+		shareList = append(shareList, model.Shares{
+			Anonymous: v.Anonymous,
+			Path:      v.Path,
+			ID:        v.ID,
+		})
+	}
+	c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: shareList})
+}
+
+func PostSambaSharesCreate(c *gin.Context) {
+	shares := []model.Shares{}
+	c.ShouldBindJSON(&shares)
+	for _, v := range shares {
+		if v.Path == "" {
+			c.JSON(common_err.CLIENT_ERROR, model.Result{Success: common_err.INSUFFICIENT_PERMISSIONS, Message: common_err.GetMsg(common_err.INSUFFICIENT_PERMISSIONS)})
+			return
+		}
+		if !file.Exists(v.Path) {
+			c.JSON(common_err.SERVICE_ERROR, model.Result{Success: common_err.DIR_NOT_EXISTS, Message: common_err.GetMsg(common_err.DIR_NOT_EXISTS)})
+			return
+		}
+		if len(service.MyService.Shares().GetSharesByPath(v.Path)) > 0 {
+			c.JSON(common_err.CLIENT_ERROR, model.Result{Success: common_err.SHARE_ALREADY_EXISTS, Message: common_err.GetMsg(common_err.SHARE_ALREADY_EXISTS)})
+			return
+		}
+		if len(service.MyService.Shares().GetSharesByPath(filepath.Base(v.Path))) > 0 {
+			c.JSON(common_err.CLIENT_ERROR, model.Result{Success: common_err.SHARE_NAME_ALREADY_EXISTS, Message: common_err.GetMsg(common_err.SHARE_NAME_ALREADY_EXISTS)})
+			return
+		}
+	}
+	for _, v := range shares {
+		shareDBModel := model2.SharesDBModel{}
+		shareDBModel.Anonymous = true
+		shareDBModel.Path = v.Path
+		shareDBModel.Name = filepath.Base(v.Path)
+		service.MyService.Shares().CreateShare(shareDBModel)
+	}
+
+	c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: shares})
+}
+func DeleteSambaShares(c *gin.Context) {
+	id := c.Param("id")
+	if id == "" {
+		c.JSON(common_err.CLIENT_ERROR, model.Result{Success: common_err.INSUFFICIENT_PERMISSIONS, Message: common_err.GetMsg(common_err.INSUFFICIENT_PERMISSIONS)})
+		return
+	}
+	service.MyService.Shares().DeleteShare(id)
+	c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: id})
+}
+
+//client
+
+func GetSambaConnectionsList(c *gin.Context) {
+	connections := service.MyService.Connections().GetConnectionsList()
+	connectionList := []model.Connections{}
+	for _, v := range connections {
+		connectionList = append(connectionList, model.Connections{
+			ID:         v.ID,
+			Username:   v.Username,
+			Port:       v.Port,
+			Host:       v.Host,
+			MountPoint: v.MountPoint,
+		})
+	}
+	c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: connectionList})
+}
+
+func PostSambaConnectionsCreate(c *gin.Context) {
+	connection := model.Connections{}
+	c.ShouldBindJSON(&connection)
+	if connection.Port == "" {
+		connection.Port = "445"
+	}
+	if connection.Username == "" || connection.Host == "" {
+		c.JSON(common_err.CLIENT_ERROR, model.Result{Success: common_err.INVALID_PARAMS, Message: common_err.GetMsg(common_err.INVALID_PARAMS)})
+		return
+	}
+	// check is exists
+
+	connections := service.MyService.Connections().GetConnectionByHost(connection.Host)
+	if len(connections) > 0 {
+		c.JSON(common_err.SERVICE_ERROR, model.Result{Success: common_err.Record_ALREADY_EXIST, Message: common_err.GetMsg(common_err.Record_ALREADY_EXIST), Data: common_err.GetMsg(common_err.Record_ALREADY_EXIST)})
+		return
+	}
+	// check connect is ok
+	directories, err := samba.GetSambaSharesList(connection.Host, connection.Port, connection.Username, connection.Password)
+	if err != nil {
+		c.JSON(common_err.SERVICE_ERROR, model.Result{Success: common_err.SERVICE_ERROR, Message: common_err.GetMsg(common_err.SERVICE_ERROR), Data: err.Error()})
+		return
+	}
+
+	connectionDBModel := model2.ConnectionsDBModel{}
+	connectionDBModel.Username = connection.Username
+	connectionDBModel.Password = connection.Password
+	connectionDBModel.Host = connection.Host
+	connectionDBModel.Port = connection.Port
+	connectionDBModel.Directories = strings.Join(directories, ",")
+	baseHostPath := "/mnt/" + connection.Host
+	connectionDBModel.MountPoint = baseHostPath
+	connection.MountPoint = baseHostPath
+	file.IsNotExistMkDir(baseHostPath)
+	for _, v := range directories {
+		mountPoint := baseHostPath + "/" + v
+		file.IsNotExistMkDir(mountPoint)
+		service.MyService.Connections().MountSmaba(connectionDBModel.Username, connectionDBModel.Host, v, connectionDBModel.Port, mountPoint, connectionDBModel.Password)
+	}
+
+	service.MyService.Connections().CreateConnection(&connectionDBModel)
+
+	connection.ID = connectionDBModel.ID
+	c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: connection})
+}
+
+func DeleteSambaConnections(c *gin.Context) {
+	id := c.Param("id")
+	connection := service.MyService.Connections().GetConnectionByID(id)
+	if connection.Username == "" {
+		c.JSON(common_err.CLIENT_ERROR, model.Result{Success: common_err.Record_NOT_EXIST, Message: common_err.GetMsg(common_err.Record_NOT_EXIST)})
+		return
+	}
+	mountPointList := service.MyService.System().GetDirPath(connection.MountPoint)
+	for _, v := range mountPointList {
+		service.MyService.Connections().UnmountSmaba(v.Path)
+	}
+	os.RemoveAll(connection.MountPoint)
+	service.MyService.Connections().DeleteConnection(id)
+	c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: id})
+}

+ 73 - 0
route/v1/samba_test.go

@@ -0,0 +1,73 @@
+/*
+ * @Author: LinkLeong link@icewhale.org
+ * @Date: 2022-08-02 15:10:56
+ * @LastEditors: LinkLeong
+ * @LastEditTime: 2022-08-02 16:58:42
+ * @FilePath: /CasaOS/route/v1/samba_test.go
+ * @Description:
+ * @Website: https://www.casaos.io
+ * Copyright (c) 2022 by icewhale, All Rights Reserved.
+ */
+package v1
+
+import (
+	"net/http"
+	"net/http/httptest"
+	"testing"
+
+	"github.com/gin-gonic/gin"
+	"github.com/golang/mock/gomock"
+	"github.com/stretchr/testify/assert"
+)
+
+func performRequest(r http.Handler, method, path string) *httptest.ResponseRecorder {
+	req, _ := http.NewRequest(method, path, nil)
+	w := httptest.NewRecorder()
+	r.ServeHTTP(w, req)
+	return w
+}
+
+// func TestHelloWorld(t *testing.T) {
+// 	// Build our expected body
+// 	body := gin.H{
+// 		"hello": "world",
+// 	}
+// 	// Grab our router
+// 	router := "SetupRouter()"
+// 	// Perform a GET request with that handler.
+// 	w := performRequest(router, "GET", "/")
+// 	// Assert we encoded correctly,
+// 	// the request gives a 200
+// 	assert.Equal(t, http.StatusOK, w.Code)
+// 	// Convert the JSON response to a map
+// 	var response map[string]string
+// 	err := json.Unmarshal([]byte(w.Body.String()), &response)
+// 	// Grab the value & whether or not it exists
+// 	value, exists := response["hello"]
+// 	// Make some assertions on the correctness of the response.
+// 	assert.Nil(t, err)
+// 	assert.True(t, exists)
+// 	assert.Equal(t, body["hello"], value)
+// }
+
+func TestGetSambaSharesList(t *testing.T) {
+	gin.SetMode(gin.TestMode)
+	ctrl := gomock.NewController(t)
+	defer ctrl.Finish()
+	executeWithContext := func() *httptest.ResponseRecorder {
+		response := httptest.NewRecorder()
+		con, ginEngine := gin.CreateTestContext(response)
+
+		requestUrl := "/v1/samba/shares"
+		httpRequest, _ := http.NewRequest("GET", requestUrl, nil)
+		GetSambaSharesList(con)
+		ginEngine.ServeHTTP(response, httpRequest)
+		return response
+	}
+
+	t.Run("Happy", func(t *testing.T) {
+		res := executeWithContext()
+		assert.Equal(t, http.StatusOK, res.Code)
+	})
+
+}

+ 89 - 2
route/v1/storage.go

@@ -2,7 +2,7 @@
  * @Author: LinkLeong link@icewhale.com
  * @Date: 2022-07-11 16:02:29
  * @LastEditors: LinkLeong
- * @LastEditTime: 2022-07-11 16:02:55
+ * @LastEditTime: 2022-08-11 14:20:02
  * @FilePath: /CasaOS/route/v1/storage.go
  * @Description:
  * @Website: https://www.casaos.io
@@ -10,8 +10,95 @@
  */
 package v1
 
-import "github.com/gin-gonic/gin"
+import (
+	"reflect"
+	"strconv"
+
+	"github.com/IceWhaleTech/CasaOS/model"
+	"github.com/IceWhaleTech/CasaOS/pkg/utils/common_err"
+	"github.com/IceWhaleTech/CasaOS/service"
+	"github.com/gin-gonic/gin"
+)
 
 func GetStorageList(c *gin.Context) {
+	system := c.Query("system")
+	storages := []model.Storages{}
+	disks := service.MyService.Disk().LSBLK(false)
+	diskNumber := 1
+	children := 1
+	findSystem := 0
+	for _, d := range disks {
+		if d.Tran != "usb" {
+			tempSystemDisk := false
+			children = 1
+			tempDisk := model.Storages{
+				DiskName: d.Model,
+				Path:     d.Path,
+				Size:     d.Size,
+			}
+
+			storageArr := []model.Storage{}
+			temp := service.MyService.Disk().SmartCTL(d.Path)
+			if reflect.DeepEqual(temp, model.SmartctlA{}) {
+				temp.SmartStatus.Passed = true
+			}
+			for _, v := range d.Children {
+				if v.MountPoint != "" {
+					if findSystem == 0 {
+						if v.MountPoint == "/" {
+							tempDisk.DiskName = "System"
+							findSystem = 1
+							tempSystemDisk = true
+						}
+						if len(v.Children) > 0 {
+							for _, c := range v.Children {
+								if c.MountPoint == "/" {
+									tempDisk.DiskName = "System"
+									findSystem = 1
+									tempSystemDisk = true
+									break
+								}
+							}
+						}
+					}
+
+					stor := model.Storage{}
+					stor.MountPoint = v.MountPoint
+					stor.Size = v.FSSize
+					stor.Avail = v.FSAvail
+					stor.Path = v.Path
+					stor.Type = v.FsType
+					stor.DriveName = v.Name
+					if len(v.Label) == 0 {
+						stor.Label = "Storage" + strconv.Itoa(diskNumber) + "_" + strconv.Itoa(children)
+						children += 1
+					} else {
+						stor.Label = v.Label
+					}
+					storageArr = append(storageArr, stor)
+				}
+			}
+
+			if len(storageArr) > 0 {
+				if tempSystemDisk && len(system) > 0 {
+					tempStorageArr := []model.Storage{}
+					for i := 0; i < len(storageArr); i++ {
+						if storageArr[i].MountPoint != "/boot/efi" && storageArr[i].Type != "swap" {
+							tempStorageArr = append(tempStorageArr, storageArr[i])
+						}
+					}
+					tempDisk.Children = tempStorageArr
+					storages = append(storages, tempDisk)
+					diskNumber += 1
+				} else if !tempSystemDisk {
+					tempDisk.Children = storageArr
+					storages = append(storages, tempDisk)
+					diskNumber += 1
+				}
+
+			}
+		}
+	}
 
+	c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: storages})
 }

+ 50 - 10
route/v1/system.go

@@ -174,7 +174,7 @@ func PostKillCasaOS(c *gin.Context) {
 func PutSystemUSBAutoMount(c *gin.Context) {
 	js := make(map[string]string)
 	c.ShouldBind(&js)
-	status := js["status"]
+	status := js["state"]
 	if status == "on" {
 		service.MyService.System().UpdateUSBAutoMount("True")
 		service.MyService.System().ExecUSBAutoMountShell("True")
@@ -182,7 +182,31 @@ func PutSystemUSBAutoMount(c *gin.Context) {
 		service.MyService.System().UpdateUSBAutoMount("False")
 		service.MyService.System().ExecUSBAutoMountShell("False")
 	}
+	go func() {
+		usbList := service.MyService.Disk().LSBLK(false)
+		usb := []model.DriveUSB{}
+		for _, v := range usbList {
+			if v.Tran == "usb" {
+				isMount := false
+				temp := model.DriveUSB{}
+				temp.Model = v.Model
+				temp.Name = v.Name
+				temp.Size = v.Size
+				for _, child := range v.Children {
+					if len(child.MountPoint) > 0 {
+						isMount = true
+						avail, _ := strconv.ParseUint(child.FSAvail, 10, 64)
+						temp.Avail += avail
 
+					}
+				}
+				if isMount {
+					usb = append(usb, temp)
+				}
+			}
+		}
+		service.MyService.Notify().SendUSBInfoBySocket(usb)
+	}()
 	c.JSON(common_err.SUCCESS,
 		model.Result{
 			Success: common_err.SUCCESS,
@@ -202,7 +226,31 @@ func GetSystemUSBAutoMount(c *gin.Context) {
 	if config.ServerInfo.USBAutoMount == "False" {
 		state = "False"
 	}
+	go func() {
+		usbList := service.MyService.Disk().LSBLK(false)
+		usb := []model.DriveUSB{}
+		for _, v := range usbList {
+			if v.Tran == "usb" {
+				isMount := false
+				temp := model.DriveUSB{}
+				temp.Model = v.Model
+				temp.Name = v.Name
+				temp.Size = v.Size
+				for _, child := range v.Children {
+					if len(child.MountPoint) > 0 {
+						isMount = true
+						avail, _ := strconv.ParseUint(child.FSAvail, 10, 64)
+						temp.Avail += avail
 
+					}
+				}
+				if isMount {
+					usb = append(usb, temp)
+				}
+			}
+		}
+		service.MyService.Notify().SendUSBInfoBySocket(usb)
+	}()
 	c.JSON(common_err.SUCCESS,
 		model.Result{
 			Success: common_err.SUCCESS,
@@ -354,21 +402,13 @@ func GetSystemUtilization(c *gin.Context) {
 			temp.Model = v.Model
 			temp.Name = v.Name
 			temp.Size = v.Size
-			mountTemp := true
-			if len(v.Children) == 0 {
-				mountTemp = false
-			}
+
 			for _, child := range v.Children {
 				if len(child.MountPoint) > 0 {
 					avail, _ := strconv.ParseUint(child.FSAvail, 10, 64)
 					temp.Avail += avail
-					used, _ := strconv.ParseUint(child.FSUsed, 10, 64)
-					temp.Used += used
-				} else {
-					mountTemp = false
 				}
 			}
-			temp.Mount = mountTemp
 			usb = append(usb, temp)
 		}
 	}

+ 69 - 0
service/connections.go

@@ -0,0 +1,69 @@
+/*
+ * @Author: LinkLeong link@icewhale.org
+ * @Date: 2022-07-26 18:13:22
+ * @LastEditors: LinkLeong
+ * @LastEditTime: 2022-08-04 20:10:31
+ * @FilePath: /CasaOS/service/connections.go
+ * @Description:
+ * @Website: https://www.casaos.io
+ * Copyright (c) 2022 by icewhale, All Rights Reserved.
+ */
+package service
+
+import (
+	"github.com/IceWhaleTech/CasaOS/pkg/config"
+	command2 "github.com/IceWhaleTech/CasaOS/pkg/utils/command"
+	"github.com/IceWhaleTech/CasaOS/service/model"
+	model2 "github.com/IceWhaleTech/CasaOS/service/model"
+	"gorm.io/gorm"
+)
+
+type ConnectionsService interface {
+	GetConnectionsList() (connections []model2.ConnectionsDBModel)
+	GetConnectionByHost(host string) (connections []model2.ConnectionsDBModel)
+	GetConnectionByID(id string) (connections model2.ConnectionsDBModel)
+	CreateConnection(connection *model2.ConnectionsDBModel)
+	DeleteConnection(id string)
+	UpdateConnection(connection *model2.ConnectionsDBModel)
+	MountSmaba(username, host, directory, port, mountPoint, password string) string
+	UnmountSmaba(mountPoint string) string
+}
+
+type connectionsStruct struct {
+	db *gorm.DB
+}
+
+func (s *connectionsStruct) GetConnectionByHost(host string) (connections []model2.ConnectionsDBModel) {
+	s.db.Select("username,host,status,id").Where("host = ?", host).Find(&connections)
+	return
+}
+func (s *connectionsStruct) GetConnectionByID(id string) (connections model2.ConnectionsDBModel) {
+	s.db.Select("username,password,host,status,id,directories,mount_point,port").Where("id = ?", id).First(&connections)
+	return
+}
+func (s *connectionsStruct) GetConnectionsList() (connections []model2.ConnectionsDBModel) {
+	s.db.Select("username,host,port,status,id,mount_point").Find(&connections)
+	return
+}
+func (s *connectionsStruct) CreateConnection(connection *model2.ConnectionsDBModel) {
+	s.db.Create(connection)
+}
+func (s *connectionsStruct) UpdateConnection(connection *model2.ConnectionsDBModel) {
+	s.db.Save(connection)
+}
+func (s *connectionsStruct) DeleteConnection(id string) {
+	s.db.Where("id= ?", id).Delete(&model.ConnectionsDBModel{})
+}
+
+func (s *connectionsStruct) MountSmaba(username, host, directory, port, mountPoint, password string) string {
+	str := command2.ExecResultStr("source " + config.AppInfo.ShellPath + "/helper.sh ;MountCIFS " + username + " " + host + " " + directory + " " + port + " " + mountPoint + " " + password)
+	return str
+}
+func (s *connectionsStruct) UnmountSmaba(mountPoint string) string {
+	str := command2.ExecResultStr("source " + config.AppInfo.ShellPath + "/helper.sh ;UMountPorintAndRemoveDir " + mountPoint)
+	return str
+}
+
+func NewConnectionsService(db *gorm.DB) ConnectionsService {
+	return &connectionsStruct{db: db}
+}

+ 7 - 1
service/disk.go

@@ -36,6 +36,7 @@ type DiskService interface {
 	DeleteMount(id string)
 	UpdateMountPoint(m model2.SerialDisk)
 	RemoveLSBLKCache()
+	UmountUSB(path string)
 }
 type diskService struct {
 	db *gorm.DB
@@ -45,6 +46,10 @@ func (d *diskService) RemoveLSBLKCache() {
 	key := "system_lsblk"
 	Cache.Delete(key)
 }
+func (d *diskService) UmountUSB(path string) {
+	r := command2.ExecResultStr("source " + config.AppInfo.ShellPath + "/helper.sh ;UDEVILUmount " + path)
+	fmt.Println(r)
+}
 func (d *diskService) SmartCTL(path string) model.SmartctlA {
 
 	key := "system_smart_" + path
@@ -243,8 +248,9 @@ func (d *diskService) GetDiskInfo(path string) model.LSBLKModel {
 }
 
 func (d *diskService) MountDisk(path, volume string) {
+	//fmt.Println("source " + config.AppInfo.ShellPath + "/helper.sh ;do_mount " + path + " " + volume)
 	r := command2.ExecResultStr("source " + config.AppInfo.ShellPath + "/helper.sh ;do_mount " + path + " " + volume)
-	fmt.Print(r)
+	fmt.Println(r)
 }
 
 func (d *diskService) SaveMountPoint(m model2.SerialDisk) {

+ 31 - 12
service/docker.go

@@ -45,7 +45,7 @@ import (
 type DockerService interface {
 	DockerPullImage(imageName string, icon, name string) error
 	IsExistImage(imageName string) bool
-	DockerContainerCreate(imageName string, m model.CustomizationPostData) (containerId string, err error)
+	DockerContainerCreate(m model.CustomizationPostData, id string) (containerId string, err error)
 	DockerContainerCopyCreate(info *types.ContainerJSON) (containerId string, err error)
 	DockerContainerStart(name string) error
 	DockerContainerStats(name string) (string, error)
@@ -376,7 +376,7 @@ func (ds *dockerService) DockerContainerCopyCreate(info *types.ContainerJSON) (c
 //param mapPort 容器主端口映射到外部的端口
 //param tcp 容器其他tcp端口
 //param udp 容器其他udp端口
-func (ds *dockerService) DockerContainerCreate(imageName string, m model.CustomizationPostData) (containerId string, err error) {
+func (ds *dockerService) DockerContainerCreate(m model.CustomizationPostData, id string) (containerId string, err error) {
 	if len(m.NetworkModel) == 0 {
 		m.NetworkModel = "bridge"
 	}
@@ -385,6 +385,7 @@ func (ds *dockerService) DockerContainerCreate(imageName string, m model.Customi
 	if err != nil {
 		return "", err
 	}
+
 	defer cli.Close()
 	ports := make(nat.PortSet)
 	portMaps := make(nat.PortMap)
@@ -523,15 +524,26 @@ func (ds *dockerService) DockerContainerCreate(imageName string, m model.Customi
 	if len(m.HostName) == 0 {
 		m.HostName = m.Label
 	}
-	config := &container.Config{
-		Image:  imageName,
-		Labels: map[string]string{"origin": m.Origin, m.Origin: m.Origin, "casaos": "casaos"},
-		Env:    envArr,
-		//	Healthcheck: health,
-		Hostname: m.HostName,
-		Cmd:      m.Cmd,
+
+	info, err := cli.ContainerInspect(context.Background(), id)
+	hostConfig := &container.HostConfig{}
+	config := &container.Config{}
+	config.Labels = map[string]string{}
+	if err == nil {
+		// info.HostConfig = &container.HostConfig{}
+		// info.Config = &container.Config{}
+		// info.NetworkSettings = &types.NetworkSettings{}
+		hostConfig = info.HostConfig
+		config = info.Config
 	}
 
+	config.Cmd = m.Cmd
+	config.Image = m.Image
+	config.Env = envArr
+	config.Hostname = m.HostName
+	config.ExposedPorts = ports
+	config.Labels["origin"] = m.Origin
+	config.Labels["casaos"] = "casaos"
 	config.Labels["web"] = m.PortMap
 	config.Labels["icon"] = m.Icon
 	config.Labels["desc"] = m.Description
@@ -541,12 +553,19 @@ func (ds *dockerService) DockerContainerCreate(imageName string, m model.Customi
 	config.Labels["protocol"] = m.Protocol
 	config.Labels["host"] = m.Host
 	config.Labels["name"] = m.Label
-	hostConfig := &container.HostConfig{Resources: res, Mounts: volumes, RestartPolicy: rp, NetworkMode: container.NetworkMode(m.NetworkModel), Privileged: m.Privileged, CapAdd: m.CapAdd}
+	//container, err := cli.ContainerCreate(context.Background(), info.Config, info.HostConfig, &network.NetworkingConfig{info.NetworkSettings.Networks}, nil, info.Name)
+
+	hostConfig.Mounts = volumes
+	hostConfig.Privileged = m.Privileged
+	hostConfig.CapAdd = m.CapAdd
+	hostConfig.NetworkMode = container.NetworkMode(m.NetworkModel)
+	hostConfig.RestartPolicy = rp
+	hostConfig.Resources = res
+	//hostConfig := &container.HostConfig{Resources: res, Mounts: volumes, RestartPolicy: rp, NetworkMode: , Privileged: m.Privileged, CapAdd: m.CapAdd}
 	//if net != "host" {
-	config.ExposedPorts = ports
+
 	hostConfig.PortBindings = portMaps
 	//}
-
 	containerDb, err := cli.ContainerCreate(context.Background(),
 		config,
 		hostConfig,

+ 28 - 0
service/model/o_connections.go

@@ -0,0 +1,28 @@
+/*
+ * @Author: LinkLeong link@icewhale.org
+ * @Date: 2022-07-26 17:17:57
+ * @LastEditors: LinkLeong
+ * @LastEditTime: 2022-08-01 17:08:08
+ * @FilePath: /CasaOS/service/model/o_connections.go
+ * @Description:
+ * @Website: https://www.casaos.io
+ * Copyright (c) 2022 by icewhale, All Rights Reserved.
+ */
+package model
+
+type ConnectionsDBModel struct {
+	ID          uint   `gorm:"column:id;primary_key" json:"id"`
+	Updated     int64  `gorm:"autoUpdateTime"`
+	Created     int64  `gorm:"autoCreateTime"`
+	Username    string `json:"username"`
+	Password    string `json:"password"`
+	Host        string `json:"host"`
+	Port        string `json:"port"`
+	Status      string `json:"status"`
+	Directories string `json:"directories"` // string array
+	MountPoint  string `json:"mount_point"` //parent directory of mount point
+}
+
+func (p *ConnectionsDBModel) TableName() string {
+	return "o_connections"
+}

+ 24 - 0
service/model/o_shares.go

@@ -0,0 +1,24 @@
+/*
+ * @Author: LinkLeong link@icewhale.org
+ * @Date: 2022-07-26 11:17:17
+ * @LastEditors: LinkLeong
+ * @LastEditTime: 2022-07-27 15:25:07
+ * @FilePath: /CasaOS/service/model/o_shares.go
+ * @Description:
+ * @Website: https://www.casaos.io
+ * Copyright (c) 2022 by icewhale, All Rights Reserved.
+ */
+package model
+
+type SharesDBModel struct {
+	ID        uint   `gorm:"column:id;primary_key" json:"id"`
+	Anonymous bool   `json:"anonymous"`
+	Path      string `json:"path"`
+	Name      string `json:"name"`
+	Updated   int64  `gorm:"autoUpdateTime"`
+	Created   int64  `gorm:"autoCreateTime"`
+}
+
+func (p *SharesDBModel) TableName() string {
+	return "o_shares"
+}

+ 31 - 18
service/service.go

@@ -2,7 +2,7 @@
  * @Author: LinkLeong link@icewhale.com
  * @Date: 2022-07-12 09:48:56
  * @LastEditors: LinkLeong
- * @LastEditTime: 2022-07-15 10:58:54
+ * @LastEditTime: 2022-07-27 10:28:48
  * @FilePath: /CasaOS/service/service.go
  * @Description:
  * @Website: https://www.casaos.io
@@ -33,31 +33,44 @@ type Repository interface {
 	Notify() NotifyServer
 	Rely() RelyService
 	System() SystemService
+	Shares() SharesService
+	Connections() ConnectionsService
 }
 
 func NewService(db *gorm.DB) Repository {
 	return &store{
-		app:    NewAppService(db),
-		user:   NewUserService(db),
-		docker: NewDockerService(),
-		casa:   NewCasaService(),
-		disk:   NewDiskService(db),
-		notify: NewNotifyService(db),
-		rely:   NewRelyService(db),
-		system: NewSystemService(),
+		app:         NewAppService(db),
+		user:        NewUserService(db),
+		docker:      NewDockerService(),
+		casa:        NewCasaService(),
+		disk:        NewDiskService(db),
+		notify:      NewNotifyService(db),
+		rely:        NewRelyService(db),
+		system:      NewSystemService(),
+		shares:      NewSharesService(db),
+		connections: NewConnectionsService(db),
 	}
 }
 
 type store struct {
-	db     *gorm.DB
-	app    AppService
-	user   UserService
-	docker DockerService
-	casa   CasaService
-	disk   DiskService
-	notify NotifyServer
-	rely   RelyService
-	system SystemService
+	db          *gorm.DB
+	app         AppService
+	user        UserService
+	docker      DockerService
+	casa        CasaService
+	disk        DiskService
+	notify      NotifyServer
+	rely        RelyService
+	system      SystemService
+	shares      SharesService
+	connections ConnectionsService
+}
+
+func (s *store) Connections() ConnectionsService {
+	return s.connections
+}
+func (s *store) Shares() SharesService {
+	return s.shares
 }
 
 func (c *store) Rely() RelyService {

+ 151 - 0
service/shares.go

@@ -0,0 +1,151 @@
+/*
+ * @Author: LinkLeong link@icewhale.org
+ * @Date: 2022-07-26 11:21:14
+ * @LastEditors: LinkLeong
+ * @LastEditTime: 2022-08-11 14:04:00
+ * @FilePath: /CasaOS/service/shares.go
+ * @Description:
+ * @Website: https://www.casaos.io
+ * Copyright (c) 2022 by icewhale, All Rights Reserved.
+ */
+package service
+
+import (
+	"path/filepath"
+	"strings"
+
+	"github.com/IceWhaleTech/CasaOS/pkg/config"
+	command2 "github.com/IceWhaleTech/CasaOS/pkg/utils/command"
+	"github.com/IceWhaleTech/CasaOS/pkg/utils/file"
+	"github.com/IceWhaleTech/CasaOS/service/model"
+	model2 "github.com/IceWhaleTech/CasaOS/service/model"
+	"gorm.io/gorm"
+)
+
+type SharesService interface {
+	GetSharesList() (shares []model2.SharesDBModel)
+	GetSharesByPath(path string) (shares []model2.SharesDBModel)
+	GetSharesByName(name string) (shares []model2.SharesDBModel)
+	CreateShare(share model2.SharesDBModel)
+	DeleteShare(id string)
+	UpdateConfigFile()
+	InitSambaConfig()
+}
+
+type sharesStruct struct {
+	db *gorm.DB
+}
+
+func (s *sharesStruct) GetSharesByName(name string) (shares []model2.SharesDBModel) {
+	s.db.Select("anonymous,path,id").Where("name = ?", name).Find(&shares)
+
+	return
+}
+func (s *sharesStruct) GetSharesByPath(path string) (shares []model2.SharesDBModel) {
+	s.db.Select("anonymous,path,id").Where("path = ?", path).Find(&shares)
+	return
+}
+func (s *sharesStruct) GetSharesList() (shares []model2.SharesDBModel) {
+	s.db.Select("anonymous,path,id").Find(&shares)
+	return
+}
+func (s *sharesStruct) CreateShare(share model2.SharesDBModel) {
+	s.db.Create(&share)
+	s.InitSambaConfig()
+	s.UpdateConfigFile()
+}
+func (s *sharesStruct) DeleteShare(id string) {
+	s.db.Where("id= ?", id).Delete(&model.SharesDBModel{})
+	s.UpdateConfigFile()
+}
+
+func (s *sharesStruct) UpdateConfigFile() {
+	shares := []model2.SharesDBModel{}
+	s.db.Select("anonymous,path").Find(&shares)
+	//generated config file
+	var configStr = ""
+	for _, share := range shares {
+		dirName := filepath.Base(share.Path)
+		configStr += `
+[` + dirName + `]
+comment = CasaOS share ` + dirName + `
+public = Yes
+path = ` + share.Path + `
+browseable = Yes
+read only = No
+guest ok = Yes
+create mask = 0777
+directory mask = 0777
+
+`
+	}
+	//write config file
+	file.WriteToPath([]byte(configStr), "/etc/samba", "smb.casa.conf")
+	//restart samba
+	command2.ExecResultStrArray("source " + config.AppInfo.ShellPath + "/helper.sh ;RestartSMBD")
+
+}
+func (s *sharesStruct) InitSambaConfig() {
+	if file.Exists("/etc/samba/smb.conf") {
+		str := file.ReadLine(1, "/etc/samba/smb.conf")
+		if strings.Contains(str, "# Copyright (c) 2021-2022 CasaOS Inc. All rights reserved.") {
+			return
+		}
+		file.MoveFile("/etc/samba/smb.conf", "/etc/samba/smb.conf.bak")
+		var smbConf = ""
+		smbConf += `# Copyright (c) 2021-2022 CasaOS Inc. All rights reserved.
+#
+#
+#                          ______     _______
+#                        (  __  \   (  ___  )
+#                        | (  \  )  | (   ) |
+#                        | |   ) |  | |   | |
+#                        | |   | |  | |   | |
+#                        | |   ) |  | |   | |
+#                        | (__/  )  | (___) |
+#                        (______/   (_______)
+#
+#                   _          _______   _________
+#                  ( (    /|  (  ___  )  \__   __/
+#                  |  \  ( |  | (   ) |     ) (
+#                  |   \ | |  | |   | |     | |
+#                  | (\ \) |  | |   | |     | |
+#                  | | \   |  | |   | |     | |
+#                  | )  \  |  | (___) |     | |
+#                  |/    )_)  (_______)     )_(
+#
+#   _______    _______    ______    _________   _______
+#  (       )  (  ___  )  (  __  \   \__   __/  (  ____ \  |\     /|
+#  | () () |  | (   ) |  | (  \  )     ) (     | (    \/  ( \   / )
+#  | || || |  | |   | |  | |   ) |     | |     | (__       \ (_) /
+#  | |(_)| |  | |   | |  | |   | |     | |     |  __)       \   /
+#  | |   | |  | |   | |  | |   ) |     | |     | (           ) (
+#  | )   ( |  | (___) |  | (__/  )  ___) (___  | )           | |
+#  |/     \|  (_______)  (______/   \_______/  |/            \_/
+#
+#
+# IMPORTANT: CasaOS will not provide technical support for any issues
+#            caused by unauthorized modification to the configuration.
+
+[global]
+## fruit settings
+   min protocol = SMB2
+   ea support = yes
+## vfs objects = fruit streams_xattr
+   fruit:metadata = stream
+   fruit:model = Macmini
+   fruit:veto_appledouble = no
+   fruit:posix_rename = yes
+   fruit:zero_file_id = yes
+   fruit:wipe_intentionally_left_blank_rfork = yes
+   fruit:delete_empty_adfiles = yes
+   map to guest = bad user
+   include=/etc/samba/smb.casa.conf`
+		file.WriteToPath([]byte(smbConf), "/etc/samba", "smb.conf")
+	}
+
+}
+
+func NewSharesService(db *gorm.DB) SharesService {
+	return &sharesStruct{db: db}
+}

+ 9 - 2
service/system.go

@@ -49,6 +49,7 @@ type SystemService interface {
 	CreateFile(path string) (int, error)
 	RenameFile(oldF, newF string) (int, error)
 	MkdirAll(path string) (int, error)
+	IsServiceRunning(name string) bool
 }
 type systemService struct {
 }
@@ -232,9 +233,9 @@ func (s *systemService) GetTimeZone() string {
 
 func (s *systemService) ExecUSBAutoMountShell(state string) {
 	if state == "False" {
-		command2.OnlyExec("source " + config.AppInfo.ShellPath + "/helper.sh ;USB_Remove_File")
+		command2.OnlyExec("source " + config.AppInfo.ShellPath + "/helper.sh ;USB_Stop_Auto")
 	} else {
-		command2.OnlyExec("source " + config.AppInfo.ShellPath + "/helper.sh ;USB_Move_File")
+		command2.OnlyExec("source " + config.AppInfo.ShellPath + "/helper.sh ;USB_Start_Auto")
 	}
 }
 
@@ -287,6 +288,12 @@ func GetDeviceAllIP() []string {
 	}
 	return address
 }
+
+func (s *systemService) IsServiceRunning(name string) bool {
+	status := command2.ExecResultStr("source " + config.AppInfo.ShellPath + "/helper.sh ;CheckServiceStatus smbd")
+	return strings.TrimSpace(status) == "running"
+
+}
 func NewSystemService() SystemService {
 	return &systemService{}
 }

+ 31 - 8
shell/helper.sh

@@ -330,17 +330,17 @@ TarFolder() {
   du -sh /DATA
 }
 
-USB_Move_File() {
+USB_Start_Auto() {
   ((EUID)) && sudo_cmd="sudo"
-  $sudo_cmd cp -rf /casaOS/server/shell/11-usb-mount.rules /etc/udev/rules.d/
-  $sudo_cmd chmod +x /casaOS/server/shell/usb-mount.sh
-  $sudo_cmd cp -rf /casaOS/server/shell/usb-mount@.service /etc/systemd/system/
+  $sudo_cmd systemctl enable devmon@devmon
+  $sudo_cmd systemctl start devmon@devmon
 }
 
-USB_Remove_File() {
+USB_Stop_Auto() {
   ((EUID)) && sudo_cmd="sudo"
-  $sudo_cmd rm -fr /etc/udev/rules.d/11-usb-mount.rules
-  $sudo_cmd rm -fr /etc/systemd/system/usb-mount@.service
+  $sudo_cmd systemctl stop devmon@devmon
+  $sudo_cmd systemctl disable devmon@devmon
+  $sudo_cmd udevil clean
 }
 
 GetDeviceTree(){  
@@ -363,4 +363,27 @@ AddSmabaUser(){
     $2
     $2
 EOF
-}
+}
+
+# $1:username $2:host $3:share $4:port $5:mountpoint $6:password 
+MountCIFS(){
+ $sudo_cmd mount -t cifs -o username=$1,password=$6,port=$4 //$2/$3 $5
+}
+
+# $1:service name
+CheckServiceStatus(){
+  rs="`systemctl status  $1 |grep -E  'Active|PID'`"
+#echo "$rs"
+  run="`echo "$rs" |grep -B 2 'running'`"
+  fai="`echo "$rs" |grep  -E -B 2 'failed|inactive|dead'`"
+  if [  "$run" == "" ]
+   then
+	    echo "failed" 
+   else
+      echo "running"
+   fi
+}
+UDEVILUmount(){
+  $sudo_cmd udevil umount -f $1
+}
+

+ 3 - 3
types/system.go

@@ -2,7 +2,7 @@
  * @Author: LinkLeong link@icewhale.com
  * @Date: 2022-02-17 18:53:22
  * @LastEditors: LinkLeong
- * @LastEditTime: 2022-07-18 18:47:15
+ * @LastEditTime: 2022-08-10 13:50:57
  * @FilePath: /CasaOS/types/system.go
  * @Description:
  * @Website: https://www.casaos.io
@@ -10,6 +10,6 @@
  */
 package types
 
-const CURRENTVERSION = "0.3.4"
+const CURRENTVERSION = "0.3.5"
 
-const BODY = ""
+const BODY = " "

+ 1 - 1
web/index.html

@@ -20,7 +20,7 @@
   <title>
     CasaOS
   </title>
-<link href="/ui/css/13.a16d5119.css" rel="prefetch"><link href="/ui/css/14.cf8c898a.css" rel="prefetch"><link href="/ui/css/4.f7a3b3b4.css" rel="prefetch"><link href="/ui/css/5.e8438f80.css" rel="prefetch"><link href="/ui/css/6.d72d6157.css" rel="prefetch"><link href="/ui/css/7.805596b0.css" rel="prefetch"><link href="/ui/css/8.92188e4d.css" rel="prefetch"><link href="/ui/css/9.dccf29b4.css" rel="prefetch"><link href="/ui/js/0.js" rel="prefetch"><link href="/ui/js/1.js" rel="prefetch"><link href="/ui/js/10.js" rel="prefetch"><link href="/ui/js/11.js" rel="prefetch"><link href="/ui/js/12.js" rel="prefetch"><link href="/ui/js/13.js" rel="prefetch"><link href="/ui/js/14.js" rel="prefetch"><link href="/ui/js/15.js" rel="prefetch"><link href="/ui/js/2.js" rel="prefetch"><link href="/ui/js/3.js" rel="prefetch"><link href="/ui/js/4.js" rel="prefetch"><link href="/ui/js/5.js" rel="prefetch"><link href="/ui/js/6.js" rel="prefetch"><link href="/ui/js/7.js" rel="prefetch"><link href="/ui/js/8.js" rel="prefetch"><link href="/ui/js/9.js" rel="prefetch"><link href="/ui/css/app.344b6034.css" rel="preload" as="style"><link href="/ui/css/vendors~app.c42f9a2b.css" rel="preload" as="style"><link href="/ui/js/app.js" rel="preload" as="script"><link href="/ui/js/vendors~app.js" rel="preload" as="script"><link href="/ui/css/vendors~app.c42f9a2b.css" rel="stylesheet"><link href="/ui/css/app.344b6034.css" rel="stylesheet"></head>
+<link href="/ui/css/13.a16d5119.css" rel="prefetch"><link href="/ui/css/14.cf8c898a.css" rel="prefetch"><link href="/ui/css/4.f17f9b01.css" rel="prefetch"><link href="/ui/css/5.e8438f80.css" rel="prefetch"><link href="/ui/css/6.d72d6157.css" rel="prefetch"><link href="/ui/css/7.805596b0.css" rel="prefetch"><link href="/ui/css/8.92188e4d.css" rel="prefetch"><link href="/ui/css/9.dccf29b4.css" rel="prefetch"><link href="/ui/js/0.js" rel="prefetch"><link href="/ui/js/1.js" rel="prefetch"><link href="/ui/js/10.js" rel="prefetch"><link href="/ui/js/11.js" rel="prefetch"><link href="/ui/js/12.js" rel="prefetch"><link href="/ui/js/13.js" rel="prefetch"><link href="/ui/js/14.js" rel="prefetch"><link href="/ui/js/15.js" rel="prefetch"><link href="/ui/js/2.js" rel="prefetch"><link href="/ui/js/3.js" rel="prefetch"><link href="/ui/js/4.js" rel="prefetch"><link href="/ui/js/5.js" rel="prefetch"><link href="/ui/js/6.js" rel="prefetch"><link href="/ui/js/7.js" rel="prefetch"><link href="/ui/js/8.js" rel="prefetch"><link href="/ui/js/9.js" rel="prefetch"><link href="/ui/css/app.c78d232d.css" rel="preload" as="style"><link href="/ui/css/vendors~app.c42f9a2b.css" rel="preload" as="style"><link href="/ui/js/app.js" rel="preload" as="script"><link href="/ui/js/vendors~app.js" rel="preload" as="script"><link href="/ui/css/vendors~app.c42f9a2b.css" rel="stylesheet"><link href="/ui/css/app.c78d232d.css" rel="stylesheet"></head>
 
 <body>
   <noscript>

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 0 - 0
web/js/10.js


A diferenza do arquivo foi suprimida porque é demasiado grande
+ 0 - 0
web/js/11.js


A diferenza do arquivo foi suprimida porque é demasiado grande
+ 0 - 0
web/js/12.js


A diferenza do arquivo foi suprimida porque é demasiado grande
+ 0 - 0
web/js/4.js


A diferenza do arquivo foi suprimida porque é demasiado grande
+ 0 - 0
web/js/5.js


A diferenza do arquivo foi suprimida porque é demasiado grande
+ 0 - 0
web/js/7.js


A diferenza do arquivo foi suprimida porque é demasiado grande
+ 0 - 0
web/js/8.js


A diferenza do arquivo foi suprimida porque é demasiado grande
+ 0 - 0
web/js/9.js


A diferenza do arquivo foi suprimida porque é demasiado grande
+ 0 - 0
web/js/app.js


A diferenza do arquivo foi suprimida porque é demasiado grande
+ 0 - 0
web/js/vendors~app.js


Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio