From d4b9fd85e7c24d2875c30923e4be518c27e001ac Mon Sep 17 00:00:00 2001 From: Thomas Durieux <5577568+tdurieux@users.noreply.github.com> Date: Tue, 3 Nov 2026 15:03:23 +0000 Subject: [PATCH 1/2] fix: isolate website limits and scope explorer counts --- docs/website-request-budgets.md | 31 ++++++++ public/asset-manifest.json | 2 +- public/partials/explorer.htm | 2 +- public/script/app.js | 28 +++++-- public/script/vendor.min.js | 42 +++++------ src/config.ts | 3 + src/core/folder-counts.ts | 52 +++++++++++++ src/server/index.ts | 7 +- src/server/routes/repository-public.ts | 3 + src/server/website-rate-limit.ts | 20 +++++ test/folder-counts.test.js | 100 +++++++++++++++++++++++++ test/vue-ui.test.js | 50 +++++++++++++ test/website-rate-limit.test.js | 52 +++++++++++++ 13 files changed, 360 insertions(+), 32 deletions(-) create mode 100644 docs/website-request-budgets.md create mode 100644 src/core/folder-counts.ts create mode 100644 src/server/website-rate-limit.ts create mode 100644 test/folder-counts.test.js create mode 100644 test/website-rate-limit.test.js diff --git a/docs/website-request-budgets.md b/docs/website-request-budgets.md new file mode 100644 index 0000000..d9cf7ad --- /dev/null +++ b/docs/website-request-budgets.md @@ -0,0 +1,31 @@ +# Website requests and folder counts + +Website assets under `/w/` have a separate Redis-backed request budget. The +default `WEBSITE_RATE_LIMIT=2000` allows 2,000 requests per visitor in 15 minutes, +including signed-out visitors. Requests across repositories share this budget. +They do not consume the `/api` or `/github` allowance. Website slowdown, access +checks, file-size limits and streaming protections still apply. Rejections +include `Retry-After` and the existing JSON `rate_limited` response. + +Visitor identity uses the existing trusted-proxy address resolver. Do not trust +arbitrary forwarding headers or change `TRUST_PROXY` without verifying the +actual proxy chain. API and maintenance-token rate policies are unchanged. + +The explorer requests `/api/repo/:repoId/files/counts?path=…` after loading each +directory, once per directory per viewer initialization. The path is the same +anonymized path used for browsing. A response contains recursive file counts for +that directory and its immediate children, rather than a repository-wide map. +Counts use the captured active tree generation and sum anonymized child-name +collisions. Entering a colliding directory resolves the same original directory +as browsing it. No original paths are returned. + +The aggregation has a two-second database deadline, allows disk spill, and +returns at most 10,000 child groups plus the parent total. It still scans files +in the requested subtree; root counts can scan the whole active tree. No +cross-request cache bypasses authorization or retains counts from old snapshots. +The legacy endpoint without `path` retains its existing bounded response. + +Counts are optional. If a count request fails, the explorer continues browsing +without unavailable badges and stops issuing further count requests until the +viewer is reinitialized. It does not label the number of loaded tree entries as +the repository's total file count. File listing guards remain in place. diff --git a/public/asset-manifest.json b/public/asset-manifest.json index f9dcca6..fb0758a 100644 --- a/public/asset-manifest.json +++ b/public/asset-manifest.json @@ -1,6 +1,6 @@ { "core.min.js": "core.4a77c8ac41.min.js", - "vendor.min.js": "vendor.0b27dbacff.min.js", + "vendor.min.js": "vendor.5616fbf9b5.min.js", "mermaid.min.js": "mermaid.a7356d5ed1.min.js", "all.min.css": "all.c9afb0c819.min.css", "markdown.min.js": "markdown.6c8dfb7b1d.min.js", diff --git a/public/partials/explorer.htm b/public/partials/explorer.htm index 18e4e9d..e1a9f3c 100644 --- a/public/partials/explorer.htm +++ b/public/partials/explorer.htm @@ -21,7 +21,7 @@
Loading GitHub info for "+_.repoId+"..."),t.get("/api/admin/repos/"+_.repoId+"/github").then(C=>{m&&(m.document.open(),m.document.write('
'+JSON.stringify(C.data,null,2).replace(/[<>]/g,N=>N==="<"?"<":">")+""),m.document.close())},C=>{let N=C&&C.data?JSON.stringify(C.data,null,2):String(C);m&&(m.document.body.innerHTML='
'+N+"")})},e.statusCountFor=_=>{let m=(e.statusCounts||[]).find(C=>C._id===_);return m?m.count:0},e.statusStorageFor=_=>{let m=(e.statusCounts||[]).find(C=>C._id===_);return m?m.storage:0},e.isErrorsOnly=()=>e.query&&e.query.error&&!e.query.ready&&!e.query.preparing&&!e.query.expired&&!e.query.removed,e.toggleErrorsOnly=()=>{e.isErrorsOnly()?Object.assign(e.query,{ready:!1,preparing:!0,expired:!1,removed:!1,error:!0}):Object.assign(e.query,{ready:!1,preparing:!1,expired:!1,removed:!1,error:!0}),e.query.page=1},e.toggleSortDirection=()=>{e.query.direction=e.query.direction==="asc"?"desc":"asc"},e.sortBy=_=>{e.query.sort===_?e.query.direction=e.query.direction==="asc"?"desc":"asc":(e.query.sort=_,e.query.direction="desc"),e.query.page=1},e.sortIcon=_=>e.query.sort===_?e.query.direction==="asc"?"fa-arrow-up":"fa-arrow-down":"";let c="admin.repos.filterPrefs",f={page:1,limit:25,sort:"lastView",direction:"desc",search:"",owner:"",conference:"",dateFrom:"",dateTo:"",ready:!1,expired:!1,removed:!1,error:!0,preparing:!0},g=loadFilterPrefs(c)||{};e.query=Object.assign({},f,g,{page:1,search:""});let y=o.search();y.owner&&(e.query.owner=y.owner),y.conference&&(e.query.conference=y.conference),y.search&&(e.query.search=y.search);let b="admin.repos.presets";e.presets=JSON.parse(localStorage.getItem(b)||"[]"),e.savePreset=()=>{let _=window.prompt("Preset name:");if(!_)return;let m=Object.assign({},e.query);delete m.page,e.presets=(e.presets||[]).filter(C=>C.name!==_),e.presets.push({name:_,query:m}),localStorage.setItem(b,JSON.stringify(e.presets))},e.applyPreset=_=>{Object.assign(e.query,_.query,{page:1})},e.deletePreset=_=>{e.presets=(e.presets||[]).filter(m=>m.name!==_.name),localStorage.setItem(b,JSON.stringify(e.presets))},e.selectAllOnPage=()=>{e.allSelected=!e.allSelected,e.repositories.forEach(_=>{e.selected[_.repoId]=e.allSelected})},e.selectedCount=()=>Object.values(e.selected||{}).filter(Boolean).length,e.selectedRepos=()=>e.repositories.filter(_=>e.selected[_.repoId]),e.bulkRefresh=()=>{let _=e.selectedRepos();_.length&&confirm(`Force refresh ${_.length} repositories?`)&&_.forEach(m=>e.updateRepository(m))},e.bulkRemoveCache=()=>{let _=e.selectedRepos();_.length&&confirm(`Purge cache for ${_.length} repositories?`)&&_.forEach(m=>e.removeCache(m))},e.clearSelection=()=>{e.selected={},e.allSelected=!1},e.exportCsv=()=>{let _=new URLSearchParams(Object.entries(e.query).filter(([,m])=>m!==""&&m!==!1&&m!=null));_.set("format","csv"),_.set("limit","10000"),window.open("/api/admin/repos?"+_.toString(),"_blank")},e.removeCache=_=>{confirm("Remove cached files for "+_.repoId+"?")&&t.delete("/api/admin/repos/"+_.repoId).then(()=>h(),m=>console.error(m))},e.removeRepository=_=>{confirm("Remove repository "+_.repoId+"?")&&t.delete("/api/repo/"+_.repoId+"/").then(()=>h(),m=>console.error(m))},e.updateRepository=_=>{let m=Fe({title:`Refreshing ${_.repoId}...`,date:new Date,body:`The repository ${_.repoId} is going to be refreshed.`});e.toasts.push(m),t.post(`/api/repo/${_.repoId}/refresh`).then(C=>{C.data.status=="ready"?m.title=`${_.repoId} is refreshed.`:m.title=`Refreshing of ${_.repoId}.`},C=>{m.title=`Error during the refresh of ${_.repoId}.`,m.body=C.body})},e.fetchError=null;function h(){e.fetchError=null,t.get("/api/admin/repos",{params:e.query}).then(_=>{e.total=_.data.total,e.totalPage=Math.ceil(_.data.total/e.query.limit),e.repositories=_.data.results,e.statusCounts=_.data.statusCounts||[],e.totalSize=_.data.totalSize||0,e.allSelected=!1},_=>{e.fetchError=_&&_.data&&_.data.error||"Failed to load repositories",console.error(_)})}h();let E=null;e.watch("query",()=>{i.timeout.cancel(E),E=i.timeout(h,500);let{page:_,search:m,...C}=e.query;saveFilterPrefs(c,C),a()},!0),a()},wl=function(e,t,o){let i=ut(),s=Zt();e.Math=Math,e.watch("user.status",()=>{e.user==null&&o.url("/")}),e.user==null&&o.url("/"),e.users=[],e.total=-1,e.totalPage=0,e.statusCounts=[],e.selected={},e.allSelected=!1;let r=h=>{if(h.key==="/"&&!["INPUT","TEXTAREA","SELECT"].includes(document.activeElement?.tagName)){h.preventDefault();let E=document.querySelector('.admin-filter-toolbar input[type="search"]');E&&E.focus()}};s(document,"keydown",r),e.on("dispose",()=>document.removeEventListener("keydown",r)),e.clearFilter=h=>{h==="dateRange"?(e.query.dateFrom="",e.query.dateTo=""):e.query[h]="",e.query.page=1},e.chips=[];let a=()=>{let h=[];e.query.role&&h.push({key:"role",label:"Role",value:e.query.role}),e.chips=h};e.statusCountFor=h=>{let E=(e.statusCounts||[]).find(_=>_._id===h);return E?E.count:0},e.toggleSortDirection=()=>{e.query.direction=e.query.direction==="asc"?"desc":"asc"},e.sortBy=h=>{e.query.sort===h?e.query.direction=e.query.direction==="asc"?"desc":"asc":(e.query.sort=h,e.query.direction="desc"),e.query.page=1},e.sortIcon=h=>e.query.sort===h?e.query.direction==="asc"?"fa-arrow-up":"fa-arrow-down":"";let c="admin.users.filterPrefs",f={page:1,limit:25,sort:"username",direction:"asc",search:"",status:"",role:"",dateFrom:"",dateTo:""},g=loadFilterPrefs(c)||{};e.query=Object.assign({},f,g,{page:1,search:""}),e.selectAllOnPage=()=>{e.allSelected=!e.allSelected,e.users.forEach(h=>{e.selected[h.username]=e.allSelected})},e.selectedCount=()=>Object.values(e.selected||{}).filter(Boolean).length,e.selectedUsers=()=>e.users.filter(h=>e.selected[h.username]),e.banUser=h=>{confirm(`Ban user ${h.username}?`)&&t.post(`/api/admin/users/${h.username}/ban`).then(y,E=>console.error(E))},e.activateUser=h=>{t.post(`/api/admin/users/${h.username}/activate`).then(y,E=>console.error(E))},e.bulkBan=()=>{let h=e.selectedUsers();h.length&&confirm(`Ban ${h.length} users?`)&&h.forEach(E=>e.banUser(E))},e.exportCsv=()=>{let h=new URLSearchParams(Object.entries(e.query).filter(([,E])=>E!==""&&E!==!1&&E!=null));h.set("format","csv"),h.set("limit","10000"),window.open("/api/admin/users?"+h.toString(),"_blank")},e.fetchError=null;function y(){e.fetchError=null,t.get("/api/admin/users",{params:e.query}).then(h=>{e.total=h.data.total,e.totalPage=Math.ceil(h.data.total/e.query.limit),e.users=h.data.results,e.statusCounts=h.data.statusCounts||[],e.allSelected=!1},h=>{e.fetchError=h&&h.data&&h.data.error||"Failed to load users",console.error(h)})}y();let b=null;e.watch("query",()=>{i.timeout.cancel(b),b=i.timeout(y,500);let{page:h,search:E,..._}=e.query;saveFilterPrefs(c,_),a()},!0),a()},kl=function(e,t,o,i){let s=ut();e.watch("user.status",()=>{e.user==null&&o.url("/")}),e.user==null&&o.url("/"),e.userInfo,e.repositories=[],e.search="",e.selected={},e.allSelected=!1;let r="admin.user.filterPrefs",a={filters:{status:{ready:!0,expired:!0,removed:!0,error:!0,preparing:!0}},sort:"anonymizeDate",direction:"desc"},c=loadFilterPrefs(r)||{};e.filters={status:Object.assign({},a.filters.status,c.filters&&c.filters.status||{})},e.query={sort:c.sort||a.sort,direction:c.direction||a.direction},e.orderBy=(e.query.direction==="asc"?"":"-")+e.query.sort,e.sortBy=h=>{e.query.sort===h?e.query.direction=e.query.direction==="asc"?"desc":"asc":(e.query.sort=h,e.query.direction="desc"),e.orderBy=(e.query.direction==="asc"?"":"-")+e.query.sort},e.sortIcon=h=>e.query.sort===h?e.query.direction==="asc"?"fa-arrow-up":"fa-arrow-down":"",e.watch("query",()=>{saveFilterPrefs(r,{filters:e.filters,sort:e.query.sort,direction:e.query.direction})},!0),e.watch("filters",()=>{saveFilterPrefs(r,{filters:e.filters,sort:e.query.sort,direction:e.query.direction})},!0),e.statusCountFor=h=>(e.repositories||[]).filter(E=>E.status===h).length,e.repoFiler=h=>e.filters.status[h.status]==!1?!1:!!(e.search.trim().length==0||h.source.fullName.indexOf(e.search)>-1||h.repoId.indexOf(e.search)>-1||h.statusMessage&&h.statusMessage.indexOf(e.search)>-1||h.conference&&h.conference.indexOf(e.search)>-1),e.selectAllOnPage=()=>{e.allSelected=!e.allSelected,(e.filteredRepositories||e.repositories).forEach(h=>{e.selected[h.repoId]=e.allSelected})},e.selectedCount=()=>Object.values(e.selected||{}).filter(Boolean).length,e.selectedRepos=()=>e.repositories.filter(h=>e.selected[h.repoId]),e.bulkRefresh=()=>{let h=e.selectedRepos();h.length&&confirm(`Force refresh ${h.length} repositories?`)&&h.forEach(E=>e.updateRepository(E))},e.bulkRemoveCache=()=>{let h=e.selectedRepos();h.length&&confirm(`Purge cache for ${h.length} repositories?`)&&h.forEach(E=>e.removeCache(E))},e.clearSelection=()=>{e.selected={},e.allSelected=!1},e.exportCsv=()=>{let h=e.filteredRepositories||e.repositories,_=["repoId","status","statusMessage","pageView","anonymizeDate","source.fullName","conference","size.storage"].join(","),m=h.map(O=>[O.repoId,O.status,O.statusMessage||"",O.pageView||0,O.anonymizeDate||"",O.source&&O.source.fullName||"",O.conference||"",O.size&&O.size.storage||0].map(q=>{let L=String(q??"");return/[",\n\r]/.test(L)?'"'+L.replace(/"/g,'""')+'"':L}).join(",")),C=new Blob([_+` +`).map(h=>h.trim()).filter(h=>h.length>0),options:e.options};e.saving=!0,e.error=null,t.post("/api/user/default",b).then(()=>{a.accept(y),e.saving=!1,e.message=e.unsavedChanges?null:"Saved",f&&i.cancel(f),f=i(()=>{e.message=null},2500)},h=>{e.saving=!1;let E=h&&h.data&&h.data.error,_="Unable to save your defaults. Please try again.";Promise.all([o("ERRORS."+E),o("ERRORS.unknown_error")]).then(([m,C])=>{e.error=E&&m!==C?m:_},()=>{e.error=_})})},e.deleteAccount=()=>{e.deletingAccount||!e.confirmDeleteAccount||e.deleteUsername!==e.user?.username||(e.deletingAccount=!0,t.delete("/api/user").then(()=>{window.location.href="/"},()=>{e.deletingAccount=!1,e.deleteError="Unable to delete the account. Please try again."}))}},dl=function(e,t,o){e.repoId=null,e.repoUrl=null,e.claim=()=>{t.post("/api/repo/claim",{repoId:e.repoId,repoUrl:e.repoUrl}).then(i=>{o.url("/dashboard")},i=>{e.error=i.data,e.claimForm.repoUrl.setValidity("not_found",!1),e.claimForm.repoId.setValidity("not_found",!1)})}},ul=function(e,t,o,i,s){e.user&&!e.user.status&&o.url("/dashboard"),e.watch("user.status",()=>{e.user&&!e.user.status&&o.url("/dashboard")}),e.features=[{key:"anonymize",num:"01",eyebrow:"Anonymize",title:"Double-anonymous,",accent:"your rules.",text:"Choose what reviewers may see: links, images, PDFs, notebooks, GitHub Pages. Add your own terms, with regex if you need it, and pick the expiration date.",cta:"Start an anonymization",href:"/anonymize",url:"anonymous.4open.science/anonymize",img:"/imgs/anonymize.png",alt:"The anonymize form: source repository, terms to redact, options and a live README preview"},{key:"review",num:"02",eyebrow:"Review",title:"Reviewers browse",accent:"the real thing.",text:"Highlighted source code, rendered PDFs, images, and notebooks, in a familiar file explorer. GitHub Pages is also supported.",cta:"Open the example",href:"https://anonymous.4open.science/r/840c8c57-3c32-451e-bf12-0e20be300389/",target:"_self",url:"anonymous.4open.science/r/840c8c57-\u2026",img:"/imgs/explorer.png",alt:"The repository explorer with a file tree and a rendered README"},{key:"manage",num:"03",eyebrow:"Manage",title:"One dashboard,",accent:"until the decision.",text:"Monitor views, edit configuration, remove or update your repository. Program chairs can group submissions under a conference with one shared expiry.",cta:"Open the dashboard",href:"/dashboard",needsUser:!0,url:"anonymous.4open.science/dashboard",img:"/imgs/dashboard.png",alt:"The dashboard listing anonymized repositories with status, views and expiry"}],e.feature=e.features[0].key,e.selectFeature=function(c){e.feature=c},e.featureHref=function(c){return c.needsUser&&!e.user?"/signin":c.href},e.featureTarget=function(c){return c.needsUser&&!e.user?"_self":c.target||void 0},e.featureKeydown=function(c,f){let g={ArrowDown:1,ArrowRight:1,ArrowUp:-1,ArrowLeft:-1,Home:"first",End:"last"}[c.key];if(g===void 0)return;c.preventDefault();let y=e.features.length,b=g==="first"?0:g==="last"?y-1:(f+g+y)%y;e.feature=e.features[b].key,s(()=>{let h=i.document.getElementById("feature-tab-"+e.feature);h&&h.focus()})},e.cards=[{key:"repositories",total:0,label:"repositories anonymized"},{key:"users",total:0,label:"researchers"},{key:"pageViews",total:0,label:"page views"},{key:"pullRequests",total:0,label:"pull requests"}];function r(){t.get("/api/stat/").then(c=>{e.stat=c.data,e.cards[0].total=c.data.nbRepositories,e.cards[1].total=c.data.nbUsers,e.cards[2].total=c.data.nbPageViews,e.cards[3].total=c.data.nbPullRequests})}r();function a(c){let f={series:c,bars:[],viewW:100,deltaToday:0,pctChange:0,pctAbs:0,isUp:!0};if(!c||c.length<2)return f;let g=new Array(c.length-1);for(let m=1;m
Loading GitHub info for "+_.repoId+"..."),t.get("/api/admin/repos/"+_.repoId+"/github").then(C=>{m&&(m.document.open(),m.document.write('
'+JSON.stringify(C.data,null,2).replace(/[<>]/g,N=>N==="<"?"<":">")+""),m.document.close())},C=>{let N=C&&C.data?JSON.stringify(C.data,null,2):String(C);m&&(m.document.body.innerHTML='
'+N+"")})},e.statusCountFor=_=>{let m=(e.statusCounts||[]).find(C=>C._id===_);return m?m.count:0},e.statusStorageFor=_=>{let m=(e.statusCounts||[]).find(C=>C._id===_);return m?m.storage:0},e.isErrorsOnly=()=>e.query&&e.query.error&&!e.query.ready&&!e.query.preparing&&!e.query.expired&&!e.query.removed,e.toggleErrorsOnly=()=>{e.isErrorsOnly()?Object.assign(e.query,{ready:!1,preparing:!0,expired:!1,removed:!1,error:!0}):Object.assign(e.query,{ready:!1,preparing:!1,expired:!1,removed:!1,error:!0}),e.query.page=1},e.toggleSortDirection=()=>{e.query.direction=e.query.direction==="asc"?"desc":"asc"},e.sortBy=_=>{e.query.sort===_?e.query.direction=e.query.direction==="asc"?"desc":"asc":(e.query.sort=_,e.query.direction="desc"),e.query.page=1},e.sortIcon=_=>e.query.sort===_?e.query.direction==="asc"?"fa-arrow-up":"fa-arrow-down":"";let c="admin.repos.filterPrefs",f={page:1,limit:25,sort:"lastView",direction:"desc",search:"",owner:"",conference:"",dateFrom:"",dateTo:"",ready:!1,expired:!1,removed:!1,error:!0,preparing:!0},g=loadFilterPrefs(c)||{};e.query=Object.assign({},f,g,{page:1,search:""});let y=o.search();y.owner&&(e.query.owner=y.owner),y.conference&&(e.query.conference=y.conference),y.search&&(e.query.search=y.search);let b="admin.repos.presets";e.presets=JSON.parse(localStorage.getItem(b)||"[]"),e.savePreset=()=>{let _=window.prompt("Preset name:");if(!_)return;let m=Object.assign({},e.query);delete m.page,e.presets=(e.presets||[]).filter(C=>C.name!==_),e.presets.push({name:_,query:m}),localStorage.setItem(b,JSON.stringify(e.presets))},e.applyPreset=_=>{Object.assign(e.query,_.query,{page:1})},e.deletePreset=_=>{e.presets=(e.presets||[]).filter(m=>m.name!==_.name),localStorage.setItem(b,JSON.stringify(e.presets))},e.selectAllOnPage=()=>{e.allSelected=!e.allSelected,e.repositories.forEach(_=>{e.selected[_.repoId]=e.allSelected})},e.selectedCount=()=>Object.values(e.selected||{}).filter(Boolean).length,e.selectedRepos=()=>e.repositories.filter(_=>e.selected[_.repoId]),e.bulkRefresh=()=>{let _=e.selectedRepos();_.length&&confirm(`Force refresh ${_.length} repositories?`)&&_.forEach(m=>e.updateRepository(m))},e.bulkRemoveCache=()=>{let _=e.selectedRepos();_.length&&confirm(`Purge cache for ${_.length} repositories?`)&&_.forEach(m=>e.removeCache(m))},e.clearSelection=()=>{e.selected={},e.allSelected=!1},e.exportCsv=()=>{let _=new URLSearchParams(Object.entries(e.query).filter(([,m])=>m!==""&&m!==!1&&m!=null));_.set("format","csv"),_.set("limit","10000"),window.open("/api/admin/repos?"+_.toString(),"_blank")},e.removeCache=_=>{confirm("Remove cached files for "+_.repoId+"?")&&t.delete("/api/admin/repos/"+_.repoId).then(()=>h(),m=>console.error(m))},e.removeRepository=_=>{confirm("Remove repository "+_.repoId+"?")&&t.delete("/api/repo/"+_.repoId+"/").then(()=>h(),m=>console.error(m))},e.updateRepository=_=>{let m=Fe({title:`Refreshing ${_.repoId}...`,date:new Date,body:`The repository ${_.repoId} is going to be refreshed.`});e.toasts.push(m),t.post(`/api/repo/${_.repoId}/refresh`).then(C=>{C.data.status=="ready"?m.title=`${_.repoId} is refreshed.`:m.title=`Refreshing of ${_.repoId}.`},C=>{m.title=`Error during the refresh of ${_.repoId}.`,m.body=C.body})},e.fetchError=null;function h(){e.fetchError=null,t.get("/api/admin/repos",{params:e.query}).then(_=>{e.total=_.data.total,e.totalPage=Math.ceil(_.data.total/e.query.limit),e.repositories=_.data.results,e.statusCounts=_.data.statusCounts||[],e.totalSize=_.data.totalSize||0,e.allSelected=!1},_=>{e.fetchError=_&&_.data&&_.data.error||"Failed to load repositories",console.error(_)})}h();let E=null;e.watch("query",()=>{i.timeout.cancel(E),E=i.timeout(h,500);let{page:_,search:m,...C}=e.query;saveFilterPrefs(c,C),a()},!0),a()},wl=function(e,t,o){let i=ut(),s=Zt();e.Math=Math,e.watch("user.status",()=>{e.user==null&&o.url("/")}),e.user==null&&o.url("/"),e.users=[],e.total=-1,e.totalPage=0,e.statusCounts=[],e.selected={},e.allSelected=!1;let r=h=>{if(h.key==="/"&&!["INPUT","TEXTAREA","SELECT"].includes(document.activeElement?.tagName)){h.preventDefault();let E=document.querySelector('.admin-filter-toolbar input[type="search"]');E&&E.focus()}};s(document,"keydown",r),e.on("dispose",()=>document.removeEventListener("keydown",r)),e.clearFilter=h=>{h==="dateRange"?(e.query.dateFrom="",e.query.dateTo=""):e.query[h]="",e.query.page=1},e.chips=[];let a=()=>{let h=[];e.query.role&&h.push({key:"role",label:"Role",value:e.query.role}),e.chips=h};e.statusCountFor=h=>{let E=(e.statusCounts||[]).find(_=>_._id===h);return E?E.count:0},e.toggleSortDirection=()=>{e.query.direction=e.query.direction==="asc"?"desc":"asc"},e.sortBy=h=>{e.query.sort===h?e.query.direction=e.query.direction==="asc"?"desc":"asc":(e.query.sort=h,e.query.direction="desc"),e.query.page=1},e.sortIcon=h=>e.query.sort===h?e.query.direction==="asc"?"fa-arrow-up":"fa-arrow-down":"";let c="admin.users.filterPrefs",f={page:1,limit:25,sort:"username",direction:"asc",search:"",status:"",role:"",dateFrom:"",dateTo:""},g=loadFilterPrefs(c)||{};e.query=Object.assign({},f,g,{page:1,search:""}),e.selectAllOnPage=()=>{e.allSelected=!e.allSelected,e.users.forEach(h=>{e.selected[h.username]=e.allSelected})},e.selectedCount=()=>Object.values(e.selected||{}).filter(Boolean).length,e.selectedUsers=()=>e.users.filter(h=>e.selected[h.username]),e.banUser=h=>{confirm(`Ban user ${h.username}?`)&&t.post(`/api/admin/users/${h.username}/ban`).then(y,E=>console.error(E))},e.activateUser=h=>{t.post(`/api/admin/users/${h.username}/activate`).then(y,E=>console.error(E))},e.bulkBan=()=>{let h=e.selectedUsers();h.length&&confirm(`Ban ${h.length} users?`)&&h.forEach(E=>e.banUser(E))},e.exportCsv=()=>{let h=new URLSearchParams(Object.entries(e.query).filter(([,E])=>E!==""&&E!==!1&&E!=null));h.set("format","csv"),h.set("limit","10000"),window.open("/api/admin/users?"+h.toString(),"_blank")},e.fetchError=null;function y(){e.fetchError=null,t.get("/api/admin/users",{params:e.query}).then(h=>{e.total=h.data.total,e.totalPage=Math.ceil(h.data.total/e.query.limit),e.users=h.data.results,e.statusCounts=h.data.statusCounts||[],e.allSelected=!1},h=>{e.fetchError=h&&h.data&&h.data.error||"Failed to load users",console.error(h)})}y();let b=null;e.watch("query",()=>{i.timeout.cancel(b),b=i.timeout(y,500);let{page:h,search:E,..._}=e.query;saveFilterPrefs(c,_),a()},!0),a()},kl=function(e,t,o,i){let s=ut();e.watch("user.status",()=>{e.user==null&&o.url("/")}),e.user==null&&o.url("/"),e.userInfo,e.repositories=[],e.search="",e.selected={},e.allSelected=!1;let r="admin.user.filterPrefs",a={filters:{status:{ready:!0,expired:!0,removed:!0,error:!0,preparing:!0}},sort:"anonymizeDate",direction:"desc"},c=loadFilterPrefs(r)||{};e.filters={status:Object.assign({},a.filters.status,c.filters&&c.filters.status||{})},e.query={sort:c.sort||a.sort,direction:c.direction||a.direction},e.orderBy=(e.query.direction==="asc"?"":"-")+e.query.sort,e.sortBy=h=>{e.query.sort===h?e.query.direction=e.query.direction==="asc"?"desc":"asc":(e.query.sort=h,e.query.direction="desc"),e.orderBy=(e.query.direction==="asc"?"":"-")+e.query.sort},e.sortIcon=h=>e.query.sort===h?e.query.direction==="asc"?"fa-arrow-up":"fa-arrow-down":"",e.watch("query",()=>{saveFilterPrefs(r,{filters:e.filters,sort:e.query.sort,direction:e.query.direction})},!0),e.watch("filters",()=>{saveFilterPrefs(r,{filters:e.filters,sort:e.query.sort,direction:e.query.direction})},!0),e.statusCountFor=h=>(e.repositories||[]).filter(E=>E.status===h).length,e.repoFiler=h=>e.filters.status[h.status]==!1?!1:!!(e.search.trim().length==0||h.source.fullName.indexOf(e.search)>-1||h.repoId.indexOf(e.search)>-1||h.statusMessage&&h.statusMessage.indexOf(e.search)>-1||h.conference&&h.conference.indexOf(e.search)>-1),e.selectAllOnPage=()=>{e.allSelected=!e.allSelected,(e.filteredRepositories||e.repositories).forEach(h=>{e.selected[h.repoId]=e.allSelected})},e.selectedCount=()=>Object.values(e.selected||{}).filter(Boolean).length,e.selectedRepos=()=>e.repositories.filter(h=>e.selected[h.repoId]),e.bulkRefresh=()=>{let h=e.selectedRepos();h.length&&confirm(`Force refresh ${h.length} repositories?`)&&h.forEach(E=>e.updateRepository(E))},e.bulkRemoveCache=()=>{let h=e.selectedRepos();h.length&&confirm(`Purge cache for ${h.length} repositories?`)&&h.forEach(E=>e.removeCache(E))},e.clearSelection=()=>{e.selected={},e.allSelected=!1},e.exportCsv=()=>{let h=e.filteredRepositories||e.repositories,_=["repoId","status","statusMessage","pageView","anonymizeDate","source.fullName","conference","size.storage"].join(","),m=h.map(A=>[A.repoId,A.status,A.statusMessage||"",A.pageView||0,A.anonymizeDate||"",A.source&&A.source.fullName||"",A.conference||"",A.size&&A.size.storage||0].map(M=>{let U=String(M??"");return/[",\n\r]/.test(U)?'"'+U.replace(/"/g,'""')+'"':U}).join(",")),C=new Blob([_+` `+m.join(` -`)],{type:"text/csv"}),N=document.createElement("a");N.href=URL.createObjectURL(C),N.download=i.username+"-repositories.csv",N.click()},e.showStatusMessage=h=>{let E=h.statusMessage||"(no message)";window.prompt(`Status message for ${h.repoId} (${h.status}):`,E)},e.fetchGithubInfo=h=>{let E=window.open("","_blank");E&&E.document.write("
Loading GitHub info for "+h.repoId+"..."),t.get("/api/admin/repos/"+h.repoId+"/github").then(_=>{E&&(E.document.open(),E.document.write('
'+JSON.stringify(_.data,null,2).replace(/[<>]/g,m=>m==="<"?"<":">")+""),E.document.close())},_=>{let m=_&&_.data?JSON.stringify(_.data,null,2):String(_);E&&(E.document.body.innerHTML='
'+m+"")})};function f(h){t.get("/api/admin/users/"+h+"/repos",{}).then(E=>{e.repositories=E.data},E=>{console.error(E)})}function g(h){t.get("/api/admin/users/"+h,{}).then(E=>{e.userInfo=E.data},E=>{console.error(E)})}g(i.username),f(i.username),e.banUser=()=>{confirm(`Ban user ${i.username}?`)&&t.post(`/api/admin/users/${i.username}/ban`).then(()=>g(i.username),h=>console.error(h))},e.activateUser=()=>{t.post(`/api/admin/users/${i.username}/activate`).then(()=>g(i.username),h=>console.error(h))},e.promoteUser=()=>{confirm(`Promote ${i.username} to admin?`)&&t.post(`/api/admin/users/${i.username}/promote`).then(()=>g(i.username),h=>console.error(h))},e.demoteUser=()=>{confirm(`Remove admin privileges from ${i.username}?`)&&t.post(`/api/admin/users/${i.username}/demote`).then(()=>g(i.username),h=>console.error(h))},e.tokens=[],e.tokenForm={name:"",plaintext:null};function y(){t.get("/api/admin/tokens").then(h=>{e.tokens=h.data||[]},h=>{h.status!==401&&h.status!==403&&console.error(h)})}y(),e.createToken=()=>{e.tokenForm.name&&t.post("/api/admin/tokens",{name:e.tokenForm.name}).then(h=>{e.tokenForm.plaintext=h.data.token,e.tokenForm.name="",y()},h=>console.error(h))},e.revokeToken=h=>{confirm(`Revoke token "${h.name}"?`)&&t.delete("/api/admin/tokens/"+h.id).then(()=>y(),E=>console.error(E))},e.removeCache=h=>{confirm("Remove cached files for "+h.repoId+"?")&&t.delete("/api/admin/repos/"+h.repoId).then(()=>f(i.username),E=>console.error(E))},e.removeRepository=h=>{confirm("Remove repository "+h.repoId+"?")&&t.delete("/api/repo/"+h.repoId+"/").then(()=>f(i.username),E=>console.error(E))},e.updateRepository=h=>{let E=Fe({title:`Refreshing ${h.repoId}...`,date:new Date,body:`The repository ${h.repoId} is going to be refreshed.`});e.toasts.push(E),t.post(`/api/repo/${h.repoId}/refresh`).then(_=>{_.data.status=="ready"?E.title=`${h.repoId} is refreshed.`:E.title=`Refreshing of ${h.repoId}.`},_=>{E.title=`Error during the refresh of ${h.repoId}.`,E.body=_.body})},e.getGitHubRepositories=h=>{t.get(`/api/user/${e.userInfo.username}/all_repositories`,{params:{force:"1"}}).then(E=>{e.userInfo.repositories=E.data})};let b=null;e.watch("query",()=>{s.timeout.cancel(b),b=s.timeout(()=>{f(i.username)},500)},!0)},El=function(e,t,o){let i=ut(),s=Zt();e.Math=Math,e.watch("user.status",()=>{e.user==null&&o.url("/")}),e.user==null&&o.url("/"),e.conferences=[],e.total=-1,e.totalPage=0,e.statusCounts=[];let r=_=>{if(_.key==="/"&&!["INPUT","TEXTAREA","SELECT"].includes(document.activeElement?.tagName)){_.preventDefault();let m=document.querySelector('.admin-filter-toolbar input[type="search"]');m&&m.focus()}};s(document,"keydown",r),e.on("dispose",()=>document.removeEventListener("keydown",r)),e.clearFilter=_=>{_==="dateRange"?(e.query.dateFrom="",e.query.dateTo=""):e.query[_]="",e.query.page=1},e.chips=[];let a=()=>{let _=[];(e.query.dateFrom||e.query.dateTo)&&_.push({key:"dateRange",label:"Date",value:(e.query.dateFrom||"\u2026")+" \u2013 "+(e.query.dateTo||"\u2026")}),e.chips=_};e.statusCountFor=_=>{let m=(e.statusCounts||[]).find(C=>C._id===_);return m?m.count:0},e.toggleSortDirection=()=>{e.query.direction=e.query.direction==="asc"?"desc":"asc"},e.sortBy=_=>{e.query.sort===_?e.query.direction=e.query.direction==="asc"?"desc":"asc":(e.query.sort=_,e.query.direction="desc"),e.query.page=1},e.sortIcon=_=>e.query.sort===_?e.query.direction==="asc"?"fa-arrow-up":"fa-arrow-down":"";let c="admin.conferences.filterPrefs",f={page:1,limit:25,sort:"name",direction:"asc",search:"",dateFrom:"",dateTo:"",ready:!1,expired:!1,removed:!1,error:!0,preparing:!0},g=loadFilterPrefs(c)||{};e.query=Object.assign({},f,g,{page:1,search:""});let y=o.search();y.search&&(e.query.search=y.search);let b="admin.conferences.presets";e.presets=JSON.parse(localStorage.getItem(b)||"[]"),e.savePreset=()=>{let _=window.prompt("Preset name:");if(!_)return;let m=Object.assign({},e.query);delete m.page,e.presets=(e.presets||[]).filter(C=>C.name!==_),e.presets.push({name:_,query:m}),localStorage.setItem(b,JSON.stringify(e.presets))},e.applyPreset=_=>{Object.assign(e.query,_.query,{page:1})},e.deletePreset=_=>{e.presets=(e.presets||[]).filter(m=>m.name!==_.name),localStorage.setItem(b,JSON.stringify(e.presets))},e.removeConference=_=>{confirm("Remove conference "+_.conferenceID+"?")&&t.delete("/api/admin/conferences/"+_.conferenceID).then(()=>h(),m=>console.error(m))},e.exportCsv=()=>{let _=new URLSearchParams(Object.entries(e.query).filter(([,m])=>m!==""&&m!==!1&&m!=null));_.set("format","csv"),_.set("limit","10000"),window.open("/api/admin/conferences?"+_.toString(),"_blank")},e.fetchError=null;function h(){e.fetchError=null,t.get("/api/admin/conferences",{params:e.query}).then(_=>{e.total=_.data.total,e.totalPage=Math.ceil(_.data.total/e.query.limit),e.conferences=_.data.results,e.statusCounts=_.data.statusCounts||[]},_=>{e.fetchError=_&&_.data&&_.data.error||"Failed to load conferences",console.error(_)})}h();let E=null;e.watch("query",()=>{i.timeout.cancel(E),E=i.timeout(h,500);let{page:_,search:m,...C}=e.query;saveFilterPrefs(c,C),a()},!0),a()},Cl=function(e,t,o,i,s){let r=ut();e.watch("user.status",()=>{e.user==null&&o.url("/")}),e.user==null&&o.url("/"),e.queueList=[],e.jobs=[],e.selectedQueue="download",e.selectedStats=null,e.range="1h",e.allStates=["active","waiting","delayed","failed","completed"],e.stateFilter={active:!0,waiting:!0,delayed:!0,failed:!0,completed:!0},e.query={search:"",autoRefresh:!0},e.filteredJobs=()=>(e.jobs||[]).filter(m=>e.stateFilter[m._state]),e.jobProgressPct=m=>m&&m.progress&&typeof m.progress=="object"&&typeof m.progress.percent=="number"?Math.max(0,Math.min(100,Math.round(m.progress.percent))):typeof m.progress=="number"?Math.max(0,Math.min(100,Math.round(m.progress))):null,e.jobDuration=m=>{if(!m.processedOn)return"-";let N=(m.finishedOn||Date.now())-m.processedOn;return N<1e3?N+"ms":(N/1e3).toFixed(1)+"s"},e.metricsPoints=[],e.selectQueue=m=>{e.selectedQueue=m,a(),c()},e.setRange=m=>{e.range=m,c()};function a(){let m={queue:e.selectedQueue,search:e.query.search};t.get("/api/admin/queues",{params:m}).then(C=>{e.queueList=C.data.queues||[],e.jobs=C.data.jobs||[],e.selectedStats=e.queueList.find(N=>N.key===e.selectedQueue)||e.queueList[0]||null},C=>console.error(C))}function c(){t.get("/api/admin/queues/metrics",{params:{queue:e.selectedQueue,range:e.range}}).then(m=>{e.metricsPoints=m.data.points||[],s(h,0)},m=>console.error(m))}a(),c();let f=i(()=>{e.query.autoRefresh&&(a(),c())},15e3);e.on("dispose",()=>i.cancel(f)),e.refreshNow=function(){a(),c()};function g(m){let C=m&&m.data&&(m.data.message||m.data.error)||"Request failed";e.actionError=C,s(()=>{e.actionError=null},5e3),console.error(m)}e.actionError=null,e.removeJob=m=>{t.delete(`/api/admin/queue/${e.selectedQueue}/${m.id}`).then(a,g)},e.retryJob=m=>{t.post(`/api/admin/queue/${e.selectedQueue}/${m.id}`).then(a,g)},e.retryFailed=()=>{confirm(`Retry all failed jobs in ${e.selectedQueue}?`)&&t.post(`/api/admin/queue/${e.selectedQueue}/retry-failed`).then(a,m=>console.error(m))},e.drainSelected=()=>{confirm(`Drain the ${e.selectedQueue} queue?`)&&t.post(`/api/admin/queue/${e.selectedQueue}/drain`).then(a,m=>console.error(m))},e.togglePause=()=>{let m=e.selectedStats&&e.selectedStats.paused?"resume":"pause";t.post(`/api/admin/queue/${e.selectedQueue}/${m}`).then(a,C=>console.error(C))},e.emptyQueue=()=>{confirm(`Empty the ${e.selectedQueue} queue? This removes ALL jobs.`)&&t.post(`/api/admin/queue/${e.selectedQueue}/empty`).then(a,m=>console.error(m))},e.pauseAll=()=>{confirm("Pause all queues?")&&t.post("/api/admin/queues/pause-all").then(a,m=>console.error(m))};let y=null;e.watch("query.search",()=>{r.timeout.cancel(y),y=r.timeout(a,350)}),e.expanded={},e.toggleJob=m=>{e.expanded[m.id]=!e.expanded[m.id]},e.humanTime=m=>{if(!m)return"";let C=new Date(m);return C.toLocaleTimeString([],{hour:"2-digit",minute:"2-digit",second:"2-digit",hour12:!1})+" "+C.toLocaleDateString([],{month:"short",day:"numeric"})},e.delayCountdown=m=>{if(!m)return"";var C=Math.max(0,Math.ceil((m-Date.now())/1e3));if(C<=0)return"resuming soon";var N=Math.floor(C/60),O=C%60;return"in "+(N>0?N+"m "+O+"s":O+"s")};function b(m){if(m<=0)return{ticks:[0],niceMax:1};let C=Math.pow(10,Math.floor(Math.log10(m))),N=C;m/N<2?N=C/2:m/N>5&&(N=C*2);let O=Math.ceil(m/N)*N,q=[];for(let L=0;L<=O;L+=N)q.push(L);return{ticks:q,niceMax:O}}function h(){var m=document.getElementById("q-throughput-chart");if(!m)return;var C=m.getContext("2d"),N=window.devicePixelRatio||1,O=m.parentElement.getBoundingClientRect(),q=44,L=50,U=20,z=O.width-40,ne=180,x=z-q-L,R=ne-U;m.width=z*N,m.height=ne*N,m.style.width=z+"px",m.style.height=ne+"px",C.setTransform(N,0,0,N,0,0);var V=document.body.classList.contains("dark-mode"),Y="#8A857C",X=V?"rgba(255,255,255,0.06)":"rgba(0,0,0,0.06)",G=V?"#A7B2FF":"#3B4AD6",J=V?"rgba(167,178,255,0.12)":"rgba(59,74,214,0.08)",ie=V?"#F08A82":"#B42318",ue=V?"rgba(240,138,130,0.08)":"rgba(180,35,24,0.06)",de=V?"#F5C842":"#B8860B",le=e.metricsPoints||[];if(le.length===0){C.fillStyle=Y,C.font="12px monospace",C.textAlign="center",C.fillText("No metrics data yet",z/2,ne/2),E=null;return}var _e=le.map(function(A){return A.completed}),Ve=le.map(function(A){return A.failed}),$e=le.map(function(A){return A.avgMs}),De=le.length,Ne=x/(De-1||1),Be=Math.max(1,Math.max.apply(null,_e),Math.max.apply(null,Ve)),Me=b(Be),ke=Math.max.apply(null,$e),H=ke>0?b(ke):{ticks:[0],niceMax:1},ee=function(A){return R-A/Me.niceMax*(R-10)},Q=function(A){return R-A/H.niceMax*(R-10)},oe=function(A){return q+A*Ne};C.textAlign="right",C.textBaseline="middle",C.font="10px monospace",Me.ticks.forEach(function(A){var I=ee(A);C.strokeStyle=X,C.lineWidth=1,C.beginPath(),C.moveTo(q,I),C.lineTo(z-L,I),C.stroke(),C.fillStyle=Y,C.fillText(A>=1e3?(A/1e3).toFixed(1)+"k":String(A),q-6,I)}),ke>0&&(C.textAlign="left",H.ticks.forEach(function(A){var I=Q(A);C.fillStyle=de,C.fillText(A>=1e3?(A/1e3).toFixed(1)+"s":A+"ms",z-L+6,I)}));var ve=Date.now(),Ce=Math.min(6,De);C.textAlign="center",C.textBaseline="top";for(var k=0;k
Manage how GitHub shares your code. Your anonymizations and URLs stay the same.
Sign in to create and manage your anonymizations.
Connect through the GitHub App with read-only repository access.
Sign in with GitHub Your existing anonymizations and connections stay with your account.',6)])])):p("v-if",!0),!e.site_options?.GITHUB_APP_ENABLED&&e.site_options?.GITHUB_OAUTH_ENABLED&&!e.accountRecovery?(l(),d("section",Uf,[...t[3]||(t[3]=[n("div",{class:"connection-title"},[n("h2",null,"Continue with GitHub")],-1),n("p",null,"Sign in to access your account. GitHub OAuth includes write access to private repositories.",-1),n("a",{class:"connection-link",target:"_self",href:"/github/login"},[v("Sign in with GitHub "),n("svg",{class:"ui-icon","aria-hidden":"true"},[n("use",{href:"#ui-icon-arrow-right"})])],-1)])])):p("v-if",!0),e.site_options&&!e.site_options.GITHUB_APP_ENABLED&&!e.site_options.GITHUB_OAUTH_ENABLED?(l(),d("p",$f,"GitHub sign-in is currently unavailable. Please try again later.")):p("v-if",!0)])])])}var Lf={class:"paper-empty"};function Al(e,t){return l(),d("div",Lf,[...t[0]||(t[0]=[fe('The anonymous mirror you\u2019re looking for has expired, been removed by its owner, or never existed. If you received this URL from an author, ask them to re-issue it.
Fill in the details \u2014 it only takes a minute.
Take ownership of an existing anonymized repository so it appears on your dashboard.
',3)),n("div",sT,[t[5]||(t[5]=n("p",{class:"paper-section-copy"},"Use this when an anonymization was created by a co-author or from another account. You must have access to the GitHub repository it was made from.",-1)),D((l(),d("form",{class:"form needs-validation",name:"claimForm",novalidate:"",onSubmit:t[0]||(t[0]=ae(s=>e.submitForm(s,()=>{e.claim()}),["prevent"]))},[n("div",iT,[t[1]||(t[1]=n("label",{class:"paper-field-label",for:"repoUrl"},"GitHub repository URL",-1)),D(n("input",{type:"text",class:P(["form-control",{"is-invalid":e.claimForm?.repoUrl?.invalid&&(e.claimForm?.repoUrl?.touched||e.claimForm?.submitted)}]),name:"repoUrl",id:"repoUrl",placeholder:"https://github.com/owner/repository",required:""},null,2),[[o,{state:e.viewState,set:s=>{e.repoUrl=s},value:e.repoUrl,form:"claimForm",options:{}}]]),D(n("div",rT," No anonymization matches this repository and ID. Check both values and that you can access the repository on GitHub. ",512),[[W,e.claimForm?.repoUrl?.errors?.not_found]])]),n("div",aT,[t[2]||(t[2]=n("label",{class:"paper-field-label",for:"repoId"},"Anonymized repository ID",-1)),D(n("input",{type:"text",class:P(["form-control",{"is-invalid":e.claimForm?.repoId?.invalid&&(e.claimForm?.repoId?.touched||e.claimForm?.submitted)}]),name:"repoId",id:"repoId",placeholder:"e.g. my-paper-7F2A","aria-describedby":"idHelp",required:""},null,2),[[o,{state:e.viewState,set:s=>{e.repoId=s},value:e.repoId,form:"claimForm",options:{}}]]),t[3]||(t[3]=n("small",{id:"idHelp",class:"form-text text-muted"},[v("The ID is the last part of the anonymized URL: "),n("code",null,"anonymous.4open.science/r/Group anonymizations by venue, give chairs a shared dashboard, and set one expiry for every submission.
If something's missing, write to us \u2014 report a bug on GitHub.
Loading GitHub info for "+h.repoId+"..."),t.get("/api/admin/repos/"+h.repoId+"/github").then(_=>{E&&(E.document.open(),E.document.write('
'+JSON.stringify(_.data,null,2).replace(/[<>]/g,m=>m==="<"?"<":">")+""),E.document.close())},_=>{let m=_&&_.data?JSON.stringify(_.data,null,2):String(_);E&&(E.document.body.innerHTML='
'+m+"")})};function f(h){t.get("/api/admin/users/"+h+"/repos",{}).then(E=>{e.repositories=E.data},E=>{console.error(E)})}function g(h){t.get("/api/admin/users/"+h,{}).then(E=>{e.userInfo=E.data},E=>{console.error(E)})}g(i.username),f(i.username),e.banUser=()=>{confirm(`Ban user ${i.username}?`)&&t.post(`/api/admin/users/${i.username}/ban`).then(()=>g(i.username),h=>console.error(h))},e.activateUser=()=>{t.post(`/api/admin/users/${i.username}/activate`).then(()=>g(i.username),h=>console.error(h))},e.promoteUser=()=>{confirm(`Promote ${i.username} to admin?`)&&t.post(`/api/admin/users/${i.username}/promote`).then(()=>g(i.username),h=>console.error(h))},e.demoteUser=()=>{confirm(`Remove admin privileges from ${i.username}?`)&&t.post(`/api/admin/users/${i.username}/demote`).then(()=>g(i.username),h=>console.error(h))},e.tokens=[],e.tokenForm={name:"",plaintext:null};function y(){t.get("/api/admin/tokens").then(h=>{e.tokens=h.data||[]},h=>{h.status!==401&&h.status!==403&&console.error(h)})}y(),e.createToken=()=>{e.tokenForm.name&&t.post("/api/admin/tokens",{name:e.tokenForm.name}).then(h=>{e.tokenForm.plaintext=h.data.token,e.tokenForm.name="",y()},h=>console.error(h))},e.revokeToken=h=>{confirm(`Revoke token "${h.name}"?`)&&t.delete("/api/admin/tokens/"+h.id).then(()=>y(),E=>console.error(E))},e.removeCache=h=>{confirm("Remove cached files for "+h.repoId+"?")&&t.delete("/api/admin/repos/"+h.repoId).then(()=>f(i.username),E=>console.error(E))},e.removeRepository=h=>{confirm("Remove repository "+h.repoId+"?")&&t.delete("/api/repo/"+h.repoId+"/").then(()=>f(i.username),E=>console.error(E))},e.updateRepository=h=>{let E=Fe({title:`Refreshing ${h.repoId}...`,date:new Date,body:`The repository ${h.repoId} is going to be refreshed.`});e.toasts.push(E),t.post(`/api/repo/${h.repoId}/refresh`).then(_=>{_.data.status=="ready"?E.title=`${h.repoId} is refreshed.`:E.title=`Refreshing of ${h.repoId}.`},_=>{E.title=`Error during the refresh of ${h.repoId}.`,E.body=_.body})},e.getGitHubRepositories=h=>{t.get(`/api/user/${e.userInfo.username}/all_repositories`,{params:{force:"1"}}).then(E=>{e.userInfo.repositories=E.data})};let b=null;e.watch("query",()=>{s.timeout.cancel(b),b=s.timeout(()=>{f(i.username)},500)},!0)},El=function(e,t,o){let i=ut(),s=Zt();e.Math=Math,e.watch("user.status",()=>{e.user==null&&o.url("/")}),e.user==null&&o.url("/"),e.conferences=[],e.total=-1,e.totalPage=0,e.statusCounts=[];let r=_=>{if(_.key==="/"&&!["INPUT","TEXTAREA","SELECT"].includes(document.activeElement?.tagName)){_.preventDefault();let m=document.querySelector('.admin-filter-toolbar input[type="search"]');m&&m.focus()}};s(document,"keydown",r),e.on("dispose",()=>document.removeEventListener("keydown",r)),e.clearFilter=_=>{_==="dateRange"?(e.query.dateFrom="",e.query.dateTo=""):e.query[_]="",e.query.page=1},e.chips=[];let a=()=>{let _=[];(e.query.dateFrom||e.query.dateTo)&&_.push({key:"dateRange",label:"Date",value:(e.query.dateFrom||"\u2026")+" \u2013 "+(e.query.dateTo||"\u2026")}),e.chips=_};e.statusCountFor=_=>{let m=(e.statusCounts||[]).find(C=>C._id===_);return m?m.count:0},e.toggleSortDirection=()=>{e.query.direction=e.query.direction==="asc"?"desc":"asc"},e.sortBy=_=>{e.query.sort===_?e.query.direction=e.query.direction==="asc"?"desc":"asc":(e.query.sort=_,e.query.direction="desc"),e.query.page=1},e.sortIcon=_=>e.query.sort===_?e.query.direction==="asc"?"fa-arrow-up":"fa-arrow-down":"";let c="admin.conferences.filterPrefs",f={page:1,limit:25,sort:"name",direction:"asc",search:"",dateFrom:"",dateTo:"",ready:!1,expired:!1,removed:!1,error:!0,preparing:!0},g=loadFilterPrefs(c)||{};e.query=Object.assign({},f,g,{page:1,search:""});let y=o.search();y.search&&(e.query.search=y.search);let b="admin.conferences.presets";e.presets=JSON.parse(localStorage.getItem(b)||"[]"),e.savePreset=()=>{let _=window.prompt("Preset name:");if(!_)return;let m=Object.assign({},e.query);delete m.page,e.presets=(e.presets||[]).filter(C=>C.name!==_),e.presets.push({name:_,query:m}),localStorage.setItem(b,JSON.stringify(e.presets))},e.applyPreset=_=>{Object.assign(e.query,_.query,{page:1})},e.deletePreset=_=>{e.presets=(e.presets||[]).filter(m=>m.name!==_.name),localStorage.setItem(b,JSON.stringify(e.presets))},e.removeConference=_=>{confirm("Remove conference "+_.conferenceID+"?")&&t.delete("/api/admin/conferences/"+_.conferenceID).then(()=>h(),m=>console.error(m))},e.exportCsv=()=>{let _=new URLSearchParams(Object.entries(e.query).filter(([,m])=>m!==""&&m!==!1&&m!=null));_.set("format","csv"),_.set("limit","10000"),window.open("/api/admin/conferences?"+_.toString(),"_blank")},e.fetchError=null;function h(){e.fetchError=null,t.get("/api/admin/conferences",{params:e.query}).then(_=>{e.total=_.data.total,e.totalPage=Math.ceil(_.data.total/e.query.limit),e.conferences=_.data.results,e.statusCounts=_.data.statusCounts||[]},_=>{e.fetchError=_&&_.data&&_.data.error||"Failed to load conferences",console.error(_)})}h();let E=null;e.watch("query",()=>{i.timeout.cancel(E),E=i.timeout(h,500);let{page:_,search:m,...C}=e.query;saveFilterPrefs(c,C),a()},!0),a()},Cl=function(e,t,o,i,s){let r=ut();e.watch("user.status",()=>{e.user==null&&o.url("/")}),e.user==null&&o.url("/"),e.queueList=[],e.jobs=[],e.selectedQueue="download",e.selectedStats=null,e.range="1h",e.allStates=["active","waiting","delayed","failed","completed"],e.stateFilter={active:!0,waiting:!0,delayed:!0,failed:!0,completed:!0},e.query={search:"",autoRefresh:!0},e.filteredJobs=()=>(e.jobs||[]).filter(m=>e.stateFilter[m._state]),e.jobProgressPct=m=>m&&m.progress&&typeof m.progress=="object"&&typeof m.progress.percent=="number"?Math.max(0,Math.min(100,Math.round(m.progress.percent))):typeof m.progress=="number"?Math.max(0,Math.min(100,Math.round(m.progress))):null,e.jobDuration=m=>{if(!m.processedOn)return"-";let N=(m.finishedOn||Date.now())-m.processedOn;return N<1e3?N+"ms":(N/1e3).toFixed(1)+"s"},e.metricsPoints=[],e.selectQueue=m=>{e.selectedQueue=m,a(),c()},e.setRange=m=>{e.range=m,c()};function a(){let m={queue:e.selectedQueue,search:e.query.search};t.get("/api/admin/queues",{params:m}).then(C=>{e.queueList=C.data.queues||[],e.jobs=C.data.jobs||[],e.selectedStats=e.queueList.find(N=>N.key===e.selectedQueue)||e.queueList[0]||null},C=>console.error(C))}function c(){t.get("/api/admin/queues/metrics",{params:{queue:e.selectedQueue,range:e.range}}).then(m=>{e.metricsPoints=m.data.points||[],s(h,0)},m=>console.error(m))}a(),c();let f=i(()=>{e.query.autoRefresh&&(a(),c())},15e3);e.on("dispose",()=>i.cancel(f)),e.refreshNow=function(){a(),c()};function g(m){let C=m&&m.data&&(m.data.message||m.data.error)||"Request failed";e.actionError=C,s(()=>{e.actionError=null},5e3),console.error(m)}e.actionError=null,e.removeJob=m=>{t.delete(`/api/admin/queue/${e.selectedQueue}/${m.id}`).then(a,g)},e.retryJob=m=>{t.post(`/api/admin/queue/${e.selectedQueue}/${m.id}`).then(a,g)},e.retryFailed=()=>{confirm(`Retry all failed jobs in ${e.selectedQueue}?`)&&t.post(`/api/admin/queue/${e.selectedQueue}/retry-failed`).then(a,m=>console.error(m))},e.drainSelected=()=>{confirm(`Drain the ${e.selectedQueue} queue?`)&&t.post(`/api/admin/queue/${e.selectedQueue}/drain`).then(a,m=>console.error(m))},e.togglePause=()=>{let m=e.selectedStats&&e.selectedStats.paused?"resume":"pause";t.post(`/api/admin/queue/${e.selectedQueue}/${m}`).then(a,C=>console.error(C))},e.emptyQueue=()=>{confirm(`Empty the ${e.selectedQueue} queue? This removes ALL jobs.`)&&t.post(`/api/admin/queue/${e.selectedQueue}/empty`).then(a,m=>console.error(m))},e.pauseAll=()=>{confirm("Pause all queues?")&&t.post("/api/admin/queues/pause-all").then(a,m=>console.error(m))};let y=null;e.watch("query.search",()=>{r.timeout.cancel(y),y=r.timeout(a,350)}),e.expanded={},e.toggleJob=m=>{e.expanded[m.id]=!e.expanded[m.id]},e.humanTime=m=>{if(!m)return"";let C=new Date(m);return C.toLocaleTimeString([],{hour:"2-digit",minute:"2-digit",second:"2-digit",hour12:!1})+" "+C.toLocaleDateString([],{month:"short",day:"numeric"})},e.delayCountdown=m=>{if(!m)return"";var C=Math.max(0,Math.ceil((m-Date.now())/1e3));if(C<=0)return"resuming soon";var N=Math.floor(C/60),A=C%60;return"in "+(N>0?N+"m "+A+"s":A+"s")};function b(m){if(m<=0)return{ticks:[0],niceMax:1};let C=Math.pow(10,Math.floor(Math.log10(m))),N=C;m/N<2?N=C/2:m/N>5&&(N=C*2);let A=Math.ceil(m/N)*N,M=[];for(let U=0;U<=A;U+=N)M.push(U);return{ticks:M,niceMax:A}}function h(){var m=document.getElementById("q-throughput-chart");if(!m)return;var C=m.getContext("2d"),N=window.devicePixelRatio||1,A=m.parentElement.getBoundingClientRect(),M=44,U=50,q=20,B=A.width-40,oe=180,ee=B-M-U,R=oe-q;m.width=B*N,m.height=oe*N,m.style.width=B+"px",m.style.height=oe+"px",C.setTransform(N,0,0,N,0,0);var j=document.body.classList.contains("dark-mode"),ie="#8A857C",ve=j?"rgba(255,255,255,0.06)":"rgba(0,0,0,0.06)",z=j?"#A7B2FF":"#3B4AD6",J=j?"rgba(167,178,255,0.12)":"rgba(59,74,214,0.08)",Q=j?"#F08A82":"#B42318",te=j?"rgba(240,138,130,0.08)":"rgba(180,35,24,0.06)",X=j?"#F5C842":"#B8860B",re=e.metricsPoints||[];if(re.length===0){C.fillStyle=ie,C.font="12px monospace",C.textAlign="center",C.fillText("No metrics data yet",B/2,oe/2),E=null;return}var Ee=re.map(function(O){return O.completed}),Te=re.map(function(O){return O.failed}),Ae=re.map(function(O){return O.avgMs}),Se=re.length,_e=ee/(Se-1||1),ze=Math.max(1,Math.max.apply(null,Ee),Math.max.apply(null,Te)),Ue=b(ze),Ie=Math.max.apply(null,Ae),H=Ie>0?b(Ie):{ticks:[0],niceMax:1},se=function(O){return R-O/Ue.niceMax*(R-10)},Y=function(O){return R-O/H.niceMax*(R-10)},Z=function(O){return M+O*_e};C.textAlign="right",C.textBaseline="middle",C.font="10px monospace",Ue.ticks.forEach(function(O){var I=se(O);C.strokeStyle=ve,C.lineWidth=1,C.beginPath(),C.moveTo(M,I),C.lineTo(B-U,I),C.stroke(),C.fillStyle=ie,C.fillText(O>=1e3?(O/1e3).toFixed(1)+"k":String(O),M-6,I)}),Ie>0&&(C.textAlign="left",H.ticks.forEach(function(O){var I=Y(O);C.fillStyle=X,C.fillText(O>=1e3?(O/1e3).toFixed(1)+"s":O+"ms",B-U+6,I)}));var ue=Date.now(),be=Math.min(6,Se);C.textAlign="center",C.textBaseline="top";for(var k=0;k
Manage how GitHub shares your code. Your anonymizations and URLs stay the same.
Sign in to create and manage your anonymizations.
Connect through the GitHub App with read-only repository access.
Sign in with GitHub Your existing anonymizations and connections stay with your account.',6)])])):p("v-if",!0),!e.site_options?.GITHUB_APP_ENABLED&&e.site_options?.GITHUB_OAUTH_ENABLED&&!e.accountRecovery?(l(),d("section",Uf,[...t[3]||(t[3]=[n("div",{class:"connection-title"},[n("h2",null,"Continue with GitHub")],-1),n("p",null,"Sign in to access your account. GitHub OAuth includes write access to private repositories.",-1),n("a",{class:"connection-link",target:"_self",href:"/github/login"},[v("Sign in with GitHub "),n("svg",{class:"ui-icon","aria-hidden":"true"},[n("use",{href:"#ui-icon-arrow-right"})])],-1)])])):p("v-if",!0),e.site_options&&!e.site_options.GITHUB_APP_ENABLED&&!e.site_options.GITHUB_OAUTH_ENABLED?(l(),d("p",$f,"GitHub sign-in is currently unavailable. Please try again later.")):p("v-if",!0)])])])}var Lf={class:"paper-empty"};function Al(e,t){return l(),d("div",Lf,[...t[0]||(t[0]=[fe('The anonymous mirror you\u2019re looking for has expired, been removed by its owner, or never existed. If you received this URL from an author, ask them to re-issue it.
Fill in the details \u2014 it only takes a minute.
Take ownership of an existing anonymized repository so it appears on your dashboard.
',3)),n("div",sT,[t[5]||(t[5]=n("p",{class:"paper-section-copy"},"Use this when an anonymization was created by a co-author or from another account. You must have access to the GitHub repository it was made from.",-1)),D((l(),d("form",{class:"form needs-validation",name:"claimForm",novalidate:"",onSubmit:t[0]||(t[0]=de(s=>e.submitForm(s,()=>{e.claim()}),["prevent"]))},[n("div",iT,[t[1]||(t[1]=n("label",{class:"paper-field-label",for:"repoUrl"},"GitHub repository URL",-1)),D(n("input",{type:"text",class:P(["form-control",{"is-invalid":e.claimForm?.repoUrl?.invalid&&(e.claimForm?.repoUrl?.touched||e.claimForm?.submitted)}]),name:"repoUrl",id:"repoUrl",placeholder:"https://github.com/owner/repository",required:""},null,2),[[o,{state:e.viewState,set:s=>{e.repoUrl=s},value:e.repoUrl,form:"claimForm",options:{}}]]),D(n("div",rT," No anonymization matches this repository and ID. Check both values and that you can access the repository on GitHub. ",512),[[W,e.claimForm?.repoUrl?.errors?.not_found]])]),n("div",aT,[t[2]||(t[2]=n("label",{class:"paper-field-label",for:"repoId"},"Anonymized repository ID",-1)),D(n("input",{type:"text",class:P(["form-control",{"is-invalid":e.claimForm?.repoId?.invalid&&(e.claimForm?.repoId?.touched||e.claimForm?.submitted)}]),name:"repoId",id:"repoId",placeholder:"e.g. my-paper-7F2A","aria-describedby":"idHelp",required:""},null,2),[[o,{state:e.viewState,set:s=>{e.repoId=s},value:e.repoId,form:"claimForm",options:{}}]]),t[3]||(t[3]=n("small",{id:"idHelp",class:"form-text text-muted"},[v("The ID is the last part of the anonymized URL: "),n("code",null,"anonymous.4open.science/r/Group anonymizations by venue, give chairs a shared dashboard, and set one expiry for every submission.
If something's missing, write to us \u2014 report a bug on GitHub.

The GitHub App has read-only access to gists and the repositories you choose. Legacy OAuth uses broader permissions. Anonymous GitHub never writes to your repositories.
Source code with syntax highlighting, Markdown, Jupyter notebooks, PDFs, images, and GitHub Pages sites. Text files are anonymized; enabled images and PDFs are served unchanged.
You choose: the mirror is removed, or visitors are redirected to the original GitHub repository once the review is over.
Track PR mirrors you\u2019ve created and their reviewer traffic.

The GitHub App has read-only access to gists and the repositories you choose. Legacy OAuth uses broader permissions. Anonymous GitHub never writes to your repositories.
Source code with syntax highlighting, Markdown, Jupyter notebooks, PDFs, images, and GitHub Pages sites. Text files are anonymized; enabled images and PDFs are served unchanged.
You choose: the mirror is removed, or visitors are redirected to the original GitHub repository once the review is over.
Track PR mirrors you\u2019ve created and their reviewer traffic.
| '+(c.oldNo||"")+' | '+(c.newNo||"")+' | '+(c.kind==="add"?"+":c.kind==="remove"?"-":c.kind==="hunk"?"@":"")+' | '+t(c.text)+" |
| '+(c.oldNo||"")+' | '+(c.newNo||"")+' | '+(c.kind==="add"?"+":c.kind==="remove"?"-":c.kind==="hunk"?"@":"")+' | '+t(c.text)+" |