mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-11-13 07:19:18 +01:00
s3tables: support multi-level namespace normalization
This commit is contained in:
1 parent
0b80f055c2
commit
be26ce74ce
10 files changed
+345
-144
No files matched your search
@@ -195,7 +195,7 @@ templ IcebergNamespaces(data dash.IcebergNamespacesData) {
|
||||
<div class="mb-3">
|
||||
<label for="icebergNamespaceName" class="form-label">Namespace</label>
|
||||
<input type="text" class="form-control" id="icebergNamespaceName" name="name" placeholder="analytics" required/>
|
||||
<div class="form-text">Use lowercase letters, numbers, and underscores. Nested namespaces are not supported.</div>
|
||||
<div class="form-text">Use lowercase letters, numbers, and underscores. Use dots for nested namespaces (for example, analytics.daily).</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
|
||||
@@ -327,7 +327,7 @@ func IcebergNamespaces(data dash.IcebergNamespacesData) templ.Component {
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "\"> <input type=\"hidden\" id=\"icebergNamespaceCsrfToken\" name=\"csrf_token\"><div class=\"mb-3\"><label for=\"icebergNamespaceName\" class=\"form-label\">Namespace</label> <input type=\"text\" class=\"form-control\" id=\"icebergNamespaceName\" name=\"name\" placeholder=\"analytics\" required><div class=\"form-text\">Use lowercase letters, numbers, and underscores. Nested namespaces are not supported.</div></div></div><div class=\"modal-footer\"><button type=\"button\" class=\"btn btn-secondary\" data-bs-dismiss=\"modal\">Cancel</button> <button type=\"submit\" class=\"btn btn-primary\"><i class=\"fas fa-plus me-1\"></i>Create</button></div></form></div></div></div><script>\n\t\tdocument.addEventListener('DOMContentLoaded', function() {\n\t\t\tinitIcebergNamespaces();\n\t\t});\n\t</script>")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "\"> <input type=\"hidden\" id=\"icebergNamespaceCsrfToken\" name=\"csrf_token\"><div class=\"mb-3\"><label for=\"icebergNamespaceName\" class=\"form-label\">Namespace</label> <input type=\"text\" class=\"form-control\" id=\"icebergNamespaceName\" name=\"name\" placeholder=\"analytics\" required><div class=\"form-text\">Use lowercase letters, numbers, and underscores. Use dots for nested namespaces (for example, analytics.daily).</div></div></div><div class=\"modal-footer\"><button type=\"button\" class=\"btn btn-secondary\" data-bs-dismiss=\"modal\">Cancel</button> <button type=\"submit\" class=\"btn btn-primary\"><i class=\"fas fa-plus me-1\"></i>Create</button></div></form></div></div></div><script>\n\t\tdocument.addEventListener('DOMContentLoaded', function() {\n\t\t\tinitIcebergNamespaces();\n\t\t});\n\t</script>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
|
||||
@@ -154,14 +154,14 @@ templ S3TablesNamespaces(data dash.S3TablesNamespacesData) {
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<form id="createS3TablesNamespaceForm">
|
||||
<div class="modal-body">
|
||||
<input type="hidden" name="bucket_arn" value={ data.BucketARN }/>
|
||||
<div class="mb-3">
|
||||
<label for="s3tablesNamespaceName" class="form-label">Namespace</label>
|
||||
<input type="text" class="form-control" id="s3tablesNamespaceName" name="name" placeholder="analytics" required/>
|
||||
<div class="form-text">Use lowercase letters, numbers, and underscores. Nested namespaces are not supported.</div>
|
||||
<div class="modal-body">
|
||||
<input type="hidden" name="bucket_arn" value={ data.BucketARN }/>
|
||||
<div class="mb-3">
|
||||
<label for="s3tablesNamespaceName" class="form-label">Namespace</label>
|
||||
<input type="text" class="form-control" id="s3tablesNamespaceName" name="name" placeholder="analytics" required/>
|
||||
<div class="form-text">Use lowercase letters, numbers, and underscores. Use dots for nested namespaces (for example, analytics.daily).</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="submit" class="btn btn-primary">
|
||||
@@ -235,41 +235,53 @@ templ S3TablesNamespaces(data dash.S3TablesNamespacesData) {
|
||||
input.setCustomValidity('');
|
||||
return '';
|
||||
}
|
||||
let message = '';
|
||||
if (!name) {
|
||||
message = 'Namespace name is required';
|
||||
} else if (name.length < 1 || name.length > 255) {
|
||||
message = 'Namespace name must be between 1 and 255 characters';
|
||||
} else if (name === '.' || name === '..') {
|
||||
message = "namespace name cannot be '.' or '..'";
|
||||
} else if (name.includes('/')) {
|
||||
message = "namespace name cannot contain '/'";
|
||||
} else {
|
||||
const start = name[0];
|
||||
const end = name[name.length - 1];
|
||||
const isStartValid = (start >= 'a' && start <= 'z') || (start >= '0' && start <= '9');
|
||||
const isEndValid = (end >= 'a' && end <= 'z') || (end >= '0' && end <= '9');
|
||||
if (!isStartValid) {
|
||||
message = 'Namespace name must start with a letter or digit';
|
||||
} else if (!isEndValid) {
|
||||
message = 'Namespace name must end with a letter or digit';
|
||||
let message = '';
|
||||
if (!name) {
|
||||
message = 'Namespace name is required';
|
||||
} else if (name.length < 1 || name.length > 255) {
|
||||
message = 'Namespace name must be between 1 and 255 characters';
|
||||
} else if (name === '.' || name === '..') {
|
||||
message = "namespace name cannot be '.' or '..'";
|
||||
} else if (name.includes('/')) {
|
||||
message = "namespace name cannot contain '/'";
|
||||
} else {
|
||||
for (const ch of name) {
|
||||
const isLower = ch >= 'a' && ch <= 'z';
|
||||
const isDigit = ch >= '0' && ch <= '9';
|
||||
if (!(isLower || isDigit || ch === '_')) {
|
||||
message = "invalid namespace name: only 'a-z', '0-9', and '_' are allowed";
|
||||
for (const part of name.split('.')) {
|
||||
if (!part) {
|
||||
message = 'namespace levels cannot be empty';
|
||||
break;
|
||||
}
|
||||
const start = part[0];
|
||||
const end = part[part.length - 1];
|
||||
const isStartValid = (start >= 'a' && start <= 'z') || (start >= '0' && start <= '9');
|
||||
const isEndValid = (end >= 'a' && end <= 'z') || (end >= '0' && end <= '9');
|
||||
if (!isStartValid) {
|
||||
message = 'Namespace name must start with a letter or digit';
|
||||
break;
|
||||
}
|
||||
if (!isEndValid) {
|
||||
message = 'Namespace name must end with a letter or digit';
|
||||
break;
|
||||
}
|
||||
for (const ch of part) {
|
||||
const isLower = ch >= 'a' && ch <= 'z';
|
||||
const isDigit = ch >= '0' && ch <= '9';
|
||||
if (!(isLower || isDigit || ch === '_')) {
|
||||
message = "invalid namespace name: only 'a-z', '0-9', and '_' are allowed";
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (message) {
|
||||
break;
|
||||
}
|
||||
if (part.startsWith('aws')) {
|
||||
message = "namespace name cannot start with reserved prefix 'aws'";
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!message && name.startsWith('aws')) {
|
||||
message = "namespace name cannot start with reserved prefix 'aws'";
|
||||
}
|
||||
}
|
||||
input.setCustomValidity(message);
|
||||
return message;
|
||||
}
|
||||
input.setCustomValidity(message);
|
||||
return message;
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
s3tablesNamespaceDeleteModalInstance = new bootstrap.Modal(document.getElementById('deleteS3TablesNamespaceModal'));
|
||||
|
||||
@@ -236,13 +236,13 @@ func S3TablesNamespaces(data dash.S3TablesNamespacesData) templ.Component {
|
||||
var templ_7745c5c3_Var12 string
|
||||
templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(data.BucketARN)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/s3tables_namespaces.templ`, Line: 158, Col: 67}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/s3tables_namespaces.templ`, Line: 158, Col: 68}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "\"><div class=\"mb-3\"><label for=\"s3tablesNamespaceName\" class=\"form-label\">Namespace</label> <input type=\"text\" class=\"form-control\" id=\"s3tablesNamespaceName\" name=\"name\" placeholder=\"analytics\" required><div class=\"form-text\">Use lowercase letters, numbers, and underscores. Nested namespaces are not supported.</div></div></div><div class=\"modal-footer\"><button type=\"button\" class=\"btn btn-secondary\" data-bs-dismiss=\"modal\">Cancel</button> <button type=\"submit\" class=\"btn btn-primary\"><i class=\"fas fa-plus me-1\"></i>Create</button></div></form></div></div></div><div class=\"modal fade\" id=\"deleteS3TablesNamespaceModal\" tabindex=\"-1\" aria-labelledby=\"deleteS3TablesNamespaceModalLabel\" aria-hidden=\"true\"><div class=\"modal-dialog\"><div class=\"modal-content\"><div class=\"modal-header\"><h5 class=\"modal-title\" id=\"deleteS3TablesNamespaceModalLabel\"><i class=\"fas fa-exclamation-triangle me-2 text-warning\"></i>Delete Namespace</h5><button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\" aria-label=\"Close\"></button></div><div class=\"modal-body\"><p>Are you sure you want to delete the namespace <strong id=\"deleteS3TablesNamespaceName\"></strong>?</p></div><div class=\"modal-footer\"><button type=\"button\" class=\"btn btn-secondary\" data-bs-dismiss=\"modal\">Cancel</button> <button type=\"button\" class=\"btn btn-danger\" onclick=\"deleteS3TablesNamespace()\"><i class=\"fas fa-trash me-1\"></i>Delete</button></div></div></div></div><script>\n\t\tlet s3tablesNamespaceDeleteModalInstance = null;\n\t\tconst dataBucketArn = document.getElementById('s3tables-namespaces-content').dataset.bucketArn || '';\n\t\tconst csrfTokenMeta = document.querySelector('meta[name=\"csrf-token\"]');\n\t\tconst csrfToken = csrfTokenMeta ? (csrfTokenMeta.getAttribute('content') || '') : '';\n\n\t\tfunction makeJSONHeaders() {\n\t\t\tconst headers = { 'Content-Type': 'application/json' };\n\t\t\tif (csrfToken) {\n\t\t\t\theaders['X-CSRF-Token'] = csrfToken;\n\t\t\t}\n\t\t\treturn headers;\n\t\t}\n\n\t\tasync function readErrorMessage(response, fallbackMessage) {\n\t\t\tconst responseBody = await response.text();\n\t\t\tif (!responseBody) {\n\t\t\t\treturn fallbackMessage;\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst payload = JSON.parse(responseBody);\n\t\t\t\tif (payload && typeof payload.error === 'string' && payload.error) {\n\t\t\t\t\treturn payload.error;\n\t\t\t\t}\n\t\t\t\tif (payload && typeof payload.message === 'string' && payload.message) {\n\t\t\t\t\treturn payload.message;\n\t\t\t\t}\n\t\t\t} catch (parseError) {\n\t\t\t\t// Fall through to the raw response body when parsing fails.\n\t\t\t}\n\t\t\treturn responseBody;\n\t\t}\n\n\t\tfunction validateNamespaceInput(input, allowEmpty) {\n\t\t\tif (typeof window.applyS3TablesNamespaceNameValidity === 'function') {\n\t\t\t\treturn window.applyS3TablesNamespaceNameValidity(input, allowEmpty);\n\t\t\t}\n\t\t\tconst name = (input.value || '').trim();\n\t\t\tif (allowEmpty && name === '') {\n\t\t\t\tinput.setCustomValidity('');\n\t\t\t\treturn '';\n\t\t\t}\n\t\t\tlet message = '';\n\t\t\tif (!name) {\n\t\t\t\tmessage = 'Namespace name is required';\n\t\t\t} else if (name.length < 1 || name.length > 255) {\n\t\t\t\tmessage = 'Namespace name must be between 1 and 255 characters';\n\t\t\t} else if (name === '.' || name === '..') {\n\t\t\t\tmessage = \"namespace name cannot be '.' or '..'\";\n\t\t\t} else if (name.includes('/')) {\n\t\t\t\tmessage = \"namespace name cannot contain '/'\";\n\t\t\t} else {\n\t\t\t\tconst start = name[0];\n\t\t\t\tconst end = name[name.length - 1];\n\t\t\t\tconst isStartValid = (start >= 'a' && start <= 'z') || (start >= '0' && start <= '9');\n\t\t\t\tconst isEndValid = (end >= 'a' && end <= 'z') || (end >= '0' && end <= '9');\n\t\t\t\tif (!isStartValid) {\n\t\t\t\t\tmessage = 'Namespace name must start with a letter or digit';\n\t\t\t\t} else if (!isEndValid) {\n\t\t\t\t\tmessage = 'Namespace name must end with a letter or digit';\n\t\t\t\t} else {\n\t\t\t\t\tfor (const ch of name) {\n\t\t\t\t\t\tconst isLower = ch >= 'a' && ch <= 'z';\n\t\t\t\t\t\tconst isDigit = ch >= '0' && ch <= '9';\n\t\t\t\t\t\tif (!(isLower || isDigit || ch === '_')) {\n\t\t\t\t\t\t\tmessage = \"invalid namespace name: only 'a-z', '0-9', and '_' are allowed\";\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif (!message && name.startsWith('aws')) {\n\t\t\t\t\t\tmessage = \"namespace name cannot start with reserved prefix 'aws'\";\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tinput.setCustomValidity(message);\n\t\t\treturn message;\n\t\t}\n\n\t\tdocument.addEventListener('DOMContentLoaded', function() {\n\t\t\ts3tablesNamespaceDeleteModalInstance = new bootstrap.Modal(document.getElementById('deleteS3TablesNamespaceModal'));\n\n\t\t\tdocument.querySelectorAll('.s3tables-delete-namespace-btn').forEach(button => {\n\t\t\t\tbutton.addEventListener(Line truncated
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "\"><div class=\"mb-3\"><label for=\"s3tablesNamespaceName\" class=\"form-label\">Namespace</label> <input type=\"text\" class=\"form-control\" id=\"s3tablesNamespaceName\" name=\"name\" placeholder=\"analytics\" required><div class=\"form-text\">Use lowercase letters, numbers, and underscores. Use dots for nested namespaces (for example, analytics.daily).</div></div></div><div class=\"modal-footer\"><button type=\"button\" class=\"btn btn-secondary\" data-bs-dismiss=\"modal\">Cancel</button> <button type=\"submit\" class=\"btn btn-primary\"><i class=\"fas fa-plus me-1\"></i>Create</button></div></form></div></div></div><div class=\"modal fade\" id=\"deleteS3TablesNamespaceModal\" tabindex=\"-1\" aria-labelledby=\"deleteS3TablesNamespaceModalLabel\" aria-hidden=\"true\"><div class=\"modal-dialog\"><div class=\"modal-content\"><div class=\"modal-header\"><h5 class=\"modal-title\" id=\"deleteS3TablesNamespaceModalLabel\"><i class=\"fas fa-exclamation-triangle me-2 text-warning\"></i>Delete Namespace</h5><button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\" aria-label=\"Close\"></button></div><div class=\"modal-body\"><p>Are you sure you want to delete the namespace <strong id=\"deleteS3TablesNamespaceName\"></strong>?</p></div><div class=\"modal-footer\"><button type=\"button\" class=\"btn btn-secondary\" data-bs-dismiss=\"modal\">Cancel</button> <button type=\"button\" class=\"btn btn-danger\" onclick=\"deleteS3TablesNamespace()\"><i class=\"fas fa-trash me-1\"></i>Delete</button></div></div></div></div><script>\n\t\tlet s3tablesNamespaceDeleteModalInstance = null;\n\t\tconst dataBucketArn = document.getElementById('s3tables-namespaces-content').dataset.bucketArn || '';\n\t\tconst csrfTokenMeta = document.querySelector('meta[name=\"csrf-token\"]');\n\t\tconst csrfToken = csrfTokenMeta ? (csrfTokenMeta.getAttribute('content') || '') : '';\n\n\t\tfunction makeJSONHeaders() {\n\t\t\tconst headers = { 'Content-Type': 'application/json' };\n\t\t\tif (csrfToken) {\n\t\t\t\theaders['X-CSRF-Token'] = csrfToken;\n\t\t\t}\n\t\t\treturn headers;\n\t\t}\n\n\t\tasync function readErrorMessage(response, fallbackMessage) {\n\t\t\tconst responseBody = await response.text();\n\t\t\tif (!responseBody) {\n\t\t\t\treturn fallbackMessage;\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst payload = JSON.parse(responseBody);\n\t\t\t\tif (payload && typeof payload.error === 'string' && payload.error) {\n\t\t\t\t\treturn payload.error;\n\t\t\t\t}\n\t\t\t\tif (payload && typeof payload.message === 'string' && payload.message) {\n\t\t\t\t\treturn payload.message;\n\t\t\t\t}\n\t\t\t} catch (parseError) {\n\t\t\t\t// Fall through to the raw response body when parsing fails.\n\t\t\t}\n\t\t\treturn responseBody;\n\t\t}\n\n\t\tfunction validateNamespaceInput(input, allowEmpty) {\n\t\t\tif (typeof window.applyS3TablesNamespaceNameValidity === 'function') {\n\t\t\t\treturn window.applyS3TablesNamespaceNameValidity(input, allowEmpty);\n\t\t\t}\n\t\t\tconst name = (input.value || '').trim();\n\t\t\tif (allowEmpty && name === '') {\n\t\t\t\tinput.setCustomValidity('');\n\t\t\t\treturn '';\n\t\t\t}\n\t\t\t\tlet message = '';\n\t\t\t\tif (!name) {\n\t\t\t\t\tmessage = 'Namespace name is required';\n\t\t\t\t} else if (name.length < 1 || name.length > 255) {\n\t\t\t\t\tmessage = 'Namespace name must be between 1 and 255 characters';\n\t\t\t\t} else if (name === '.' || name === '..') {\n\t\t\t\t\tmessage = \"namespace name cannot be '.' or '..'\";\n\t\t\t\t} else if (name.includes('/')) {\n\t\t\t\t\tmessage = \"namespace name cannot contain '/'\";\n\t\t\t\t} else {\n\t\t\t\t\tfor (const part of name.split('.')) {\n\t\t\t\t\t\tif (!part) {\n\t\t\t\t\t\t\tmessage = 'namespace levels cannot be empty';\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst start = part[0];\n\t\t\t\t\t\tconst end = part[part.length - 1];\n\t\t\t\t\t\tconst isStartValid = (start >= 'a' && start <= 'z') || (start >= '0' && start <= '9');\n\t\t\t\t\t\tconst isEndValid = (end >= 'a' && end <= 'z') || (end >= '0' && end <= '9');\n\t\t\t\t\t\tif (!isStartValid) {\n\t\t\t\t\t\t\tmessage = 'Namespace name must start with a letter or digit';\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (!isEndValid) {\n\t\t\t\t\t\t\tmessage = 'Namespace name must end with a letter or digit';\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfor (const ch of part) {\n\t\t\t\t\t\t\tconst isLower = ch >= 'a' && ch <= 'z';\n\t\t\t\t\t\t\tconst isDigit = ch >= '0' && ch <= '9';\n\t\t\t\t\t\t\tif (!(isLower || isDigit || ch === '_')) {\n\t\t\t\t\t\t\t\tmessage = \"invalid namespace name: only 'a-z', '0-9', and '_' are allowed\";\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (message) {\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (part.startsWith('aws')) {\n\t\t\t\t\t\t\tmessage = \"namespace name cannot start with reserved prefix 'aws'\";\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\Line truncated
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user