| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658 |
- package app
- import (
- "fmt"
- "github.com/seaweedfs/seaweedfs/weed/admin/dash"
- )
- templ Policies(data dash.PoliciesData) {
- <div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
- <h1 class="h2">
- <i class="fas fa-shield-alt me-2"></i>IAM Policies
- </h1>
- <div class="btn-toolbar mb-2 mb-md-0">
- <div class="btn-group me-2">
- <button type="button" class="btn btn-sm btn-primary" data-bs-toggle="modal" data-bs-target="#createPolicyModal">
- <i class="fas fa-plus me-1"></i>Create Policy
- </button>
- </div>
- </div>
- </div>
- <div id="policies-content">
- <!-- Summary Cards -->
- <div class="row mb-4">
- <div class="col-xl-4 col-md-6 mb-4">
- <div class="card border-left-primary shadow h-100 py-2">
- <div class="card-body">
- <div class="row no-gutters align-items-center">
- <div class="col mr-2">
- <div class="text-xs font-weight-bold text-primary text-uppercase mb-1">
- Total Policies
- </div>
- <div class="h5 mb-0 font-weight-bold text-gray-800">
- {fmt.Sprintf("%d", data.TotalPolicies)}
- </div>
- </div>
- <div class="col-auto">
- <i class="fas fa-shield-alt fa-2x text-gray-300"></i>
- </div>
- </div>
- </div>
- </div>
- </div>
- <div class="col-xl-4 col-md-6 mb-4">
- <div class="card border-left-success shadow h-100 py-2">
- <div class="card-body">
- <div class="row no-gutters align-items-center">
- <div class="col mr-2">
- <div class="text-xs font-weight-bold text-success text-uppercase mb-1">
- Active Policies
- </div>
- <div class="h5 mb-0 font-weight-bold text-gray-800">
- {fmt.Sprintf("%d", data.TotalPolicies)}
- </div>
- </div>
- <div class="col-auto">
- <i class="fas fa-check-circle fa-2x text-gray-300"></i>
- </div>
- </div>
- </div>
- </div>
- </div>
- <div class="col-xl-4 col-md-6 mb-4">
- <div class="card border-left-info shadow h-100 py-2">
- <div class="card-body">
- <div class="row no-gutters align-items-center">
- <div class="col mr-2">
- <div class="text-xs font-weight-bold text-info text-uppercase mb-1">
- Last Updated
- </div>
- <div class="h5 mb-0 font-weight-bold text-gray-800">
- {data.LastUpdated.Format("15:04")}
- </div>
- </div>
- <div class="col-auto">
- <i class="fas fa-clock fa-2x text-gray-300"></i>
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- <!-- Policies Table -->
- <div class="row">
- <div class="col-12">
- <div class="card shadow mb-4">
- <div class="card-header py-3 d-flex flex-row align-items-center justify-content-between">
- <h6 class="m-0 font-weight-bold text-primary">
- <i class="fas fa-shield-alt me-2"></i>IAM Policies
- </h6>
- <div class="dropdown no-arrow">
- <a class="dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown">
- <i class="fas fa-ellipsis-v fa-sm fa-fw text-gray-400"></i>
- </a>
- <div class="dropdown-menu dropdown-menu-right shadow animated--fade-in">
- <div class="dropdown-header">Actions:</div>
- <a class="dropdown-item" href="#">
- <i class="fas fa-download me-2"></i>Export List
- </a>
- </div>
- </div>
- </div>
- <div class="card-body">
- <div class="table-responsive">
- <table class="table table-hover" width="100%" cellspacing="0">
- <thead>
- <tr>
- <th>Policy Name</th>
- <th>Version</th>
- <th>Statements</th>
- <th>Created</th>
- <th>Updated</th>
- <th>Actions</th>
- </tr>
- </thead>
- <tbody>
- for _, policy := range data.Policies {
- <tr>
- <td>
- <strong>{policy.Name}</strong>
- </td>
- <td>
- <span class="badge bg-info">{policy.Document.Version}</span>
- </td>
- <td>
- <span class="badge bg-secondary">{fmt.Sprintf("%d statements", len(policy.Document.Statement))}</span>
- </td>
- <td>
- <small class="text-muted">{policy.CreatedAt.Format("2006-01-02 15:04")}</small>
- </td>
- <td>
- <small class="text-muted">{policy.UpdatedAt.Format("2006-01-02 15:04")}</small>
- </td>
- <td>
- <div class="btn-group btn-group-sm" role="group">
- <button type="button" class="btn btn-outline-info view-policy-btn" title="View Policy" data-policy-name={policy.Name}>
- <i class="fas fa-eye"></i>
- </button>
- <button type="button" class="btn btn-outline-primary edit-policy-btn" title="Edit Policy" data-policy-name={policy.Name}>
- <i class="fas fa-edit"></i>
- </button>
- <button type="button" class="btn btn-outline-danger delete-policy-btn" title="Delete Policy" data-policy-name={policy.Name}>
- <i class="fas fa-trash"></i>
- </button>
- </div>
- </td>
- </tr>
- }
- if len(data.Policies) == 0 {
- <tr>
- <td colspan="6" class="text-center text-muted py-4">
- <i class="fas fa-shield-alt fa-3x mb-3 text-muted"></i>
- <div>
- <h5>No IAM policies found</h5>
- <p>Create your first policy to manage access permissions.</p>
- <button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#createPolicyModal">
- <i class="fas fa-plus me-1"></i>Create Policy
- </button>
- </div>
- </td>
- </tr>
- }
- </tbody>
- </table>
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- <!-- Create Policy Modal -->
- <div class="modal fade" id="createPolicyModal" tabindex="-1" aria-labelledby="createPolicyModalLabel" aria-hidden="true">
- <div class="modal-dialog modal-lg">
- <div class="modal-content">
- <div class="modal-header">
- <h5 class="modal-title" id="createPolicyModalLabel">
- <i class="fas fa-shield-alt me-2"></i>Create IAM Policy
- </h5>
- <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
- </div>
- <div class="modal-body">
- <form id="createPolicyForm">
- <div class="mb-3">
- <label for="policyName" class="form-label">Policy Name</label>
- <input type="text" class="form-control" id="policyName" name="name" required placeholder="e.g., S3ReadOnlyPolicy">
- <div class="form-text">Enter a unique name for this policy (alphanumeric and underscores only)</div>
- </div>
-
- <div class="mb-3">
- <label for="policyDocument" class="form-label">Policy Document</label>
- <textarea class="form-control" id="policyDocument" name="document" rows="15" required placeholder="Enter IAM policy JSON document..."></textarea>
- <div class="form-text">Enter the policy document in AWS IAM JSON format</div>
- </div>
- <div class="mb-3">
- <div class="row">
- <div class="col-md-6">
- <button type="button" class="btn btn-outline-info btn-sm" onclick="insertSamplePolicy()">
- <i class="fas fa-file-alt me-1"></i>Use Sample Policy
- </button>
- </div>
- <div class="col-md-6 text-end">
- <button type="button" class="btn btn-outline-secondary btn-sm" onclick="validatePolicyDocument()">
- <i class="fas fa-check me-1"></i>Validate JSON
- </button>
- </div>
- </div>
- </div>
- </form>
- </div>
- <div class="modal-footer">
- <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
- <button type="button" class="btn btn-primary" onclick="createPolicy()">
- <i class="fas fa-plus me-1"></i>Create Policy
- </button>
- </div>
- </div>
- </div>
- </div>
- <!-- View Policy Modal -->
- <div class="modal fade" id="viewPolicyModal" tabindex="-1" aria-labelledby="viewPolicyModalLabel" aria-hidden="true">
- <div class="modal-dialog modal-lg">
- <div class="modal-content">
- <div class="modal-header">
- <h5 class="modal-title" id="viewPolicyModalLabel">
- <i class="fas fa-eye me-2"></i>View IAM Policy
- </h5>
- <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
- </div>
- <div class="modal-body">
- <div id="viewPolicyContent">
- <div class="text-center">
- <div class="spinner-border" role="status">
- <span class="visually-hidden">Loading...</span>
- </div>
- <p class="mt-2">Loading policy...</p>
- </div>
- </div>
- </div>
- <div class="modal-footer">
- <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
- <button type="button" class="btn btn-primary" id="editFromViewBtn">
- <i class="fas fa-edit me-1"></i>Edit Policy
- </button>
- </div>
- </div>
- </div>
- </div>
- <!-- Edit Policy Modal -->
- <div class="modal fade" id="editPolicyModal" tabindex="-1" aria-labelledby="editPolicyModalLabel" aria-hidden="true">
- <div class="modal-dialog modal-lg">
- <div class="modal-content">
- <div class="modal-header">
- <h5 class="modal-title" id="editPolicyModalLabel">
- <i class="fas fa-edit me-2"></i>Edit IAM Policy
- </h5>
- <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
- </div>
- <div class="modal-body">
- <form id="editPolicyForm">
- <div class="mb-3">
- <label for="editPolicyName" class="form-label">Policy Name</label>
- <input type="text" class="form-control" id="editPolicyName" name="name" readonly>
- <div class="form-text">Policy name cannot be changed</div>
- </div>
-
- <div class="mb-3">
- <label for="editPolicyDocument" class="form-label">Policy Document</label>
- <textarea class="form-control" id="editPolicyDocument" name="document" rows="15" required></textarea>
- <div class="form-text">Edit the policy document in AWS IAM JSON format</div>
- </div>
- <div class="mb-3">
- <div class="row">
- <div class="col-md-6">
- <button type="button" class="btn btn-outline-info btn-sm" onclick="insertSamplePolicyEdit()">
- <i class="fas fa-file-alt me-1"></i>Reset to Sample
- </button>
- </div>
- <div class="col-md-6 text-end">
- <button type="button" class="btn btn-outline-secondary btn-sm" onclick="validateEditPolicyDocument()">
- <i class="fas fa-check me-1"></i>Validate JSON
- </button>
- </div>
- </div>
- </div>
- </form>
- </div>
- <div class="modal-footer">
- <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
- <button type="button" class="btn btn-primary" onclick="updatePolicy()">
- <i class="fas fa-save me-1"></i>Save Changes
- </button>
- </div>
- </div>
- </div>
- </div>
- <!-- JavaScript for Policy Management -->
- <script>
- // Current policy being viewed/edited
- let currentPolicy = null;
-
- // Event listeners for policy actions
- document.addEventListener('DOMContentLoaded', function() {
- // View policy buttons
- document.querySelectorAll('.view-policy-btn').forEach(button => {
- button.addEventListener('click', function() {
- const policyName = this.getAttribute('data-policy-name');
- viewPolicy(policyName);
- });
- });
-
- // Edit policy buttons
- document.querySelectorAll('.edit-policy-btn').forEach(button => {
- button.addEventListener('click', function() {
- const policyName = this.getAttribute('data-policy-name');
- editPolicy(policyName);
- });
- });
-
- // Delete policy buttons
- document.querySelectorAll('.delete-policy-btn').forEach(button => {
- button.addEventListener('click', function() {
- const policyName = this.getAttribute('data-policy-name');
- deletePolicy(policyName);
- });
- });
-
- // Edit from view button
- document.getElementById('editFromViewBtn').addEventListener('click', function() {
- if (currentPolicy) {
- const viewModal = bootstrap.Modal.getInstance(document.getElementById('viewPolicyModal'));
- if (viewModal) viewModal.hide();
- editPolicy(currentPolicy.name);
- }
- });
- });
-
- function createPolicy() {
- const form = document.getElementById('createPolicyForm');
- const formData = new FormData(form);
-
- const policyName = formData.get('name');
- const policyDocumentText = formData.get('document');
-
- if (!policyName || !policyDocumentText) {
- alert('Please fill in all required fields');
- return;
- }
-
- let policyDocument;
- try {
- policyDocument = JSON.parse(policyDocumentText);
- } catch (e) {
- alert('Invalid JSON in policy document: ' + e.message);
- return;
- }
-
- const requestData = {
- name: policyName,
- document: policyDocument
- };
-
- fetch('/api/object-store/policies', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify(requestData)
- })
- .then(response => response.json())
- .then(data => {
- if (data.success) {
- alert('Policy created successfully!');
- const modal = bootstrap.Modal.getInstance(document.getElementById('createPolicyModal'));
- if (modal) modal.hide();
- location.reload(); // Refresh the page to show the new policy
- } else {
- alert('Error creating policy: ' + (data.error || 'Unknown error'));
- }
- })
- .catch(error => {
- console.error('Error:', error);
- alert('Error creating policy: ' + error.message);
- });
- }
-
- function viewPolicy(policyName) {
- // Show the modal first
- const modal = new bootstrap.Modal(document.getElementById('viewPolicyModal'));
- modal.show();
-
- // Reset content to loading state
- document.getElementById('viewPolicyContent').innerHTML = `
- <div class="text-center">
- <div class="spinner-border" role="status">
- <span class="visually-hidden">Loading...</span>
- </div>
- <p class="mt-2">Loading policy...</p>
- </div>
- `;
-
- // Fetch policy data
- fetch('/api/object-store/policies/' + encodeURIComponent(policyName))
- .then(response => {
- if (!response.ok) {
- throw new Error('Policy not found');
- }
- return response.json();
- })
- .then(policy => {
- currentPolicy = policy;
- displayPolicyDetails(policy);
- })
- .catch(error => {
- console.error('Error:', error);
- document.getElementById('viewPolicyContent').innerHTML = `
- <div class="alert alert-danger" role="alert">
- <i class="fas fa-exclamation-triangle me-2"></i>
- Error loading policy: ${error.message}
- </div>
- `;
- });
- }
-
- function displayPolicyDetails(policy) {
- const content = document.getElementById('viewPolicyContent');
-
- let statementsHtml = '';
- if (policy.document && policy.document.Statement) {
- statementsHtml = policy.document.Statement.map((stmt, index) => `
- <div class="card mb-2">
- <div class="card-header py-2">
- <h6 class="mb-0">Statement ${index + 1}</h6>
- </div>
- <div class="card-body py-2">
- <div class="row">
- <div class="col-md-6">
- <strong>Effect:</strong>
- <span class="badge ${stmt.Effect === 'Allow' ? 'bg-success' : 'bg-danger'}">${stmt.Effect}</span>
- </div>
- <div class="col-md-6">
- <strong>Actions:</strong> ${Array.isArray(stmt.Action) ? stmt.Action.join(', ') : stmt.Action}
- </div>
- </div>
- <div class="row mt-2">
- <div class="col-12">
- <strong>Resources:</strong> ${Array.isArray(stmt.Resource) ? stmt.Resource.join(', ') : stmt.Resource}
- </div>
- </div>
- </div>
- </div>
- `).join('');
- }
-
- content.innerHTML = `
- <div class="row mb-3">
- <div class="col-md-6">
- <strong>Policy Name:</strong> ${policy.name || 'Unknown'}
- </div>
- <div class="col-md-6">
- <strong>Version:</strong> <span class="badge bg-info">${policy.document?.Version || 'Unknown'}</span>
- </div>
- </div>
-
- <div class="mb-3">
- <strong>Statements:</strong>
- <div class="mt-2">
- ${statementsHtml || '<p class="text-muted">No statements found</p>'}
- </div>
- </div>
-
- <div class="mb-3">
- <strong>Raw Policy Document:</strong>
- <pre class="bg-light p-3 border rounded mt-2"><code>${JSON.stringify(policy.document, null, 2)}</code></pre>
- </div>
- `;
- }
-
- function editPolicy(policyName) {
- // Show the modal first
- const modal = new bootstrap.Modal(document.getElementById('editPolicyModal'));
- modal.show();
-
- // Set policy name
- document.getElementById('editPolicyName').value = policyName;
- document.getElementById('editPolicyDocument').value = 'Loading...';
-
- // Fetch policy data
- fetch('/api/object-store/policies/' + encodeURIComponent(policyName))
- .then(response => {
- if (!response.ok) {
- throw new Error('Policy not found');
- }
- return response.json();
- })
- .then(policy => {
- currentPolicy = policy;
- document.getElementById('editPolicyDocument').value = JSON.stringify(policy.document, null, 2);
- })
- .catch(error => {
- console.error('Error:', error);
- alert('Error loading policy for editing: ' + error.message);
- const editModal = bootstrap.Modal.getInstance(document.getElementById('editPolicyModal'));
- if (editModal) editModal.hide();
- });
- }
-
- function updatePolicy() {
- const policyName = document.getElementById('editPolicyName').value;
- const policyDocumentText = document.getElementById('editPolicyDocument').value;
-
- if (!policyName || !policyDocumentText) {
- alert('Please fill in all required fields');
- return;
- }
-
- let policyDocument;
- try {
- policyDocument = JSON.parse(policyDocumentText);
- } catch (e) {
- alert('Invalid JSON in policy document: ' + e.message);
- return;
- }
-
- const requestData = {
- document: policyDocument
- };
-
- fetch('/api/object-store/policies/' + encodeURIComponent(policyName), {
- method: 'PUT',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify(requestData)
- })
- .then(response => response.json())
- .then(data => {
- if (data.success) {
- alert('Policy updated successfully!');
- const modal = bootstrap.Modal.getInstance(document.getElementById('editPolicyModal'));
- if (modal) modal.hide();
- location.reload(); // Refresh the page to show the updated policy
- } else {
- alert('Error updating policy: ' + (data.error || 'Unknown error'));
- }
- })
- .catch(error => {
- console.error('Error:', error);
- alert('Error updating policy: ' + error.message);
- });
- }
-
- function insertSamplePolicy() {
- const samplePolicy = {
- "Version": "2012-10-17",
- "Statement": [
- {
- "Effect": "Allow",
- "Action": [
- "s3:GetObject",
- "s3:PutObject"
- ],
- "Resource": [
- "arn:aws:s3:::my-bucket/*"
- ]
- }
- ]
- };
-
- document.getElementById('policyDocument').value = JSON.stringify(samplePolicy, null, 2);
- }
-
- function insertSamplePolicyEdit() {
- const samplePolicy = {
- "Version": "2012-10-17",
- "Statement": [
- {
- "Effect": "Allow",
- "Action": [
- "s3:GetObject",
- "s3:PutObject"
- ],
- "Resource": [
- "arn:aws:s3:::my-bucket/*"
- ]
- }
- ]
- };
-
- document.getElementById('editPolicyDocument').value = JSON.stringify(samplePolicy, null, 2);
- }
-
- function validatePolicyDocument() {
- const policyText = document.getElementById('policyDocument').value;
- validatePolicyJSON(policyText);
- }
-
- function validateEditPolicyDocument() {
- const policyText = document.getElementById('editPolicyDocument').value;
- validatePolicyJSON(policyText);
- }
-
- function validatePolicyJSON(policyText) {
- if (!policyText) {
- alert('Please enter a policy document first');
- return;
- }
-
- try {
- const policy = JSON.parse(policyText);
-
- // Basic validation
- if (!policy.Version) {
- alert('Policy must have a Version field');
- return;
- }
-
- if (!policy.Statement || !Array.isArray(policy.Statement)) {
- alert('Policy must have a Statement array');
- return;
- }
-
- alert('Policy document is valid JSON!');
- } catch (e) {
- alert('Invalid JSON: ' + e.message);
- }
- }
-
- function deletePolicy(policyName) {
- if (confirm('Are you sure you want to delete policy "' + policyName + '"?')) {
- fetch('/api/object-store/policies/' + encodeURIComponent(policyName), {
- method: 'DELETE'
- })
- .then(response => response.json())
- .then(data => {
- if (data.success) {
- alert('Policy deleted successfully!');
- location.reload(); // Refresh the page
- } else {
- alert('Error deleting policy: ' + (data.error || 'Unknown error'));
- }
- })
- .catch(error => {
- console.error('Error:', error);
- alert('Error deleting policy: ' + error.message);
- });
- }
- }
- </script>
- }
|