Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 15 additions & 7 deletions api-runtime/common-runtime/SecurityGroupManager.go
Original file line number Diff line number Diff line change
Expand Up @@ -588,6 +588,7 @@ func ListSecurity(connectionName string, rsType string) ([]*cres.SecurityInfo, e

// set VPC SystemId
var vpcIIDInfo VPCIIDInfo
var vpcLookupErr error
if os.Getenv("PERMISSION_BASED_CONTROL_MODE") != "" {
var iidInfoList []*VPCIIDInfo
err = getAuthIIDInfoList(connectionName, &iidInfoList)
Expand All @@ -597,16 +598,23 @@ func ListSecurity(connectionName string, rsType string) ([]*cres.SecurityInfo, e
}
castedIIDInfo, err := getAuthIIDInfo(&iidInfoList, iidInfo.OwnerVPCName)
if err != nil {
cblog.Error(err)
return nil, err
vpcLookupErr = err
} else {
vpcIIDInfo = *castedIIDInfo.(*VPCIIDInfo)
}
vpcIIDInfo = *castedIIDInfo.(*VPCIIDInfo)
} else {
err = infostore.GetByConditions(&vpcIIDInfo, CONNECTION_NAME_COLUMN, connectionName, NAME_ID_COLUMN, iidInfo.OwnerVPCName)
if err != nil {
cblog.Error(err)
return nil, err
vpcLookupErr = infostore.GetByConditions(&vpcIIDInfo, CONNECTION_NAME_COLUMN, connectionName, NAME_ID_COLUMN, iidInfo.OwnerVPCName)
}
if vpcLookupErr != nil {
cblog.Error(vpcLookupErr)
if !checkNotFoundError(vpcLookupErr) {
return nil, vpcLookupErr
}
// Owner VPC metadata is gone (e.g. the VPC was deleted without this SG
// being cleaned up first) - still list the orphaned SG so the user can
// see and delete it, instead of failing the whole listing.
infoList2 = append(infoList2, &info)
continue
}
info.VpcIID = getUserIID(cres.IID{NameId: vpcIIDInfo.NameId, SystemId: vpcIIDInfo.SystemId})

Expand Down
23 changes: 19 additions & 4 deletions api-runtime/rest-runtime/admin-web/AdminWeb-S3.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,12 @@ func S3Management(c echo.Context) error {
return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
}

buckets, err := fetchS3Buckets(connConfig)
providerName, err := getProviderName(connConfig)
if err != nil {
return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
}

buckets, err := fetchS3Buckets(connConfig, providerName)
var errorMessage string
if err != nil {
// Set error message but don't return error - show empty table with error message
Expand Down Expand Up @@ -102,7 +107,7 @@ func S3Management(c echo.Context) error {
return nil
}

func fetchS3Buckets(connConfig string) ([]S3BucketInfo, error) {
func fetchS3Buckets(connConfig string, providerName string) ([]S3BucketInfo, error) {
// Use new S3 API endpoint: /spider/s3
client := &http.Client{}
req, err := http.NewRequest("GET", spiderBaseURL()+"/spider/s3", nil)
Expand Down Expand Up @@ -187,8 +192,9 @@ func fetchS3Buckets(connConfig string) ([]S3BucketInfo, error) {
for _, bucket := range xmlResult.Buckets.Bucket {
// Check if bucket exists in CSP (creation date "0001-01-01" means metadata-only)
// For metadata-only buckets, skip CSP API calls to avoid errors and improve performance
isMetadataOnly := bucket.CreationDate == "0001-01-01T00:00:00Z" || bucket.CreationDate == "1-01-01 09:00:00 KST"
var versioningStatus, corsStatus string
if bucket.CreationDate == "0001-01-01T00:00:00Z" || bucket.CreationDate == "1-01-01 09:00:00 KST" {
if isMetadataOnly {
// Metadata-only bucket (not in CSP) - use default values without CSP API calls
versioningStatus = "Suspended"
corsStatus = "Not configured"
Expand All @@ -197,9 +203,18 @@ func fetchS3Buckets(connConfig string) ([]S3BucketInfo, error) {
versioningStatus = fetchVersioningStatus(connConfig, bucket.Name)
corsStatus = fetchCORSStatus(connConfig, bucket.Name)
}

creationDate := bucket.CreationDate
if providerName == "NHN" && !isMetadataOnly {
// NHN Object Storage is Swift-based and doesn't track a real container
// creation time; its S3-compatible gateway returns a fixed placeholder
// timestamp instead, so don't show it as if it were real.
creationDate = ""
}

result = append(result, S3BucketInfo{
Name: bucket.Name,
CreationDate: bucket.CreationDate,
CreationDate: creationDate,
BucketRegion: "", // Region info not available in standard S3 list buckets response
VersioningStatus: versioningStatus,
CORSStatus: corsStatus,
Expand Down
117 changes: 102 additions & 15 deletions api-runtime/rest-runtime/admin-web/html/dashboard.html
Original file line number Diff line number Diff line change
Expand Up @@ -107,12 +107,27 @@
width: 100%;
background-color: white;
z-index: 1000;
padding: 10px 0;
padding: 10px 8px;
box-sizing: border-box;
border-bottom: 1px solid #ddd;
}
.content {
margin-top: 100px;
}
.connection-status-banner {
display: none;
position: fixed;
top: 104px;
right: 8px;
z-index: 1500;
background-color: #f8d7da;
border: 1px solid #f5c2c7;
color: #842029;
padding: 8px 14px;
border-radius: 4px;
font-size: 13px;
box-shadow: 0 2px 6px rgba(0,0,0,0.15);
}
/* Input styles */
.refresh-input {
width: 50px;
Expand All @@ -129,6 +144,23 @@
display: none;
margin-left: 10px;
}
.fullscreen-btn {
border: 1px solid #9ec5fe;
background-color: #cfe2ff;
color: #0a58ca;
border-radius: 4px;
width: 26px;
height: 26px;
padding: 0;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
.fullscreen-btn:hover {
background-color: #b6d4fe;
}
.right-controls {
display: flex;
align-items: center;
Expand Down Expand Up @@ -169,12 +201,43 @@
};

var refreshIntervalId;
var lastSuccessfulRefresh = null;

function showConnectionBanner(message) {
var banner = document.getElementById('connectionStatusBanner');
banner.textContent = message;
banner.style.display = 'block';
}

function hideConnectionBanner() {
document.getElementById('connectionStatusBanner').style.display = 'none';
}

function refreshDashboard() {
fetch(window.location.href, {cache: "no-store"})
.then(response => response.text())
.then(html => {
.then(function(response) {
if (response.status === 401 || response.status === 403) {
throw new Error('AUTH');
}
if (!response.ok) {
throw new Error('SERVER_ERROR');
}
return response.text();
})
.then(function(html) {
document.querySelector('.content').innerHTML = new DOMParser().parseFromString(html, 'text/html').querySelector('.content').innerHTML;
lastSuccessfulRefresh = new Date();
hideConnectionBanner();
})
.catch(function(err) {
var asOf = lastSuccessfulRefresh ? ' (as of ' + lastSuccessfulRefresh.toLocaleTimeString() + ')' : '';
if (err.message === 'AUTH') {
showConnectionBanner('⚠ Session expired or unauthorized - please log in again.');
} else if (err.message === 'SERVER_ERROR') {
showConnectionBanner('⚠ Server error - showing last known data' + asOf + '.');
} else {
showConnectionBanner('⚠ Cannot reach the CB-Spider server - showing last known data' + asOf + '.');
}
});
}

Expand Down Expand Up @@ -264,7 +327,27 @@
}, '*');
}

var isAppFullscreen = false;
var FULLSCREEN_ENTER_ICON_PATH = 'M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z';
var FULLSCREEN_EXIT_ICON_PATH = 'M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z';

function toggleFullscreen() {
isAppFullscreen = !isAppFullscreen;
window.parent.postMessage({ type: 'toggleAppFullscreen' }, '*');

var btn = document.getElementById('fullscreenBtn');
var icon = document.getElementById('fullscreenIconPath');
if (isAppFullscreen) {
icon.setAttribute('d', FULLSCREEN_EXIT_ICON_PATH);
btn.title = 'Exit Full Screen';
} else {
icon.setAttribute('d', FULLSCREEN_ENTER_ICON_PATH);
btn.title = 'Full Screen';
}
}

document.addEventListener('DOMContentLoaded', function() {
lastSuccessfulRefresh = new Date();
toggleRefresh();
});
</script>
Expand All @@ -283,9 +366,13 @@ <h1>Multi-Cloud Dashboard</h1>
<div id="intervalContainer" class="interval-container">
<input type="number" id="refreshInterval" class="refresh-input" value="5" min="1" oninput="setRefresh()">
</div>
<button type="button" id="fullscreenBtn" class="fullscreen-btn" onclick="toggleFullscreen()" title="Full Screen">
<svg viewBox="0 0 24 24" width="24" height="24" fill="currentColor"><path id="fullscreenIconPath" d="M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z"/></svg>
</button>
</div>
</div>
</div>
<div id="connectionStatusBanner" class="connection-status-banner"></div>
<div class="content">
{{range $provider := .Providers}}
{{if or $.ShowEmpty (index $.ResourceCounts $provider)}}
Expand Down Expand Up @@ -313,19 +400,19 @@ <h2>{{$provider}}</h2>
<td><a href="javascript:void(0)" onclick="setTopMenu('{{$count.ConnectionName}}', '{{$provider}}', '{{index $.Regions $count.RegionName}}')">
{{$count.ConnectionName}}
</a></td>
<td class="{{if gt $count.VPCs 0}}highlight{{end}}"><a href="#" onclick="openOverlay('http://{{$.ServerIP}}/spider/adminweb/vpc/{{$count.ConnectionName}}'); return false;">{{$count.VPCs}}</a></td>
<td class="{{if gt $count.VPCs 0}}highlight{{end}}"><a href="#" onclick="openOverlay('/spider/adminweb/vpc/{{$count.ConnectionName}}'); return false;">{{$count.VPCs}}</a></td>
<td class="{{if gt $count.Subnets 0}}highlight{{end}}">{{$count.Subnets}}</td>
<td class="{{if gt $count.SecurityGroups 0}}highlight{{end}}"><a href="#" onclick="openOverlay('http://{{$.ServerIP}}/spider/adminweb/securitygroup/{{$count.ConnectionName}}'); return false;">{{$count.SecurityGroups}}</a></td>
<td class="{{if gt $count.KeyPairs 0}}highlight{{end}}"><a href="#" onclick="openOverlay('http://{{$.ServerIP}}/spider/adminweb/keypair/{{$count.ConnectionName}}'); return false;">{{$count.KeyPairs}}</a></td>
<td class="{{if gt $count.VMs 0}}highlight-vms{{end}}"><a href="#" onclick="openOverlay('http://{{$.ServerIP}}/spider/adminweb/vm/{{$count.ConnectionName}}'); return false;">{{$count.VMs}}</a></td>
<td class="{{if gt $count.NICs 0}}highlight{{end}}"><a href="#" onclick="openOverlay('http://{{$.ServerIP}}/spider/adminweb/nic/{{$count.ConnectionName}}'); return false;">{{$count.NICs}}</a></td>
<td class="{{if gt $count.PublicIPs 0}}highlight{{end}}"><a href="#" onclick="openOverlay('http://{{$.ServerIP}}/spider/adminweb/publicip/{{$count.ConnectionName}}'); return false;">{{$count.PublicIPs}}</a></td>
<td class="{{if gt $count.Disks 0}}highlight{{end}}"><a href="#" onclick="openOverlay('http://{{$.ServerIP}}/spider/adminweb/disk/{{$count.ConnectionName}}'); return false;">{{$count.Disks}}</a></td>
<td class="{{if gt $count.MyImages 0}}highlight{{end}}"><a href="#" onclick="openOverlay('http://{{$.ServerIP}}/spider/adminweb/myimage/{{$count.ConnectionName}}'); return false;">{{$count.MyImages}}</a></td>
<td class="{{if gt $count.NetworkLoadBalancers 0}}highlight{{end}}"><a href="#" onclick="openOverlay('http://{{$.ServerIP}}/spider/adminweb/nlb/{{$count.ConnectionName}}'); return false;">{{$count.NetworkLoadBalancers}}</a></td>
<td class="{{if gt $count.Clusters 0}}highlight-clusters{{end}}"><a href="#" onclick="openOverlay('http://{{$.ServerIP}}/spider/adminweb/cluster/{{$count.ConnectionName}}'); return false;">{{$count.Clusters}}</a></td>
<td class="{{if gt $count.S3Buckets 0}}highlight{{end}}"><a href="#" onclick="openOverlay('http://{{$.ServerIP}}/spider/adminweb/s3/{{$count.ConnectionName}}'); return false;">{{$count.S3Buckets}}</a></td>
<td class="{{if gt $count.RDBMSInstances 0}}highlight{{end}}"><a href="#" onclick="openOverlay('http://{{$.ServerIP}}/spider/adminweb/rdbms/{{$count.ConnectionName}}'); return false;">{{$count.RDBMSInstances}}</a></td>
<td class="{{if gt $count.SecurityGroups 0}}highlight{{end}}"><a href="#" onclick="openOverlay('/spider/adminweb/securitygroup/{{$count.ConnectionName}}'); return false;">{{$count.SecurityGroups}}</a></td>
<td class="{{if gt $count.KeyPairs 0}}highlight{{end}}"><a href="#" onclick="openOverlay('/spider/adminweb/keypair/{{$count.ConnectionName}}'); return false;">{{$count.KeyPairs}}</a></td>
<td class="{{if gt $count.VMs 0}}highlight-vms{{end}}"><a href="#" onclick="openOverlay('/spider/adminweb/vm/{{$count.ConnectionName}}'); return false;">{{$count.VMs}}</a></td>
<td class="{{if gt $count.NICs 0}}highlight{{end}}"><a href="#" onclick="openOverlay('/spider/adminweb/nic/{{$count.ConnectionName}}'); return false;">{{$count.NICs}}</a></td>
<td class="{{if gt $count.PublicIPs 0}}highlight{{end}}"><a href="#" onclick="openOverlay('/spider/adminweb/publicip/{{$count.ConnectionName}}'); return false;">{{$count.PublicIPs}}</a></td>
<td class="{{if gt $count.Disks 0}}highlight{{end}}"><a href="#" onclick="openOverlay('/spider/adminweb/disk/{{$count.ConnectionName}}'); return false;">{{$count.Disks}}</a></td>
<td class="{{if gt $count.MyImages 0}}highlight{{end}}"><a href="#" onclick="openOverlay('/spider/adminweb/myimage/{{$count.ConnectionName}}'); return false;">{{$count.MyImages}}</a></td>
<td class="{{if gt $count.NetworkLoadBalancers 0}}highlight{{end}}"><a href="#" onclick="openOverlay('/spider/adminweb/nlb/{{$count.ConnectionName}}'); return false;">{{$count.NetworkLoadBalancers}}</a></td>
<td class="{{if gt $count.Clusters 0}}highlight-clusters{{end}}"><a href="#" onclick="openOverlay('/spider/adminweb/cluster/{{$count.ConnectionName}}'); return false;">{{$count.Clusters}}</a></td>
<td class="{{if gt $count.S3Buckets 0}}highlight{{end}}"><a href="#" onclick="openOverlay('/spider/adminweb/s3/{{$count.ConnectionName}}'); return false;">{{$count.S3Buckets}}</a></td>
<td class="{{if gt $count.RDBMSInstances 0}}highlight{{end}}"><a href="#" onclick="openOverlay('/spider/adminweb/rdbms/{{$count.ConnectionName}}'); return false;">{{$count.RDBMSInstances}}</a></td>
</tr>
{{end}}
{{else}}
Expand Down
4 changes: 2 additions & 2 deletions api-runtime/rest-runtime/admin-web/html/left_menu.html
Original file line number Diff line number Diff line change
Expand Up @@ -218,11 +218,11 @@
</div>
<div class="menu-item" onclick="checkConnectionAndRedirect('nic', event)">
<img id="nic-img" src="./images/left-menu/disk.png" alt="NIC Icon" onerror="this.src='./images/left-menu/disk.png'">
<div class="menu-text">NIC</div>
<div class="menu-text">NIC (WIP)</div>
</div>
<div class="menu-item" onclick="checkConnectionAndRedirect('publicip', event)">
<img id="publicip-img" src="./images/left-menu/disk.png" alt="PublicIP Icon">
<div class="menu-text">PublicIP</div>
<div class="menu-text">PublicIP (WIP)</div>
</div>
<div class="menu-item" onclick="checkConnectionAndRedirect('disk', event)">
<img id="disk-img" src="./images/left-menu/disk.png" alt="Disk Icon">
Expand Down
13 changes: 13 additions & 0 deletions api-runtime/rest-runtime/admin-web/html/main.html
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,13 @@
height: 95%;
position: relative;
}
.container.app-fullscreen .left-menu,
.container.app-fullscreen .top-menu {
display: none;
}
.container.app-fullscreen .body-frame {
height: 100%;
}
iframe {
width: 100%;
height: 100%;
Expand Down Expand Up @@ -487,6 +494,12 @@
hideProgressBar();
}
});

window.addEventListener('message', function(event) {
if (event.data.type === 'toggleAppFullscreen') {
document.querySelector('.container').classList.toggle('app-fullscreen');
}
});
</script>
</head>
<body>
Expand Down
2 changes: 1 addition & 1 deletion api-runtime/rest-runtime/admin-web/html/s3.html
Original file line number Diff line number Diff line change
Expand Up @@ -3124,7 +3124,7 @@ <h2>In-Progress Uploads: <span id="inprogress-bucket-name"></span></h2>
});

function formatTimeSimple(originalTime) {
if (!originalTime) return '';
if (!originalTime) return 'NA';
const parts = originalTime.split(" ");
const date = parts[0];
const time = parts[1] ? parts[1].slice(0, 5) : '';
Expand Down
Loading