mirror of
https://github.com/pldubouilh/gossa.git
synced 2026-08-28 12:56:35 -04:00
Compare commits
28
Commits
dependabot
..
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b59ea991ff | ||
|
|
d17040df0b | ||
|
|
1cac56abfa | ||
|
|
ad423948cc | ||
|
|
da7f84f5c9 | ||
|
|
23e9e6853f | ||
|
|
7644c9f4d0 | ||
|
|
fc4d4dbd09 | ||
|
|
8443ab443c | ||
|
|
31c8ee518a | ||
|
|
cc703cd70d | ||
|
|
c963d1773b | ||
|
|
623acc30a6 | ||
|
|
746d6d55bc | ||
|
|
5a1f75265d | ||
|
|
d4a60b3ece | ||
|
|
c0d7616101 | ||
|
|
83038f6de2 | ||
|
|
a7132076cb | ||
|
|
f384c3025b | ||
|
|
5fbc140e53 | ||
|
|
1861de0d57 | ||
|
|
50c524cc9b | ||
|
|
99a6aec8db | ||
|
|
33c93fc0b4 | ||
|
|
ec06354eb0 | ||
|
|
b92197cf0f | ||
|
|
b5505d4773 |
@@ -6,20 +6,15 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v1
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: 1.19.1
|
go-version: 1.23.0
|
||||||
id: go
|
id: go
|
||||||
|
|
||||||
- name: deps
|
|
||||||
run: npm i -g standard
|
|
||||||
|
|
||||||
- name: Check out code into the Go module directory
|
|
||||||
uses: actions/checkout@v1
|
|
||||||
with:
|
|
||||||
submodules: true
|
|
||||||
|
|
||||||
- name: Run
|
- name: Run
|
||||||
run: make ci
|
run: make ci
|
||||||
|
|
||||||
|
|||||||
@@ -10,33 +10,25 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v1
|
uses: actions/checkout@v4
|
||||||
with:
|
|
||||||
submodules: true
|
|
||||||
|
|
||||||
- name: Set up Go
|
|
||||||
uses: actions/setup-go@v1
|
|
||||||
with:
|
|
||||||
go-version: 1.19.1
|
|
||||||
id: go
|
|
||||||
|
|
||||||
- name: Set env
|
- name: Set env
|
||||||
run: echo "GIT_TAG=`echo $(git describe --tags --abbrev=0)`" >> $GITHUB_ENV
|
run: echo "GIT_TAG=`echo $(git describe --tags --abbrev=0)`" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v1.2.0
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v1.6.0
|
|
||||||
|
|
||||||
- name: Login to DockerHub
|
- name: Login to DockerHub
|
||||||
uses: docker/login-action@v1.10.0
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKER_USER }}
|
username: ${{ secrets.DOCKER_USER }}
|
||||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
- name: Build and release on dockerhub
|
- name: Build and release on dockerhub
|
||||||
uses: docker/build-push-action@v2.7.0
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
file: support/build.Dockerfile
|
file: support/build.Dockerfile
|
||||||
push: true
|
push: true
|
||||||
@@ -47,17 +39,19 @@ jobs:
|
|||||||
run: make build-all
|
run: make build-all
|
||||||
|
|
||||||
- name: "Release gh release versioned"
|
- name: "Release gh release versioned"
|
||||||
uses: ncipollo/release-action@58ae73b360456532aafd58ee170c045abbeaee37
|
uses: ncipollo/release-action@v1
|
||||||
with:
|
with:
|
||||||
allowUpdates: true
|
allowUpdates: true
|
||||||
artifacts: "builds/*"
|
artifacts: "builds/*"
|
||||||
|
bodyFile: "builds/buildout"
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: "Release gh release latest"
|
- name: "Release gh release latest"
|
||||||
uses: ncipollo/release-action@58ae73b360456532aafd58ee170c045abbeaee37
|
uses: ncipollo/release-action@v1
|
||||||
with:
|
with:
|
||||||
tag: latest
|
tag: latest
|
||||||
name: Latest
|
name: Latest
|
||||||
allowUpdates: true
|
allowUpdates: true
|
||||||
artifacts: "builds/*"
|
artifacts: "builds/*"
|
||||||
|
bodyFile: "builds/buildout"
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|||||||
@@ -1,39 +0,0 @@
|
|||||||
name: rc
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- 'rc/**'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
name: Build
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
|
|
||||||
- name: Set up Go
|
|
||||||
uses: actions/setup-go@v1
|
|
||||||
with:
|
|
||||||
go-version: 1.19.1
|
|
||||||
id: go
|
|
||||||
|
|
||||||
- name: Check out code into the Go module directory
|
|
||||||
uses: actions/checkout@v1
|
|
||||||
with:
|
|
||||||
submodules: true
|
|
||||||
|
|
||||||
- name: Run
|
|
||||||
run: make ci
|
|
||||||
|
|
||||||
- name: Build all artifacts
|
|
||||||
run: make build-all
|
|
||||||
|
|
||||||
- name: "Release gh release prerelease"
|
|
||||||
uses: ncipollo/release-action@58ae73b360456532aafd58ee170c045abbeaee37
|
|
||||||
with:
|
|
||||||
allowUpdates: true
|
|
||||||
prerelease: true
|
|
||||||
tag: "rc"
|
|
||||||
artifacts: "builds/*"
|
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
FLAGS := -ldflags "-s -w" -trimpath
|
FLAGS := -trimpath
|
||||||
NOCGO := CGO_ENABLED=0
|
NOCGO := CGO_ENABLED=0
|
||||||
|
|
||||||
build::
|
build::
|
||||||
@@ -17,10 +17,7 @@ run-ro::
|
|||||||
run-extra::
|
run-extra::
|
||||||
./gossa -verb=true -prefix="/fancy-path/" -k=false -symlinks=true test-fixture
|
./gossa -verb=true -prefix="/fancy-path/" -k=false -symlinks=true test-fixture
|
||||||
|
|
||||||
lint-js::
|
ci:: build-all test
|
||||||
standard
|
|
||||||
|
|
||||||
ci:: build-all test lint-js
|
|
||||||
echo "done"
|
echo "done"
|
||||||
|
|
||||||
test::
|
test::
|
||||||
@@ -59,13 +56,14 @@ watch-test::
|
|||||||
ls gossa.go gossa_test.go ui/* | entr -rc make test
|
ls gossa.go gossa_test.go ui/* | entr -rc make test
|
||||||
|
|
||||||
build-all:: build
|
build-all:: build
|
||||||
${NOCGO} GOOS=linux GOARCH=amd64 go build ${FLAGS} -o builds/gossa-linux-x64
|
go version
|
||||||
${NOCGO} GOOS=linux GOARCH=arm go build ${FLAGS} -o builds/gossa-linux-arm
|
${NOCGO} GOOS=linux GOARCH=amd64 go build ${FLAGS} -o builds/gossa-linux-x64
|
||||||
${NOCGO} GOOS=linux GOARCH=arm64 go build ${FLAGS} -o builds/gossa-linux-arm64
|
${NOCGO} GOOS=linux GOARCH=arm go build ${FLAGS} -o builds/gossa-linux-arm
|
||||||
${NOCGO} GOOS=darwin GOARCH=amd64 go build ${FLAGS} -o builds/gossa-mac-x64
|
${NOCGO} GOOS=linux GOARCH=arm64 go build ${FLAGS} -o builds/gossa-linux-arm64
|
||||||
${NOCGO} GOOS=darwin GOARCH=arm64 go build ${FLAGS} -o builds/gossa-mac-arm64
|
${NOCGO} GOOS=darwin GOARCH=amd64 go build ${FLAGS} -o builds/gossa-mac-x64
|
||||||
${NOCGO} GOOS=windows GOARCH=amd64 go build ${FLAGS} -o builds/gossa-windows.exe
|
${NOCGO} GOOS=darwin GOARCH=arm64 go build ${FLAGS} -o builds/gossa-mac-arm64
|
||||||
sha256sum builds/*
|
${NOCGO} GOOS=windows GOARCH=amd64 go build ${FLAGS} -o builds/gossa-windows.exe
|
||||||
|
sha256sum builds/* | tee builds/buildout
|
||||||
|
|
||||||
clean::
|
clean::
|
||||||
rm -f gossa
|
rm -f gossa
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
module github.com/pldubouilh/gossa
|
module github.com/pldubouilh/gossa
|
||||||
|
|
||||||
go 1.16
|
go 1.23.0
|
||||||
|
|||||||
@@ -3,16 +3,21 @@ package main
|
|||||||
import (
|
import (
|
||||||
"archive/zip"
|
"archive/zip"
|
||||||
"compress/gzip"
|
"compress/gzip"
|
||||||
|
"crypto/md5"
|
||||||
|
"crypto/sha1"
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/sha512"
|
||||||
_ "embed"
|
_ "embed"
|
||||||
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"hash"
|
||||||
"html"
|
"html"
|
||||||
"html/template"
|
"html/template"
|
||||||
"io"
|
"io"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
@@ -25,7 +30,7 @@ import (
|
|||||||
|
|
||||||
type rowTemplate struct {
|
type rowTemplate struct {
|
||||||
Name string
|
Name string
|
||||||
Href template.HTML
|
Href template.URL
|
||||||
Size string
|
Size string
|
||||||
Ext string
|
Ext string
|
||||||
}
|
}
|
||||||
@@ -83,9 +88,9 @@ func humanize(bytes int64) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func replyList(w http.ResponseWriter, r *http.Request, fullPath string, path string) {
|
func replyList(w http.ResponseWriter, r *http.Request, fullPath string, path string) {
|
||||||
_files, err := ioutil.ReadDir(fullPath)
|
files, err := os.ReadDir(fullPath)
|
||||||
check(err)
|
check(err)
|
||||||
sort.Slice(_files, func(i, j int) bool { return strings.ToLower(_files[i].Name()) < strings.ToLower(_files[j].Name()) })
|
sort.Slice(files, func(i, j int) bool { return strings.ToLower(files[i].Name()) < strings.ToLower(files[j].Name()) })
|
||||||
|
|
||||||
if !strings.HasSuffix(path, "/") {
|
if !strings.HasSuffix(path, "/") {
|
||||||
path += "/"
|
path += "/"
|
||||||
@@ -100,18 +105,19 @@ func replyList(w http.ResponseWriter, r *http.Request, fullPath string, path str
|
|||||||
p.Ro = *ro
|
p.Ro = *ro
|
||||||
p.Title = template.HTML(html.EscapeString(title))
|
p.Title = template.HTML(html.EscapeString(title))
|
||||||
|
|
||||||
for _, el := range _files {
|
for _, el := range files {
|
||||||
|
info, errInfo := el.Info()
|
||||||
|
el, err := os.Stat(fullPath + "/" + el.Name())
|
||||||
|
if err != nil || errInfo != nil {
|
||||||
|
log.Println("error - cant stat a file", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
if *skipHidden && strings.HasPrefix(el.Name(), ".") {
|
if *skipHidden && strings.HasPrefix(el.Name(), ".") {
|
||||||
continue // dont print hidden files if we're not allowed
|
continue // dont print hidden files if we're not allowed
|
||||||
}
|
}
|
||||||
if !*symlinks && el.Mode()&os.ModeSymlink != 0 {
|
if !*symlinks && info.Mode()&os.ModeSymlink != 0 {
|
||||||
continue // dont print symlinks if were not allowed
|
continue // dont follow symlinks if we're not allowed
|
||||||
}
|
|
||||||
|
|
||||||
el, err := os.Stat(fullPath + "/" + el.Name())
|
|
||||||
if err != nil {
|
|
||||||
log.Println("error - cant stat a file", err)
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
href := url.PathEscape(el.Name())
|
href := url.PathEscape(el.Name())
|
||||||
@@ -122,11 +128,13 @@ func replyList(w http.ResponseWriter, r *http.Request, fullPath string, path str
|
|||||||
}
|
}
|
||||||
|
|
||||||
if el.IsDir() {
|
if el.IsDir() {
|
||||||
p.RowsFolders = append(p.RowsFolders, rowTemplate{name + "/", template.HTML(href), "", "folder"})
|
row := rowTemplate{name + "/", template.URL(href), "", "folder"}
|
||||||
|
p.RowsFolders = append(p.RowsFolders, row)
|
||||||
} else {
|
} else {
|
||||||
sl := strings.Split(name, ".")
|
sl := strings.Split(name, ".")
|
||||||
ext := strings.ToLower(sl[len(sl)-1])
|
ext := strings.ToLower(sl[len(sl)-1])
|
||||||
p.RowsFiles = append(p.RowsFiles, rowTemplate{name, template.HTML(href), humanize(el.Size()), ext})
|
row := rowTemplate{name, template.URL(href), humanize(el.Size()), ext}
|
||||||
|
p.RowsFiles = append(p.RowsFiles, row)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,9 +144,9 @@ func replyList(w http.ResponseWriter, r *http.Request, fullPath string, path str
|
|||||||
gz, err := gzip.NewWriterLevel(w, gzip.BestSpeed) // BestSpeed is Much Faster than default - base on a very unscientific local test, and only ~30% larger (compression remains still very effective, ~6x)
|
gz, err := gzip.NewWriterLevel(w, gzip.BestSpeed) // BestSpeed is Much Faster than default - base on a very unscientific local test, and only ~30% larger (compression remains still very effective, ~6x)
|
||||||
check(err)
|
check(err)
|
||||||
defer gz.Close()
|
defer gz.Close()
|
||||||
templateParsed.Execute(gz, p)
|
tmpl.Execute(gz, p)
|
||||||
} else {
|
} else {
|
||||||
templateParsed.Execute(w, p)
|
tmpl.Execute(w, p)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,21 +233,42 @@ func zipRPC(w http.ResponseWriter, r *http.Request) {
|
|||||||
func rpc(w http.ResponseWriter, r *http.Request) {
|
func rpc(w http.ResponseWriter, r *http.Request) {
|
||||||
var err error
|
var err error
|
||||||
var rpc rpcCall
|
var rpc rpcCall
|
||||||
defer exitPath(w, "rpc", rpc)
|
defer exitPath(w, "rpc", &rpc)
|
||||||
bodyBytes, err := ioutil.ReadAll(r.Body)
|
bodyBytes, err := io.ReadAll(r.Body)
|
||||||
check(err)
|
check(err)
|
||||||
json.Unmarshal(bodyBytes, &rpc)
|
json.Unmarshal(bodyBytes, &rpc)
|
||||||
|
ret := []byte("ok")
|
||||||
|
|
||||||
if rpc.Call == "mkdirp" {
|
switch rpc.Call {
|
||||||
|
case "mkdirp":
|
||||||
err = os.MkdirAll(enforcePath(rpc.Args[0]), os.ModePerm)
|
err = os.MkdirAll(enforcePath(rpc.Args[0]), os.ModePerm)
|
||||||
} else if rpc.Call == "mv" {
|
case "mv":
|
||||||
err = os.Rename(enforcePath(rpc.Args[0]), enforcePath(rpc.Args[1]))
|
err = os.Rename(enforcePath(rpc.Args[0]), enforcePath(rpc.Args[1]))
|
||||||
} else if rpc.Call == "rm" {
|
case "rm":
|
||||||
err = os.RemoveAll(enforcePath(rpc.Args[0]))
|
err = os.RemoveAll(enforcePath(rpc.Args[0]))
|
||||||
|
case "sum":
|
||||||
|
file, err := os.Open(enforcePath(rpc.Args[0]))
|
||||||
|
check(err)
|
||||||
|
var hash hash.Hash
|
||||||
|
switch rpc.Args[1] {
|
||||||
|
case "md5":
|
||||||
|
hash = md5.New()
|
||||||
|
case "sha1":
|
||||||
|
hash = sha1.New()
|
||||||
|
case "sha256":
|
||||||
|
hash = sha256.New()
|
||||||
|
case "sha512":
|
||||||
|
hash = sha512.New()
|
||||||
|
}
|
||||||
|
_, err = io.Copy(hash, file)
|
||||||
|
check(err)
|
||||||
|
checksum := hash.Sum(nil)
|
||||||
|
ret = make([]byte, hex.EncodedLen(len(checksum)))
|
||||||
|
hex.Encode(ret, checksum)
|
||||||
}
|
}
|
||||||
|
|
||||||
check(err)
|
check(err)
|
||||||
w.Write([]byte("ok"))
|
w.Write(ret)
|
||||||
}
|
}
|
||||||
|
|
||||||
func enforcePath(p string) string {
|
func enforcePath(p string) string {
|
||||||
|
|||||||
Executable → Regular
+13
-5
@@ -17,10 +17,18 @@ var styleCss string
|
|||||||
var faviconSvg []byte
|
var faviconSvg []byte
|
||||||
|
|
||||||
//go:embed ui/ui.tmpl
|
//go:embed ui/ui.tmpl
|
||||||
var template0 string
|
var uiTmpl string
|
||||||
|
|
||||||
|
var tmpl *template.Template
|
||||||
|
|
||||||
// fill in template
|
// fill in template
|
||||||
var template1 = strings.Replace(template0, "css_will_be_here", styleCss, 1)
|
func init() {
|
||||||
var template2 = strings.Replace(template1, "js_will_be_here", scriptJs, 1)
|
var err error
|
||||||
var template3 = strings.Replace(template2, "favicon_will_be_here", base64.StdEncoding.EncodeToString(faviconSvg), 2)
|
t := strings.Replace(uiTmpl, "css_will_be_here", styleCss, 1)
|
||||||
var templateParsed, _ = template.New("").Parse(template3)
|
t = strings.Replace(t, "js_will_be_here", scriptJs, 1)
|
||||||
|
t = strings.Replace(t, "favicon_will_be_here", base64.StdEncoding.EncodeToString(faviconSvg), 2)
|
||||||
|
tmpl, err = template.New("").Parse(t)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -204,6 +204,17 @@ func doTestRegular(t *testing.T, url string, testExtra bool) {
|
|||||||
t.Fatal("post file incorrect path didnt errored")
|
t.Fatal("post file incorrect path didnt errored")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ~~~~~~~~~~~~~~~~~
|
||||||
|
fmt.Println("\r\n~~~~~~~~~~ test post file")
|
||||||
|
path = "2024-01-02-10:36:58.png"
|
||||||
|
payload = "123123123123123123123123"
|
||||||
|
body0 = postDummyFile(t, url, path, payload)
|
||||||
|
body1 = get(t, url+path)
|
||||||
|
body2 = fetchAndTestDefault(t, url)
|
||||||
|
if body0 != `ok` || body1 != payload || !strings.Contains(body2, `href="2024-01-02-10:36:58.png"`) {
|
||||||
|
t.Fatal("post file errored")
|
||||||
|
}
|
||||||
|
|
||||||
// ~~~~~~~~~~~~~~~~~
|
// ~~~~~~~~~~~~~~~~~
|
||||||
fmt.Println("\r\n~~~~~~~~~~ test mv rpc")
|
fmt.Println("\r\n~~~~~~~~~~ test mv rpc")
|
||||||
body0 = postJSON(t, url+"rpc", `{"call":"mv","args":["/AAA", "/hols/AAA"]}`)
|
body0 = postJSON(t, url+"rpc", `{"call":"mv","args":["/AAA", "/hols/AAA"]}`)
|
||||||
@@ -227,6 +238,8 @@ func doTestRegular(t *testing.T, url string, testExtra bool) {
|
|||||||
hasListing := strings.Contains(body0, `readme.md`)
|
hasListing := strings.Contains(body0, `readme.md`)
|
||||||
body1 = get(t, url+"/support/readme.md")
|
body1 = get(t, url+"/support/readme.md")
|
||||||
hasReadme := strings.Contains(body1, `the master branch is automatically built and pushed`)
|
hasReadme := strings.Contains(body1, `the master branch is automatically built and pushed`)
|
||||||
|
body2 = get(t, url)
|
||||||
|
hasMainListing := strings.Contains(body2, `href="support">support/</a>`)
|
||||||
|
|
||||||
if !testExtra && hasReadme {
|
if !testExtra && hasReadme {
|
||||||
t.Fatal("error symlink file reached where illegal")
|
t.Fatal("error symlink file reached where illegal")
|
||||||
@@ -238,6 +251,11 @@ func doTestRegular(t *testing.T, url string, testExtra bool) {
|
|||||||
} else if testExtra && !hasListing {
|
} else if testExtra && !hasListing {
|
||||||
t.Fatal("error symlink folder unreachable")
|
t.Fatal("error symlink folder unreachable")
|
||||||
}
|
}
|
||||||
|
if !testExtra && hasMainListing {
|
||||||
|
t.Fatal("error symlink folder where illegal")
|
||||||
|
} else if testExtra && !hasMainListing {
|
||||||
|
t.Fatal("error symlink folder unreachable")
|
||||||
|
}
|
||||||
|
|
||||||
if testExtra {
|
if testExtra {
|
||||||
fmt.Println("\r\n~~~~~~~~~~ test symlink mkdir & cleanup")
|
fmt.Println("\r\n~~~~~~~~~~ test symlink mkdir & cleanup")
|
||||||
|
|||||||
@@ -29,7 +29,9 @@ a simple UI comes as default, featuring :
|
|||||||
|
|
||||||
[nix](https://search.nixos.org/packages?channel=unstable&show=gossa&from=0&size=50&sort=relevance&type=packages&query=gossa) - e.g. `nix-shell -p gossa`
|
[nix](https://search.nixos.org/packages?channel=unstable&show=gossa&from=0&size=50&sort=relevance&type=packages&query=gossa) - e.g. `nix-shell -p gossa`
|
||||||
|
|
||||||
binaries are available on the [release page](https://github.com/pldubouilh/gossa/releases) - or simply `make build` this repo.
|
[mpr](https://mpr.makedeb.org/packages/gossa)
|
||||||
|
|
||||||
|
binaries are available on the [release page](https://github.com/pldubouilh/gossa/releases)
|
||||||
|
|
||||||
all builds are reproducible, checkout the hashes on the release page.
|
all builds are reproducible, checkout the hashes on the release page.
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,16 @@
|
|||||||
FROM golang:1.18 as builder
|
FROM docker.io/library/golang:1.23.0-alpine AS builder
|
||||||
|
RUN apk add --no-cache make
|
||||||
|
WORKDIR /gossaSrc
|
||||||
COPY . /gossaSrc
|
COPY . /gossaSrc
|
||||||
RUN cd /gossaSrc && make
|
RUN make
|
||||||
|
|
||||||
FROM alpine:3.15
|
FROM docker.io/library/alpine:3.20
|
||||||
ENV UID="1000" GID="1000" HOST="0.0.0.0" PORT="8001" PREFIX="/" FOLLOW_SYMLINKS="false" SKIP_HIDDEN_FILES="true" DATADIR="/shared" READONLY="false" VERB="false"
|
ENV UID="1000" GID="1000" HOST="0.0.0.0" PORT="8001" PREFIX="/" FOLLOW_SYMLINKS="false" SKIP_HIDDEN_FILES="true" DATADIR="/shared" READONLY="false" VERB="false"
|
||||||
RUN apk add --no-cache su-exec
|
|
||||||
COPY ./support/entrypoint.sh /entrypoint.sh
|
|
||||||
COPY --from=builder /gossaSrc/gossa /gossa
|
COPY --from=builder /gossaSrc/gossa /gossa
|
||||||
ENTRYPOINT "/entrypoint.sh"
|
RUN addgroup -g ${GID} user \
|
||||||
|
&& adduser -D -u ${UID} -G user user
|
||||||
|
WORKDIR ${DATADIR}
|
||||||
|
RUN chown ${UID}:${GID} ${DATADIR}
|
||||||
|
USER ${UID}:${GID}
|
||||||
|
ENTRYPOINT /gossa -h ${HOST} -p ${PORT} -k=${SKIP_HIDDEN_FILES} -ro=${READONLY} --symlinks=${FOLLOW_SYMLINKS} --prefix=${PREFIX} --verb=${VERB} ${DATADIR}
|
||||||
|
HEALTHCHECK --timeout=5s --start-period=5s --retries=3 CMD wget --no-verbose --tries=1 --spider 127.0.0.1:8001 || exit 1
|
||||||
@@ -2,9 +2,29 @@ version: '2'
|
|||||||
|
|
||||||
services:
|
services:
|
||||||
gossa-server:
|
gossa-server:
|
||||||
image: pldubouilh/gossa
|
image: docker.io/pldubouilh/gossa:latest
|
||||||
container_name: gossa
|
container_name: gossa
|
||||||
restart: always
|
restart: always
|
||||||
|
read_only: true
|
||||||
|
# uncomment to set the user
|
||||||
|
# user: "1000:1000"
|
||||||
|
# userns_mode: "keep-id" # uncomment if using rootless podman as well as the x-podman directive at the bottom
|
||||||
|
# environment:
|
||||||
|
#- READONLY=true # uncomment to set gossa as read only
|
||||||
|
# - UID=1000 # this should match the user set above
|
||||||
|
# - GID=1000 # this should match the user's group
|
||||||
|
cap_drop:
|
||||||
|
- ALL
|
||||||
|
cap_add:
|
||||||
|
- SETGID
|
||||||
|
- SETUID
|
||||||
|
# uncomment to set resource usage limits
|
||||||
|
# deploy:
|
||||||
|
# resources:
|
||||||
|
# limits:
|
||||||
|
# cpus: "2"
|
||||||
|
# memory: 250m
|
||||||
|
# pids: 1024
|
||||||
ports:
|
ports:
|
||||||
- 8001:8001
|
- 8001:8001
|
||||||
volumes:
|
volumes:
|
||||||
@@ -14,3 +34,6 @@ services:
|
|||||||
# - "traefik.port=8001"
|
# - "traefik.port=8001"
|
||||||
# - "traefik.backend=gossa"
|
# - "traefik.backend=gossa"
|
||||||
# - "traefik.frontend.rule=Host:${GOSSA}.${DOMAIN}"
|
# - "traefik.frontend.rule=Host:${GOSSA}.${DOMAIN}"
|
||||||
|
|
||||||
|
# x-podman: # uncomment if using rootless podman as well as the userns_mode directive at the top
|
||||||
|
# in_pod: false
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
exec su-exec ${UID}:${GID} /gossa -h ${HOST} -p ${PORT} -k=${SKIP_HIDDEN_FILES} -ro=${READONLY} --symlinks=${FOLLOW_SYMLINKS} --prefix=${PREFIX} --verb=${VERB} ${DATADIR}
|
|
||||||
+100
-31
@@ -38,36 +38,67 @@ if you prefer building the image yourself :
|
|||||||
|
|
||||||
a docker-compose example image is also provided. running docker compose should be straightforward : `docker-compose up .` have a look in `docker-compose.yml` for further configuration.
|
a docker-compose example image is also provided. running docker compose should be straightforward : `docker-compose up .` have a look in `docker-compose.yml` for further configuration.
|
||||||
|
|
||||||
## multi-account setup
|
## multi-account setup with Caddy
|
||||||
|
|
||||||
authentication / user routing has been left out of the design of gossa, as simple tools are already available for this purpose. [caddy](https://caddyserver.com/v1/) is used here as an example, but other proxy can be used in a similar fashion.
|
authentication / user routing has been left out of the design of gossa, as simple tools are already available for this purpose. [caddy](https://caddyserver.com) is used here as an example, but other proxy can be used in a similar fashion.
|
||||||
|
|
||||||
### example 1 root, multiple read-only users
|
### example 1 root, multiple read-only users
|
||||||
|
|
||||||
this sample caddy config will
|
This sample Caddyfile will
|
||||||
+ enable https on the domain myserver.com
|
+ enable https on the domain myserver.com (http will be automatically redirected to https)
|
||||||
+ password protect the access
|
+ password protect the access
|
||||||
+ route the root user requests to 1 gossa instance
|
+ route the root user requests to 1 gossa instance
|
||||||
+ route user1 and user2 requests to a readonly gossa instance
|
+ route user1 and user2 requests to a readonly gossa instance
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Legacy Caddy v1 Caddyfile</summary>
|
||||||
|
|
||||||
|
```sh
|
||||||
|
myserver.com
|
||||||
|
|
||||||
|
# proxy regular and read only instance
|
||||||
|
proxy / 127.0.0.1:8001
|
||||||
|
proxy /ro 127.0.0.1:8002 { without /ro }
|
||||||
|
|
||||||
|
# reroute non-root user to read-only
|
||||||
|
# cm9... is the output of `printf "root:password" | base64`
|
||||||
|
rewrite {
|
||||||
|
if {>Authorization} not "Basic cm9vdDpwYXNzd29yZA=="
|
||||||
|
to /ro/{path}
|
||||||
|
}
|
||||||
|
|
||||||
|
# gate access
|
||||||
|
basicauth / root password
|
||||||
|
basicauth / ro_user1 passworduser1
|
||||||
|
basicauth / ro_user2 passworduser2
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
Caddy v2 Caddyfile
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
myserver.com
|
myserver.com
|
||||||
|
|
||||||
# proxy regular and read only instance
|
# gate access
|
||||||
proxy / 127.0.0.1:8001
|
basic_auth {
|
||||||
proxy /ro 127.0.0.1:8002 { without /ro }
|
root $2a$14$Zkx19XLiW6VYouLHR5NmfOFU0z2GTNmpkT/5qqR7hx4IjWJPDhjvG # password is "hiccup"
|
||||||
|
ro_user1 $2a$14$Zkx19XLiW6VYouLHR5NmfOFU0z2GTNmpkT/5qqR7hx4IjWJPDhjvG # password is "hiccup"
|
||||||
# reroute non-root user to read-only
|
ro_user2 $2a$14$Zkx19XLiW6VYouLHR5NmfOFU0z2GTNmpkT/5qqR7hx4IjWJPDhjvG # password is "hiccup"
|
||||||
# cm9... is the output of `printf "root:password" | base64`
|
|
||||||
rewrite {
|
|
||||||
if {>Authorization} not "Basic cm9vdDpwYXNzd29yZA=="
|
|
||||||
to /ro/{path}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# gate access
|
# named matcher for root user
|
||||||
basicauth / root password
|
@isroot {
|
||||||
basicauth / ro_user1 passworduser1
|
vars {http.auth.user.id} root
|
||||||
basicauth / ro_user2 passworduser2
|
}
|
||||||
|
|
||||||
|
# proxy regular and read only instance
|
||||||
|
handle @isroot {
|
||||||
|
reverse_proxy 127.0.0.1:8001
|
||||||
|
}
|
||||||
|
# route non-root user to read only instance
|
||||||
|
handle {
|
||||||
|
reverse_proxy 127.0.0.1:8002
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
then simply start the 2 gossa instances, and caddy
|
then simply start the 2 gossa instances, and caddy
|
||||||
@@ -85,30 +116,59 @@ then simply start the 2 gossa instances, and caddy
|
|||||||
|
|
||||||
### example 2 users on 2 different folders
|
### example 2 users on 2 different folders
|
||||||
|
|
||||||
this sample caddy config will
|
This sample Caddyfile will
|
||||||
+ enable https on the domain myserver.com
|
+ enable https on the domain myserver.com (http will be automatically redirected to https)
|
||||||
+ password protect the access
|
+ password protect the access
|
||||||
+ route user1 to own folder
|
+ route user1 to own folder
|
||||||
+ route user2 to own folder
|
+ route user2 to own folder
|
||||||
+ share a folder between 2 users with a symlink
|
+ share a folder between 2 users with a symlink
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Legacy Caddy v1 Caddyfile</summary>
|
||||||
|
|
||||||
|
```sh
|
||||||
|
myserver.com
|
||||||
|
|
||||||
|
proxy /user1 127.0.0.1:8001 { without /user1 }
|
||||||
|
proxy /user2 127.0.0.1:8002 { without /user2 }
|
||||||
|
|
||||||
|
basicauth / user1 passworduser1
|
||||||
|
basicauth / user2 passworduser2
|
||||||
|
|
||||||
|
rewrite {
|
||||||
|
if {>Authorization} is "Basic dXNlcjE6cGFzc3dvcmR1c2VyMQ=="
|
||||||
|
to /user1/{path}
|
||||||
|
}
|
||||||
|
|
||||||
|
rewrite {
|
||||||
|
if {>Authorization} is "Basic dXNlcjI6cGFzc3dvcmR1c2VyMg=="
|
||||||
|
to /user2/{path}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
Caddy v2 Caddyfile
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
myserver.com
|
myserver.com
|
||||||
|
|
||||||
proxy /user1 127.0.0.1:8001 { without /user1 }
|
basic_auth {
|
||||||
proxy /user2 127.0.0.1:8002 { without /user2 }
|
user1 $2a$14$Zkx19XLiW6VYouLHR5NmfOFU0z2GTNmpkT/5qqR7hx4IjWJPDhjvG # password is "hiccup"
|
||||||
|
user2 $2a$14$Zkx19XLiW6VYouLHR5NmfOFU0z2GTNmpkT/5qqR7hx4IjWJPDhjvG # password is "hiccup"
|
||||||
basicauth / user1 passworduser1
|
|
||||||
basicauth / user2 passworduser2
|
|
||||||
|
|
||||||
rewrite {
|
|
||||||
if {>Authorization} is "Basic dXNlcjE6cGFzc3dvcmR1c2VyMQ=="
|
|
||||||
to /user1/{path}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
rewrite {
|
@user1auth {
|
||||||
if {>Authorization} is "Basic dXNlcjI6cGFzc3dvcmR1c2VyMg=="
|
vars {http.auth.user.id} user1
|
||||||
to /user2/{path}
|
}
|
||||||
|
handle @user1auth {
|
||||||
|
reverse_proxy 127.0.0.1:8001
|
||||||
|
}
|
||||||
|
|
||||||
|
@user2auth {
|
||||||
|
vars {http.auth.user.id} user2
|
||||||
|
}
|
||||||
|
handle @user2auth {
|
||||||
|
reverse_proxy 127.0.0.1:8002
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -124,3 +184,12 @@ start 2 gossa instances, and caddy
|
|||||||
% ./gossa -p 8002 -symlinks=true test/user2 &
|
% ./gossa -p 8002 -symlinks=true test/user2 &
|
||||||
% ./caddy
|
% ./caddy
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## nginx setup
|
||||||
|
|
||||||
|
In order to allow for larger uploads, it's recommended to increase the maximum body size on your nginx config :
|
||||||
|
|
||||||
|
```
|
||||||
|
# increase maximum request size
|
||||||
|
client_max_body_size 100M;
|
||||||
|
```
|
||||||
|
|||||||
Vendored
+99
-25
@@ -11,6 +11,7 @@ const rmMsg = () => !confirm('Remove file?\n')
|
|||||||
const ensureMove = () => !confirm('move items?')
|
const ensureMove = () => !confirm('move items?')
|
||||||
const isRo = () => window.ro
|
const isRo = () => window.ro
|
||||||
|
|
||||||
|
// DOM elements
|
||||||
const upBarName = document.getElementById('upBarName')
|
const upBarName = document.getElementById('upBarName')
|
||||||
const upBarPc = document.getElementById('upBarPc')
|
const upBarPc = document.getElementById('upBarPc')
|
||||||
const upGrid = document.getElementById('drop-grid')
|
const upGrid = document.getElementById('drop-grid')
|
||||||
@@ -29,6 +30,8 @@ const editor = document.getElementById('text-editor')
|
|||||||
const crossIcon = document.getElementById('quitAll')
|
const crossIcon = document.getElementById('quitAll')
|
||||||
const toast = document.getElementById('toast')
|
const toast = document.getElementById('toast')
|
||||||
const table = document.getElementById('linkTable')
|
const table = document.getElementById('linkTable')
|
||||||
|
const helpMsg = document.getElementById('help_message')
|
||||||
|
|
||||||
const transparentPixel = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII='
|
const transparentPixel = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII='
|
||||||
|
|
||||||
// helpers
|
// helpers
|
||||||
@@ -157,6 +160,7 @@ function rpc (call, args, cb) {
|
|||||||
const mkdirCall = (path, cb) => rpc('mkdirp', [prependPath(path)], cb)
|
const mkdirCall = (path, cb) => rpc('mkdirp', [prependPath(path)], cb)
|
||||||
const rmCall = (path1, cb) => rpc('rm', [prependPath(path1)], cb)
|
const rmCall = (path1, cb) => rpc('rm', [prependPath(path1)], cb)
|
||||||
const mvCall = (path1, path2, cb) => rpc('mv', [path1, path2], cb)
|
const mvCall = (path1, path2, cb) => rpc('mv', [path1, path2], cb)
|
||||||
|
const sumCall = (path, type, cb) => rpc('sum', [prependPath(path), type], cb)
|
||||||
|
|
||||||
// File upload
|
// File upload
|
||||||
let totalDone = 0
|
let totalDone = 0
|
||||||
@@ -380,7 +384,7 @@ function resetView () {
|
|||||||
scrollToArrow()
|
scrollToArrow()
|
||||||
}
|
}
|
||||||
|
|
||||||
window.quitAll = () => helpOff() || picsOff() || videosOff() || padOff() || pdfOff()
|
window.quitAll = () => helpOff() || sumsOff() || picsOff() || videosOff() || padOff() || pdfOff()
|
||||||
|
|
||||||
// Mkdir icon
|
// Mkdir icon
|
||||||
window.mkdirBtn = function () {
|
window.mkdirBtn = function () {
|
||||||
@@ -656,6 +660,40 @@ function helpOff () {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// checksums
|
||||||
|
function getSum (type) {
|
||||||
|
upBarPc.style.display = 'block'
|
||||||
|
upBarPc.innerText = 'computing checksum...'
|
||||||
|
upBarPc.style.width = '100%'
|
||||||
|
sumsOff()
|
||||||
|
sumCall(getASelected().innerText, type, loaded => {
|
||||||
|
navigator.clipboard.writeText(loaded.target.responseText)
|
||||||
|
upBarPc.style.display = 'none'
|
||||||
|
flicker(okBadge)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const isSumsMode = () => sums.style.display === 'block'
|
||||||
|
|
||||||
|
const sumsToggle = () => isSumsMode() ? sumsOff() : sumsOn()
|
||||||
|
|
||||||
|
function sumsOn () {
|
||||||
|
if (isFolder(getASelected())) {
|
||||||
|
alert('cannot checksum a directory')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sums.style.display = 'block'
|
||||||
|
table.style.display = 'none'
|
||||||
|
}
|
||||||
|
|
||||||
|
window.sumsOff = sumsOff
|
||||||
|
function sumsOff () {
|
||||||
|
if (!isSumsMode()) return
|
||||||
|
sums.style.display = 'none'
|
||||||
|
table.style.display = 'table'
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// Paste handler
|
// Paste handler
|
||||||
const cuts = []
|
const cuts = []
|
||||||
function onPaste () {
|
function onPaste () {
|
||||||
@@ -746,40 +784,69 @@ document.body.addEventListener('keydown', e => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ctrl keys
|
// Modifier keys
|
||||||
if ((e.ctrlKey || e.metaKey) && !e.shiftKey) {
|
if (!e.shiftKey) {
|
||||||
switch (e.code) {
|
if (e.ctrlKey || e.metaKey) {
|
||||||
case 'KeyC':
|
switch (e.code) {
|
||||||
return prevent(e) || isRo() || cpPath()
|
case 'KeyC':
|
||||||
|
return prevent(e) || isRo() || cpPath()
|
||||||
|
|
||||||
case 'KeyH':
|
case 'KeyH':
|
||||||
return prevent(e) || isRo() || helpToggle()
|
return prevent(e) || isRo() || helpToggle()
|
||||||
|
|
||||||
case 'KeyX':
|
case 'KeyZ':
|
||||||
return prevent(e) || isRo() || onCut()
|
return prevent(e) || isRo() || sumsToggle()
|
||||||
|
|
||||||
case 'KeyR':
|
case 'KeyX':
|
||||||
return prevent(e) || refresh()
|
return prevent(e) || isRo() || onCut()
|
||||||
|
|
||||||
case 'KeyV':
|
case 'KeyR':
|
||||||
return prevent(e) || isRo() || ensureMove() || onPaste()
|
return prevent(e) || refresh()
|
||||||
|
|
||||||
case 'Backspace':
|
case 'KeyV':
|
||||||
return prevent(e) || isRo() || window.rm(e)
|
return prevent(e) || isRo() || ensureMove() || onPaste()
|
||||||
|
|
||||||
case 'KeyE':
|
case 'Backspace':
|
||||||
return prevent(e) || isRo() || window.rename(e)
|
return prevent(e) || isRo() || window.rm(e)
|
||||||
|
|
||||||
case 'KeyM':
|
case 'KeyE':
|
||||||
return prevent(e) || isRo() || window.mkdirBtn()
|
return prevent(e) || isRo() || window.rename(e)
|
||||||
|
|
||||||
case 'KeyU':
|
case 'KeyM':
|
||||||
return prevent(e) || isRo() || manualUpload.click()
|
return prevent(e) || isRo() || window.mkdirBtn()
|
||||||
|
|
||||||
case 'Enter':
|
case 'KeyU':
|
||||||
case 'ArrowRight':
|
return prevent(e) || isRo() || manualUpload.click()
|
||||||
return prevent(e) || dl(getASelected())
|
|
||||||
|
case 'Enter':
|
||||||
|
case 'ArrowRight':
|
||||||
|
return prevent(e) || dl(getASelected())
|
||||||
|
}
|
||||||
|
} else if (isSumsMode()) {
|
||||||
|
switch (e.code) {
|
||||||
|
case 'Digit1':
|
||||||
|
return prevent(e) || isRo() || getSum('sha1')
|
||||||
|
|
||||||
|
case 'Digit2':
|
||||||
|
return prevent(e) || isRo() || getSum('sha256')
|
||||||
|
|
||||||
|
case 'Digit3':
|
||||||
|
return prevent(e) || isRo() || getSum('sha512')
|
||||||
|
|
||||||
|
case 'Digit5':
|
||||||
|
return prevent(e) || isRo() || getSum('md5')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// Workaround Firefox requirement for transient activation
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Web/Security/User_activation
|
||||||
|
// Firefox requires user interaction (that is not reserved by the user agent)
|
||||||
|
// before a file picker can be displayed. This means that ctrl/meta are not
|
||||||
|
// usable as modifiers until the user clicks the page or presses another
|
||||||
|
// non-modifier key. To work around this, the shift key can be used, instead.
|
||||||
|
if (e.code == 'KeyU') {
|
||||||
|
return prevent(e) || isRo() || manualUpload.click()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (e.code) {
|
switch (e.code) {
|
||||||
@@ -847,5 +914,12 @@ function init () {
|
|||||||
const matchingA = allA.find(a => a.href === cleanURL)
|
const matchingA = allA.find(a => a.href === cleanURL)
|
||||||
padOn(matchingA)
|
padOn(matchingA)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check if we're at root path
|
||||||
|
if (location.pathname === window.extraPath + '/') {
|
||||||
|
helpMsg.style.display = 'block'
|
||||||
|
} else {
|
||||||
|
helpMsg.style.display = 'none'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
init()
|
init()
|
||||||
|
|||||||
Vendored
+21
-9
@@ -24,6 +24,9 @@
|
|||||||
.file-size {
|
.file-size {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
#help_message {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* hello standards 👋🏻 */
|
/* hello standards 👋🏻 */
|
||||||
@@ -341,12 +344,8 @@ h1 > span:hover {
|
|||||||
right: 30px;
|
right: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#helpHead {
|
#helpTable,
|
||||||
margin-top: 60px;
|
#sumsTable {
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
#helpTable {
|
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
width: 70%;
|
width: 70%;
|
||||||
max-width: 790px;
|
max-width: 790px;
|
||||||
@@ -357,7 +356,8 @@ h1 > span:hover {
|
|||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
#helpTable td {
|
#helpTable td,
|
||||||
|
#sumsTable td {
|
||||||
width: 200px;
|
width: 200px;
|
||||||
padding: 9px;
|
padding: 9px;
|
||||||
border: 1px solid #fff;
|
border: 1px solid #fff;
|
||||||
@@ -366,7 +366,8 @@ h1 > span:hover {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#help {
|
#help,
|
||||||
|
#sums {
|
||||||
background-color: black;
|
background-color: black;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0px;
|
top: 0px;
|
||||||
@@ -388,6 +389,18 @@ h1 > span:hover {
|
|||||||
z-index: 101;
|
z-index: 101;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#help_message {
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 14px;
|
||||||
|
position: fixed;
|
||||||
|
bottom: 5px;
|
||||||
|
right: 13px;
|
||||||
|
opacity: 50%;
|
||||||
|
user-select: none;
|
||||||
|
display: none;
|
||||||
|
margin: unset;
|
||||||
|
}
|
||||||
|
|
||||||
.icon-large-folder {
|
.icon-large-folder {
|
||||||
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgBAMAAACBVGfHAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAJFBMVEUAAACZmQAICAj4+Pj//5nMzGb/+/AAAAD/zJkzMwAAAP////+gebovAAAAAXRSTlMAQObYZgAAAAFiS0dECx/XxMAAAAAHdElNRQfiBhgXAy5jJ3EAAAAA1klEQVQoz42RMQ7CMBAEQ0mZIIp0/AGecJFO7kC2FKXmATxikaxUtPS48Ss5XxwnoWI7j3f3fHJV/aFa1KzOuwsRdacFHCjpthjuChbL0U261ipJnDd65IokJhCaGTCzEOZ25SAwaAUgASoOyFFzxWEsEkQGz2BskFJfHMMwWB07AVAXg/QwZscYPzHqXAVicDE4eJ47QOOoHexzhINxQRxIEVkC6I217OGLg9DLIn7qkHHEWiAvEwAdJyQlNKK3gKfcITuxZjzrO+qt2mr/3uj1z1//6AsSG3mwAQQN9wAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxOC0wNi0yNFQyMzowMzo0Ni0wNDowMFfZ8nYAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTgtMDYtMjRUMjM6MDM6NDYtMDQ6MDAmhErKAAAAAElFTkSuQmCC");
|
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgBAMAAACBVGfHAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAJFBMVEUAAACZmQAICAj4+Pj//5nMzGb/+/AAAAD/zJkzMwAAAP////+gebovAAAAAXRSTlMAQObYZgAAAAFiS0dECx/XxMAAAAAHdElNRQfiBhgXAy5jJ3EAAAAA1klEQVQoz42RMQ7CMBAEQ0mZIIp0/AGecJFO7kC2FKXmATxikaxUtPS48Ss5XxwnoWI7j3f3fHJV/aFa1KzOuwsRdacFHCjpthjuChbL0U261ipJnDd65IokJhCaGTCzEOZ25SAwaAUgASoOyFFzxWEsEkQGz2BskFJfHMMwWB07AVAXg/QwZscYPzHqXAVicDE4eJ47QOOoHexzhINxQRxIEVkC6I217OGLg9DLIn7qkHHEWiAvEwAdJyQlNKK3gKfcITuxZjzrO+qt2mr/3uj1z1//6AsSG3mwAQQN9wAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxOC0wNi0yNFQyMzowMzo0Ni0wNDowMFfZ8nYAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTgtMDYtMjRUMjM6MDM6NDYtMDQ6MDAmhErKAAAAAElFTkSuQmCC");
|
||||||
}
|
}
|
||||||
@@ -466,4 +479,3 @@ h1 > span:hover {
|
|||||||
.icon-dl {
|
.icon-dl {
|
||||||
background-image: url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48IS0tIFVwbG9hZGVkIHRvOiBTVkcgUmVwbywgd3d3LnN2Z3JlcG8uY29tLCBHZW5lcmF0b3I6IFNWRyBSZXBvIE1peGVyIFRvb2xzIC0tPg0KPHN2ZyB3aWR0aD0iODAwcHgiIGhlaWdodD0iODAwcHgiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4NCjxwYXRoIGQ9Ik0yMCAxNVYxOEMyMCAxOS4xMDQ2IDE5LjEwNDYgMjAgMTggMjBINkM0Ljg5NTQzIDIwIDQgMTkuMTA0NiA0IDE4TDQgMTVNOCAxMUwxMiAxNU0xMiAxNUwxNiAxMU0xMiAxNVYzIiBzdHJva2U9IiNmZmYiIHN0cm9rZS13aWR0aD0iMS41IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz4NCjwvc3ZnPg==");
|
background-image: url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48IS0tIFVwbG9hZGVkIHRvOiBTVkcgUmVwbywgd3d3LnN2Z3JlcG8uY29tLCBHZW5lcmF0b3I6IFNWRyBSZXBvIE1peGVyIFRvb2xzIC0tPg0KPHN2ZyB3aWR0aD0iODAwcHgiIGhlaWdodD0iODAwcHgiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4NCjxwYXRoIGQ9Ik0yMCAxNVYxOEMyMCAxOS4xMDQ2IDE5LjEwNDYgMjAgMTggMjBINkM0Ljg5NTQzIDIwIDQgMTkuMTA0NiA0IDE4TDQgMTVNOCAxMUwxMiAxNU0xMiAxNUwxNiAxMU0xMiAxNVYzIiBzdHJva2U9IiNmZmYiIHN0cm9rZS13aWR0aD0iMS41IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz4NCjwvc3ZnPg==");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Vendored
+13
-3
@@ -4,7 +4,7 @@
|
|||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="theme-color" content="rgb(45,52,54)">
|
<meta name="theme-color" content="rgb(45,52,54)">
|
||||||
<meta name="msapplication-navbutton-color" content="rgb(45,52,54)">
|
<meta name="msapplication-navbutton-color" content="rgb(45,52,54)">
|
||||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
<meta name="mobile-web-app-capable" content="yes">
|
||||||
<meta name="viewport" content="width=device-width">
|
<meta name="viewport" content="width=device-width">
|
||||||
|
|
||||||
<link rel="manifest" href='data:application/manifest+json,{"name":"{{.Title}}","short_name":"{{.Title}}","description":" ","icons":[{"src":"data:image/svg+xml;base64,favicon_will_be_here","sizes":"150x150","type":"image/svg+xml"}],"background":"rgb(45,52,54)","theme_color":"rgb(45,52,54)","display":"standalone"}' />
|
<link rel="manifest" href='data:application/manifest+json,{"name":"{{.Title}}","short_name":"{{.Title}}","description":" ","icons":[{"src":"data:image/svg+xml;base64,favicon_will_be_here","sizes":"150x150","type":"image/svg+xml"}],"background":"rgb(45,52,54)","theme_color":"rgb(45,52,54)","display":"standalone"}' />
|
||||||
@@ -26,10 +26,11 @@
|
|||||||
<tr><td>Ctrl/Meta + C</td><td>copy URL to clipboard</td></tr>
|
<tr><td>Ctrl/Meta + C</td><td>copy URL to clipboard</td></tr>
|
||||||
<tr><td>Ctrl/Meta + E</td><td>rename item</td></tr>
|
<tr><td>Ctrl/Meta + E</td><td>rename item</td></tr>
|
||||||
<tr><td>Ctrl/Meta + Backspace</td><td>delete item</td></tr>
|
<tr><td>Ctrl/Meta + Backspace</td><td>delete item</td></tr>
|
||||||
<tr><td>Ctrl/Meta + U</td><td>upload new file/folder</td></tr>
|
<tr><td>Ctrl/Meta/Shift + U</td><td>upload new file/folder</td></tr>
|
||||||
<tr><td>Ctrl/Meta + M</td><td>create a new directory</td></tr>
|
<tr><td>Ctrl/Meta + M</td><td>create a new directory</td></tr>
|
||||||
<tr><td>Ctrl/Meta + X</td><td>cut selected path</td></tr>
|
<tr><td>Ctrl/Meta + X</td><td>cut selected path</td></tr>
|
||||||
<tr><td>Ctrl/Meta + V</td><td>paste previously selected paths to directory</td></tr>
|
<tr><td>Ctrl/Meta + V</td><td>paste previously selected paths to directory</td></tr>
|
||||||
|
<tr><td>Ctrl/Meta + Z</td><td>copy checksums of selected file</td></tr>
|
||||||
<tr><td>Ctrl + click</td><td>download selected item as archive</td></tr>
|
<tr><td>Ctrl + click</td><td>download selected item as archive</td></tr>
|
||||||
<tr><td>click file icon </td><td>rename item</td></tr>
|
<tr><td>click file icon </td><td>rename item</td></tr>
|
||||||
<tr><td>double click file icon</td><td>delete item</td></tr>
|
<tr><td>double click file icon</td><td>delete item</td></tr>
|
||||||
@@ -38,10 +39,18 @@
|
|||||||
<tr><td>any other letter</td><td>fuzzy search</td></tr>
|
<tr><td>any other letter</td><td>fuzzy search</td></tr>
|
||||||
</tbody></table></div>
|
</tbody></table></div>
|
||||||
|
|
||||||
|
<div onclick="window.sumsOff()" style="display: none;" id="sums"><table id="sumsTable"><tbody>
|
||||||
|
<tr><td>Key</td><td>Hash Algorithm</td></tr>
|
||||||
|
<tr><td>1</td><td>copy sha1 sum</td></tr>
|
||||||
|
<tr><td>2</td><td>copy sha256 sum</td></tr>
|
||||||
|
<tr><td>3</td><td>copy sha512 sum</td></tr>
|
||||||
|
<tr><td>5</td><td>copy md5 sum</td></tr>
|
||||||
|
</tbody></table></div>
|
||||||
|
|
||||||
<div style="display: none;" onclick="window.quitAll()" id="quitAll"><i style="display: none;" id="toast">cant reach server</i></div>
|
<div style="display: none;" onclick="window.quitAll()" id="quitAll"><i style="display: none;" id="toast">cant reach server</i></div>
|
||||||
<textarea style="display: none;" id="text-editor"></textarea>
|
<textarea style="display: none;" id="text-editor"></textarea>
|
||||||
<div id="drop-grid"></div>
|
<div id="drop-grid"></div>
|
||||||
<input type="file" id="clickupload" style="display:none"/>
|
<input type="file" id="clickupload" multiple style="display:none"/>
|
||||||
|
|
||||||
<h1 onclick="return titleClick(event)">.{{.Title}}</h1>
|
<h1 onclick="return titleClick(event)">.{{.Title}}</h1>
|
||||||
|
|
||||||
@@ -78,6 +87,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
{{end}}
|
{{end}}
|
||||||
</table>
|
</table>
|
||||||
|
<p id="help_message">Help: Ctrl/Cmd + h<p>
|
||||||
</body>
|
</body>
|
||||||
<div id="upBar" class="bar">
|
<div id="upBar" class="bar">
|
||||||
<span style="display: none;" class="barName" id="upBarName"></span>
|
<span style="display: none;" class="barName" id="upBarName"></span>
|
||||||
|
|||||||
Reference in New Issue
Block a user