You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

658 lines
29 KiB

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>
}