forked from tangled.org/core
Monorepo for Tangled

appview: repo/index: add ref selector dropdown

nix: builds for repoguard and keyfetch

anirudh.fi 5e314cec 32c6cef1

verified
Changed files
+307 -172
appview
pages
templates
repo
state
knotserver
types
+2
appview/pages/pages.go
··· 237 237 LoggedInUser *auth.User 238 238 RepoInfo RepoInfo 239 239 Active string 240 + Branches []types.Branch 241 + Tags []*types.TagReference 240 242 types.RepoIndexResponse 241 243 } 242 244
+124 -67
appview/pages/templates/repo/index.html
··· 1 - {{define "repoContent"}} 1 + {{ define "repoContent" }} 2 + <main> 3 + {{- if .IsEmpty }} 4 + this repo is empty 5 + {{ else }} 6 + <div class="flex gap-4"> 7 + <div id="file-tree" class="w-1/2"> 8 + {{ $containerstyle := "py-1" }} 9 + {{ $linkstyle := "no-underline hover:underline" }} 2 10 3 - <main> 4 - {{- if .IsEmpty }} 5 - this repo is empty 6 - {{ else }} 7 - <div class="flex gap-4"> 8 - <div id="file-tree" class="w-1/2"> 9 - {{ $containerstyle := "py-1" }} 10 - {{ $linkstyle := "no-underline hover:underline" }} 11 11 12 - {{ range .Files }} 13 - {{ if not .IsFile }} 14 - <div class="{{ $containerstyle }}"> 15 - <a href="/{{ $.RepoInfo.FullName }}/tree/{{ $.Ref }}/{{ .Name }}" class="{{ $linkstyle }}"> 16 - <div class="flex items-center gap-2"> 17 - <i class="w-3 h-3 fill-current" data-lucide="folder"></i>{{ .Name }}/ 18 - </div> 19 - </a> 20 - </div> 21 - {{ end }} 22 - {{ end }} 12 + <div class="flex justify-end"> 13 + <select 14 + hx-get="/{{ .RepoInfo.FullName }}/tree/" 15 + hx-on::config-request = "event.detail.path += this.value" 16 + hx-trigger="change" 17 + hx-target="#repo-content" 18 + hx-select="#repo-content" 19 + hx-push-url="true" 20 + class="p-1 border border-gray-500 bg-white" 21 + > 22 + <optgroup label="branches" class="font-semibold"> 23 + {{ range .Branches }} 24 + <option 25 + value="{{ .Reference.Name }}" 26 + class="py-1" 27 + > 28 + {{ .Reference.Name }} 29 + </option> 30 + {{ end }} 31 + </optgroup> 32 + <optgroup label="tags" class="font-semibold"> 33 + {{ range .Tags }} 34 + <option 35 + value="{{ .Reference.Name }}" 36 + class="py-1" 37 + > 38 + {{ .Reference.Name }} 39 + </option> 40 + {{ end }} 41 + </optgroup> 42 + </select> 43 + </div> 23 44 24 - {{ range .Files }} 25 - {{ if .IsFile }} 26 - <div class="{{ $containerstyle }}"> 27 - <a href="/{{ $.RepoInfo.FullName }}/blob/{{ $.Ref }}/{{ .Name }}" class="{{ $linkstyle }}"> 28 - <div class="flex items-center gap-2"> 29 - <i class="w-3 h-3" data-lucide="file"></i>{{ .Name }} 30 - </div> 31 - </a> 32 - </div> 33 - {{ end }} 34 - {{ end }} 35 - </div> 36 - <div id="commit-log" class="flex-1"> 37 - {{ range .Commits }} 38 - <div class= 39 - "relative 45 + <section id="repo-content"> 46 + {{ range .Files }} 47 + {{ if not .IsFile }} 48 + <div class="{{ $containerstyle }}"> 49 + <a 50 + href="/{{ $.RepoInfo.FullName }}/tree/{{ $.Ref }}/{{ .Name }}" 51 + class="{{ $linkstyle }}" 52 + > 53 + <div class="flex items-center gap-2"> 54 + <i 55 + class="w-3 h-3 fill-current" 56 + data-lucide="folder" 57 + ></i 58 + >{{ .Name }}/ 59 + </div> 60 + </a> 61 + </div> 62 + {{ end }} 63 + {{ end }} 64 + 65 + {{ range .Files }} 66 + {{ if .IsFile }} 67 + <div class="{{ $containerstyle }}"> 68 + <a 69 + href="/{{ $.RepoInfo.FullName }}/blob/{{ $.Ref }}/{{ .Name }}" 70 + class="{{ $linkstyle }}" 71 + > 72 + <div class="flex items-center gap-2"> 73 + <i 74 + class="w-3 h-3" 75 + data-lucide="file" 76 + ></i 77 + >{{ .Name }} 78 + </div> 79 + </a> 80 + </div> 81 + {{ end }} 82 + {{ end }} 83 + </div> 84 + <div id="commit-log" class="flex-1"> 85 + {{ range .Commits }} 86 + <div 87 + class="relative 40 88 px-4 41 89 py-4 42 90 border-l 43 91 border-black 44 - before:content-[''] 45 - before:absolute 92 + before:content-[''] 93 + before:absolute 46 94 before:w-1 47 95 before:h-1 48 96 before:bg-black ··· 64 112 </div> 65 113 </div> 66 114 67 - <div class="text-xs text-gray-500"> 68 - <span class="font-mono"> 69 - <a href="/{{ $.RepoInfo.FullName }}/commit/{{ .Hash.String }}" class="text-gray-500 no-underline hover:underline">{{ slice .Hash.String 0 8 }}</a> 70 - </span> 71 - &nbsp;·&nbsp; 72 - <span> 73 - <a href="mailto:{{ .Author.Email }}" class="text-gray-500 no-underline hover:underline">{{ .Author.Name }}</a> 74 - </span> 75 - &nbsp;·&nbsp; 76 - <span>{{ timeFmt .Author.When }}</span> 77 - </div> 115 + <div class="text-xs text-gray-500"> 116 + <span class="font-mono"> 117 + <a 118 + href="/{{ $.RepoInfo.FullName }}/commit/{{ .Hash.String }}" 119 + class="text-gray-500 no-underline hover:underline" 120 + >{{ slice .Hash.String 0 8 }}</a 121 + > 122 + </span> 123 + &nbsp;·&nbsp; 124 + <span> 125 + <a 126 + href="mailto:{{ .Author.Email }}" 127 + class="text-gray-500 no-underline hover:underline" 128 + >{{ .Author.Name }}</a 129 + > 130 + </span> 131 + &nbsp;·&nbsp; 132 + <span>{{ timeFmt .Author.When }}</span> 133 + </div> 134 + </div> 135 + {{ end }} 136 + </div> 137 + </div> 138 + </section> 139 + {{- if .Readme }} 140 + <article class="readme"> 141 + {{- .Readme -}} 142 + </article> 143 + {{- end -}} 144 + {{- end -}} 78 145 79 - </div> 80 - {{ end }} 81 - </div> 82 - </div> 83 - {{- if .Readme }} 84 - <article class="readme"> 85 - {{- .Readme -}} 86 - </article> 87 - {{- end -}} 88 - {{- end -}} 89 146 90 - <div class="clone-url"> 91 - <strong>clone</strong> 92 - <pre> 147 + <div class="clone-url"> 148 + <strong>clone</strong> 149 + <pre> 93 150 git clone https://tangled.sh/{{ .RepoInfo.OwnerWithAt }}/{{ .RepoInfo.Name }} 94 - </pre> 95 - </div> 96 - </main> 97 - {{end}} 98 - 151 + </pre 152 + > 153 + </div> 154 + </main> 155 + {{ end }}
+50 -1
appview/state/repo.go
··· 18 18 ) 19 19 20 20 func (s *State) RepoIndex(w http.ResponseWriter, r *http.Request) { 21 + ref := chi.URLParam(r, "ref") 21 22 f, err := fullyResolvedRepo(r) 22 23 if err != nil { 23 24 log.Println("failed to fully resolve repo", err) 24 25 return 25 26 } 27 + var reqUrl string 28 + if ref != "" { 29 + reqUrl = fmt.Sprintf("http://%s/%s/%s/tree/%s", f.Knot, f.OwnerDid(), f.RepoName, ref) 30 + } else { 31 + reqUrl = fmt.Sprintf("http://%s/%s/%s", f.Knot, f.OwnerDid(), f.RepoName) 32 + } 26 33 27 - resp, err := http.Get(fmt.Sprintf("http://%s/%s/%s", f.Knot, f.OwnerDid(), f.RepoName)) 34 + resp, err := http.Get(reqUrl) 28 35 if err != nil { 29 36 s.pages.Error503(w) 30 37 log.Println("failed to reach knotserver", err) ··· 45 52 return 46 53 } 47 54 55 + branchesResp, err := http.Get(fmt.Sprintf("http://%s/%s/%s/branches", f.Knot, f.OwnerDid(), f.RepoName)) 56 + if err != nil { 57 + log.Println("failed to reach knotserver for branches", err) 58 + return 59 + } 60 + defer branchesResp.Body.Close() 61 + 62 + tagsResp, err := http.Get(fmt.Sprintf("http://%s/%s/%s/tags", f.Knot, f.OwnerDid(), f.RepoName)) 63 + if err != nil { 64 + log.Println("failed to reach knotserver for tags", err) 65 + return 66 + } 67 + defer tagsResp.Body.Close() 68 + 69 + branchesBody, err := io.ReadAll(branchesResp.Body) 70 + if err != nil { 71 + log.Println("failed to read branches response", err) 72 + return 73 + } 74 + 75 + tagsBody, err := io.ReadAll(tagsResp.Body) 76 + if err != nil { 77 + log.Println("failed to read tags response", err) 78 + return 79 + } 80 + 81 + var branchesResult types.RepoBranchesResponse 82 + err = json.Unmarshal(branchesBody, &branchesResult) 83 + if err != nil { 84 + log.Println("failed to parse branches response", err) 85 + return 86 + } 87 + 88 + var tagsResult types.RepoTagsResponse 89 + err = json.Unmarshal(tagsBody, &tagsResult) 90 + if err != nil { 91 + log.Println("failed to parse tags response", err) 92 + return 93 + } 94 + 48 95 log.Println(resp.Status, result) 49 96 50 97 user := s.auth.GetUser(r) ··· 57 104 SettingsAllowed: settingsAllowed(s, user, f), 58 105 }, 59 106 RepoIndexResponse: result, 107 + Branches: branchesResult.Branches, 108 + Tags: tagsResult.Tags, 60 109 }) 61 110 62 111 return
+1
appview/state/state.go
··· 685 685 r.Get("/", s.RepoIndex) 686 686 r.Get("/log/{ref}", s.RepoLog) 687 687 r.Route("/tree/{ref}", func(r chi.Router) { 688 + r.Get("/", s.RepoIndex) 688 689 r.Get("/*", s.RepoTree) 689 690 }) 690 691 r.Get("/commit/{ref}", s.RepoCommit)
+122 -97
flake.nix
··· 12 12 flake = false; 13 13 }; 14 14 lucide-src = { 15 - url = "https://unpkg.com/lucide@latest"; 16 - flake = false; 15 + url = "https://unpkg.com/lucide@latest"; 16 + flake = false; 17 17 }; 18 18 gitignore = { 19 19 url = "github:hercules-ci/gitignore.nix"; ··· 21 21 }; 22 22 }; 23 23 24 - outputs = { 25 - self, 26 - nixpkgs, 27 - indigo, 28 - htmx-src, 29 - lucide-src, 30 - gitignore, 31 - }: let 32 - supportedSystems = ["x86_64-linux" "x86_64-darwin" "aarch64-linux" "aarch64-darwin"]; 33 - forAllSystems = nixpkgs.lib.genAttrs supportedSystems; 34 - nixpkgsFor = forAllSystems (system: 35 - import nixpkgs { 36 - inherit system; 37 - overlays = [self.overlays.default]; 38 - }); 39 - inherit (gitignore.lib) gitignoreSource; 40 - in { 41 - overlays.default = final: prev: { 42 - indigo-lexgen = with final; 43 - final.buildGoModule { 44 - pname = "indigo-lexgen"; 45 - version = "0.1.0"; 46 - src = indigo; 47 - subPackages = ["cmd/lexgen"]; 48 - vendorHash = "sha256-pGc29fgJFq8LP7n/pY1cv6ExZl88PAeFqIbFEhB3xXs="; 49 - doCheck = false; 50 - }; 24 + outputs = 25 + { self 26 + , nixpkgs 27 + , indigo 28 + , htmx-src 29 + , lucide-src 30 + , gitignore 31 + , 32 + }: 33 + let 34 + supportedSystems = [ "x86_64-linux" "x86_64-darwin" "aarch64-linux" "aarch64-darwin" ]; 35 + forAllSystems = nixpkgs.lib.genAttrs supportedSystems; 36 + nixpkgsFor = forAllSystems (system: 37 + import nixpkgs { 38 + inherit system; 39 + overlays = [ self.overlays.default ]; 40 + }); 41 + inherit (gitignore.lib) gitignoreSource; 42 + in 43 + { 44 + overlays.default = final: prev: { 45 + indigo-lexgen = with final; 46 + final.buildGoModule { 47 + pname = "indigo-lexgen"; 48 + version = "0.1.0"; 49 + src = indigo; 50 + subPackages = [ "cmd/lexgen" ]; 51 + vendorHash = "sha256-pGc29fgJFq8LP7n/pY1cv6ExZl88PAeFqIbFEhB3xXs="; 52 + doCheck = false; 53 + }; 51 54 52 - appview = with final; 53 - final.pkgsStatic.buildGoModule { 54 - pname = "appview"; 55 - version = "0.1.0"; 56 - src = gitignoreSource ./.; 57 - postConfigureHook = '' 58 - cp -f ${htmx-src} appview/pages/static/htmx.min.js 59 - cp -f ${lucide-src} appview/pages/static/lucide.min.js 60 - ${pkgs.tailwindcss}/bin/tailwindcss -i input.css -o appview/pages/static/tw.css 61 - ''; 62 - subPackages = ["cmd/appview"]; 63 - vendorHash = "sha256-t7lWrCyFWCI7zjUcC6XNWzCrUPSCFnFu9gTmRTsYrz0="; 64 - env.CGO_ENABLED = 1; 65 - stdenv = pkgsStatic.stdenv; 66 - }; 67 - knotserver = with final; 55 + appview = with final; 56 + final.pkgsStatic.buildGoModule { 57 + pname = "appview"; 58 + version = "0.1.0"; 59 + src = gitignoreSource ./.; 60 + postConfigureHook = '' 61 + cp -f ${htmx-src} appview/pages/static/htmx.min.js 62 + cp -f ${lucide-src} appview/pages/static/lucide.min.js 63 + ${pkgs.tailwindcss}/bin/tailwindcss -i input.css -o appview/pages/static/tw.css 64 + ''; 65 + subPackages = [ "cmd/appview" ]; 66 + vendorHash = "sha256-t7lWrCyFWCI7zjUcC6XNWzCrUPSCFnFu9gTmRTsYrz0="; 67 + env.CGO_ENABLED = 1; 68 + stdenv = pkgsStatic.stdenv; 69 + }; 70 + knotserver = with final; 71 + final.pkgsStatic.buildGoModule { 72 + pname = "knotserver"; 73 + version = "0.1.0"; 74 + src = gitignoreSource ./.; 75 + subPackages = [ "cmd/knotserver" ]; 76 + vendorHash = "sha256-t7lWrCyFWCI7zjUcC6XNWzCrUPSCFnFu9gTmRTsYrz0="; 77 + env.CGO_ENABLED = 1; 78 + }; 79 + repoguard = with final; 80 + final.pkgsStatic.buildGoModule { 81 + pname = "repoguard"; 82 + version = "0.1.0"; 83 + src = gitignoreSource ./.; 84 + subPackages = [ "cmd/repoguard" ]; 85 + vendorHash = "sha256-t7lWrCyFWCI7zjUcC6XNWzCrUPSCFnFu9gTmRTsYrz0="; 86 + env.CGO_ENABLED = 0; 87 + }; 88 + keyfetch = with final; 68 89 final.pkgsStatic.buildGoModule { 69 - pname = "knotserver"; 70 - version = "0.1.0"; 71 - src = gitignoreSource ./.; 72 - subPackages = ["cmd/knotserver"]; 73 - vendorHash = "sha256-t7lWrCyFWCI7zjUcC6XNWzCrUPSCFnFu9gTmRTsYrz0="; 74 - env.CGO_ENABLED = 1; 75 - }; 90 + pname = "keyfetch"; 91 + version = "0.1.0"; 92 + src = gitignoreSource ./.; 93 + subPackages = [ "cmd/keyfetch" ]; 94 + vendorHash = "sha256-t7lWrCyFWCI7zjUcC6XNWzCrUPSCFnFu9gTmRTsYrz0="; 95 + env.CGO_ENABLED = 0; 96 + }; 97 + }; 98 + packages = forAllSystems (system: { 99 + inherit (nixpkgsFor."${system}") indigo-lexgen appview knotserver repoguard keyfetch; 100 + }); 101 + defaultPackage = forAllSystems (system: nixpkgsFor.${system}.appview); 102 + formatter = forAllSystems (system: nixpkgsFor."${system}".alejandra); 103 + devShells = forAllSystems (system: 104 + let 105 + pkgs = nixpkgsFor.${system}; 106 + staticShell = pkgs.mkShell.override { 107 + stdenv = pkgs.pkgsStatic.stdenv; 108 + }; 109 + in 110 + { 111 + default = staticShell { 112 + nativeBuildInputs = [ 113 + pkgs.go 114 + pkgs.air 115 + pkgs.gopls 116 + pkgs.httpie 117 + pkgs.indigo-lexgen 118 + pkgs.litecli 119 + pkgs.websocat 120 + pkgs.tailwindcss 121 + ]; 122 + }; 123 + }); 124 + apps = forAllSystems (system: 125 + let 126 + pkgs = nixpkgsFor."${system}"; 127 + air-watcher = name: 128 + pkgs.writeShellScriptBin "run" 129 + '' 130 + ${pkgs.air}/bin/air -c /dev/null \ 131 + -build.cmd "${pkgs.tailwindcss}/bin/tailwindcss -i input.css -o ./appview/pages/static/tw.css && ${pkgs.go}/bin/go build -o ./out/${name}.out ./cmd/${name}/main.go" \ 132 + -build.bin "./out/${name}.out" \ 133 + -build.include_ext "go,html,css" 134 + ''; 135 + in 136 + { 137 + watch-appview = { 138 + type = "app"; 139 + program = ''${air-watcher "appview"}/bin/run''; 140 + }; 141 + watch-knotserver = { 142 + type = "app"; 143 + program = ''${air-watcher "knotserver"}/bin/run''; 144 + }; 145 + }); 76 146 }; 77 - packages = forAllSystems (system: { 78 - inherit (nixpkgsFor."${system}") indigo-lexgen appview knotserver; 79 - }); 80 - defaultPackage = forAllSystems (system: nixpkgsFor.${system}.appview); 81 - formatter = forAllSystems (system: nixpkgsFor."${system}".alejandra); 82 - devShells = forAllSystems (system: let 83 - pkgs = nixpkgsFor.${system}; 84 - staticShell = pkgs.mkShell.override { 85 - stdenv = pkgs.pkgsStatic.stdenv; 86 - }; 87 - in { 88 - default = staticShell { 89 - nativeBuildInputs = [ 90 - pkgs.go 91 - pkgs.air 92 - pkgs.gopls 93 - pkgs.httpie 94 - pkgs.indigo-lexgen 95 - pkgs.litecli 96 - pkgs.websocat 97 - pkgs.tailwindcss 98 - ]; 99 - }; 100 - }); 101 - apps = forAllSystems (system: let 102 - pkgs = nixpkgsFor."${system}"; 103 - air-watcher = name: 104 - pkgs.writeShellScriptBin "run" 105 - '' 106 - ${pkgs.air}/bin/air -c /dev/null \ 107 - -build.cmd "${pkgs.tailwindcss}/bin/tailwindcss -i input.css -o ./appview/pages/static/tw.css && ${pkgs.go}/bin/go build -o ./out/${name}.out ./cmd/${name}/main.go" \ 108 - -build.bin "./out/${name}.out" \ 109 - -build.include_ext "go,html,css" 110 - ''; 111 - in { 112 - watch-appview = { 113 - type = "app"; 114 - program = ''${air-watcher "appview"}/bin/run''; 115 - }; 116 - watch-knotserver = { 117 - type = "app"; 118 - program = ''${air-watcher "knotserver"}/bin/run''; 119 - }; 120 - }); 121 - }; 122 147 }
+5 -4
knotserver/routes.go
··· 342 342 rtags := []*types.TagReference{} 343 343 for _, tag := range tags { 344 344 tr := types.TagReference{ 345 - Ref: types.Reference{ 346 - Name: tag.Name(), 347 - Hash: tag.Hash().String(), 348 - }, 349 345 Tag: tag.TagObject(), 346 + } 347 + 348 + tr.Reference = types.Reference{ 349 + Name: tag.Name(), 350 + Hash: tag.Hash().String(), 350 351 } 351 352 352 353 if tag.Message() != "" {
+3 -3
types/repo.go
··· 39 39 } 40 40 41 41 type TagReference struct { 42 - Ref Reference `json:"ref,omitempty"` 43 - Tag *object.Tag `json:"tag,omitempty"` 44 - Message string `json:"message,omitempty"` 42 + Reference `json:"ref,omitempty"` 43 + Tag *object.Tag `json:"tag,omitempty"` 44 + Message string `json:"message,omitempty"` 45 45 } 46 46 47 47 type Reference struct {