-
- Included AWS Services ({{ includedServices.length }})
-
+
+
+
Deployment mode
+
+
+
+ Deploys a CloudFormation stack in one AWS region. Parameters are pre-filled automatically.
+
+
+ Deploys across multiple regions using CloudFormation StackSets.
+ Requires AWS Organizations or self-managed IAM roles.
+ Parameters are shown for copy-paste into the AWS console wizard.
+
+
+
-
-
-
-
-
-
-
-
- {{ service.name }}
-
-
- {{ service.description }}
-
-
- {{ formatCategory(service.category) }}
-
-
-
-
-
-
+
+
-
Deployment Details
+
+
-
-
Estimated Setup Time:
-
5-10 minutes for stack creation
+
+
+
+
Admin region (where the StackSet is managed)
+
+
+
+
+
-
-
AWS Permissions Required:
-
CloudFormation, IAM, Kinesis Firehose, S3
-
-
-
-
What Gets Created:
-
- - Kinesis Firehose delivery streams for each service
- - IAM roles with appropriate permissions
- - S3 bucket for backup (optional)
- - CloudWatch log subscriptions
-
-
-
-
-
Cost Considerations:
-
- Charges apply for Kinesis Firehose data transfer, CloudWatch Logs ingestion, and OpenObserve storage.
- Refer to AWS and OpenObserve pricing for details.
+
+
-
+
+
+
+
+
Deployment model
+
+
+
+ Requires AWSCloudFormationStackSetAdministrationRole and
+ AWSCloudFormationStackSetExecutionRole IAM roles in your account.
+
+
+ Uses AWS Organizations. Your account must be the management or delegated admin account.
+
+
+
+
+
+
+
+
+
+ {{ deploymentMode === 'single' ? 'Launch CloudFormation Stack' : 'Open StackSets Console' }}
+
+
+ Select at least one service
+
+
+ Select at least one target region
+
+
+ {{ enabledServices.length }} service{{ enabledServices.length > 1 ? 's' : '' }} selected
+ · {{ targetRegions.length }} region{{ targetRegions.length > 1 ? 's' : '' }}
+
+
+
+
+
+
+
+
+
+
Parameters to enter in the AWS wizard
+
+
+
+ The StackSets console doesn't support URL pre-fill. Enter these values as you go through the wizard.
+
+
+
+
{{ param.key }}
+
+ {{ param.value }}
+
+ Copy
+
+
+
+
+
+
Target regions to enter in "Deployment targets":
+
+ {{ r }}
+
+
+
+
@@ -137,143 +276,170 @@ import { defineComponent, ref, computed } from "vue";
import { useStore } from "vuex";
import { useQuasar } from "quasar";
import { getEndPoint, getIngestionURL } from "@/utils/zincutils";
-import { generateCloudFormationURL, awsIntegrations } from "@/utils/awsIntegrations";
+import { generateCloudFormationURL, AWS_REGIONS, QUICK_SETUP_SERVICES } from "@/utils/awsIntegrations";
import segment from "@/services/segment_analytics";
+const COMPLETE_TEMPLATE_URL = 'https://openobserve-datasources-bucket.s3.us-east-2.amazonaws.com/datasource/cloud/aws/aws_complete.yaml';
+
export default defineComponent({
name: "AWSQuickSetup",
setup() {
const store = useStore();
const q = useQuasar();
- const showDetails = ref(false);
- // Get endpoint information during setup
+ const deploymentMode = ref<'single' | 'stackset'>('single');
+ const stackSetModel = ref<'self' | 'service'>('self');
+ const selectedRegion = ref('us-east-1');
+ const targetRegions = ref
(['us-east-1']);
+ const enabledServices = ref(QUICK_SETUP_SERVICES.map(s => s.flag));
+ const showParamHelper = ref(false);
+ const showTargetRegions = ref(false);
+ const showServices = ref(false);
+
let endpoint: any = null;
try {
- const ingestionURL = getIngestionURL();
- endpoint = getEndPoint(ingestionURL);
+ endpoint = getEndPoint(getIngestionURL());
} catch (e) {
- console.error("Error getting endpoint during setup:", e);
+ console.error("Error getting endpoint:", e);
}
- // Get all services with CloudFormation templates
- const includedServices = computed(() => {
- return awsIntegrations
- .filter(integration =>
- integration.cloudFormationTemplate ||
- (integration.cloudFormationTemplates && integration.cloudFormationTemplates.length > 0)
- )
- .map(integration => ({
- name: integration.displayName,
- description: integration.description,
- category: integration.category,
- }))
- .sort((a, b) => a.name.localeCompare(b.name));
+ const getCredentials = () => {
+ const organizationId = store.state?.selectedOrganization?.identifier;
+ const email = store.state?.userInfo?.email;
+ const passcode = store.state?.organizationData?.organizationPasscode;
+ return { organizationId, email, passcode };
+ };
+
+ const selectAll = () => { enabledServices.value = QUICK_SETUP_SERVICES.map(s => s.flag); };
+ const deselectAll = () => { enabledServices.value = []; };
+ const selectAllRegions = () => { targetRegions.value = AWS_REGIONS.map(r => r.value); };
+
+ const buildServiceFlags = () => {
+ const flags: Record = {};
+ QUICK_SETUP_SERVICES.forEach(({ flag }) => {
+ flags[flag] = enabledServices.value.includes(flag) ? 'true' : 'false';
+ });
+ return flags;
+ };
+
+ // Parameters the user will need to copy-paste in the StackSets wizard
+ const stackSetParams = computed(() => {
+ const { organizationId, email, passcode } = getCredentials();
+ if (!organizationId || !email || !passcode || !endpoint?.url) return [];
+
+ const accessKey = btoa(`${email}:${passcode}`);
+ const endpointUrl = `${endpoint.url}/aws/${organizationId}/default/_kinesis_firehose`;
+ const serviceFlags = buildServiceFlags();
+
+ const params = [
+ { key: 'Amazon S3 URL (template)', value: COMPLETE_TEMPLATE_URL },
+ { key: 'TemplateS3Bucket', value: 'openobserve-datasources-bucket' },
+ { key: 'TemplateS3Prefix', value: 'datasource/cloud/aws' },
+ { key: 'OpenObserveEndpoint', value: endpointUrl },
+ { key: 'OpenObserveAccessKey', value: accessKey },
+ ...QUICK_SETUP_SERVICES.map(({ flag }) => ({ key: flag, value: serviceFlags[flag] })),
+ ];
+ return params;
});
- const getCategoryIcon = (category: string) => {
- const iconMap: Record = {
- 'logs': 'description',
- 'metrics': 'speed',
- 'security': 'security',
- 'networking': 'network_check',
- 'other': 'settings',
- };
- return iconMap[category] || 'cloud';
+ const copyParam = (value: string) => {
+ navigator.clipboard.writeText(value);
+ q.notify({ type: 'positive', message: 'Copied to clipboard', timeout: 1500 });
};
- const formatCategory = (category: string) => {
- return category.charAt(0).toUpperCase() + category.slice(1);
- };
+ const handleLaunch = () => {
+ if (!endpoint?.url) {
+ q.notify({ type: "negative", message: "Invalid ingestion endpoint. Please check configuration.", timeout: 3000 });
+ return;
+ }
- const handleDeployStack = () => {
- try {
- // Validate endpoint
- if (!endpoint?.url) {
- console.error("Invalid endpoint:", endpoint);
- q.notify({
- type: "negative",
- message: "Invalid ingestion endpoint. Please check configuration.",
- timeout: 3000,
- });
+ const { organizationId, email, passcode } = getCredentials();
+ if (!organizationId || !email || !passcode) {
+ q.notify({ type: "negative", message: "Missing organization credentials. Please refresh the page.", timeout: 3000 });
+ return;
+ }
+
+ if (deploymentMode.value === 'single') {
+ launchSingleRegion(organizationId, email, passcode);
+ } else {
+ if (targetRegions.value.length === 0) {
+ q.notify({ type: "warning", message: "Select at least one target region.", timeout: 3000 });
return;
}
-
- // Get organization details
- const organizationId = store.state?.selectedOrganization?.identifier;
- const email = store.state?.userInfo?.email;
- const passcode = store.state?.organizationData?.organizationPasscode;
-
- // Validate required data
- if (!organizationId || !email || !passcode) {
- console.error("Missing required data:", { organizationId, email, hasPasscode: !!passcode });
- q.notify({
- type: "negative",
- message: "Missing organization credentials. Please refresh the page.",
- timeout: 3000,
- });
- return;
- }
-
- // Generate base64 encoded access key
- const accessKey = btoa(`${email}:${passcode}`);
-
- // Create a unified integration object
- const unifiedIntegration = {
- id: 'aws-complete',
- name: 'AWS-Complete',
- displayName: 'AWS Complete Integration',
- cloudFormationTemplate: 'https://openobserve-datasources-bucket.s3.us-east-2.amazonaws.com/datasource/cloud/aws/aws_complete.yaml',
- comingSoon: false,
- };
-
- // Generate CloudFormation URL
- const cloudFormationURL = generateCloudFormationURL(
- unifiedIntegration as any,
- organizationId,
- `${endpoint.url}/aws/${organizationId}/default/_kinesis_firehose`,
- accessKey
- );
-
- if (!cloudFormationURL) {
- q.notify({
- type: "warning",
- message: "CloudFormation template not available yet",
- timeout: 3000,
- });
- return;
- }
-
- // Open AWS Console in new tab
- window.open(cloudFormationURL, '_blank', 'noopener,noreferrer');
-
- // Track analytics
- segment.track("AWS Complete Integration Started", {
- integration_type: "complete",
- services_count: includedServices.value.length,
- });
-
- q.notify({
- type: "info",
- message: "Opening AWS Console to deploy complete integration stack",
- timeout: 3000,
- });
- } catch (error) {
- console.error("Error deploying stack:", error);
- q.notify({
- type: "negative",
- message: `Error opening AWS Console: ${error instanceof Error ? error.message : 'Unknown error'}`,
- timeout: 5000,
- });
+ launchStackSet(organizationId);
}
};
+ const launchSingleRegion = (organizationId: string, email: string, passcode: string) => {
+ const accessKey = btoa(`${email}:${passcode}`);
+
+ const url = generateCloudFormationURL(
+ {
+ id: 'aws-complete',
+ name: 'AWS-Complete',
+ displayName: 'AWS Complete Integration',
+ cloudFormationTemplate: COMPLETE_TEMPLATE_URL,
+ comingSoon: false,
+ } as any,
+ organizationId,
+ `${endpoint.url}/aws/${organizationId}/default/_kinesis_firehose`,
+ accessKey,
+ selectedRegion.value,
+ {
+ TemplateS3Bucket: 'openobserve-datasources-bucket',
+ TemplateS3Prefix: 'datasource/cloud/aws',
+ ...buildServiceFlags(),
+ }
+ );
+
+ if (!url) {
+ q.notify({ type: "warning", message: "CloudFormation template not available yet", timeout: 3000 });
+ return;
+ }
+
+ window.open(url, '_blank', 'noopener,noreferrer');
+ segment.track("AWS Complete Integration Started", { mode: 'single', region: selectedRegion.value, services: enabledServices.value });
+ q.notify({ type: "info", message: "Opening AWS Console to deploy complete integration stack", timeout: 3000 });
+ };
+
+ const launchStackSet = (organizationId: string) => {
+ // StackSets console doesn't support URL pre-fill — open the console and show param helper
+ const consoleUrl = `https://console.aws.amazon.com/cloudformation/home?region=${selectedRegion.value}#/stacksets/create`;
+ window.open(consoleUrl, '_blank', 'noopener,noreferrer');
+ showParamHelper.value = true;
+
+ segment.track("AWS StackSet Integration Started", {
+ mode: 'stackset',
+ model: stackSetModel.value,
+ admin_region: selectedRegion.value,
+ target_regions: targetRegions.value,
+ services: enabledServices.value,
+ });
+
+ q.notify({
+ type: "info",
+ message: "AWS StackSets console opened. Use the parameter values below to complete setup.",
+ timeout: 5000,
+ });
+ };
+
return {
- showDetails,
- includedServices,
- handleDeployStack,
- getCategoryIcon,
- formatCategory,
+ deploymentMode,
+ stackSetModel,
+ selectedRegion,
+ targetRegions,
+ enabledServices,
+ showParamHelper,
+ showTargetRegions,
+ showServices,
+ stackSetParams,
+ AWS_REGIONS,
+ QUICK_SETUP_SERVICES,
+ selectAll,
+ deselectAll,
+ selectAllRegions,
+ copyParam,
+ handleLaunch,
};
},
});
@@ -286,96 +452,75 @@ export default defineComponent({
margin: 0 auto;
}
- .service-item {
- transition: all 0.2s ease;
+ .step-label {
+ font-weight: 600;
+ font-size: 0.9rem;
+ }
+
+ .param-helper {
+ border-radius: 8px;
+ padding: 16px;
+ }
+
+ .param-table {
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+ }
+
+ .param-row {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ padding: 6px 10px;
+ border-radius: 4px;
+ font-size: 0.8rem;
+ font-family: monospace;
+ }
+
+ .param-key {
+ min-width: 240px;
+ font-weight: 600;
+ flex-shrink: 0;
+ }
+
+ .param-value {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ flex: 1;
+ overflow: hidden;
+ }
+
+ .param-val-text {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ flex: 1;
}
.body--light & {
- .title {
- color: #1a1a1a;
- }
-
- .description,
- .benefit-text,
- .detail-value {
- color: #666;
- }
-
- .service-name {
- color: #1a1a1a;
- }
-
- .service-description {
- color: #666;
- }
-
- .service-category {
- color: #999;
- font-weight: 500;
- }
-
- .service-item {
- border: 1px solid #e0e0e0;
- background-color: #fafafa;
-
- &:hover {
- background-color: #f5f5f5;
- }
- }
-
- .section-header,
- .detail-label,
- .details-title {
- color: #333;
- }
-
- .notice-banner {
- background-color: #e3f2fd;
- }
+ .title { color: #1a1a1a; }
+ .description, .detail-value, .mode-hint { color: #666; }
+ .step-label { color: #333; }
+ .region-hint { color: #888; }
+ .param-helper { background: #f5f5f5; }
+ .param-row { background: #fff; border: 1px solid #e0e0e0; }
+ .param-key { color: #333; }
+ .param-val-text { color: #555; }
+ .region-collapsible-header { background: #f0f4ff; border: 1px solid #d0d9f0; &:hover { background: #e8eeff; } }
}
.body--dark & {
- .title {
- color: #e0e0e0;
- }
-
- .description,
- .benefit-text,
- .detail-value {
- color: #b0b0b0;
- }
-
- .service-name {
- color: #e0e0e0;
- }
-
- .service-description {
- color: #b0b0b0;
- }
-
- .service-category {
- color: #808080;
- font-weight: 500;
- }
-
- .service-item {
- border: 1px solid #404040;
- background-color: rgba(255, 255, 255, 0.05);
-
- &:hover {
- background-color: rgba(255, 255, 255, 0.08);
- }
- }
-
- .section-header,
- .detail-label,
- .details-title {
- color: #d0d0d0;
- }
-
- .notice-banner {
- background-color: rgba(33, 150, 243, 0.1);
- }
+ .title { color: #e0e0e0; }
+ .description, .detail-value, .mode-hint { color: #b0b0b0; }
+ .step-label { color: #d0d0d0; }
+ .region-hint { color: #888; }
+ .param-helper { background: rgba(255,255,255,0.05); }
+ .param-row { background: rgba(255,255,255,0.03); border: 1px solid #404040; }
+ .param-key { color: #ccc; }
+ .param-val-text { color: #aaa; }
+ .region-collapsible-header { background: rgba(255,255,255,0.06); border: 1px solid #404040; &:hover { background: rgba(255,255,255,0.09); } }
}
}
diff --git a/web/src/components/ingestion/recommended/AzureConfig.spec.ts b/web/src/components/ingestion/recommended/AzureConfig.spec.ts
index 42a273095d..fb644ac976 100644
--- a/web/src/components/ingestion/recommended/AzureConfig.spec.ts
+++ b/web/src/components/ingestion/recommended/AzureConfig.spec.ts
@@ -1,6 +1,6 @@
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { mount, VueWrapper } from '@vue/test-utils';
-import { Quasar } from 'quasar';
+import { Quasar, Notify } from 'quasar';
import { createStore } from 'vuex';
import { createI18n } from 'vue-i18n';
import { createRouter, createWebHistory } from 'vue-router';
@@ -14,37 +14,30 @@ vi.mock('../../../utils/zincutils', () => ({
getImageURL: vi.fn((path) => `mock-image-url-${path}`),
getEndPoint: vi.fn(() => ({ url: 'http://localhost:5080', host: 'localhost', port: '5080', protocol: 'http', tls: false })),
getIngestionURL: vi.fn(() => 'http://localhost:5080'),
+ b64EncodeStandard: vi.fn((str) => btoa(str)),
+ maskText: vi.fn((str) => '***'),
}));
vi.mock('../../../utils/azureIntegrations', () => ({
azureIntegrations: [
- { id: 'azure-ad', name: 'AzureAD', displayName: 'Azure Active Directory', description: 'Azure AD logs', category: 'security', hasDashboard: false },
- { id: 'azure-storage', name: 'AzureStorage', displayName: 'Azure Storage', description: 'Azure Storage logs', category: 'storage', hasDashboard: true },
+ {
+ id: 'activity-logs',
+ name: 'Activity Logs',
+ displayName: 'Azure Activity Logs',
+ description: 'Stream Azure subscription activity logs',
+ armTemplate: 'https://mock-s3-bucket.s3.amazonaws.com/activity-logs.json',
+ hasDashboard: false,
+ category: 'logs',
+ },
],
+ generateARMTemplateURL: vi.fn(() => 'https://portal.azure.com/#create/Microsoft.Template/uri/mock'),
generateAzureDashboardURL: vi.fn(() => 'https://mock-dashboard-url'),
}));
-vi.mock('@/services/dashboards', () => ({
- default: {
- list_Folders: vi.fn(() => Promise.resolve({ data: { list: [] } })),
- new_Folder: vi.fn(() => Promise.resolve({ data: { folderId: 'folder-1', name: 'Microsoft' } })),
- list: vi.fn(() => Promise.resolve({ data: { dashboards: [] } })),
- create: vi.fn(() => Promise.resolve({ data: {} })),
- delete: vi.fn(() => Promise.resolve({ data: {} })),
- },
-}));
-
vi.mock('@/services/segment_analytics', () => ({
default: { track: vi.fn() },
}));
-vi.mock('./AzureQuickSetup.vue', () => ({
- default: {
- name: 'AzureQuickSetup',
- template: 'Azure Quick Setup
',
- },
-}));
-
const mockStore = createStore({
state: {
selectedOrganization: { identifier: 'test-org', name: 'Test Organization' },
@@ -64,20 +57,21 @@ describe('AzureConfig.vue', () => {
beforeEach(() => {
vi.clearAllMocks();
+ vi.stubGlobal('open', vi.fn());
});
afterEach(() => {
if (wrapper) wrapper.unmount();
+ vi.unstubAllGlobals();
});
- const createWrapper = () => {
- return mount(AzureConfig, {
+ const createWrapper = () =>
+ mount(AzureConfig, {
global: {
- plugins: [Quasar, mockI18n, mockRouter],
+ plugins: [[Quasar, { plugins: { Notify } }], mockI18n, mockRouter],
provide: { store: mockStore },
},
});
- };
describe('Component Rendering', () => {
it('should mount without errors', () => {
@@ -90,16 +84,20 @@ describe('AzureConfig.vue', () => {
expect(() => wrapper.unmount()).not.toThrow();
});
- it('should render AzureQuickSetup child component', () => {
- wrapper = createWrapper();
- const child = wrapper.find('[data-test="azure-quick-setup"]');
- expect(child.exists()).toBe(true);
- });
-
it('should have azure-config-page class', () => {
wrapper = createWrapper();
expect(wrapper.find('.azure-config-page').exists()).toBe(true);
});
+
+ it('should render Azure Activity Logs title', () => {
+ wrapper = createWrapper();
+ expect(wrapper.text()).toContain('Azure Activity Logs');
+ });
+
+ it('should render deploy button', () => {
+ wrapper = createWrapper();
+ expect(wrapper.find('[data-test="azure-activity-logs-deploy-btn"]').exists()).toBe(true);
+ });
});
describe('Component Identity', () => {
@@ -114,4 +112,39 @@ describe('AzureConfig.vue', () => {
expect(typeof wrapper.vm).toBe('object');
});
});
+
+ describe('Step 2 Mode Toggle', () => {
+ it('should default to portal mode', () => {
+ wrapper = createWrapper();
+ expect((wrapper.vm as any).step2Mode).toBe('portal');
+ });
+
+ it('should switch to cli mode', async () => {
+ wrapper = createWrapper();
+ (wrapper.vm as any).step2Mode = 'cli';
+ await wrapper.vm.$nextTick();
+ expect((wrapper.vm as any).step2Mode).toBe('cli');
+ });
+ });
+
+ describe('Category Selection', () => {
+ it('should start with all categories enabled', () => {
+ wrapper = createWrapper();
+ expect((wrapper.vm as any).enabledCategories.length).toBe(8);
+ });
+
+ it('should clear categories', () => {
+ wrapper = createWrapper();
+ (wrapper.vm as any).enabledCategories = [];
+ expect((wrapper.vm as any).enabledCategories.length).toBe(0);
+ });
+ });
+
+ describe('handleDeploy', () => {
+ it('should call window.open when credentials are valid', () => {
+ wrapper = createWrapper();
+ (wrapper.vm as any).handleDeploy();
+ expect(window.open).toHaveBeenCalled();
+ });
+ });
});
diff --git a/web/src/components/ingestion/recommended/AzureConfig.vue b/web/src/components/ingestion/recommended/AzureConfig.vue
index 5a20206849..d13cec7120 100644
--- a/web/src/components/ingestion/recommended/AzureConfig.vue
+++ b/web/src/components/ingestion/recommended/AzureConfig.vue
@@ -16,34 +16,294 @@ along with this program. If not, see .
-
+
+
+
+
+
+
Azure Activity Logs
+
+ Stream Azure subscription activity logs to OpenObserve via Event Hub.
+ The ARM template sets up the Event Hub infrastructure — you then configure Azure to export logs to it.
+
+
+
+
+
+
+
+
1
+
+
Deploy ARM Template
+
+ Creates an Event Hub namespace, Event Hub, and all required resources in your Azure subscription.
+
+
+
+ Deploy to Azure
+
+
+
+
+
+
+
+
+
2
+
+
Configure Diagnostic Settings
+
+ After the ARM deployment completes, route Activity Logs to the Event Hub that was created.
+
+
+
+
+
+
+
+
+ - Go to Azure Portal → Subscriptions → your subscription
+ - Click Activity log in the left menu
+ - Click Export Activity Logs (or Diagnostic settings → + Add diagnostic setting)
+ - Enter a name, check the log categories you want to enable
+ - Under Destination details, choose Stream to an event hub
+ - Select the Event Hub namespace and Event Hub created in Step 1 (prefix:
o2-activity)
+ - Click Save
+
+
+
+
+
+
+
+
+
Log categories to enable
+
+ c.value)" />
+
+
+
+
+
+
+
+
+
+
+
+ Select at least one log category above.
+
+
+
Run this command after your ARM deployment completes:
+
+
+
+
+
+
+
+
+
+
Manual Configuration (for reference)
+
+
+
diff --git a/web/src/components/ingestion/recommended/AzureIndividualServices.vue b/web/src/components/ingestion/recommended/AzureIndividualServices.vue
index a8912f034b..c5dc3129c7 100644
--- a/web/src/components/ingestion/recommended/AzureIndividualServices.vue
+++ b/web/src/components/ingestion/recommended/AzureIndividualServices.vue
@@ -43,6 +43,7 @@ along with this program. If not, see .
data-test="azure-integration-category-tabs"
>
+
diff --git a/web/src/components/ingestion/recommended/AzureIntegrationTile.vue b/web/src/components/ingestion/recommended/AzureIntegrationTile.vue
index 38f750bb75..4ed5b4433b 100644
--- a/web/src/components/ingestion/recommended/AzureIntegrationTile.vue
+++ b/web/src/components/ingestion/recommended/AzureIntegrationTile.vue
@@ -46,9 +46,19 @@ along with this program. If not, see .
-
+
+
+ .
class="tw:flex-1"
:data-test="`azure-${integration.id}-documentation-btn`"
/>
+
+
+ View Documentation
+
{
+ if (!props.integration.armTemplate) return;
+
+ if (!endpoint?.url) {
+ q.notify({ type: "negative", message: "Invalid ingestion endpoint. Please check configuration.", timeout: 3000 });
+ return;
+ }
+
+ const organizationId = store.state?.selectedOrganization?.identifier;
+ const email = store.state?.userInfo?.email;
+ const passcode = store.state?.organizationData?.organizationPasscode;
+
+ if (!organizationId || !email || !passcode) {
+ q.notify({ type: "negative", message: "Missing organization credentials. Please refresh the page.", timeout: 3000 });
+ return;
+ }
+
+ const accessKey = btoa(`${email}:${passcode}`);
+ const endpointUrl = `${endpoint.url}/azure/${organizationId}/default/_event_hub`;
+
+ const url = generateARMTemplateURL(props.integration, endpointUrl, accessKey);
+
+ window.open(url, '_blank', 'noopener,noreferrer');
+
+ segment.track("Azure ARM Template Deploy Started", {
+ service: props.integration.name,
+ integration_id: props.integration.id,
+ });
+
+ q.notify({ type: "info", message: `Opening Azure portal to deploy ${props.integration.displayName}`, timeout: 3000 });
+ };
+
const handleDashboard = () => {
if (!props.integration.hasDashboard) {
return;
@@ -146,6 +210,7 @@ export default defineComponent({
};
return {
+ handleDeploy,
handleDashboard,
handleDocumentation,
};
diff --git a/web/src/utils/awsIntegrations.ts b/web/src/utils/awsIntegrations.ts
index 2f5b62951c..462b73e87b 100644
--- a/web/src/utils/awsIntegrations.ts
+++ b/web/src/utils/awsIntegrations.ts
@@ -345,6 +345,59 @@ export const awsIntegrations: AWSIntegration[] = [
},
];
+export const AWS_REGIONS = [
+ { value: 'us-east-1', label: 'US East (N. Virginia)' },
+ { value: 'us-east-2', label: 'US East (Ohio)' },
+ { value: 'us-west-1', label: 'US West (N. California)' },
+ { value: 'us-west-2', label: 'US West (Oregon)' },
+ { value: 'ca-central-1', label: 'Canada (Central)' },
+ { value: 'ca-west-1', label: 'Canada West (Calgary)' },
+ { value: 'eu-west-1', label: 'Europe (Ireland)' },
+ { value: 'eu-west-2', label: 'Europe (London)' },
+ { value: 'eu-west-3', label: 'Europe (Paris)' },
+ { value: 'eu-central-1', label: 'Europe (Frankfurt)' },
+ { value: 'eu-central-2', label: 'Europe (Zurich)' },
+ { value: 'eu-north-1', label: 'Europe (Stockholm)' },
+ { value: 'eu-south-1', label: 'Europe (Milan)' },
+ { value: 'eu-south-2', label: 'Europe (Spain)' },
+ { value: 'ap-east-1', label: 'Asia Pacific (Hong Kong)' },
+ { value: 'ap-south-1', label: 'Asia Pacific (Mumbai)' },
+ { value: 'ap-south-2', label: 'Asia Pacific (Hyderabad)' },
+ { value: 'ap-southeast-1', label: 'Asia Pacific (Singapore)' },
+ { value: 'ap-southeast-2', label: 'Asia Pacific (Sydney)' },
+ { value: 'ap-southeast-3', label: 'Asia Pacific (Jakarta)' },
+ { value: 'ap-southeast-4', label: 'Asia Pacific (Melbourne)' },
+ { value: 'ap-northeast-1', label: 'Asia Pacific (Tokyo)' },
+ { value: 'ap-northeast-2', label: 'Asia Pacific (Seoul)' },
+ { value: 'ap-northeast-3', label: 'Asia Pacific (Osaka)' },
+ { value: 'sa-east-1', label: 'South America (São Paulo)' },
+ { value: 'me-south-1', label: 'Middle East (Bahrain)' },
+ { value: 'me-central-1', label: 'Middle East (UAE)' },
+ { value: 'il-central-1', label: 'Israel (Tel Aviv)' },
+ { value: 'af-south-1', label: 'Africa (Cape Town)' },
+ { value: 'us-gov-east-1', label: 'AWS GovCloud (US-East)' },
+ { value: 'us-gov-west-1', label: 'AWS GovCloud (US-West)' },
+];
+
+export const QUICK_SETUP_SERVICES: { label: string; flag: string }[] = [
+ { label: 'CloudTrail', flag: 'EnableCloudTrail' },
+ { label: 'CloudWatch Logs', flag: 'EnableCloudWatchLogs' },
+ { label: 'CloudWatch Metrics', flag: 'EnableCloudWatchMetrics' },
+ { label: 'VPC Flow Logs', flag: 'EnableVPCFlowLogs' },
+ { label: 'WAF', flag: 'EnableWAF' },
+ { label: 'ALB', flag: 'EnableALB' },
+ { label: 'API Gateway', flag: 'EnableApiGateway' },
+ { label: 'RDS', flag: 'EnableRDS' },
+ { label: 'S3 Access Logs', flag: 'EnableS3AccessLogs' },
+ { label: 'CloudFront', flag: 'EnableCloudFront' },
+ { label: 'Route53', flag: 'EnableRoute53' },
+ { label: 'DynamoDB', flag: 'EnableDynamoDB' },
+ { label: 'EC2', flag: 'EnableEC2' },
+ { label: 'EventBridge', flag: 'EnableEventBridge' },
+ { label: 'Kinesis', flag: 'EnableKinesisStream' },
+ { label: 'Cognito', flag: 'EnableCognito' },
+];
+
/**
* Generate AWS CloudFormation console URL with pre-filled parameters
*/
@@ -353,12 +406,13 @@ export const generateCloudFormationURL = (
organizationId: string,
endpoint: string,
accessKey: string,
+ region: string = 'us-east-1',
+ extraParams: Record = {},
): string => {
if (!integration.cloudFormationTemplate || integration.comingSoon) {
return '';
}
- const region = 'us-east-1'; // Default region, can be made configurable
const stackName = `o2-${integration.name.replace(/\s+/g, '-')}`;
// Encode parameters for URL
@@ -372,6 +426,7 @@ export const generateCloudFormationURL = (
param_OrganizationId: organizationId,
param_HttpEndpointUrl: endpoint,
param_AccessKey: accessKey,
+ ...Object.fromEntries(Object.entries(extraParams).map(([k, v]) => [`param_${k}`, v])),
});
return `https://console.aws.amazon.com/cloudformation/home?region=${region}#/stacks/create/review?${params.toString()}`;
diff --git a/web/src/utils/azureIntegrations.ts b/web/src/utils/azureIntegrations.ts
index c6baa22d7a..014c9e2111 100644
--- a/web/src/utils/azureIntegrations.ts
+++ b/web/src/utils/azureIntegrations.ts
@@ -19,6 +19,7 @@ export interface AzureIntegration {
displayName: string;
icon: string;
description: string;
+ armTemplate?: string; // HTTPS URL to ARM template JSON on S3
hasDashboard: boolean;
dashboardFolderId?: string;
dashboardGithubUrl?: string;
@@ -27,6 +28,17 @@ export interface AzureIntegration {
}
export const azureIntegrations: AzureIntegration[] = [
+ {
+ id: 'activity-logs',
+ name: 'Activity Logs',
+ displayName: 'Azure Activity Logs',
+ icon: '',
+ description: 'Stream Azure subscription activity logs to OpenObserve via Event Hub and Event Publisher',
+ armTemplate: 'https://openobserve-datasources-bucket.s3.us-east-2.amazonaws.com/datasource/cloud/azure/activity-logs-to-openobserve.json',
+ hasDashboard: false,
+ documentationUrl: 'https://learn.microsoft.com/en-us/azure/azure-monitor/essentials/activity-log',
+ category: 'logs',
+ },
{
id: 'aks',
name: 'AKS',
@@ -189,6 +201,27 @@ export const azureIntegrations: AzureIntegration[] = [
},
];
+/**
+ * Generate Azure portal custom deployment URL with pre-filled parameters.
+ * Uses the ARM template quick-deploy format:
+ * https://portal.azure.com/#create/Microsoft.Template/uri/{encoded-template-url}/parameters/{encoded-params}
+ */
+export const generateARMTemplateURL = (
+ integration: AzureIntegration,
+ endpoint: string,
+ accessKey: string,
+): string => {
+ if (!integration.armTemplate) return '';
+
+ const templateUri = encodeURIComponent(integration.armTemplate);
+ const parameters = encodeURIComponent(JSON.stringify({
+ openObserveEndpoint: { value: endpoint },
+ openObserveAccessKey: { value: accessKey },
+ }));
+
+ return `https://portal.azure.com/#create/Microsoft.Template/uri/${templateUri}/parameters/${parameters}`;
+};
+
/**
* Generate dashboard URL for the current environment
*/