Expand 735 hidden lines 736 736 option value=( value. as_str ( ) ) selected[ vis == value] { ( label) } 737 737 } 738 738 } ; 739 - let perm_option = |value : & str , label : & str | { 740 - html ! { option value=( value) { ( label) } } 741 - } ; 742 739 let body = html ! { 743 740 ( repo_header ( state, & ctx, Tab :: Settings , & ctx. repo. default_branch, & branches) ) 744 741 section class="listing" { Expand 13 hidden lines 758 755 } 759 756 } 760 757 } 758 + ( collaborators_section ( id, & chrome. csrf, & collaborators) ) 759 + section class="listing" { 760 + h2 { "Archive" } 761 + div class="card" { 762 + @if ctx. repo. archived_at. is_some ( ) { 763 + p class="muted" { "This repository is archived (read-only). Unarchiving restores pushing." } 764 + form method="post" action=( format ! ( "/repo-settings/{id}/archive" ) ) { 765 + input type ="hidden" name="_csrf" value=( chrome. csrf) ; 766 + input type ="hidden" name="archived" value="false" ; 767 + button class="btn btn-primary" type ="submit" { "Unarchive repository" } 768 + } 769 + } @else { 770 + p class="muted" { "Archiving makes the repository read-only; pushes are refused until it is unarchived." } 771 + form method="post" action=( format ! ( "/repo-settings/{id}/archive" ) ) { 772 + input type ="hidden" name="_csrf" value=( chrome. csrf) ; 773 + input type ="hidden" name="archived" value="true" ; 774 + button class="btn" type ="submit" { "Archive repository" } 775 + } 776 + } 777 + } 778 + } 779 + section class="listing" { 780 + h2 { "Danger zone" } 781 + div class="card danger-zone" { 782 + p { strong { "Delete this repository." } " This permanently removes it from fabrica; the on-disk copy is moved to trash." } 783 + form method="post" action=( format ! ( "/repo-settings/{id}/delete" ) ) { 784 + input type ="hidden" name="_csrf" value=( chrome. csrf) ; 785 + button class="btn btn-danger" type ="submit" { "Delete repository" } 786 + } 787 + } 788 + } 789 + } ; 790 + let title = format ! ( "{}/{}: settings" , ctx. owner. username, ctx. repo. path) ; 791 + Ok ( ( jar, page ( & chrome, & title, body) ) . into_response ( ) ) 792 + } 793 + 794 + 795 + 796 + fn collaborators_section ( id : & str , csrf : & str , collaborators : & [ store:: Collaborator ] ) -> Markup { 797 + let perm_option = |value : & str , label : & str | html ! { option value=( value) { ( label) } } ; 798 + html ! { 761 799 section class="listing" { 762 800 h2 { "Collaborators" } 763 801 div class="card" { Expand 1 hidden lines 765 803 p class="muted" { "No collaborators yet." } 766 804 } @else { 767 805 ul class="entry-list collab-list" { 768 - @for c in & collaborators { 806 + @for c in collaborators { 769 807 li class="entry-row" { 770 808 div class="entry-row-body" { 771 809 a class="entry-row-title" href={ "/" ( c. username) } { ( c. username) } Expand 2 hidden lines 774 812 div class="entry-row-actions" { 775 813 form method="post" 776 814 action=( format ! ( "/repo-settings/{id}/collaborators/remove" ) ) { 777 - input type ="hidden" name="_csrf" value=( chrome. csrf) ; 815 + input type ="hidden" name="_csrf" value=( csrf) ; 778 816 input type ="hidden" name="user_id" value=( c. user_id) ; 779 817 button class="btn btn-danger" type ="submit" { "Remove" } 780 818 } Expand 4 hidden lines 785 823 } 786 824 form class="collab-add" method="post" 787 825 action=( format ! ( "/repo-settings/{id}/collaborators" ) ) { 788 - input type ="hidden" name="_csrf" value=( chrome. csrf) ; 826 + input type ="hidden" name="_csrf" value=( csrf) ; 789 827 input type ="text" name="username" placeholder="Username" aria-label="Username" required; 790 828 select name="permission" aria-label="Permission" { 791 829 ( perm_option ( "read" , "Read" ) ) Expand 4 hidden lines 796 834 } 797 835 } 798 836 } 799 - section class="listing" { 800 - h2 { "Danger zone" } 801 - div class="card danger-zone" { 802 - p { strong { "Delete this repository." } " This permanently removes it from fabrica; the on-disk copy is moved to trash." } 803 - form method="post" action=( format ! ( "/repo-settings/{id}/delete" ) ) { 804 - input type ="hidden" name="_csrf" value=( chrome. csrf) ; 805 - button class="btn btn-danger" type ="submit" { "Delete repository" } 806 - } 807 - } 808 - } 809 - } ; 810 - let title = format ! ( "{}/{}: settings" , ctx. owner. username, ctx. repo. path) ; 811 - Ok ( ( jar, page ( & chrome, & title, body) ) . into_response ( ) ) 837 + } 812 838 } 813 839 814 840 Expand 143 hidden lines 958 984 } 959 985 } 960 986 987 + 988 + # [ derive ( Debug , Deserialize ) ] 989 + pub struct RepoArchiveForm { 990 + 991 + # [ serde ( default ) ] 992 + archived: String , 993 + 994 + # [ serde ( rename = "_csrf" ) ] 995 + csrf: String , 996 + } 997 + 998 + 999 + pub async fn settings_archive ( 1000 + State ( state) : State < AppState > , 1001 + MaybeUser ( viewer) : MaybeUser , 1002 + jar : CookieJar , 1003 + headers : HeaderMap , 1004 + Path ( id) : Path < String > , 1005 + Form ( form) : Form < RepoArchiveForm > , 1006 + ) -> Response { 1007 + if verify_csrf ( & jar, & headers, Some ( & form. csrf) ) . is_err ( ) { 1008 + return AppError :: Forbidden . into_response ( ) ; 1009 + } 1010 + let result = async { 1011 + let repo = require_admin_repo ( & state, viewer. as_ref ( ) , & id) . await ?; 1012 + state 1013 + . store 1014 + . set_archived ( & repo. id, form. archived == "true" ) 1015 + . await ?; 1016 + Ok :: < String , AppError > ( repo_settings_url ( & state, & repo) . await ) 1017 + } 1018 + . await ; 1019 + match result { 1020 + Ok ( dest) => Redirect :: to ( & dest) . into_response ( ) , 1021 + Err ( err) => err. into_response ( ) , 1022 + } 1023 + } 1024 + 961 1025 962 1026 async fn repo_settings_url ( state : & AppState , repo : & Repo ) -> String { 963 1027 let owner = state Expand 539 hidden lines 1503 1567 1504 1568 1505 1569 pub ( crate ) updated: i64 , 1570 + 1571 + pub ( crate ) archived: bool , 1506 1572 1507 1573 pub ( crate ) description: Option < String > , 1508 1574 } Expand 7 hidden lines 1516 1582 visibility: repo. visibility, 1517 1583 default_branch: repo. default_branch. clone ( ) , 1518 1584 updated: repo. pushed_at. unwrap_or ( repo. updated_at) , 1585 + archived: repo. archived_at. is_some ( ) , 1519 1586 description: repo. description. clone ( ) , 1520 1587 } 1521 1588 } Expand 7 hidden lines 1529 1596 visibility: repo. visibility, 1530 1597 default_branch: repo. default_branch. clone ( ) , 1531 1598 updated: repo. pushed_at. unwrap_or ( repo. updated_at) , 1599 + archived: repo. archived_at. is_some ( ) , 1532 1600 description: repo. description. clone ( ) , 1533 1601 } 1534 1602 } Expand 8 hidden lines 1543 1611 } 1544 1612 } 1545 1613 1614 + 1615 + fn archived_badge ( archived : bool ) -> Markup { 1616 + html ! { 1617 + @if archived { 1618 + span class="badge badge-archived" { "archived" } 1619 + } 1620 + } 1621 + } 1622 + 1546 1623 1547 1624 1548 1625 Expand 12 hidden lines 1561 1638 span class="repo-row-name" { 1562 1639 ( icon ( Icon :: Box ) ) span { ( e. label) } 1563 1640 ( visibility_badge ( e. visibility) ) 1641 + ( archived_badge ( e. archived) ) 1564 1642 } 1565 1643 @if let Some ( desc) = & e. description { 1566 1644 span class="repo-row-desc muted" { ( desc) } Expand 131 hidden lines 1698 1776 span class="slug-sep" { "/" } 1699 1777 a href=( base) { ( ctx. repo. name) } 1700 1778 ( visibility_badge ( ctx. repo. visibility) ) 1779 + ( archived_badge ( ctx. repo. archived_at. is_some ( ) ) ) 1701 1780 } 1702 1781 @if let Some ( desc) = & ctx. repo. description { p class="muted" { ( desc) } } 1703 1782 div class="repo-nav" {